@alipay/alipay-aipay 1.5.2-beta → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -2
  3. package/skill/VERSION +1 -1
  4. package/skill/references/integration/flow.md +3 -3
  5. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +1 -1
  6. package/skill/references/normal/alipay-cli-env.md +1 -1
  7. package/skill/references/normal/full-process-routing.md +1 -1
  8. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +128 -13
  9. package/skill/references/normal/scripts/onboarding_message_runner.mjs +132 -47
  10. package/skill/references/normal/scripts/render_customer_message.mjs +3 -3
  11. package/skill/references/normal/scripts/runtime.mjs +69 -0
  12. package/skill/references/normal/scripts/telemetry.mjs +27 -26
  13. package/skill/references/normal/telemetry.md +4 -4
  14. package/skill/references/onboarding/flow.md +33 -21
  15. package/skill/references/onboarding/modules/app-release.md +3 -1
  16. package/skill/references/onboarding/modules/authorization.md +3 -2
  17. package/skill/references/onboarding/modules/error-handling.md +6 -2
  18. package/skill/references/onboarding/modules/product-sign.md +5 -1
  19. package/skill/references/onboarding/modules/scripts/app.sh +27 -10
  20. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +6 -0
  21. package/skill/references/onboarding/modules/scripts/auth.sh +127 -37
  22. package/skill/references/onboarding/modules/scripts/error_handler.sh +108 -20
  23. package/skill/references/onboarding/modules/scripts/service.sh +9 -0
  24. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +10 -1
  25. package/skill/references/onboarding/modules/service-registration.md +1 -1
  26. package/skill/references/runtime-entry.md +6 -6
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # 支付宝 AI 付 Skill
2
2
 
3
- 支持按量付费、网站支付、APP 支付的代码开发与产品开通。
3
+ 支持 AI 按量付费、AI 网页应用收款、AI 移动应用收款的代码开发与产品开通。
4
4
 
5
5
  支付宝 AI 付站点:https://aipay.alipay.com/?from=alipay-aipay-skill
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.5.2-beta",
3
+ "version": "1.5.3",
4
4
  "description": "支付宝 AI 付 Skill 安装包 - 安装 Agent Skill,并检测/后台准备 alipay-cli;jq 可通过 --with-jq 尝试准备",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,8 +14,9 @@
14
14
  "scripts": {
15
15
  "build": "tsc",
16
16
  "dev": "tsc --watch",
17
+ "test": "node scripts/run-tests.mjs",
17
18
  "test:windows-integration": "node --test test/windows-integration-contract.test.mjs test/integration-message-runner.test.mjs test/alipay-cli-env.test.mjs",
18
- "prepublishOnly": "node scripts/validate-maintenance-alignment.mjs --release && node scripts/validate-process-optimization.mjs && npm run build"
19
+ "prepublishOnly": "npm test && node scripts/validate-maintenance-alignment.mjs --release && node scripts/validate-process-optimization.mjs && npm run build"
19
20
  },
