@alipay/alipay-aipay 1.5.8-beta3 → 1.5.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.5.8-beta3",
3
+ "version": "1.5.8",
4
4
  "description": "支付宝 AI 付 Skill 安装包 - 优先安装 Agent Skill,并在 macOS/Linux 后台准备用户级 CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.8-beta3
1
+ 1.5.8
@@ -163,12 +163,13 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message win
163
163
 
164
164
  5. Unix/macOS/Linux 的 `ensure` 只依据同一目标项目的 `.alipay-sandbox.json` 当前事实分流:文件不存在时执行既有 `create`;文件存在或为符号链接时执行既有 `verify` 安全路径。已存在配置不得再次调用 `createAnonymousSandbox`、不得覆盖,也不得因为中断重来要求用户删除配置;无效、不安全或被 Git 跟踪时不得使用,脚本按待配置终态统一输出后继续代码开发。
165
165
  6. 只有 Unix/macOS/Linux 创建分支会调用冻结的 `alipay-anonymous-sandbox.createAnonymousSandbox`、`{"request":{"appType":"PUBLICAPP"}}`、`PLATFORM` 环境变量和 `result.content[0].text -> success -> data` 解包逻辑;禁止传入 `PRODUCT`。Windows 不调用该方法或 `sandbox_config.sh`。
166
- 7. Unix/macOS/Linux 只接受以下互斥终态,不能把待配置标记当作成功:
166
+ 7. Unix/macOS/Linux 只接受以下互斥最终结果和一个临时恢复结果;待配置不算成功:
167
167
  - `READY`:退出码为 0;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_ENSURE_ACTION=CREATED|VERIFIED`、`SANDBOX_CONFIG_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_READY` 内部事实。
168
168
  - `CREATE_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/CREATE` 正文;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_CREATE` 内部事实;不得出现 READY 标记。
169
169
  - `VERIFY_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/VERIFY` 正文;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_VERIFY` 内部事实;不得出现 READY 标记。
170
- 8. 所有 `ALIPAY_AIPAY_INTERNAL:` 行只用于流程转移;即使宿主合并 stdout/stderr,也必须整行剔除后再判断对客正文。命中 `READY` 时剔除后 stdout 为空,不发送“环境检查完成”“沙箱已创建/已复核”等消息,记录 `sandboxConfigState=READY` 并直接进入步骤 2;命中待配置终态时,脚本 stdout 已包含唯一对客正文,Agent 原文发送且不得转述,分别记录 `sandboxConfigState=CREATE_PENDING|VERIFY_PENDING` 后直接进入步骤 2。两类终态都禁止追加 `jq`、`cat`、`grep`、`ls`、`stat` 或临时脚本;待配置状态还禁止读取、引用或复制当前无效/不存在配置中的任何字段。
171
- 9. 命令非 0、终态标记缺失/重复/冲突、路径不一致或输出含 `SANDBOX_ERROR` 时属于脚本执行协议失败,仍停留在步骤 1;不得把这种执行异常降级为待配置,也不得临场改写 jq、重放原始 MCP 命令或自行检查敏感配置。Windows 配置检查未通过仍按 Windows 登记分支处理。项目内自建 JSON、Agent 自报或补充诊断结果都不能替代上述终态。
170
+ - `LOCAL_FS_RECOVERY`:仅创建分支、退出码 `77`,唯一包含上述 CREATE pending 两个事实及 `SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE`、`CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`;stdout 是现有 pending 正文,先暂存且不得发送,CLI 调用次数为 0。允许权限升级后原样重试一次:`READY` 时丢弃暂存正文;普通 `CREATE_PENDING` 或再次命中本结果时只发送重试 stdout 并接受其 pending 事实;拒绝或宿主不支持时发送首次 stdout 并接受首次 pending 事实。不得再次申请、新增消息或确认。
171
+ 8. 所有 `ALIPAY_AIPAY_INTERNAL:` 行只用于转移;宿主合并输出时也先整行剔除。`READY` 的 stdout 为空,静默记录 `READY` 并直接进入步骤 2;普通 pending 或按上项降级时只原文发送最终选定的一份 stdout,记录 `CREATE_PENDING|VERIFY_PENDING` 并直接进入步骤 2。禁止追加 `jq|cat|grep|ls|stat` 或临时脚本;待配置时禁止读取、引用或复制无效/不存在配置的字段。
172
+ 9. 除上项精确的 `LOCAL_FS_RECOVERY` 外,命令非 0、终态标记缺失/重复/冲突、路径不一致或输出含 `SANDBOX_ERROR` 时属于脚本执行协议失败,仍停留在步骤 1;不得把这种执行异常降级为待配置,也不得临场改写 jq、重放原始 MCP 命令或自行检查敏感配置。CLI 已执行后才出现本地状态权限证据时,创建请求属于 `MAYBE_SENT`,脚本不得返回恢复 marker 或重放,`ensure` 直接使用既有 `CREATE_PENDING` 正文和终态继续代码开发。Windows 配置检查未通过仍按 Windows 登记分支处理。项目内自建 JSON、Agent 自报或补充诊断结果都不能替代上述终态。
172
173
  10. 准备阶段不得渲染 `sandbox.environment.summary` 或 `sandbox.environment.reminder`,不得展示沙箱买家密码,也不得输出“沙箱测试通过”。只有 `sandboxConfigState=READY` 且代码开发完成并通过配置后置校验后,才按步骤 5 的摘要展示规则对客展示沙箱环境配置。
173
174
 
174
175
  ### 步骤 2:产品决策
@@ -555,7 +556,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message che
555
556
  ```
556
557
  <!-- ALIPAY_AIPAY_PRODUCT:END -->
557
558
 
558
- `failedItems` 与 `manualItems` 必须在各自受控单行值中包含对应的确定下一步;没有对应项目时明确传入“无”。`sandboxConfigState` 必须直接复用本轮脚本终态,只允许 `READY|CREATE_PENDING|VERIFY_PENDING`。`blockingDefectState` 来自同一最终 checklist:除沙箱配置及其直接依赖动作外,存在任一代码、安全或必要依据缺口时必须为 `PRESENT`,否则为 `NONE`。待配置时 `failedItems` 必须包含沙箱配置及其依赖动作未完成;恢复口令已经由步骤 1/3 的 `sandbox.configuration.pending` 输出,本结果不再重复。
559
+ `failedItems` 与 `manualItems` 必须在各自受控单行值中包含对应的确定下一步;没有对应项目时明确传入“无”。`sandboxConfigState` 必须直接复用本轮脚本终态,只允许 `READY|CREATE_PENDING|VERIFY_PENDING`。`blockingDefectState` 来自同一最终 checklist:除沙箱配置及其直接依赖动作外,存在任一代码、安全或必要依据缺口时必须为 `PRESENT`,否则为 `NONE`。待配置时 `failedItems` 必须包含沙箱配置及其依赖动作未完成;恢复口令已由步骤 1/3 选定并发送的唯一 `sandbox.configuration.pending` 正文输出,checklist runner 不生成也不重复该消息。
559
560
 
