mcpField
S
service-de-channel-create

service-de-channel-create

github.com/forcedotcom其他1.1k656 次安装1 天前更新
代码仓库

关于 service-de-channel-create

供 Salesforce 渠道配置流程调用的内部 skill,用于创建未激活的 Enhanced MessagingChannel。它支持 WhatsApp、Line、AppleBusinessChat、Facebook 和 Text,并通过 Connect REST API 与预检实现幂等插入。

主要功能

  1. 01支持五种消息渠道类型
  2. 02通过 Connect REST API 插入渠道
  3. 03按平台键和类型预检复用记录
  4. 04处理类型专属前置步骤
  5. 05返回统一结果结构

适用场景

  1. 01在完整配置流程中插入渠道
  2. 02恢复流程时检测并复用已有渠道
  3. 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.md
connect-insert.md
facebook.md
line.md
sms.md
whatsapp.md
worked-examples.md

详细介绍

插入消息渠道

参考文件索引

参考文件加载时机
references/whatsapp.mdMESSAGE_TYPE=WhatsApp — WABA 合作关系(阶段 2)+ SF 侧管理 WABA(阶段 3)的前置条件、请求正文、WhatsApp 注意事项。
references/line.mdMESSAGE_TYPE=Line — 可选的令牌验证、authDetails 正文结构、LINE 注意事项。
references/apple.mdMESSAGE_TYPE=AppleBusinessChat — 空配置正文、Apple 前置条件错误、Apple 注意事项。
references/facebook.mdMESSAGE_TYPE=Facebook — 浏览器 OAuth → 页面选择流程、Facebook 注意事项。
references/sms.mdMESSAGE_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电话号码 IDservice-de-waba-integrate(在阶段 2-3 运行)externalAccountId、isoCountryCodewhatsapp.md
LineLINE 渠道 idLINE id + secret + token(LINE Developers Console)authDetails.{client_secret,access_token}line.md
AppleBusinessChatApple BC Account ID (GUID)Apple BC Account ID (register.apple.com/business-chat)(无 — 空配置)apple.md
FacebookFacebook Page ID内部处理 OAuthauthDetails: {}(由 OAuth 派生)facebook.md
Text电话号码 / 短代码号码已完成配置(由调用方提供)smsProvider(键为必填,值可以是 "")、isoCountryCodesms.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} — 可选;sf CLI 目标组织别名。默认值: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 操作),因此下游无需考虑分发。


注意事项(通用)

  1. 类型名称区分大小写。 "whatsapp" ≠ "WhatsApp" —— SF 选项列表、SOQL 筛选条件 和 Connect 枚举都要求完全匹配。应在上游进行规范化;不要让阶段 0.1 忽略大小写。
  2. 预检是幂等性保障。 MessagingChannel 行无法通过标准方式删除,因此重复运行时依赖阶段 1 找到现有行。
  3. 封装结构与调用方紧密耦合。 如果在此处重命名字段,每个调用方 (尤其是 service-de-headless-channel-configure)都会无提示地感知此变化——应在测试中明确报错,而不是重新映射字段名。
  4. 各类型的注意事项记录在参考文件中。 WABA/phone-id 混淆、LINE 长效与短效令牌、Apple 的空配置请求体 + 不调用预配置服务,以及 Facebook OAuth 页面选择 + 401 错误区分——调试相应类型时,请加载匹配的 references/<type>.md。

评论

支持 Markdown