@alipay/alipay-aipay 1.5.2 → 1.5.4

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 (24) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/skill/VERSION +1 -1
  4. package/skill/references/integration/flow.md +2 -2
  5. package/skill/references/normal/json/customer-messages.json +2 -2
  6. package/skill/references/normal/json/policy-rules.json +6 -0
  7. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +137 -20
  8. package/skill/references/normal/scripts/onboarding_message_runner.mjs +144 -52
  9. package/skill/references/normal/scripts/render_customer_message.mjs +36 -3
  10. package/skill/references/normal/scripts/runtime.mjs +69 -0
  11. package/skill/references/normal/scripts/telemetry.mjs +11 -0
  12. package/skill/references/onboarding/flow.md +36 -22
  13. package/skill/references/onboarding/modules/app-release.md +3 -1
  14. package/skill/references/onboarding/modules/authorization.md +3 -2
  15. package/skill/references/onboarding/modules/error-handling.md +10 -4
  16. package/skill/references/onboarding/modules/product-sign.md +5 -1
  17. package/skill/references/onboarding/modules/scripts/app.sh +27 -10
  18. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +6 -0
  19. package/skill/references/onboarding/modules/scripts/auth.sh +118 -61
  20. package/skill/references/onboarding/modules/scripts/error_handler.sh +147 -22
  21. package/skill/references/onboarding/modules/scripts/service.sh +9 -0
  22. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +10 -1
  23. package/skill/references/onboarding/modules/service-registration.md +1 -1
  24. package/skill/references/runtime-entry.md +5 -5
@@ -221,6 +221,7 @@ if [ "$RETRY_RC" -eq 75 ]; then
221
221
  RECONCILE_OUTPUT=$(bash "${SCRIPT_DIR}/query_sign_status.sh" --sales-code "$SALES_CODE" --product-type "$PRODUCT_TYPE" 2>&1 || true)
222
222
  RECONCILED_STATUS=$(printf '%s\n' "$RECONCILE_OUTPUT" | sed -n 's/^ALIPAY_AIPAY_INTERNAL:SIGN_STATUS=//p' | tail -1)
223
223
  if [ "$RECONCILED_STATUS" = "SIGN_SUBMITTED" ] || [ "$RECONCILED_STATUS" = "SIGNED_EFFECTIVE" ]; then
224
+ emit_control "SIGN_APPLY_RESULT=SUCCESS"
224
225
  case "$PRODUCT_TYPE" in
225
226
  aipay) emit_control "FLOW:AI_PAY_SIGN_CONTINUE" ;;
226
227
  webpay) emit_control "FLOW:PC_WEB_SIGN_CONTINUE" ;;
@@ -229,14 +230,17 @@ if [ "$RETRY_RC" -eq 75 ]; then
229
230
  exit 0
230
231
  fi
231
232
  echo "❌ 签约提交响应不明且现有查询无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提交"
233
+ emit_control "SIGN_APPLY_RESULT=UNKNOWN"
232
234
  exit 1
233
235
  elif [ "$RETRY_RC" -ne 0 ]; then
234
236
  echo "❌ 签约提交因明确未发送的网络异常在自动重试两次后仍未恢复"
237
+ emit_control "SIGN_APPLY_RESULT=FAILED"
235
238
  exit 1
236
239
  fi
237
240
 
238
241
  # 错误检测(先于业务解析,handle_error 内部会自动解包信封)
239
242
  if ! handle_error "$RESULT"; then
243
+ emit_control "SIGN_APPLY_RESULT=FAILED"
240
244
  exit 1
241
245
  fi
242
246
 
@@ -247,6 +251,7 @@ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
247
251
  if [ "$SUCCESS" = "true" ]; then
248
252
  emit_control "✅ 签约提交成功"
249
253
  emit_control "SIGN_APPLY_STATUS=SUBMITTED"
254
+ emit_control "SIGN_APPLY_RESULT=SUCCESS"
250
255
  # 签约提交成功后无需等待审核,立即推进后续步骤
251
256
  # AI 按量付费: 继续 5.2 服务注册 → 5.3 应用发布
252
257
  # AI 网页应用收款/AI 移动应用收款: 继续 5.3 应用发布
@@ -263,5 +268,6 @@ else
263
268
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
264
269
  ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
265
270
  echo "❌ 签约提交失败: $ERROR_MSG"
271
+ emit_control "SIGN_APPLY_RESULT=FAILED"
266
272
  exit 1
267
273
  fi
@@ -14,7 +14,7 @@
14
14
  # init: AUTH_FLOW:SKIP | AUTH_FLOW:READY_TO_OPEN | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
15
15
  # wait: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
16
16
  # confirm: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
