关于 service-de-channel-activate
用于 Salesforce 的技能,通过 sf CLI 更改 MessagingChannelUsage 状态来激活已有消息渠道。它会先检查渠道、路由和使用记录,并报告配置结果或失败原因。
主要功能
- 01检查渠道、路由和使用记录
- 02通过 REST PATCH 激活
- 03处理同步配置结果
- 04返回结构化结果与错误详情
适用场景
- 01激活已配置的消息渠道
- 02排查配置为何被拒绝
- 03避免重复激活已启用渠道
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill service-de-channel-activate安装后在 Claude Code 中输入/service-de-channel-activate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/gotchas.mdphone-verification.mdworked-examples.md详细介绍
激活消息渠道
此技能的作用
给定一个 {CHANNEL_ID},读取该渠道的 MessagingChannelUsage.Id,然后发送 PATCH /services/data/v{V}/sobjects/MessagingChannelUsage/{MCU_ID},请求正文为 {"DeploymentStatus":"Provisioning"}。服务端调用链如下:
MessagingChannelUsageFunctions.validateBeforeSave运行validateDeploymentStatus→validateChannelReadinessOnProvisioning。对于 WhatsApp,此步骤会确认已配置同意设置。被拒绝的写入会返回 HTTP 400FIELD_INTEGRITY_EXCEPTION。MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnce会在 UPDATE 语句执行后无条件触发。它会调用MessagingChannelUsageFunctionsHelper.handlePostSave,后者会注册一个提交后TransactionObserver。- 提交时,观察器会调用
ConversationChannelUsageDeploymentStatusService.handleProvisioning(继承自AbstractChannelUsageDeploymentStatusService),该方法会:- 调用
runProvisioning,按MessageType使用switch分派外部调用:WHATS_APP→registerCsotWhatsAppNumber→LiveMessageSetupApi.registerWhatsAppNumber→ Meta/register+ 状态验证。耗时 15-21 秒。FACEBOOK→metaGraphApiService.subscribeFacebookPage。TEXT→registerCsotSms。AppleBusinessChat、Line及其他所有类型 →default分支,不进行外部调用,也无需等待网络响应。
- 成功时:通过 PLSQL 将
DeploymentStatus = 'Active'写入数据库。 - 失败时:将
DeploymentStatus = 'Error'以及ErrorReason/ErrorDetails写入数据库。
- 调用
- 在同一个观察器中,第二轮处理会在 MCU 达到
Active后将MessagingChannel.IsActive = true同步更新(当状态仍为Provisioning时,isTransitioningStatus标志会跳过此更新)。
所有步骤都会在 PATCH 请求中同步完成——只有完整调用链执行完毕后才会返回 204 响应。WhatsApp:约 15-21 秒(Meta /register 往返耗时)。Apple / Line:约 1 秒(无外部调用;只执行本地保存钩子、观察器和 PLSQL 写入)。已于 2026-04-30 在 wadtesting 上验证。
参考文件索引
| 参考文件 | 何时加载 |
|---|---|
references/phone-verification.md | 第 3 阶段返回 ErrorReason === "VERIFICATION_REQUIRED" 时(仅限 WhatsApp)——电话号码 OTP 验证子流程。 |
references/worked-examples.md | 需要参考 WhatsApp 成功流程、Apple 激活、就绪状态检查失败或已处于激活状态时的空操作示例。 |
references/gotchas.md | 排查意外结果,或修改此技能前——其中列出了 11 个已知注意事项。 |
为什么使用 REST PATCH,而不是 Apex?
直接使用 REST PATCH 会产生与旧 activateChannelUsage Apex 方法完全相同的保存钩子调用链,同时大幅减少所需操作——无需获取 CSRF Cookie、无需引导请求、无需解析 Aura 响应,也无需处理双层封装的 returnValue。REST 语义清晰:204 表示状态转换成功;4xx 表示转换失败。
代码依据:MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnce 会在任何 DML 路径(REST、SOAP、Apex、Metadata API)上触发——不存在仅适用于 Apex 的门控。实体 XML(MessagingChannelUsage.entity.xml)将 DeploymentStatus 标记为 editAccess="always",且没有 <readonly> 属性。状态转换验证器(getValidAPIStatusTransitions)允许 Disabled → Provisioning(以及 New → Provisioning、Error → Provisioning | Deprovisioning 和 Active → Deprovisioning)。仅限数据库的状态转换 Provisioning → Active | Error 则由观察器的 PLSQL 调用处理——因此我们只写入 Provisioning,由服务器决定最终状态。
不应使用此技能的情况
- 通道的
IsActive=true。 API 转换验证器会阻止再次触发(Active → Provisioning不在getValidAPIStatusTransitions()中),因此 PATCH 会返回 400。Stage 1 的前置条件检查会捕获这种情况,并输出noop:true。 - 未配置路由。 以前,
activateChannelUsage会在 Apex 入口处因LiveMessageSetupException/nullQueueId而失败。使用 REST 路径时,同样的检查位于validateChannelReadinessOnProvisioning中——当SessionHandlerId=null && FallbackQueueId=null时写入会返回 400FIELD_INTEGRITY_EXCEPTION。请先运行service-de-channel-routing-configure。Stage 1 检查仍会执行防御性校验。 - MCU 不存在。 无法 PATCH 缺失的行。请先运行插入技能——它会始终将 MCU 作为
addChannel的副作用创建。
输入(来自调用方)
{CHANNEL_ID}— 15 或 18 个字符的MessagingChannel.Id(前缀为0Mj)。通道必须已存在,且SessionHandlerId或FallbackQueueId不为 null。{ORG_ALIAS}— 可选;sfCLI 目标组织别名。默认值:sf config get target-org返回的值。用于 OAuth 和 SOQL 读取。{API_VERSION}— 可选;REST API 版本。默认值:68.0。任何支持将MessagingChannelUsage作为标准 sobject 访问的版本均可(v50+ 应该可用;未进行全面测试)。
与此技能基于 Aura 的旧版本不同,没有 {POLL_TIMEOUT_S} / {POLL_INTERVAL_S} 输入——PATCH 端到端同步执行。
输出(给调用方)
成功——通道已上线:
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "deploymentStatus": "Active", "isActive": true, "messageType": "WhatsApp", "durationMs": 20934}
成功——无操作(已激活):
{"ok": true, "noop": true, "channelId": "0Mj...", "message": "Channel already active"}
失败——未满足前置条件:
{"ok": false, "kind": "no-routing", "hint": "run service-de-channel-routing-configure first"}
{"ok": false, "kind": "no-mcu", "hint": "no MessagingChannelUsage row for this channel — run the insertion skill first"}
{"ok": false, "kind": "channel-missing","hint": "MessagingChannel id not found"}
失败——验证器或服务端配置错误:
{"ok": false, "kind": "readiness-failed", "errorCode": "FIELD_INTEGRITY_EXCEPTION",
"message": "...", "hint": "validateChannelReadinessOnProvisioning rejected the write — most commonly missing consent; run service-de-channel-settings-configure first"}
{"ok": false, "kind": "provisioning-error", "mcuId": "0gL...", "errorReason": "MetaRegistrationFailed", "errorDetails": "..."}
{"ok": false, "kind": "verification-failed", "hint": "WhatsApp phone number verification failed or was declined by user"}
{"ok": false, "kind": "verification-request-failed", "errorCode": "...", "message": "...", "hint": "Could not request verification code from Meta"}
失败——身份验证 / 传输:
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "transport", "status": 500, "message": "..."}
Stage 1:前置条件检查
先读取通道,再读取其 MCU,分别执行两次 SOQL 调用。合并子查询虽然更省资源,但在子关系名称不可用的组织中会失败(参见注意事项 #4)。
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeveloperName, MessageType, IsActive, SessionHandlerId, FallbackQueueId, MessagingPlatformKey FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" \
--json > /tmp/amc-precheck-channel.json
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeploymentStatus, DeploymentType, ErrorReason, ErrorDetails FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \
--json > /tmp/amc-precheck-mcu.json
将 channel = /tmp/amc-precheck-channel.json records[0] 和 mcu = /tmp/amc-precheck-mcu.json records[0] 作为:
| 条件 | 返回值 |
|---|---|
| 通道查询返回 0 条记录 | {ok:false, kind:"channel-missing", hint:"MessagingChannel id not found"} |
channel.IsActive === true | {ok:true, noop:true, channelId, message:"Channel already active"} — 返回 |
channel.SessionHandlerId == null && channel.FallbackQueueId == null | {ok:false, kind:"no-routing", hint:"run service-de-channel-routing-configure first"} |
| MCU 查询返回 0 条记录 | {ok:false, kind:"no-mcu", hint:"no MessagingChannelUsage row for this channel — run the insertion skill first"} |
| 其他情况 | 记录 {MCU_ID} = mcu.Id、{MESSAGE_TYPE} = channel.MessageType、{MESSAGING_PLATFORM_KEY} = channel.MessagingPlatformKey、{INITIAL_MCU_STATUS} = mcu.DeploymentStatus,然后继续 Stage 2。 |
同时记录 {T0}(Stage 2 开始时的纪元毫秒),以便最终返回值报告 durationMs。
Stage 1.1:MCU 已处于配置中状态时的快速路径
如果 {INITIAL_MCU_STATUS} === "Provisioning",则 MCU 已因本次事务窗口内的先前调用而处于处理中状态。完全跳过 Stage 2(触发 PATCH),直接跳到 Stage 3(验证)。这是一个罕见的竞态保护:观察器与 PATCH 同步执行,因此调用方看到 204 时,状态已经是终态(Active 或 Error)——外部本不应看到 Provisioning。如果前置检查中确实看到该状态,说明有其他 DML 将其写入 Provisioning,且观察器仍在处理中——不要再次触发 PATCH。
阶段 2:将 MessagingChannelUsage.DeploymentStatus = "Provisioning" 设为 PATCH
使用 sf api request rest,让身份验证留在 CLI 的传输过程中,不会将 OAuth 令牌提取到 shell 状态中。
执行 PATCH。对于 WhatsApp,此调用可能需要 15-30 秒——观察器会同步运行,包括 Meta 的 /register 往返调用。sf api request rest 没有单独的客户端超时设置;它会等待底层 HTTP 调用完成。
sf api request rest \
"/services/data/v{API_VERSION}/sobjects/MessagingChannelUsage/{MCU_ID}" \
--method PATCH \
--target-org '{ORG_ALIAS}' \
--header 'Content-Type: application/json' \
--body '{"DeploymentStatus":"Provisioning"}' \
--include \
> /tmp/amc-patch-response.txt 2>&1
HTTP_CODE=$(head -1 /tmp/amc-patch-response.txt | grep -oE '[0-9]{3}')
--include 会在响应正文前打印 HTTP 状态/标头块(在 204 响应中,正文通常为空)——请从该块中读取状态行,不要使用类似 -w 的尾随标记。
根据 HTTP 状态进行分类:
| 状态 | 正文 | 处理方式 |
|---|---|---|
| 204 | 空 | 成功。观察器已运行完成;MCU 状态为 Active 或 Error。继续执行阶段 3,读取终态。 |
| 400 | [{errorCode:"FIELD_INTEGRITY_EXCEPTION", message:"..."}] | 验证器拒绝。请参阅下表。 |
| 400 | [{errorCode:"INVALID_FIELD_FOR_INSERT_UPDATE" or "MALFORMED_ID", ...}] | Skill 错误——阶段 1 中的 MCU_ID 有误,或正文格式不正确。输出 {ok:false, kind:"transport", status:400, message}。 |
| 401 | (通常为空) | Bearer 令牌无效。输出 {ok:false, kind:"auth", hint:"OAuth token invalid / expired — run 'sf org login web'"}。 |
| 403 | [{errorCode:"INSUFFICIENT_ACCESS"}] | 用户没有写入 DeploymentStatus 的权限。输出 {ok:false, kind:"business", errorCode:"INSUFFICIENT_ACCESS", message}。 |
| 5xx | 不定 | 传输错误。观察器可能已部分提交——阶段 3 的 SOQL 查询是事实依据。读取 MCU 状态;如果为 Active,则报告成功并附带警告;如果为 Error 或仍为 Disabled,则分类为 {ok:false, kind:"transport", status, message}。 |
已知的 400 FIELD_INTEGRITY_EXCEPTION 消息:
| 消息片段 | 含义 | Envelope |
|---|---|---|
| "invalid deployment status transition" | 当前状态不允许转换为 → Provisioning(例如 MCU 已为 Active——阶段 1 本应检测到,但存在竞态窗口)。 | 重新读取 MCU;如果现在为 Active,则输出 success-noop。如果为 Provisioning,则轮询阶段 3。否则输出 {ok:false, kind:"readiness-failed", ...}。 |
| "consent" / "keyword" / 提及 STOP/HELP | validateChannelReadinessOnProvisioning 拒绝了请求——频道未配置所需的同意信息。 | {ok:false, kind:"readiness-failed", errorCode:"FIELD_INTEGRITY_EXCEPTION", message, hint:"channel requires a ConsentType and a matching MsgChannelLanguageKeyword record (opt-out keyword + confirmation) before activation — run service-de-channel-settings-configure"}。 |
| "routing" / "queue" / "SessionHandler" | 路由前置条件(阶段 1 本应检测到,但验证器会再次检查)。 | {ok:false, kind:"no-routing", message, hint:"run service-de-channel-routing-configure first"}。 |
| 其他 | 无法识别的验证器错误。 | {ok:false, kind:"readiness-failed", errorCode, message}。 |
阶段 3:读取 MCU 终态
PATCH 是同步的,因此到达这里时,MCU 已为 Active 或 Error——无需轮询。读取一次:
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, DeploymentStatus, ErrorReason, ErrorDetails FROM MessagingChannelUsage WHERE Id = '{MCU_ID}'" \
--json > /tmp/amc-poststate-mcu.json
| 状态 | 处理方式 |
|---|---|
Active | 继续执行阶段 4。 |
Error,且 ErrorReason === "VERIFICATION_REQUIRED" | 仅适用于 WhatsApp 频道:电话号码需要通过 OTP 验证。继续执行阶段 3.2(WhatsApp 验证流程)。 |
Error(其他) | 输出 {ok:false, kind:"provisioning-error", mcuId, errorReason, errorDetails}。 |
Provisioning | 意外情况——观察器本应在 PATCH 返回 204 前终止。转入防御性轮询(参见阶段 3.1)。 |
Disabled | PATCH 返回了 204,但写入似乎未生效?输出 {ok:false, kind:"transport", message:"PATCH returned 204 but MCU is still Disabled — observer didn't commit"}。 |
阶段 3.2:WhatsApp 电话号码验证(仅当 ErrorReason === "VERIFICATION_REQUIRED" 时)
此子流程仅适用于 WhatsApp 渠道,且仅在激活因 ErrorReason === "VERIFICATION_REQUIRED" 而失败时运行——该电话号码需要先通过 Meta 的 OTP 验证,才能完成注册。如果 MCU 返回 ErrorReason === "VERIFICATION_REQUIRED"(仅限 WhatsApp),请加载 references/phone-verification.md 并按其中说明操作,执行电话号码验证子流程(请求验证码 → 提示用户 → 验证验证码 → 重试一次激活)。
阶段 3.1:防御性轮询(仅当阶段 3 发现状态为 Provisioning 时)
观察器的外部调用模块(WhatsApp/Facebook/SMS)会在 PATCH 请求中同步运行——对于任何消息类型,runProvisioning 都没有异步队列间接调用(已根据 ConversationChannelUsageDeploymentStatusService 中的 switch/case 逻辑进行验证)。因此,正常情况下,阶段 3 不应出现 Provisioning 状态。如果出现,可能的原因包括:外部调用成功后、PLSQL 终态写入前抛出了异常;实例配置不寻常,导致观察器异步执行;或未来出现了我们尚未考虑其分派行为的 MessageType。如果阶段 3 返回 Provisioning,请循环执行:
for i in 1 2 3 4 5 6 7 8 9 10; do
sleep 3
STATUS=$(sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT DeploymentStatus FROM MessagingChannelUsage WHERE Id='{MCU_ID}'" --json \
| node -e 'console.log(JSON.parse(require("fs").readFileSync(0,"utf8")).result.records[0].DeploymentStatus)')
case "$STATUS" in
Provisioning) continue ;;
Active|Error) break ;;
esac
done
最长等待时间:30s。如果循环结束后仍为 Provisioning:输出 {ok:false, kind:"timeout", mcuId, lastStatus:"Provisioning", elapsedS:30}。已于 2026-04-30 在 WhatsApp/wadtesting 上验证:实际上此循环不应执行。
阶段 4:验证 MessagingChannel.IsActive
观察器会在写入状态终态的同一回调中切换 IsActive。原则上,PATCH 返回时该值应已设置。请确认:
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, IsActive FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json > /tmp/amc-verify-channel.json
如果 IsActive === true:计算 durationMs = Date.now() - T0 并输出成功结果。如果 MCU 为 Active,但 IsActive !== true:则 IsActive 同步环节被跳过(例如,观察器运行时 isTransitioningStatus 为 true,这种情况在终态之后不应发生)。请输出:
{"ok": false, "kind": "provisioning-error", "mcuId": "...", "errorReason": "mcu-active-but-channel-inactive",
"errorDetails": "MCU DeploymentStatus=Active but MessagingChannel.IsActive=false — observer's IsActive sync pass didn't run. Inspect MessagingChannelUsageFunctionsHelper.handlePostSave."}
阶段 5:向调用方报告
构建成功结果对象:
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "{MCU_ID}", "deploymentStatus": "Active",
"isActive": true, "messageType": "{MESSAGE_TYPE}", "durationMs": 20934}
如果此技能是叶节点(用户直接调用了该技能),请输出:
成功 — 已激活 — MessagingChannel {CHANNEL_ID} ({messageType}) 现已上线。MCU DeploymentStatus=Active,IsActive=true。(约 {durationMs/1000}s)信息:已激活 — MessagingChannel {CHANNEL_ID} 的 IsActive=true。未作更改。(无操作路径)错误:未配置路由 — 请先运行 'service-de-channel-routing-configure' 技能。(no-routing)错误:没有 MessagingChannelUsage 行 — 请先运行插入技能。(no-mcu)错误:就绪性检查失败:{message} — 如果涉及 consent/keywords,请先运行 'service-de-channel-settings-configure'。(readiness-failed)错误:激活失败:{errorReason} — {errorDetails}(provisioning-error)错误:WhatsApp 电话号码验证失败:{hint}(verification-failed)错误:无法请求验证码:{message}(verification-request-failed)超时:激活在 {elapsedS}s 后超时,MCU.DeploymentStatus={lastStatus}。已达到防御性轮询上限;这不常见。请在 Setup 中检查 MCU {mcuId}。(timeout)
操作示例
端到端激活过程示例(WhatsApp 顺利完成、Apple 激活、就绪性验证器失败,以及已激活时的无操作情况),请参阅 references/worked-examples.md。
注意事项
已知有十一项注意事项——WhatsApp 的同步 PATCH 时序、有效的 API 状态转换、IsActive 同步环节、不同组织间关系名称的差异、consent 前置条件、DeploymentStatus 选项列表的大小写、REST 与 Apex 的等效性、ErrorReason 值、VERIFICATION_REQUIRED 处理、OAuth token 提取,以及状态码为 409 的 Admin API 冲突。在排查意外结果或修改此技能之前,请加载 references/gotchas.md 并按其中说明操作。