560
561
  同一次代码开发流程只渲染一次最终 checklist 结果;renderer stdout 是本次默认收口的唯一对客正文,禁止在前后追加风险说明、通过项摘要、执行说明、衔接提醒或第二份待办。
561
562
 
@@ -160,7 +160,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message che
160
160
 
161
161
  未完成公网 HTTPS `notify_url` 联调时,禁止把第一种表述写成第二种。
162
162
 
163
- `failedItems` 与 `manualItems` 必须在各自受控单行值中包含对应的确定下一步;没有对应项目时明确传入“无”。`overallResult=通过` 时两者必须都是“无”、`sandboxConfigState=READY` 且 `blockingDefectState=NONE`;“部分通过”必须至少存在一类缺口;“未通过”必须列出未通过项。`blockingDefectState` 只表示除沙箱配置及其直接依赖动作之外,是否存在代码、安全或必要依据缺口;当前环境无法自动取得 UI、浏览器或公网联调证据但代码与安全依据已完成时,只登记 `manualItems`,不把该人工待验证项自动计为 `PRESENT`。待配置时恢复口令由 renderer 固定生成。`full_process` 在待配置且无其他阻塞缺口,或 `READY` 且仅剩非阻塞人工待验证项时,输出“部分通过”并继续产品开通;其他未通过组合保持停留提醒。
163
+ `failedItems` 与 `manualItems` 必须在各自受控单行值中包含对应的确定下一步;没有对应项目时明确传入“无”。`overallResult=通过` 时两者必须都是“无”、`sandboxConfigState=READY` 且 `blockingDefectState=NONE`;“部分通过”必须至少存在一类缺口;“未通过”必须列出未通过项。`blockingDefectState` 只表示除沙箱配置及其直接依赖动作之外,是否存在代码、安全或必要依据缺口;当前环境无法自动取得 UI、浏览器或公网联调证据但代码与安全依据已完成时,只登记 `manualItems`,不把该人工待验证项自动计为 `PRESENT`。待配置恢复口令来自步骤 1/3 已选定并发送的唯一 `sandbox.configuration.pending` 正文,checklist runner 不生成或重复该消息。`full_process` 在待配置且无其他阻塞缺口,或 `READY` 且仅剩非阻塞人工待验证项时,输出“部分通过”并继续产品开通;其他未通过组合保持停留提醒。
164
164
 
165
165
  同一次代码开发只渲染一次最终结果;renderer stdout 是默认收口的唯一对客正文,禁止在前后追加通过摘要、风险说明、产品开通提醒或第二份待办。
166
166
 
@@ -62,6 +62,15 @@ LANGUAGE="$(normalize_language "$LANGUAGE")" || {
62
62
  CONFIG_PATH="${PROJECT_PATH}/.alipay-sandbox.json"
63
63
  GITIGNORE_PATH="${PROJECT_PATH}/.gitignore"
64
64
 
65
+ render_pending() {
66
+ printf '%s' '{}' | node "$RENDERER" sandbox.configuration.pending --variant "$PENDING_VARIANT" || {
67
+ echo "SANDBOX_ERROR:沙箱待配置消息渲染失败" >&2
68
+ return 1
69
+ }
70
+ emit_control_field "SANDBOX_PENDING_PATH" "$CONFIG_PATH" || return 1
71
+ emit_control "$PENDING_MARKER"
72
+ }
73
+
65
74
  if [ "$MODE" = "summary" ]; then
66
75
  case "$PRODUCT_TYPE" in aipay) PRODUCT_NAME="AI 按量付费" ;; webpay) PRODUCT_NAME="AI 网页应用收款" ;; apppay) PRODUCT_NAME="AI 移动应用收款" ;; *) echo "SANDBOX_ERROR:产品非法,仅支持 aipay|webpay|apppay" >&2; exit 2 ;; esac
67
76
  fi
@@ -96,6 +105,14 @@ if [ "$MODE" = "ensure" ] || [ "$MODE" = "reverify" ]; then
96
105
  while true; do
97
106
  ensure_output=$(bash "$0" "$ENSURE_MODE" "$PROJECT_PATH" "$LANGUAGE" 2>&1)
98
107
  ensure_rc=$?
