@alipay/alipay-aipay 1.4.9 → 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.
- package/package.json +5 -2
- package/skill/SKILL.md +2 -2
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +109 -41
- package/skill/references/integration/modules/checklist.md +25 -17
- 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 +1 -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 +1 -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 +1 -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 +1 -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 +1 -1
- package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +3 -4
- package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +4 -4
- package/skill/references/integration/modules/interface-contract/common-trade-contract.md +1 -1
- package/skill/references/integration/modules/interface-contract/notify-url-contract.md +4 -4
- package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +4 -4
- package/skill/references/integration/modules/interface-guide.md +15 -13
- package/skill/references/integration/modules/product-decision.md +13 -13
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +2 -2
- package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +1 -1
- package/skill/references/integration/modules/scripts/sandbox_config.sh +1 -1
- package/skill/references/integration/modules/troubleshooting-flow.md +3 -3
- package/skill/references/normal/alipay-cli-env.md +2 -2
- package/skill/references/normal/full-process-routing.md +7 -5
- package/skill/references/normal/json/customer-messages.json +56 -51
- package/skill/references/normal/json/policy-rules.json +6 -6
- package/skill/references/normal/rejection-guide.md +1 -1
- package/skill/references/normal/scripts/alipay_cli_env.mjs +6 -2
- package/skill/references/normal/scripts/alipay_cli_env.sh +36 -2
- package/skill/references/normal/scripts/common.sh +64 -0
- package/skill/references/normal/scripts/flow_loader.mjs +241 -0
- package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
- package/skill/references/normal/scripts/integration_message_runner.mjs +237 -15
- package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
- package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +27 -12
- package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
- package/skill/references/normal/scripts/project_route_inspector.sh +3 -3
- package/skill/references/normal/scripts/python_script_runner.mjs +1 -1
- package/skill/references/normal/scripts/render_customer_message.mjs +67 -42
- package/skill/references/normal/scripts/telemetry.mjs +54 -5
- package/skill/references/normal/telemetry.md +26 -0
- package/skill/references/onboarding/flow.md +166 -152
- package/skill/references/onboarding/modules/app-release.md +39 -386
- package/skill/references/onboarding/modules/authorization.md +25 -270
- package/skill/references/onboarding/modules/error-handling.md +50 -253
- package/skill/references/onboarding/modules/product-sign.md +24 -289
- package/skill/references/onboarding/modules/scripts/app.sh +58 -21
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +15 -12
- package/skill/references/onboarding/modules/scripts/auth.sh +6 -1
- package/skill/references/onboarding/modules/scripts/error_handler.sh +1 -0
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +5 -3
- package/skill/references/onboarding/modules/scripts/service.sh +3 -2
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +24 -12
- package/skill/references/onboarding/modules/service-registration.md +20 -312
- package/skill/references/runtime-entry.md +17 -16
|
@@ -51,6 +51,15 @@ function loadJson(file) {
|
|
|
51
51
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function allowedRepliesFor(catalog, message) {
|
|
55
|
+
if (!message.allowedReplySet) return message.allowedReplies || [];
|
|
56
|
+
const replies = catalog.interactionReplySets?.[message.allowedReplySet];
|
|
57
|
+
if (!Array.isArray(replies) || replies.length === 0 || replies.some((reply) => typeof reply !== 'string' || !reply)) {
|
|
58
|
+
throw new Error(`${message.messageId}: allowedReplySet 无效`);
|
|
59
|
+
}
|
|
60
|
+
return replies;
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
export function loadMccRecords() {
|
|
55
64
|
const records = new Map();
|
|
56
65
|
let primaryCode = '';
|
|
@@ -412,7 +421,7 @@ function validateServiceCreateSummary(variantName, variables, normalized) {
|
|
|
412
421
|
}
|
|
413
422
|
|
|
414
423
|
function validateIntegrationStart(variantName, variables) {
|
|
415
|
-
const expectedProducts = { AIPAY: '按量付费', WEBPAY: '
|
|
424
|
+
const expectedProducts = { AIPAY: 'AI 按量付费', WEBPAY: 'AI 网页应用收款', APPPAY: 'AI 移动应用收款' };
|
|
416
425
|
if (expectedProducts[variantName] !== variables.productName) {
|
|
417
426
|
throw new Error('integration.start.confirm variant 与产品不匹配');
|
|
418
427
|
}
|
|
@@ -425,12 +434,12 @@ function validateWriteConfirmation(variantName, variables, normalized) {
|
|
|
425
434
|
const common = ['sessionSummary', 'subjectSummary', 'productName', 'actionTypes'];
|
|
426
435
|
const service = ['serviceId', 'serviceName', 'serviceDescription', 'serviceUrl', 'servicePricing', 'serviceSchema'];
|
|
427
436
|
const definitions = {
|
|
428
|
-
SERVICE_UPDATE_ONLY: { actions: '服务修改', product: '按量付费', required: ['serviceId', 'serviceName', 'serviceDescription', 'serviceUrl', 'servicePricing', 'serviceSchema'], allowed: [...common, ...service] }
|
|
437
|
+
SERVICE_UPDATE_ONLY: { actions: '服务修改', product: 'AI 按量付费', required: ['serviceId', 'serviceName', 'serviceDescription', 'serviceUrl', 'servicePricing', 'serviceSchema'], allowed: [...common, ...service] }
|
|
429
438
|
};
|
|
430
439
|
const rule = definitions[variantName];
|
|
431
440
|
if (!rule) throw new Error(`onboarding.write.confirm 不支持 variant ${variantName}`);
|
|
432
441
|
if (variables.actionTypes !== rule.actions) throw new Error('写确认 variant 与 actionTypes 不匹配');
|
|
433
|
-
if (rule.product && variables.productName !== rule.product) throw new Error('
|
|
442
|
+
if (rule.product && variables.productName !== rule.product) throw new Error('服务写确认只适用于 AI 按量付费');
|
|
434
443
|
for (const name of rule.required) {
|
|
435
444
|
if (!hasValue(variables, name)) throw new Error(`写确认缺少 ${name}`);
|
|
436
445
|
}
|
|
@@ -445,23 +454,23 @@ function validateWriteConfirmation(variantName, variables, normalized) {
|
|
|
445
454
|
}
|
|
446
455
|
|
|
447
456
|
function productionSpecificLines(productName) {
|
|
448
|
-
if (productName === '按量付费') {
|
|
457
|
+
if (productName === 'AI 按量付费') {
|
|
449
458
|
return [
|
|
450
|
-
'3. 按量付费 `serviceId`:把沙箱 `api_mock_service_id` 替换为真实 `serviceId`,`serviceId` 获取方式是:登录支付宝 AI 付站点(https://aipay.alipay.com/?from=alipay-aipay-skill) → 控制台 → 服务管理 → 复制审核已通过的目标服务 ID。',
|
|
451
|
-
'4. 按量付费商户标识:`seller_id`/`seller_unique_id` 填收款商户 PID/2088,PID 获取方式是:登录支付宝商家平台(https://b.alipay.com/page/portal/home) → 点击头像 → 复制账户名下方 2088 开头的商家 ID;`seller_app_id` 填生产 `appId`。',
|
|
452
|
-
'5. 按量付费验收:A2M 不使用 `notify_url`;上线前用生产应用私钥重新签名 402 账单,并验证 `Payment-Proof`、金额/订单/资源一致、`trade_no` 防重复履约和 `alipay.aipay.agent.fulfillment.confirm` 异步回执。'
|
|
459
|
+
'3. AI 按量付费 `serviceId`:把沙箱 `api_mock_service_id` 替换为真实 `serviceId`,`serviceId` 获取方式是:登录支付宝 AI 付站点(https://aipay.alipay.com/?from=alipay-aipay-skill) → 控制台 → 服务管理 → 复制审核已通过的目标服务 ID。',
|
|
460
|
+
'4. AI 按量付费商户标识:`seller_id`/`seller_unique_id` 填收款商户 PID/2088,PID 获取方式是:登录支付宝商家平台(https://b.alipay.com/page/portal/home) → 点击头像 → 复制账户名下方 2088 开头的商家 ID;`seller_app_id` 填生产 `appId`。',
|
|
461
|
+
'5. AI 按量付费验收:A2M 不使用 `notify_url`;上线前用生产应用私钥重新签名 402 账单,并验证 `Payment-Proof`、金额/订单/资源一致、`trade_no` 防重复履约和 `alipay.aipay.agent.fulfillment.confirm` 异步回执。'
|
|
453
462
|
];
|
|
454
463
|
}
|
|
455
|
-
if (productName === '
|
|
464
|
+
if (productName === 'AI 移动应用收款') {
|
|
456
465
|
return [
|
|
457
|
-
'3.
|
|
458
|
-
'4.
|
|
466
|
+
'3. AI 移动应用收款 `notify_url`:生成生产 `orderStr` 时传入商家已部署的公网 HTTPS `notify_url`;未传不会触发异步通知。',
|
|
467
|
+
'4. AI 移动应用收款验收:`notify_url` 完成验签,校验 `app_id`/`out_trade_no`/`total_amount`/`trade_status`,幂等处理并返回 `success`;支付结果以异步通知或 `alipay.trade.query` 补偿查询为准。客户端 SDK 使用服务端 `orderStr` 调起支付,客户端结果只做展示,不能作为付款成功依据。'
|
|
459
468
|
];
|
|
460
469
|
}
|
|
461
470
|
return [
|
|
462
|
-
'3.
|
|
463
|
-
'4.
|
|
464
|
-
'5.
|
|
471
|
+
'3. AI 网页应用收款 `notify_url`:生产下单时传入商家已部署的公网 HTTPS `notify_url`;该地址是商家后端通知地址,不是支付宝生成的 URL。',
|
|
472
|
+
'4. AI 网页应用收款验收:`notify_url` 完成验签,校验 `app_id`/`out_trade_no`/`total_amount`/`trade_status`,幂等处理并返回 `success`;支付结果以异步通知或 `alipay.trade.query` 补偿查询为准,同步回跳或前台页面不能作为付款成功依据。',
|
|
473
|
+
'5. AI 网页应用收款限制:如需解除 AI 网页应用收款的收款限制,请前往 支付宝商家平台 - 签约管理 补全收款信息:https://b.alipay.com/page/ar-center/ar-manage'
|
|
465
474
|
];
|
|
466
475
|
}
|
|
467
476
|
|
|
@@ -538,7 +547,7 @@ function validateVariantInputShape(messageId, variantName, variables) {
|
|
|
538
547
|
return;
|
|
539
548
|
}
|
|
540
549
|
if (Object.prototype.hasOwnProperty.call(variables, 'serviceStatus')) {
|
|
541
|
-
throw new Error(`onboarding.discovery.summary/${variantName} 禁止传入 serviceStatus
|
|
550
|
+
throw new Error(`onboarding.discovery.summary/${variantName} 禁止传入 serviceStatus 字段;AI 网页应用收款和 AI 移动应用收款的服务市场由模板固定为“无需处理”,请从 JSON 删除 serviceStatus 字段;允许变量:signingStatus、applicationStatus`);
|
|
542
551
|
}
|
|
543
552
|
}
|
|
544
553
|
}
|
|
@@ -573,12 +582,12 @@ const managedEntrypoints = {
|
|
|
573
582
|
reason: '产品已明确且 MCC 存在多个相似候选时由 runner 绑定本地 MCC 表生成候选事实,禁止手写候选表。'
|
|
574
583
|
},
|
|
575
584
|
'onboarding.discovery.summary': {
|
|
576
|
-
command: 'node ../normal/scripts/
|
|
577
|
-
reason: '
|
|
585
|
+
command: 'node ../normal/scripts/onboarding_discovery_runner.mjs run --product-type <aipay|webpay|apppay> --sales-code <SALES_CODE>',
|
|
586
|
+
reason: '该 runner 执行本轮适用查询、校验原始脚本事实、输出唯一 DISCOVERY_RESULT envelope,并在同一次 stdout 生成摘要;禁止单独重放摘要。'
|
|
578
587
|
},
|
|
579
588
|
'materials.category.collect': {
|
|
580
589
|
command: 'node ../normal/scripts/onboarding_message_runner.mjs material-collect --category <signing|service|application> --state <INITIAL|PARTIAL|INVALID|APP_MOBILE_INITIAL> --product-type <aipay|webpay|apppay>',
|
|
581
|
-
reason: '运行时禁止手写 categoryName/currentStatus/missingFields;runner
|
|
590
|
+
reason: '运行时禁止手写 categoryName/currentStatus/missingFields;runner 会归一化“签约资料”“AI 网页应用收款签约截图”“mobilePlatform”等常见别名后再调用 renderer。'
|
|
582
591
|
},
|
|
583
592
|
'application.candidates.facts': {
|
|
584
593
|
command: 'node ../normal/scripts/onboarding_message_runner.mjs application-candidates --application-type <WEBAPP|MOBILEAPP>',
|
|
@@ -598,7 +607,7 @@ const managedEntrypoints = {
|
|
|
598
607
|
},
|
|
599
608
|
'process.partial_result': {
|
|
600
609
|
command: 'node ../normal/scripts/onboarding_message_runner.mjs closeout',
|
|
601
|
-
reason: '产品开通 Step 6 最终收口必须由 runner 统一合并代码开发状态、产品开通分支状态、最近应用分支结果和上线前关键点;禁止 Agent
|
|
610
|
+
reason: '产品开通 Step 6 最终收口必须由 runner 统一合并代码开发状态、产品开通分支状态、最近应用分支结果和上线前关键点;禁止 Agent 手写、缩写或删减 AI 网页应用收款限制引导。'
|
|
602
611
|
},
|
|
603
612
|
'auth.page': {
|
|
604
613
|
command: 'bash modules/scripts/auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>,页面 stdout 独立送达后执行 bash modules/scripts/auth.sh open ...',
|
|
@@ -607,7 +616,7 @@ const managedEntrypoints = {
|
|
|
607
616
|
},
|
|
608
617
|
'auth.pending': {
|
|
609
618
|
command: 'bash modules/scripts/auth.sh wait|confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]',
|
|
610
|
-
reason: '授权待完成状态只能来自 auth.sh
|
|
619
|
+
reason: '授权待完成状态只能来自 auth.sh 有限轮询耗尽或登记肯定回复后的单次实际查询。',
|
|
611
620
|
allowedCallers: ['auth.sh']
|
|
612
621
|
},
|
|
613
622
|
'auth.expired': {
|
|
@@ -747,9 +756,9 @@ export function getMessageSchema(messageId, variantName = 'DEFAULT') {
|
|
|
747
756
|
for (const requiredName of ['serviceId', 'serviceName', 'serviceDescription', 'serviceUrl', 'servicePricing', 'serviceSchema']) {
|
|
748
757
|
setVariableRule(inputVariables, requiredName, { required: true });
|
|
749
758
|
}
|
|
750
|
-
setVariableRule(inputVariables, 'productName', { values: ['按量付费'] });
|
|
759
|
+
setVariableRule(inputVariables, 'productName', { values: ['AI 按量付费'] });
|
|
751
760
|
setVariableRule(inputVariables, 'actionTypes', { values: ['服务修改'] });
|
|
752
|
-
variantRules.push('SERVICE_UPDATE_ONLY
|
|
761
|
+
variantRules.push('SERVICE_UPDATE_ONLY 只用于 AI 按量付费服务修改;签约提交、服务创建、应用创建和应用提审不得进入该确认消息。');
|
|
753
762
|
}
|
|
754
763
|
|
|
755
764
|
return {
|
|
@@ -759,7 +768,7 @@ export function getMessageSchema(messageId, variantName = 'DEFAULT') {
|
|
|
759
768
|
owner: message.owner,
|
|
760
769
|
blocking: Boolean(message.blocking),
|
|
761
770
|
replyMode: message.replyMode || 'exact',
|
|
762
|
-
allowedReplies: message
|
|
771
|
+
allowedReplies: allowedRepliesFor(catalog, message),
|
|
763
772
|
inputHintType: message.inputHintType || null,
|
|
764
773
|
postRenderHintType: message.postRenderHintType || null,
|
|
765
774
|
managedEntrypoint: publicManagedEntrypoint(messageId),
|
|
@@ -801,11 +810,17 @@ export function validateCatalog() {
|
|
|
801
810
|
}
|
|
802
811
|
|
|
803
812
|
for (const message of catalog.messages) {
|
|
813
|
+
let allowedReplies = [];
|
|
814
|
+
try {
|
|
815
|
+
allowedReplies = allowedRepliesFor(catalog, message);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
errors.push(error.message);
|
|
818
|
+
}
|
|
804
819
|
if (messages.has(message.messageId)) errors.push(`重复 messageId: ${message.messageId}`);
|
|
805
820
|
messages.set(message.messageId, message);
|
|
806
821
|
if (!['catalog_owned', 'script_owned'].includes(message.owner)) errors.push(`${message.messageId}: owner 无效`);
|
|
807
|
-
if (message.blocking &&
|
|
808
|
-
errors.push(`${message.messageId}:
|
|
822
|
+
if (message.blocking && allowedReplies.length > 0 && !['confirm', 'authorizationComplete', 'publicKeyConfirmationComplete', 'complete'].includes(message.inputHintType)) {
|
|
823
|
+
errors.push(`${message.messageId}: 阻塞回复消息必须声明合法 inputHintType`);
|
|
809
824
|
}
|
|
810
825
|
if (message.postRenderHintType !== undefined && message.postRenderHintType !== 'scanReturn') {
|
|
811
826
|
errors.push(`${message.messageId}: postRenderHintType 无效`);
|
|
@@ -848,8 +863,8 @@ export function validateCatalog() {
|
|
|
848
863
|
if (!officialUrls.has(match[0])) errors.push(`${message.messageId}/${variantName}: 静态 URL 未登记 ${match[0]}`);
|
|
849
864
|
}
|
|
850
865
|
if (/<INTERNAL_/i.test(template)) errors.push(`${message.messageId}/${variantName}: 模板包含内部标签`);
|
|
851
|
-
if (message.blocking &&
|
|
852
|
-
errors.push(`${message.messageId}/${variantName}:
|
|
866
|
+
if (message.blocking && allowedReplies.length > 0 && /(?:回复|输入)\s*`?1`?/.test(template)) {
|
|
867
|
+
errors.push(`${message.messageId}/${variantName}: 阻塞输入提示必须由 renderer 统一追加`);
|
|
853
868
|
}
|
|
854
869
|
}
|
|
855
870
|
}
|
|
@@ -963,10 +978,10 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
963
978
|
if (messageId === 'integration.checklist.result') {
|
|
964
979
|
const sandboxPending = variables.sandboxConfigState !== 'READY';
|
|
965
980
|
const hasBlockingDefect = variables.blockingDefectState === 'PRESENT';
|
|
966
|
-
if (variantName === 'AIPAY' && !variables.sandboxResult) throw new Error('按量付费 checklist 缺少沙箱联调结果');
|
|
967
|
-
if (variantName !== 'AIPAY' && variables.sandboxResult) throw new Error('
|
|
968
|
-
if (variantName === 'WEBPAY' && !variables.webpayExperienceResult) throw new Error('
|
|
969
|
-
if (variantName !== 'WEBPAY' && variables.webpayExperienceResult) throw new Error('
|
|
981
|
+
if (variantName === 'AIPAY' && !variables.sandboxResult) throw new Error('AI 按量付费 checklist 缺少沙箱联调结果');
|
|
982
|
+
if (variantName !== 'AIPAY' && variables.sandboxResult) throw new Error('非 AI 按量付费 checklist 禁止传入 AI 按量付费沙箱结果');
|
|
983
|
+
if (variantName === 'WEBPAY' && !variables.webpayExperienceResult) throw new Error('AI 网页应用收款 checklist 缺少沙箱体验实际结果');
|
|
984
|
+
if (variantName !== 'WEBPAY' && variables.webpayExperienceResult) throw new Error('非 AI 网页应用收款 checklist 禁止传入 AI 网页应用收款沙箱体验结果');
|
|
970
985
|
const hasFailedItems = variables.failedItems !== '无';
|
|
971
986
|
const hasManualItems = variables.manualItems !== '无';
|
|
972
987
|
const manualOnly = !hasFailedItems && hasManualItems;
|
|
@@ -1002,17 +1017,17 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1002
1017
|
if (sandboxPending && !hasFailedItems) {
|
|
1003
1018
|
throw new Error('沙箱配置未完成时必须列入未通过或部分通过项');
|
|
1004
1019
|
}
|
|
1005
|
-
if (sandboxPending && variantName === 'AIPAY' && variables.sandboxResult !== '按量付费沙箱服务端联调未通过') {
|
|
1006
|
-
throw new Error('
|
|
1020
|
+
if (sandboxPending && variantName === 'AIPAY' && variables.sandboxResult !== 'AI 按量付费沙箱服务端联调未通过') {
|
|
1021
|
+
throw new Error('沙箱配置未完成时 AI 按量付费联调不得标记通过');
|
|
1007
1022
|
}
|
|
1008
1023
|
if (sandboxPending && variantName === 'WEBPAY' && variables.webpayExperienceResult !== '未提供浏览器付款入口或完整操作指引,需先补齐') {
|
|
1009
|
-
throw new Error('
|
|
1024
|
+
throw new Error('沙箱配置未完成时 AI 网页应用收款不得标记已交付付款体验');
|
|
1010
1025
|
}
|
|
1011
|
-
if (variantName === 'AIPAY' && variables.overallResult === '通过' && variables.sandboxResult !== '按量付费沙箱服务端联调通过') {
|
|
1012
|
-
throw new Error('按量付费代码开发通过必须有沙箱服务端联调通过结论');
|
|
1026
|
+
if (variantName === 'AIPAY' && variables.overallResult === '通过' && variables.sandboxResult !== 'AI 按量付费沙箱服务端联调通过') {
|
|
1027
|
+
throw new Error('AI 按量付费代码开发通过必须有沙箱服务端联调通过结论');
|
|
1013
1028
|
}
|
|
1014
1029
|
if (variantName === 'WEBPAY' && variables.overallResult === '通过' && variables.webpayExperienceResult === '未提供浏览器付款入口或完整操作指引,需先补齐') {
|
|
1015
|
-
throw new Error('
|
|
1030
|
+
throw new Error('AI 网页应用收款未交付付款入口时不得标记代码开发通过');
|
|
1016
1031
|
}
|
|
1017
1032
|
if (manualOnly) {
|
|
1018
1033
|
normalized.failedItems = `人工待验证项未完成:${normalized.manualItems}`;
|
|
@@ -1038,6 +1053,16 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1038
1053
|
normalized.paymentPageHint = variables.missingFields.split('、').includes('支付页截图')
|
|
1039
1054
|
? definitions.get('paymentPageHint')?.values?.[0] || ''
|
|
1040
1055
|
: '';
|
|
1056
|
+
normalized.serviceUrlHint = variantName === 'INVALID'
|
|
1057
|
+
&& variables.categoryName === '服务资料'
|
|
1058
|
+
&& variables.missingFields.split('、').includes('服务地址')
|
|
1059
|
+
? definitions.get('serviceUrlHint')?.values?.[0] || ''
|
|
1060
|
+
: '';
|
|
1061
|
+
normalized.servicePricingHint = variantName === 'INVALID'
|
|
1062
|
+
&& variables.categoryName === '服务资料'
|
|
1063
|
+
&& variables.missingFields.split('、').includes('服务单价')
|
|
1064
|
+
? definitions.get('servicePricingHint')?.values?.[0] || ''
|
|
1065
|
+
: '';
|
|
1041
1066
|
}
|
|
1042
1067
|
if (messageId === 'process.partial_result') {
|
|
1043
1068
|
const statusQueryHintDefinition = definitions.get('statusQueryHint');
|
|
@@ -1126,13 +1151,13 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1126
1151
|
} else {
|
|
1127
1152
|
throw new Error('full_process 进入产品开通收口时只允许已完成、沙箱待配置或人工待验证');
|
|
1128
1153
|
}
|
|
1129
|
-
if (variables.productName === '按量付费' && variables.serviceResult === '无需处理') {
|
|
1130
|
-
throw new Error('按量付费产品开通不能把服务市场标记为无需处理');
|
|
1154
|
+
if (variables.productName === 'AI 按量付费' && variables.serviceResult === '无需处理') {
|
|
1155
|
+
throw new Error('AI 按量付费产品开通不能把服务市场标记为无需处理');
|
|
1131
1156
|
}
|
|
1132
|
-
if (variables.productName !== '按量付费' && variables.serviceResult !== '无需处理') {
|
|
1133
|
-
throw new Error('
|
|
1157
|
+
if (variables.productName !== 'AI 按量付费' && variables.serviceResult !== '无需处理') {
|
|
1158
|
+
throw new Error('AI 网页应用收款或 AI 移动应用收款的服务市场结果必须为无需处理');
|
|
1134
1159
|
}
|
|
1135
|
-
const expectedServiceComplete = variables.productName === '按量付费'
|
|
1160
|
+
const expectedServiceComplete = variables.productName === 'AI 按量付费'
|
|
1136
1161
|
? ['已复用', '创建成功,状态未取得', '修改成功,状态未取得', '已完成'].includes(variables.serviceResult)
|
|
1137
1162
|
: variables.serviceResult === '无需处理';
|
|
1138
1163
|
const productBranchesComplete =
|
|
@@ -1161,7 +1186,7 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1161
1186
|
if (messageId === 'process.partial_result' || messageId === 'error.backend' || messageId === 'error.cli') {
|
|
1162
1187
|
rendered = normalizeProcessPartialResultSpacing(rendered);
|
|
1163
1188
|
}
|
|
1164
|
-
if (message.blocking &&
|
|
1189
|
+
if (message.blocking && allowedRepliesFor(catalog, message).length > 0) {
|
|
1165
1190
|
const hintKeys = {
|
|
1166
1191
|
confirm: 'confirmInputHint',
|
|
1167
1192
|
authorizationComplete: 'authorizationCompletionInputHint',
|
|
@@ -35,7 +35,14 @@ const permissionedFlushNetworkErrors = new Set([
|
|
|
35
35
|
'NETWORK_UNREACHABLE'
|
|
36
36
|
]);
|
|
37
37
|
const productByVariant = { AIPAY: 'AIPAY', WEBPAY: 'WEBPAY', APPPAY: 'APPPAY' };
|
|
38
|
-
const productByName = {
|
|
38
|
+
const productByName = {
|
|
39
|
+
'AI 按量付费': 'AIPAY',
|
|
40
|
+
'AI 网页应用收款': 'WEBPAY',
|
|
41
|
+
'AI 移动应用收款': 'APPPAY',
|
|
42
|
+
按量付费: 'AIPAY',
|
|
43
|
+
网站支付: 'WEBPAY',
|
|
44
|
+
'APP 支付': 'APPPAY'
|
|
45
|
+
};
|
|
39
46
|
const productAliases = {
|
|
40
47
|
aipay: 'AIPAY',
|
|
41
48
|
webpay: 'WEBPAY',
|
|
@@ -394,9 +401,44 @@ export function emitMetricEvent(event, { env = process.env, stream = process.std
|
|
|
394
401
|
return true;
|
|
395
402
|
}
|
|
396
403
|
|
|
397
|
-
export function emitStageMetric(stage, context = {}) {
|
|
404
|
+
export function emitStageMetric(stage, context = {}, env = process.env) {
|
|
398
405
|
try {
|
|
399
|
-
return emitMetricEvent(stageMetricEvent(stage, context));
|
|
406
|
+
return emitMetricEvent(stageMetricEvent(stage, context, Date.now(), env), { env });
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function emitStageMetricAsync(stage, context = {}, env = process.env) {
|
|
413
|
+
try {
|
|
414
|
+
if (!telemetryEnabled(env) || !stageRules.has(stage)) return false;
|
|
415
|
+
if (normalizeSwitch(env.ALIPAY_AIPAY_TEST_MODE) === '1') {
|
|
416
|
+
return emitStageMetric(stage, context, env);
|
|
417
|
+
}
|
|
418
|
+
const args = [scriptPath(), 'stage', stage, '--timestamp-ms', String(Date.now())];
|
|
419
|
+
const options = [
|
|
420
|
+
['run-id', context.runId || context.run_id],
|
|
421
|
+
['root-intent', context.rootIntent || context.root_intent],
|
|
422
|
+
['product-type', context.productType || context.product_type],
|
|
423
|
+
['product-name', context.productName]
|
|
424
|
+
];
|
|
425
|
+
for (const [name, value] of options) {
|
|
426
|
+
if (typeof value === 'string' && value) args.push(`--${name}`, value);
|
|
427
|
+
}
|
|
428
|
+
const child = spawn(process.execPath, args, {
|
|
429
|
+
detached: true,
|
|
430
|
+
stdio: 'ignore',
|
|
431
|
+
env
|
|
432
|
+
});
|
|
433
|
+
child.on('error', (error) => {
|
|
434
|
+
recordLocalTelemetryFailure(
|
|
435
|
+
safeDimension(error?.code || error?.name || 'TELEMETRY_STAGE_SPAWN_FAILED', 'TELEMETRY_STAGE_SPAWN_FAILED', 80),
|
|
436
|
+
safeDimension(error?.message || 'failed to start telemetry stage process', 'failed to start telemetry stage process', 240),
|
|
437
|
+
env
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
child.unref();
|
|
441
|
+
return true;
|
|
400
442
|
} catch {
|
|
401
443
|
return false;
|
|
402
444
|
}
|
|
@@ -451,6 +493,13 @@ function contextFromCliArgs(args) {
|
|
|
451
493
|
};
|
|
452
494
|
}
|
|
453
495
|
|
|
496
|
+
function timestampFromCliArgs(args) {
|
|
497
|
+
if (args['timestamp-ms'] === undefined) return Date.now();
|
|
498
|
+
const value = Number(args['timestamp-ms']);
|
|
499
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error('--timestamp-ms 必须是正整数毫秒时间戳');
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
|
|
454
503
|
function assertCliArgs(args, allowedOptions, positionalCount) {
|
|
455
504
|
for (const name of Object.keys(args)) {
|
|
456
505
|
if (name !== '_' && !allowedOptions.has(name)) throw new Error(`不支持参数 --${name}`);
|
|
@@ -1257,8 +1306,8 @@ export async function runTelemetryCli(argv = process.argv) {
|
|
|
1257
1306
|
emitTelemetryError(new Error('invalid telemetry arguments'));
|
|
1258
1307
|
return 0;
|
|
1259
1308
|
}
|
|
1260
|
-
assertCliArgs(args, new Set(['run-id', 'root-intent', 'product-type', 'product-name']), 1);
|
|
1261
|
-
const event = stageMetricEvent(args._[0], contextFromCliArgs(args));
|
|
1309
|
+
assertCliArgs(args, new Set(['run-id', 'root-intent', 'product-type', 'product-name', 'timestamp-ms']), 1);
|
|
1310
|
+
const event = stageMetricEvent(args._[0], contextFromCliArgs(args), timestampFromCliArgs(args));
|
|
1262
1311
|
if (event) updateExistingRunStateFromEvent(event);
|
|
1263
1312
|
emitMetricEvent(event);
|
|
1264
1313
|
return 0;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 遥测维护参考
|
|
2
|
+
|
|
3
|
+
本文件只供维护、测试和故障排查按需读取;业务运行默认只执行 `runtime-entry.md` 与动作 runner/script 登记的入口。
|
|
4
|
+
|
|
5
|
+
## 运行契约
|
|
6
|
+
|
|
7
|
+
- 固定 stage 枚举、Yuyan 字段和低敏维度以 `scripts/telemetry.mjs` 的 `approvedStages`、事件构造器和测试为准。
|
|
8
|
+
- `start-run` 创建或复用 `run_id`;动作 runner/script 使用异步子进程记录 stage。缺少 run、遥测关闭、落盘失败、网络失败和非 2xx 都不得改变业务 stdout、退出语义或 flow 转移。
|
|
9
|
+
- 前台只写用户系统临时目录下带 `alipay-aipay` 标识的私有 outbox,并启动后台补传;collector 返回 HTTP 2xx 后才删除记录。维护/测试进程不复用默认 run state,默认不远端上传。
|
|
10
|
+
- `PLATFORM_ID` 必须等于当前 `run_id`;模型维度只读 `AIPAY_MODEL`,禁止读取用户项目通用 `MODEL` 推断。
|
|
11
|
+
- 遥测内部行统一使用 `ALIPAY_AIPAY_INTERNAL:` 前缀,禁止对客。
|
|
12
|
+
|
|
13
|
+
## Run 生命周期
|
|
14
|
+
|
|
15
|
+
- 根入口用 `root_intent + product_type` 判定复用或新建;组合不变时不按经过时长失效,`UNKNOWN` 补全为明确产品仍复用同一 `run_id`。只有明确换产品、切换根意图或已有 run 终态才重建;用户只说“重新开始/重新走流程/作废本轮流程”但组合没有变化时继续复用。
|
|
16
|
+
- 若宿主提供 `COZE_PROJECT_ID`、`MEOO_PROJECT_ID`,或 `MIAODA` 环境下的 `VITE_APP_ID`/`PLATFORM_ID`,新建 run 直接使用该项目 ID。当前宿主项目 ID 与已有状态不兼容时静默跳过不兼容事件且不改写 run state。
|
|
17
|
+
- `integration_only` 由 checklist 最终消息终止;`full_process` 的 Integration 收口不终止,和 `onboarding_only` 一样由 Onboarding closeout 终止。
|
|
18
|
+
- 查看当前主键只使用 `current-run-id`,不得为查看而新建 run。业务脚本继续继承 `PLATFORM_ID=<run_id>`;Agent 不手写环境变量前缀或改写 MCP payload。
|
|
19
|
+
|
|
20
|
+
## 诊断与补传
|
|
21
|
+
|
|
22
|
+
`node scripts/telemetry.mjs upload-status` 只读取内部诊断状态。网络类失败且 outbox 有积压时,先执行 `node scripts/telemetry.mjs permissioned-flush-needed`;只有内部事实为 `PERMISSIONED_FLUSH_NEEDED=true` 时才申请一次可复用联网权限运行 `node scripts/telemetry.mjs flush-outbox`。同一 Agent 会话只请求一次;已取得权限后可在后续阶段旁路触发同一异步命令。`flush-outbox` 只启动后台补传并立即返回;用户拒绝、后台失败或非 2xx 都保留 outbox 并继续业务。collector HTTP 结果、失败或待重试结果不得作为业务转移或完成条件。
|
|
23
|
+
|
|
24
|
+
## 修改要求
|
|
25
|
+
|
|
26
|
+
修改 stage、run 生命周期、outbox 或上传逻辑时,必须覆盖遥测关闭、缺少 run、写盘失败、无网络、非 2xx、并发 claim、终态 run 和 `PLATFORM_ID` 继承测试。不得把实现细节复制回 `runtime-entry.md` 或两个主 flow。
|