@alipay/alipay-aipay 1.5.3 → 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.
- package/package.json +1 -1
- package/skill/VERSION +1 -1
- package/skill/references/normal/json/customer-messages.json +2 -2
- package/skill/references/normal/json/policy-rules.json +6 -0
- package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +9 -7
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +15 -8
- package/skill/references/normal/scripts/render_customer_message.mjs +33 -0
- package/skill/references/normal/scripts/telemetry.mjs +11 -0
- package/skill/references/onboarding/flow.md +4 -2
- package/skill/references/onboarding/modules/error-handling.md +4 -2
- package/skill/references/onboarding/modules/scripts/auth.sh +27 -60
- package/skill/references/onboarding/modules/scripts/error_handler.sh +82 -45
- package/skill/references/runtime-entry.md +1 -1
package/package.json
CHANGED
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.5.
|
|
1
|
+
1.5.4
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"trigger": "onboarding.windows_platform",
|
|
44
44
|
"blocking": false,
|
|
45
45
|
"allowedReplies": [],
|
|
46
|
-
"variables": [],
|
|
46
|
+
"variables": [{ "name": "officialUrl", "type": "officialTemporaryUrl", "required": true, "urlKind": "windowsOnboardingRedirect" }],
|
|
47
47
|
"requiredPolicyIds": [],
|
|
48
48
|
"variants": {
|
|
49
|
-
"DEFAULT": { "templateLines": ["当前为 Windows 系统,请您暂时前往支付宝 AI 付站点完成产品开通:", "
|
|
49
|
+
"DEFAULT": { "templateLines": ["当前为 Windows 系统,请您暂时前往支付宝 AI 付站点完成产品开通:", "{{officialUrl}}"] }
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
{
|
|
@@ -174,6 +174,12 @@
|
|
|
174
174
|
"path": "/public-key-confirm",
|
|
175
175
|
"requiredQueryParams": ["keyConfirmToken"],
|
|
176
176
|
"optionalQueryParams": []
|
|
177
|
+
},
|
|
178
|
+
"windowsOnboardingRedirect": {
|
|
179
|
+
"host": "aipay.alipay.com",
|
|
180
|
+
"path": "/",
|
|
181
|
+
"requiredQueryParams": ["from", "reason", "platform_id"],
|
|
182
|
+
"optionalQueryParams": []
|
|
177
183
|
}
|
|
178
184
|
}
|
|
179
185
|
}
|
|
@@ -306,20 +306,22 @@ async function runDiscovery(args) {
|
|
|
306
306
|
if (globalBlocker) envelope.global_blocker = globalBlocker;
|
|
307
307
|
emitEnvelope(envelope);
|
|
308
308
|
|
|
309
|
-
|
|
309
|
+
const customerOutputResults = globalBlocker
|
|
310
|
+
? executedResults.filter(hasAuthBlocker)
|
|
311
|
+
: executedResults;
|
|
312
|
+
const customerOutputs = customerOutputResults
|
|
310
313
|
.map((result) => result.stdout.trim())
|
|
311
314
|
.filter(Boolean);
|
|
312
|
-
|
|
313
|
-
if (globalBlocker)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
customerOutputs.push(renderDiscoverySummary(productType, {
|
|
315
|
+
let uniqueCustomerOutputs = [...new Set(customerOutputs)];
|
|
316
|
+
if (globalBlocker) uniqueCustomerOutputs = uniqueCustomerOutputs.slice(0, 1);
|
|
317
|
+
if (!globalBlocker) {
|
|
318
|
+
uniqueCustomerOutputs.push(renderDiscoverySummary(productType, {
|
|
317
319
|
signStatus: envelope.signing.query_status === 'SUCCESS' ? envelope.signing.sign_status : 'QUERY_FAILED',
|
|
318
320
|
serviceFlow: envelope.service.query_status === 'SUCCESS' ? envelope.service.flow : 'QUERY_FAILED',
|
|
319
321
|
appFlow: envelope.application.query_status === 'SUCCESS' ? envelope.application.flow : 'QUERY_FAILED'
|
|
320
322
|
}));
|
|
321
323
|
}
|
|
322
|
-
if (
|
|
324
|
+
if (uniqueCustomerOutputs.length > 0) process.stdout.write(`${uniqueCustomerOutputs.join('\n\n')}\n`);
|
|
323
325
|
return globalBlocker ? 2 : 0;
|
|
324
326
|
}
|
|
325
327
|
|
|
@@ -5,8 +5,8 @@ import path from 'node:path';
|
|
|
5
5
|
import process from 'node:process';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { isWindowsPlatform } from './platform_compat.mjs';
|
|
8
|
-
import { loadMccRecords, renderMessage } from './render_customer_message.mjs';
|
|
9
|
-
import { emitStageMetricAsync, emitTerminalMetric } from './telemetry.mjs';
|
|
8
|
+
import { buildWindowsOnboardingRedirectUrl, loadMccRecords, renderMessage } from './render_customer_message.mjs';
|
|
9
|
+
import { currentRunContext, emitStageMetricAsync, emitTerminalMetric } from './telemetry.mjs';
|
|
10
10
|
|
|
11
11
|
const productTypes = {
|
|
12
12
|
aipay: { variant: 'AIPAY', productName: 'AI 按量付费' },
|
|
@@ -129,9 +129,14 @@ async function readStdin() {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function runPlatformEntry() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
if (!isWindowsPlatform()) return '';
|
|
133
|
+
const runContext = currentRunContext();
|
|
134
|
+
if (!runContext || runContext.terminal || !['onboarding_only', 'full_process'].includes(runContext.root_intent)) {
|
|
135
|
+
throw new Error('Windows 产品开通引导缺少有效的当前 Onboarding run context');
|
|
136
|
+
}
|
|
137
|
+
return renderMessage('onboarding.windows.redirect', 'DEFAULT', {
|
|
138
|
+
officialUrl: buildWindowsOnboardingRedirectUrl(runContext.run_id)
|
|
139
|
+
});
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
function optional(args, name) {
|
|
@@ -297,16 +302,18 @@ function resolveMccResult(query, args) {
|
|
|
297
302
|
const deterministicCodes = deterministicMatches.map(({ record }) => record.code);
|
|
298
303
|
const agentCodes = agent.codes;
|
|
299
304
|
if (agent.semanticStatus !== 'MATCHED') {
|
|
305
|
+
const finalCodes = deterministicStrongCodes.slice(0, 8);
|
|
306
|
+
const candidates = finalCodes.map((code) => mccCandidateFromRecord(records.get(code)));
|
|
300
307
|
return {
|
|
301
|
-
matchType: 'NONE',
|
|
308
|
+
matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
|
|
302
309
|
semanticStatus: agent.semanticStatus,
|
|
303
310
|
deterministicCandidateCodes: deterministicCodes,
|
|
304
311
|
deterministicStrongCandidateCodes: deterministicStrongCodes,
|
|
305
312
|
deterministicWeakCandidateCodes: deterministicWeakCodes,
|
|
306
313
|
agentCandidateCodes: agentCodes,
|
|
307
|
-
mergedCandidateCodes:
|
|
314
|
+
mergedCandidateCodes: finalCodes,
|
|
308
315
|
mccTableSummary: mccTableSummary(records),
|
|
309
|
-
candidates
|
|
316
|
+
candidates
|
|
310
317
|
};
|
|
311
318
|
}
|
|
312
319
|
const deterministicCodeSet = new Set(deterministicCodes);
|
|
@@ -140,6 +140,26 @@ function validateUrl(value, policy, urlKind, expected = {}) {
|
|
|
140
140
|
}
|
|
141
141
|
if (/[\r\n]/.test(parsed.searchParams.get('keyConfirmToken'))) throw new Error('publicKeyConfirmation URL keyConfirmToken 无效');
|
|
142
142
|
}
|
|
143
|
+
if (urlKind === 'windowsOnboardingRedirect') {
|
|
144
|
+
const required = allowed.requiredQueryParams || ['from', 'reason', 'platform_id'];
|
|
145
|
+
const allowedKeys = new Set(required);
|
|
146
|
+
const actualKeys = [...parsed.searchParams.keys()];
|
|
147
|
+
if (actualKeys.some((key) => !allowedKeys.has(key))) throw new Error('windowsOnboardingRedirect URL 包含未允许的查询参数');
|
|
148
|
+
for (const key of required) {
|
|
149
|
+
if (parsed.searchParams.getAll(key).length !== 1 || !parsed.searchParams.get(key)) {
|
|
150
|
+
throw new Error('windowsOnboardingRedirect URL 缺少必要查询参数或参数重复');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (parsed.searchParams.get('from') !== 'alipay-aipay-skill') {
|
|
154
|
+
throw new Error('windowsOnboardingRedirect URL from 无效');
|
|
155
|
+
}
|
|
156
|
+
if (parsed.searchParams.get('reason') !== 'alipay-aipay-skill-onboarding-not-support-windows') {
|
|
157
|
+
throw new Error('windowsOnboardingRedirect URL reason 无效');
|
|
158
|
+
}
|
|
159
|
+
if (/[\u0000-\u001f\u007f,^=]/u.test(parsed.searchParams.get('platform_id'))) {
|
|
160
|
+
throw new Error('windowsOnboardingRedirect URL platform_id 无效');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
143
163
|
return parsed;
|
|
144
164
|
}
|
|
145
165
|
|
|
@@ -191,6 +211,19 @@ export function buildAuthorizationUrl(input) {
|
|
|
191
211
|
return value;
|
|
192
212
|
}
|
|
193
213
|
|
|
214
|
+
export function buildWindowsOnboardingRedirectUrl(platformId) {
|
|
215
|
+
if (typeof platformId !== 'string' || !platformId || /[\u0000-\u001f\u007f,^=]/u.test(platformId)) {
|
|
216
|
+
throw new Error('Windows 产品开通引导缺少合法的当前流程 run_id');
|
|
217
|
+
}
|
|
218
|
+
const url = new URL('https://aipay.alipay.com/');
|
|
219
|
+
url.searchParams.set('from', 'alipay-aipay-skill');
|
|
220
|
+
url.searchParams.set('reason', 'alipay-aipay-skill-onboarding-not-support-windows');
|
|
221
|
+
url.searchParams.set('platform_id', platformId);
|
|
222
|
+
const value = url.toString();
|
|
223
|
+
validateUrl(value, loadJson(policyPath), 'windowsOnboardingRedirect');
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
|
|
194
227
|
function validateValue(definition, value, policy) {
|
|
195
228
|
if (value === undefined || value === null || value === '') {
|
|
196
229
|
if (definition.required) throw new Error(`缺少必填变量 ${definition.name}`);
|
|
@@ -240,6 +240,17 @@ function readRunState(env = process.env) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
export function currentRunContext(env = process.env) {
|
|
244
|
+
const state = readRunState(env);
|
|
245
|
+
if (!state) return null;
|
|
246
|
+
return Object.freeze({
|
|
247
|
+
run_id: state.run_id,
|
|
248
|
+
root_intent: state.root_intent,
|
|
249
|
+
product_type: state.product_type,
|
|
250
|
+
terminal: state.terminal === true
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
243
254
|
function canReadImplicitRunState(env = process.env) {
|
|
244
255
|
return !isMaintenanceProcess(env);
|
|
245
256
|
}
|
|
@@ -82,7 +82,7 @@ Step 1: 方案规划 → Step 2: 环境检查 → Step 3: 登录授权
|
|
|
82
82
|
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message platform-entry
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
- Windows:runner stdout 是唯一对客正文,必须原文发送;发送后停止本 flow 的产品开通动作,不再执行 Step 1、MCC 澄清、方案展示、登录或材料收集。`onboarding_only` 在根意图识别后进入本 flow 即执行;`full_process` 保留已完成的 Integration 实际结果,在衔接本 flow 时执行。
|
|
85
|
+
- Windows:runner 从当前私有 run context 读取同一个 `run_id`,编码为站点引导 URL 的 `platform_id`;禁止 Agent 读取 marker 后手写、传入或拼接该参数。runner stdout 是唯一对客正文,必须原文发送;发送后停止本 flow 的产品开通动作,不再执行 Step 1、MCC 澄清、方案展示、登录或材料收集。`onboarding_only` 在根意图识别后进入本 flow 即执行;`full_process` 保留已完成的 Integration 实际结果,在衔接本 flow 时执行。
|
|
86
86
|
- 非 Windows:runner 成功且 stdout 为空,直接继续下方既有流程,不输出平台提示,不改变步骤、确认或完成条件。
|
|
87
87
|
- 命令失败或输出无法唯一判断时停止并核对 runner,禁止手写、改写或补发近似引导。
|
|
88
88
|
|
|
@@ -134,7 +134,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-
|
|
|
134
134
|
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mcc-resolve-plan --query "$MCC_SEARCH_QUERY" --product-type "$PRODUCT_TYPE" --agent-mcc-status "$AGENT_MCC_STATUS" [--agent-mcc-codes "$AGENT_MCC_CODES"]
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
`AGENT_MCC_STATUS` 仅允许 `MATCHED|INSUFFICIENT|UNSUPPORTED`;`MATCHED` 必须提供 1 到 3 个用英文逗号分隔的完整编码,后两者不得提供编码。runner 会重新执行确定性召回并校验 Agent 编码确实存在于当前 MCC 表,名称、适用商家和特殊资质全部从同一表重建;非法、重复、超量、状态与候选数量矛盾或表外编码均为 Skill 契约错误,禁止降级为 Agent
|
|
137
|
+
`AGENT_MCC_STATUS` 仅允许 `MATCHED|INSUFFICIENT|UNSUPPORTED`;`MATCHED` 必须提供 1 到 3 个用英文逗号分隔的完整编码,后两者不得提供编码。runner 会重新执行确定性召回并校验 Agent 编码确实存在于当前 MCC 表,名称、适用商家和特殊资质全部从同一表重建;非法、重复、超量、状态与候选数量矛盾或表外编码均为 Skill 契约错误,禁止降级为 Agent 自报结果。`MATCHED` 时按交集候选、确定性强命中、Agent 独立候选和确定性弱命中的顺序合并;`INSUFFICIENT|UNSUPPORTED` 不得否决确定性强命中,强命中仍可形成唯一或多候选结果,但确定性弱命中不得单独推进。确定性候选最多 5 个,最终合并候选最多 8 个。按次收费、支付渠道和目标产品只用于产品决策,不能改变 MCC 排名。
|
|
138
138
|
|
|
139
139
|
只接受唯一 `ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=<json>`,其中必须包含 `semanticStatus`、`deterministicCandidateCodes`、`agentCandidateCodes`、`mergedCandidateCodes` 和 `mccTableSummary`:`matchType=UNIQUE` 时必须恰有一条候选,stdout 已直接渲染同一候选绑定的 `onboarding.plan.confirm`,必须原文发送但不停止、不等待回复,随后立即进入 `execute`;`MULTIPLE` 时 runner stdout 已直接渲染固定候选消息,必须原文发送并停止,等待用户回复序号、MCC 二级类目名称或编码;`NONE` 时 runner stdout 已直接渲染独立 `onboarding.mcc.no_match`,必须原文发送并停止。禁止再次调用旧的 `mcc-search`/`mcc-candidates` 组合、自由拼候选表、解释、示例或“是否继续”等近似问法。
|
|
140
140
|
|
|
@@ -226,6 +226,8 @@ Step 1 的产品/MCC 唯一事实校验和方案送达、Step 2 的环境 READY
|
|
|
226
226
|
└─ mismatch 统一 logout;后置条件成立后用正确 scope 重建授权页
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
`mismatch` 中 logout 明确业务失败但 `whoami` 仍登录时,脚本内部以“初次 + 2 次”固定预算、间隔 3 秒重试完全相同的 logout。每次 logout 失败后只执行一次 `whoami` 后置核验;logout 明确成功时无需额外核验。任一次证明已退出即继续重新授权;预算耗尽且仍登录才输出 `AUTH_FLOW:LOGOUT_STILL_LOGGED_IN`。网络/服务异常、冲突输出或结果无法确认不消耗该业务重试预算,只转 `AUTH_FLOW:RETRY_WITH_NETWORK`。整个过程不增加用户确认或 Agent 命令,logout 未证明成功前禁止 login。
|
|
230
|
+
|
|
229
231
|
`AUTH_FLOW:CONTEXT_MISMATCH` 适用于 `open|wait|confirm|mismatch` 的显式上下文与同一次 init 短时状态不一致;脚本必须在任何 `login --complete/logout` 前停止且不输出对客正文。Agent 不得忽略差异、改用旧状态继续或手工清理状态;必须立即用当前 Step 1 已确认的完整参数重新执行第 1 项。新的 init 先完成固定产品/MCC 机器校验,再安全替换旧短时状态;若 CLI 仍处于登录态,因为签约查询没有权威 MCC 字段,init 必须返回对应 `SCOPE_MISMATCH/MCC_MISMATCH` 并转第 4 项,不得返回 `SKIP`。CLI 未登录时直接生成当前上下文的新授权页。两种分支都不得复用旧 deviceCode 或新增用户确认。
|
|
230
232
|
|
|
231
233
|
`auth.page` 非阻塞并追加“扫码完成后请回到 Agent 对话页面”;`auth.pending` 仅由 wait 耗尽或单次 confirm 的 PENDING 渲染,其他对客终态由脚本渲染对应消息。自动轮询期间即使 `login --complete` 已返回可复核状态,只要 `whoami` 尚未取得有效本机登录态,也继续按 PENDING 处理并消耗本次 wait 预算,不输出断头失败文案。剔除 `ALIPAY_AIPAY_INTERNAL:` 内部行后的 stdout 是当前动作唯一对客正文;禁止再次调用 renderer、另写近似话术、输出 stderr 或内部行、重复转述。授权脚本明确返回链接过期时必须重建,旧回复失效。
|
|
@@ -19,7 +19,9 @@ discovery runner 和截图上传这类并发/分组动作不得让每个子进
|
|
|
19
19
|
|
|
20
20
|
## 解包与业务错误
|
|
21
21
|
|
|
22
|
-
`unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text`
|
|
22
|
+
`unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text` 信封和混合日志。CLI 捕获必须始终合并 stdout 与 stderr 后执行一次唯一性解析:单个 JSON 加普通日志可接受,相同 JSON 重复可去重,多个不同 JSON 或成功/失败冲突不得选择任一路,也不得回显原始响应。每次 MCP 调用先把唯一原始结果交给 `handle_error`,返回成功后才调用 `unwrap_mcp` 和当前脚本的业务解析;不得统一假定成功字段位于 `.success`。
|
|
23
|
+
|
|
24
|
+
每次 logout 失败后只执行一次 `whoami` 后置核验;logout 明确成功时无需额外核验,最多尝试 3 次 logout。`whoami` 后置核验必须先满足唯一 JSON、退出码为 0 且 `detect_error=SUCCESS`,再读取类型正确的 `logged_in/is_expired`;业务失败响应即使携带 `logged_in=false` 也不能证明已退出。logout 网络/服务异常、冲突输出或结果无法确认时,即使 `whoami` 仍显示已登录,也只能转 `AUTH_FLOW:RETRY_WITH_NETWORK` 并重试同一动作。logout 是可由 `whoami` 验证最终状态的幂等恢复动作:明确业务失败且仍登录时,脚本内部使用“初次 + 2 次”固定预算、间隔 3 秒重试完全相同的 logout;任一次确认已退出即继续,全部耗尽且仍登录才转 `AUTH_FLOW:LOGOUT_STILL_LOGGED_IN`。所有失败分支都不得执行 login。每个 auth 动作只允许产生一个最终 `AUTH_FLOW:*`,具体恢复 marker 已由错误处理器输出时,调用方不得追加 `AUTH_FLOW:FAILED`。
|
|
23
25
|
|
|
24
26
|
业务错误对象可能位于顶层、`data.response` 或 `errorContext.errorStack[]`。保留脚本当前字段优先级:读取 `errorCode` 的字符串或对象形式、`errorMessage|errorMsg`、`bizTips`、`checkedError`、`errorScene` 和 `errorSpecific`;只有已验证的服务写入协议 `data.success=false` 才优先使用 `data.subMsg|data.msg`,并忽略只表示外层调用成功的顶层 `msg="Success"`。`APP_MAX_ERROR` 只说明应用数量达到上限,引导复用已有上线应用或前往支付宝开放平台处理配额,不推断其他审核规则。
|
|
25
27
|
|
|
@@ -28,7 +30,7 @@ discovery runner 和截图上传这类并发/分组动作不得让每个子进
|
|
|
28
30
|
## 网络重试与写后核验
|
|
29
31
|
|
|
30
32
|
- `MCP_SERVICE_ERROR`、`SERVICE_UNSTABLE` 和已识别本地网络错误:初次调用后最多重试 2 次,每次间隔 3 秒,始终复用完全相同的 argv、请求 JSON、业务请求号、资源名和动态字段。
|
|
31
|
-
-
|
|
33
|
+
- 认证、授权不匹配、参数、业务错误和结构歧义不进入通用网络重试。唯一例外是上述可验证最终状态的幂等 logout 恢复,它使用同一三次总预算且不经通用 `network_retry.sh`。stderr 不拼入待解析 JSON。
|
|
32
34
|
- 只读调用可使用完整重试预算。非幂等写操作只有明确 `NOT_SENT` 才自动重试;DNS 失败、connection refused 或宿主在连接前明确拒绝网络访问可归为 `NOT_SENT`。
|
|
33
35
|
- timeout、连接中断、发送阶段不明和 `SERVICE_UNSTABLE` 一律归为 `MAYBE_SENT`,停止写重试并使用现有只读能力核验;无法确认输出 `UNKNOWN`。
|
|
34
36
|
- 签约提交用签约查询核验;服务修改只有列表按完整 `serviceId` 和五项资料精确匹配才算成功;服务新建和应用创建不得按同名候选猜测;应用提审只看现有应用信息状态。没有登记核验能力的公钥确认页不得凭页面或同名结果推断成功。
|
|
@@ -345,12 +345,32 @@ auth_cli_environment_failure() {
|
|
|
345
345
|
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
346
346
|
}
|
|
347
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
|
+
|
|
348
368
|
wait_for_effective_whoami() {
|
|
349
369
|
local action="$1"
|
|
350
370
|
local max_attempts=3
|
|
351
371
|
local interval_seconds=2
|
|
352
372
|
local attempt=1
|
|
353
|
-
local logged_in is_expired
|
|
373
|
+
local logged_in is_expired
|
|
354
374
|
|
|
355
375
|
AUTH_WHOAMI_RESULT=""
|
|
356
376
|
if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
|
|
@@ -362,19 +382,7 @@ wait_for_effective_whoami() {
|
|
|
362
382
|
auth_cli_environment_failure "$action"
|
|
363
383
|
return 1
|
|
364
384
|
}
|
|
365
|
-
|
|
366
|
-
if ! handle_error "$AUTH_WHOAMI_RESULT"; then
|
|
367
|
-
case "$whoami_error_type" in
|
|
368
|
-
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
369
|
-
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
370
|
-
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
371
|
-
;;
|
|
372
|
-
*)
|
|
373
|
-
emit_control "AUTH_FLOW:FAILED"
|
|
374
|
-
;;
|
|
375
|
-
esac
|
|
376
|
-
return 1
|
|
377
|
-
fi
|
|
385
|
+
handle_auth_cli_result "$AUTH_WHOAMI_RESULT" || return 1
|
|
378
386
|
|
|
379
387
|
logged_in=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.logged_in // false')
|
|
380
388
|
is_expired=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.is_expired // false')
|
|
@@ -403,18 +411,6 @@ run_json_command() {
|
|
|
403
411
|
exit_code=$?
|
|
404
412
|
stderr_output=$(cat "$stderr_file")
|
|
405
413
|
|
|
406
|
-
if json_payload=$(extract_json_payload "$stdout"); then
|
|
407
|
-
rm -f "$stderr_file"
|
|
408
|
-
printf '%s' "$json_payload"
|
|
409
|
-
return 0
|
|
410
|
-
fi
|
|
411
|
-
|
|
412
|
-
if json_payload=$(extract_json_payload "$stderr_output"); then
|
|
413
|
-
rm -f "$stderr_file"
|
|
414
|
-
printf '%s' "$json_payload"
|
|
415
|
-
return 0
|
|
416
|
-
fi
|
|
417
|
-
|
|
418
414
|
combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
|
|
419
415
|
if json_payload=$(extract_json_payload "$combined_output"); then
|
|
420
416
|
rm -f "$stderr_file"
|
|
@@ -433,7 +429,7 @@ validate_current_authorization() {
|
|
|
433
429
|
local whoami_result="${1:-}"
|
|
434
430
|
local logged_in is_expired granted_scope required_scope
|
|
435
431
|
local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match mcc_raw retry_rc
|
|
436
|
-
local mcc_error_type mcc_error_code mcc_success
|
|
432
|
+
local mcc_error_type mcc_error_code mcc_success
|
|
437
433
|
local mismatch_type sign_status_output sign_status_marker sign_flow_marker sign_reuse_receipt
|
|
438
434
|
local granted_scope_item required_scope_item required_scope_found
|
|
439
435
|
local -a granted_scope_items required_scope_items
|
|
@@ -451,19 +447,7 @@ validate_current_authorization() {
|
|
|
451
447
|
}
|
|
452
448
|
fi
|
|
453
449
|
|
|
454
|
-
|
|
455
|
-
if ! handle_error "$whoami_result"; then
|
|
456
|
-
case "$whoami_error_type" in
|
|
457
|
-
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
458
|
-
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
459
|
-
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
460
|
-
;;
|
|
461
|
-
*)
|
|
462
|
-
emit_control "AUTH_FLOW:FAILED"
|
|
463
|
-
;;
|
|
464
|
-
esac
|
|
465
|
-
return 1
|
|
466
|
-
fi
|
|
450
|
+
handle_auth_cli_result "$whoami_result" || return 1
|
|
467
451
|
|
|
468
452
|
logged_in=$(echo "$whoami_result" | jq -r '.data.logged_in // false')
|
|
469
453
|
is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
|
|
@@ -617,10 +601,7 @@ auth_init() {
|
|
|
617
601
|
auth_cli_environment_failure "登录状态检查"
|
|
618
602
|
return 1
|
|
619
603
|
}
|
|
620
|
-
|
|
621
|
-
emit_control "AUTH_FLOW:FAILED"
|
|
622
|
-
return 1
|
|
623
|
-
fi
|
|
604
|
+
handle_auth_cli_result "$CHECK_RESULT" || return 1
|
|
624
605
|
LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
|
|
625
606
|
IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
|
|
626
607
|
|
|
@@ -650,10 +631,7 @@ auth_init() {
|
|
|
650
631
|
auth_cli_environment_failure "授权登录发起"
|
|
651
632
|
return 1
|
|
652
633
|
}
|
|
653
|
-
|
|
654
|
-
emit_control "AUTH_FLOW:FAILED"
|
|
655
|
-
return 1
|
|
656
|
-
fi
|
|
634
|
+
handle_auth_cli_result "$LOGIN_RESULT" || return 1
|
|
657
635
|
LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
|
|
658
636
|
|
|
659
637
|
if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
|
|
@@ -750,7 +728,6 @@ auth_open() {
|
|
|
750
728
|
# ─── auth confirm: 授权确认 + scope 校验 + MCC 授权有效性校验 ────────────────
|
|
751
729
|
auth_confirm() {
|
|
752
730
|
AUTH_CONFIRM_RESULT=""
|
|
753
|
-
local complete_error_type
|
|
754
731
|
# 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
|
|
755
732
|
restore_auth_context_or_state "$@" || exit 1
|
|
756
733
|
|
|
@@ -798,18 +775,8 @@ auth_confirm() {
|
|
|
798
775
|
elif [ "$OUTER_SUCCESS" = "true" ] || [ "$DATA_STATUS" = "logged_in" ] || [ "$DATA_STATUS" = "already_logged_in" ] || [ "$DATA_STATUS" = "completed" ]; then
|
|
799
776
|
:
|
|
800
777
|
else
|
|
801
|
-
|
|
802
|
-
if ! handle_error "$LOGIN_COMPLETE_RESULT"; then
|
|
778
|
+
if ! handle_auth_cli_result "$LOGIN_COMPLETE_RESULT"; then
|
|
803
779
|
cleanup_auth_state
|
|
804
|
-
case "$complete_error_type" in
|
|
805
|
-
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
806
|
-
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
807
|
-
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
808
|
-
;;
|
|
809
|
-
*)
|
|
810
|
-
emit_control "AUTH_FLOW:FAILED"
|
|
811
|
-
;;
|
|
812
|
-
esac
|
|
813
780
|
return 1
|
|
814
781
|
fi
|
|
815
782
|
wait_for_effective_whoami "授权完成确认状态复核"
|
|
@@ -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
|
|
@@ -403,12 +403,6 @@ run_alipay_cli_json_capture() {
|
|
|
403
403
|
stderr_output=$(cat "$stderr_file")
|
|
404
404
|
rm -f "$stderr_file"
|
|
405
405
|
|
|
406
|
-
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$stdout"); then
|
|
407
|
-
return 0
|
|
408
|
-
fi
|
|
409
|
-
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$stderr_output"); then
|
|
410
|
-
return 0
|
|
411
|
-
fi
|
|
412
406
|
combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
|
|
413
407
|
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$combined_output"); then
|
|
414
408
|
return 0
|
|
@@ -419,48 +413,79 @@ run_alipay_cli_json_capture() {
|
|
|
419
413
|
}
|
|
420
414
|
|
|
421
415
|
confirm_logout_postcondition() {
|
|
422
|
-
local logout_error_type whoami_error_type
|
|
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
|
|
423
420
|
|
|
424
421
|
ALIPAY_LOGOUT_POSTCONDITION=""
|
|
425
422
|
ALIPAY_LOGOUT_ERROR_MESSAGE=""
|
|
426
423
|
|
|
427
|
-
if
|
|
428
|
-
|
|
429
|
-
if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -eq 0 ] && [ "$logout_error_type" = "SUCCESS" ]; then
|
|
430
|
-
ALIPAY_LOGOUT_POSTCONDITION="COMMAND_CONFIRMED_SUCCESS"
|
|
431
|
-
return 0
|
|
432
|
-
fi
|
|
433
|
-
ALIPAY_LOGOUT_ERROR_MESSAGE=$(sanitize_customer_error_text "$(extract_error_message "$ALIPAY_CLI_JSON_CAPTURE")")
|
|
434
|
-
else
|
|
435
|
-
ALIPAY_LOGOUT_ERROR_MESSAGE="退出命令未返回可唯一解析结果"
|
|
424
|
+
if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ]; then
|
|
425
|
+
interval_seconds=0
|
|
436
426
|
fi
|
|
437
427
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
449
|
+
else
|
|
450
|
+
ALIPAY_LOGOUT_ERROR_MESSAGE="退出命令未返回可唯一解析结果"
|
|
451
|
+
fi
|
|
442
452
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
ALIPAY_LOGOUT_POSTCONDITION="STILL_LOGGED_IN"
|
|
454
|
-
return 1
|
|
455
|
-
fi
|
|
456
|
-
whoami_error_type=$(detect_error "$ALIPAY_CLI_JSON_CAPTURE")
|
|
457
|
-
if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -ne 0 ] || { [ "$whoami_error_type" != "SUCCESS" ] && [ "$logged_in" = "true" ] && [ "$is_expired" != "true" ]; }; then
|
|
458
|
-
ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
|
|
459
|
-
return 1
|
|
460
|
-
fi
|
|
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
|
|
462
|
+
fi
|
|
461
463
|
|
|
462
|
-
|
|
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
|
|
464
489
|
}
|
|
465
490
|
|
|
466
491
|
render_logout_unconfirmed() {
|
|
@@ -476,7 +501,10 @@ render_logout_still_logged_in() {
|
|
|
476
501
|
|
|
477
502
|
# ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
|
|
478
503
|
handle_mcp_auth_error() {
|
|
479
|
-
render_error_message error.mcp.auth START '{}' ||
|
|
504
|
+
render_error_message error.mcp.auth START '{}' || {
|
|
505
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
506
|
+
return 1
|
|
507
|
+
}
|
|
480
508
|
if [ "${ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT:-}" = "1" ]; then
|
|
481
509
|
emit_control "AUTH_FLOW:AUTH_REQUIRED"
|
|
482
510
|
return 1
|
|
@@ -484,15 +512,24 @@ handle_mcp_auth_error() {
|
|
|
484
512
|
confirm_logout_postcondition
|
|
485
513
|
case "$ALIPAY_LOGOUT_POSTCONDITION" in
|
|
486
514
|
COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
|
|
487
|
-
render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' ||
|
|
515
|
+
render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || {
|
|
516
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
517
|
+
return 1
|
|
518
|
+
}
|
|
488
519
|
emit_control "AUTH_FLOW:AUTH_REQUIRED"
|
|
489
520
|
;;
|
|
490
521
|
STILL_LOGGED_IN)
|
|
491
|
-
render_logout_still_logged_in ||
|
|
522
|
+
render_logout_still_logged_in || {
|
|
523
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
524
|
+
return 1
|
|
525
|
+
}
|
|
492
526
|
emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
|
|
493
527
|
;;
|
|
494
528
|
*)
|
|
495
|
-
render_logout_unconfirmed ||
|
|
529
|
+
render_logout_unconfirmed || {
|
|
530
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
531
|
+
return 1
|
|
532
|
+
}
|
|
496
533
|
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
497
534
|
;;
|
|
498
535
|
esac
|
|
@@ -44,7 +44,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" render <messageId> --va
|
|
|
44
44
|
| `onboarding_only` | 只要求签约、入驻或产品开通,未同时要求代码开发 | 执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-bootstrap --root-intent onboarding_only` | 当前阶段的授权、查询或写分支模块 |
|
|
45
45
|
| `full_process` | 明确要求完整接入、一站式,或同一请求同时要求代码开发和产品开通 | `normal/full-process-routing.md` | 先 Integration,满足衔接条件后 Onboarding |
|
|
46
46
|
|
|
47
|
-
`onboarding_only` 正常入口使用 `onboarding-bootstrap --root-intent onboarding_only`,该组合动作只串行执行 `telemetry start-run`、`onboarding-message platform-entry` 和 `flow onboarding entry` 三个既有无业务写入动作;Windows 只发送登记引导并停止 Skill
|
|
47
|
+
`onboarding_only` 正常入口使用 `onboarding-bootstrap --root-intent onboarding_only`,该组合动作只串行执行 `telemetry start-run`、`onboarding-message platform-entry` 和 `flow onboarding entry` 三个既有无业务写入动作;Windows 只发送登记引导并停止 Skill 内产品开通,且引导 URL 的 `platform_id` 由 runner 读取本次已创建的同一 `run_id` 后编码生成,禁止 Agent 传入或拼接;非 Windows 继续返回 entry 执行契约。执行该组合动作后不得再单独重复执行这三条原子命令。`full_process` 衔接 Onboarding 时已存在同一根流程 run,不使用带 `start-run` 的 bootstrap;仍先执行 flow 登记的 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message platform-entry`,再按需加载 Onboarding entry,避免重复 `run.intent_detected`。
|
|
48
48
|
|
|
49
49
|
识别根意图后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry start-run --root-intent <integration_only|onboarding_only|full_process>`;产品已唯一确定时追加 `--product-type <aipay|webpay|apppay>`。唯一例外是 `onboarding_only` 已按上表执行 `onboarding-bootstrap --root-intent onboarding_only` 时,bootstrap 已包含同一次 `start-run`,不得重复执行本条 telemetry。命令创建/复用当前流程 `run_id`,并在遥测开启时记录 `run.intent_detected`:同一意图和产品继续复用,`UNKNOWN` 补全产品仍复用同一 `run_id`;明确换产品、换根意图或既有 run 已终态才重建。第三方平台项目 ID 仍优先固定为 `run_id`,不得被显式 run ID 或本地生成值覆盖。业务 stage 只由登记 runner/script 异步记录。
|
|
50
50
|
|