service-de-headless-channel-configure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Headless Channel Setup (type-agnostic)

无头通道配置(与类型无关)

What this skill does

本技能的功能

Takes a channel type (
WhatsApp
|
Line
|
AppleBusinessChat
|
Facebook
|
Text
) plus its type-specific inputs and drives it to an activated
MessagingChannel
. It first gates on the Enhanced Messaging Terms and Conditions, then runs four steps:
  1. Accept Terms and Conditions — renders the Enhanced Messaging disclaimer and requires the user to certify they have authority to bind their org, mirroring the in-org "Add a Channel" wizard. Blocking — a decline ends the run before any org work. (Stage 0.5, in this skill)
  2. Insert the channel — invokes
    service-de-channel-create
    , which handles the per-type third-party prereq internally (e.g. WhatsApp's
    service-de-waba-integrate
    runs inside
    service-de-channel-create
    ). (skill:
    service-de-channel-create
    )
  3. Configure routing — sets
    SessionHandlerId
    to an Omni-Channel Queue (pick existing or create new). Message-type-agnostic. (skill:
    service-de-channel-routing-configure
    )
  4. Configure consent — ensures a valid
    ConsentType
    + matching
    MsgChannelLanguageKeyword
    record. The Connect insert auto-seeds a default
    ConsentType=ImplicitOptIn
    + opt-out keyword, so on a fresh channel this step is frequently a no-op — or an upgrade if the caller wants ExplicitOptIn/DoubleOptIn. Activation's readiness check requires both routing AND consent, so this runs before activation. (skill:
    service-de-channel-consent-configure
    )
  5. Activate — PATCHes
    MessagingChannelUsage.DeploymentStatus
    to
    Provisioning
    via REST; the server-side save-hook drives the full observer chain synchronously and returns 204 once the MCU reaches
    Active
    and
    MessagingChannel.IsActive=true
    . Message-type-agnostic. (skill:
    service-de-channel-activate
    )
