关于 service-de-channel-create
供 Salesforce 渠道配置流程调用的内部 skill,用于创建未激活的 Enhanced MessagingChannel。它支持 WhatsApp、Line、AppleBusinessChat、Facebook 和 Text,并通过 Connect REST API 与预检实现幂等插入。
主要功能
- 01支持五种消息渠道类型
- 02通过 Connect REST API 插入渠道
- 03按平台键和类型预检复用记录
- 04处理类型专属前置步骤
- 05返回统一结果结构
适用场景
- 01在完整配置流程中插入渠道
- 02恢复流程时检测并复用已有渠道
- 03为受支持类型创建未激活渠道
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill service-de-channel-create安装后在 Claude Code 中输入/service-de-channel-create确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/apple.mdconnect-insert.mdfacebook.mdline.mdsms.mdwhatsapp.mdworked-examples.md详细介绍
插入消息渠道
参考文件索引
| 参考文件 | 加载时机 |
|---|---|
references/whatsapp.md | MESSAGE_TYPE=WhatsApp — WABA 合作关系(阶段 2)+ SF 侧管理 WABA(阶段 3)的前置条件、请求正文、WhatsApp 注意事项。 |
references/line.md | MESSAGE_TYPE=Line — 可选的令牌验证、authDetails 正文结构、LINE 注意事项。 |
references/apple.md | MESSAGE_TYPE=AppleBusinessChat — 空配置正文、Apple 前置条件错误、Apple 注意事项。 |
references/facebook.md | MESSAGE_TYPE=Facebook — 浏览器 OAuth → 页面选择流程、Facebook 注意事项。 |
references/sms.md | MESSAGE_TYPE=Text — 电话号码平台密钥、存在但未经验证的 smsProvider、号码配置前置条件(由调用方提供)、短信注意事项。 |
references/connect-insert.md | 通用的 authProviderId 查询 + Connect POST + 响应分类详情(阶段 4)。 |
references/worked-examples.md | 每种类型的端到端跟踪示例(全新插入、预检短路、OAuth)。 |
此技能的功能
给定 {MESSAGE_TYPE} 及其输入,创建一条 MessagingChannel 记录,设置
PlatformType=Enhanced、IsActive=false。同一条服务器端路径
(LiveMessageSetupServiceImpl.addChannel → LiveMessageChannelsUtil.findOrCreateCsotMessagingChannel)
也会插入一条 MessagingChannelUsage 记录(全新插入时为 DeploymentType=Conversation、DeploymentStatus=New
——对于曾激活后又停用的渠道,则为 Disabled),这是附带完成的。
所有类型统一使用一条主路径:Connect REST API。
使用由 sf api request rest 管理的组织 OAuth 会话,调用
POST /services/data/v{V}/connect/livemessage/channels。该 API 公开、受支持,自 API v66+ 起可用。无需 Aura 会话,也不会将令牌提取到 shell 状态中。
基于 (MessagingPlatformKey, MessageType) 实现幂等——使用相同的平台密钥再次调用时,会返回现有渠道的 id。由于无法通过标准方式删除 MessagingChannel 记录,预检(阶段 1)实际上是幂等性保障。
所有类型都返回相同的封装结构,因此插入后调用方无需按类型分支:
{ok, channelId, mcuId, developerName, isActive:false, messageType, messagingPlatformKey, path, created, durationMs}。
支持的类型(唯一的分支点)
{MESSAGE_TYPE} | 平台密钥(messagingPlatformKey) | 前置条件(由调用方提供) | 正文附加项 | 参考文件 |
|---|---|---|---|---|
WhatsApp | 电话号码 ID | service-de-waba-integrate(在阶段 2-3 运行) | externalAccountId、isoCountryCode | whatsapp.md |
Line | LINE 渠道 id | LINE id + secret + token(LINE Developers Console) | authDetails.{client_secret,access_token} | line.md |
AppleBusinessChat | Apple BC Account ID (GUID) | Apple BC Account ID (register.apple.com/business-chat) | (无 — 空配置) | apple.md |
Facebook | Facebook Page ID | 内部处理 OAuth | authDetails: {}(由 OAuth 派生) | facebook.md |
Text | 电话号码 / 短代码 | 号码已完成配置(由调用方提供) | smsProvider(键为必填,值可以是 "")、isoCountryCode | sms.md |
不支持的类型(需要专属的分支逻辑,尚未构建):WeChat、MsCopilot、Alexa。
对于这些类型,返回 unsupported-type 封装(阶段 0)。
以下情况不要使用此技能
- 端到端配置。 如果用户希望完成插入 → 路由 → 激活,请调用
service-de-headless-channel-configure— 它会编排此技能以及路由、同意和激活。此技能仅处理插入步骤(参见阶段 0)。 - 渠道已存在,且你只想重新配置路由 / 激活。 跳过插入;
使用现有的
channelId调用service-de-channel-routing-configure/service-de-channel-activate。
输入(来自调用方)
{MESSAGE_TYPE}— 以下值之一:WhatsApp、Line、AppleBusinessChat、Facebook、Text。 区分大小写(与 SF 选项列表一致)。Text是 SMS 的枚举值,不存在SMS类型。{ORG_ALIAS}— 可选;sfCLI 目标组织别名。默认值:sf config get target-org。{API_VERSION}— 可选;Connect API 版本。默认值:组织当前的 API 版本 (sf org display --json | jq -r '.result.apiVersion')。始终使用组织的原生版本,以 避免出现 404。{CHANNEL_NAME}— Setup → Messaging Settings 中显示的名称。AppleBusinessChat必填(不支持使用电话号码/屏幕名称作为备用值);其他类型未提供时会提示输入。
按类型区分的输入:
- WhatsApp:
{WABA_ID}(作为externalAccountId发送)、{PHONE_NUMBER_ID}(平台 键 — 必须与 WABA_ID 不同)、{ISO_COUNTRY_CODE}(可选,默认值为US)。 - Line:
{LINE_CHANNEL_ID}(平台键)、{LINE_CHANNEL_SECRET}、{LINE_ACCESS_TOKEN}、{VERIFY_TOKEN_FIRST}(可选,默认值为true)。 - AppleBusinessChat:
{APPLE_BC_ID}(平台键,GUID)。 - Facebook:
{PAGE_ID}(可选 — 若缺失,OAuth 会获取或提示输入)。 - Text(SMS):
{SMS_NUMBER}(平台键 — 电话号码/短代码字符串,已预配给组织)、{SMS_PROVIDER}(作为smsProvider发送 — 正文中必须包含smsProvider键,否则插入操作会返回 400 错误“SMS provider is missing”,但其值不会经过验证或存储,因此空字符串""可用;调用方未提供服务商时,默认使用"")、{ISO_COUNTRY_CODE}(可选,默认值为US)。此技能不会预配号码 — 调用方必须已知晓自己的号码。
输出(给调用方)
成功:
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "developerName": "...",
"isActive": false, "messageType": "{MESSAGE_TYPE}", "messagingPlatformKey": "...",
"path": "connect" | "preflight", "created": true | false, "durationMs": 1234}
created: false + path: "preflight" 表示该记录已存在,并以幂等方式返回。
失败(通用):
{"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 的 partnership-blocked、user-declined、waba-manage-failed、
meta-precondition;LINE 的 line-token-invalid;Apple 的 apple-precondition;
Facebook 的 oauth-not-complete、oauth-failed)记录在各类型的参考文件中。
阶段 0:强制仅由编排器调用
此技能仅供内部使用。 只能由
service-de-headless-channel-configure 编排器调用。
如果用户的原始请求是“安装”“设置”“创建”或“激活”消息渠道(或任何暗示需要端到端设置的表述),请立即返回:
{
"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"
}
并向用户显示:
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以编程方式调用 - 用户明确表示只想插入(不需要路由或激活)
- 这是重试/恢复场景,且路由/激活已成功
如有疑问,请转交给编排器。
阶段 0.1:验证类型和必需输入
在支持类型表中查找 {MESSAGE_TYPE}。如果不存在,则输出 unsupported-type
封装并返回。
验证该类型的必需输入是否齐全(Facebook 除外 — 没有必需输入;OAuth → 页面选择 → 提示输入均在 Facebook 流程中完成)。如果有任何输入缺失,则输出包含缺失项列表的 missing-input 并返回。
**仅限 WhatsApp:**另须断言 {WABA_ID} != {PHONE_NUMBER_ID} — 它们是不同的 Meta
实体,将同一个值用于两者会导致难以诊断的激活失败。如果两者相同,则输出
{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."}。
捕获 {T0} = Date.now(),用于计算 durationMs。
阶段 0.2:建立临时目录
此技能写入的所有临时产物(预检查询结果、请求正文、Connect 响应)都放在每次运行专用的临时目录中,该目录位于执行环境的工作区域内——绝不使用裸露的绝对 /tmp 路径,因为该路径可被所有用户写入且不具可移植性。只需建立一次:
SCRATCH_DIR="$(mktemp -d "${outputDir:-${TMPDIR:-/tmp}}/icc.XXXXXX")"
基础目录为执行环境提供的 ${outputDir}(若已设置),否则为用户私有的 ${TMPDIR},再否则才将 /tmp 作为最后的选择。所有后续阶段(以及参考流程)都写入 ${SCRATCH_DIR}/ 下;持久化报告保留在 ${outputDir} 下。
阶段 1:预检——该渠道是否已存在?
首先通过 SOQL 查询是否存在相应记录;若找到,即可跳过所有前置条件和插入操作。
如果 Facebook 未提供 {PAGE_ID},则跳过此阶段——必须等 OAuth → 页面选择完成后才能确定页面 ID(参见 references/facebook.md)。在选择页面后执行预检。
使用两个查询,而不是子查询。 (SELECT ... FROM MessagingChannelUsages) 子查询在子关系名称不可用的组织中会因 INVALID_TYPE 失败(已在 wadtesting / PRODDEBUG 上验证)。按外键键入的形式则适用于所有情况。
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:
记录sf data query --target-org '{ORG_ALIAS}' \ --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \ --json > "${SCRATCH_DIR}/preflight-mcu.json"{MCU_ID}(若 MCU 为空:标记此情况,不要失败——激活技能会处理)。 - 输出
path: "preflight", created: false的成功结果封装。返回。
如果 records.length > 1:存在歧义(唯一性约束本应避免这种情况)——使用第一条记录,并在结果封装中注明警告。
如果 records.length === 0:继续阶段 2。
阶段 2-3:特定类型的前置条件
大多数类型在此处没有前置条件——直接进入阶段 4。
- **WhatsApp:**需要建立 Meta WABA↔Salesforce 合作关系(阶段 2,通过
service-de-waba-integrate),并设置 SF 侧的 webhook 订阅和信用额度共享 (阶段 3,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):**插入时无前置步骤。号码必须已配置到组织中,且调用方必须知道其
smsProvider,但此技能不会执行任何配置步骤——它为调用方已有的号码插入记录。(ESP 注册在激活时执行,而不是插入时执行——参见references/sms.md。)
阶段 4:通过 Connect REST API 插入
所有类型共用此流程。每种类型唯一的差异是请求正文;authProviderId 查询、POST 调用以及响应分类表都完全相同。
阶段 4.1:authProviderId(组织级;除 Text 外所有类型都必需)
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id FROM AuthProvider WHERE DeveloperName = 'LiveMessageSetup'" \
--json > "${SCRATCH_DIR}/authprovider.json"
一行 → {AUTH_PROVIDER_ID}。零行 → 按类型分支:
Text— 非致命。Text 的 Connect 签名不接受authProviderId,因此将{AUTH_PROVIDER_ID}留空并继续执行阶段 4.2;POST 会完全省略该参数(见下文)。 对 Text 不要发出业务错误——从未运行过 LiveMessage 设置的组织,正是仅使用 Text 路径的适用场景;在此处阻止会导致这些组织无法使用宣传的 SMS 插入功能。- 其他所有类型(
WhatsApp、Line、AppleBusinessChat、Facebook)——该组织从未运行过 LiveMessage 设置,无法继续插入;发出{ok:false, kind:"business", message:"No AuthProvider 'LiveMessageSetup' found — run the Messaging Setup wizard once on this org first"}。
如果缺少此 URL 参数,Connect 端点会以 400 ILLEGAL_QUERY_PARAMETER_VALUE "Missing argument authProviderId" 拒绝 POST——已针对除 Text 外的所有类型验证,包括 Apple。(Text 有自己的
Connect 签名,不需要此参数。)如果查到一行,则所有类型(包括 Text)都发送
authProviderId(在 Text 中无影响);如果未查到,则只有 Text 可以继续,且继续时不带此参数。
authProviderId 必须放在 URL 查询字符串中,绝不能放在请求体中(请求体中包含该字段会返回
400 JSON_PARSER_ERROR "Unrecognized field 'authProviderId'")。
阶段 4.2:构建特定类型的请求体并 POST
先解析 {CHANNEL_NAME}(如果必需但未提供,则提示输入——见各类型参考)。然后根据支持类型表
构建请求体并 POST。将请求体写入文件(--body @...)——LINE 令牌很长,且可能包含 shell
不兼容的字符。
仅当阶段 4.1 解析出 authProviderId 时,才附加该查询参数——否则(没有 AuthProvider 的
Text 情况)POST 到不带参数的端点,因为空的 ?authProviderId= 也会触发相同的
ILLEGAL_QUERY_PARAMETER_VALUE:
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 会在请求体之前打印 HTTP 状态和标头块——应从中读取状态,而不是读取末尾的
-w 标记。sf api request rest 会在内部管理 OAuth。
要查看准确的各类型请求体结构和完整的响应分类表(包括返回 Stage 1 的
DUPLICATE_VALUE 竞态、Facebook 的 AUTH_PROVIDER_NEEDS_AUTH 401 与 400 的区分,以及各类型的前置条件错误),请加载 references/connect-insert.md 及相应类型的参考文档。
阶段 5:验证插入结果
执行两个查询(原因与阶段 1 中不能使用子查询相同)。仅使用通用字段集——
ExternalAccountId 和 MessagingPlatform 并非所有组织都存在。
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
预期结果:渠道 IsActive=false;恰好有一个 MCU,DeploymentType=Conversation,
DeploymentStatus 属于 {New, Disabled}(新插入的记录状态为 New;曾激活后又停用的渠道状态为
Disabled——两者都是激活前的有效初始状态)。
MCU 查询结果为空 → {ok:false, kind:"verify-failed", hint:"Connect returned {CHANNEL_ID} but no child MessagingChannelUsage was created"}。
以数据库为准,而不是 Connect 响应体,来确认 MasterLabel / DeveloperName——201 响应体
包含过时的输入投影(见各类型注意事项)。
阶段 6:向调用方报告
{"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>}
直接调用(而非由编排器调用)时的输出:
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}
成功后,调用方可立即使用返回的 channelId 调用
service-de-channel-routing-configure,然后调用 service-de-channel-settings-configure,最后调用
service-de-channel-activate。这三个工具与消息类型无关(它们基于
MessagingChannel.Id + MessagingChannelUsage.Id 操作),因此下游无需考虑分发。
注意事项(通用)
- 类型名称区分大小写。
"whatsapp"≠"WhatsApp"—— SF 选项列表、SOQL 筛选条件 和 Connect 枚举都要求完全匹配。应在上游进行规范化;不要让阶段 0.1 忽略大小写。 - 预检是幂等性保障。
MessagingChannel行无法通过标准方式删除,因此重复运行时依赖阶段 1 找到现有行。 - 封装结构与调用方紧密耦合。 如果在此处重命名字段,每个调用方
(尤其是
service-de-headless-channel-configure)都会无提示地感知此变化——应在测试中明确报错,而不是重新映射字段名。 - 各类型的注意事项记录在参考文件中。 WABA/phone-id 混淆、LINE 长效与短效令牌、Apple 的空配置请求体 + 不调用预配置服务,以及 Facebook OAuth 页面选择 + 401 错误区分——调试相应类型时,请加载匹配的
references/<type>.md。