108
+ if [ "$ensure_rc" -eq "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE" ] &&
109
+ [ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE')" -eq 1 ] &&
110
+ [ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION')" -eq 1 ]; then
111
+ render_pending || exit 1
112
+ emit_control "SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE"
113
+ emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
114
+ exit "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
115
+ fi
99
116
  if [ "$ensure_rc" -eq 0 ] &&
100
117
  [ "$(printf '%s\n' "$ensure_output" | grep -Fxc "ALIPAY_AIPAY_INTERNAL:SANDBOX_CONFIG_PATH=$CONFIG_PATH")" -eq 1 ] &&
101
118
  [ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:FLOW:SANDBOX_CONFIG_READY')" -eq 1 ] &&
@@ -115,12 +132,7 @@ if [ "$MODE" = "ensure" ] || [ "$MODE" = "reverify" ]; then
115
132
  break
116
133
  done
117
134
 
118
- printf '%s' '{}' | node "$RENDERER" sandbox.configuration.pending --variant "$PENDING_VARIANT" || {
119
- echo "SANDBOX_ERROR:沙箱待配置消息渲染失败" >&2
120
- exit 1
121
- }
122
- emit_control_field "SANDBOX_PENDING_PATH" "$CONFIG_PATH" || exit 1
123
- emit_control "$PENDING_MARKER"
135
+ render_pending || exit 1
124
136
  exit 0
125
137
  fi
126
138
 
@@ -230,10 +242,19 @@ if [ "$MODE" = "create" ]; then
230
242
  ensure_gitignore_protection || { echo "SANDBOX_ERROR:无法为沙箱配置建立 Git 忽略保护" >&2; exit 1; }
231
243
  PLATFORM=$(bash "$DETECTOR") || PLATFORM="unknown"
232
244
  init_platform_id
245
+ if ! require_alipay_cli_local_state_access; then
246
+ emit_control "SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE"
247
+ emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
248
+ exit "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
249
+ fi
233
250
  attempt=1
234
251
  while true; do
235
252
  raw=$(env -u PRODUCT PLATFORM="$PLATFORM" PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox --data '{"request":{"appType":"PUBLICAPP"}}' --json 2>&1)
236
253
  rc=$?
254
+ if alipay_cli_has_local_state_permission_error "$raw"; then
255
+ echo "SANDBOX_ERROR:快速沙箱创建结果不明,禁止自动重复创建" >&2
256
+ exit 1
257
+ fi
237
258
  [ "$rc" -eq 0 ] && break
238
259
  if printf '%s' "$raw" | grep -qiE '版本过旧|version.*old'; then break; fi
239
260
  if [ "$attempt" -lt 3 ] && printf '%s' "$raw" | grep -qiE 'MCP_SERVICE_ERROR|SERVICE_UNSTABLE|timeout|timed out|connection|network|ECONN|socket|temporar'; then
@@ -255,6 +276,10 @@ if [ "$MODE" = "create" ]; then
255
276
  sleep 2
256
277
  raw=$(env -u PRODUCT PLATFORM="$PLATFORM" PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox --data '{"request":{"appType":"PUBLICAPP"}}' --json 2>&1)
257
278
  rc=$?
279
+ if alipay_cli_has_local_state_permission_error "$raw"; then
280
+ echo "SANDBOX_ERROR:快速沙箱创建结果不明,禁止自动重复创建" >&2
281
+ exit 1
282
+ fi
258
283
  [ "$rc" -eq 0 ] || { echo "SANDBOX_ERROR:证书密钥临时错误重试后 CLI 调用失败" >&2; exit 1; }
259
284
  outer=$(extract_json_payload "$raw") || { echo "SANDBOX_ERROR:重试输出中未找到唯一合法 JSON 对象" >&2; exit 1; }
260
285
  text=$(echo "$outer" | jq -r '.result.content[0].text // .content[0].text // empty')
@@ -39,6 +39,9 @@ which alipay-cli 2>/dev/null && alipay-cli version
39
39
  - 若命令已经因网络失败,重试时必须复用同一条命令并申请可联网权限;不要把该失败解释为用户网络故障。
40
40
  - `auth.sh init|confirm|mismatch` 返回 `AUTH_FLOW:RETRY_WITH_NETWORK`:当前环境未取得可确认 CLI 结果;取联网权限后重试同一完整命令,不得解释为业务或 login/logout 失败,也不得改为用户手工操作。
41
41
  - `auth.sh init|wait|confirm|mismatch` 返回 `AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`:仅缺 CLI 状态目录权限。允许同一 runtime 命令访问 `$HOME/.alipay-cli/credentials`、`${XDG_DATA_HOME:-$HOME/.local/share}/alipay-cli/logs` 后原样重试一次;仅供 CLI 使用,Agent 禁止读取、列举、打印或复制已有内容,不改变其他脚本边界。
42
+ - 签约、服务、应用、截图上传及匿名沙箱创建等受控 runtime 动作同样属于“联网 + CLI 私有状态读写”命令。脚本在参数和上下文校验后、首次 `alipay-cli` 调用前,对上述两个目录执行私有随机文件的创建、固定内容写入、同目录 rename 和删除探测;同一 Shell 进程后续分页、轮询或写后核验不重复探测,不读取、列举或改动任何已有凭据和日志。
43
+ - 非 auth 动作在 CLI 调用前探测失败时只输出 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`,本次 CLI 调用次数为 0;允许当前同一 runtime 命令访问两个目录后原样重试一次。discovery 将其收口为唯一 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`。拒绝、不支持或重试仍命中同一 marker 时停止申请,按当前 flow 的既有分支失败或待配置边界继续,不新增业务确认或普通对客文案。
44
+ - CLI 已执行后才出现本地状态权限证据时,只读动作仍可按上述 marker 原样重试一次;写动作一律视为 `MAYBE_SENT`,只走现有查询核验或 `UNKNOWN`,不得输出本地权限重试 marker、不得重放原写请求。stdout 成功 JSON 与 stderr 本地权限强证据冲突时仍按本规则阻断。
42
45
  - `app.sh reuse|verify-key|verify-key-and-audit|audit` 已取得 RSA2 支付宝公钥但 `${XDG_CONFIG_HOME:-$HOME/.config}` 探测失败时,返回 `APP_FLOW:RETRY_WITH_LOCAL_CONFIG_PERMISSION` 和 `ALIPAY_PUBLIC_KEY_EXPORT_STATUS=RETRY_WITH_LOCAL_CONFIG_PERMISSION`。仅允许同一 runtime 命令访问该目录后原样重试一次,用于写 `<appId>-alipayPublicKey.keytext`;Agent 禁止检查已有文件。仍失败按目录文案收口。
43
46
  - 本地纯解析、文件检查、`bash -n`、`jq` 校验等不联网命令仍可在默认沙箱内执行。
44
47
 
@@ -106,6 +106,14 @@ require_alipay_cli_local_state_access() {
106
106
  ALIPAY_CLI_LOCAL_STATE_ACCESS_CONFIRMED=true
107
107
  }
108
108
 
109
+ require_alipay_cli_local_state_access_or_emit() {
110
+ if require_alipay_cli_local_state_access; then
111
+ return 0
112
+ fi
113
+ emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
114
+ return "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
115
+ }
116
+
109
117
  alipay_aipay_config_dir() {
110
118
  local config_home="${XDG_CONFIG_HOME:-}"
111
119
  if [ -z "$config_home" ]; then
@@ -255,17 +255,29 @@ function applicationBranch(result) {
255
255
  return { query_status: 'SUCCESS', application_type: applicationType, flow, candidates };
256
256
  }
257
257
 
258
- function hasAuthBlocker(result) {
259
- return internalLines(result.stderr).some((line) => [
258
+ function authBlockerFlows(result) {
259
+ return internalLines(result.stderr).filter((line) => [
260
260
  'AUTH_FLOW:AUTH_REQUIRED',
261
261
  'AUTH_FLOW:SCOPE_MISMATCH',
262
- 'AUTH_FLOW:MCC_MISMATCH',
263
- 'AUTH_FLOW:RETRY_WITH_NETWORK',
264
- 'AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION',
265
- 'AUTH_FLOW:LOGOUT_STILL_LOGGED_IN'
262
+ 'AUTH_FLOW:MCC_MISMATCH'
266
263
  ].includes(line));
267
264
  }
268
265
 
266
+ function hasAuthBlocker(result) {
267
+ return authBlockerFlows(result).length > 0;
268
+ }
269
+
270
+ function authRecoveryAction(results) {
271
+ const flows = new Set(results.flatMap(authBlockerFlows));
272
+ if (flows.has('AUTH_FLOW:SCOPE_MISMATCH') || flows.has('AUTH_FLOW:MCC_MISMATCH')) return 'MISMATCH';
273
+ if (flows.has('AUTH_FLOW:AUTH_REQUIRED')) return 'INIT';
274
+ return '';
275
+ }
276
+
277
+ function hasLocalFsRecovery(result) {
278
+ return internalLines(result.stderr).includes('CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION');
279
+ }
280
+
269
281
  function hasNetworkRecovery(result) {
270
282
  return internalLines(result.stderr).some((line) =>
271
283
  line.startsWith('NETWORK_RETRY_EXHAUSTED=') && line.endsWith(':RETRY_WITH_NETWORK'));
@@ -299,8 +311,10 @@ async function runDiscovery(args) {
299
311
  ]);
300
312
 
301
313
  const executedResults = [signingResult, serviceResult, applicationResult].filter(Boolean);
302
- const globalBlocker = executedResults.some(hasAuthBlocker) ? 'AUTHORIZATION_INVALID' : '';
303
- const retryWithNetwork = !globalBlocker && executedResults.some(hasNetworkRecovery);
314
+ const recoveryAction = authRecoveryAction(executedResults);
315
+ const globalBlocker = recoveryAction ? 'AUTHORIZATION_INVALID' : '';
316
+ const retryWithLocalFs = !globalBlocker && executedResults.some(hasLocalFsRecovery);
317
+ const retryWithNetwork = !globalBlocker && !retryWithLocalFs && executedResults.some(hasNetworkRecovery);
304
318
  const envelope = {
305
319
  schema_ver: 1,
306
320
  product_type: productType,
@@ -310,21 +324,28 @@ async function runDiscovery(args) {
310
324
  : { query_status: 'NOT_APPLICABLE', applicable: false },
311
325
  application: applicationBranch(applicationResult)
312
326
  };
313
- if (globalBlocker) envelope.global_blocker = globalBlocker;
327
+ if (globalBlocker) {
328
+ envelope.global_blocker = globalBlocker;
329
+ envelope.auth_recovery_action = recoveryAction;
330
+ }
314
331
  emitEnvelope(envelope);
315
- if (retryWithNetwork) {
332
+ if (retryWithLocalFs) {
333
+ process.stderr.write(`${internalPrefix}DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION\n`);
334
+ } else if (retryWithNetwork) {
316
335
  process.stderr.write(`${internalPrefix}DISCOVERY_FLOW:RETRY_WITH_NETWORK\n`);
317
336
  }
318
337
 
319
- const customerOutputResults = globalBlocker
320
- ? executedResults.filter(hasAuthBlocker)
321
- : executedResults;
338
+ const customerOutputResults = retryWithLocalFs
339
+ ? []
340
+ : globalBlocker
341
+ ? executedResults.filter(hasAuthBlocker)
342
+ : executedResults;
322
343
  const customerOutputs = customerOutputResults
323
344
  .map((result) => result.stdout.trim())
324
345
  .filter(Boolean);
325
346
  let uniqueCustomerOutputs = [...new Set(customerOutputs)];
326
347
  if (globalBlocker) uniqueCustomerOutputs = uniqueCustomerOutputs.slice(0, 1);
327
- if (!globalBlocker) {
348
+ if (!globalBlocker && !retryWithLocalFs) {
328
349
  uniqueCustomerOutputs.push(renderDiscoverySummary(productType, {
329
350
  signStatus: envelope.signing.query_status === 'SUCCESS' ? envelope.signing.sign_status : 'QUERY_FAILED',
330
351
  serviceFlow: envelope.service.query_status === 'SUCCESS' ? envelope.service.flow : 'QUERY_FAILED',
@@ -332,7 +353,7 @@ async function runDiscovery(args) {
332
353
  }));
333
354
  }
334
355
  if (uniqueCustomerOutputs.length > 0) process.stdout.write(`${uniqueCustomerOutputs.join('\n\n')}\n`);
335
- return globalBlocker || retryWithNetwork ? 2 : 0;
356
+ return globalBlocker || retryWithLocalFs || retryWithNetwork ? 2 : 0;
336
357
  }
337
358
 
338
359
  export async function runOnboardingDiscoveryCli(argv = process.argv) {
@@ -6,7 +6,7 @@
6
6
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" self-update check --skill-dir "<SKILL_DIR>"
7
7
  ```
8
8
 
9
- `<SKILL_DIR>` 必须是包含当前 updater 的本份 Skill 规范化绝对路径,不得换成启动目录、项目目录、Home、npm 源码根或其他副本。命令不得改成变量赋值、命令替换、相对路径或特定 shell 语法;`VERSION` 或 `references/runtime-entry.md` 缺失不改变该绑定,由检查器进入精确修复。
9
+ `<SKILL_DIR>` 必须是当前这份包含 updater 的 Skill 规范化绝对路径,不得换成启动目录、项目目录、Home、npm 源码根或其他副本。命令不得改成变量赋值、命令替换、相对路径或特定 shell 语法;`VERSION` 或 `references/runtime-entry.md` 缺失不改变该绑定,由检查器进入精确修复。
10
10
 
11
11
  stderr 必须恰有一个下列 `SELF_UPDATE:*` 内部终态;不对客,不透传 npm/npx 原始输出。
12
12
 
@@ -54,7 +54,7 @@
54
54
 
55
55
  ---
56
56
 
57
- ## 二、主流程
57
+ ## 主流程概览
58
58
 
59
59
  ```
60
60
  Step 1: 方案规划 → Step 2: 环境检查 → Step 3: 登录授权
@@ -94,7 +94,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message ente
94
94
 
95
95
  `onboarding_only` 固定使用 `ONBOARDING_ONLY`;`full_process` 只会在代码开发已通过后进入本 flow,固定使用 `FULL_PROCESS`。runner stdout 是该意图的最终产品开通消息,必须把 stdout 原文作为当前回复正文发给用户;命令面板、折叠输出或“已渲染/已展示”说明不能替代对客消息。发出 stdout 原文后直接停止本 flow,不再执行 Step 6 或追加第二份收口。
96
96
 
97
- ## 一、产品映射
97
+ ## 产品映射
98
98
 
99
99
  > 术语说明:本 Skill 对客统一使用“AI 网页应用收款”,覆盖电脑网页和手机浏览器网页/H5 场景;“网站支付”“电脑网站支付”“PC网站支付”“手机网站支付”“H5支付”均作为输入别名映射到同一产品。签约 payload 中的 `appType=PC_WEB` 是接口字段名,不代表只能用于 PC 端网页。
100
100
 
@@ -104,7 +104,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message ente
104
104
  | AI 网页应用收款 | webpay | WEBPAY | I1080300001000041203 | app:all,fast_instant_trade_pay:write | 需要3张网站截图 |
105
105
  | AI 移动应用收款 | apppay | APPPAY | I1080300001000041313 | app:all,auth_alipay_apppay:write | 未签约时需要 APP 名称和 3 张 APP 界面截图,签约状态固定按 `OFFLINE` 提交;选择新建应用时在 Step 4 前置采集移动平台信息,Step 5.3 才用于创建 |
106
106
 
107
- ## 三、Step 详解
107
+ ## Step 详解
108
108
 
109
109
  ### Step 1: 方案规划
110
110
 
@@ -140,7 +140,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-
140
140
 
141
141
  `MULTIPLE` 同时只接受唯一 `ALIPAY_AIPAY_INTERNAL:MCC_SELECTION_RECEIPT=<opaque>`。用户回复序号、当前候选表中的完整 MCC 名称或编码时,只能映射到该 receipt 绑定候选快照的对应行,并执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --select "$SELECTED_MCC_CODE" --selection-receipt "$MCC_SELECTION_RECEIPT"`;`MCC_SEARCH_QUERY` 仍使用产生该候选快照的原始经营描述,runner 必须校验产品、原始描述摘要、候选成员和当前 MCC 表摘要均未变化后生成唯一方案,不重新运行 Agent 泛化或扩大候选。候选、MCC 表摘要、产品或原始经营描述变化后旧 receipt 失效。用户提供新的经营描述时才开始新一轮双路召回。`UNIQUE` 直接输出非阻塞方案;`MULTIPLE|NONE` 继续使用 runner 本次真实 stdout。无需固定重试口令,不得猜测或追加近似候选。
142
142
 
143
- 产品确定后,`salesCode`、`scope` 和材料要求统一取自“一、产品映射”,禁止在本步骤另建映射或改写固定值。
143
+ 产品确定后,`salesCode`、`scope` 和材料要求统一取自“产品映射”,禁止在本步骤另建映射或改写固定值。
144
144
 
145
145
  **MCC类目**:
146
146
  - 用户直接提供当前表内完整 MCC 编码时,由 runner 精确校验并使用表内原值,不读取 `modules/mcc-reference.md` 做 Agent 泛化;其余经营描述无论确定性召回结果为唯一、多候选或无匹配,都必须先按上文只读取一次该文件完成 Agent 语义判断
@@ -238,7 +238,7 @@ Step 1 的产品/MCC 唯一事实校验和方案送达、Step 2 的环境 READY
238
238
 
239
239
  `auth.page` 非阻塞并追加“扫码完成后请回到 Agent 对话页面”;`auth.pending` 仅由 wait 耗尽或单次 confirm 的 PENDING 渲染,其他对客终态由脚本渲染对应消息。自动轮询期间即使 `login --complete` 已返回可复核状态,只要 `whoami` 尚未取得有效本机登录态,也继续按 PENDING 处理并消耗本次 wait 预算,不输出断头失败文案。剔除 `ALIPAY_AIPAY_INTERNAL:` 内部行后的 stdout 是当前动作唯一对客正文;禁止再次调用 renderer、另写近似话术、输出 stderr 或内部行、重复转述。授权脚本明确返回链接过期时必须重建,旧回复失效。
240
240
 
241
- 授权 `scope` 统一使用“一、产品映射”中当前产品的固定值,禁止自行修改。
241
+ 授权 `scope` 统一使用“产品映射”中当前产品的固定值,禁止自行修改。
242
242
 
243
243
  必须原样展示 init stdout,并在 login 前完成固定产品/MCC 上下文和 URL 逐项校验。禁止 CLI `verification_url`、非登记 URL 或要求用户输入确认码;这些正常路径约束均由本节命令和 `auth.sh` 托管,不为重复确认而读取模块。
244
244
 
@@ -255,9 +255,9 @@ Step 1 的产品/MCC 唯一事实校验和方案送达、Step 2 的环境 READY
255
255
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-discovery run --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE" [--reuse-sign-receipt "$AUTH_SIGN_REUSE_RECEIPT"]
256
256
  ```
257
257
 
258
- 只接受唯一 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_RESULT=<json>`。runner stdout 保留底层脚本已有的受控错误正文,并在没有全局授权阻断时追加原 `onboarding.discovery.summary` 标准正文;必须原文送达,不得手写、压缩或再次调用 `discovery-summary`。envelope 中每个分支只允许 `SUCCESS|QUERY_FAILED|NOT_APPLICABLE`:成功分支携带原脚本的确定状态和候选,失败不得伪装为空列表;存在 `global_blocker=AUTHORIZATION_INVALID` 时停止查询组,按 Step 3 重新授权,不进入 Step 4。不存在授权阻断且底层重试耗尽具有 DNS、连接、timeout、socket/TLS、`fetch failed`、`network_error`、`网络连接失败` 或连接前沙箱禁网强证据时,runner 额外且至多输出一个 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_FLOW:RETRY_WITH_NETWORK`;支付宝侧明确服务异常、业务错误、裸 `permission denied` 或结构异常不得触发该 marker。
258
+ 只接受唯一 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_RESULT=<json>`。runner stdout 保留底层受控错误,并在无全局授权阻断或本地权限恢复时追加原 `onboarding.discovery.summary`;必须原文送达,不得手写、压缩或重放摘要。envelope 中每个分支只允许 `SUCCESS|QUERY_FAILED|NOT_APPLICABLE`;服务/应用成功时 `candidates` 必须是数组,应用还须有合法 `application_type`,否则不得当空列表。`global_blocker=AUTHORIZATION_INVALID` 必须唯一携带 `auth_recovery_action=INIT|MISMATCH`:任一子查询为 `SCOPE_MISMATCH|MCC_MISMATCH` 时固定 `MISMATCH` 并执行 Step 3 第 4 项,否则 `AUTH_REQUIRED` 固定 `INIT` 并执行第 1 项;随后停止查询且不进入 Step 4。字段非法、缺失或与该优先级矛盾时停止核对,禁止猜测。无授权阻断且任一子查询命中 CLI 私有状态权限证据时,runner 只输出一个 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`,不输出摘要或网络 marker;否则仅在 DNS、连接、timeout、socket/TLS、`fetch failed`、`network_error`、`网络连接失败` 或连接前沙箱禁网强证据下至多输出一个 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_FLOW:RETRY_WITH_NETWORK`。支付宝侧服务异常、业务错误、裸 `permission denied` 或结构异常不得触发二者。
259
259
 
260
- 命中 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 时先暂存本次 stdout,不对客发送;宿主支持时申请联网权限,并以完全相同的产品、salesCode 和签约复用回执原样重试同一条 discovery 命令一次。重试成功只送达重试后的 stdout 并继续;权限被拒绝、宿主不支持时送达首次 stdout 一次;重试仍返回同一 marker 时只送达重试后的 stdout 一次并停止再次申请。该恢复不新增对客文案、确认点或 closeout 枚举。`AUTHORIZATION_INVALID` 优先,禁止同时进入网络恢复。
260
+ 命中 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 时 stdout 必须为空;宿主支持时只允许当前同一 discovery 命令访问两个 CLI 私有状态目录并原样重试一次。拒绝、不支持或再次命中时停止申请,按最新 envelope 保留失败分支,不渲染额外恢复文案。命中 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 时先暂存本次 stdout,不对客发送;宿主支持时申请联网权限,并以完全相同的产品、salesCode 和签约复用回执原样重试同一条 discovery 命令一次。重试成功只送达重试后的 stdout 并继续;权限被拒绝、宿主不支持时送达首次 stdout 一次;重试仍返回同一 marker 时只送达重试后的 stdout 一次并停止再次申请。两类恢复都不新增对客文案、确认点或 closeout 枚举。优先级固定为 `AUTHORIZATION_INVALID`、本地权限、网络权限,禁止同时进入两个恢复分支。
261
261
 
262
262
  不存在全局授权阻断时,执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding branch-summary` 加载 Step 4 和三个分支共用的 Step 5 约束;共用区块只加载一次,不得整份读取后续 flow。
263
263
 
@@ -273,8 +273,9 @@ Step 5 直接使用对应 phase 的完整命令;仅出现脚本异常、需要
273
273
 
274
274
  所有内部事实行都必须以 `ALIPAY_AIPAY_INTERNAL:` 开头;宿主合并 stdout/stderr 时也逐行剔除。无前缀的相似文本不得作为流程事实;唯一字段缺失、重复或冲突时按结构异常处理。
275
275
 
276
- - MCP 认证失败或产品/scope/MCC 授权不匹配:discovery runner 子查询只上报认证失效事实,不在并发子进程内 logout;立即停止查询组,不再处理剩余查询结果。授权不匹配时调用 Step 3 的 `auth.sh mismatch`,由该命令统一 logout 并重新授权。重新授权后仅恢复尚未执行的查询,保留重新授权前已经成功取得且主体未变化的结果。若无法确认仍为同一账号主体,则重新执行全部适用查询。
276
+ - MCP 认证失败或产品/scope/MCC 不匹配:子查询只报事实、不 logout,并停止查询组;只按 `auth_recovery_action` 执行 Step 3 第 1 或第 4 项,禁止凭 stdout 猜测。重新授权后保留主体未变化的成功结果并恢复其余查询;主体无法确认时重查全部。
277
277
  - 强网络证据且无授权阻断:只按上文 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 契约申请权限并原样重试 discovery 一次;连接前沙箱明确禁网不在子脚本内浪费两次固定间隔重试。远端服务异常仍使用既有分支失败和收口,不误报为本地联网权限。
278
+ - CLI 私有状态权限且无授权阻断:只按上文 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 契约恢复同一只读查询组一次;不得改走 auth mismatch、不得解释为网络或支付宝侧查询失败。
278
279
  - 单个业务失败、响应结构错误或 MCP 服务失败,但当前会话仍有效:把该分支标记为查询失败,继续其他不依赖该结果的只读查询,最后一次性输出“已取得结果 + 失败分支”摘要。不得把失败视为空列表;应用查询失败时禁止应用复用/创建,服务查询失败时禁止服务复用/新建/修改。
279
280
  - 签约查询失败:只阻断签约分支;可继续应用/服务查询、候选展示、资源决策及其独立资料采集和写操作。不得推断签约状态,不得采集签约截图或签约所需 APP 名称,`signStatus` 和签约 `materialStatus` 保持未设置。签约查询恢复成功后,只有状态为 `NOT_SIGNED` 才进入签约材料类别。应用或服务查询失败也只阻断对应分支;认证失败、授权不匹配或无法确认登录主体一致时仍是全局阻断。
280
281
 
@@ -287,9 +288,9 @@ Step 5 直接使用对应 phase 的完整命令;仅出现脚本异常、需要
287
288
  <!-- ALIPAY_AIPAY_FLOW:ONBOARDING_BRANCH_SUMMARY:START -->
288
289
  ### Step 4: 分支摘要与分类材料
289
290
 
290
- Step 3.1 的 discovery runner 已在同一次 stdout 中展示签约、服务市场和应用发布摘要;网站支付和 APP 支付的服务市场固定为“无需处理”,查询失败显示“查询失败”。进入本阶段后只消费同一 `DISCOVERY_RESULT` envelope,禁止再次调用 `discovery-summary`、手写状态枚举或重复输出摘要。
291
+ Step 3.1 通常已在同一次 stdout 展示三分支摘要;网站支付和 APP 支付的服务市场固定为“无需处理”,查询失败显示“查询失败”。本地权限未恢复时没有摘要,只消费最新失败 envelope,禁止补写或重放。进入本阶段后只消费同一 `DISCOVERY_RESULT`,不得再次调用 `discovery-summary`、手写或重复输出摘要。
291
292
 
292
- 轮到服务或应用时,以下 runner 用同一 discovery 快照一次生成候选事实和适用选择提示;stdout 是唯一对客正文,禁止提前展示、手工拼表、压缩为 ID、拆开或改写:
293
+ 轮到服务或应用时,只有 `query_status=SUCCESS` 且 `candidates` 为数组,才用以下 runner 基于同一快照生成候选事实和选择提示;stdout 是唯一正文,禁止提前展示、手工拼表、压缩、拆分或改写:
293
294
 
294
295
  ```bash
295
296
  # 仅按量付费,轮到服务类别时
@@ -299,7 +300,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message serv
299
300
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message application-candidates --application-type "$APPLICATION_TYPE" --candidates-json "$APP_CANDIDATES_JSON"
300
301
  ```
301
302
 
302
- 空候选时直接进入新建资料分支;非空时发出 runner stdout 后等待当前候选允许的选择。
303
+ 只有成功分支的实际 `candidates.length === 0` 才进入新建;非空时发送 runner stdout 并等待选择。`QUERY_FAILED`、候选缺失或类型非法均阻断该分支,禁止候选 runner、创建资料和写动作,不影响其他成功分支。
303
304
 
304
305
  每次只展示一个类别,同类一次收齐且只补问缺失或错误字段。`MATERIAL_CATEGORY` 固定为 `signing|service|application`;`INITIAL` 对应“待补充”,`PARTIAL` 对应“部分已提供,待补充”,`INVALID` 对应“校验失败,需更正”,其他状态停止核对。字段别名只交给 runner 归一化;禁止使用 `签约资料` 等同义词,正常路径禁止调用 `render --schema`、`jq` 或读取消息目录。只执行:
305
306
 
@@ -313,6 +314,8 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mate
313
314
 
314
315
  按“签约 -> 服务 -> 应用”默认顺序,一次只处理一个实际适用且可推进的类别;顺序不构成分支依赖。认证、授权或主体变化时全局停止并重新校验;单分支失败只登记该分支恢复动作,继续其他独立分支,不清除已验证材料或成功事实。全部适用分支已处理,或失败、未知及外部待办均已登记后,才执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding closeout`;仍有可推进分支时不得提前收口。
315
316
 
317
+ 本阶段的签约、服务、应用和截图 runtime 命令在 CLI 调用前返回唯一 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 时,当前业务调用次数为 0;允许同一命令访问 CLI 私有状态目录后原样重试一次,不重收材料、不重开页面、不新增确认。拒绝、不支持或再次命中时停止申请并按当前分支既有失败边界收口。CLI 已执行后出现同类证据时,读操作仍可进入该恢复;签约提交、服务保存、应用创建/公钥页/提审和截图上传等写操作只走既有核验或 `UNKNOWN`,禁止因权限 marker 重放。
318
+
316
319
  轮到某类别且即将执行其业务动作时,先执行对应 loader:签约用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding signing --product-type "$PRODUCT_TYPE"`,仅按量付费服务用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding service --product-type "$PRODUCT_TYPE"`,应用用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding application --product-type "$PRODUCT_TYPE"`。loader 自动记录该分支 stage;未轮到或不适用的分支不得加载、不得记录。
317
320
 
318
321
  <!-- ALIPAY_AIPAY_FLOW:ONBOARDING_BRANCH_SUMMARY:END -->
@@ -356,7 +359,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" sign --product apppay -
356
359
  <!-- ALIPAY_AIPAY_STAGE:onboarding.service.start products=aipay -->
357
360
  #### 5.2 服务市场注册(仅按量付费)
358
361
 
359
- 列表为空进入新建;存在候选只接受当前完整 `serviceId`、`新建` 或 `修改:<当前serviceId>`,不接受序号、候选外/历史 ID 或自由格式修改。创建/修改必须用服务名称、描述、地址、单价、请求示例 JSON 五项资料通过 `validate`。单价可输入纯数字或带 `元`/`元/次` 后缀(可空格),后续只复用脚本 `SERVICE_PRICING=<纯数字>` 内部事实。复用不写;创建由 `save` 内部重查完整列表,只有当前服务数量小于固定上限 `10` 才调用保存方法;达到 `10` 个时禁止调用保存方法,调用次数必须为 0。校验通过先输出非阻塞摘要后直接创建。只有修改必须取得 `SERVICE_UPDATE_ONLY` 当前摘要确认;会话、主体、产品、目标或参数变化即失效。成功实际 `serviceId` 记为 `productionServiceId`;状态未返回只能写“状态未取得”,不得推断或重复 `save`。
362
+ 仅 Step 3.1 服务查询成功且候选数组为空时新建;查询失败、缺失或非法时阻断。存在候选只接受当前完整 `serviceId`、`新建` 或 `修改:<当前serviceId>`,不接受序号、候选外/历史 ID 或自由格式修改。创建/修改必须用服务名称、描述、地址、单价、请求示例 JSON 五项资料通过 `validate`。单价可输入纯数字或带 `元`/`元/次` 后缀(可空格),后续只复用脚本 `SERVICE_PRICING=<纯数字>` 内部事实。复用不写;创建由 `save` 内部重查完整列表,只有当前服务数量小于固定上限 `10` 才调用保存方法;达到 `10` 个时禁止调用保存方法,调用次数必须为 0。校验通过先输出非阻塞摘要后直接创建。只有修改必须取得 `SERVICE_UPDATE_ONLY` 当前摘要确认;会话、主体、产品、目标或参数变化即失效。成功实际 `serviceId` 记为 `productionServiceId`;状态未返回只能写“状态未取得”,不得推断或重复 `save`。
360
363
 
361
364
  ```bash
362
365
  # 创建摘要后不等待回复
@@ -385,7 +388,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" service save --service-
385
388
  <!-- ALIPAY_AIPAY_STAGE:onboarding.application.start -->
386
389
  #### 5.3 应用发布
387
390
 
388
- **应用分支**:按量付费/网站支付使用 `WEBAPP`,APP 支付使用 `MOBILEAPP`。复用 Step 3.1 候选和 Step 4 条件资料,按本节固定命令执行复用或新建、设置/校验应用公钥和提审;产品上下文、平台字段、`appSign` 边界、MCP 参数和返回处理由 `app.sh` 托管,只有异常诊断或业务解释时读取 `modules/app-release.md`。
391
+ **应用分支**:按量付费/网站支付使用 `WEBAPP`,APP 支付使用 `MOBILEAPP`。仅 Step 3.1 查询成功、类型合法且候选为数组时继续;失败、缺失或非法时阻断。复用候选和 Step 4 资料后按固定命令执行复用/新建、设钥/验钥和提审;上下文、平台字段、`appSign`、MCP 参数和返回由 `app.sh` 托管,仅异常诊断或业务解释时读取 `modules/app-release.md`。
389
392
 
390
393
  候选正文只由 Step 4 的同快照 runner 输出:存在 `ON_LINE` 时只接受当前完整 `appId` 或 `新建`;仅非上线候选时只接受 `新建|暂不新建`;禁止序号、历史/自报 ID。`新建` 已构成创建确认,候选复查不变且资料齐备时不追加确认。按量付费/网站支付新建时说明应用公钥要求;APP 支付首次资料只执行下列固定消息,一次收集 `IOS` 的 `bundleId`、`ANDROID` 的 `appPackage+appSign` 或 `ALL` 的三项组合,补问只用消息中的中文字段名:
391
394
 
@@ -512,13 +515,13 @@ renderer 还会按 `productName` 和分支完成条件自动派生上线前检
512
515
 
513
516
  ---
514
517
 
515
- ## 四、错误处理(全局规则)
518
+ ## 错误处理(全局规则)
516
519
 
517
520
  所有 CLI/MCP 操作立即使用脚本内置错误检测,失败后禁止解析业务结果;分类与恢复以 `modules/scripts/error_handler.sh` 和按需读取的 `modules/error-handling.md` 为准。Step 3.1 查询失败仍按分支隔离。
518
521
 
519
522
  ---
520
523
 
521
- ## 五、当前会话事实
524
+ ## 当前会话事实
522
525
 
523
526
  只保留当前已确认产品/MCC、已验证材料、候选和脚本结果;重入时重新校验授权并查询全部适用分支。可验证的提前材料直接复用,否则重收。
524
527
 
@@ -6,24 +6,25 @@
6
6
 
7
7
  | 内部分类 | 已验证触发 | 恢复边界 |
8
8
  | --- | --- | --- |
9
- | `MCP_AUTH_ERROR` | HTTP 401、`Authorization is empty`、非法认证信息、`用户未登录`、`not_logged_in` 等明确登录失效证据;已认证资源动作顶层或 `data.logged_in=false` | 停止查询组;logout 后置条件成立后重授权。仍登录则阻断;不可确认或无网络时只重试同一动作,不解释为用户本机或支付宝业务失败 |
9
+ | `MCP_AUTH_ERROR` | 非 JSON 传输错误中的 HTTP 401/认证强证据;JSON 顶层或登记 error/response 层中的数字/字符串 `401`、`unauthorized`、`Authorization is empty`、非法认证信息、`用户未登录`、`not_logged_in` 等明确登录失效证据;已认证资源动作顶层或 `data.logged_in=false` | 停止查询组;logout 后置条件成立后重授权。仍登录则阻断;不可确认或无网络时只重试同一动作,不解释为用户本机或支付宝业务失败。成功候选、说明或请求示例中的相同文本不构成认证错误 |
10
10
  | `AUTH_MISMATCH` | `mccCode is not auth`、`salesProductCodes is not auth`、`scope is not auth` | 停止当前动作,只进 `auth.sh mismatch`;logout 后置条件未成立前禁重授权 |
11
11
  | `MCP_SERVICE_ERROR` / `SERVICE_UNSTABLE` | 已识别连接、网络或服务异常 | 按下方有限重试规则处理;耗尽后记录受影响分支,依赖允许时继续其他独立分支,最终统一收口 |
12
12
  | `DISCOVERY_FLOW:RETRY_WITH_NETWORK` | discovery 子查询重试耗尽,且原始输出包含 DNS、连接、timeout、socket/TLS、`fetch failed`、`network_error`、`网络连接失败` 或连接前沙箱禁网强证据 | 只在无授权阻断时输出一次;暂存首次 stdout,申请联网权限后原样重试同一 discovery 命令一次。拒绝、不支持或再次命中时停止申请,只送达最新一次既有 stdout |
13
- | `CLI_LOCAL_FS_PERMISSION` | 授权状态命令 preflight 失败;或原始 stdout+stderr 命中私有状态写失败强证据,或私有状态路径与权限错误组合 | 仅用于 `auth.sh init|wait|confirm|mismatch` 的 `login/login --complete/logout` 链路;可恢复阶段 stdout 为空,只输出唯一 `AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`,取得 CLI 私有状态目录权限后原样重试一次,不改产品、scope、MCC 或授权页 |
13
+ | `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` | discovery 任一子查询在 CLI 调用前探测失败,或只读调用后命中 CLI 私有状态权限证据 | 授权阻断优先;本次不渲染 discovery 摘要,取得两个 CLI 私有状态目录权限后原样重试同一 discovery 命令一次,不改产品、salesCode 或签约复用回执 |
14
+ | `CLI_LOCAL_FS_PERMISSION` | 受控 CLI 动作 preflight 失败;或原始 stdout+stderr 命中私有状态写失败强证据,或私有状态路径与权限错误组合 | auth 使用唯一 `AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`;其他未发送或只读动作使用唯一 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`。取得目录权限后原样重试一次;写动作已执行则保持 `MAYBE_SENT` 并只核验/标记 `UNKNOWN`,不得重放 |
14
15
  | `APP_LOCAL_CONFIG_PERMISSION` | 已取 RSA2 支付宝公钥但 config 探针失败 | stdout 空;输出两个本地权限 marker,取权后原样重试当前 app 命令一次,不重开公钥页 |
15
16
  | `OPEN_ERROR_KIND` | URL 已交付但 opener 失败 | 保留 URL 兜底;诊断不对客,不改写为业务、网络或授权失败 |
16
17
  | `ERROR:<code>` | 当前响应中存在可识别业务错误 | 展示登记且脱敏的业务错误,修正业务条件前不重试 |
17
18
  | `CLI_ERROR:<message>` | CLI 失败、异常结构或结果不可唯一解析 | 隐藏原始输出,停止并按登记 CLI 错误恢复,不猜成功或空结果 |
18
19
  | `SUCCESS` | 唯一合法成功结构 | 才允许进入当前脚本的业务字段解析 |
19
20
 
20
- 发现阶段的单分支失败不得伪装为空列表,也不得回滚其他已成功分支;认证失效或授权不匹配属于查询组全局阻断,重新授权后恢复尚未执行的查询,主体一致性无法确认时重新查询全部适用状态。`logged_in=false` 只在已认证的签约、服务、应用和上传等资源动作中属于认证失效;`auth.sh whoami` 的成功未登录结果仍是正常登录入口,失败响应即使携带该字段也不能证明已退出。
21
+ 发现阶段的单分支失败不得伪装为空列表,也不得回滚其他已成功分支;认证失效或授权不匹配属于查询组全局阻断,重新授权后恢复尚未执行的查询,主体一致性无法确认时重新查询全部适用状态。`logged_in=false` 只在已认证的签约、服务、应用和上传等资源动作中属于认证失效;`auth.sh whoami` 的成功未登录结果仍是正常登录入口,失败响应即使携带该字段也不能证明已退出。discovery 的恢复优先级固定为真实认证/授权阻断、本地 CLI 状态权限、网络权限、普通分支失败;`AUTH_FLOW:RETRY_WITH_NETWORK|RETRY_WITH_LOCAL_FS_PERMISSION|LOGOUT_STILL_LOGGED_IN` 不是 discovery 子查询的认证失效证据,不得映射为 `AUTHORIZATION_INVALID`。
21
22
 
22
23
  discovery runner 和截图上传这类并发/分组动作不得让每个子进程各自 logout。子脚本在分组环境下只上报认证失效事实;由 flow 登记的唯一授权恢复入口执行一次 logout 后置条件确认。三张截图同时认证失败时只允许产生一个全局认证恢复 marker。
23
24
 
24
25
  ## 解包与业务错误
25
26
 
26
- `unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text` 信封和混合日志。授权状态命令捕获先合并 stdout/stderr 检查 `写入临时文件失败|删除凭据文件失败|can't rename log file` 强证据,或 `poll_state.enc.tmp|session.enc|.alipay-cli/credentials|alipay-cli/logs` 与 `permission denied|operation not permitted|EACCES|EPERM` 组合;命中时不得按 `network_error` 处理,裸后端 `permission denied` 不算本地权限。其他 CLI 捕获再执行唯一性解析:单个 JSON 加普通日志可接受,相同 JSON 重复可去重,多个不同 JSON 或成功/失败冲突不得选择任一路,也不得回显原始响应。每次 MCP 调用先把唯一原始结果交给 `handle_error`,返回成功后才调用 `unwrap_mcp` 和当前脚本的业务解析;不得统一假定成功字段位于 `.success`。
27
+ `unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text` 信封和混合日志。所有受控 CLI 捕获先合并 stdout/stderr 检查 `写入临时文件失败|删除凭据文件失败|can't rename log file` 强证据,或 `poll_state.enc.tmp|session.enc|.alipay-cli/credentials|alipay-cli/logs` 与 `permission denied|operation not permitted|EACCES|EPERM` 组合;命中时不得按成功 JSON 或 `network_error` 处理,裸后端 `permission denied` 不算本地权限。其他 CLI 捕获再执行唯一性解析:单个 JSON 加普通日志可接受,相同 JSON 重复可去重,多个不同 JSON 或成功/失败冲突不得选择任一路,也不得回显原始响应。每次 MCP 调用先把唯一原始结果交给 `handle_error`,返回成功后才调用 `unwrap_mcp` 和当前脚本的业务解析;不得统一假定成功字段位于 `.success`。认证分类只扫描真实响应错误层,禁止递归扫描成功候选、业务说明或请求示例中的错误样例。
27
28
 
28
29
  每次 logout 失败后只执行一次 `whoami` 后置核验;logout 明确成功时无需额外核验,最多尝试 3 次 logout。`whoami` 后置核验必须先满足唯一 JSON、退出码为 0 且 `detect_error=SUCCESS`,再读取类型正确的 `logged_in/is_expired`;业务失败响应即使携带 `logged_in=false` 也不能证明已退出。logout 网络/服务异常、冲突输出或结果无法确认时,即使 `whoami` 仍显示已登录,也只能转 `AUTH_FLOW:RETRY_WITH_NETWORK` 并重试同一动作。logout 是可由 `whoami` 验证最终状态的幂等恢复动作:明确业务失败且仍登录时,脚本内部使用“初次 + 2 次”固定预算、间隔 3 秒重试完全相同的 logout;任一次确认已退出即继续,全部耗尽且仍登录才转 `AUTH_FLOW:LOGOUT_STILL_LOGGED_IN`。所有失败分支都不得执行 login。每个 auth 动作只允许产生一个最终 `AUTH_FLOW:*`,具体恢复 marker 已由错误处理器输出时,调用方不得追加 `AUTH_FLOW:FAILED`。
29
30