Resume-by-default. Each step is idempotent — if you re-run after a failure, the orchestrator detects existing state and skips steps that already succeeded.
Prompt mid-flow. If
{MESSAGE_TYPE}
isn't in the initial request, the orchestrator asks upfront. Routing choices (pick queue / create new) are prompted when step 2 runs, not upfront — keeps leaf skills self-contained.
接收通道类型(
WhatsApp
|
Line
|
AppleBusinessChat
|
Facebook
|
Text
)及其特定类型输入,将其配置为已激活的
MessagingChannel
。首先会检查增强型消息服务的条款和条件,然后执行四个步骤:
  1. 接受条款和条件 —— 显示增强型消息服务的免责声明,要求用户确认其有权约束所在组织,与组织内的“添加通道”向导保持一致。此步骤为阻塞式操作——若用户拒绝,则在执行任何组织操作前终止运行。(属于本技能的0.5阶段)
  2. 插入通道 —— 调用
    service-de-channel-create
    ,该技能会在内部处理各类型的第三方前置要求(例如,WhatsApp的
    service-de-waba-integrate
    会在
    service-de-channel-create
    内部运行)。(技能:
    service-de-channel-create
  3. 配置路由 —— 将
    SessionHandlerId
    设置为Omni-Channel队列(选择现有队列或创建新队列)。此步骤与消息类型无关。(技能:
    service-de-channel-routing-configure
  4. 配置同意设置 —— 确保存在有效的
    ConsentType
    + 匹配的
    MsgChannelLanguageKeyword
    记录。插入通道时会自动生成默认的
    ConsentType=ImplicitOptIn
    + 退订关键词,因此对于新创建的通道,此步骤通常是无操作(no-op)——或者如果调用者需要ExplicitOptIn/DoubleOptIn,则会进行升级。激活的就绪检查需要同时满足路由和同意设置,因此此步骤会在激活前执行。(技能:
    service-de-channel-consent-configure
  5. 激活通道 —— 通过REST将
    MessagingChannelUsage.DeploymentStatus
    更新为
    Provisioning
    ;服务器端的保存钩子会同步驱动完整的观察者链,一旦MCU达到
    Active
    状态且
    MessagingChannel.IsActive=true
    ,则返回204。此步骤与消息类型无关。(技能:
    service-de-channel-activate
默认支持恢复。每个步骤都是幂等的——如果在失败后重新运行,编排器会检测现有状态并跳过已成功完成的步骤。
流程中提示。如果初始请求中未包含
{MESSAGE_TYPE}
,编排器会先进行提示。路由选择(选择队列/创建新队列)会在步骤2执行时进行提示,而非提前提示——这样可保持子技能的独立性。

Reference File Index

参考文件索引

Reference fileLoad when
references/inputs.md
You need the full input field list — MESSAGE_TYPE/ORG_ALIAS plus per-type fields for WhatsApp, LINE, AppleBusinessChat, Facebook, Text (SMS).
references/terms-and-conditions.md
Rendering the Stage 0.5 disclaimer and certification prompt — verbatim text and per-type label/third-party mappings.
references/output-envelopes.md
Wiring error handling or parsing the canonical success/failure/terms-declined JSON envelope shapes.
references/partial-success.md
The orchestrator fails mid-way and you need to render the partial-success/resume report.
references/gotchas.md
Debugging unexpected behavior — duplicate-key errors, resume/idempotency questions, preflight SOQL shape, unattended/CI usage.
references/worked-examples.md
You need a reference trace (fresh WhatsApp run, resume after failure, Apple dispatch, unsupported-type rejection) for exact envelope shapes.

参考文件加载时机
references/inputs.md
需要完整的输入字段列表时——包括MESSAGE_TYPE/ORG_ALIAS以及WhatsApp、LINE、AppleBusinessChat、Facebook、Text(SMS)的特定类型字段。
references/terms-and-conditions.md
显示0.5阶段的免责声明和确认提示时——包含原文文本以及各类型的标签/第三方映射。
references/output-envelopes.md
处理错误或解析标准的成功/失败/条款拒绝JSON信封格式时。
references/partial-success.md
编排器中途失败,需要显示部分成功/恢复报告时。
references/gotchas.md
调试意外行为时——包括重复键错误、恢复/幂等性问题、预检查SOQL格式、无人值守/CI使用场景。
references/worked-examples.md
需要参考跟踪记录时(如新WhatsApp运行、失败后恢复、Apple调度、不支持类型的拒绝),以获取准确的信封格式。

Critical execution rule

关键执行规则

This orchestrator MUST run all stages (0-5) in a SINGLE response without stopping between stages.
When you invoke a leaf skill (service-de-channel-create, service-de-channel-routing-configure, service-de-channel-consent-configure, service-de-channel-activate) and it returns via function_results:
  • DO: Immediately parse the function_results return value and continue to the next stage
  • DO: Run Stage 0.5 → Stage 1 → Stage 2 → Stage 3 → Stage 3.5 → Stage 4 → Stage 5 consecutively in ONE response
  • DO NOT: Stop and report back to the user between stages
  • DO NOT: Wait for user confirmation between stages
  • DO NOT: Treat each stage as a separate turn in the conversation
Why this matters: The user invoked THIS orchestrator skill to get an end-to-end activated channel. They did NOT invoke the individual leaf skills. Your job is to run the entire pipeline autonomously and report the final result. Stopping mid-flow breaks the orchestrator pattern and forces the user to manually resume each stage.
Exception: Only stop mid-flow if a leaf skill returns
ok: false
with a terminal error (not a recoverable prompt), or if the user declines the Stage 0.5 Terms and Conditions gate (emit
terms-not-accepted
and halt). Then report the failure immediately with context about which stage failed and what state was reached.
Stage 0.5 is a required blocking prompt. The Terms and Conditions gate is the one prompt that must be answered affirmatively before any org work. It is not optional and must not be auto-accepted. A
no
/decline ends the run per the exception above; a
yes
continues to Stage 1 in the same response.
Leaf skill prompts: Some leaf skills (like
service-de-waba-integrate
or
service-de-channel-routing-configure
) may prompt the user for input mid-execution. That's fine — answer those prompts as they come up, but then CONTINUE to the next stage immediately after the prompt is answered. Don't stop just because a prompt was involved.

此编排器必须在单个响应中运行所有阶段(0-5),阶段之间不得停止。
当调用子技能(service-de-channel-create、service-de-channel-routing-configure、service-de-channel-consent-configure、service-de-channel-activate)并通过function_results返回结果时:
  • 必须: 立即解析function_results的返回值并继续执行下一阶段
  • 必须: 在同一个响应中连续执行阶段0.5 → 阶段1 → 阶段2 → 阶段3 → 阶段3.5 → 阶段4 → 阶段5
  • 禁止: 在阶段之间停止并向用户报告
  • 禁止: 在阶段之间等待用户确认
  • 禁止: 将每个阶段视为对话中的独立回合
为什么这很重要: 用户调用此编排器技能是为了获得端到端的已激活通道。他们并未调用单个子技能。你的任务是自主运行整个流程并报告最终结果。中途停止会破坏编排器模式,迫使用户手动恢复每个阶段。
例外情况: 只有当子技能返回
ok: false
且包含终端错误(不可恢复的提示除外),或者用户拒绝了0.5阶段的条款和条件门限(发出
terms-not-accepted
并停止)时,才可以中途停止。然后立即报告失败,并说明哪个阶段失败以及达到了什么状态。
0.5阶段是必填的阻塞式提示。 条款和条件门限是必须得到用户肯定答复后才能执行任何组织操作的提示。它是必填项,不得自动接受。如果用户回答“否”/拒绝,则按照上述例外情况终止运行;如果回答“是”,则在同一响应中继续执行阶段1。
子技能提示: 某些子技能(如
service-de-waba-integrate
service-de-channel-routing-configure
)可能会在执行过程中提示用户输入。这是允许的——在回答提示后立即继续执行下一阶段。不要因为出现提示就停止运行。

When NOT to use this skill

不适用本技能的场景

  • You only want one step. Invoke the leaf skill directly (
    service-de-channel-create
    ,
    service-de-channel-routing-configure
    ,
    service-de-channel-activate
    ). This orchestrator is for the full automated path from nothing → activated channel.
  • You want to run stages independently in separate conversation turns. This orchestrator runs all stages consecutively in one response. If you need manual control between stages, invoke the leaf skills individually.
  • You want a message type outside
    WhatsApp
    /
    Line
    /
    AppleBusinessChat
    /
    Facebook
    /
    Text
    .
    The dispatcher will return
    unsupported-type
    . WeChat / MsCopilot / Alexa would need their own leaves + a dispatcher update.
  • The prereq hasn't been met (WABA not shared, LINE channel not created, Apple account not registered, SMS number not provisioned). The per-type insert skill's Stage 2-equivalent surfaces this clearly and returns; no orchestration fixes it. (For SMS/Text the number-provisioning prereq isn't checked at insert — an unprovisioned number surfaces later as an activation-time
    provisioning-error
    .)
  • 仅需要执行单个步骤。直接调用子技能(
    service-de-channel-create
    service-de-channel-routing-configure
    service-de-channel-activate
    )。此编排器适用于从无到有→已激活通道的完整自动化路径。
  • 希望在对话的不同回合中独立运行各个阶段。此编排器会在一个响应中连续运行所有阶段。如果需要在阶段之间进行手动控制,请单独调用子技能。
  • 需要支持
    WhatsApp
    /
    Line
    /
    AppleBusinessChat
    /
    Facebook
    /
    Text
    之外的消息类型
    。调度器会返回
    unsupported-type
    。WeChat/MsCopilot/Alexa需要各自的子技能+调度器更新。
  • 未满足前置要求(WABA未共享、LINE通道未创建、Apple账户未注册、SMS号码未配置)。各类型的插入技能的等效阶段2会明确显示此问题并返回结果;编排无法解决此问题。(对于SMS/Text,插入时不会检查号码配置要求——未配置的号码会在激活阶段显示为
    provisioning-error
    。)

Inputs (from user)

用户输入

{MESSAGE_TYPE}
(prompted if omitted) and
{ORG_ALIAS}
(optional), plus type-specific fields for WhatsApp, LINE, AppleBusinessChat, Facebook, and Text (SMS) forwarded to the dispatcher. Load
references/inputs.md
and follow it
for the full field list and the omitted-type prompt text.
{MESSAGE_TYPE}
(若省略则会提示)和
{ORG_ALIAS}
(可选),以及WhatsApp、LINE、AppleBusinessChat、Facebook和Text(SMS)的特定类型字段,这些字段会转发给调度器。加载
references/inputs.md
并按照其中的说明操作
,获取完整的字段列表和省略类型时的提示文本。

Output (to user)

输出给用户

Three top-level outcomes: success, step-annotated failure, and terms-declined. Load
references/output-envelopes.md
and follow it
for the exact JSON shapes.

三种顶层结果:成功、带步骤注释的失败、条款拒绝。加载
references/output-envelopes.md
并按照其中的说明操作
,获取准确的JSON格式。

Stage 0: Resolve
{MESSAGE_TYPE}
and type-specific key

阶段0:解析
{MESSAGE_TYPE}
和特定类型密钥

If
{MESSAGE_TYPE}
is omitted, prompt the user (see "Inputs" above). Refuse to proceed without one — there's no sensible default.
Once known, determine the preflight key — the field we'll filter the
MessagingChannel
SOQL on to detect existing state. It's always
MessagingPlatformKey
, but what that value is varies by type:
{MESSAGE_TYPE}
Preflight key value
{PLATFORM_KEY}
Notes
WhatsApp
{PHONE_NUMBER_ID}
Required upfront
Line
{LINE_CHANNEL_ID}
Required upfront
AppleBusinessChat
{APPLE_BC_ID}
Required upfront
Facebook
{PAGE_ID}
Optional - OAuth → fetch pages → prompt if not provided
Text
{SMS_NUMBER}
Required upfront (the phone number / short code itself)
Facebook is unique: If
{PAGE_ID}
is not provided, the orchestrator cannot run its own Stage 1 preflight. Instead, skip Stage 1 and let
service-de-channel-create
handle the full OAuth → page selection → preflight → creation flow. The insertion skill has its own Stage 1 preflight that runs after page selection.
For non-Facebook types, validate that the caller provided the required type-specific inputs for
{MESSAGE_TYPE}
(see Inputs above). If missing, prompt or halt — don't call the dispatcher, it will re-validate and return
missing-input
, wasting a round trip.

如果省略
{MESSAGE_TYPE}
,则提示用户(参见上面的“输入”部分)。拒绝在未提供该参数的情况下继续——没有合理的默认值。
确定后,找到预检查密钥——我们将在
MessagingChannel
的SOQL查询中使用此字段来检测现有状态。此字段始终是
MessagingPlatformKey
,但其值因类型而异:
{MESSAGE_TYPE}
预检查密钥值
{PLATFORM_KEY}
说明
WhatsApp
{PHONE_NUMBER_ID}
必须提前提供
Line
{LINE_CHANNEL_ID}
必须提前提供
AppleBusinessChat
{APPLE_BC_ID}
必须提前提供
Facebook
{PAGE_ID}
可选 - OAuth → 获取页面 → 若未提供则提示
Text
{SMS_NUMBER}
必须提前提供(电话号码/短码本身)
Facebook是特殊情况: 如果未提供
{PAGE_ID}
,编排器无法运行自己的阶段1预检查。此时跳过阶段1,让
service-de-channel-create
处理完整的OAuth→页面选择→预检查→创建流程。插入技能在页面选择后会运行自己的阶段1预检查。
对于非Facebook类型,验证调用者是否提供了
{MESSAGE_TYPE}
所需的特定类型输入(参见上面的“输入”部分)。如果缺失,则提示或终止——不要调用调度器,因为它会重新验证并返回
missing-input
,浪费一次往返。

Stage 0.5: Enhanced Messaging Terms and Conditions (blocking gate)

阶段0.5:增强型消息服务条款和条件(阻塞式门限)

Run this before any org query or channel work. The in-org "Add a Channel" wizard requires the admin to accept the Enhanced Messaging Terms and Conditions before it will let them proceed to channel setup. The headless flow must enforce the same gate — do not skip it, do not accept on the user's behalf. This runs once per orchestrator invocation, after
{MESSAGE_TYPE}
is known (the disclaimer names the channel) and before Stage 1.
Render the disclaimer and certification prompt from
references/terms-and-conditions.md
(verbatim, substituting the channel label) — load it and follow it for the exact wording and per-type label/third-party mappings.
Gate behavior:
  • If the user answers yes (affirmative acceptance), record
    {TC_ACCEPTED} = true
    and continue to Stage 1.
  • If the user answers no, declines, or does not affirmatively accept, halt immediately — do not run Stage 1 or any subsequent stage. Emit:
    json
    {"ok": false, "kind": "terms-not-accepted",
     "hint": "user did not accept the Enhanced Messaging Terms and Conditions — channel setup cannot proceed. Re-run when ready to accept."}
  • Acceptance is required on every orchestrator invocation, including resume runs. It is a per-session gate, not persisted — we don't have a place to durably record it, and the cost of re-confirming is one prompt.

在执行任何组织查询或通道操作前运行此阶段。 组织内的“添加通道”向导要求管理员接受增强型消息服务的条款和条件后才能继续通道设置。无头流程必须强制执行相同的门限——不得跳过,不得代表用户接受。此阶段在每次调用编排器时运行一次,在确定
{MESSAGE_TYPE}
之后(免责声明会提及通道),阶段1之前。
references/terms-and-conditions.md
中加载并显示免责声明和确认提示(原文,替换通道标签)——按照其中的说明操作,获取准确的措辞以及各类型的标签/第三方映射。
门限行为:
  • 如果用户回答(肯定接受),记录
    {TC_ACCEPTED} = true
    并继续执行阶段1。
  • 如果用户回答、拒绝或未明确接受,立即终止——不要运行阶段1或任何后续阶段。发出:
    json
    {"ok": false, "kind": "terms-not-accepted",
     "hint": "user did not accept the Enhanced Messaging Terms and Conditions — channel setup cannot proceed. Re-run when ready to accept."}
  • 每次调用编排器(包括恢复运行)都需要接受条款。这是每个会话的门限,不会持久化——我们没有持久记录的位置,重新确认的成本只是一次提示。

Stage 1: Pre-flight state detection (resume support)

阶段1:预检查状态检测(支持恢复)

Skip this stage entirely if
MESSAGE_TYPE=Facebook
and
{PAGE_ID}
was not provided.
The Facebook insertion skill needs to run OAuth first before we know which PAGE_ID to check for. Let the insertion skill handle its own preflight in Stage 1 after page selection.
For all other cases, query the org to see how far a prior run got. This enables skip-ahead on resume. Two queries, not a subquery — the
(SELECT ... FROM MessagingChannelUsages)
subquery fails on some orgs where the child relationship is unnameable. Use FK-keyed second query instead (see gotcha #9).
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeveloperName, MessageType, IsActive, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \
  --json > /tmp/hcs-preflight.json
If the channel query returns a row, fire a second query to resolve its MCU:
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \
  --json > /tmp/hcs-preflight-mcu.json
Parse the combined result to figure out
{CURRENT_STATE}
:
Preflight result
CURRENT_STATE
First step to run
No records
fresh
Step 1 (insert)
Record exists,
IsActive=false
,
SessionHandlerId=null
and
FallbackQueueId=null
inserted-no-routing
Step 2 (configure routing)
Record exists,
IsActive=false
, routing set
routed-not-active
Step 3 (configure consent), then Step 4 (activate)
Record exists,
IsActive=true
already-active
Emit no-op success envelope; return.
Record the existing
{CHANNEL_ID}
and
{MCU_ID}
if present — downstream steps need them.
Initialize
{STEPS_RUN} = []
,
{STEPS_SKIPPED} = []
,
{T0} = Date.now()
.

如果
MESSAGE_TYPE=Facebook
且未提供
{PAGE_ID}
,则完全跳过此阶段。
Facebook插入技能需要先运行OAuth,然后才能知道要检查哪个PAGE_ID。让插入技能在页面选择后的阶段1中处理自己的预检查。
对于其他所有情况,查询组织以查看之前的运行进展到了哪一步。这支持在恢复时跳过已完成的步骤。使用两个查询,而非子查询——
(SELECT ... FROM MessagingChannelUsages)
子查询在某些组织中会失败,因为子关系无法命名。改用外键关联的第二个查询(参见陷阱#9)。
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeveloperName, MessageType, IsActive, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE MessagingPlatformKey = '{PLATFORM_KEY}' AND MessageType = '{MESSAGE_TYPE}'" \
  --json > /tmp/hcs-preflight.json
如果通道查询返回行,则执行第二个查询以解析其MCU:
bash
sf data query --target-org '{ORG_ALIAS}' \
  --query "SELECT Id, DeploymentStatus FROM MessagingChannelUsage WHERE MessagingChannelId = '{CHANNEL_ID}'" \
  --json > /tmp/hcs-preflight-mcu.json
解析组合结果以确定
{CURRENT_STATE}
预检查结果
CURRENT_STATE
要运行的第一个步骤
无记录
fresh
步骤1(插入)
存在记录,
IsActive=false
SessionHandlerId=null
FallbackQueueId=null
inserted-no-routing
步骤2(配置路由)
存在记录,
IsActive=false
,已设置路由
routed-not-active
步骤3(配置同意设置),然后步骤4(激活)
存在记录,
IsActive=true
already-active
发出无操作成功信封;返回。
记录现有的
{CHANNEL_ID}
{MCU_ID}
(如果存在)——后续步骤需要这些值。
初始化
{STEPS_RUN} = []
{STEPS_SKIPPED} = []
{T0} = Date.now()

Stage 2: Insert the channel (step 1 of 4)

阶段2:插入通道(4个步骤中的第1步)

Skip this stage if
CURRENT_STATE
{inserted-no-routing, routed-not-active, already-active}
— the channel already exists. Append
"insert"
to
STEPS_SKIPPED
in that case.
Otherwise invoke
service-de-channel-create
with:
  • {MESSAGE_TYPE}
    = (from Stage 0)
  • All type-specific inputs (forwarded verbatim — dispatcher routes to the right leaf)
  • {ORG_ALIAS}
The insertion skill handles per-type prereqs internally (e.g., for WhatsApp,
service-de-channel-create
invokes
service-de-waba-integrate
as its Stage 2). This orchestrator doesn't need to know about WABA linking, LINE token verification, or Apple registration —
service-de-channel-create
owns the prereq semantics for whichever type it's running.
Handle the envelope:
Leaf envelopeOrchestrator action
{ok: true, channelId, mcuId, ...}
Record
{CHANNEL_ID}
and
{MCU_ID}
. Append
"insert"
to
STEPS_RUN
. Continue.
{ok: false, kind: "partnership-blocked" | "user-declined" | "apple-registration-pending" | "line-prereq-missing"}
These come from the per-type prereq stage. Pass through as
failedStep: "insert"
,
failedStepIndex: 1
. Return.
{ok: false, kind: "missing-input" | "unsupported-type"}
Should have been caught in Stage 0 — emit failure with
failedStep: "insert"
and a note that the caller's inputs were incomplete.
{ok: false, kind: <anything else>}
Emit failure envelope with
failedStep: "insert"
,
failedStepIndex: 1
. Return.
At this point we have
{CHANNEL_ID}
and
{MCU_ID}
regardless of whether step 1 ran or was skipped.
→ Continue immediately to Stage 3 in this same response. DO NOT STOP.

如果
CURRENT_STATE
{inserted-no-routing, routed-not-active, already-active}
,则跳过此阶段——通道已存在。此时将
"insert"
添加到
STEPS_SKIPPED
中。
否则调用
service-de-channel-create
,参数包括:
  • {MESSAGE_TYPE}
    = (来自阶段0)
  • 所有特定类型输入(原样转发——调度器会路由到正确的子技能)
  • {ORG_ALIAS}
插入技能会在内部处理各类型的前置要求(例如,对于WhatsApp,
service-de-channel-create
会调用
service-de-waba-integrate
作为其阶段2)。此编排器无需了解WABA链接、LINE令牌验证或Apple注册——
service-de-channel-create
负责处理其运行的任何类型的前置要求语义。
处理信封:
子技能信封编排器操作
{ok: true, channelId, mcuId, ...}
记录
{CHANNEL_ID}
{MCU_ID}
。将
"insert"
添加到
STEPS_RUN
中。继续执行。
{ok: false, kind: "partnership-blocked" | "user-declined" | "apple-registration-pending" | "line-prereq-missing"}
这些来自各类型的前置要求阶段。原样传递,设置
failedStep: "insert"
failedStepIndex: 1
。返回。
{ok: false, kind: "missing-input" | "unsupported-type"}
本应在阶段0中捕获——发出失败信息,设置
failedStep: "insert"
并提示调用者的输入不完整。
{ok: false, kind: <其他任何类型>}
发出失败信封,设置
failedStep: "insert"
failedStepIndex: 1
。返回。
此时无论步骤1是否运行或被跳过,我们都已获得
{CHANNEL_ID}
{MCU_ID}
→ 立即在同一个响应中继续执行阶段3。禁止停止。

Stage 3: Configure routing (step 2 of 4)

阶段3:配置路由(4个步骤中的第2步)

Skip this stage if
CURRENT_STATE === "routed-not-active"
— routing is already set. Append
"route"
to
STEPS_SKIPPED
.
Otherwise invoke
service-de-channel-routing-configure
:
  • {CHANNEL_ID}
    = recorded above
  • {ORG_ALIAS}
This is the step that prompts the user for queue choice (pick existing / create new / escape hatch). Don't try to pre-empt the prompts — forward the user's answers as-is.
Handle the envelope:
Leaf envelopeOrchestrator action
{ok: true, sessionHandlerId, queueName, ...}
(including
noop: true
)
Record
{SESSION_HANDLER_ID}
and
{QUEUE_NAME}
. Append
"route"
to
STEPS_RUN
. Continue.
{ok: false, kind: "unsupported-routing-type", ...}
User chose flow/user/asa/aea. Emit partial-success with
failedStep: "route"
and a resume hint pointing at the UI. Return.
{ok: false, kind: "metadata-deploy-failed" | "patch-failed" | "verify-failed"}
Emit failure envelope with
failedStep: "route"
. Return.
→ Continue immediately to Stage 3.5 in this same response. DO NOT STOP.

如果
CURRENT_STATE === "routed-not-active"
,则跳过此阶段——路由已设置。将
"route"
添加到
STEPS_SKIPPED
中。
否则调用
service-de-channel-routing-configure
  • {CHANNEL_ID}
    = 上面记录的值
  • {ORG_ALIAS}
此步骤会提示用户选择队列(选择现有队列/创建新队列/退出)。不要提前提示——原样转发用户的回答。
处理信封:
子技能信封编排器操作
{ok: true, sessionHandlerId, queueName, ...}
(包括
noop: true
记录
{SESSION_HANDLER_ID}
{QUEUE_NAME}
。将
"route"
添加到
STEPS_RUN
中。继续执行。
{ok: false, kind: "unsupported-routing-type", ...}
用户选择了flow/user/asa/aea。发出部分成功信息,设置
failedStep: "route"
并提供指向UI的恢复提示。返回。
{ok: false, kind: "metadata-deploy-failed" | "patch-failed" | "verify-failed"}
发出失败信封,设置
failedStep: "route"
。返回。
→ 立即在同一个响应中继续执行阶段3.5。禁止停止。

Stage 3.5: Configure consent (before activation)

阶段3.5:配置同意设置(激活前)

Activation's readiness check requires consent AND routing — a fully-routed channel still fails to activate if consent isn't configured. Invoke
service-de-channel-consent-configure
:
  • {CHANNEL_ID}
    = recorded above
  • {CONSENT_TYPE}
    ,
    {LANGUAGE}
    , and the keyword/prompt values — forward whatever the user supplied; omit what they didn't
  • {ORG_ALIAS}
This stage ensures consent is activation-ready — it doesn't always write. The Connect insert auto-seeds
ConsentType=ImplicitOptIn
plus a default opt-out
MsgChannelLanguageKeyword
, which already satisfies the readiness check. So the leaf's Stage 1 will frequently find the channel already ready and return
noop: true
— the expected outcome on a fresh
ImplicitOptIn
channel, not a bug. It performs a real write only to upgrade (ExplicitOptIn/DoubleOptIn) or override the seeded keywords/prompts. When the user pre-supplied inputs asking for an upgrade, expect a write and let the leaf prompt for missing values — don't suppress those prompts.
Handle the envelope:
Leaf envelopeOrchestrator action
{ok: true, ...}
(including
noop: true
)
Append
"consent"
to
STEPS_RUN
(or
STEPS_SKIPPED
if
noop
). Continue.
{ok: false, kind: "not-enhanced"}
Shouldn't happen — these skills only create Enhanced channels. Emit failure with
failedStep: "consent"
. Return.
{ok: false, kind: "missing-consent-input", hint}
The user didn't supply a required keyword/prompt for the chosen ConsentType. Emit failure with
failedStep: "consent"
and pass through the
hint
. Return.
{ok: false, ...}
(any other kind —
channel-patch-failed
,
keyword-record-failed
,
verify-failed
, or an unrecognized envelope)
Emit failure with
failedStep: "consent"
and pass through the leaf's
kind
and message verbatim. Return.
→ Continue immediately to Stage 4 in this same response. DO NOT STOP.

激活的就绪检查需要同意设置和路由——即使通道已完全配置路由,如果未配置同意设置,激活仍会失败。调用
service-de-channel-consent-configure
  • {CHANNEL_ID}
    = 上面记录的值
  • {CONSENT_TYPE}
    {LANGUAGE}
    以及关键词/提示值——转发用户提供的所有值,省略未提供的值
  • {ORG_ALIAS}
此阶段确保同意设置满足激活要求——并非总是需要写入数据。插入通道时会自动生成
ConsentType=ImplicitOptIn
+ 默认退订
MsgChannelLanguageKeyword
,这已经满足就绪检查。因此子技能的阶段1通常会发现通道已经就绪并返回
noop: true
——这是新创建的
ImplicitOptIn
通道的预期结果,而非错误。只有在升级(ExplicitOptIn/DoubleOptIn)或覆盖生成的关键词/提示时,才会执行实际写入。当用户提前提供了要求升级的输入时,预期会执行写入操作,让子技能提示缺失的值——不要抑制这些提示。
处理信封:
子技能信封编排器操作
{ok: true, ...}
(包括
noop: true
"consent"
添加到
STEPS_RUN
中(如果是
noop
则添加到
STEPS_SKIPPED
)。继续执行。
{ok: false, kind: "not-enhanced"}
不应发生——这些技能仅创建增强型通道。发出失败信息,设置
failedStep: "consent"
。返回。
{ok: false, kind: "missing-consent-input", hint}
用户未提供所选ConsentType所需的关键词/提示。发出失败信息,设置
failedStep: "consent"
并传递
hint
。返回。
{ok: false, ...}
(任何其他类型——
channel-patch-failed
keyword-record-failed
verify-failed
或无法识别的信封)
发出失败信息,设置
failedStep: "consent"
并原样传递子技能的
kind
和消息。返回。
→ 立即在同一个响应中继续执行阶段4。禁止停止。

Stage 4: Activate (step 4 of 4)

阶段4:激活通道(4个步骤中的第4步)

Always run (unless
CURRENT_STATE === "already-active"
, in which case Stage 1 returned early).
Invoke
service-de-channel-activate
:
  • {CHANNEL_ID}
    = recorded above
  • {ORG_ALIAS}
No timeout knobs — activation is a single synchronous PATCH. The leaf does a defensive poll if the server ever returns mid-transition, but the expected path is one round-trip.
Handle the envelope:
Leaf envelopeOrchestrator action
{ok: true, isActive: true, ...}
(including
noop: true
)
Record results. Append
"activate"
to
STEPS_RUN
. Continue to Stage 5.
{ok: false, kind: "no-routing"}
Unusual (we just set it) — probably a race or permission issue. Emit failure with a hint about the permissions check. Return.
{ok: false, kind: "no-mcu" | "channel-missing"}
Shouldn't happen after successful insert. Emit failure with the leaf's envelope. Return.
{ok: false, kind: "readiness-failed", errorMessage}
validateChannelReadinessOnProvisioning
rejected the PATCH — missing consent (STOP/HELP keyword record). Stage 3.5 should have caught this; if it re-appears, re-run
service-de-channel-consent-configure
for the channel. Emit failure and pass through the resume hint. Return.
{ok: false, kind: "provisioning-error", errorReason, errorDetails}
Third-party side rejected (Meta
/register
failed, etc.). MCU is now in
Error
. Emit failure and pass through
errorReason
/
errorDetails
. Return.
{ok: false, kind: "auth" | "transport"}
REST call failed at the HTTP layer (401 from
sf
auth, 5xx from the instance). Pass through. Return.

始终运行此阶段(除非
CURRENT_STATE === "already-active"
,此时阶段1已提前返回)。
调用
service-de-channel-activate
  • {CHANNEL_ID}
    = 上面记录的值
  • {ORG_ALIAS}
没有超时设置——激活是单个同步PATCH操作。如果服务器返回中间过渡状态,子技能会进行防御性轮询,但预期路径是一次往返。
处理信封:
子技能信封编排器操作
{ok: true, isActive: true, ...}
(包括
noop: true
记录结果。将
"activate"
添加到
STEPS_RUN
中。继续执行阶段5。
{ok: false, kind: "no-routing"}
不常见(我们刚设置完路由)——可能是竞争条件或权限问题。发出失败信息并提示检查权限。返回。
{ok: false, kind: "no-mcu" | "channel-missing"}
插入成功后不应发生。发出失败信息并传递子技能的信封。返回。
{ok: false, kind: "readiness-failed", errorMessage}
validateChannelReadinessOnProvisioning
拒绝了PATCH——缺少同意设置(STOP/HELP关键词记录)。阶段3.5本应捕获此问题;如果再次出现,重新运行
service-de-channel-consent-configure
。发出失败信息并传递恢复提示。返回。
{ok: false, kind: "provisioning-error", errorReason, errorDetails}
第三方拒绝(Meta
/register
失败等)。MCU现在处于
Error
状态。发出失败信息并传递
errorReason
/
errorDetails
。返回。
{ok: false, kind: "auth" | "transport"}
REST调用在HTTP层失败(
sf
认证返回401,实例返回5xx)。原样传递。返回。

Stage 5: Final report

阶段5:最终报告

Build the success envelope:
json
{
  "ok": true,
  "messageType": "{MESSAGE_TYPE}",
  "channelId": "{CHANNEL_ID}",
  "mcuId": "{MCU_ID}",
  "channelName": "{CHANNEL_NAME or inferred}",
  "sessionHandlerId": "{SESSION_HANDLER_ID}",
  "queueName": "{QUEUE_NAME}",
  "isActive": true,
  "stepsRun": [...],
  "stepsSkipped": [...],
  "totalDurationMs": Date.now() - T0
}
Render to the user as:
text
Success — {MESSAGE_TYPE} channel '{CHANNEL_NAME}' is live on {ORG_ALIAS}.
   Channel ID: {CHANNEL_ID}
   Routed to: {queueName} ({SESSION_HANDLER_ID})
   Steps: ran {STEPS_RUN.join(", ")}{, skipped " + STEPS_SKIPPED.join(", ") if any}
   Total time: {formatted from durationMs}
If any step was skipped due to resume, the report should make that visible.

构建成功信封:
json
{
  "ok": true,
  "messageType": "{MESSAGE_TYPE}",
  "channelId": "{CHANNEL_ID}",
  "mcuId": "{MCU_ID}",
  "channelName": "{CHANNEL_NAME or inferred}",
  "sessionHandlerId": "{SESSION_HANDLER_ID}",
  "queueName": "{QUEUE_NAME}",
  "isActive": true,
  "stepsRun": [...],
  "stepsSkipped": [...],
  "totalDurationMs": Date.now() - T0
}
向用户显示为:
text
Success — {MESSAGE_TYPE} channel '{CHANNEL_NAME}' is live on {ORG_ALIAS}.
   Channel ID: {CHANNEL_ID}
   Routed to: {queueName} ({SESSION_HANDLER_ID})
   Steps: ran {STEPS_RUN.join(", ")}{, skipped " + STEPS_SKIPPED.join(", ") if any}
   Total time: {formatted from durationMs}
如果由于恢复而跳过了任何步骤,报告中应明确显示这一点。

Partial-success and resume reporting

部分成功和恢复报告

If the orchestrator fails mid-way, the envelope always includes
stateSoFar
describing what landed.
stateSoFar
must always carry both
stepsRun
and
stepsSkipped
(each an array, empty if nothing landed/skipped yet) alongside the fields that landed — the partial-success renderer reads both. When rendering a failure, load
references/partial-success.md
and follow it
— it has the exact template for showing which steps already landed and how to retry.

如果编排器中途失败,信封始终包含
stateSoFar
字段,描述已完成的操作。
stateSoFar
必须始终包含
stepsRun
stepsSkipped
(均为数组,如果尚未完成/跳过任何步骤则为空)以及已完成的字段——部分成功渲染器会读取这两个字段。显示失败信息时,加载
references/partial-success.md
并按照其中的说明操作
——其中包含显示已完成步骤以及如何重试的准确模板。

Worked examples

示例

For reference traces (fresh WhatsApp run, resume after routing failure, Apple dispatch, unsupported-type rejection), see
references/worked-examples.md
.

有关参考跟踪记录(新WhatsApp运行、路由失败后恢复、Apple调度、不支持类型的拒绝),请参见
references/worked-examples.md

Gotchas

陷阱

Nine known gotchas covering prereq ownership,
MessagingPlatformKey
uniqueness, resume semantics, CSOT-only support, error-classification boundaries, prompt placement, platform-key mapping, wall-clock duration, and the two-query preflight workaround. When debugging unexpected behavior, load
references/gotchas.md
and follow it.
九个已知陷阱,涵盖前置要求归属、
MessagingPlatformKey
唯一性、恢复语义、仅CSOT支持、错误分类边界、提示位置、平台密钥映射、挂钟时长以及双查询预检查解决方法。调试意外行为时,加载
references/gotchas.md
并按照其中的说明操作。