@alipay/alipay-aipay 1.2.2 → 1.5.0

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 (104) hide show
  1. package/README.md +5 -0
  2. package/dist/commands/install.d.ts.map +1 -1
  3. package/dist/commands/install.js +13 -12
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/uninstall.d.ts.map +1 -1
  6. package/dist/commands/uninstall.js +17 -9
  7. package/dist/commands/uninstall.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +5 -5
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/config.d.ts +5 -3
  14. package/dist/utils/config.d.ts.map +1 -1
  15. package/dist/utils/config.js +34 -12
  16. package/dist/utils/config.js.map +1 -1
  17. package/dist/utils/file.d.ts +8 -0
  18. package/dist/utils/file.d.ts.map +1 -1
  19. package/dist/utils/file.js +161 -9
  20. package/dist/utils/file.js.map +1 -1
  21. package/dist/utils/jq.d.ts.map +1 -1
  22. package/dist/utils/jq.js +8 -0
  23. package/dist/utils/jq.js.map +1 -1
  24. package/package.json +9 -5
  25. package/skill/SKILL.md +15 -169
  26. package/skill/VERSION +1 -1
  27. package/skill/references/integration/flow.md +388 -291
  28. package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
  29. package/skill/references/integration/modules/checklist.md +54 -32
  30. package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +199 -0
  31. package/skill/references/integration/modules/code-examples/csharp/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +8 -4
  32. package/skill/references/integration/modules/code-examples/csharp/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +6 -2
  33. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +3 -1
  34. package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +184 -0
  35. package/skill/references/integration/modules/code-examples/java/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  36. package/skill/references/integration/modules/code-examples/java/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  37. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +3 -1
  38. package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +135 -0
  39. package/skill/references/integration/modules/code-examples/nodejs/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +16 -10
  40. package/skill/references/integration/modules/code-examples/nodejs/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +10 -4
  41. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +2 -1
  42. package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +157 -0
  43. package/skill/references/integration/modules/code-examples/php/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  44. package/skill/references/integration/modules/code-examples/php/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  45. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +2 -1
  46. package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +128 -0
  47. package/skill/references/integration/modules/code-examples/python/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  48. package/skill/references/integration/modules/code-examples/python/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -1
  49. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +4 -2
  50. package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +52 -0
  51. package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  52. package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  53. package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  54. package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  55. package/skill/references/integration/modules/interface-guide.md +38 -71
  56. package/skill/references/integration/modules/product-decision.md +18 -35
  57. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
  58. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
  59. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
  60. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +358 -148
  61. package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
  62. package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
  63. package/skill/references/normal/alipay-cli-env.md +15 -5
  64. package/skill/references/normal/full-process-routing.md +82 -0
  65. package/skill/references/normal/json/customer-messages.json +942 -0
  66. package/skill/references/normal/json/policy-rules.json +179 -0
  67. package/skill/references/normal/rejection-guide.md +12 -7
  68. package/skill/references/normal/scripts/alipay_cli_env.mjs +33 -0
  69. package/skill/references/normal/scripts/alipay_cli_env.sh +55 -0
  70. package/skill/references/normal/scripts/common.sh +151 -0
  71. package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
  72. package/skill/references/normal/scripts/flow_loader.mjs +241 -0
  73. package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
  74. package/skill/references/normal/scripts/integration_message_runner.mjs +1023 -0
  75. package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
  76. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
  77. package/skill/references/normal/scripts/onboarding_message_runner.mjs +451 -0
  78. package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
  79. package/skill/references/normal/scripts/open_official_url.sh +82 -0
  80. package/skill/references/normal/scripts/platform_compat.mjs +39 -0
  81. package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
  82. package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
  83. package/skill/references/normal/scripts/render_customer_message.mjs +1288 -0
  84. package/skill/references/normal/scripts/self_update.mjs +160 -0
  85. package/skill/references/normal/scripts/telemetry.mjs +1327 -0
  86. package/skill/references/normal/self-update.md +18 -51
  87. package/skill/references/normal/telemetry.md +26 -0
  88. package/skill/references/onboarding/flow.md +421 -309
  89. package/skill/references/onboarding/modules/app-release.md +44 -438
  90. package/skill/references/onboarding/modules/authorization.md +25 -281
  91. package/skill/references/onboarding/modules/error-handling.md +31 -237
  92. package/skill/references/onboarding/modules/product-sign.md +25 -294
  93. package/skill/references/onboarding/modules/scripts/app.sh +445 -169
  94. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +47 -28
  95. package/skill/references/onboarding/modules/scripts/auth.sh +484 -167
  96. package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
  97. package/skill/references/onboarding/modules/scripts/error_handler.sh +101 -130
  98. package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
  99. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +20 -71
  100. package/skill/references/onboarding/modules/scripts/service.sh +154 -89
  101. package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  102. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +34 -20
  103. package/skill/references/onboarding/modules/service-registration.md +20 -317
  104. package/skill/references/runtime-entry.md +63 -0
