service-de-channel-activate
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseActivating a Messaging Channel
激活消息通道
What this skill does
本技能的作用
Given a , reads the channel's , then fires with body . The server-side chain:
{CHANNEL_ID}MessagingChannelUsage.IdPATCH /services/data/v{V}/sobjects/MessagingChannelUsage/{MCU_ID}{"DeploymentStatus":"Provisioning"}- runs
MessagingChannelUsageFunctions.validateBeforeSave→validateDeploymentStatus. For WhatsApp this confirms consent is configured. Rejected writes return HTTP 400validateChannelReadinessOnProvisioning.FIELD_INTEGRITY_EXCEPTION - fires unconditionally after the UPDATE statement. It calls
MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOncewhich registers a post-commitMessagingChannelUsageFunctionsHelper.handlePostSave.TransactionObserver - At commit, the observer calls (inherited from
ConversationChannelUsageDeploymentStatusService.handleProvisioning), which:AbstractChannelUsageDeploymentStatusService- Calls —
runProvisioning-dispatches byswitchfor the external callout:MessageType- →
WHATS_APP→registerCsotWhatsAppNumber→ MetaLiveMessageSetupApi.registerWhatsAppNumber+ status verification. 15-21s wall-clock./register - →
FACEBOOK.metaGraphApiService.subscribeFacebookPage - →
TEXT.registerCsotSms - ,
AppleBusinessChat, everything else →Linebranch, no external callout, no network wait.default
- On success: writes via PLSQL.
DeploymentStatus = 'Active' - On failure: writes plus
DeploymentStatus = 'Error'/ErrorReason.ErrorDetails
- Calls
- Inside the same observer, a second pass syncs once MCU reaches
MessagingChannel.IsActive = true(theActiveflag skips the flip while status is stillisTransitioningStatus).Provisioning
All synchronous within the PATCH request — the 204 response only comes back after the full chain completes. WhatsApp: ~15-21s (Meta round-trip). Apple / Line: ~1s (no external call; just the local save-hook + observer + PLSQL write). Verified on wadtesting 2026-04-30.
/register给定,读取通道的,然后发起请求,请求体为。服务端流程如下:
{CHANNEL_ID}MessagingChannelUsage.IdPATCH /services/data/v{V}/sobjects/MessagingChannelUsage/{MCU_ID}{"DeploymentStatus":"Provisioning"}- 会执行
MessagingChannelUsageFunctions.validateBeforeSave→validateDeploymentStatus。对于WhatsApp,这一步会确认同意配置已完成。验证不通过的写入请求会返回HTTP 400validateChannelReadinessOnProvisioning。FIELD_INTEGRITY_EXCEPTION - 会在UPDATE语句执行后无条件触发。它会调用
MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnce,后者会注册一个提交后的MessagingChannelUsageFunctionsHelper.handlePostSave。TransactionObserver - 提交时,观察者会调用(继承自
ConversationChannelUsageDeploymentStatusService.handleProvisioning),该方法会:AbstractChannelUsageDeploymentStatusService- 调用——根据
runProvisioning分发外部调用:MessageType- →
WHATS_APP→registerCsotWhatsAppNumber→ 调用Meta的LiveMessageSetupApi.registerWhatsAppNumber接口并验证状态,耗时约15-21秒。/register - →
FACEBOOK。metaGraphApiService.subscribeFacebookPage - →
TEXT。registerCsotSms - 、
AppleBusinessChat及其他通道 → 进入Line分支,无需外部调用,无需等待网络请求。default
- 成功时:通过PLSQL将设置为
DeploymentStatus。Active - 失败时:将设置为
DeploymentStatus,并写入Error和ErrorReason。ErrorDetails
- 调用
- 在同一个观察者中,当MCU状态变为后,会同步更新
Active(MessagingChannel.IsActive = true标志会在状态仍为isTransitioningStatus时跳过此切换操作)。Provisioning
整个流程在PATCH请求内同步完成——只有当整个流程执行完毕后才会返回204响应。WhatsApp通道耗时约15-21秒(包含Meta 接口的往返时间);Apple/Line通道耗时约1秒(无需外部调用,仅包含本地保存钩子、观察者及PLSQL写入操作)。已在wadtesting环境验证,验证时间为2026-04-30。
/registerReference File Index
参考文件索引
| Reference file | Load when |
|---|---|
| Stage 3 comes back with |
| You want a reference run of the WhatsApp happy-path, Apple activation, a readiness failure, or the already-active no-op. |
| Troubleshooting an unexpected result, or before modifying this skill — the eleven known gotchas. |
| 参考文件 | 加载时机 |
|---|---|
| 阶段3返回 |
| 需要参考WhatsApp正常流程、Apple激活流程、就绪检查失败流程或已激活无操作流程的运行示例时。 |
| 排查异常结果或修改本技能前——查看已知的11个注意事项。 |
Why REST PATCH instead of Apex?
为何选择REST PATCH而非Apex?
A direct REST PATCH produces the identical save-hook chain as the old Apex method, with substantially less machinery — no CSRF cookie acquisition, no bootstrap fetch, no Aura response parsing, no double-wrapped . REST semantics are honest: 204 means the transition succeeded; 4xx means it didn't.
activateChannelUsagereturnValueCode proof: fires on any DML path (REST, SOAP, Apex, Metadata API) — there is no Apex-specific gate. The entity XML () marks as with no attribute. The transition validator () allows (and , and , and ). The DB-only transitions are reserved for the observer's PLSQL call — that's why we write and let the server pick the terminal state.
MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnceMessagingChannelUsage.entity.xmlDeploymentStatuseditAccess="always"<readonly>getValidAPIStatusTransitionsDisabled → ProvisioningNew → ProvisioningError → Provisioning | DeprovisioningActive → DeprovisioningProvisioning → Active | ErrorProvisioning直接的REST PATCH操作会触发与旧版 Apex方法完全相同的保存钩子流程,但所需的机制大幅减少——无需获取CSRF Cookie、无需引导请求、无需解析Aura响应、无需双层包装的。REST语义清晰:204表示转换成功;4xx表示转换失败。
activateChannelUsagereturnValue代码证明:会在任何DML路径(REST、SOAP、Apex、Metadata API)下触发——不存在Apex专属的限制。实体XML文件将标记为,且无属性。转换验证器允许(以及、、)。仅数据库层面的状态转换由观察者的PLSQL调用保留——这也是我们写入状态并让服务端决定最终状态的原因。
MessagingChannelUsageFunctions.saveHook_PostStmtExecuteOnceMessagingChannelUsage.entity.xmlDeploymentStatuseditAccess="always"<readonly>getValidAPIStatusTransitionsDisabled → ProvisioningNew → ProvisioningError → Provisioning | DeprovisioningActive → DeprovisioningProvisioning → Active | ErrorProvisioningWhen NOT to use this skill
本技能的不适用场景
- The channel is already . Re-firing is blocked by the API transition validator (
IsActive=trueis not inActive → Provisioning) — the PATCH would return 400. The Stage 1 precondition check catches this and emitsgetValidAPIStatusTransitions().noop:true - Routing isn't configured. used to fail with
activateChannelUsage/LiveMessageSetupExceptionat the Apex entry point. With the REST path the same guard lives innullQueueId— write withvalidateChannelReadinessOnProvisioning→ 400SessionHandlerId=null && FallbackQueueId=null. RunFIELD_INTEGRITY_EXCEPTIONfirst. The Stage 1 check still runs defensively.service-de-channel-routing-configure - The MCU doesn't exist. Can't PATCH a row that's missing. Run the insertion skill first — it always creates the MCU as a side-effect of .
addChannel
- 通道已处于状态:API转换验证器会阻止重复触发(
IsActive=true不在Active → Provisioning允许的范围内)——PATCH请求会返回400。阶段1的前置检查会捕获此情况并返回getValidAPIStatusTransitions()。noop:true - 未配置路由:旧版会在Apex入口点因
activateChannelUsage/LiveMessageSetupException失败。使用REST路径时,相同的校验逻辑位于nullQueueId中——若validateChannelReadinessOnProvisioning,写入请求会返回400SessionHandlerId=null && FallbackQueueId=null。需先执行FIELD_INTEGRITY_EXCEPTION。阶段1的检查仍会进行防御性校验。service-de-channel-routing-configure - MCU不存在:无法对不存在的记录执行PATCH操作。需先执行插入技能——该技能会在的副作用中创建MCU。
addChannel
Inputs (from caller)
调用方输入参数
- — a 15- or 18-char
{CHANNEL_ID}(prefixMessagingChannel.Id). The channel must already exist with a non-null0MjorSessionHandlerId.FallbackQueueId - — optional; the
{ORG_ALIAS}CLI target-org alias. Default: whateversfreturns. Used for OAuth and SOQL reads.sf config get target-org - — optional; REST API version. Default:
{API_VERSION}. Any version where68.0is addressable as a standard sobject is fine (v50+ should work; not exhaustively tested).MessagingChannelUsage
Unlike the old Aura-based version of this skill, there are no / inputs — the PATCH is synchronous end-to-end.
{POLL_TIMEOUT_S}{POLL_INTERVAL_S}- ——15或18位的
{CHANNEL_ID}(前缀为MessagingChannel.Id)。通道必须已存在,且0Mj或SessionHandlerId不为空。FallbackQueueId - ——可选;
{ORG_ALIAS}CLI的目标组织别名。默认值:sf返回的值。用于OAuth认证和SOQL查询。sf config get target-org - ——可选;REST API版本。默认值:
{API_VERSION}。任何可将68.0作为标准sobject访问的版本均可(v50+应该可用;未进行全面测试)。MessagingChannelUsage
与旧版基于Aura的技能不同,本技能无需 / 输入参数——PATCH操作是端到端同步的。
{POLL_TIMEOUT_S}{POLL_INTERVAL_S}Output (to caller)
返回给调用方的结果
Success — channel is live:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "deploymentStatus": "Active", "isActive": true, "messageType": "WhatsApp", "durationMs": 20934}Success — no-op (already active):
json
{"ok": true, "noop": true, "channelId": "0Mj...", "message": "Channel already active"}Failure — precondition not met:
json
{"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"}Failure — validator or server-side provisioning error:
json
{"ok": false, "kind": "readiness-failed", "errorCode": "FIELD_INTEGRITY_EXCEPTION",
"message": "...", "hint": "validateChannelReadinessOnProvisioning rejected the write — most commonly missing consent; run service-de-channel-consent-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"}Failure — auth / transport:
json
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "transport", "status": 500, "message": "..."}成功——通道已启用:
json
{"ok": true, "channelId": "0Mj...", "mcuId": "0gL...", "deploymentStatus": "Active", "isActive": true, "messageType": "WhatsApp", "durationMs": 20934}成功——无操作(通道已激活):
json
{"ok": true, "noop": true, "channelId": "0Mj...", "message": "Channel already active"}失败——未满足前置条件:
json
{"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"}失败——验证器或服务端预配错误:
json
{"ok": false, "kind": "readiness-failed", "errorCode": "FIELD_INTEGRITY_EXCEPTION",
"message": "...", "hint": "validateChannelReadinessOnProvisioning rejected the write — most commonly missing consent; run service-de-channel-consent-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"}失败——认证/传输错误:
json
{"ok": false, "kind": "auth", "hint": "OAuth token invalid / expired — run 'sf org login web'"}
{"ok": false, "kind": "transport", "status": 500, "message": "..."}Stage 1: Precondition checks
阶段1:前置条件检查
Read the channel, then its MCU, as two separate SOQL calls. A combined subquery would be cheaper but fails on orgs where the child relationship is unnameable (see gotcha #4).
bash
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.jsonLet and :
channel = /tmp/amc-precheck-channel.json records[0]mcu = /tmp/amc-precheck-mcu.json records[0]| Condition | Envelope |
|---|---|
| Channel query returned 0 records | |
| |
| |
| MCU query returned 0 records | |
| Otherwise | Record |
Also record (epoch ms at start of Stage 2) so the final envelope can report .
{T0}durationMs通过两次独立的SOQL查询读取通道及其对应的MCU。虽然联合子查询更高效,但在子关系不可命名的组织中会失败(参见注意事项#4)。
bash
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条记录 | |
| |
| |
| MCU查询返回0条记录 | |
| 其他情况 | 记录 |
同时记录(阶段2开始时的时间戳,单位为毫秒),以便最终结果中可以返回。
{T0}durationMsStage 1.1: Fast path for already-provisioning MCU
阶段1.1:已处于预配状态的MCU快速路径
If — the MCU is already mid-flight from a prior call in this transaction window. Skip Stage 2 (firing the PATCH) entirely and jump to Stage 3 (verification). This is a rare race guard: the observer is synchronous with the PATCH, so by the time the caller sees the 204 the status is already terminal ( or ) — should be invisible from outside. If we do see it in the precheck, something wrote in a separate DML and the observer is still mid-flight — don't fire a second PATCH.
{INITIAL_MCU_STATUS} === "Provisioning"ActiveErrorProvisioningProvisioning如果——MCU已在当前事务窗口中由之前的调用触发预配流程。跳过阶段2(发起PATCH请求),直接进入阶段3(验证状态)。这是一种罕见的竞争防护机制:观察者与PATCH操作同步执行,因此当调用方收到204响应时,状态已变为最终状态(或)——外部应该看不到状态。如果前置检查中确实看到了该状态,说明有其他DML操作写入了状态,且观察者仍在执行中——请勿再次发起PATCH请求。
{INITIAL_MCU_STATUS} === "Provisioning"ActiveErrorProvisioningProvisioningStage 2: PATCH MessagingChannelUsage.DeploymentStatus = "Provisioning"
MessagingChannelUsage.DeploymentStatus = "Provisioning"阶段2:PATCH更新MessagingChannelUsage.DeploymentStatus = "Provisioning"
MessagingChannelUsage.DeploymentStatus = "Provisioning"Use so authentication stays inside the CLI's transport — no OAuth token is ever extracted into shell state.
sf api request restFire the PATCH. This call can take 15-30 seconds for WhatsApp — the observer runs synchronously, including Meta's round trip. has no separate client-side timeout to raise; it waits on the underlying HTTP call.
/registersf api request restbash
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-wClassify by HTTP status:
| Status | Body | Handling |
|---|---|---|
| 204 | empty | Success. The observer ran to completion; MCU is |
| 400 | | Validator rejection. See table below. |
| 400 | | Skill bug — the MCU_ID from Stage 1 was wrong, or the body shape is off. Emit |
| 401 | (usually empty) | Bearer token invalid. Emit |
| 403 | | User lacks perm to write |
| 5xx | varies | Transport. The observer may have partially committed — Stage 3's SOQL is the source of truth. Read MCU state; if it's |
Known 400 messages:
FIELD_INTEGRITY_EXCEPTION| Message fragment | Meaning | Envelope |
|---|---|---|
| "invalid deployment status transition" | The current status doesn't allow | Re-read MCU; if now |
| "consent" / "keyword" / mentions of STOP/HELP | | |
| "routing" / "queue" / "SessionHandler" | Routing precondition (Stage 1 should have caught, but the validator re-checks). | |
| other | Unrecognized validator error. | |
使用命令,确保认证操作在CLI的传输层内完成——OAuth令牌不会被提取到shell状态中。
sf api request rest发起PATCH请求。对于WhatsApp,此请求可能需要15-30秒——观察者会同步执行,包括Meta 接口的往返时间。没有单独的客户端超时设置;它会等待底层HTTP请求完成。
/registersf api request restbash
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-w根据HTTP状态码分类处理:
| 状态码 | 响应体 | 处理方式 |
|---|---|---|
| 204 | 空 | 成功。观察者已执行完毕;MCU状态为 |
| 400 | | 验证器拒绝请求。参见下方表格。 |
| 400 | | 技能存在bug——阶段1获取的MCU_ID错误,或请求体格式有误。返回 |
| 401 | 通常为空 | Bearer令牌无效。返回 |
| 403 | | 用户无写入 |
| 5xx | 内容不定 | 传输错误。观察者可能已部分提交——阶段3的SOQL查询是权威数据源。读取MCU状态;如果状态为 |
已知的400 错误信息:
FIELD_INTEGRITY_EXCEPTION| 消息片段 | 含义 | 返回结果 |
|---|---|---|
| "invalid deployment status transition" | 当前状态不允许转换为 | 重新读取MCU;如果状态变为 |
| "consent" / "keyword" / 提及STOP/HELP | | |
| "routing" / "queue" / "SessionHandler" | 路由前置条件不满足(阶段1应已捕获,但验证器会重新检查)。 | |
| 其他 | 未识别的验证器错误。 | |
Stage 3: Read the terminal MCU state
阶段3:读取MCU的最终状态
The PATCH is synchronous, so by the time we're here the MCU is or — no polling. Read once:
ActiveErrorbash
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| Status | Handling |
|---|---|
| Continue to Stage 4. |
| For WhatsApp channels only: phone number needs OTP verification. Continue to Stage 3.2 (WhatsApp verification flow). |
| Emit |
| Unexpected — the observer was supposed to terminate before the PATCH returned 204. Fall through to a defensive poll (see Stage 3.1). |
| The PATCH returned 204 but the write didn't take? Emit |
PATCH操作是同步的,因此当进入此阶段时,MCU状态已变为或——无需轮询。只需读取一次:
ActiveErrorbash
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| 状态 | 处理方式 |
|---|---|
| 继续执行阶段4。 |
| 仅适用于WhatsApp通道:手机号需要OTP验证。继续执行阶段3.2(WhatsApp验证流程)。 |
| 返回 |
| 异常情况——观察者应在PATCH返回204之前完成状态转换。执行防御性轮询(参见阶段3.1)。 |
| PATCH返回204但写入未生效?返回 |
Stage 3.2: WhatsApp phone number verification (only if ErrorReason === "VERIFICATION_REQUIRED")
阶段3.2:WhatsApp手机号验证(仅当ErrorReason === "VERIFICATION_REQUIRED"时执行)
This sub-flow only runs for WhatsApp channels when activation fails with — the phone number needs OTP verification with Meta before it can be registered. If the MCU comes back with (WhatsApp only), load and follow it to drive the phone-number verification sub-flow (request code → prompt user → validate code → retry activation once).
ErrorReason === "VERIFICATION_REQUIRED"ErrorReason === "VERIFICATION_REQUIRED"references/phone-verification.md此子流程仅在WhatsApp通道激活失败且时运行——手机号需通过Meta的OTP验证才能完成注册。如果MCU返回(仅适用于WhatsApp),请加载并按照其中的步骤执行手机号验证子流程(请求验证码→提示用户→验证验证码→重试激活一次)。
ErrorReason === "VERIFICATION_REQUIRED"ErrorReason === "VERIFICATION_REQUIRED"references/phone-verification.mdStage 3.1: Defensive poll (only if Stage 3 saw Provisioning
)
Provisioning阶段3.1:防御性轮询(仅当阶段3检测到Provisioning
状态时执行)
ProvisioningThe observer's external-callout block (WhatsApp/Facebook/SMS) runs synchronously inside the PATCH request — there's no async queue indirection in for any message type (verified against the switch/case in ). So a status at Stage 3 should not happen in steady state. Possible causes if it does: an exception thrown after the external call succeeded but before the PLSQL terminal-write ran; unusual instance config with async observer execution; or a future MessageType whose dispatch behavior we haven't accounted for. If Stage 3 returned , loop:
runProvisioningConversationChannelUsageDeploymentStatusServiceProvisioningProvisioningbash
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
doneMax wait: 30s. If still after the loop: emit . Verified on WhatsApp/wadtesting 2026-04-30: this loop should never actually iterate.
Provisioning{ok:false, kind:"timeout", mcuId, lastStatus:"Provisioning", elapsedS:30}观察者的外部调用块(WhatsApp/Facebook/SMS)在PATCH请求内同步执行——中针对任何消息类型均无异步队列间接调用(已通过中的switch/case验证)。因此在稳定状态下,阶段3不应检测到状态。如果出现这种情况,可能的原因包括:外部调用成功但PLSQL写入最终状态前抛出异常;实例配置特殊导致观察者异步执行;或存在未考虑到的新MessageType分发行为。如果阶段3返回状态,则执行循环:
runProvisioningConversationChannelUsageDeploymentStatusServiceProvisioningProvisioningbash
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最长等待时间:30秒。如果循环结束后状态仍为,则返回。已在WhatsApp/wadtesting环境验证,验证时间为2026-04-30:此循环实际不应执行。
Provisioning{ok:false, kind:"timeout", mcuId, lastStatus:"Provisioning", elapsedS:30}Stage 4: Verify MessagingChannel.IsActive
MessagingChannel.IsActive阶段4:验证MessagingChannel.IsActive
MessagingChannel.IsActiveThe observer flips inside the same callback as the status terminal write. In principle this is set by the time the PATCH returns. Confirm:
IsActivebash
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, IsActive FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json > /tmp/amc-verify-channel.jsonIf : compute and emit success. If despite MCU : the sync pass skipped (e.g. was true when the observer ran, which shouldn't happen post-terminal). Emit:
IsActive === truedurationMs = Date.now() - T0IsActive !== trueActiveIsActiveisTransitioningStatusjson
{"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."}观察者会在写入最终状态的同一个回调中切换的值。理论上,当PATCH返回时此值已设置完成。请确认:
IsActivebash
sf data query --target-org '{ORG_ALIAS}' \
--query "SELECT Id, IsActive FROM MessagingChannel WHERE Id = '{CHANNEL_ID}'" --json > /tmp/amc-verify-channel.json如果:计算并返回成功结果。如果MCU状态为但:说明同步步骤未执行(例如观察者执行时为true,而这在状态变为最终状态后不应发生)。返回:
IsActive === truedurationMs = Date.now() - T0ActiveIsActive !== trueIsActiveisTransitioningStatusjson
{"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."}Stage 5: Report to caller
阶段5:向调用方返回结果
Build the success envelope:
json
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "{MCU_ID}", "deploymentStatus": "Active",
"isActive": true, "messageType": "{MESSAGE_TYPE}", "durationMs": 20934}If this skill is the leaf (user invoked it directly), render:
Success — Activated — MessagingChannel {CHANNEL_ID} ({messageType}) is now live. MCU DeploymentStatus=Active, IsActive=true. (~{durationMs/1000}s)- (no-op path)
Info: Already active — MessagingChannel {CHANNEL_ID} is IsActive=true. No changes. - (no-routing)
Error: Routing not configured — run 'service-de-channel-routing-configure' skill first. - (no-mcu)
Error: No MessagingChannelUsage row — run the insertion skill first. - (readiness-failed)
Error: Readiness check failed: {message} — if it mentions consent/keywords, run 'service-de-channel-consent-configure' first. - (provisioning-error)
Error: Activation failed: {errorReason} — {errorDetails} - (verification-failed)
Error: WhatsApp phone number verification failed: {hint} - (verification-request-failed)
Error: Could not request verification code: {message} - (timeout)
Timeout: Activation timed out after {elapsedS}s with MCU.DeploymentStatus={lastStatus}. Defensive-poll limit hit; this is unusual. Check MCU {mcuId} in Setup.
构建成功结果:
json
{"ok": true, "channelId": "{CHANNEL_ID}", "mcuId": "{MCU_ID}", "deploymentStatus": "Active",
"isActive": true, "messageType": "{MESSAGE_TYPE}", "durationMs": 20934}如果本技能是最终节点(用户直接调用),则输出:
Success — Activated — MessagingChannel {CHANNEL_ID} ({messageType}) is now live. MCU DeploymentStatus=Active, IsActive=true. (~{durationMs/1000}s)- (无操作路径)
Info: Already active — MessagingChannel {CHANNEL_ID} is IsActive=true. No changes. - (未配置路由)
Error: Routing not configured — run 'service-de-channel-routing-configure' skill first. - (无MCU记录)
Error: No MessagingChannelUsage row — run the insertion skill first. - (就绪检查失败)
Error: Readiness check failed: {message} — if it mentions consent/keywords, run 'service-de-channel-consent-configure' first. - (预配错误)
Error: Activation failed: {errorReason} — {errorDetails} - (验证失败)
Error: WhatsApp phone number verification failed: {hint} - (请求验证码失败)
Error: Could not request verification code: {message} - (超时)
Timeout: Activation timed out after {elapsedS}s with MCU.DeploymentStatus={lastStatus}. Defensive-poll limit hit; this is unusual. Check MCU {mcuId} in Setup.
Worked examples
运行示例
For end-to-end activation traces (WhatsApp happy-path, Apple activation, a readiness-validator failure, and the already-active no-op), see .
references/worked-examples.md如需端到端激活跟踪示例(WhatsApp正常流程、Apple激活流程、就绪验证失败流程及已激活无操作流程),请查看。
references/worked-examples.mdGotchas
注意事项
Eleven known gotchas — synchronous PATCH timing for WhatsApp, valid API status transitions, the sync pass, relationship-name variance across orgs, consent preconditions, picklist casing, the REST-vs-Apex equivalence, values, handling, OAuth token extraction, and the Status-code-409 Admin API conflict. Before troubleshooting an unexpected result or modifying this skill, load and follow it.
IsActiveDeploymentStatusErrorReasonVERIFICATION_REQUIREDreferences/gotchas.md已知的11个注意事项——WhatsApp的同步PATCH时序、有效的API状态转换、同步步骤、不同组织间的关系名称差异、同意前置条件、选择器大小写、REST与Apex的等效性、值、处理、OAuth令牌提取及状态码409的Admin API冲突。在排查异常结果或修改本技能前,请加载并按照其中的步骤操作。
IsActiveDeploymentStatusErrorReasonVERIFICATION_REQUIREDreferences/gotchas.md