17
- # mismatch: logout 可确认成功后调用 auth init 重新生成授权链接;上下文冲突时输出 AUTH_FLOW:CONTEXT_MISMATCH 且不 logout;无法确认 CLI 结果时输出 AUTH_FLOW:RETRY_WITH_NETWORK
17
+ # mismatch: logout 后置条件可确认后调用 auth init 重新生成授权链接;上下文冲突时输出 AUTH_FLOW:CONTEXT_MISMATCH 且不 logout;无法确认 CLI 结果时输出 AUTH_FLOW:RETRY_WITH_NETWORK;仍处于登录态时输出 AUTH_FLOW:LOGOUT_STILL_LOGGED_IN
18
18
  # stdout: 仅登记对客正文;AUTH_FLOW/状态/查询复用标记写入 stderr
19
19
  #=============================================================================
20
20
 
@@ -37,6 +37,28 @@ require_command node || exit 1
37
37
  require_command mktemp || exit 1
38
38
  umask 077
39
39
 
40
+ auth_product_type_from_sales_code() {
41
+ case "$1" in
42
+ "I1080300001000160457") printf 'aipay' ;;
43
+ "I1080300001000041203") printf 'webpay' ;;
44
+ "I1080300001000041313") printf 'apppay' ;;
45
+ *) return 1 ;;
46
+ esac
47
+ }
48
+
49
+ create_auth_sign_reuse_receipt() {
50
+ local sign_status="$1" sign_flow="$2" product_type payload encoded
51
+ product_type=$(auth_product_type_from_sales_code "$SALES_CODE") || return 1
52
+ payload=$(jq -cn \
53
+ --arg productType "$product_type" \
54
+ --arg salesCode "$SALES_CODE" \
55
+ --arg signStatus "$sign_status" \
56
+ --arg flow "$sign_flow" \
57
+ '{schema_ver:1,product_type:$productType,sales_code:$salesCode,sign_status:$signStatus,flow:$flow}') || return 1
58
+ encoded=$(printf '%s' "$payload" | jq -rR '@base64' | tr '+/' '-_' | tr -d '=\n') || return 1
59
+ printf 'AIPAY_SIGN_REUSE_V1.%s' "$encoded"
60
+ }
61
+
40
62
  # ─── 授权状态文件(用于独立执行 confirm/mismatch 时兜底恢复非敏感参数)────
41
63
  AUTH_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
42
64
  AUTH_STATE_BASE="${TMPDIR:-/tmp}"
@@ -159,6 +181,14 @@ save_auth_state() {
159
181
  }
160
182
  }
161
183
 