@@ -0,0 +1,157 @@
1
+ #!/bin/bash
2
+
3
+ set -u
4
+ umask 077
5
+
6
+ MANUAL_URL="https://opendocs.alipay.com/isv/02kipk"
7
+ DEST_DIR="${1:-}"
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
10
+
11
+ manual_fallback_message() {
12
+ echo "未能完成自动下载,可能是当前环境不支持、无法访问下载源、本地目录不可写或安装包校验未通过。请访问支付宝开放平台手动下载支付宝开放平台密钥工具:"
13
+ echo
14
+ echo "$MANUAL_URL"
15
+ echo
16
+ echo "仅向 Agent 提供【应用公钥】,应用私钥请自行妥善保管。"
17
+ }
18
+
19
+ render_fallback() {
20
+ if command -v node >/dev/null 2>&1 && [ -f "$RENDERER" ]; then
21
+ printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=download_key_tool.sh node "$RENDERER" key_tool.download.fallback --variant DEFAULT && return 0
22
+ fi
23
+ manual_fallback_message
24
+ }
25
+
26
+ render_downloaded() {
27
+ local target_file="$1"
28
+ local message_input
29
+ if command -v node >/dev/null 2>&1 && [ -f "$RENDERER" ]; then
30
+ message_input=$(node -e 'process.stdout.write(JSON.stringify({downloadPath: process.argv[1]}))' "$target_file") \
31
+ && printf '%s' "$message_input" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=download_key_tool.sh node "$RENDERER" key_tool.download.result --variant DOWNLOADED \
32
+ && return 0
33
+ fi
34
+ render_fallback
35
+ }
36
+
37
+ fail_download() {
38
+ render_fallback
39
+ exit 0
40
+ }
41
+
42
+ is_windows_reparse_point() {
43
+ local path="$1" powershell_rc
44
+ case "$(uname -s 2>/dev/null || true)" in MINGW*|MSYS*|CYGWIN*) ;; *) return 1 ;; esac
45
+ command -v powershell.exe >/dev/null 2>&1 || return 0
46
+ env "ALIPAY_AIPAY_REPARSE_PATH=$path" powershell.exe -NoProfile -NonInteractive -Command '
47
+ try {
48
+ $item = Get-Item -LiteralPath $env:ALIPAY_AIPAY_REPARSE_PATH -Force -ErrorAction Stop
49
+ } catch {
50
+ exit 2
51
+ }
52
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { exit 0 } else { exit 1 }
53
+ ' >/dev/null 2>&1
54
+ powershell_rc=$?
55
+ [ "$powershell_rc" -eq 0 ] && return 0
56
+ [ "$powershell_rc" -eq 1 ] && return 1
57
+ return 0
58
+ }
59
+
60
+ OS_NAME="${ALIPAY_AIPAY_TEST_OS:-$(uname -s 2>/dev/null || true)}"
61
+ case "$OS_NAME" in
62
+ Darwin) PLATFORM="mac"; EXT="dmg" ;;
63
+ MINGW*|MSYS*|CYGWIN*|Windows_NT) PLATFORM="win"; EXT="exe" ;;
64
+ *) fail_download "UNSUPPORTED_OS" ;;
65
+ esac
66
+
67
+ if [ -z "$DEST_DIR" ]; then
68
+ DEST_DIR="${HOME:-}/Downloads"
69
+ fi
70
+ [ -n "$DEST_DIR" ] && [ -d "$DEST_DIR" ] && [ -w "$DEST_DIR" ] && [ ! -L "$DEST_DIR" ] || fail_download "DEST_DIR_UNAVAILABLE"
71
+ ! is_windows_reparse_point "$DEST_DIR" || fail_download "DEST_DIR_UNAVAILABLE"
72
+ command -v curl >/dev/null 2>&1 || fail_download "DEPENDENCY_MISSING"
73
+ command -v node >/dev/null 2>&1 || fail_download "DEPENDENCY_MISSING"
74
+
75
+ CURRENT_URL="https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=${PLATFORM}&channelType=WEB"
76
+ PART_FILE=$(mktemp "${DEST_DIR}/.alipay-key-tool.XXXXXX.part") || fail_download "DEST_DIR_UNAVAILABLE"
77
+ HEADER_FILE=$(mktemp "${DEST_DIR}/.alipay-key-tool.XXXXXX.headers") || { rm -f "$PART_FILE"; fail_download "DEST_DIR_UNAVAILABLE"; }
78
+ chmod 600 "$PART_FILE" "$HEADER_FILE" 2>/dev/null || true
79
+ cleanup_download() { rm -f "$PART_FILE" "$HEADER_FILE"; }
80
+ trap cleanup_download EXIT INT TERM
81
+
82
+ REDIRECTS=0
83
+ while true; do
84
+ : > "$PART_FILE"
85
+ : > "$HEADER_FILE"
86
+ curl --silent --show-error --connect-timeout 10 --max-time 120 --dump-header "$HEADER_FILE" --output "$PART_FILE" --max-redirs 0 "$CURRENT_URL" 2>/dev/null
87
+ CURL_RC=$?
88
+ STATUS=$(awk '/^HTTP\// {code=$2; sub(/\r$/, "", code)} END {print code}' "$HEADER_FILE")
89
+ [ -n "$STATUS" ] || fail_download "NETWORK_UNAVAILABLE"
90
+ case "$STATUS" in
91
+ 301|302|303|307|308)
92
+ [ "$REDIRECTS" -lt 5 ] || fail_download "UNTRUSTED_REDIRECT"
93
+ LOCATION=$(awk 'tolower($0) ~ /^location:/ {sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); print; exit}' "$HEADER_FILE")
94
+ [ -n "$LOCATION" ] || fail_download "UNTRUSTED_REDIRECT"
95
+ if ! node -e '
96
+ const [raw] = process.argv.slice(1); let u;
97
+ try { u = new URL(raw); } catch { process.exit(1); }
98
+ if (u.protocol !== "https:" || u.username || u.password || u.port) process.exit(1);
99
+ if (!["ideservice.alipay.com", "mdn.alipayobjects.com"].includes(u.hostname)) process.exit(1);
100
+ ' "$LOCATION"; then fail_download "UNTRUSTED_REDIRECT"; fi
101
+ CURRENT_URL="$LOCATION"
102
+ REDIRECTS=$((REDIRECTS + 1))
103
+ ;;
104
+ 200)
105
+ [ "$CURL_RC" -eq 0 ] || fail_download "NETWORK_UNAVAILABLE"
106
+ FINAL_HOST=$(node -e 'console.log(new URL(process.argv[1]).hostname)' "$CURRENT_URL" 2>/dev/null) || fail_download "UNTRUSTED_REDIRECT"
107
+ [ "$FINAL_HOST" = "mdn.alipayobjects.com" ] || fail_download "UNTRUSTED_REDIRECT"
108
+ break
109
+ ;;
110
+ *) fail_download "NETWORK_UNAVAILABLE" ;;
111
+ esac
112
+ done
113
+
114
+ DISPOSITION=$(awk 'tolower($0) ~ /^content-disposition:/ {sub(/\r$/, ""); print; exit}' "$HEADER_FILE")
115
+ FILENAME=$(printf '%s' "$DISPOSITION" | sed -n 's/.*filename="\{0,1\}\([^";]*\)"\{0,1\}.*/\1/p')
116
+ if [ -z "$FILENAME" ]; then
117
+ FILENAME=$(node -e '
118
+ let url;
119
+ try { url = new URL(process.argv[1]); } catch { process.exit(1); }
120
+ process.stdout.write(url.searchParams.get("af_fileName") || "");
121
+ ' "$CURRENT_URL") || fail_download "INVALID_PACKAGE"
122
+ fi
123
+ case "$FILENAME" in ""|*/*|*\\*|.|..) fail_download "INVALID_PACKAGE" ;; esac
124
+ if ! node -e '
125
+ const name = process.argv[1] || "";
126
+ if (/[\u0000-\u001f\u007f`|]/.test(name)) process.exit(1);
127
+ ' "$FILENAME"; then fail_download "INVALID_PACKAGE"; fi
128
+ case "$FILENAME" in *.$EXT) ;; *) fail_download "INVALID_PACKAGE" ;; esac
129
+
130
+ SIZE=$(wc -c < "$PART_FILE" | tr -d ' ')
131
+ [ "$SIZE" -gt 0 ] || fail_download "INVALID_PACKAGE"
132
+ CONTENT_LENGTH=$(awk 'tolower($0) ~ /^content-length:/ {sub(/\r$/, ""); sub(/^[^:]*:[[:space:]]*/, ""); print; exit}' "$HEADER_FILE")
133
+ if [ -n "$CONTENT_LENGTH" ]; then
134
+ case "$CONTENT_LENGTH" in *[!0-9]*) fail_download "INVALID_PACKAGE" ;; esac
135
+ [ "$SIZE" -eq "$CONTENT_LENGTH" ] || fail_download "INVALID_PACKAGE"
136
+ fi
137
+ FIRST_NONSPACE=$(LC_ALL=C tr -d '[:space:]' < "$PART_FILE" | head -c 1)
138
+ case "$FIRST_NONSPACE" in '<'|'{'|'[') fail_download "INVALID_PACKAGE" ;; esac
139
+ if [ "$EXT" = "exe" ]; then
140
+ [ "$(head -c 2 "$PART_FILE")" = "MZ" ] || fail_download "INVALID_PACKAGE"
141
+ else
142
+ LC_ALL=C grep -a -q 'koly' "$PART_FILE" || fail_download "INVALID_PACKAGE"
143
+ fi
144
+
145
+ TARGET_FILE="${DEST_DIR}/${FILENAME}"
146
+ if [ -e "$TARGET_FILE" ] || [ -L "$TARGET_FILE" ]; then
147
+ BASE="${FILENAME%.*}"
148
+ TARGET_FILE="${DEST_DIR}/${BASE}-$(date +%Y%m%d%H%M%S)-$$.${EXT}"
149
+ fi
150
+ [ ! -e "$TARGET_FILE" ] && [ ! -L "$TARGET_FILE" ] || fail_download "DEST_DIR_UNAVAILABLE"
151
+ ln "$PART_FILE" "$TARGET_FILE" || fail_download "DEST_DIR_UNAVAILABLE"
152
+ rm -f "$PART_FILE"
153
+ trap - EXIT INT TERM
154
+ rm -f "$HEADER_FILE"
155
+ [ -f "$TARGET_FILE" ] && [ ! -L "$TARGET_FILE" ] || fail_download "DEST_DIR_UNAVAILABLE"
156
+ chmod 600 "$TARGET_FILE" 2>/dev/null || true
157
+ render_downloaded "$TARGET_FILE"
@@ -9,15 +9,22 @@
9
9
 
