@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.
Files changed (55) hide show
  1. package/package.json +5 -2
  2. package/skill/SKILL.md +2 -2
  3. package/skill/VERSION +1 -1
  4. package/skill/references/integration/flow.md +109 -41
  5. package/skill/references/integration/modules/checklist.md +25 -17
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +3 -4
  12. package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +4 -4
  13. package/skill/references/integration/modules/interface-contract/common-trade-contract.md +1 -1
  14. package/skill/references/integration/modules/interface-contract/notify-url-contract.md +4 -4
  15. package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +4 -4
  16. package/skill/references/integration/modules/interface-guide.md +15 -13
  17. package/skill/references/integration/modules/product-decision.md +13 -13
  18. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +2 -2
  19. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +1 -1
  20. package/skill/references/integration/modules/scripts/sandbox_config.sh +1 -1
  21. package/skill/references/integration/modules/troubleshooting-flow.md +3 -3
  22. package/skill/references/normal/alipay-cli-env.md +2 -2
  23. package/skill/references/normal/full-process-routing.md +7 -5
  24. package/skill/references/normal/json/customer-messages.json +56 -51
  25. package/skill/references/normal/json/policy-rules.json +6 -6
  26. package/skill/references/normal/rejection-guide.md +1 -1
  27. package/skill/references/normal/scripts/alipay_cli_env.mjs +6 -2
  28. package/skill/references/normal/scripts/alipay_cli_env.sh +36 -2
  29. package/skill/references/normal/scripts/common.sh +64 -0
  30. package/skill/references/normal/scripts/flow_loader.mjs +241 -0
  31. package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
  32. package/skill/references/normal/scripts/integration_message_runner.mjs +237 -15
  33. package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
  34. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
  35. package/skill/references/normal/scripts/onboarding_message_runner.mjs +27 -12
  36. package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
  37. package/skill/references/normal/scripts/project_route_inspector.sh +3 -3
  38. package/skill/references/normal/scripts/python_script_runner.mjs +1 -1
  39. package/skill/references/normal/scripts/render_customer_message.mjs +67 -42
  40. package/skill/references/normal/scripts/telemetry.mjs +54 -5
  41. package/skill/references/normal/telemetry.md +26 -0
  42. package/skill/references/onboarding/flow.md +166 -152
  43. package/skill/references/onboarding/modules/app-release.md +39 -386
  44. package/skill/references/onboarding/modules/authorization.md +25 -270
  45. package/skill/references/onboarding/modules/error-handling.md +50 -253
  46. package/skill/references/onboarding/modules/product-sign.md +24 -289
  47. package/skill/references/onboarding/modules/scripts/app.sh +58 -21
  48. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +15 -12
  49. package/skill/references/onboarding/modules/scripts/auth.sh +6 -1
  50. package/skill/references/onboarding/modules/scripts/error_handler.sh +1 -0
  51. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +5 -3
  52. package/skill/references/onboarding/modules/scripts/service.sh +3 -2
  53. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +24 -12
  54. package/skill/references/onboarding/modules/service-registration.md +20 -312
  55. 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: '网站支付', APPPAY: 'APP 支付' };
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 === 'APP 支付') {
464
+ if (productName === 'AI 移动应用收款') {
456
465
  return [
457
- '3. APP 支付 `notify_url`:生成生产 `orderStr` 时传入商家已部署的公网 HTTPS `notify_url`;未传不会触发异步通知。',
458
- '4. APP 支付验收:`notify_url` 完成验签,校验 `app_id`/`out_trade_no`/`total_amount`/`trade_status`,幂等处理并返回 `success`;支付结果以异步通知或 `alipay.trade.query` 补偿查询为准。客户端 SDK 使用服务端 `orderStr` 调起支付,客户端结果只做展示,不能作为付款成功依据。'
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. 网站支付 `notify_url`:生产下单时传入商家已部署的公网 HTTPS `notify_url`;该地址是商家后端通知地址,不是支付宝生成的 URL。',
463
- '4. 网站支付验收:`notify_url` 完成验签,校验 `app_id`/`out_trade_no`/`total_amount`/`trade_status`,幂等处理并返回 `success`;支付结果以异步通知或 `alipay.trade.query` 补偿查询为准,同步回跳或前台页面不能作为付款成功依据。',
464
- '5. 网站支付收款限制:如需解除网站支付的收款限制,请前往 [支付宝商家平台 - 签约管理](https://b.alipay.com/page/ar-center/ar-manage) 补全网站支付信息。'
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 字段;网站支付和 APP 支付的服务市场由模板固定为“无需处理”,请从 JSON 删除 serviceStatus 字段;允许变量:signingStatus、applicationStatus`);
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/onboarding_message_runner.mjs discovery-summary --product-type <aipay|webpay|apppay>',
577
- reason: '运行时只传脚本原始 signStatus/serviceFlow/appFlow,由 runner 映射枚举并处理网站支付/APP 支付禁用 serviceStatus 的变体规则。'
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 会归一化“签约资料”“网站支付签约截图”“mobilePlatform”等常见别名后再调用 renderer。'
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 有限轮询耗尽或用户输入 1 后的单次实际查询。',
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.allowedReplies || [],
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 && JSON.stringify(message.allowedReplies) === '["1"]' && !['confirm', 'authorizationComplete', 'publicKeyConfirmationComplete', 'complete'].includes(message.inputHintType)) {
808
- errors.push(`${message.messageId}: 单一确认消息必须声明合法 inputHintType`);
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 && JSON.stringify(message.allowedReplies) === '["1"]' && /(?:回复|输入)\s*`?1`?/.test(template)) {
852
- errors.push(`${message.messageId}/${variantName}: 单一确认输入提示必须由 renderer 统一追加`);
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('非按量付费 checklist 禁止传入按量付费沙箱结果');
968
- if (variantName === 'WEBPAY' && !variables.webpayExperienceResult) throw new Error('网站支付 checklist 缺少沙箱体验实际结果');
969
- if (variantName !== 'WEBPAY' && variables.webpayExperienceResult) throw new Error('非网站支付 checklist 禁止传入网站支付沙箱体验结果');
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('网站支付或 APP 支付的服务市场结果必须为无需处理');
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 && JSON.stringify(message.allowedReplies) === '["1"]') {
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 = { '按量付费': 'AIPAY', '网站支付': 'WEBPAY', 'APP 支付': 'APPPAY' };
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。