20
21
  "keywords": [
21
22
  "alipay",
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.2-beta
1
+ 1.5.3
@@ -185,7 +185,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message win
185
185
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message start-confirm --product-type "$PRODUCT_TYPE" --product-name "$PRODUCT_NAME" --project-path "$PROJECT_PATH" --project-selection "$PROJECT_SELECTION" --language "$SERVER_LANGUAGE" --framework "$FRAMEWORK"
186
186
  ```
187
187
 
188
- runner 失败时不得展示手写方案或进入步骤 3。成功时必须同时取得唯一 `ALIPAY_AIPAY_INTERNAL:PROJECT_CONFIRM_RECEIPT=<receipt>`;缺失、重复或不可解析都视为失败。stdout 是步骤 2 唯一对客消息;Agent 必须把 stdout 原文作为非阻塞消息发给用户,不能用命令面板里的 stdout、折叠输出或“已渲染/已展示”说明替代。禁止添加 `<...>` 标签、“已输出完整方案”“服务声明已打印”、重复摘要或任何前后缀。发送后不停止、不等待回复,立即执行统一 runtime 的 `flow integration development --product-type "$PRODUCT_TYPE" --project-confirm-receipt "$PROJECT_CONFIRM_RECEIPT"`。loader 返回 `PROJECT_CONFIRMATION=VALID` 才能进入步骤 3;返回 `STALE` 时 stdout 已重新渲染当前标准方案并给出新 receipt,必须原文发送后使用新 receipt 立即重试一次;连续变化说明项目事实不稳定,停在步骤 2 核对,不要求用户输入确认口令。
188
+ runner 失败时不得展示手写方案或进入步骤 3。成功时必须同时取得唯一 `ALIPAY_AIPAY_INTERNAL:PROJECT_CONFIRM_RECEIPT=<receipt>`;缺失、重复或不可解析都视为失败。stdout 是步骤 2 唯一对客消息;Agent 必须把 stdout 原文作为非阻塞消息发给用户,不能用命令面板里的 stdout、折叠输出或“已渲染/已展示”说明替代。禁止添加 `<...>` 标签、“已输出完整方案”“服务声明已打印”、重复摘要或任何前后缀。发送后不停止、不等待回复,立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow integration development --product-type "$PRODUCT_TYPE" --project-confirm-receipt "$PROJECT_CONFIRM_RECEIPT"`。loader 返回 `PROJECT_CONFIRMATION=VALID` 才能进入步骤 3;返回 `STALE` 时 stdout 已重新渲染当前标准方案并给出新 receipt,必须原文发送后使用新 receipt 立即重试一次;连续变化说明项目事实不稳定,停在步骤 2 核对,不要求用户输入确认口令。
189
189
 
190
190
  - `full_process`:`../normal/full-process-routing.md` 已使用同一个 `integration.start.confirm`,在同一方案中绑定检查器产生的项目来源、代码状态和其他产品,以及产品、项目路径、语言/框架和服务声明;消息送达后已经直接通过 receipt 进入本步骤,禁止再次渲染或要求确认。该消息不展示步骤清单,也不缩减执行范围;后续仍必须按当前产品完成本 flow 的全部适用接口、完成条件和 checklist。任一方案字段变化时必须返回完整接入 router 重新扫描、渲染并自动校验新的单条方案。该方案仍禁止提前展示 MCC、授权范围、产品开通材料或 onboarding 待办。
191
191
 
@@ -426,7 +426,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message a2m
426
426
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message a2m-execution-guard --project-path "$PROJECT_PATH" --service-url "$SERVICE_URL" --http-method POST --request-body-file "$POST_BODY_FILE" [--prior-receipt "$A2M_EXECUTION_RECEIPT"]
427
427
  ```
428
428
 
429
- 只接受唯一 `ALIPAY_AIPAY_INTERNAL:A2M_EXECUTION=<json>`。`decision=RUN` 时保存新 receipt 并继续;`REUSE_PASSED` 复用同一项目和输入的本轮通过证据,不重复联调;`BLOCK_UNCHANGED` 表示失败后代码、配置和输入均未变化,禁止重跑;`UNKNOWN_PREVIOUS_EXECUTION` 表示上次命令可能已发出但未登记终态,禁止自动重跑,先核对实际结果。项目、服务 URL、方法或 POST body 变化时 runner 才会产生新的 `RUN` receipt。receipt 只保留在当前会话,不写跨会话业务进度。
429
+ 只接受唯一 `ALIPAY_AIPAY_INTERNAL:A2M_EXECUTION=<json>`。`decision=RUN` 时保存新 receipt 并继续;`REUSE_PASSED` 复用同一项目和输入的本轮通过证据,不重复联调;`BLOCK_UNCHANGED` 表示失败后代码、配置和输入均未变化,禁止重跑,将本轮 A2M 联调记录为失败并继续步骤 7/8;`UNKNOWN_PREVIOUS_EXECUTION` 表示上次命令可能已发出但未登记终态,当前没有登记的结果查询入口,禁止自动重跑或临时探测,固定记录联调结果未知并继续步骤 7/8。项目、服务 URL、方法或 POST body 变化时 runner 才会产生新的 `RUN` receipt。receipt 只保留在当前会话,不写跨会话业务进度。
430
430
 
431
431
  4. 仅 `decision=RUN` 时执行下列固定 runner 命令,输出固定联调说明后立即继续,不等待用户确认;禁止另写开始话术。服务地址和 HTTP 方法只用于后续实际命令,不在本消息中展示:
432
432
 
@@ -464,7 +464,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" a2m run --url "$SERVICE
464
464
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" a2m run --url "$SERVICE_URL" --method POST --body "@$POST_BODY_FILE" --buyer-id "$BUYER_2088" --auto-complete --require-payment-validation
465
465
  ```
466
466
 
467
- 只传已确认的本机服务 URL、GET/POST、实际沙箱买家 userId 和项目内 POST body 文件;禁止传入自定义收银端点、`Payment-Proof` 或 attestation。只有同一次命令退出码为 0,且输出包含实际资源交付校验通过和敏感产物已清理,才能记录联调通过;204、空响应、空资源、明确验付/履约失败或异常 `Payment-Validation` 均不得通过。命令结束后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message a2m-execution-result --receipt "$A2M_EXECUTION_RECEIPT" --outcome <PASSED|FAILED>` 并保存返回的新 receipt;进程被中断且无法登记时保留原 receipt,由下次 guard 返回 `UNKNOWN_PREVIOUS_EXECUTION`。失败后只有项目代码、配置或执行输入实际变化并取得新 `RUN` receipt 才能重新联调。
467
+ 只传已确认的本机服务 URL、GET/POST、实际沙箱买家 userId 和项目内 POST body 文件;禁止传入自定义收银端点、`Payment-Proof` 或 attestation。只有同一次命令退出码为 0,且输出包含实际资源交付校验通过和敏感产物已清理,才能记录联调通过;204、空响应、空资源、明确验付/履约失败或异常 `Payment-Validation` 均不得通过。命令结束后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message a2m-execution-result --receipt "$A2M_EXECUTION_RECEIPT" --outcome <PASSED|FAILED>` 并保存返回的新 receipt;进程被中断且无法登记时保留原 receipt,由下次 guard 返回 `UNKNOWN_PREVIOUS_EXECUTION`。失败、阻断重复或结果未知都必须进入步骤 7 和最终 checklist 的失败/人工待验证项;只有项目代码、配置或执行输入实际变化并取得新 `RUN` receipt 才能重新联调。
468
468
 
469
469
  服务未就绪时固定使用 `integration.service.not_ready` 输出实际缺口和恢复动作;必须执行下列命令。不得运行测试、登记 `INT.A2M.SERVICE_READY=PASS` 或临场改写近似话术。
470
470
 
@@ -39,7 +39,7 @@ alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox \
39
39
  | 变量 | 来源 | 取值规则 |
40
40
  |------|------|----------|
41
41
  | `PLATFORM` | AI 编程工具识别结果 | 参考通用脚本 `../../../normal/scripts/detect_dev_tool.sh` 识别当前工具后使用其输出值;缺失时使用 `unknown`。例如 Codex 环境检测结果为 `codex` |
42
- | `PLATFORM_ID` | 当前流程 `run_id` | 由 `sandbox_config.sh` 通过 `common.sh` 从当前 telemetry run 读取;若宿主提供 `COZE_PROJECT_ID`、`MEOO_PROJECT_ID`,或 `MIAODA` 环境下的 `VITE_APP_ID`/`PLATFORM_ID`,新建 run 时 `run_id` 与该项目 ID 一致;缺失时不阻断沙箱创建 |
42
+ | `PLATFORM_ID` | 当前流程 `run_id` | 由 `sandbox_config.sh` 通过 `common.sh` 从当前流程 run context 读取,遥测开启或关闭都不影响该值;若宿主提供 `COZE_PROJECT_ID`、`MEOO_PROJECT_ID`,或 `MIAODA` 环境下的 `VITE_APP_ID`/`PLATFORM_ID`,新建 run 时优先以该项目 ID 为 `run_id`;缺失时不阻断沙箱创建 |
43
43
  | `PRODUCT` | 不再传入 | 该 MCP 方法当前不需要产品上下文;不得把产品名写入 `--data` 或 CLI 环境变量 |
44
44
 
45
45
  **Codex 环境示例:**
@@ -166,7 +166,7 @@ source ~/.bashrc # Linux/bash
166
166
 
167
167
  > ⚠️ **强制规范:检测结果仅供内部使用,禁止向用户输出!**
168
168
 
169
- AI 编程工具检测使用通用脚本 `scripts/detect_dev_tool.sh`。shell 脚本通过 `scripts/common.sh` 提供的 `init_alipay_cli_context` 初始化 `DEV_TOOL_NAME`、`PLATFORM` 和 `PLATFORM_ID`;其中 `PLATFORM_ID` 取当前 telemetry `run_id`,用于把同一流程的 Skill 时间戳事件和 alipay-cli 上报关联起来。Onboarding 在产品确认后还通过同一公共脚本把 `aipay|webpay|apppay` 严格映射为 CLI 环境变量 `PRODUCT=AIPAY|WEBPAY|APPPAY`,所有环境检查、登录、查询、写入和文件上传调用都必须携带当前值;该字段不进入 MCP 请求 JSON。Integration 的产品确定前环境检查和快速沙箱仍不传 `PRODUCT`。若宿主环境提供 `COZE_PROJECT_ID`、`MEOO_PROJECT_ID`,或 `MIAODA` 环境下的 `VITE_APP_ID`/`PLATFORM_ID`,telemetry 新建 run 时直接使用该项目 ID 作为 `run_id`,脚本也会将同一值作为 `PLATFORM_ID`。文档示例或非 shell 流程可直接参考 `detect_dev_tool.sh` 的输出。
169
+ AI 编程工具检测使用通用脚本 `scripts/detect_dev_tool.sh`。shell 脚本通过 `scripts/common.sh` 提供的 `init_alipay_cli_context` 初始化 `DEV_TOOL_NAME`、`PLATFORM` 和 `PLATFORM_ID`;其中 `PLATFORM_ID` 取当前流程 `run_id`,用于关联同一流程,遥测开启或关闭都不影响该值。Onboarding 在产品确认后还通过同一公共脚本把 `aipay|webpay|apppay` 严格映射为 CLI 环境变量 `PRODUCT=AIPAY|WEBPAY|APPPAY`,所有环境检查、登录、查询、写入和文件上传调用都必须携带当前值;该字段不进入 MCP 请求 JSON。Integration 的产品确定前环境检查和快速沙箱仍不传 `PRODUCT`。若宿主环境提供 `COZE_PROJECT_ID`、`MEOO_PROJECT_ID`,或 `MIAODA` 环境下的 `VITE_APP_ID`/`PLATFORM_ID`,新建 run 时优先使用该项目 ID 作为 `run_id`,脚本也会将同一值作为 `PLATFORM_ID`。文档示例或非 shell 流程可直接参考 `detect_dev_tool.sh` 的输出。
170
170
 
171
171
  ```
172
172
  ❌ 禁止:向用户输出"环境检查完成,检测到您正在使用 Claude Code 环境"等检测结果
@@ -68,7 +68,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message sta
68
68
 
69
69
  上面命令失败时不得展示手写确认或进入子流程。成功时必须同时保存唯一 `ALIPAY_AIPAY_INTERNAL:PROJECT_CONFIRM_RECEIPT=<receipt>`;缺失、重复或不可解析视为失败。参数必须是当前确定值,禁止直接拼用户输入。runner 校验产品 variant 并固定服务声明;实际范围仍取当前 Integration flow 和 checklist。
70
70
 
71
- 分流、展示和交接事实必须来自 runner 内同一次 `scan` 对象;禁止用描述、Agent 自报或历史分类代替。`evidence` 仅内部校验,不进 renderer;Onboarding Step 1 前禁止传/猜 `mccCode`。方案同时满足 `INT.START.PLAN_DELIVERED` 与 `INT.SERVICE_STATEMENT.DELIVERED` 后,执行统一 runtime 的 `flow integration development --product-type "$PRODUCT_TYPE" --project-confirm-receipt "$PROJECT_CONFIRM_RECEIPT"`。只有内部 `PROJECT_CONFIRMATION=VALID` 才进入步骤 3;`STALE` 时 loader stdout 已重新渲染当前标准方案,必须原文发送并用新 receipt 立即重试一次,连续变化则停止核对。loader 在原位置记录兼容 stage 和预开发阅读 stage,禁止手工执行 telemetry;receipt 有效时禁止再次渲染 `integration.start.confirm`。
71
+ 分流、展示和交接事实必须来自 runner 内同一次 `scan` 对象;禁止用描述、Agent 自报或历史分类代替。`evidence` 仅内部校验,不进 renderer;Onboarding Step 1 前禁止传/猜 `mccCode`。方案同时满足 `INT.START.PLAN_DELIVERED` 与 `INT.SERVICE_STATEMENT.DELIVERED` 后,执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow integration development --product-type "$PRODUCT_TYPE" --project-confirm-receipt "$PROJECT_CONFIRM_RECEIPT"`。只有内部 `PROJECT_CONFIRMATION=VALID` 才进入步骤 3;`STALE` 时 loader stdout 已重新渲染当前标准方案,必须原文发送并用新 receipt 立即重试一次,连续变化则停止核对。loader 在原位置记录兼容 stage 和预开发阅读 stage,禁止手工执行 telemetry;receipt 有效时禁止再次渲染 `integration.start.confirm`。
72
72
 
73
73
  合法组合必须确定产品、路径/来源、代码状态、语言/框架、服务声明和顺序;发现其他产品时保留。零散信息、`新建` 选择、未选候选或新项目未准备均不能替代完整机器校验。方案送达并通过 receipt 校验后仍完整执行 Integration 和 checklist。
74
74
 
@@ -22,10 +22,11 @@ const productSalesCodes = Object.freeze({
22
22
  apppay: 'I1080300001000041313'
23
23
  });
24
24
  const signStatuses = new Set(['NOT_SIGNED', 'SIGN_SUBMITTED', 'SIGNED_EFFECTIVE', 'OTHER_STATUS']);
25
- const signFlows = Object.freeze({
26
- aipay: new Set(['FLOW:AI_PAY_NOT_SIGNED', 'FLOW:AI_PAY_SIGNED']),
27
- webpay: new Set(['FLOW:PC_WEB_NOT_SIGNED', 'FLOW:PC_WEB_SIGNED']),
28
- apppay: new Set(['FLOW:APP_NOT_SIGNED', 'FLOW:APP_SIGNED'])
25
+ const authSignReuseReceiptPrefix = 'AIPAY_SIGN_REUSE_V1.';
26
+ const signFlowByProduct = Object.freeze({
27
+ aipay: Object.freeze({ notSigned: 'FLOW:AI_PAY_NOT_SIGNED', signed: 'FLOW:AI_PAY_SIGNED' }),
28
+ webpay: Object.freeze({ notSigned: 'FLOW:PC_WEB_NOT_SIGNED', signed: 'FLOW:PC_WEB_SIGNED' }),
29
+ apppay: Object.freeze({ notSigned: 'FLOW:APP_NOT_SIGNED', signed: 'FLOW:APP_SIGNED' })
29
30
  });
30
31
  const serviceFlows = new Set(['FLOW:CREATE_NEW', 'FLOW:SELECT']);
31
32
  const applicationFlows = new Set(['FLOW:CREATE_NEW', 'FLOW:SELECT', 'FLOW:PENDING_APPLICATIONS']);
@@ -69,6 +70,92 @@ function uniqueFlow(lines, allowed) {
69
70
  return values.length === 1 ? values[0] : '';
70
71
  }
71
72
 
73
+ function allowedSignFlowValues(productType) {
74
+ const flows = signFlowByProduct[productType];
75
+ return new Set([flows.notSigned, flows.signed, 'FLOW:OTHER_STATUS']);
76
+ }
77
+
78
+ function normalizeSignFlow(value) {
79
+ if (typeof value !== 'string' || value.length === 0) return '';
80
+ return value.startsWith('FLOW:') ? value : `FLOW:${value}`;
81
+ }
82
+
83
+ function legalSignReuseDescription(productType) {
84
+ const flows = signFlowByProduct[productType];
85
+ return [
86
+ `NOT_SIGNED + ${flows.notSigned}`,
87
+ `SIGNED_EFFECTIVE + ${flows.signed}`,
88
+ `SIGN_SUBMITTED + ${flows.signed}`,
89
+ 'OTHER_STATUS + FLOW:OTHER_STATUS'
90
+ ].join(';');
91
+ }
92
+
93
+ function assertValidSignReuse({ productType, salesCode, signStatus, flow }) {
94
+ if (!productSalesCodes[productType]) {
95
+ throw new Error('签约复用 productType 非法,仅支持 aipay、webpay、apppay');
96
+ }
97
+ if (salesCode !== productSalesCodes[productType]) {
98
+ throw new Error(`签约复用 salesCode 与 productType 不匹配;${productType} 必须使用 ${productSalesCodes[productType]}`);
99
+ }
100
+ const normalizedFlow = normalizeSignFlow(flow);
101
+ if (!signStatuses.has(signStatus) || !allowedSignFlowValues(productType).has(normalizedFlow)) {
102
+ throw new Error(`签约复用状态或 flow 非法;允许组合:${legalSignReuseDescription(productType)}`);
103
+ }
104
+ const flows = signFlowByProduct[productType];
105
+ const validPair =
106
+ (signStatus === 'NOT_SIGNED' && normalizedFlow === flows.notSigned)
107
+ || ((signStatus === 'SIGNED_EFFECTIVE' || signStatus === 'SIGN_SUBMITTED') && normalizedFlow === flows.signed)
108
+ || (signStatus === 'OTHER_STATUS' && normalizedFlow === 'FLOW:OTHER_STATUS');
109
+ if (!validPair) {
110
+ throw new Error(`签约复用 status/flow 组合矛盾;允许组合:${legalSignReuseDescription(productType)}`);
111
+ }
112
+ return { signStatus, flow: normalizedFlow };
113
+ }
114
+
115
+ function decodeBase64UrlJson(value) {
116
+ const normalized = value.replace(/-/gu, '+').replace(/_/gu, '/');
117
+ const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
118
+ return JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
119
+ }
120
+
121
+ function parseSignReuseReceipt(receipt, productType, salesCode) {
122
+ if (typeof receipt !== 'string' || !receipt.startsWith(authSignReuseReceiptPrefix)) {
123
+ throw new Error('签约复用回执格式非法;必须使用 AUTH_SIGN_REUSE_RECEIPT 原值');
124
+ }
125
+ let parsed;
126
+ try {
127
+ parsed = decodeBase64UrlJson(receipt.slice(authSignReuseReceiptPrefix.length));
128
+ } catch {
129
+ throw new Error('签约复用回执无法解析;必须使用 AUTH_SIGN_REUSE_RECEIPT 原值');
130
+ }
131
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
132
+ throw new Error('签约复用回执内容非法');
133
+ }
134
+ const unknownKeys = Object.keys(parsed).filter((key) => ![
135
+ 'schema_ver',
136
+ 'product_type',
137
+ 'sales_code',
138
+ 'sign_status',
139
+ 'flow'
140
+ ].includes(key));
141
+ if (unknownKeys.length > 0) throw new Error(`签约复用回执包含未知字段:${unknownKeys.join('、')}`);
142
+ if (parsed.schema_ver !== 1) throw new Error('签约复用回执版本非法;当前仅支持 schema_ver=1');
143
+ if (parsed.product_type !== productType) throw new Error('签约复用回执与当前 product-type 不匹配');
144
+ if (parsed.sales_code !== salesCode) throw new Error('签约复用回执与当前 sales-code 不匹配');
145
+ const validated = assertValidSignReuse({
146
+ productType,
147
+ salesCode,
148
+ signStatus: parsed.sign_status,
149
+ flow: parsed.flow
150
+ });
151
+ return {
152
+ query_status: 'SUCCESS',
153
+ source: 'AUTH_REUSE',
154
+ sign_status: validated.signStatus,
155
+ flow: validated.flow
156
+ };
157
+ }
158
+
72
159
  function parseArray(value) {
73
160
  if (!value) return null;
74
161
  try {
@@ -83,7 +170,7 @@ function runScript(script, args) {
83
170
  return new Promise((resolve) => {
84
171
  const child = spawn('bash', [script, ...args], {
85
172
  cwd: onboardingScriptsDir,
86
- env: process.env,
173
+ env: { ...process.env, ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT: '1' },
87
174
  stdio: ['ignore', 'pipe', 'pipe']
88
175
  });
89
176
  let stdout = '';
@@ -108,19 +195,45 @@ function failedBranch(result, extra = {}) {
108
195
  function signingBranch(result, productType) {
109
196
  const lines = internalLines(result.stderr);
110
197
  const signStatus = uniqueValue(lines, 'SIGN_STATUS=');
111
- const flow = uniqueFlow(lines, signFlows[productType]);
198
+ const flow = uniqueFlow(lines, allowedSignFlowValues(productType));
112
199
  if (result.status !== 0 || !signStatuses.has(signStatus) || !flow) return failedBranch(result);
113
- return { query_status: 'SUCCESS', source: 'QUERY', sign_status: signStatus, flow };
200
+ try {
201
+ const validated = assertValidSignReuse({
202
+ productType,
203
+ salesCode: productSalesCodes[productType],
204
+ signStatus,
205
+ flow
206
+ });
207
+ return { query_status: 'SUCCESS', source: 'QUERY', sign_status: validated.signStatus, flow: validated.flow };
208
+ } catch {
209
+ return failedBranch(result);
210
+ }
114
211
  }
115
212
 
116
213
  function reusedSigningBranch(args, productType) {
214
+ const salesCode = productSalesCodes[productType];
215
+ const receipt = args['reuse-sign-receipt'];
117
216
  const signStatus = args['reuse-sign-status'];
118
217
  const flow = args['reuse-sign-flow'];
119
- if (signStatus === undefined && flow === undefined) return null;
120
- if (!signStatuses.has(signStatus) || !signFlows[productType].has(flow)) {
121
- throw new Error('授权签约复用标记不完整或与当前产品不匹配');
218
+ if (receipt === undefined && signStatus === undefined && flow === undefined) return null;
219
+ if (receipt !== undefined) {
220
+ const fromReceipt = parseSignReuseReceipt(receipt, productType, salesCode);
221
+ if (signStatus !== undefined || flow !== undefined) {
222
+ if (signStatus === undefined || flow === undefined) {
223
+ throw new Error('签约复用旧双参数必须同时提供 --reuse-sign-status 与 --reuse-sign-flow');
224
+ }
225
+ const fromLegacy = assertValidSignReuse({ productType, salesCode, signStatus, flow });
226
+ if (fromLegacy.signStatus !== fromReceipt.sign_status || fromLegacy.flow !== fromReceipt.flow) {
227
+ throw new Error('签约复用回执与旧双参数冲突,禁止降级复用');
228
+ }
229
+ }
230
+ return fromReceipt;
231
+ }
232
+ if (signStatus === undefined || flow === undefined) {
233
+ throw new Error('签约复用旧双参数必须同时提供 --reuse-sign-status 与 --reuse-sign-flow');
122
234
  }
123
- return { query_status: 'SUCCESS', source: 'AUTH_REUSE', sign_status: signStatus, flow };
235
+ const validated = assertValidSignReuse({ productType, salesCode, signStatus, flow });
236
+ return { query_status: 'SUCCESS', source: 'AUTH_REUSE', sign_status: validated.signStatus, flow: validated.flow };
124
237
  }
125
238
 
126
239
  function serviceBranch(result) {
@@ -146,7 +259,9 @@ function hasAuthBlocker(result) {
146
259
  return internalLines(result.stderr).some((line) => [
147
260
  'AUTH_FLOW:AUTH_REQUIRED',
148
261
  'AUTH_FLOW:SCOPE_MISMATCH',
149
- 'AUTH_FLOW:MCC_MISMATCH'
262
+ 'AUTH_FLOW:MCC_MISMATCH',
263
+ 'AUTH_FLOW:RETRY_WITH_NETWORK',
264
+ 'AUTH_FLOW:LOGOUT_STILL_LOGGED_IN'
150
265
  ].includes(line));
151
266
  }
152
267
 
@@ -211,7 +326,7 @@ async function runDiscovery(args) {
211
326
  export async function runOnboardingDiscoveryCli(argv = process.argv) {
212
327
  const { command, args } = parseArgs(argv);
213
328
  if (command !== 'run') {
214
- throw new Error('用法: onboarding_discovery_runner.mjs run --product-type <aipay|webpay|apppay> --sales-code <code> [--reuse-sign-status <status> --reuse-sign-flow <flow>]');
329
+ throw new Error('用法: onboarding_discovery_runner.mjs run --product-type <aipay|webpay|apppay> --sales-code <code> [--reuse-sign-receipt <AUTH_SIGN_REUSE_RECEIPT> | --reuse-sign-status <status> --reuse-sign-flow <flow>]');
215
330
  }
216
331
  process.exitCode = await runDiscovery(args);
217
332
  }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { createHash } from 'node:crypto';
3
4
  import path from 'node:path';
4
5
  import process from 'node:process';
5
6
  import { fileURLToPath } from 'node:url';
@@ -93,7 +94,9 @@ function parseArgs(argv) {
93
94
  if (!key.startsWith('--')) throw new Error(`未知参数 ${key}`);
94
95
  const value = argv[index + 1];
95
96
  if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${key} 缺少取值`);
96
- args[key.slice(2)] = value;
97
+ const name = key.slice(2);
98
+ if (Object.prototype.hasOwnProperty.call(args, name)) throw new Error(`参数 ${key} 重复`);
99
+ args[name] = value;
97
100
  index += 1;
98
101
  }
99
102
  return { command, args };
@@ -183,8 +186,34 @@ const mccAliasRules = [
183
186
  targetTerms: ['电商', '网上商城', '线上商城']
184
187
  }
185
188
  ];
189
+ const agentMccStatuses = new Set(['MATCHED', 'INSUFFICIENT', 'UNSUPPORTED']);
186
190
 
187
- function mccSearchResult(query) {
191
+ function mccCandidateFromRecord(record) {
192
+ return {
193
+ mccCode: record.code,
194
+ mccName: record.name,
195
+ applicableBusinesses: record.applicableBusinesses,
196
+ qualification: record.qualification
197
+ };
198
+ }
199
+
200
+ function mccTableSummary(records = loadMccRecords()) {
201
+ const entries = [...records.values()]
202
+ .map((record) => [record.code, record.name, record.applicableBusinesses, record.qualification].join('\t'))
203
+ .sort()
204
+ .join('\n');
205
+ return {
206
+ recordCount: records.size,
207
+ sha256: createHash('sha256').update(entries).digest('hex')
208
+ };
209
+ }
210
+
211
+ function directMccCode(query) {
212
+ const raw = query.trim().toUpperCase();
213
+ return /^A\d{4}_B\d{4}$/u.test(raw) ? raw : '';
214
+ }
215
+
216
+ function deterministicMccMatches(query, { limit = 8 } = {}) {
188
217
  const normalizedQuery = assertMccQuery(query);
189
218
  const scored = [];
190
219
  for (const record of loadMccRecords().values()) {
@@ -201,43 +230,112 @@ function mccSearchResult(query) {
201
230
  score = Math.max(score, 50);
202
231
  }
203
232
  }
204
- if (score > 0) scored.push({ record, score });
233
+ if (score > 0) scored.push({ record, score, strength: score >= 70 ? 'STRONG' : 'WEAK' });
205
234
  }
206
235
  const strongMatches = scored.filter(({ score }) => score >= 70);
207
236
  const ranked = strongMatches.length > 0 ? strongMatches : scored;
208
- const candidates = ranked
237
+ return ranked
209
238
  .sort((left, right) => right.score - left.score || left.record.code.localeCompare(right.record.code))
210
- .slice(0, 8)
211
- .map(({ record }) => ({
212
- mccCode: record.code,
213
- mccName: record.name,
214
- applicableBusinesses: record.applicableBusinesses,
215
- qualification: record.qualification
216
- }));
239
+ .slice(0, limit);
240
+ }
241
+
242
+ function parseAgentMccCodes(args) {
243
+ const raw = optional(args, 'agent-mcc-codes') || '';
244
+ const codes = raw.trim()
245
+ ? raw.split(/[,,、\s]+/u).map((code) => code.trim().toUpperCase()).filter(Boolean)
246
+ : [];
247
+ if (codes.length > 3) throw new Error('agent-mcc-codes 最多允许 3 个编码');
248
+ if (new Set(codes).size !== codes.length) throw new Error('agent-mcc-codes 包含重复 MCC 编码');
249
+ return codes;
250
+ }
251
+
252
+ function agentMccInput(args, query, records) {
253
+ const exactCode = directMccCode(query);
254
+ if (exactCode && records.has(exactCode) && args['agent-mcc-status'] === undefined && args['agent-mcc-codes'] === undefined) {
255
+ return {
256
+ semanticStatus: 'NOT_REQUIRED_DIRECT_CODE',
257
+ codes: [],
258
+ directCode: exactCode
259
+ };
260
+ }
261
+
262
+ const semanticStatus = required(args, 'agent-mcc-status');
263
+ if (!agentMccStatuses.has(semanticStatus)) {
264
+ throw new Error('agent-mcc-status 非法,仅支持 MATCHED、INSUFFICIENT、UNSUPPORTED');
265
+ }
266
+ const codes = parseAgentMccCodes(args);
267
+ if (semanticStatus === 'MATCHED' && codes.length === 0) throw new Error('agent-mcc-status=MATCHED 时必须提供 1 到 3 个 agent-mcc-codes');
268
+ if (semanticStatus !== 'MATCHED' && codes.length > 0) throw new Error('agent-mcc-status=INSUFFICIENT|UNSUPPORTED 时不得提供 agent-mcc-codes');
269
+ for (const code of codes) {
270
+ if (!/^A\d{4}_B\d{4}$/u.test(code)) throw new Error(`agent-mcc-codes 包含非法 MCC 编码:${code}`);
271
+ if (!records.has(code)) throw new Error(`agent-mcc-codes 包含当前 MCC 表不存在的编码:${code}`);
272
+ }
273
+ return { semanticStatus, codes, directCode: '' };
274
+ }
275
+
276
+ function resolveMccResult(query, args) {
277
+ const records = loadMccRecords();
278
+ const agent = agentMccInput(args, query, records);
279
+ if (agent.directCode) {
280
+ const candidate = mccCandidateFromRecord(records.get(agent.directCode));
281
+ return {
282
+ matchType: 'UNIQUE',
283
+ semanticStatus: agent.semanticStatus,
284
+ deterministicCandidateCodes: [agent.directCode],
285
+ deterministicStrongCandidateCodes: [agent.directCode],
286
+ deterministicWeakCandidateCodes: [],
287
+ agentCandidateCodes: [],
288
+ mergedCandidateCodes: [agent.directCode],
289
+ mccTableSummary: mccTableSummary(records),
290
+ candidates: [candidate]
291
+ };
292
+ }
293
+
294
+ const deterministicMatches = deterministicMccMatches(query, { limit: 5 });
295
+ const deterministicStrongCodes = deterministicMatches.filter(({ strength }) => strength === 'STRONG').map(({ record }) => record.code);
296
+ const deterministicWeakCodes = deterministicMatches.filter(({ strength }) => strength === 'WEAK').map(({ record }) => record.code);
297
+ const deterministicCodes = deterministicMatches.map(({ record }) => record.code);
298
+ const agentCodes = agent.codes;
299
+ if (agent.semanticStatus !== 'MATCHED') {
300
+ return {
301
+ matchType: 'NONE',
302
+ semanticStatus: agent.semanticStatus,
303
+ deterministicCandidateCodes: deterministicCodes,
304
+ deterministicStrongCandidateCodes: deterministicStrongCodes,
305
+ deterministicWeakCandidateCodes: deterministicWeakCodes,
306
+ agentCandidateCodes: agentCodes,
307
+ mergedCandidateCodes: [],
308
+ mccTableSummary: mccTableSummary(records),
309
+ candidates: []
310
+ };
311
+ }
312
+ const deterministicCodeSet = new Set(deterministicCodes);
313
+ const agentCodeSet = new Set(agentCodes);
314
+ const appendUnique = (target, codes) => {
315
+ for (const code of codes) {
316
+ if (!target.includes(code)) target.push(code);
317
+ }
318
+ };
319
+ const mergedCodes = [];
320
+ appendUnique(mergedCodes, deterministicCodes.filter((code) => agentCodeSet.has(code)));
321
+ appendUnique(mergedCodes, deterministicStrongCodes.filter((code) => !agentCodeSet.has(code)));
322
+ appendUnique(mergedCodes, agentCodes.filter((code) => !deterministicCodeSet.has(code)));
323
+ appendUnique(mergedCodes, deterministicWeakCodes.filter((code) => !agentCodeSet.has(code)));
324
+ const finalCodes = mergedCodes.slice(0, 8);
325
+ const candidates = finalCodes.map((code) => mccCandidateFromRecord(records.get(code)));
217
326
  return {
218
327
  matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
328
+ semanticStatus: agent.semanticStatus,
329
+ deterministicCandidateCodes: deterministicCodes,
330
+ deterministicStrongCandidateCodes: deterministicStrongCodes,
331
+ deterministicWeakCandidateCodes: deterministicWeakCodes,
332
+ agentCandidateCodes: agentCodes,
333
+ mergedCandidateCodes: finalCodes,
334
+ mccTableSummary: mccTableSummary(records),
219
335
  candidates
220
336
  };
221
337
  }
222
338
 
223
- async function runMccSearch(args) {
224
- const productType = args['product-type'];
225
- if (productType !== undefined) productFor({ ...args, 'product-type': productType });
226
- emitStageMetricAsync('onboarding.mcc_prepare.start', { productType });
227
- const query = required(args, 'query');
228
- return JSON.stringify(mccSearchResult(query));
229
- }
230
-
231
- async function runMccCandidates(args) {
232
- const productType = required(args, 'product-type');
233
- productFor({ ...args, 'product-type': productType });
234
- const result = mccSearchResult(required(args, 'query'));
235
- if (result.candidates.length === 0) return renderMessage('onboarding.mcc.no_match', 'DEFAULT', {}, { productType });
236
- return renderMessage('onboarding.mcc.candidates', 'DEFAULT', {
237
- mccCandidates: result.candidates
238
- }, { productType });
239
- }
240
-
241
339
  function renderPlanConfirm(productType, mccCandidate) {
242
340
  const product = productFor({ 'product-type': productType });
243
341
  return renderMessage('onboarding.plan.confirm', product.variant, {
@@ -257,14 +355,14 @@ async function runPlanConfirm(args) {
257
355
  });
258
356
  }
259
357
 
260
- async function runMccResolve(args, { renderPlan = false } = {}) {
358
+ async function runMccResolvePlan(args) {
261
359
  const productType = required(args, 'product-type');
262
360
  productFor({ ...args, 'product-type': productType });
263
361
  emitStageMetricAsync('onboarding.mcc_prepare.start', { productType });
264
- const result = mccSearchResult(required(args, 'query'));
362
+ const result = resolveMccResult(required(args, 'query'), args);
265
363
  process.stderr.write(`ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=${JSON.stringify(result)}\n`);
266
364
  if (result.matchType === 'UNIQUE') {
267
- return renderPlan ? renderPlanConfirm(productType, result.candidates[0]) : '';
365
+ return renderPlanConfirm(productType, result.candidates[0]);
268
366
  }
269
367
  if (result.matchType === 'NONE') {
270
368
  return renderMessage('onboarding.mcc.no_match', 'DEFAULT', {}, { productType });
@@ -561,21 +659,8 @@ export async function runOnboardingMessageCli(argv = process.argv) {
561
659
  console.log(await runMccClarify(args));
562
660
  return;
563
661
  }
564
- if (command === 'mcc-search') {
565
- console.log(await runMccSearch(args));
566
- return;
567
- }
568
- if (command === 'mcc-candidates') {
569
- console.log(await runMccCandidates(args));
570
- return;
571
- }
572
- if (command === 'mcc-resolve') {
573
- const message = await runMccResolve(args);
574
- if (message) console.log(message);
575
- return;
576
- }
577
662
  if (command === 'mcc-resolve-plan') {
578
- const message = await runMccResolve(args, { renderPlan: true });
663
+ const message = await runMccResolvePlan(args);
579
664
  if (message) console.log(message);
580
665
  return;
581
666
  }
@@ -632,7 +717,7 @@ export async function runOnboardingMessageCli(argv = process.argv) {
632
717
  console.log(runApplicationResult(args));
633
718
  return;
634
719
  }
635
- throw new Error('用法: onboarding_message_runner.mjs <platform-entry|product-clarify|enterprise-unsupported|mcc-resolve|mcc-resolve-plan|plan-confirm|mcc-search|mcc-clarify|mcc-candidates|material-collect|application-candidates|service-candidates|application-recheck|closeout|service-create-summary|service-update-confirm|application-harmony-manual-create|application-reuse-no-key|signing-result|service-result|application-result> ...');
720
+ throw new Error('用法: onboarding_message_runner.mjs <platform-entry|product-clarify|enterprise-unsupported|mcc-resolve-plan|plan-confirm|mcc-clarify|material-collect|application-candidates|service-candidates|application-recheck|closeout|service-create-summary|service-update-confirm|application-harmony-manual-create|application-reuse-no-key|signing-result|service-result|application-result> ...');
636
721
  }
637
722
 
638
723
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
@@ -586,11 +586,11 @@ const managedEntrypoints = {
586
586
  reason: '产品已明确但用户尚未提供 MCC/经营描述时由 runner 绑定产品名称,禁止手写经营类目补问;检索无匹配时不得回到本消息。'
587
587
  },
588
588
  'onboarding.mcc.no_match': {
589
- command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-candidates --query <business-description> --product-type <aipay|webpay|apppay>',
589
+ command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query <business-description> --product-type <aipay|webpay|apppay> --agent-mcc-status <MATCHED|INSUFFICIENT|UNSUPPORTED> [--agent-mcc-codes <Axxxx_Bxxxx,...>]',
590
590
  reason: '用户已经提供经营描述但当前 MCC 表无确定匹配时,由 runner 输出独立恢复路径,禁止重新渲染初始 mcc-clarify 形成循环。'
591
591
  },
592
592
  'onboarding.mcc.candidates': {
593
- command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-candidates --query <business-description> --product-type <aipay|webpay|apppay>',
593
+ command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query <business-description> --product-type <aipay|webpay|apppay> --agent-mcc-status <MATCHED|INSUFFICIENT|UNSUPPORTED> [--agent-mcc-codes <Axxxx_Bxxxx,...>]',
594
594
  reason: '产品已明确且 MCC 存在多个相似候选时由 runner 绑定本地 MCC 表生成候选事实,禁止手写候选表。'
595
595
  },
596
596
  'onboarding.discovery.summary': {
@@ -598,7 +598,7 @@ const managedEntrypoints = {
598
598
  reason: '该 runner 执行本轮适用查询、校验原始脚本事实、输出唯一 DISCOVERY_RESULT envelope,并在同一次 stdout 生成摘要;禁止单独重放摘要。'
599
599
  },
600
600
  'onboarding.plan.confirm': {
601
- command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query <经营描述|MCC编码|MCC名称> --product-type <aipay|webpay|apppay>,或已持有唯一 MCC 事实时使用同一入口执行 onboarding-message plan-confirm --product-type <...> --mcc-code <...> --mcc-name <...> --mcc-qualification <...>',
601
+ command: 'node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> [--agent-mcc-status <MATCHED|INSUFFICIENT|UNSUPPORTED> --agent-mcc-codes <Axxxx_Bxxxx,...>],或已持有唯一 MCC 事实时使用同一入口执行 onboarding-message plan-confirm --product-type <...> --mcc-code <...> --mcc-name <...> --mcc-qualification <...>',
602
602
  reason: '方案展示必须由 runner 绑定本轮唯一 MCC 事实和产品变体生成,避免 Agent 用 jq/printf/管道手写 renderer 输入。'
603
603
  },
604
604
  'materials.category.collect': {
@@ -36,6 +36,8 @@ const nodeEntrypoints = Object.freeze({
36
36
 
37
37
  export const runtimeDomains = Object.freeze([
38
38
  'self-update',
39
+ 'onboarding-bootstrap',
40
+ 'onboarding-execute-ready',
39
41
  ...Object.keys(nodeEntrypoints),
40
42
  'env',
41
43
  ...Object.keys(shellEntrypoints),
@@ -84,7 +86,74 @@ function runtimeError(domain, error) {
84
86
  process.stderr.write(`${prefixes[domain] || 'ALIPAY_AIPAY_INTERNAL:RUNTIME_ERROR='}${message}\n`);
85
87
  }
86
88
 
89
+ function parseRuntimeArgs(args) {
90
+ const parsed = {};
91
+ for (let index = 0; index < args.length; index += 1) {
92
+ const token = args[index];
93
+ if (!token.startsWith('--')) throw new Error(`未知参数 ${token}`);
94
+ const value = args[index + 1];
95
+ if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${token} 缺少取值`);
96
+ const name = token.slice(2);
97
+ if (Object.prototype.hasOwnProperty.call(parsed, name)) throw new Error(`参数 ${token} 重复`);
98
+ parsed[name] = value;
99
+ index += 1;
100
+ }
101
+ return parsed;
102
+ }
103
+
104
+ function requiredOption(options, name) {
105
+ const value = options[name];
106
+ if (typeof value !== 'string' || value.length === 0) throw new Error(`缺少参数 --${name}`);
107
+ return value;
108
+ }
109
+
110
+ function assertOnlyOptions(options, allowedNames) {
111
+ const allowed = new Set(allowedNames);
112
+ const unknown = Object.keys(options).filter((name) => !allowed.has(name));
113
+ if (unknown.length > 0) throw new Error(`未知参数 --${unknown[0]}`);
114
+ }
115
+
116
+ function assertEnumOption(options, name, allowedValues, { required = true } = {}) {
117
+ if (!Object.prototype.hasOwnProperty.call(options, name)) {
118
+ if (required) throw new Error(`缺少参数 --${name}`);
119
+ return '';
120
+ }
121
+ const value = requiredOption(options, name);
122
+ if (!allowedValues.includes(value)) {
123
+ throw new Error(`参数 --${name} 非法,仅支持 ${allowedValues.join('、')}`);
124
+ }
125
+ return value;
126
+ }
127
+
128
+ async function runOnboardingBootstrap(args) {
129
+ const options = parseRuntimeArgs(args);
130
+ assertOnlyOptions(options, ['root-intent', 'product-type']);
131
+ const rootIntent = assertEnumOption(options, 'root-intent', ['onboarding_only']);
132
+ const productType = assertEnumOption(options, 'product-type', ['aipay', 'webpay', 'apppay'], { required: false });
133
+ const telemetryArgs = ['start-run', '--root-intent', rootIntent];
134
+ if (productType) telemetryArgs.push('--product-type', productType);
135
+ const { runTelemetryCli } = await import('./telemetry.mjs');
136
+ await runTelemetryCli(cliArgv(telemetryArgs));
137
+ const { runOnboardingMessageCli } = await import('./onboarding_message_runner.mjs');
138
+ await runOnboardingMessageCli(cliArgv(['platform-entry']));
139
+ const { isWindowsPlatform } = await import('./platform_compat.mjs');
140
+ if (isWindowsPlatform()) return;
141
+ const { runFlowCli } = await import('./flow_loader.mjs');
142
+ runFlowCli(cliArgv(['onboarding', 'entry']));
143
+ }
144
+
145
+ async function runOnboardingExecuteReady(args) {
146
+ const options = parseRuntimeArgs(args);
147
+ assertOnlyOptions(options, ['product-type']);
148
+ const productType = assertEnumOption(options, 'product-type', ['aipay', 'webpay', 'apppay']);
149
+ const { runFlowCli } = await import('./flow_loader.mjs');
150
+ runFlowCli(cliArgv(['onboarding', 'execute', '--product-type', productType]));
151
+ return dispatch('env', ['check', '--product-type', productType]);
152
+ }
153
+
87
154
  async function dispatch(domain, args) {
155
+ if (domain === 'onboarding-bootstrap') return runOnboardingBootstrap(args);
156
+ if (domain === 'onboarding-execute-ready') return runOnboardingExecuteReady(args);
88
157
  if (shellEntrypoints[domain]) {
89
158
  const [script, cwd] = shellPath(domain);
90
159
  return runShell(script, cwd, args);