channel-publish
Publish content to the user's bound Channel Accounts, schedule or change Publish At, cancel an unfinished Publish Task, or update already published content. Use when the user wants to post, schedule, cancel, or update social content on a connected platform.
Try your first task
A source-grounded starter task has not been verified for this skill yet.
SKILL.md
name: channel-publishdescription: Publish content to the user's bound Channel Accounts, schedule or change Publish At, cancel an unfinished Publish Task, or update already published content. Use when the user wants to post, schedule, cancel, or update social content on a connected platform.requiredTools: - list_channel_accounts - list_channel_platforms - get_channel_platform - get_channel_publish_recordChannel Publish
Publish through Channel Accounts. Limits come from get_channel_platform, not from this file.
Environment
Some domestic platforms are not available on this edition — treat list_channel_platforms as the source of truth and never promise an unlisted platform. When the user asks to publish to a platform that is not listed, say it is not supported here and guide them to the domestic edition.
Tools
list_channel_accounts/get_channel_account: pickaccountIdand platform. Accountstatus:1usable,0offline (authorization lost) — onlystatus: 1accounts can publish. Not required for plugin publish (xhs / wxSph, see Workflow step 1).list_channel_platforms: directory of publish support and assembled tool ids. ForauthType==='plugin'platforms (xhs / wxSph)assembledToolIdsis empty —create_rednote/wechat_channels_publish_flowdoes not exist and is not searchable viatoolSearch.get_channel_platform: required before create. Follow the returned text for title, body, media count/format/duration, cover, topics, and publish mode. Forxhs / wxSphit returns “不支持服务端发布,请使用浏览器插件发布(guide_{slug}_plugin_publish)”.list_channel_publish_records/get_channel_publish_record: inspect flows and tasks. UseflowId/taskIdto getChannelPublishRecord.id(recordId)when you need arecordId.guide_user_action: channel auth guidance only. Input is{ kind:'channel_auth', platform }. Guide signal with empty output (z.object({})); frontend renders from the input. Do not add extra output fields. Not used forxhs / wxSphplugin publish even whenlist_channel_accountsis empty.guide_{slug}_plugin_publish: browser plugin publish guidance — the ONLY publish method forauthType==='plugin'platforms. Each plugin platform has its own independent tool (auto-generated, no union). No bound account required;accountIdsis optional.guide_rednote_plugin_publish(小红书xhs): inputtitle?/body?/mediaUrls?/coverUrl?/publishAt?/accountIds?+originalStatement?: boolean(声明原创) +userDeclarationBind?: '1'|'2'|'3'(1 虚拟演绎仅供娱乐 2 笔记含 AI 合成内容 3 内容包含营销广告)guide_wechat_channels_plugin_publish(微信视频号wxSph): inputtitle?/body?/mediaUrls?/coverUrl?/publishAt?/accountIds?+postFlag?: '0'|'1'(声明原创,1 勾选 0 不勾选) Each tool has noplatformdiscriminator and no union — base content fields plus platform-specific options only. Output is an empty guide signal (z.object({})); frontend renders from the input.
extract_video_frame: extract a single frame from a video (videoUrl, optionaltimeInSeconds, default 2s) and upload it as a cover image. Returns{ coverUrl }. Use it before plugin publish when the platform requires a cover for video content (e.g. xhs video notes,coverRequired); you may pass the returnedcoverUrltogenerate_imageas a reference to design a styled cover first.probe_video_metadata/probe_audio_metadata/probe_image_metadata(auxiliary): probe a media URL's duration / resolution / size to pre-check material against theget_channel_platformlimits before creating a publish flow — fewersuccess:falsevalidation round-trips. Each probe is billed per call; video/audio probes are async, so wait for the result and do not probe the same URL twice.get_channel_publish_user_action: DouyinWaitingForUserActiononly. Input isrecordIdonly — resolve it viaget_channel_publish_record { flowId | taskId }first. Output containsshareId/schemeUrl/shortLink/expiresAt/qrImageUrl(httpUrl);qrImageUrlis an httpUrl produced by agent-sideqrcode+AssetsService.uploadFromBuffer, frontend renders it. Do NOT use rawqrText.publish_channel_task_now/update_channel_publish_at/cancel_channel_publish_task/request_channel_publish_update: task commands. Search them when needed.
Do not invent platform limits. Do not dump option schema into the user reply.
Publish modes
- Platforms with publish modes (e.g. Facebook, Instagram) expose one create tool per mode:
create_{slug}_{mode}_publish_flow(e.g.create_facebook_post_publish_flow/create_facebook_reel_publish_flow/create_facebook_story_publish_flow). The mode option field (e.g.content_category) is filled by the tool automatically — never pass it yourself. - Pick the tool that matches the media type: video content → the video/reel mode tool; image or text content → the post/image mode tool.
get_channel_platformlists each mode's limits and create tool id under## 发布模式/## 工具. - If a create tool fails with a validation error indicating the media type does not match the publish mode, retry with the matching publish mode's create tool (corrected-parameter retries are fine; unchanged retries are not).
AI content label
When publishing AI-generated media, default to declaring it via the platform's AI label option; omit it only when the user explicitly says not to label it. It is an optional option field on the create tool: is_ai_generated (Facebook, reel mode only), is_aigc (TikTok), made_with_ai (Twitter), containsSyntheticMedia (YouTube). Platforms or modes without such a field simply do not send one.
Topics and body
create_*_publish_flowtools have notopicsfield. Topics are carried only as#hashtaginsidebody. Server extracts them via/#([\w\p{Script=Han}]+)/gu(parseTopicsFromBody) and fornativeField:trueplatforms strips them from body (stripTopicsFromBody) into the native topic field.- When
get_channel_platformshows## 话题as “支持话题”: if the user gave topics (list or “带上 #AI”), normalize each to#+ trimmed non-empty tag, dedupe, and insert intobodyas#hashtag(e.g.正文 #AI #旅行or inline within the body). If no topics were given, do not invent any. RespectmaxCount/maxTotalLength— truncate or ask the user to choose.request_channel_publish_updatefollows the same rule. - When
## 话题says “不要带话题”: do not insert hashtags. If the user insists, warn and strip them. guide_{slug}_plugin_publishfollows the same base content contract as publish (title/body/mediaUrls/coverUrl/publishAt/accountIdssame field names asFlatCreatePublishInput/COMMON_CREATE_PROPERTIES) plus platform-specific options (rednote: originalStatement/userDeclarationBind,wechat_channels: postFlag). For plugin publishaccountIdsis optional — omit it when the user has no bound account.
Defaults — do not interrogate the user
Complete a publish request in the same turn; never ask the user to supply copy or optional settings first:
- Write
titleandbodyyourself based on the content being published — never ask the user for copy. - Do not ask about optional settings: omit
publishAtto publish immediately, default originality/AI-label flags per the sections above (e.g.postFlag/originalStatementunchecked unless the user asks), and skip optional decorations. - Produce required fields yourself — when a cover is required, call
extract_video_frame(optionally restyle the frame viagenerate_image) instead of asking the user for a URL. - Ask only when a decision truly blocks the call: which account to use among several usable ones (Workflow step 1), or the media to publish is missing entirely.
Failure and browser boundary
[!CAUTION] For any platform where
publish.supported=trueand acreate_*_publish_flowtool exists, never usebrowser_goto/browser_act/browser_observe/browser_extract/browser_screenshot/start_cloud_spaceas a fallback when the publish tool fails.
success:false(validation failure): readissues[].pathandissues[].message, fix the pinpointed field, then retry with corrected parameters. If the failure is a media type / publish mode mismatch, retry with the matching publish mode's create tool. Do not retry without changes.isError:truecaused by authorization (account offline / token expired / auth failure): do not retry and do not switch to browser automation — callguide_user_action { kind:'channel_auth', platform }, tell the user to re-authorize the account, and stop.isError:true(non-authorization failure) or approval rejected: explain what is blocked and stop. Do not switch to browser automation. Forxhs / wxSphcreate_*_publish_flowdoes not exist and is not searchable. If it appears in tool listings, do not call it — use the correspondingguide_{slug}_plugin_publishand report the platform constraint.
Only when the platform reports “不支持服务端发布” and the user explicitly asks for browser/web operation may you switch to browser-operations, after telling the user the platform has no server-side publish capability.
Douyin WaitingForUserAction — two-step query and QR
Douyin uses CompletionStrategy.UserHandoff. After publish, a task may enter WaitingForUserAction. You must do a two-step query:
get_channel_publish_recordwith{ flowId }(or{ taskId }) from theChannelPublishFlowresult to obtainChannelPublishRecord.idasrecordId. The publish tools returnflowId+tasks[].id(taskId)only —recordIdis not returned directly.get_channel_publish_user_action { recordId }to fetchschemeUrl/shortLink/expiresAt/qrImageUrl. The QR image is generated and uploaded agent-side (qrcode+AssetsService.uploadFromBuffer→qrImageUrlhttpUrl) for frontend rendering. Never render rawqrText. Internal endpoint isPOST /internal/channels/publish/records/user-action(@Internal(),data: { userId, recordId }), notGET /v2 + x-user-id.
After step 2, echo the QR URL in the conversation as clickable shortLink + schemeUrl text plus expiresAt, and let frontend render qrImageUrl. Tell the user to scan with the Douyin App or tap the link before expiry. Continue polling get_channel_publish_record until Published / Failed.
Workflow
- Determine platform first. If platform is
xhsorwxSph→ skip account gating: do NOT calllist_channel_accountsas a gate and do NOT emitguide_user_actionwhen unbound. Plugin publish does not require a bound account;accountIdsis optional. Go to step 2. Otherwise:list_channel_accountsfor that platform. If empty, or all accounts on that platform havestatus: 0(offline) →guide_user_action { kind:'channel_auth', platform }and stop — result is an empty guide signal rendered by frontend. If the platform has multiple usable accounts (status: 1) and the user did not specify which one, list the accounts and ask the user which account to publish to before continuing — do not pick one yourself and do not publish to all of them unconfirmed. Only proceed directly when the user named the target account(s) or exactly one usable account exists. Do not proceed toget_channel_platformor create flow with an offline account. - Call
get_channel_platformfor that platform. Shape title, body, media, cover, topics, and mode from that text. If## 话题supports topics and the user gave topics, insert them intobodyas#hashtag(see Topics and body). - For
xhs / wxSphuseguide_{slug}_plugin_publish(no need to search forcreate_*_publish_flow— it does not exist / not searchable). For other platforms:toolSearchthe create / option tool ids from the platform result. Fill dynamic options withlist_*/search_*/create_*first. - If platform is
xhs→ video notes require a cover: if nocoverUrl, callextract_video_framefirst (optionally restyle the returned frame viagenerate_imagewith it as reference) and pass the result ascoverUrl. Then always useguide_rednote_plugin_publish { title?, body?, mediaUrls?, coverUrl?, publishAt?, accountIds?, originalStatement?, userDeclarationBind? }(noplatformfield, no union;accountIdsoptional). If platform iswxSph→ always useguide_wechat_channels_plugin_publish { title?, body?, mediaUrls?, coverUrl?, publishAt?, accountIds?, postFlag? }. Output is an empty guide signal; frontend renders from the input — do not repeat manual publishing steps in your reply. Examples:- 小红书视频笔记: 先
extract_video_frame { videoUrl:'https://...mp4' }→guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...mp4'], coverUrl:'https://...(截帧返回的 coverUrl)', originalStatement:true } - 小红书图文笔记(无绑定):
guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...'], originalStatement:true } - 小红书(已绑定):
guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...'], accountIds:['...'], originalStatement:true, userDeclarationBind:'2' } - 视频号(无绑定):
guide_wechat_channels_plugin_publish { body:'正文', mediaUrls:['https://...'], postFlag:'1' } - 视频号(已绑定):
guide_wechat_channels_plugin_publish { body:'正文', mediaUrls:['https://...'], accountIds:['...'], postFlag:'1' }If other platform has no direct publish tool (publish.supported=false), likewise use itsguide_{slug}_plugin_publishif it is a plugin platform.
- 小红书视频笔记: 先
- Otherwise call the publish mode's
create_{slug}_{mode}_publish_flow(see Publish modes; single-mode platforms usecreate_{slug}_publish_flow). If it returnssuccess: false, readissues[].pathandissues[].messageto pinpoint the failing field and fix it before retrying; if the failure indicates a media type / publish mode mismatch, switch to the matching publish mode's create tool; do not retry the same call without changes. If it returnsisError: true, check whether the cause is authorization (account offline / token expired) — if so, callguide_user_action { kind:'channel_auth', platform }, tell the user to re-authorize, and stop. Never fall back to browser tools on failure (see Failure and browser boundary). - Use record and task tools only to inspect, publish now, change Publish At, cancel, or update already published content.
- Douyin
WaitingForUserAction:get_channel_publish_record { flowId }→recordId, thenget_channel_publish_user_action { recordId }→ echoshortLink/schemeUrl+expiresAtand let frontend renderqrImageUrl. See Douyin WaitingForUserAction — two-step query and QR.
Failed or canceled Publish Tasks are not retried. Create a new Publish Flow to publish again.