service-de-channel-create
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInserting Messaging Channels
插入消息渠道
Reference File Index
参考文件索引
| Reference file | Load when |
|---|---|
| |
| |
| |
| |
| |
| Shared authProviderId lookup + Connect POST + response-classification detail (Stage 4). |
| End-to-end traces per type (fresh insert, preflight short-circuit, OAuth). |
| 参考文件 | 加载时机 |
|---|---|
| |
| |
| |
| |
| |
| 通用AuthProviderId查询 + Connect POST + 响应分类细节(第4阶段)。 |
| 各类型的端到端跟踪示例(全新插入、预检短路、OAuth流程)。 |
What this skill does
本技能的功能
Given a and its inputs, creates a record with
, . The same server-side path
( → )
also inserts a row (,
on fresh insert — on a channel that was previously activated then deactivated) — we get
it for free.
{MESSAGE_TYPE}MessagingChannelPlatformType=EnhancedIsActive=falseLiveMessageSetupServiceImpl.addChannelLiveMessageChannelsUtil.findOrCreateCsotMessagingChannelMessagingChannelUsageDeploymentType=ConversationDeploymentStatus=NewDisabledOne primary path for every type: Connect REST API.
with an org OAuth session managed by
. Public, sanctioned, live since API v66+. No Aura session, no token extracted
into shell state.
POST /services/data/v{V}/connect/livemessage/channelssf api request restIdempotent on — re-firing with the same platform key
returns the existing channel's id. This matters because records are not deletable
via standard means, so the preflight (Stage 1) is the practical idempotency guard.
(MessagingPlatformKey, MessageType)MessagingChannelAll types return the same envelope shape, so callers don't branch on type after insert:
.
{ok, channelId, mcuId, developerName, isActive:false, messageType, messagingPlatformKey, path, created, durationMs}给定及其输入,将创建一条、的记录。同一服务器端路径( → )还会自动插入一条行(全新插入时、;若渠道曾激活后又停用,则)—— 此为附带的免费功能。
{MESSAGE_TYPE}PlatformType=EnhancedIsActive=falseMessagingChannelLiveMessageSetupServiceImpl.addChannelLiveMessageChannelsUtil.findOrCreateCsotMessagingChannelMessagingChannelUsageDeploymentType=ConversationDeploymentStatus=NewDeploymentStatus=Disabled所有类型共用一条核心路径:Connect REST API。
通过管理的组织OAuth会话,调用。该端点为公开官方认可接口,自API v66+起可用。无需Aura会话,也不会将令牌提取到shell状态中。
sf api request restPOST /services/data/v{V}/connect/livemessage/channels基于实现幂等性 — 使用相同平台密钥重复触发时,将返回现有渠道的ID。这一点至关重要,因为记录无法通过标准方式删除,因此预检(第1阶段)是实际的幂等性保障机制。
(MessagingPlatformKey, MessageType)MessagingChannel所有类型返回相同的响应结构,因此调用方无需在插入后按类型分支处理:
。
{ok, channelId, mcuId, developerName, isActive:false, messageType, messagingPlatformKey, path, created, durationMs}Supported types (the only branch point)
支持的类型(唯一分支点)
| Platform key ( | Prerequisite (caller supplies) | Body extras | Ref |
|---|---|---|---|---|
| Phone Number ID | | | |
| LINE channel id | LINE id + secret + token (LINE Developers Console) | | |
| Apple BC Account ID (GUID) | Apple BC Account ID (register.apple.com/business-chat) | (none — empty config) | |
| Facebook Page ID | OAuth handled internally | | |
| Phone number / short code | number already provisioned (caller supplies) | | |
Unsupported (would need their own leaf logic, not yet built): , , .
For these, emit the envelope (Stage 0).
WeChatMsCopilotAlexaunsupported-type | 平台密钥( | 前置条件(调用方提供) | 请求体附加内容 | 参考文件 |
|---|---|---|---|---|
| 电话号码ID | | | |
| LINE渠道ID | LINE ID + 密钥 + 令牌(LINE开发者控制台获取) | | |
| Apple BC账户ID(GUID格式) | Apple BC账户ID(通过register.apple.com/business-chat注册) | (无 — 空配置) | |
| Facebook页面ID | OAuth流程内部处理 | | |
| 电话号码/短码 | 号码已完成预配(调用方提供) | | |
暂不支持的类型(需单独编写分支逻辑,尚未开发):, , 。
针对这些类型,将返回响应结构(第0阶段)。
WeChatMsCopilotAlexaunsupported-typeWhen NOT to use this skill
请勿使用本技能的场景
- End-to-end setup. If the user wants insert → route → activate, invoke
— it orchestrates this skill plus routing, consent, and activation. This skill only handles the insertion step (see Stage 0).
service-de-headless-channel-configure - The channel already exists and you only want to re-route / activate it. Skip insertion;
call /
service-de-channel-routing-configurewith the existingservice-de-channel-activate.channelId
- 端到端设置:如果用户需要完成插入→路由→激活的全流程,请调用— 它会编排本技能及路由、授权、激活步骤。本技能仅处理插入环节(见第0阶段)。
service-de-headless-channel-configure - 渠道已存在,仅需重新路由/激活:跳过插入步骤;使用现有调用
channelId/service-de-channel-routing-configure。service-de-channel-activate
Inputs (from caller)
输入参数(来自调用方)
- — one of
{MESSAGE_TYPE},WhatsApp,Line,AppleBusinessChat,Facebook. Case-sensitive (matches the SF picklist).Textis the enum value for SMS — there is noTexttype.SMS - — optional;
{ORG_ALIAS}CLI target-org alias. Default:sf.sf config get target-org - — optional; Connect API version. Default: the org's current API version (
{API_VERSION}). Always use the org's native version to avoid 404s.sf org display --json | jq -r '.result.apiVersion' - — display name shown in Setup → Messaging Settings. Required for
{CHANNEL_NAME}(no phone/screen-name fallback); prompted if omitted for others.AppleBusinessChat
Type-specific inputs:
- WhatsApp: (sent as
{WABA_ID}),externalAccountId(the platform key — must differ from WABA_ID),{PHONE_NUMBER_ID}(optional, default{ISO_COUNTRY_CODE}).US - Line: (platform key),
{LINE_CHANNEL_ID},{LINE_CHANNEL_SECRET},{LINE_ACCESS_TOKEN}(optional, default{VERIFY_TOKEN_FIRST}).true - AppleBusinessChat: (platform key, GUID).
{APPLE_BC_ID} - Facebook: (optional — OAuth fetches/prompts if absent).
{PAGE_ID} - Text (SMS): (the platform key — the phone number / short code string, already provisioned to the org),
{SMS_NUMBER}(sent as{SMS_PROVIDER}— thesmsProviderkey must be present in the body or the insert 400s with "SMS provider is missing", but its value is not validated or stored, so an empty stringsmsProviderworks; default to""when the caller doesn't supply a provider),""(optional, default{ISO_COUNTRY_CODE}). This skill does not provision the number — the caller must already know their number.US
- — 可选值为
{MESSAGE_TYPE},WhatsApp,Line,AppleBusinessChat,Facebook。 区分大小写(与Salesforce选择列表匹配)。Text是SMS的枚举值 — 不存在Text类型。SMS - — 可选;
{ORG_ALIAS}CLI的目标组织别名。默认值:sf的结果。sf config get target-org - — 可选;Connect API版本。默认值:组织当前的API版本(
{API_VERSION}的结果)。请始终使用组织原生版本,避免出现404错误。sf org display --json | jq -r '.result.apiVersion' - — 在Setup → Messaging Settings中显示的名称。
{CHANNEL_NAME}必填(无电话号码/屏幕名称作为 fallback);其他类型若未提供则会提示用户输入。AppleBusinessChat
类型特定输入参数:
- WhatsApp:(作为
{WABA_ID}发送)、externalAccountId(平台密钥 — 必须与WABA_ID不同)、{PHONE_NUMBER_ID}(可选,默认值{ISO_COUNTRY_CODE})。US - Line:(平台密钥)、
{LINE_CHANNEL_ID}、{LINE_CHANNEL_SECRET}、{LINE_ACCESS_TOKEN}(可选,默认值{VERIFY_TOKEN_FIRST})。true - AppleBusinessChat:(平台密钥,GUID格式)。
{APPLE_BC_ID} - Facebook:(可选 — 若未提供,OAuth流程会自动获取或提示用户输入)。
{PAGE_ID} - Text (SMS):(平台密钥 — 电话号码/短码字符串,已预配到组织)、
{SMS_NUMBER}(作为{SMS_PROVIDER}发送 — 请求体中必须存在smsProvider字段,否则插入会返回400错误:"SMS provider is missing",但其值不会被验证或存储,因此空字符串smsProvider有效;若调用方未提供,默认设为"")、""(可选,默认值{ISO_COUNTRY_CODE})。本技能不负责号码预配 — 调用方必须已拥有该号码。US
Output (to caller)
输出结果(返回给调用方)
Success:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "developerName": "...",
"isActive": false, "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "...",
"path": "connect" | "preflight", "created": true | false, "durationMs": 1234}created: falsepath: "preflight"Failure (shared):
json
{"ok": false, "kind": "wrong-skill", "correctSkill": "service-de-headless-channel-configure", "hint": "..."}
{"ok": false, "kind": "unsupported-type", "supportedTypes": ["WhatsApp","Line","AppleBusinessChat","Facebook","Text"], "hint": "..."}
{"ok": false, "kind": "missing-input", "missing": ["..."], "hint": "caller must provide all required inputs for this type"}
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "business", "message": "...", "errorCode": "..."}
{"ok": false, "kind": "transport","status": 500, "message": "..."}
{"ok": false, "kind": "verify-failed", "hint": "Connect returned 201 but follow-up SOQL shows no matching channel"}Type-specific failure kinds (, , ,
for WhatsApp; for LINE; for Apple;
, for Facebook) are documented in the per-type reference files.
partnership-blockeduser-declinedwaba-manage-failedmeta-preconditionline-token-invalidapple-preconditionoauth-not-completeoauth-failed成功响应:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "developerName": "...",
"isActive": false, "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "...",
"path": "connect" | "preflight", "created": true | false, "durationMs": 1234}created: falsepath: "preflight"通用失败响应:
json
{"ok": false, "kind": "wrong-skill", "correctSkill": "service-de-headless-channel-configure", "hint": "..."}
{"ok": false, "kind": "unsupported-type", "supportedTypes": ["WhatsApp","Line","AppleBusinessChat","Facebook","Text"], "hint": "..."}
{"ok": false, "kind": "missing-input", "missing": ["..."], "hint": "caller must provide all required inputs for this type"}
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "business", "message": "...", "errorCode": "..."}
{"ok": false, "kind": "transport","status": 500, "message": "..."}
{"ok": false, "kind": "verify-failed", "hint": "Connect returned 201 but follow-up SOQL shows no matching channel"}类型特定的失败类型(WhatsApp的, , , ;LINE的;Apple的;Facebook的, )记录在对应类型的参考文件中。
partnership-blockeduser-declinedwaba-manage-failedmeta-preconditionline-token-invalidapple-preconditionoauth-not-completeoauth-failedStage 0: Enforce orchestrator-only invocation
第0阶段:仅允许编排器调用
This skill is INTERNAL USE ONLY. It should only be invoked by the
orchestrator.
service-de-headless-channel-configureIf the user's original request was to "install", "set up", "create", or "activate" a messaging
channel (or any phrasing that implies end-to-end setup), immediately return:
json
{
"ok": false,
"kind": "wrong-skill",
"hint": "This is an internal insertion skill. For end-to-end channel setup (insert → route → activate), invoke 'service-de-headless-channel-configure' instead with the desired MESSAGE_TYPE.",
"correctSkill": "service-de-headless-channel-configure"
}And render to the user:
text
Error: Wrong skill invoked.
This skill (service-de-channel-create) only handles the insertion step.
For complete channel setup, use: /service-de-headless-channel-configure
That orchestrator runs: insert → configure routing → activate in one flow.Only proceed to Stage 0.1 if:
- This skill was invoked programmatically by
service-de-headless-channel-configure - The user explicitly stated they ONLY want insertion (not routing or activation)
- This is a retry/resume scenario where routing/activation already succeeded
When in doubt, redirect to the orchestrator.
本技能仅限内部使用,仅应由编排器调用。
service-de-headless-channel-configure如果用户的原始请求是“安装”“设置”“创建”或“激活”消息渠道(或任何暗示端到端设置的表述),立即返回:
json
{
"ok": false,
"kind": "wrong-skill",
"hint": "This is an internal insertion skill. For end-to-end channel setup (insert → route → activate), invoke 'service-de-headless-channel-configure' instead with the desired MESSAGE_TYPE.",
"correctSkill": "service-de-headless-channel-configure"
}并向用户展示:
text
Error: Wrong skill invoked.
This skill (service-de-channel-create) only handles the insertion step.
For complete channel setup, use: /service-de-headless-channel-configure
That orchestrator runs: insert → configure routing → activate in one flow.仅在以下情况时进入第0.1阶段:
- 本技能由以编程方式调用
service-de-headless-channel-configure - 用户明确表示仅需要插入操作(不需要路由或激活)
- 属于重试/恢复场景,且路由/激活已成功完成
如有疑问,直接引导至编排器。
Stage 0.1: Validate type and required inputs
第0.1阶段:验证类型及必填输入参数
Look up in the supported-types table. If absent, emit the
envelope and return.
{MESSAGE_TYPE}unsupported-typeValidate the type's required inputs are present (Facebook is the exception — no required
inputs; OAuth → page selection → prompts happen inside the Facebook flow). If any are missing,
emit with the list and return.
missing-inputWhatsApp only: additionally assert — they are distinct Meta
entities and reusing one value for both causes cryptic activation failures. If equal, emit
.
{WABA_ID} != {PHONE_NUMBER_ID}{ok:false, kind:"invalid-input", hint:"WABA_ID and PHONE_NUMBER_ID must differ — WABA_ID is the WhatsApp Business Account ID; PHONE_NUMBER_ID is a specific phone number within it. Find both in Meta Business Manager → WhatsApp Accounts."}Capture for .
{T0} = Date.now()durationMs在支持类型表中查找。若不存在,返回响应结构并终止流程。
{MESSAGE_TYPE}unsupported-type验证该类型的必填输入参数是否齐全(Facebook除外 — 无必填参数;OAuth→页面选择→提示流程在Facebook内部完成)。若有缺失,返回响应结构并列出缺失项,终止流程。
missing-input仅WhatsApp: 额外验证 — 它们是Meta的不同实体,若复用同一值会导致难以排查的激活失败。若两者相等,返回。
{WABA_ID} != {PHONE_NUMBER_ID}{ok:false, kind:"invalid-input", hint:"WABA_ID和PHONE_NUMBER_ID必须不同 — WABA_ID是WhatsApp商务账户ID;PHONE_NUMBER_ID是该账户下的特定电话号码。可在Meta商务管理平台→WhatsApp账户中找到两者。"}记录用于计算。
{T0} = Date.now()durationMsStage 0.2: Establish the scratch directory
第0.2阶段:创建临时目录
Every transient artifact this skill writes (preflight query results, request bodies, Connect
responses) goes into a per-run scratch directory rooted inside the harness working area — never a
bare absolute path, which is world-writable and non-portable. Establish it once:
/tmpbash
SCRATCH_DIR="$(mktemp -d "${outputDir:-${TMPDIR:-/tmp}}/icc.XXXXXX")"The base dir is the harness-provided when set, else the private user-scoped
, else only as a last resort. All later stages (and the reference flows) write
under ; the durable report stays under .
${outputDir}${TMPDIR}/tmp${SCRATCH_DIR}/${outputDir}本技能生成的所有临时文件(预检查询结果、请求体、Connect响应)都将存储在测试工具工作区内的单次运行临时目录中 — 绝不使用裸绝对路径,该路径为全局可写且不具备可移植性。只需创建一次:
/tmpbash
SCRATCH_DIR="$(mktemp -d "${outputDir:-${TMPDIR:-/tmp}}/icc.XXXXXX")"基础目录优先使用测试工具提供的;若未设置,则使用用户私有范围的;仅当以上两者都不可用时才使用。后续所有阶段(及参考流程)的文件都将写入;持久化报告存储在下。
${outputDir}${TMPDIR}/tmp${SCRATCH_DIR}/${outputDir}Stage 1: Preflight — is the channel already here?
第1阶段:预检 — 渠道是否已存在?
SOQL-probe for an existing row first; a hit lets us skip all prerequisites and the insert.
For Facebook without a , skip this stage — the page id isn't known until OAuth →
page selection completes (see ). Run the preflight after page selection.
{PAGE_ID}references/facebook.mdTwo queries, not a subquery. The child subquery
fails with on orgs where the child relationship is unnameable (verified on
/ PRODDEBUG). The FK-keyed form works everywhere.
(SELECT ... FROM MessagingChannelUsages)INVALID_TYPEwadtestingbash
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeveloperName, MasterLabel, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \
--json > "${SCRATCH_DIR}/preflight.json"If :
records.length === 1- Record ,
{CHANNEL_ID},{DEVELOPER_NAME}.{IS_ACTIVE} - Second query for the MCU by FK:
Recordbash
sf data query --target-org '{ORG_ALIAS}' \ --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \ --json > "${SCRATCH_DIR}/preflight-mcu.json"(empty MCU: flag, don't fail — the activation skill handles it).{MCU_ID} - Emit the success envelope with . Return.
path: "preflight", created: false
If : ambiguous (uniqueness should prevent this) — proceed with the first,
note a warning in the envelope.
records.length > 1If : proceed to Stage 2.
records.length === 0首先通过SOQL查询是否存在现有记录;若存在,可跳过所有前置条件和插入步骤。
若Facebook未提供,跳过本阶段 — 页面ID需在OAuth→页面选择完成后才能获取(详见)。在页面选择完成后再执行预检。
{PAGE_ID}references/facebook.md使用两次查询,而非子查询。子查询在子关系不可命名的组织中会返回错误(已在/PRODDEBUG环境中验证)。基于外键的查询方式在所有环境中都有效。
(SELECT ... FROM MessagingChannelUsages)INVALID_TYPEwadtestingbash
sf data query --target-org '{ORG_ALIAS}' \\
--query "SELECT Id, DeveloperName, MasterLabel, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \\
--json > "${SCRATCH_DIR}/preflight.json"若:
records.length === 1- 记录、
{CHANNEL_ID}、{DEVELOPER_NAME}。{IS_ACTIVE} - 通过外键执行第二次查询获取MCU:
记录bash
sf data query --target-org '{ORG_ALIAS}' \\ --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \\ --json > "${SCRATCH_DIR}/preflight-mcu.json"(若MCU为空:标记但不终止流程 — 激活技能会处理该情况)。{MCU_ID} - 返回成功响应结构,其中。终止流程。
path: "preflight", created: false
若:结果不明确(唯一性约束应避免此情况) — 取第一条记录,在响应结构中添加警告信息。
records.length > 1若:进入第2阶段。
records.length === 0Stage 2-3: Type-specific prerequisites
第2-3阶段:类型特定前置条件
Most types have no prerequisite here — go straight to Stage 4.
- WhatsApp: requires the Meta WABA↔Salesforce partnership (Stage 2, via
) and the SF-side webhook subscription + credit-line share (Stage 3,
service-de-waba-integrate). LoadPOST /connect/livemessage/whatsapp/business-accountand follow it before inserting.references/whatsapp.md - Line: optional token preflight against if
https://api.line.me/v2/bot/infois true. See{VERIFY_TOKEN_FIRST}.references/line.md - Facebook: browser-based OAuth authorization + page-list fetch + page selection happen here,
before the insert. Load and follow it.
references/facebook.md - Apple: none. (Apple's certificate handshake is provisioned on Apple's side and is per-message
at runtime, not at channel insert — see .)
references/apple.md - Text (SMS): none at insert. The number must already be provisioned to the org and the caller
must know its , but this skill performs no provisioning step — it inserts the record for a number the caller already has. (ESP registration runs at activation, not insert — see
smsProvider.)references/sms.md
大多数类型无前置条件 — 直接进入第4阶段。
- WhatsApp:需要Meta WABA与Salesforce的合作关系(第2阶段,通过完成),以及Salesforce侧的Webhook订阅+信用额度共享(第3阶段,调用
service-de-waba-integrate)。插入前请加载POST /connect/livemessage/whatsapp/business-account并遵循其指引。references/whatsapp.md - Line:若为
{VERIFY_TOKEN_FIRST},可选对true执行令牌预检。详见https://api.line.me/v2/bot/info。references/line.md - Facebook:基于浏览器的OAuth授权+页面列表获取+页面选择在此阶段完成,之后再执行插入。请加载并遵循其指引。
references/facebook.md - Apple:无前置条件。(Apple的证书握手在Apple侧配置,且在运行时按消息执行,而非在渠道插入时 — 详见。)
references/apple.md - Text (SMS):插入时无前置条件。号码必须已预配到组织,且调用方必须知晓其,但本技能不执行预配步骤 — 仅为调用方已拥有的号码插入记录。(ESP注册在激活时执行,而非插入时 — 详见
smsProvider。)references/sms.md
Stage 4: Insert via Connect REST API
第4阶段:通过Connect REST API插入
Shared across all types. The only per-type difference is the request body; the authProviderId
lookup, the POST invocation, and the response-classification table are identical.
所有类型共用本阶段流程。唯一的类型差异是请求体;AuthProviderId查询、POST调用、响应分类表均相同。
Stage 4.1: authProviderId (org-global; required for every type except Text)
第4.1阶段:authProviderId(组织全局;除Text外所有类型必填)
bash
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id FROM AuthProvider WHERE DeveloperName = 'LiveMessageSetup'" \
--json > "${SCRATCH_DIR}/authprovider.json"One row → . Zero rows → branch on type:
{AUTH_PROVIDER_ID}- — non-fatal. Text's Connect signature doesn't take
Text, so leaveauthProviderIdunset and continue to Stage 4.2; the POST omits the parameter entirely (see below). Do not emit the business error for Text — an org that has never run LiveMessage setup is exactly where the Text-only path matters, and blocking here would make the advertised SMS insert unreachable on those orgs.{AUTH_PROVIDER_ID} - every other type (,
WhatsApp,Line,AppleBusinessChat) — the org has never run LiveMessage setup and the insert can't proceed; emitFacebook.{ok:false, kind:"business", message:"No AuthProvider 'LiveMessageSetup' found — run the Messaging Setup wizard once on this org first"}
The Connect endpoint rejects the POST with if this URL parameter is absent — verified for every type except , Apple
included. ( has its own Connect signature that doesn't require it.) When the lookup finds a row,
send for every type including Text (harmless there); when it finds none, only Text may
proceed, and it proceeds without the parameter. goes in the URL query string,
never the body (body → ).
400 ILLEGAL_QUERY_PARAMETER_VALUE "Missing argument authProviderId"TextTextauthProviderIdauthProviderId400 JSON_PARSER_ERROR "Unrecognized field 'authProviderId'"bash
sf data query --target-org '{ORG_ALIAS}' \\
--query "SELECT Id FROM AuthProvider WHERE DeveloperName = 'LiveMessageSetup'" \\
--json > "${SCRATCH_DIR}/authprovider.json"若返回1条记录 → 记录。若返回0条记录 → 按类型分支处理:
{AUTH_PROVIDER_ID}- — 非致命错误。Text的Connect签名不需要
Text,因此留空authProviderId并进入第4.2阶段;POST请求中完全省略该参数(见下文)。请勿为Text返回业务错误 — 从未运行过LiveMessage设置的组织正是Text专属路径的适用场景,若在此处阻断会导致这些组织无法使用宣传的SMS插入功能。{AUTH_PROVIDER_ID} - 其他所有类型(,
WhatsApp,Line,AppleBusinessChat)— 组织从未运行过LiveMessage设置,无法继续插入;返回Facebook。{ok:false, kind:"business", message:"未找到AuthProvider 'LiveMessageSetup' — 请先在该组织中运行一次消息设置向导"}
若URL参数中缺少,Connect端点会返回 — 已验证除外的所有类型(包括Apple)均如此。(有自己的Connect签名,不需要该参数。)当查询到记录时,为所有类型(包括Text)发送(对Text无影响);当未查询到记录时,仅Text可继续,且需不带该参数。需放在URL查询字符串中,绝不能放在请求体中(放在请求体中会返回)。
authProviderId400 ILLEGAL_QUERY_PARAMETER_VALUE "Missing argument authProviderId"TextTextauthProviderIdauthProviderId400 JSON_PARSER_ERROR "Unrecognized field 'authProviderId'"Stage 4.2: Build the type-specific body and POST
第4.2阶段:构建类型特定请求体并执行POST
Resolve first (prompt if required and absent — see per-type ref). Then build the
body from the supported-types table and POST. Write the body to a file () — LINE
tokens are long and can contain shell-hostile characters.
{CHANNEL_NAME}--body @...Append the query parameter only when Stage 4.1 resolved one — otherwise (the
Text-with-no-AuthProvider case) POST to the bare endpoint, since an empty would
trip the same :
authProviderId?authProviderId=ILLEGAL_QUERY_PARAMETER_VALUEbash
URL="/services/data/v{API_VERSION}/connect/livemessage/channels"
[ -n "${AUTH_PROVIDER_ID:-}" ] && URL="${URL}?authProviderId=${AUTH_PROVIDER_ID}"
sf api request rest "${URL}" \
--method POST --target-org '{ORG_ALIAS}' \
--header 'Content-Type: application/json' --header 'Accept: application/json' \
--body @"${SCRATCH_DIR}/body.json" --include > "${SCRATCH_DIR}/connect-response.txt" 2>&1--include-wsf api request restFor the exact per-type body shape and the full response-classification table (including the
race back to Stage 1, the Facebook 401-vs-400
disambiguation, and per-type precondition errors), load plus the
per-type ref.
DUPLICATE_VALUEAUTH_PROVIDER_NEEDS_AUTHreferences/connect-insert.md首先确定(若必填且未提供则提示用户输入 — 详见对应类型的参考文件)。然后根据支持类型表构建请求体并执行POST。将请求体写入文件()— LINE令牌较长,可能包含对shell不友好的字符。
{CHANNEL_NAME}--body @...仅当第4.1阶段获取到时,才在URL中附加该查询参数 — 否则(Text无AuthProvider的情况)直接调用裸端点,因为空的会触发相同的错误:
authProviderId?authProviderId=ILLEGAL_QUERY_PARAMETER_VALUEbash
URL="/services/data/v{API_VERSION}/connect/livemessage/channels"
[ -n "${AUTH_PROVIDER_ID:-}" ] && URL="${URL}?authProviderId=${AUTH_PROVIDER_ID}"
sf api request rest "${URL}" \\
--method POST --target-org '{ORG_ALIAS}' \\
--header 'Content-Type: application/json' --header 'Accept: application/json' \\
--body @"${SCRATCH_DIR}/body.json" --include > "${SCRATCH_DIR}/connect-response.txt" 2>&1--include-wsf api request rest关于具体的类型特定请求体结构和完整的响应分类表(包括错误回退到第1阶段、Facebook 的401与400区分、类型特定前置条件错误),请加载及对应类型的参考文件。
DUPLICATE_VALUEAUTH_PROVIDER_NEEDS_AUTHreferences/connect-insert.mdStage 5: Verify the insert
第5阶段:验证插入结果
Two queries (same no-subquery reason as Stage 1). Keep to the universal field set —
and don't exist on all orgs.
ExternalAccountIdMessagingPlatformbash
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeploymentStatus, DeploymentType FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" --jsonExpected: channel ; exactly one MCU, ,
in (fresh inserts land in ; is a
previously-activated-then-deactivated channel — both are valid starting states for activation).
Empty MCU query → .
IsActive=falseDeploymentType=ConversationDeploymentStatus{New, Disabled}NewDisabled{ok:false, kind:"verify-failed", hint:"Connect returned {CHANNEL_ID} but no child MessagingChannelUsage was created"}Trust the DB, not the Connect response body, for / — the 201 body
carries stale input-rep projections (see per-type gotchas).
MasterLabelDeveloperName执行两次查询(原因与第1阶段相同,避免使用子查询)。仅使用通用字段集 — 和并非在所有组织中都存在。
ExternalAccountIdMessagingPlatformbash
sf data query --target-org '{ORG_ALIAS}' \\
--query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json
sf data query --target-org '{ORG_ALIAS}' \\
--query "SELECT Id, DeploymentStatus, DeploymentType FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" --json预期结果:渠道;存在且仅存在一条MCU,,为(全新插入时为;曾激活后停用的渠道为 — 两者均为有效的激活起始状态)。若MCU查询为空 → 返回。
IsActive=falseDeploymentType=ConversationDeploymentStatus{New, Disabled}NewDisabled{ok:false, kind:"verify-failed", hint:"Connect返回了{CHANNEL_ID}但未创建子记录MessagingChannelUsage"}以数据库结果为准,而非Connect响应体获取/ — 201响应体中的值为过时的输入投影(详见对应类型的注意事项)。
MasterLabelDeveloperNameStage 6: Report to caller
第6阶段:向调用方返回结果
json
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "<from Stage 5>",
"developerName": "<authoritative, from Stage 5>", "isActive": false,
"messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "{PLATFORM_KEY}",
"path": "connect", "created": true, "durationMs": <Date.now() - T0>}Rendered (when invoked directly rather than by the orchestrator):
Success — MessagingChannel {CHANNEL_ID} ({developerName}) created via {path}. MCU {mcuId} auto-created. IsActive=false — run routing, then consent, then activate next.Info: Channel already exists — {CHANNEL_ID} ({developerName}). Proceed to routing/activation.Error: {kind}: {message or hint}
On success the caller can immediately invoke with the
returned , then , then
. Those three are message-type-agnostic (they operate on
+ ), so there's no downstream dispatch concern.
service-de-channel-routing-configurechannelIdservice-de-channel-consent-configureservice-de-channel-activateMessagingChannel.IdMessagingChannelUsage.Idjson
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "<来自第5阶段>",
"developerName": "<权威值,来自第5阶段>", "isActive": false,
"messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "{PLATFORM_KEY}",
"path": "connect", "created": true, "durationMs": <Date.now() - T0>}当直接调用本技能(而非由编排器调用)时,展示以下内容:
Success — MessagingChannel {CHANNEL_ID} ({developerName}) created via {path}. MCU {mcuId} auto-created. IsActive=false — run routing, then consent, then activate next.Info: Channel already exists — {CHANNEL_ID} ({developerName}). Proceed to routing/activation.Error: {kind}: {message or hint}
成功后,调用方可立即使用返回的调用,然后调用,最后调用。这三个技能与消息类型无关(仅基于 + 操作),因此无需担心下游分支处理问题。
channelIdservice-de-channel-routing-configureservice-de-channel-consent-configureservice-de-channel-activateMessagingChannel.IdMessagingChannelUsage.IdGotchas (shared)
通用注意事项
- Case-sensitive type names. ≠
"whatsapp"— the SF picklist, SOQL filters, and Connect enum all match exactly. Normalize upstream; don't make Stage 0.1 case-insensitive."WhatsApp" - Preflight is the idempotency guard. rows aren't deletable via standard means, so re-runs rely on Stage 1 finding the existing row.
MessagingChannel - Envelope shape is a tight coupling with callers. If a field is renamed here, every caller
(especially ) silently sees the change — break loudly in tests rather than remap field names.
service-de-headless-channel-configure - Per-type gotchas live in the reference files. WABA/phone-id confusion, LINE long-lived vs
short-lived tokens, Apple's empty-config body + no-provisioning-callout, Facebook OAuth page
selection + 401 disambiguation — load the matching when debugging that type.
references/<type>.md
- 类型名称区分大小写:≠
"whatsapp"— Salesforce选择列表、SOQL过滤器、Connect枚举均严格匹配。请在上游完成标准化;不要让第0.1阶段支持大小写不敏感匹配。"WhatsApp" - 预检是幂等性保障:记录无法通过标准方式删除,因此重复运行依赖第1阶段找到现有记录。
MessagingChannel - 响应结构与调用方强耦合:若此处重命名字段,所有调用方(尤其是)会无声地受到影响 — 请在测试中主动抛出错误,而非映射字段名称。
service-de-headless-channel-configure - 类型特定注意事项在参考文件中:WABA/电话号码ID混淆、LINE长效/短效令牌、Apple空配置请求体+无预配调用、Facebook OAuth页面选择+401错误区分 — 调试对应类型时,请加载匹配的文件。",
references/<type>.md