@alipay/alipay-aipay 1.2.2 → 1.4.9
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/README.md +5 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +13 -12
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +17 -9
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +5 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +5 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +34 -12
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/file.d.ts +8 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +161 -9
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/jq.d.ts.map +1 -1
- package/dist/utils/jq.js +8 -0
- package/dist/utils/jq.js.map +1 -1
- package/package.json +6 -5
- package/skill/SKILL.md +15 -169
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +321 -292
- package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
- package/skill/references/integration/modules/checklist.md +41 -27
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +53 -0
- package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
- package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
- package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
- package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
- package/skill/references/integration/modules/interface-guide.md +37 -72
- package/skill/references/integration/modules/product-decision.md +10 -27
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
- package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +357 -147
- package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
- package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
- package/skill/references/normal/alipay-cli-env.md +15 -5
- package/skill/references/normal/full-process-routing.md +80 -0
- package/skill/references/normal/json/customer-messages.json +937 -0
- package/skill/references/normal/json/policy-rules.json +179 -0
- package/skill/references/normal/rejection-guide.md +11 -6
- package/skill/references/normal/scripts/alipay_cli_env.mjs +29 -0
- package/skill/references/normal/scripts/alipay_cli_env.sh +21 -0
- package/skill/references/normal/scripts/common.sh +87 -0
- package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
- package/skill/references/normal/scripts/integration_message_runner.mjs +801 -0
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +436 -0
- package/skill/references/normal/scripts/open_official_url.sh +82 -0
- package/skill/references/normal/scripts/platform_compat.mjs +39 -0
- package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
- package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
- package/skill/references/normal/scripts/render_customer_message.mjs +1263 -0
- package/skill/references/normal/scripts/self_update.mjs +160 -0
- package/skill/references/normal/scripts/telemetry.mjs +1278 -0
- package/skill/references/normal/self-update.md +18 -51
- package/skill/references/onboarding/flow.md +381 -283
- package/skill/references/onboarding/modules/app-release.md +132 -179
- package/skill/references/onboarding/modules/authorization.md +53 -64
- package/skill/references/onboarding/modules/error-handling.md +31 -34
- package/skill/references/onboarding/modules/product-sign.md +62 -66
- package/skill/references/onboarding/modules/scripts/app.sh +397 -158
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +38 -22
- package/skill/references/onboarding/modules/scripts/auth.sh +479 -167
- package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
- package/skill/references/onboarding/modules/scripts/error_handler.sh +100 -130
- package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -70
- package/skill/references/onboarding/modules/scripts/service.sh +153 -89
- package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +16 -14
- package/skill/references/onboarding/modules/service-registration.md +67 -72
- package/skill/references/runtime-entry.md +62 -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
|
-
|
|
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
|
|
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
|
-
|
|
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,27 @@ 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
|
-
|
|
366
|
-
|
|
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
|
+
render_error_message error.mcp.auth START '{}' || return 1
|
|
392
|
+
LOGOUT_RESULT=$(PLATFORM=${DEV_TOOL_NAME:-unknown} PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli logout --json 2>&1)
|
|
377
393
|
LOGOUT_EXIT_CODE=$?
|
|
378
394
|
LOGOUT_ERROR_TYPE=$(detect_error "$LOGOUT_RESULT")
|
|
395
|
+
LOGOUT_ANALYSIS=$(extract_unique_json "$LOGOUT_RESULT" 2>/dev/null || echo '{"status":"none"}')
|
|
396
|
+
LOGOUT_PARSE_STATUS=$(echo "$LOGOUT_ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
|
|
379
397
|
|
|
380
398
|
if [ "$LOGOUT_EXIT_CODE" -ne 0 ] || [ "$LOGOUT_ERROR_TYPE" != "SUCCESS" ]; then
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
LOGOUT_ERROR_MSG=$(extract_error_message "$LOGOUT_RESULT")
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
echo "📋 错误信息:$LOGOUT_RESULT"
|
|
399
|
+
if [ "$LOGOUT_PARSE_STATUS" != "ok" ] || [ "$LOGOUT_ERROR_TYPE" = "MCP_SERVICE_ERROR" ] || [ "$LOGOUT_ERROR_TYPE" = "SERVICE_UNSTABLE" ]; then
|
|
400
|
+
render_error_message error.mcp.auth LOGOUT_UNCONFIRMED '{}' || return 1
|
|
401
|
+
else
|
|
402
|
+
LOGOUT_ERROR_MSG=$(sanitize_customer_error_text "$(extract_error_message "$LOGOUT_RESULT")")
|
|
403
|
+
MESSAGE_INPUT=$(jq -cn --arg errorMessage "$LOGOUT_ERROR_MSG" '{errorMessage:$errorMessage}')
|
|
404
|
+
render_error_message error.mcp.auth LOGOUT_FAILED "$MESSAGE_INPUT" || return 1
|
|
388
405
|
fi
|
|
389
|
-
echo "📋 请先重试退出登录;确认退出成功后再重新授权。"
|
|
390
406
|
return 1
|
|
391
407
|
fi
|
|
392
408
|
|
|
393
|
-
|
|
394
|
-
echo "✅ 已退出登录状态"
|
|
395
|
-
echo ""
|
|
396
|
-
echo "📌 请重新执行登录授权流程:"
|
|
397
|
-
echo " 1. 我会生成授权链接"
|
|
398
|
-
echo " 2. 您使用支付宝扫码授权"
|
|
399
|
-
echo " 3. 授权成功后继续当前操作"
|
|
400
|
-
echo ""
|
|
401
|
-
echo "📌 产品、MCC 和授权范围未变化时,沿用已有确认立即重新生成授权链接;发生变化时先重新确认受影响内容。"
|
|
409
|
+
render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || return 1
|
|
402
410
|
}
|
|
403
411
|
|
|
404
412
|
# ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
|
|
@@ -406,57 +414,27 @@ handle_mcp_service_error() {
|
|
|
406
414
|
local CLI_RESULT="$1"
|
|
407
415
|
local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
|
|
408
416
|
|
|
409
|
-
echo ""
|
|
410
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
411
|
-
echo "⚠️ MCP 服务暂时不可用"
|
|
412
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
413
|
-
echo ""
|
|
414
417
|
if [ "${DEV_TOOL_NAME:-unknown}" != "unknown" ]; then
|
|
415
|
-
|
|
416
|
-
echo ""
|
|
417
|
-
echo "📋 处理方式:"
|
|
418
|
-
echo " - Agent 应申请可联网权限后重试同一条命令"
|
|
419
|
-
echo " - 不要要求用户排查本机网络,也不要更换业务参数"
|
|
420
|
-
echo ""
|
|
421
|
-
echo "请授权 Agent 联网重试,或稍后重新执行当前命令。"
|
|
418
|
+
render_error_message error.mcp.service AGENT_NETWORK '{}'
|
|
422
419
|
return
|
|
423
420
|
fi
|
|
424
421
|
|
|
425
|
-
|
|
426
|
-
echo ""
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
echo "您可以:"
|
|
431
|
-
echo " 1. 检查网络连接是否正常"
|
|
432
|
-
echo " 2. 等待几分钟后重新执行"
|
|
433
|
-
echo " 3. 联系技术支持获取帮助"
|
|
434
|
-
echo ""
|
|
435
|
-
echo "请回复「重试」重新尝试,或「退出」结束流程。"
|
|
422
|
+
local SERVICE_ERROR_MSG
|
|
423
|
+
SERVICE_ERROR_MSG=$(echo "$UNWRAPPED" | jq -r '.error.message // .errorMessage // .message // "未知错误"' 2>/dev/null) || SERVICE_ERROR_MSG="服务调用失败,原始输出已隐藏"
|
|
424
|
+
SERVICE_ERROR_MSG=$(sanitize_customer_error_text "$SERVICE_ERROR_MSG")
|
|
425
|
+
MESSAGE_INPUT=$(jq -cn --arg errorDetail "$SERVICE_ERROR_MSG" '{errorDetail:$errorDetail}')
|
|
426
|
+
render_error_message error.mcp.service GENERIC "$MESSAGE_INPUT"
|
|
436
427
|
}
|
|
437
428
|
|
|
438
429
|
# ─── 授权信息不匹配处理 ────────────────────────────────────────────────────
|
|
439
430
|
handle_auth_mismatch() {
|
|
440
|
-
local
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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 不满足"
|
|
431
|
+
local UNWRAPPED
|
|
432
|
+
UNWRAPPED=$(unwrap_mcp "${1:-}")
|
|
433
|
+
if echo "$UNWRAPPED" | grep -qiE "mccCode.*is not auth"; then
|
|
434
|
+
emit_control "AUTH_FLOW:MCC_MISMATCH"
|
|
435
|
+
else
|
|
436
|
+
emit_control "AUTH_FLOW:SCOPE_MISMATCH"
|
|
454
437
|
fi
|
|
455
|
-
|
|
456
|
-
echo ""
|
|
457
|
-
echo ""
|
|
458
|
-
echo "⛔ 已停止当前操作和后续调用"
|
|
459
|
-
echo "📋 请调用 auth.sh mismatch;该命令会统一退出当前登录并使用正确范围重新授权"
|
|
460
438
|
}
|
|
461
439
|
|
|
462
440
|
# ─── 后端业务错误处理(完整透出错误信息) ──────────────────────────────────
|
|
@@ -465,75 +443,57 @@ handle_backend_error() {
|
|
|
465
443
|
local ERROR_CODE="$2"
|
|
466
444
|
local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
|
|
467
445
|
|
|
468
|
-
echo ""
|
|
469
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
470
|
-
echo "❌ 业务处理失败"
|
|
471
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
472
|
-
echo ""
|
|
473
|
-
echo "📋 错误码:$ERROR_CODE"
|
|
474
|
-
|
|
475
446
|
# 提取错误信息
|
|
476
447
|
local ERROR_MSG=$(extract_error_message "$UNWRAPPED")
|
|
477
448
|
local BIZ_TIPS=$(extract_biz_tips "$UNWRAPPED")
|
|
478
|
-
local NEED_RETRY=$(extract_need_retry "$UNWRAPPED")
|
|
479
449
|
local ERROR_SCENE=$(extract_error_object_field "$UNWRAPPED" "errorScene")
|
|
480
450
|
local ERROR_SPECIFIC=$(extract_error_object_field "$UNWRAPPED" "errorSpecific")
|
|
481
451
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if [ -n "$
|
|
485
|
-
echo "📋 错误场景:${ERROR_SCENE:-未知} / ${ERROR_SPECIFIC:-未知}"
|
|
486
|
-
fi
|
|
452
|
+
ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
|
|
453
|
+
if [ -n "$ERROR_SCENE" ]; then ERROR_SCENE=$(sanitize_customer_error_text "$ERROR_SCENE"); fi
|
|
454
|
+
if [ -n "$ERROR_SPECIFIC" ]; then ERROR_SPECIFIC=$(sanitize_customer_error_text "$ERROR_SPECIFIC"); fi
|
|
487
455
|
|
|
488
456
|
# 透出 checkedError 中的详细错误描述(如 ar-sign.apply 返回的字段级校验错误)
|
|
489
457
|
local CHECKED_ERRORS=$(extract_checked_errors "$UNWRAPPED")
|
|
458
|
+
local CHECKED_ERRORS_JSON='[]'
|
|
490
459
|
if [ -n "$CHECKED_ERRORS" ] && [ "$CHECKED_ERRORS" != "null" ] && [ "$CHECKED_ERRORS" != "[]" ]; then
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
460
|
+
while IFS= read -r line; do
|
|
461
|
+
if [ -n "$line" ]; then
|
|
462
|
+
line=$(sanitize_customer_error_text "$line")
|
|
463
|
+
CHECKED_ERRORS_JSON=$(jq -cn --argjson items "$CHECKED_ERRORS_JSON" --arg item "$line" '$items + [$item]')
|
|
464
|
+
fi
|
|
465
|
+
done < <(echo "$CHECKED_ERRORS" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null)
|
|
496
466
|
fi
|
|
497
467
|
|
|
498
|
-
if [ -n "$BIZ_TIPS" ] && [ "$BIZ_TIPS" != "null" ]
|
|
499
|
-
|
|
500
|
-
|
|
468
|
+
if [ -n "$BIZ_TIPS" ] && [ "$BIZ_TIPS" != "null" ]; then
|
|
469
|
+
BIZ_TIPS=$(sanitize_customer_error_text "$BIZ_TIPS")
|
|
470
|
+
else
|
|
471
|
+
BIZ_TIPS=""
|
|
501
472
|
fi
|
|
502
|
-
|
|
473
|
+
local APP_MAX="NO"
|
|
503
474
|
if [ "$ERROR_CODE" = "APP_MAX_ERROR" ]; then
|
|
504
|
-
|
|
505
|
-
echo "📌 处理建议:当前主体下应用数量已达到上限,无法继续新建应用。请优先复用已有上线应用;如必须新建,请前往支付宝开放平台处理应用配额或联系支付宝技术支持。"
|
|
506
|
-
fi
|
|
507
|
-
|
|
508
|
-
if [ "$NEED_RETRY" = "true" ]; then
|
|
509
|
-
echo ""
|
|
510
|
-
echo "🔄 此错误可重试,请回复「重试」重新执行"
|
|
475
|
+
APP_MAX="YES"
|
|
511
476
|
fi
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
477
|
+
MESSAGE_INPUT=$(jq -cn \
|
|
478
|
+
--arg errorCode "$(sanitize_customer_error_text "$ERROR_CODE")" \
|
|
479
|
+
--arg errorMessage "$ERROR_MSG" \
|
|
480
|
+
--arg errorScene "$ERROR_SCENE" \
|
|
481
|
+
--arg errorSpecific "$ERROR_SPECIFIC" \
|
|
482
|
+
--argjson checkedErrors "$CHECKED_ERRORS_JSON" \
|
|
483
|
+
--arg bizTips "$BIZ_TIPS" \
|
|
484
|
+
--arg appMax "$APP_MAX" \
|
|
485
|
+
'{errorCode:$errorCode,errorMessage:$errorMessage,errorScene:$errorScene,errorSpecific:$errorSpecific,checkedErrors:$checkedErrors,bizTips:$bizTips,appMax:$appMax}')
|
|
486
|
+
render_error_message error.backend DEFAULT "$MESSAGE_INPUT"
|
|
515
487
|
}
|
|
516
488
|
|
|
517
489
|
# ─── MCP 服务不稳定处理 ────────────────────────────────────────────────────
|
|
518
490
|
handle_service_unstable() {
|
|
519
|
-
|
|
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 "请回复「重试」重新尝试,或「退出」结束流程。"
|
|
491
|
+
render_error_message error.service.unstable DEFAULT '{}'
|
|
532
492
|
}
|
|
533
493
|
|
|
534
494
|
# ─── 统一错误处理入口 ──────────────────────────────────────────────────────
|
|
535
495
|
# 参数: $1 - CLI 执行结果文本(原始输出,含信封或不含)
|
|
536
|
-
# 返回: 0=成功, 1
|
|
496
|
+
# 返回: 0=成功, 1=当前动作失败(由主流程判断恢复、阻断或继续独立分支)
|
|
537
497
|
handle_error() {
|
|
538
498
|
local CLI_RESULT="$1"
|
|
539
499
|
local ERROR_TYPE=$(detect_error "$CLI_RESULT")
|
|
@@ -559,19 +519,29 @@ handle_error() {
|
|
|
559
519
|
;;
|
|
560
520
|
"CLI_ERROR:"*)
|
|
561
521
|
local MSG="${ERROR_TYPE#CLI_ERROR:}"
|
|
562
|
-
echo "❌ 命令执行失败:$MSG"
|
|
563
522
|
# CLI_ERROR 也透出 bizTips 和 checkedError(如果有的话)
|
|
564
523
|
local CLI_BIZ_TIPS=$(extract_biz_tips "$_UNWRAPPED")
|
|
565
|
-
if [ -n "$CLI_BIZ_TIPS" ] && [ "$CLI_BIZ_TIPS" != "null" ]
|
|
566
|
-
|
|
524
|
+
if [ -n "$CLI_BIZ_TIPS" ] && [ "$CLI_BIZ_TIPS" != "null" ]; then
|
|
525
|
+
CLI_BIZ_TIPS=$(sanitize_customer_error_text "$CLI_BIZ_TIPS")
|
|
526
|
+
else
|
|
527
|
+
CLI_BIZ_TIPS=""
|
|
567
528
|
fi
|
|
568
529
|
local CLI_CHECKED=$(extract_checked_errors "$_UNWRAPPED")
|
|
530
|
+
local CLI_CHECKED_JSON='[]'
|
|
569
531
|
if [ -n "$CLI_CHECKED" ] && [ "$CLI_CHECKED" != "null" ] && [ "$CLI_CHECKED" != "[]" ]; then
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
532
|
+
while IFS= read -r line; do
|
|
533
|
+
if [ -n "$line" ]; then
|
|
534
|
+
line=$(sanitize_customer_error_text "$line")
|
|
535
|
+
CLI_CHECKED_JSON=$(jq -cn --argjson items "$CLI_CHECKED_JSON" --arg item "$line" '$items + [$item]')
|
|
536
|
+
fi
|
|
537
|
+
done < <(echo "$CLI_CHECKED" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null)
|
|
574
538
|
fi
|
|
539
|
+
MESSAGE_INPUT=$(jq -cn \
|
|
540
|
+
--arg errorMessage "$(sanitize_customer_error_text "$MSG")" \
|
|
541
|
+
--arg bizTips "$CLI_BIZ_TIPS" \
|
|
542
|
+
--argjson checkedErrors "$CLI_CHECKED_JSON" \
|
|
543
|
+
'{errorMessage:$errorMessage,bizTips:$bizTips,checkedErrors:$checkedErrors}')
|
|
544
|
+
render_error_message error.cli DEFAULT "$MESSAGE_INPUT"
|
|
575
545
|
;;
|
|
576
546
|
"SUCCESS")
|
|
577
547
|
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
|
+
}
|