184
+ save_auth_state_after_page_delivery() {
185
+ if save_auth_state; then
186
+ return 0
187
+ fi
188
+ emit_control "AUTH_STATE:SAVE_FAILED"
189
+ return 0
190
+ }
191
+
162
192
  load_auth_state() {
163
193
  if ! validate_auth_state_file; then
164
194
  echo "❌ 缺少授权上下文(请传入 --sales-code/--mcc-code,或先执行 auth.sh init)"
@@ -315,6 +345,26 @@ auth_cli_environment_failure() {
315
345
  emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
316
346
  }
317
347
 
348
+ handle_auth_cli_result() {
349
+ local cli_result="$1" error_type
350
+ error_type=$(detect_error "$cli_result")
351
+ if handle_error "$cli_result"; then
352
+ return 0
353
+ fi
354
+ case "$error_type" in
355
+ MCP_AUTH_ERROR|AUTH_MISMATCH)
356
+ # error_handler.sh already emitted the unique recovery marker.
357
+ ;;
358
+ MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
359
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
360
+ ;;
361
+ *)
362
+ emit_control "AUTH_FLOW:FAILED"
363
+ ;;
364
+ esac
365
+ return 1
366
+ }
367
+
318
368
  wait_for_effective_whoami() {
319
369
  local action="$1"
320
370
  local max_attempts=3
@@ -332,10 +382,7 @@ wait_for_effective_whoami() {
332
382
  auth_cli_environment_failure "$action"
333
383
  return 1
334
384
  }
335
- if ! handle_error "$AUTH_WHOAMI_RESULT"; then
336
- emit_control "AUTH_FLOW:FAILED"
337
- return 1
338
- fi
385
+ handle_auth_cli_result "$AUTH_WHOAMI_RESULT" || return 1
339
386
 
340
387
  logged_in=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.logged_in // false')
341
388
  is_expired=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.is_expired // false')
@@ -364,18 +411,6 @@ run_json_command() {
364
411
  exit_code=$?
365
412
  stderr_output=$(cat "$stderr_file")
366
413
 
367
- if json_payload=$(extract_json_payload "$stdout"); then
368
- rm -f "$stderr_file"
369
- printf '%s' "$json_payload"
370
- return 0
371
- fi
372
-
373
- if json_payload=$(extract_json_payload "$stderr_output"); then
374
- rm -f "$stderr_file"
375
- printf '%s' "$json_payload"
376
- return 0
377
- fi
378
-
379
414
  combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
380
415
  if json_payload=$(extract_json_payload "$combined_output"); then
381
416
  rm -f "$stderr_file"
@@ -395,7 +430,7 @@ validate_current_authorization() {
395
430
  local logged_in is_expired granted_scope required_scope
396
431
  local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match mcc_raw retry_rc
397
432
  local mcc_error_type mcc_error_code mcc_success
398
- local mismatch_type sign_status_output
433
+ local mismatch_type sign_status_output sign_status_marker sign_flow_marker sign_reuse_receipt
399
434
  local granted_scope_item required_scope_item required_scope_found
400
435
  local -a granted_scope_items required_scope_items
401
436
 
@@ -412,10 +447,7 @@ validate_current_authorization() {
412
447
  }
413
448
  fi
414
449
 
415
- if ! handle_error "$whoami_result"; then
416
- emit_control "AUTH_FLOW:FAILED"
417
- return 1
418
- fi
450
+ handle_auth_cli_result "$whoami_result" || return 1
419
451
 
420
452
  logged_in=$(echo "$whoami_result" | jq -r '.data.logged_in // false')
421
453
  is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
@@ -461,12 +493,12 @@ validate_current_authorization() {
461
493
  retry_rc=$?
462
494
  if [ "$retry_rc" -ne 0 ]; then
463
495
  echo "❌ 授权 MCC 校验因网络或服务异常在自动重试两次后仍未恢复"
464
- emit_control "AUTH_FLOW:FAILED"
496
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
465
497
  return 1
466
498
  fi
467
499
  mcc_verify_result=$(extract_json_payload "$mcc_raw") || {
468
500
  echo "❌ alipay-cli mcp call ar-query.queryArInfosBySalesProd 未返回合法 JSON"
469
- emit_control "AUTH_FLOW:FAILED"
501
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
470
502
  return 1
471
503
  }
472
504
 
@@ -494,6 +526,11 @@ validate_current_authorization() {
494
526
  if [ "$mcc_match" = "true" ] && sign_status_output=$(emit_sign_status_from_business "$mcc_verify_unwrapped" "$SALES_CODE" 2>/dev/null); then
495
527
  printf '%s\n' "$sign_status_output" \
496
528
  | while IFS= read -r marker; do emit_control "$marker"; done
529
+ sign_status_marker=$(printf '%s\n' "$sign_status_output" | awk -F= '/^SIGN_STATUS=/{print $2; exit}')
530
+ sign_flow_marker=$(printf '%s\n' "$sign_status_output" | awk '/^FLOW:/{print; exit}')
531
+ if [ -n "$sign_status_marker" ] && [ -n "$sign_flow_marker" ] && sign_reuse_receipt=$(create_auth_sign_reuse_receipt "$sign_status_marker" "$sign_flow_marker"); then
532
+ emit_control "AUTH_SIGN_REUSE_RECEIPT=${sign_reuse_receipt}"
533
+ fi
497
534
  emit_control "AUTH_SIGN_QUERY_REUSABLE=true"
498
535
  fi
499
536
  fi
@@ -564,10 +601,7 @@ auth_init() {
564
601
  auth_cli_environment_failure "登录状态检查"
565
602
  return 1
566
603
  }
567
- if ! handle_error "$CHECK_RESULT"; then
568
- emit_control "AUTH_FLOW:FAILED"
569
- return 1
570
- fi
604
+ handle_auth_cli_result "$CHECK_RESULT" || return 1
571
605
  LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
572
606
  IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
573
607
 
@@ -576,13 +610,17 @@ auth_init() {
576
610
  # 后端查询没有权威 MCC 字段,旧状态变化时不能把现有登录误判为当前上下文已授权。
577
611
  DEVICE_CODE=""
578
612
  BROWSER_URL=""
579
- save_auth_state || return 1
613
+ if ! save_auth_state; then
614
+ emit_control "AUTH_STATE:SAVE_FAILED"
615
+ fi
580
616
  emit_control "AUTH_FLOW:${AUTH_INIT_CONTEXT_MISMATCH_TYPE}"
581
617
  return 1
582
618
  fi
583
619
  validate_current_authorization "$CHECK_RESULT" || return 1
584
620
  DEVICE_CODE=""
585
- save_auth_state || return 1
621
+ if ! save_auth_state; then
622
+ emit_control "AUTH_STATE:SAVE_FAILED"
623
+ fi
586
624
  emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
587
625
  emit_control "AUTH_FLOW:SKIP"
588
626
  return 0
@@ -593,16 +631,15 @@ auth_init() {
593
631
  auth_cli_environment_failure "授权登录发起"
594
632
  return 1
595
633
  }
596
- if ! handle_error "$LOGIN_RESULT"; then
597
- emit_control "AUTH_FLOW:FAILED"
598
- return 1
599
- fi
634
+ handle_auth_cli_result "$LOGIN_RESULT" || return 1
600
635
  LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
601
636
 
602
637
  if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
603
638
  validate_current_authorization || return 1
604
639
  DEVICE_CODE=""
605
- save_auth_state || return 1
640
+ if ! save_auth_state; then
641
+ emit_control "AUTH_STATE:SAVE_FAILED"
642
+ fi
606
643
  emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
607
644
  emit_control "AUTH_FLOW:SKIP"
608
645
  return 0
@@ -630,10 +667,7 @@ auth_init() {
630
667
  return 1
631
668
  }
632
669
 
633
- # Step 6: 保存参数到状态文件(供 confirm/mismatch 使用)
634
- save_auth_state || return 1
635
-
636
- # Step 7: 有效期转换(对非数字做防护)
670
+ # Step 6: 有效期转换(对非数字做防护)
637
671
  if [[ "$EXPIRES_IN" =~ ^[0-9]+$ ]] && [ "$EXPIRES_IN" -ge 60 ]; then
638
672
  EXPIRES_MIN=$((EXPIRES_IN / 60))
639
673
  EXPIRES_DISPLAY="${EXPIRES_MIN} 分钟"
@@ -643,7 +677,7 @@ auth_init() {
643
677
  EXPIRES_DISPLAY="10 分钟"
644
678
  fi
645
679
 
646
- # Step 8: 由统一目录渲染授权信息;独立 open 动作在消息发出后打开。
680
+ # Step 7: 由统一目录渲染授权信息;独立 open 动作在消息发出后打开。
647
681
  DISPLAY_CODE="${VERIFICATION_CODE:-(请查看支付宝授权页面)}"
648
682
  MESSAGE_INPUT=$(printf '%s\n' "$PRODUCT_NAME" "$DEVICE_CODE" "$MCC_CODE" "$MCC_NAME" "$DISPLAY_CODE" "$EXPIRES_DISPLAY" "$BROWSER_URL" | jq -Rs '
649
683
  split("\n") as $v | {productName:$v[0],deviceCode:$v[1],mccCode:$v[2],mccName:$v[3],verificationCode:$v[4],expiresDisplay:$v[5],officialUrl:$v[6]}
@@ -653,6 +687,8 @@ auth_init() {
653
687
  emit_control "AUTH_FLOW:FAILED"
654
688
  exit 1
655
689
  fi
690
+ # Step 8: 页面 stdout 已经安全送达时,状态文件只用于 opener 便利;保存失败不得生成第二个授权页或截断 wait。
691
+ save_auth_state_after_page_delivery
656
692
  echo ""
657
693
  emit_control "AUTH_FLOW:READY_TO_OPEN"
658
694
  }
@@ -661,12 +697,11 @@ auth_open() {
661
697
  local saved_device saved_url validate_input open_status open_helper
662
698
 
663
699
  restore_auth_context_or_state "$@" || exit 1
664
- saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE")
665
- saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE")
700
+ saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
701
+ saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
666
702
  if [ -z "$saved_device" ] || [ -z "$saved_url" ]; then
667
- echo "❌ 缺少待打开的授权页面上下文,请重新执行 auth.sh init --defer-open"
668
- emit_control "AUTH_FLOW:FAILED"
669
- return 1
703
+ emit_control "AUTH_OPEN:LINK_ONLY"
704
+ return 0
670
705
  fi
671
706
 
672
707
  validate_input=$(jq -n \
@@ -740,12 +775,31 @@ auth_confirm() {
740
775
  elif [ "$OUTER_SUCCESS" = "true" ] || [ "$DATA_STATUS" = "logged_in" ] || [ "$DATA_STATUS" = "already_logged_in" ] || [ "$DATA_STATUS" = "completed" ]; then
741
776
  :
742
777
  else
743
- if handle_error "$LOGIN_COMPLETE_RESULT"; then
744
- echo "❌ 授权确认失败:返回结果未包含可识别的完成状态"
778
+ if ! handle_auth_cli_result "$LOGIN_COMPLETE_RESULT"; then
779
+ cleanup_auth_state
780
+ return 1
745
781
  fi
782
+ wait_for_effective_whoami "授权完成确认状态复核"
783
+ whoami_rc=$?
784
+ if [ "$whoami_rc" -eq 2 ]; then
785
+ AUTH_CONFIRM_RESULT="PENDING"
786
+ if [ "${AUTH_POLLING:-false}" != "true" ]; then
787
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
788
+ emit_control "AUTH_FLOW:FAILED"
789
+ return 1
790
+ fi
791
+ emit_control "AUTH_FLOW:PENDING"
792
+ fi
793
+ return 0
794
+ elif [ "$whoami_rc" -ne 0 ]; then
795
+ return 1
796
+ fi
797
+ validate_current_authorization "$AUTH_WHOAMI_RESULT" || return 1
746
798
  cleanup_auth_state
747
- emit_control "AUTH_FLOW:FAILED"
748
- return 1
799
+ AUTH_CONFIRM_RESULT="AUTH_SUCCESS"
800
+ emit_control "✅ 授权确认成功(本机 CLI 登录态、scope 与 MCC 授权有效性均已通过)"
801
+ emit_control "AUTH_FLOW:AUTH_SUCCESS"
802
+ return 0
749
803
  fi
750
804
 
751
805
  # Step 3: 复用 init 的 scope + MCC 授权有效性校验
@@ -817,8 +871,6 @@ auth_wait() {
817
871
 
818
872
  # ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
819
873
  auth_mismatch() {
820
- local logout_result logout_error_type
821
-
822
874
  if [ "${1:-}" = "--defer-open" ]; then
823
875
  shift
824
876
  else
@@ -847,16 +899,21 @@ auth_mismatch() {
847
899
  emit_control "AUTH_FLOW:FAILED"
848
900
  return 1
849
901
  fi
850
- logout_result=$(run_json_command "alipay-cli logout --json" alipay-cli logout --json) || {
851
- auth_cli_environment_failure "退出当前登录"
852
- return 1
853
- }
854
- logout_error_type=$(detect_error "$logout_result")
855
- if [ "$logout_error_type" != "SUCCESS" ]; then
856
- echo "❌ 退出当前登录失败,已停止重新授权"
857
- emit_control "AUTH_FLOW:FAILED"
858
- return 1
859
- fi
902
+ confirm_logout_postcondition
903
+ case "$ALIPAY_LOGOUT_POSTCONDITION" in
904
+ COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
905
+ ;;
906
+ STILL_LOGGED_IN)
907
+ render_logout_still_logged_in || return 1
908
+ emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
909
+ return 1
910
+ ;;
911
+ *)
912
+ render_logout_unconfirmed || return 1
913
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
914
+ return 1
915
+ ;;
916
+ esac
860
917
 
861
918
  # Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出唯一授权消息。
862
919
  # DEV_TOOL_NAME 已 export,子进程可继承。
@@ -348,7 +348,7 @@ detect_error() {
348
348
  fi
349
349
 
350
350
  # 3. MCP 调用失败(网络/连接错误)
351
- if echo "$UNWRAPPED" | grep -qiE "MCP 调用失败|connection refused|timeout|network error"; then
351
+ if echo "$UNWRAPPED" | grep -qiE "MCP 调用失败|connection refused|timeout|network[ _]error|网络连接失败"; then
352
352
  echo "MCP_SERVICE_ERROR"
353
353
  return
354
354
  fi
@@ -383,31 +383,156 @@ detect_error() {
383
383
  echo "SUCCESS"
384
384
  }
385
385
 
386
- # ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
387
- handle_mcp_auth_error() {
388
- local LOGOUT_RESULT LOGOUT_EXIT_CODE LOGOUT_ERROR_TYPE LOGOUT_ERROR_MSG
389
- local LOGOUT_ANALYSIS LOGOUT_PARSE_STATUS
390
-
391
- emit_control "AUTH_FLOW:AUTH_REQUIRED"
392
- render_error_message error.mcp.auth START '{}' || return 1
393
- LOGOUT_RESULT=$(PLATFORM=${DEV_TOOL_NAME:-unknown} PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli logout --json 2>&1)
394
- LOGOUT_EXIT_CODE=$?
395
- LOGOUT_ERROR_TYPE=$(detect_error "$LOGOUT_RESULT")
396
- LOGOUT_ANALYSIS=$(extract_unique_json "$LOGOUT_RESULT" 2>/dev/null || echo '{"status":"none"}')
397
- LOGOUT_PARSE_STATUS=$(echo "$LOGOUT_ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
398
-
399
- if [ "$LOGOUT_EXIT_CODE" -ne 0 ] || [ "$LOGOUT_ERROR_TYPE" != "SUCCESS" ]; then
400
- if [ "$LOGOUT_PARSE_STATUS" != "ok" ] || [ "$LOGOUT_ERROR_TYPE" = "MCP_SERVICE_ERROR" ] || [ "$LOGOUT_ERROR_TYPE" = "SERVICE_UNSTABLE" ]; then
401
- render_error_message error.mcp.auth LOGOUT_UNCONFIRMED '{}' || return 1
386
+ extract_captured_json() {
387
+ local RAW="$1"
388
+ local ANALYSIS STATUS
389
+ ANALYSIS=$(extract_unique_json "$RAW" 2>/dev/null || echo '{"status":"none"}')
390
+ STATUS=$(echo "$ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
391
+ [ "$STATUS" = "ok" ] || return 1
392
+ echo "$ANALYSIS" | jq -ce '.value | select(type == "object")' 2>/dev/null
393
+ }
394
+
395
+ run_alipay_cli_json_capture() {
396
+ local stderr_file stdout stderr_output combined_output
397
+
398
+ ALIPAY_CLI_JSON_CAPTURE=""
399
+ ALIPAY_CLI_JSON_EXIT_CODE=0
400
+ stderr_file="$(mktemp "${TMPDIR:-/tmp}/alipay_cli_capture_stderr.XXXXXX")" || return 1
401
+ stdout=$(PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" "$@" 2>"$stderr_file")
402
+ ALIPAY_CLI_JSON_EXIT_CODE=$?
403
+ stderr_output=$(cat "$stderr_file")
404
+ rm -f "$stderr_file"
405
+
406
+ combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
407
+ if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$combined_output"); then
408
+ return 0
409
+ fi
410
+
411
+ ALIPAY_CLI_JSON_CAPTURE=""
412
+ return 1
413
+ }
414
+
415
+ confirm_logout_postcondition() {
416
+ local logout_error_type whoami_error_type whoami_state
417
+ local logout_result_state attempt=1
418
+ local max_attempts=3
419
+ local interval_seconds=3
420
+
421
+ ALIPAY_LOGOUT_POSTCONDITION=""
422
+ ALIPAY_LOGOUT_ERROR_MESSAGE=""
423
+
424
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ]; then
425
+ interval_seconds=0
426
+ fi
427
+
428
+ while [ "$attempt" -le "$max_attempts" ]; do
429
+ logout_result_state="UNCONFIRMED"
430
+ if run_alipay_cli_json_capture alipay-cli logout --json; then
431
+ logout_error_type=$(detect_error "$ALIPAY_CLI_JSON_CAPTURE")
432
+ if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -eq 0 ] && [ "$logout_error_type" = "SUCCESS" ]; then
433
+ ALIPAY_LOGOUT_POSTCONDITION="COMMAND_CONFIRMED_SUCCESS"
434
+ return 0
435
+ fi
436
+ ALIPAY_LOGOUT_ERROR_MESSAGE=$(sanitize_customer_error_text "$(extract_error_message "$ALIPAY_CLI_JSON_CAPTURE")")
437
+ case "$logout_error_type" in
438
+ MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
439
+ logout_result_state="RETRYABLE"
440
+ ;;
441
+ SUCCESS)
442
+ # A success payload paired with a non-zero exit code cannot prove the command result.
443
+ logout_result_state="UNCONFIRMED"
444
+ ;;
445
+ *)
446
+ logout_result_state="EXPLICIT_FAILURE"
447
+ ;;
448
+ esac
402
449
  else
403
- LOGOUT_ERROR_MSG=$(sanitize_customer_error_text "$(extract_error_message "$LOGOUT_RESULT")")
404
- MESSAGE_INPUT=$(jq -cn --arg errorMessage "$LOGOUT_ERROR_MSG" '{errorMessage:$errorMessage}')
405
- render_error_message error.mcp.auth LOGOUT_FAILED "$MESSAGE_INPUT" || return 1
450
+ ALIPAY_LOGOUT_ERROR_MESSAGE="退出命令未返回可唯一解析结果"
451
+ fi
452
+
453
+ if ! run_alipay_cli_json_capture alipay-cli whoami --json; then
454
+ ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
455
+ return 1
456
+ fi
457
+
458
+ whoami_error_type=$(detect_error "$ALIPAY_CLI_JSON_CAPTURE")
459
+ if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -ne 0 ] || [ "$whoami_error_type" != "SUCCESS" ]; then
460
+ ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
461
+ return 1
406
462
  fi
463
+
464
+ whoami_state=$(echo "$ALIPAY_CLI_JSON_CAPTURE" | jq -r '
465
+ if (.data | type) != "object" then "UNCONFIRMED"
466
+ elif (.data.logged_in? | type) == "boolean" and .data.logged_in == false then "LOGGED_OUT"
467
+ elif (.data.is_expired? | type) == "boolean" and .data.is_expired == true then "LOGGED_OUT"
468
+ elif (.data.logged_in? | type) == "boolean" and .data.logged_in == true and
469
+ ((.data | has("is_expired") | not) or ((.data.is_expired? | type) == "boolean" and .data.is_expired == false)) then "STILL_LOGGED_IN"
470
+ else "UNCONFIRMED"
471
+ end
472
+ ' 2>/dev/null)
473
+ if [ "$whoami_state" = "LOGGED_OUT" ]; then
474
+ ALIPAY_LOGOUT_POSTCONDITION="POSTCONDITION_LOGGED_OUT"
475
+ return 0
476
+ fi
477
+ if [ "$whoami_state" != "STILL_LOGGED_IN" ] || [ "$logout_result_state" != "EXPLICIT_FAILURE" ]; then
478
+ ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
479
+ return 1
480
+ fi
481
+
482
+ if [ "$attempt" -eq "$max_attempts" ]; then
483
+ ALIPAY_LOGOUT_POSTCONDITION="STILL_LOGGED_IN"
484
+ return 1
485
+ fi
486
+ sleep "$interval_seconds"
487
+ attempt=$((attempt + 1))
488
+ done
489
+ }
490
+
491
+ render_logout_unconfirmed() {
492
+ render_error_message error.mcp.auth LOGOUT_UNCONFIRMED '{}'
493
+ }
494
+
495
+ render_logout_still_logged_in() {
496
+ local message input
497
+ message="${ALIPAY_LOGOUT_ERROR_MESSAGE:-退出命令未能使当前登录态失效}"
498
+ input=$(jq -cn --arg errorMessage "$message" '{errorMessage:$errorMessage}')
499
+ render_error_message error.mcp.auth LOGOUT_FAILED "$input"
500
+ }
501
+
502
+ # ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
503
+ handle_mcp_auth_error() {
504
+ render_error_message error.mcp.auth START '{}' || {
505
+ emit_control "AUTH_FLOW:FAILED"
506
+ return 1
507
+ }
508
+ if [ "${ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT:-}" = "1" ]; then
509
+ emit_control "AUTH_FLOW:AUTH_REQUIRED"
407
510
  return 1
408
511
  fi
409
-
410
- render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || return 1
512
+ confirm_logout_postcondition
513
+ case "$ALIPAY_LOGOUT_POSTCONDITION" in
514
+ COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
515
+ render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || {
516
+ emit_control "AUTH_FLOW:FAILED"
517
+ return 1
518
+ }
519
+ emit_control "AUTH_FLOW:AUTH_REQUIRED"
520
+ ;;
521
+ STILL_LOGGED_IN)
522
+ render_logout_still_logged_in || {
523
+ emit_control "AUTH_FLOW:FAILED"
524
+ return 1
525
+ }
526
+ emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
527
+ ;;
528
+ *)
529
+ render_logout_unconfirmed || {
530
+ emit_control "AUTH_FLOW:FAILED"
531
+ return 1
532
+ }
533
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
534
+ ;;
535
+ esac
411
536
  }
412
537
 
413
538
  # ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
@@ -351,6 +351,7 @@ service_save() {
351
351
  if [ "$RETRY_RC" -eq 75 ]; then
352
352
  if [ -z "$SERVICE_ID" ]; then
353
353
  echo "❌ 服务创建响应不明;现有查询无法把同名候选唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
354
+ emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
354
355
  exit 1
355
356
  fi
356
357
  if fetch_service_list; then
@@ -365,11 +366,13 @@ service_save() {
365
366
  if [ "$MATCH_COUNT" -eq 1 ]; then
366
367
  emit_control "✅ 服务修改已由列表查询核验成功"
367
368
  emit_control "📋 服务ID: $SERVICE_ID"
369
+ emit_control "SERVICE_SAVE_RESULT=SUCCESS"
368
370
  emit_control_field "SERVICE_ID" "$SERVICE_ID" || return 1
369
371
  return 0
370
372
  fi
371
373
  fi
372
374
  echo "❌ 服务修改响应不明且现有列表无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复修改"
375
+ emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
373
376
  exit 1
374
377
  elif [ "$RETRY_RC" -ne 0 ]; then
375
378
  if [ -z "$SERVICE_ID" ]; then
@@ -377,11 +380,13 @@ service_save() {
377
380
  else
378
381
  echo "❌ 服务修改因明确未发送的网络异常在自动重试两次后仍未恢复"
379
382
  fi
383
+ emit_control "SERVICE_SAVE_RESULT=FAILED"
380
384
  exit 1
381
385
  fi
382
386
 
383
387
  # 错误检测
384
388
  if ! handle_error "$RESULT"; then
389
+ emit_control "SERVICE_SAVE_RESULT=FAILED"
385
390
  exit 1
386
391
  fi
387
392
 
@@ -418,10 +423,12 @@ service_save() {
418
423
 
419
424
  if ! validate_control_value "$NEW_SERVICE_ID"; then
420
425
  echo "❌ 服务创建返回成功,但未解析到 serviceId,禁止继续后续流程"
426
+ emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
421
427
  exit 1
422
428
  fi
423
429
  emit_control "✅ 服务${OPERATION}成功"
424
430
  emit_control "📋 服务ID: $NEW_SERVICE_ID"
431
+ emit_control "SERVICE_SAVE_RESULT=SUCCESS"
425
432
  emit_control_field "SERVICE_ID" "$NEW_SERVICE_ID" || exit 1
426
433
  else
427
434
  OPERATION="创建"
@@ -437,8 +444,10 @@ service_save() {
437
444
  ' 2>/dev/null)
438
445
  ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
439
446
  echo "❌ 服务${OPERATION}失败: ${BUSINESS_CODE} / ${ERROR_MSG}"
447
+ emit_control "SERVICE_SAVE_RESULT=FAILED"
440
448
  else
441
449
  echo "❌ 服务${OPERATION}返回结构异常,无法确认操作结果"
450
+ emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
442
451
  fi
443
452
  exit 1
444
453
  fi
@@ -33,6 +33,7 @@ case "$PRODUCT_TYPE" in
33
33
  *) echo "❌ 截图上传仅支持 webpay|apppay"; exit 1 ;;
34
34
  esac
35
35
  init_alipay_cli_product "$PRODUCT_TYPE" || exit 1
36
+ export ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1
36
37
 
37
38
  HOME_IMG="${1:-}"
38
39
  SHOP_IMG="${2:-}"
@@ -83,7 +84,7 @@ wait
83
84
  # 解析图片引用值(兼容 file upload 返回 fileKey 或 URL 字段)
84
85
  parse_image_ref() {
85
86
  local RAW=$(cat "$1")
86
- if ! handle_error "$RAW" >/dev/null; then
87
+ if [ "$(detect_error "$RAW")" != "SUCCESS" ]; then
87
88
  return 1
88
89
  fi
89
90
  # alipay-cli file upload 可能返回 MCP 信封,先解包
@@ -120,7 +121,15 @@ PAY_REF=$(parse_image_ref "$TMP_PAY")
120
121
  # 校验上传结果(含认证错误检测)
121
122
  check_upload_error() {
122
123
  local RAW=$(cat "$1")
124
+ local ERROR_TYPE
125
+ ERROR_TYPE=$(detect_error "$RAW")
126
+ if [ "$ERROR_TYPE" = "MCP_AUTH_ERROR" ] && [ "${UPLOAD_AUTH_ERROR_SEEN:-false}" = "true" ]; then
127
+ return 1
128
+ fi
123
129
  if ! handle_error "$RAW"; then
130
+ if [ "$ERROR_TYPE" = "MCP_AUTH_ERROR" ]; then
131
+ UPLOAD_AUTH_ERROR_SEEN=true
132
+ fi
124
133
  return 1
125
134
  fi
126
135
  return 0
@@ -29,7 +29,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" service validate --name
29
29
  - 修改:必须传当前候选完整 `serviceId` 和全部五项资料;保存前使用 `onboarding.write.confirm/SERVICE_UPDATE_ONLY` 绑定当前登录会话、脱敏主体、产品、动作、目标与参数,并等待当前摘要后的明确肯定回复;`1` 只是兼容快捷输入。
30
30
  - 任何目标、参数、主体或会话变化都使旧修改确认失效。创建不得进入修改确认。
31
31
 
32
- 保存写操作调用 `a2a-pay-service.saveBazaarServiceForMcp`。明确 `NOT_SENT` 才能自动重试;`MAYBE_SENT` 时用列表按 `serviceId` 和完整五项资料核验,不能靠同名服务猜测。创建结果未解析到实际 `serviceId` 时禁止宣称完成。
32
+ 保存写操作调用 `a2a-pay-service.saveBazaarServiceForMcp`。明确 `NOT_SENT` 才能自动重试;`MAYBE_SENT` 时用列表按 `serviceId` 和完整五项资料核验,不能靠同名服务猜测。创建结果未解析到实际 `serviceId` 时禁止宣称完成。脚本必须输出 `SERVICE_SAVE_RESULT=SUCCESS|FAILED|UNKNOWN`;`UNKNOWN` 只表示禁止自动重复保存并进入服务分支待办,不清除其他分支事实。
33
33
 
34
34
  成功结果只用登记 renderer:
35
35