10
10
  ERROR_HANDLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  COMMON_SCRIPT="${ERROR_HANDLER_DIR}/../../../normal/scripts/common.sh"
12
+ ERROR_RENDERER="${ERROR_HANDLER_DIR}/../../../normal/scripts/render_customer_message.mjs"
12
13
  if [ -f "$COMMON_SCRIPT" ]; then
13
14
  # shellcheck source=/dev/null
14
15
  source "$COMMON_SCRIPT"
15
- init_dev_tool_name
16
+ init_alipay_cli_context
16
17
  else
17
18
  echo "❌ 缺少公共脚本: $COMMON_SCRIPT"
18
19
  return 1 2>/dev/null || exit 1
19
20
  fi
20
21
 
22
+ render_error_message() {
23
+ local MESSAGE_ID="$1" VARIANT="$2" INPUT_JSON="$3"
24
+ printf '%s' "$INPUT_JSON" \
25
+ | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=error_handler.sh node "$ERROR_RENDERER" "$MESSAGE_ID" --variant "$VARIANT"
26
+ }
27
+
21
28
  # ─── MCP 信封解包 ──────────────────────────────────────────────────────────
22
29
  # alipay-cli mcp call 返回 MCP 协议信封:
23
30
  # { "content": [ { "text": "<业务JSON>", "type": "text" } ] }
@@ -104,6 +111,27 @@ extract_unique_json() {
104
111
  ' 2>/dev/null
105
112
  }
106
113
 
114
+ # 对客错误详情只保留受限单行文本。解析和错误分类仍使用原始值,
115
+ # 但任何疑似凭据、临时 URL 或密钥内容都不能进入普通终端输出。
116
+ sanitize_customer_error_text() {
117
+ local VALUE="${1:-}" NORMALIZED
118
+ NORMALIZED=$(printf '%s' "$VALUE" | tr '\000-\037\177' ' ' | awk '{$1=$1; print}')
119
+ if [ -z "$NORMALIZED" ]; then
120
+ echo "未知错误"
121
+ return
122
+ fi
123
+ if printf '%s' "$NORMALIZED" | grep -qiE '\{\{|\}\}|<INTERNAL_'; then
124
+ echo "错误详情包含内部标记或模板占位符,已隐藏"
125
+ return
126
+ fi
127
+ if printf '%s' "$NORMALIZED" | grep -qiE \
128
+ 'https?://|verification_url|device[_-]?code|authorization:[[:space:]]*bearer|payment-proof|-----BEGIN|private[_ -]?key|public[_ -]?key|["'\'']?(password|passwd|secret|api[_-]?key|access[_-]?token)["'\'']?[[:space:]]*[:=]|(^|[^[:alnum:]_])token[=:]'; then
129
+ echo "错误详情包含临时链接或敏感字段,已隐藏"
130
+ return
131
+ fi
132
+ printf '%s' "$NORMALIZED" | cut -c1-1000
133
+ }
134
+
107
135
  unwrap_mcp() {
108
136
  local RAW="$1"
109
137
  if [ -z "$RAW" ]; then
@@ -120,7 +148,6 @@ unwrap_mcp() {
120
148
  ;;
121
149
  ambiguous)
122
150
  echo "CLI 输出包含多个 JSON 候选,无法唯一解析"
123
- echo "$RAW"
124
151
  return
125
152
  ;;
126
153
  *)
@@ -308,7 +335,7 @@ detect_error() {
308
335
  # 先解包 MCP 信封,再进行错误检测
309
336
  local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
310
337
 
311
- # 1. 优先检测 MCP 认证错误(HTTP 401)
338
+ # 1. 优先检测 MCP 认证错误
312
339
  if echo "$UNWRAPPED" | grep -qiE "HTTP 401|Authorization is empty|非法的认证信息"; then
313
340
  echo "MCP_AUTH_ERROR"
314
341
  return
@@ -333,9 +360,7 @@ detect_error() {
333
360
  fi
334
361
 
335
362
  if ! echo "$UNWRAPPED" | jq -e . >/dev/null 2>&1; then
336
- local FIRST_LINE
337
- FIRST_LINE=$(printf "%s" "$UNWRAPPED" | sed -n '1p')
338
- echo "CLI_ERROR:${FIRST_LINE:-CLI 返回非 JSON 内容}"
363
+ echo "CLI_ERROR:CLI 返回非 JSON 内容,原始输出已隐藏"
339
364
  return
340
365
  fi
341
366
 
@@ -361,44 +386,28 @@ detect_error() {
361
386
  # ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
362
387
  handle_mcp_auth_error() {
363
388
  local LOGOUT_RESULT LOGOUT_EXIT_CODE LOGOUT_ERROR_TYPE LOGOUT_ERROR_MSG
389
+ local LOGOUT_ANALYSIS LOGOUT_PARSE_STATUS
364
390
 
365
- echo ""
366
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
367
- echo "🔐 未登录或授权已过期"
368
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
369
- echo ""
370
- echo "❌ 当前登录状态已失效,无法继续操作"
371
- echo ""
372
- echo "📋 原因:MCP 调用返回认证错误(HTTP 401)"
373
- echo "📋 说明:Authorization is empty(认证信息为空)"
374
- echo ""
375
- echo "🔄 正在退出当前登录..."
376
- LOGOUT_RESULT=$(PLATFORM=${DEV_TOOL_NAME:-unknown} alipay-cli logout --json 2>&1)
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)
377
394
  LOGOUT_EXIT_CODE=$?
378
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)
379
398
 
380
399
  if [ "$LOGOUT_EXIT_CODE" -ne 0 ] || [ "$LOGOUT_ERROR_TYPE" != "SUCCESS" ]; then
381
- echo ""
382
- echo "❌ 退出当前登录失败,已停止重新授权"
383
- if echo "$LOGOUT_RESULT" | jq -e . >/dev/null 2>&1; then
384
- LOGOUT_ERROR_MSG=$(extract_error_message "$LOGOUT_RESULT")
385
- echo "📋 错误信息:$LOGOUT_ERROR_MSG"
386
- elif [ -n "$LOGOUT_RESULT" ]; then
387
- echo "📋 错误信息:$LOGOUT_RESULT"
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
402
+ 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
388
406
  fi
389
- echo "📋 请先重试退出登录;确认退出成功后再重新授权。"
390
407
  return 1
391
408
  fi
392
409
 
393
- echo ""
394
- echo "✅ 已退出登录状态"
395
- echo ""
396
- echo "📌 请重新执行登录授权流程:"
397
- echo " 1. 我会生成授权链接"
398
- echo " 2. 您使用支付宝扫码授权"
399
- echo " 3. 授权成功后继续当前操作"
400
- echo ""
401
- echo "📌 产品、MCC 和授权范围未变化时,沿用已有确认立即重新生成授权链接;发生变化时先重新确认受影响内容。"
410
+ render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || return 1
402
411
  }
403
412
 
404
413
  # ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
@@ -406,57 +415,27 @@ handle_mcp_service_error() {
406
415
  local CLI_RESULT="$1"
407
416
  local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
408
417
 
409
- echo ""
410
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
411
- echo "⚠️ MCP 服务暂时不可用"
412
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
413
- echo ""
414
418
  if [ "${DEV_TOOL_NAME:-unknown}" != "unknown" ]; then
415
- echo "❌ 当前命令需要访问支付宝服务,但当前 Agent 执行环境可能没有外网权限"
416
- echo ""
417
- echo "📋 处理方式:"
418
- echo " - Agent 应申请可联网权限后重试同一条命令"
419
- echo " - 不要要求用户排查本机网络,也不要更换业务参数"
420
- echo ""
421
- echo "请授权 Agent 联网重试,或稍后重新执行当前命令。"
419
+ render_error_message error.mcp.service AGENT_NETWORK '{}'
422
420
  return
423
421
  fi
424
422
 
425
- echo "❌ 无法连接到支付宝服务,请稍后重试"
426
- echo ""
427
- echo "📋 错误详情:"
428
- echo "$UNWRAPPED" | jq -r '.error.message // .errorMessage // .message // "未知错误"' 2>/dev/null || echo "$UNWRAPPED"
429
- echo ""
430
- echo "您可以:"
431
- echo " 1. 检查网络连接是否正常"
432
- echo " 2. 等待几分钟后重新执行"
433
- echo " 3. 联系技术支持获取帮助"
434
- echo ""
435
- echo "请回复「重试」重新尝试,或「退出」结束流程。"
423
+ local SERVICE_ERROR_MSG
424
+ SERVICE_ERROR_MSG=$(echo "$UNWRAPPED" | jq -r '.error.message // .errorMessage // .message // "未知错误"' 2>/dev/null) || SERVICE_ERROR_MSG="服务调用失败,原始输出已隐藏"
425
+ SERVICE_ERROR_MSG=$(sanitize_customer_error_text "$SERVICE_ERROR_MSG")
426
+ MESSAGE_INPUT=$(jq -cn --arg errorDetail "$SERVICE_ERROR_MSG" '{errorDetail:$errorDetail}')
427
+ render_error_message error.mcp.service GENERIC "$MESSAGE_INPUT"
436
428
  }
437
429
 
438
430
  # ─── 授权信息不匹配处理 ────────────────────────────────────────────────────
439
431
  handle_auth_mismatch() {
440
- local CLI_RESULT="$1"
441
- local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
442
-
443
- echo ""
444
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
445
- echo "❌ 授权信息不匹配,需要重新授权"
446
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
447
-
448
- if echo "$UNWRAPPED" | grep -qi "mccCode is not auth"; then
449
- echo "📋 原因:经营类目未授权"
450
- elif echo "$UNWRAPPED" | grep -qi "salesProductCodes is not auth"; then
451
- echo "📋 原因:产品未授权"
452
- elif echo "$UNWRAPPED" | grep -qi "scope is not auth"; then
453
- echo "📋 原因:授权 scope 不满足"
432
+ local UNWRAPPED
433
+ UNWRAPPED=$(unwrap_mcp "${1:-}")
434
+ if echo "$UNWRAPPED" | grep -qiE "mccCode.*is not auth"; then
435
+ emit_control "AUTH_FLOW:MCC_MISMATCH"
436
+ else
437
+ emit_control "AUTH_FLOW:SCOPE_MISMATCH"
454
438
  fi
455
-
456
- echo ""
457
- echo ""
458
- echo "⛔ 已停止当前操作和后续调用"
459
- echo "📋 请调用 auth.sh mismatch;该命令会统一退出当前登录并使用正确范围重新授权"
460
439
  }
461
440
 
462
441
  # ─── 后端业务错误处理(完整透出错误信息) ──────────────────────────────────
@@ -465,75 +444,57 @@ handle_backend_error() {
465
444
  local ERROR_CODE="$2"
466
445
  local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
467
446
 
468
- echo ""
469
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
470
- echo "❌ 业务处理失败"
471
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
472
- echo ""
473
- echo "📋 错误码:$ERROR_CODE"
474
-
475
447
  # 提取错误信息
476
448
  local ERROR_MSG=$(extract_error_message "$UNWRAPPED")
477
449
  local BIZ_TIPS=$(extract_biz_tips "$UNWRAPPED")
478
- local NEED_RETRY=$(extract_need_retry "$UNWRAPPED")
479
450
  local ERROR_SCENE=$(extract_error_object_field "$UNWRAPPED" "errorScene")
480
451
  local ERROR_SPECIFIC=$(extract_error_object_field "$UNWRAPPED" "errorSpecific")
481
452
 
482
- echo "📋 错误信息:$ERROR_MSG"
483
-
484
- if [ -n "$ERROR_SCENE" ] || [ -n "$ERROR_SPECIFIC" ]; then
485
- echo "📋 错误场景:${ERROR_SCENE:-未知} / ${ERROR_SPECIFIC:-未知}"
486
- fi
453
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
454
+ if [ -n "$ERROR_SCENE" ]; then ERROR_SCENE=$(sanitize_customer_error_text "$ERROR_SCENE"); fi
455
+ if [ -n "$ERROR_SPECIFIC" ]; then ERROR_SPECIFIC=$(sanitize_customer_error_text "$ERROR_SPECIFIC"); fi
487
456
 
488
457
  # 透出 checkedError 中的详细错误描述(如 ar-sign.apply 返回的字段级校验错误)
489
458
  local CHECKED_ERRORS=$(extract_checked_errors "$UNWRAPPED")
459
+ local CHECKED_ERRORS_JSON='[]'
490
460
  if [ -n "$CHECKED_ERRORS" ] && [ "$CHECKED_ERRORS" != "null" ] && [ "$CHECKED_ERRORS" != "[]" ]; then
491
- echo ""
492
- echo "📋 详细校验错误:"
493
- echo "$CHECKED_ERRORS" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null | while IFS= read -r line; do
494
- [ -n "$line" ] && echo " - $line"
495
- done
461
+ while IFS= read -r line; do
462
+ if [ -n "$line" ]; then
463
+ line=$(sanitize_customer_error_text "$line")
464
+ CHECKED_ERRORS_JSON=$(jq -cn --argjson items "$CHECKED_ERRORS_JSON" --arg item "$line" '$items + [$item]')
465
+ fi
466
+ done < <(echo "$CHECKED_ERRORS" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null)
496
467
  fi
497
468
 
498
- if [ -n "$BIZ_TIPS" ] && [ "$BIZ_TIPS" != "null" ] && [ "$BIZ_TIPS" != "" ]; then
499
- echo ""
500
- echo "💡 提示:$BIZ_TIPS"
469
+ if [ -n "$BIZ_TIPS" ] && [ "$BIZ_TIPS" != "null" ]; then
470
+ BIZ_TIPS=$(sanitize_customer_error_text "$BIZ_TIPS")
471
+ else
472
+ BIZ_TIPS=""
501
473
  fi
502
-
474
+ local APP_MAX="NO"
503
475
  if [ "$ERROR_CODE" = "APP_MAX_ERROR" ]; then
504
- echo ""
505
- echo "📌 处理建议:当前主体下应用数量已达到上限,无法继续新建应用。请优先复用已有上线应用;如必须新建,请前往支付宝开放平台处理应用配额或联系支付宝技术支持。"
506
- fi
507
-
508
- if [ "$NEED_RETRY" = "true" ]; then
509
- echo ""
510
- echo "🔄 此错误可重试,请回复「重试」重新执行"
476
+ APP_MAX="YES"
511
477
  fi
512
-
513
- echo ""
514
- echo "如需帮助,请联系技术支持并提供以上错误信息。"
478
+ MESSAGE_INPUT=$(jq -cn \
479
+ --arg errorCode "$(sanitize_customer_error_text "$ERROR_CODE")" \
480
+ --arg errorMessage "$ERROR_MSG" \
481
+ --arg errorScene "$ERROR_SCENE" \
482
+ --arg errorSpecific "$ERROR_SPECIFIC" \
483
+ --argjson checkedErrors "$CHECKED_ERRORS_JSON" \
484
+ --arg bizTips "$BIZ_TIPS" \
485
+ --arg appMax "$APP_MAX" \
486
+ '{errorCode:$errorCode,errorMessage:$errorMessage,errorScene:$errorScene,errorSpecific:$errorSpecific,checkedErrors:$checkedErrors,bizTips:$bizTips,appMax:$appMax}')
487
+ render_error_message error.backend DEFAULT "$MESSAGE_INPUT"
515
488
  }
516
489
 
517
490
  # ─── MCP 服务不稳定处理 ────────────────────────────────────────────────────
518
491
  handle_service_unstable() {
519
- echo ""
520
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
521
- echo "⚠️ MCP 服务暂时不稳定"
522
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
523
- echo ""
524
- echo "当前无法继续执行,请稍后重试。"
525
- echo ""
526
- echo "您可以:"
527
- echo " 1. 等待几分钟后重新执行"
528
- echo " 2. 检查网络连接是否正常"
529
- echo " 3. 联系技术支持获取帮助"
530
- echo ""
531
- echo "请回复「重试」重新尝试,或「退出」结束流程。"
492
+ render_error_message error.service.unstable DEFAULT '{}'
532
493
  }
533
494
 
534
495
  # ─── 统一错误处理入口 ──────────────────────────────────────────────────────
535
496
  # 参数: $1 - CLI 执行结果文本(原始输出,含信封或不含)
536
- # 返回: 0=成功, 1=失败(需要用户干预)
497
+ # 返回: 0=成功, 1=当前动作失败(由主流程判断恢复、阻断或继续独立分支)
537
498
  handle_error() {
538
499
  local CLI_RESULT="$1"
539
500
  local ERROR_TYPE=$(detect_error "$CLI_RESULT")
@@ -559,19 +520,29 @@ handle_error() {
559
520
  ;;
560
521
  "CLI_ERROR:"*)
561
522
  local MSG="${ERROR_TYPE#CLI_ERROR:}"
562
- echo "❌ 命令执行失败:$MSG"
563
523
  # CLI_ERROR 也透出 bizTips 和 checkedError(如果有的话)
564
524
  local CLI_BIZ_TIPS=$(extract_biz_tips "$_UNWRAPPED")
565
- if [ -n "$CLI_BIZ_TIPS" ] && [ "$CLI_BIZ_TIPS" != "null" ] && [ "$CLI_BIZ_TIPS" != "" ]; then
566
- echo "💡 $CLI_BIZ_TIPS"
525
+ if [ -n "$CLI_BIZ_TIPS" ] && [ "$CLI_BIZ_TIPS" != "null" ]; then
526
+ CLI_BIZ_TIPS=$(sanitize_customer_error_text "$CLI_BIZ_TIPS")
527
+ else
528
+ CLI_BIZ_TIPS=""
567
529
  fi
568
530
  local CLI_CHECKED=$(extract_checked_errors "$_UNWRAPPED")
531
+ local CLI_CHECKED_JSON='[]'
569
532
  if [ -n "$CLI_CHECKED" ] && [ "$CLI_CHECKED" != "null" ] && [ "$CLI_CHECKED" != "[]" ]; then
570
- echo "📋 详细校验错误:"
571
- echo "$CLI_CHECKED" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null | while IFS= read -r line; do
572
- [ -n "$line" ] && echo " - $line"
573
- done
533
+ while IFS= read -r line; do
534
+ if [ -n "$line" ]; then
535
+ line=$(sanitize_customer_error_text "$line")
536
+ CLI_CHECKED_JSON=$(jq -cn --argjson items "$CLI_CHECKED_JSON" --arg item "$line" '$items + [$item]')
537
+ fi
538
+ done < <(echo "$CLI_CHECKED" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null)
574
539
  fi
540
+ MESSAGE_INPUT=$(jq -cn \
541
+ --arg errorMessage "$(sanitize_customer_error_text "$MSG")" \
542
+ --arg bizTips "$CLI_BIZ_TIPS" \
543
+ --argjson checkedErrors "$CLI_CHECKED_JSON" \
544
+ '{errorMessage:$errorMessage,bizTips:$bizTips,checkedErrors:$checkedErrors}')
545
+ render_error_message error.cli DEFAULT "$MESSAGE_INPUT"
575
546
  ;;
576
547
  "SUCCESS")
577
548
  return 0
@@ -0,0 +1,96 @@
1
+ #!/bin/bash
2
+
3
+ # Source after error_handler.sh. This wrapper never changes MCP argv or business JSON.
4
+
5
+ NETWORK_RETRY_CLASS=""
6
+ NETWORK_DISPATCH_STATE=""
7
+ NETWORK_ATTEMPTS=0
8
+
9
+ network_retry_is_local_transport_error() {
10
+ printf '%s' "$1" | grep -Eqi \
11
+ 'could not resolve host|name or service not known|nodename nor servname|connection refused|failed to connect|network access[^[:cntrl:]]*(denied|blocked)[^[:cntrl:]]*before connection'
12
+ }
13
+
14
+ network_retry_is_not_sent() {
15
+ printf '%s' "$1" | grep -Eqi \
16
+ 'could not resolve host|name or service not known|nodename nor servname|connection refused|network access[^[:cntrl:]]*(denied|blocked)[^[:cntrl:]]*before connection'
17
+ }
18
+
19
+ run_network_retry() {
20
+ local result_var="$1" operation="$2" action_name="$3"
21
+ shift 3
22
+ [ "${1:-}" = "--" ] || { echo "NETWORK_RETRY_ERROR:缺少 -- 参数分隔符" >&2; return 64; }
23
+ shift
24
+ [ "$#" -gt 0 ] || { echo "NETWORK_RETRY_ERROR:缺少命令 argv" >&2; return 64; }
25
+ case "$operation" in read|write) ;; *) echo "NETWORK_RETRY_ERROR:operation 必须为 read 或 write" >&2; return 64 ;; esac
26
+
27
+ local max_retries="${NETWORK_RETRY_MAX_RETRIES:-2}"
28
+ local delay_seconds="${NETWORK_RETRY_DELAY_SECONDS:-3}"
29
+ local attempt=0 stdout_file stderr_file stdout stderr rc classification transient dispatch
30
+ stdout_file=$(mktemp "${TMPDIR:-/tmp}/alipay-aipay-stdout.XXXXXX") || return 1
31
+ stderr_file=$(mktemp "${TMPDIR:-/tmp}/alipay-aipay-stderr.XXXXXX") || { rm -f "$stdout_file"; return 1; }
32
+ chmod 600 "$stdout_file" "$stderr_file" 2>/dev/null || true
33
+ trap 'rm -f "$stdout_file" "$stderr_file"; trap - RETURN' RETURN
34
+
35
+ while true; do
36
+ attempt=$((attempt + 1))
37
+ : > "$stdout_file"
38
+ : > "$stderr_file"
39
+ "$@" >"$stdout_file" 2>"$stderr_file"
40
+ rc=$?
41
+ stdout=$(cat "$stdout_file")
42
+ stderr=$(cat "$stderr_file")
43
+ classification=""
44
+ transient=false
45
+ dispatch="MAYBE_SENT"
46
+
47
+ local stdout_is_business_json=false unwrapped_stdout=""
48
+ if [ -n "$stdout" ]; then
49
+ classification=$(detect_error "$stdout" 2>/dev/null || true)
50
+ unwrapped_stdout=$(unwrap_mcp "$stdout" 2>/dev/null || true)
51
+ if printf '%s' "$unwrapped_stdout" | jq -e . >/dev/null 2>&1; then
52
+ stdout_is_business_json=true
53
+ fi
54
+ # 合法业务 JSON 始终以 stdout 为准。普通启动日志会被 detect_error 归为
55
+ # CLI_ERROR;此时仍须检查 stderr,避免真实连接失败被非空 stdout 掩盖。
56
+ if [ "$stdout_is_business_json" != true ] && [ "$rc" -ne 0 ] && [ -n "$stderr" ]; then
57
+ classification=$(detect_error "$stderr" 2>/dev/null || true)
58
+ fi
59
+ elif [ -n "$stderr" ]; then
60
+ classification=$(detect_error "$stderr" 2>/dev/null || true)
61
+ fi
62
+
63
+ case "$classification" in
64
+ MCP_SERVICE_ERROR|SERVICE_UNSTABLE) transient=true ;;
65
+ *)
66
+ if [ "$stdout_is_business_json" != true ] && { [ "$rc" -ne 0 ] || [ -n "$stderr" ]; } && network_retry_is_local_transport_error "$stderr"; then
67
+ classification="LOCAL_TRANSPORT_ERROR"
68
+ transient=true
69
+ fi
70
+ ;;
71
+ esac
72
+
73
+ if network_retry_is_not_sent "$stderr"; then
74
+ dispatch="NOT_SENT"
75
+ fi
76
+
77
+ NETWORK_RETRY_CLASS="$classification"
78
+ NETWORK_DISPATCH_STATE="$dispatch"
79
+ NETWORK_ATTEMPTS="$attempt"
80
+ printf -v "$result_var" '%s' "$stdout"
81
+
82
+ if [ "$transient" != true ]; then
83
+ return 0
84
+ fi
85
+
86
+ if [ "$operation" = "write" ] && [ "$dispatch" != "NOT_SENT" ]; then
87
+ return 75
88
+ fi
89
+
90
+ if [ "$attempt" -gt "$max_retries" ]; then
91
+ echo "NETWORK_RETRY_EXHAUSTED:${action_name}:${classification}:${dispatch}:${attempt}" >&2
92
+ return 76
93
+ fi
94
+ sleep "$delay_seconds"
95
+ done
96
+ }