@alipay/alipay-aipay 1.7.0 → 1.7.2

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 (169) hide show
  1. package/README.md +30 -0
  2. package/dist/commands/install-current.js +15 -7
  3. package/dist/commands/install.js +20 -16
  4. package/dist/commands/uninstall.js +8 -3
  5. package/dist/commands/update.js +34 -16
  6. package/dist/index.js +10 -4
  7. package/dist/utils/channels.js +67 -0
  8. package/dist/utils/config.js +15 -11
  9. package/dist/utils/file.js +23 -10
  10. package/dist/utils/npm.js +19 -33
  11. package/package.json +8 -3
  12. package/skill/SKILL.md +2 -0
  13. package/skill/references/normal/json/release-channel.json +1 -0
  14. package/skill/references/normal/scripts/self_update.mjs +62 -17
  15. package/skill/references/normal/self-update.md +3 -1
  16. package/skill-pre/SKILL.md +65 -0
  17. package/skill-pre/VERSION +1 -0
  18. package/skill-pre/references/integration/flow.md +602 -0
  19. package/skill-pre/references/integration/modules/alipay-sdk-reminder.md +281 -0
  20. package/skill-pre/references/integration/modules/checklist.md +154 -0
  21. package/skill-pre/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
  22. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +68 -0
  23. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +83 -0
  24. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +80 -0
  25. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +73 -0
  26. package/skill-pre/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 +62 -0
  27. package/skill-pre/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 +132 -0
  28. package/skill-pre/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +640 -0
  29. package/skill-pre/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
  30. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +72 -0
  31. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +96 -0
  32. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +81 -0
  33. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +77 -0
  34. package/skill-pre/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 +69 -0
  35. package/skill-pre/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 +138 -0
  36. package/skill-pre/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.md +623 -0
  37. package/skill-pre/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
  38. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +51 -0
  39. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +63 -0
  40. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +59 -0
  41. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +54 -0
  42. package/skill-pre/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 +84 -0
  43. package/skill-pre/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 +71 -0
  44. package/skill-pre/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +498 -0
  45. package/skill-pre/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
  46. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +59 -0
  47. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +73 -0
  48. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +69 -0
  49. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
  50. package/skill-pre/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 +59 -0
  51. package/skill-pre/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 +110 -0
  52. package/skill-pre/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +806 -0
  53. package/skill-pre/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
  54. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +57 -0
  55. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +70 -0
  56. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
  57. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +62 -0
  58. package/skill-pre/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 +51 -0
  59. package/skill-pre/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 +47 -0
  60. package/skill-pre/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +541 -0
  61. package/skill-pre/references/integration/modules/interface-contract/aipay-interface-contract.md +52 -0
  62. package/skill-pre/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  63. package/skill-pre/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  64. package/skill-pre/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  65. package/skill-pre/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  66. package/skill-pre/references/integration/modules/interface-guide.md +61 -0
  67. package/skill-pre/references/integration/modules/product-decision.md +73 -0
  68. package/skill-pre/references/integration/modules/sandbox/a2m-sandbox-test.md +235 -0
  69. package/skill-pre/references/integration/modules/sandbox/alipay-sandbox-tool.md +142 -0
  70. package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +239 -0
  71. package/skill-pre/references/integration/modules/scripts/local_402_sandbox_pay.py +900 -0
  72. package/skill-pre/references/integration/modules/scripts/sandbox_config.sh +347 -0
  73. package/skill-pre/references/integration/modules/troubleshooting-flow.md +28 -0
  74. package/skill-pre/references/normal/alipay-cli-env.md +169 -0
  75. package/skill-pre/references/normal/conversation-telemetry.md +101 -0
  76. package/skill-pre/references/normal/full-process-routing.md +92 -0
  77. package/skill-pre/references/normal/integration-worker.md +90 -0
  78. package/skill-pre/references/normal/json/customer-messages.json +1 -0
  79. package/skill-pre/references/normal/json/managed-jq-assets.json +1 -0
  80. package/skill-pre/references/normal/json/policy-rules.json +1 -0
  81. package/skill-pre/references/normal/json/release-channel.json +1 -0
  82. package/skill-pre/references/normal/rejection-guide.md +41 -0
  83. package/skill-pre/references/normal/scripts/agent_platform.mjs +43 -0
  84. package/skill-pre/references/normal/scripts/alipay_cli_env.mjs +34 -0
  85. package/skill-pre/references/normal/scripts/alipay_cli_env.sh +55 -0
  86. package/skill-pre/references/normal/scripts/alipay_cli_refresh.mjs +129 -0
  87. package/skill-pre/references/normal/scripts/auth_runtime_runner.mjs +60 -0
  88. package/skill-pre/references/normal/scripts/checklist_sections.mjs +43 -0
  89. package/skill-pre/references/normal/scripts/common.sh +365 -0
  90. package/skill-pre/references/normal/scripts/conversation_telemetry.mjs +105 -0
  91. package/skill-pre/references/normal/scripts/detect_dev_tool.sh +79 -0
  92. package/skill-pre/references/normal/scripts/flow_loader.mjs +237 -0
  93. package/skill-pre/references/normal/scripts/integration_context_runner.mjs +151 -0
  94. package/skill-pre/references/normal/scripts/integration_message_runner.mjs +1456 -0
  95. package/skill-pre/references/normal/scripts/interaction_reply.mjs +140 -0
  96. package/skill-pre/references/normal/scripts/managed_jq.mjs +215 -0
  97. package/skill-pre/references/normal/scripts/onboarding_discovery_runner.mjs +387 -0
  98. package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +1425 -0
  99. package/skill-pre/references/normal/scripts/onboarding_recovery_runner.mjs +100 -0
  100. package/skill-pre/references/normal/scripts/open_official_url.sh +205 -0
  101. package/skill-pre/references/normal/scripts/platform_compat.mjs +42 -0
  102. package/skill-pre/references/normal/scripts/preview_runtime.mjs +151 -0
  103. package/skill-pre/references/normal/scripts/project_route_inspector.sh +132 -0
  104. package/skill-pre/references/normal/scripts/python_script_runner.mjs +58 -0
  105. package/skill-pre/references/normal/scripts/render_customer_message.mjs +1792 -0
  106. package/skill-pre/references/normal/scripts/runtime.mjs +271 -0
  107. package/skill-pre/references/normal/scripts/self_update.mjs +376 -0
  108. package/skill-pre/references/normal/scripts/telemetry.mjs +2102 -0
  109. package/skill-pre/references/normal/self-update.md +7 -0
  110. package/skill-pre/references/normal/telemetry.md +70 -0
  111. package/skill-pre/references/onboarding/assets/keygen.html +927 -0
  112. package/skill-pre/references/onboarding/flow.md +607 -0
  113. package/skill-pre/references/onboarding/modules/app-release.md +90 -0
  114. package/skill-pre/references/onboarding/modules/authorization.md +43 -0
  115. package/skill-pre/references/onboarding/modules/error-handling.md +62 -0
  116. package/skill-pre/references/onboarding/modules/product-sign.md +49 -0
  117. package/skill-pre/references/onboarding/modules/scripts/app.sh +1485 -0
  118. package/skill-pre/references/onboarding/modules/scripts/ar_sign_apply.sh +331 -0
  119. package/skill-pre/references/onboarding/modules/scripts/auth.sh +1040 -0
  120. package/skill-pre/references/onboarding/modules/scripts/download_key_tool.sh +4 -0
  121. package/skill-pre/references/onboarding/modules/scripts/error_handler.sh +841 -0
  122. package/skill-pre/references/onboarding/modules/scripts/mcc.sh +1085 -0
  123. package/skill-pre/references/onboarding/modules/scripts/network_retry.sh +135 -0
  124. package/skill-pre/references/onboarding/modules/scripts/query_sign_status.sh +105 -0
  125. package/skill-pre/references/onboarding/modules/scripts/service.sh +528 -0
  126. package/skill-pre/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  127. package/skill-pre/references/onboarding/modules/scripts/upload_screenshots.sh +208 -0
  128. package/skill-pre/references/onboarding/modules/scripts/upload_special_license.sh +195 -0
  129. package/skill-pre/references/onboarding/modules/service-registration.md +40 -0
  130. package/skill-pre/references/runtime-entry.md +99 -0
  131. package/dist/commands/install-current.d.ts +0 -4
  132. package/dist/commands/install-current.d.ts.map +0 -1
  133. package/dist/commands/install-current.js.map +0 -1
  134. package/dist/commands/install.d.ts +0 -10
  135. package/dist/commands/install.d.ts.map +0 -1
  136. package/dist/commands/install.js.map +0 -1
  137. package/dist/commands/prepare-tools.d.ts +0 -5
  138. package/dist/commands/prepare-tools.d.ts.map +0 -1
  139. package/dist/commands/prepare-tools.js.map +0 -1
  140. package/dist/commands/uninstall.d.ts +0 -10
  141. package/dist/commands/uninstall.d.ts.map +0 -1
  142. package/dist/commands/uninstall.js.map +0 -1
  143. package/dist/commands/update.d.ts +0 -10
  144. package/dist/commands/update.d.ts.map +0 -1
  145. package/dist/commands/update.js.map +0 -1
  146. package/dist/index.d.ts +0 -2
  147. package/dist/index.d.ts.map +0 -1
  148. package/dist/index.js.map +0 -1
  149. package/dist/types.d.ts +0 -42
  150. package/dist/types.d.ts.map +0 -1
  151. package/dist/types.js.map +0 -1
  152. package/dist/utils/alipay-cli.d.ts +0 -20
  153. package/dist/utils/alipay-cli.d.ts.map +0 -1
  154. package/dist/utils/alipay-cli.js.map +0 -1
  155. package/dist/utils/config.d.ts +0 -52
  156. package/dist/utils/config.d.ts.map +0 -1
  157. package/dist/utils/config.js.map +0 -1
  158. package/dist/utils/file.d.ts +0 -77
  159. package/dist/utils/file.d.ts.map +0 -1
  160. package/dist/utils/file.js.map +0 -1
  161. package/dist/utils/jq.d.ts +0 -17
  162. package/dist/utils/jq.d.ts.map +0 -1
  163. package/dist/utils/jq.js.map +0 -1
  164. package/dist/utils/npm.d.ts +0 -17
  165. package/dist/utils/npm.d.ts.map +0 -1
  166. package/dist/utils/npm.js.map +0 -1
  167. package/dist/utils/shell.d.ts +0 -24
  168. package/dist/utils/shell.d.ts.map +0 -1
  169. package/dist/utils/shell.js.map +0 -1
@@ -0,0 +1,1085 @@
1
+ #!/bin/bash
2
+ #=============================================================================
3
+ # 脚本名称: mcc.sh
4
+ # 功能描述: 查询当前产品支持的 MCC 经营类目并托管 MCC 匹配与方案展示
5
+ # 调用位置: Step 1 方案规划
6
+ # 用法:
7
+ # bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--semantic-terms-json <JSON数组>] [--agent-candidate-refs-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]
8
+ #=============================================================================
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ if [ -f "${SCRIPT_DIR}/error_handler.sh" ]; then
13
+ # shellcheck source=/dev/null
14
+ source "${SCRIPT_DIR}/error_handler.sh"
15
+ else
16
+ echo "❌ 缺少错误处理脚本: ${SCRIPT_DIR}/error_handler.sh"
17
+ exit 1
18
+ fi
19
+ # shellcheck source=/dev/null
20
+ source "${SCRIPT_DIR}/network_retry.sh"
21
+
22
+ ONBOARDING_RUNNER="${SCRIPT_DIR}/../../../normal/scripts/onboarding_message_runner.mjs"
23
+ MCC_SEMANTIC_RETRY_LIMIT=2
24
+
25
+ require_command jq || exit 1
26
+ require_command node || exit 1
27
+ require_command alipay-cli || exit 1
28
+ require_command mktemp || exit 1
29
+
30
+ product_sales_code() {
31
+ case "$1" in
32
+ aipay) printf 'I1080300001000160457' ;;
33
+ webpay) printf 'I1080300001000041203' ;;
34
+ apppay) printf 'I1080300001000041313' ;;
35
+ *) return 1 ;;
36
+ esac
37
+ }
38
+
39
+ extract_business_json() {
40
+ local raw="$1"
41
+ local business
42
+ business=$(unwrap_mcp "$raw")
43
+ printf '%s' "$business" | jq -ce 'select(type == "object")' 2>/dev/null
44
+ }
45
+
46
+ capture_or_exit() {
47
+ local __result_var="$1" output rc
48
+ shift
49
+ output=$("$@")
50
+ rc=$?
51
+ if [ "$rc" -ne 0 ]; then
52
+ [ -z "$output" ] || printf '%s\n' "$output"
53
+ exit "$rc"
54
+ fi
55
+ printf -v "$__result_var" '%s' "$output"
56
+ }
57
+
58
+ network_failure() {
59
+ echo "❌ MCC 经营类目查询因网络或服务异常在自动重试两次后仍未恢复"
60
+ emit_control "MCC_FLOW:RETRY_WITH_NETWORK"
61
+ exit 1
62
+ }
63
+
64
+ mcc_query_failed() {
65
+ echo "❌ MCC 经营类目查询结果暂时无法处理,请稍后重试。"
66
+ emit_control "MCC_QUERY_RESULT=FAILED"
67
+ exit 1
68
+ }
69
+
70
+ ensure_mcc_cli_success() {
71
+ local error_type
72
+ error_type=$(detect_error "$1" "MCP_AUTHENTICATED_CALL")
73
+ [ "$error_type" = "SUCCESS" ]
74
+ }
75
+
76
+ mcc_cache_ttl_seconds() {
77
+ local value="${ALIPAY_AIPAY_MCC_TREE_CACHE_TTL_SECONDS:-1800}"
78
+ case "$value" in
79
+ ''|*[!0-9]*) printf '1800' ;;
80
+ *) printf '%s' "$value" ;;
81
+ esac
82
+ }
83
+
84
+ mcc_cache_scope_id() {
85
+ local candidate="${ALIPAY_AIPAY_RUN_ID:-${ALIPAY_AIPAY_FLOW_RUN_ID:-}}" marker telemetry_script
86
+ if valid_platform_id "$candidate"; then
87
+ printf '%s' "$candidate"
88
+ return 0
89
+ fi
90
+ telemetry_script="${SCRIPT_DIR}/../../../normal/scripts/telemetry.mjs"
91
+ if [ -f "$telemetry_script" ] && command -v node >/dev/null 2>&1; then
92
+ marker="$(node "$telemetry_script" current-run-id 2>&1 >/dev/null || true)"
93
+ candidate="$(printf '%s\n' "$marker" | sed -n 's/^ALIPAY_AIPAY_INTERNAL:RUN_ID=//p' | head -n 1)"
94
+ if valid_platform_id "$candidate"; then
95
+ printf '%s' "$candidate"
96
+ return 0
97
+ fi
98
+ fi
99
+ return 1
100
+ }
101
+
102
+ mcc_cache_root() {
103
+ if [ -n "${ALIPAY_AIPAY_MCC_CACHE_DIR:-}" ]; then
104
+ case "$ALIPAY_AIPAY_MCC_CACHE_DIR" in
105
+ /*) printf '%s' "$ALIPAY_AIPAY_MCC_CACHE_DIR"; return 0 ;;
106
+ *) return 1 ;;
107
+ esac
108
+ fi
109
+ if [ -n "${XDG_CACHE_HOME:-}" ]; then
110
+ case "$XDG_CACHE_HOME" in
111
+ /*) printf '%s/alipay-aipay/mcc-query-tree' "$XDG_CACHE_HOME"; return 0 ;;
112
+ *) return 1 ;;
113
+ esac
114
+ fi
115
+ case "${HOME:-}" in
116
+ /*) [ "$HOME" != "/" ] || return 1; printf '%s/.cache/alipay-aipay/mcc-query-tree' "$HOME" ;;
117
+ *) return 1 ;;
118
+ esac
119
+ }
120
+
121
+ mcc_cache_component() {
122
+ printf '%s' "$1" | LC_ALL=C tr -c 'A-Za-z0-9._-' '_'
123
+ }
124
+
125
+ mcc_cache_file() {
126
+ local product_type="$1" sales_code="$2" root scope
127
+ root="$(mcc_cache_root)" || return 1
128
+ scope="$(mcc_cache_scope_id)" || return 1
129
+ if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
130
+ return 1
131
+ fi
132
+ mkdir -p "$root" 2>/dev/null || return 1
133
+ chmod 700 "$root" 2>/dev/null || true
134
+ printf '%s/%s.%s.%s.json' \
135
+ "$root" \
136
+ "$(mcc_cache_component "$scope")" \
137
+ "$(mcc_cache_component "$product_type")" \
138
+ "$(mcc_cache_component "$sales_code")"
139
+ }
140
+
141
+ mcc_selection_cache_file() {
142
+ local product_type="$1" sales_code="$2" receipt="$3" root scope digest
143
+ root="$(mcc_cache_root)" || return 1
144
+ scope="$(mcc_cache_scope_id)" || return 1
145
+ case "$receipt" in
146
+ AIPAY_MCC_SELECTION_V1.*.*) ;;
147
+ *) return 1 ;;
148
+ esac
149
+ digest="${receipt##*.}"
150
+ case "$digest" in
151
+ ''|*[!A-Za-z0-9_-]*) return 1 ;;
152
+ esac
153
+ [ "${#digest}" -ge 32 ] && [ "${#digest}" -le 128 ] || return 1
154
+ if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
155
+ return 1
156
+ fi
157
+ mkdir -p "$root" 2>/dev/null || return 1
158
+ chmod 700 "$root" 2>/dev/null || true
159
+ printf '%s/%s.%s.%s.selection.%s.json' \
160
+ "$root" \
161
+ "$(mcc_cache_component "$scope")" \
162
+ "$(mcc_cache_component "$product_type")" \
163
+ "$(mcc_cache_component "$sales_code")" \
164
+ "$digest"
165
+ }
166
+
167
+ read_mcc_tree_cache() {
168
+ local product_type="$1" sales_code="$2" file now ttl
169
+ file="$(mcc_cache_file "$product_type" "$sales_code")" || return 1
170
+ [ -f "$file" ] && [ ! -L "$file" ] || return 1
171
+ now="$(date +%s 2>/dev/null)" || return 1
172
+ ttl="$(mcc_cache_ttl_seconds)"
173
+ jq -ce \
174
+ --arg productType "$product_type" \
175
+ --arg salesCode "$sales_code" \
176
+ --argjson now "$now" \
177
+ --argjson ttl "$ttl" '
178
+ if .schemaVersion == 1
179
+ and .productType == $productType
180
+ and .salesCode == $salesCode
181
+ and ((.createdAtEpoch // null) | type == "number")
182
+ and (($now - .createdAtEpoch) >= 0)
183
+ and (($now - .createdAtEpoch) <= $ttl)
184
+ and (.tree.success == true)
185
+ and ((.tree.resultObj.childrenNodes // null) | type == "array")
186
+ then .tree
187
+ else empty
188
+ end
189
+ ' "$file" 2>/dev/null
190
+ }
191
+
192
+ write_mcc_tree_cache() {
193
+ local product_type="$1" sales_code="$2" tree="$3" file dir now tmp
194
+ file="$(mcc_cache_file "$product_type" "$sales_code")" || return 0
195
+ dir="${file%/*}"
196
+ now="$(date +%s 2>/dev/null)" || return 0
197
+ tmp="$(mktemp "$dir/.mcc-tree.XXXXXX" 2>/dev/null)" || return 0
198
+ chmod 600 "$tmp" 2>/dev/null || true
199
+ if ! jq -cn \
200
+ --arg productType "$product_type" \
201
+ --arg salesCode "$sales_code" \
202
+ --argjson createdAtEpoch "$now" \
203
+ --argjson tree "$tree" \
204
+ '{
205
+ schemaVersion: 1,
206
+ productType: $productType,
207
+ salesCode: $salesCode,
208
+ createdAtEpoch: $createdAtEpoch,
209
+ tree: $tree
210
+ }' >"$tmp" 2>/dev/null; then
211
+ rm -f "$tmp" 2>/dev/null || true
212
+ return 0
213
+ fi
214
+ mv "$tmp" "$file" 2>/dev/null || {
215
+ rm -f "$tmp" 2>/dev/null || true
216
+ return 0
217
+ }
218
+ }
219
+
220
+ read_mcc_selection_cache() {
221
+ local product_type="$1" sales_code="$2" query="$3" receipt="$4" file now ttl
222
+ file="$(mcc_selection_cache_file "$product_type" "$sales_code" "$receipt")" || return 1
223
+ [ -f "$file" ] && [ ! -L "$file" ] || return 1
224
+ now="$(date +%s 2>/dev/null)" || return 1
225
+ ttl="$(mcc_cache_ttl_seconds)"
226
+ jq -ce \
227
+ --arg productType "$product_type" \
228
+ --arg salesCode "$sales_code" \
229
+ --arg query "$query" \
230
+ --arg receipt "$receipt" \
231
+ --argjson now "$now" \
232
+ --argjson ttl "$ttl" '
233
+ if .schemaVersion == 3
234
+ and .productType == $productType
235
+ and .salesCode == $salesCode
236
+ and .query == $query
237
+ and .selectionReceipt == $receipt
238
+ and ((.semanticTerms // null) | type == "array")
239
+ and ((.semanticTerms | length) <= 12)
240
+ and all(.semanticTerms[]; type == "string")
241
+ and ((.createdAtEpoch // null) | type == "number")
242
+ and (($now - .createdAtEpoch) >= 0)
243
+ and (($now - .createdAtEpoch) <= $ttl)
244
+ and ((.candidateRows // null) | type == "array")
245
+ and ((.candidateRows | length) >= 1)
246
+ and ((.candidateRows | length) <= 8)
247
+ and all(.candidateRows[];
248
+ (.fullCode | type) == "string"
249
+ and (.fullCode | test("^A[0-9]{4}_B[0-9]{4}$"))
250
+ and (.mccCode | type) == "string"
251
+ and (.mccCode | test("^B[0-9]{4}$"))
252
+ and (.treeName | type) == "string"
253
+ and (.treeName | test("\\S"))
254
+ and (.treeDescription | type) == "string"
255
+ and (.treeDescription | test("\\S"))
256
+ and (.treeNeedSpecialQual | type) == "boolean"
257
+ )
258
+ and (
259
+ (all(.candidateRows[]; .treeNeedSpecialQual == false) and .details == null)
260
+ or
261
+ (any(.candidateRows[]; .treeNeedSpecialQual == true)
262
+ and (.details | type) == "object"
263
+ and .details.success == true
264
+ and ((.details.mccDetails // null) | type == "array")
265
+ and all(.details.mccDetails[]; .success == true))
266
+ )
267
+ then {candidateRows: .candidateRows, details: .details}
268
+ else empty
269
+ end
270
+ ' "$file" 2>/dev/null
271
+ }
272
+
273
+ write_mcc_selection_cache() {
274
+ local product_type="$1" sales_code="$2" query="$3" receipt="$4" semantic_terms="$5" candidate_rows="$6" details="$7"
275
+ local file dir now tmp
276
+ file="$(mcc_selection_cache_file "$product_type" "$sales_code" "$receipt")" || return 0
277
+ dir="${file%/*}"
278
+ now="$(date +%s 2>/dev/null)" || return 0
279
+ tmp="$(mktemp "$dir/.mcc-selection.XXXXXX" 2>/dev/null)" || return 0
280
+ chmod 600 "$tmp" 2>/dev/null || true
281
+ if ! jq -cn \
282
+ --arg productType "$product_type" \
283
+ --arg salesCode "$sales_code" \
284
+ --arg query "$query" \
285
+ --arg selectionReceipt "$receipt" \
286
+ --argjson semanticTerms "$semantic_terms" \
287
+ --argjson createdAtEpoch "$now" \
288
+ --argjson candidateRows "$candidate_rows" \
289
+ --argjson details "$details" \
290
+ '{
291
+ schemaVersion: 3,
292
+ productType: $productType,
293
+ salesCode: $salesCode,
294
+ query: $query,
295
+ selectionReceipt: $selectionReceipt,
296
+ semanticTerms: $semanticTerms,
297
+ createdAtEpoch: $createdAtEpoch,
298
+ candidateRows: $candidateRows,
299
+ details: $details
300
+ }' >"$tmp" 2>/dev/null; then
301
+ rm -f "$tmp" 2>/dev/null || true
302
+ return 0
303
+ fi
304
+ mv "$tmp" "$file" 2>/dev/null || {
305
+ rm -f "$tmp" 2>/dev/null || true
306
+ return 0
307
+ }
308
+ }
309
+
310
+ mcc_semantic_retry_file() {
311
+ local product_type="$1" sales_code="$2" root scope
312
+ root="$(mcc_cache_root)" || return 1
313
+ scope="$(mcc_cache_scope_id)" || return 1
314
+ if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
315
+ return 1
316
+ fi
317
+ mkdir -p "$root" 2>/dev/null || return 1
318
+ chmod 700 "$root" 2>/dev/null || true
319
+ printf '%s/%s.%s.%s.semantic-retries.json' \
320
+ "$root" \
321
+ "$(mcc_cache_component "$scope")" \
322
+ "$(mcc_cache_component "$product_type")" \
323
+ "$(mcc_cache_component "$sales_code")"
324
+ }
325
+
326
+ mcc_agent_review_file() {
327
+ local product_type="$1" sales_code="$2" root scope
328
+ root="$(mcc_cache_root)" || return 1
329
+ scope="$(mcc_cache_scope_id)" || return 1
330
+ if [ -e "$root" ] && { [ ! -d "$root" ] || [ -L "$root" ]; }; then
331
+ return 1
332
+ fi
333
+ mkdir -p "$root" 2>/dev/null || return 1
334
+ chmod 700 "$root" 2>/dev/null || true
335
+ printf '%s/%s.%s.%s.agent-reviews.json' \
336
+ "$root" \
337
+ "$(mcc_cache_component "$scope")" \
338
+ "$(mcc_cache_component "$product_type")" \
339
+ "$(mcc_cache_component "$sales_code")"
340
+ }
341
+
342
+ mcc_agent_review_issued() {
343
+ local product_type="$1" sales_code="$2" input_id="$3" file
344
+ file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
345
+ [ -f "$file" ] && [ ! -L "$file" ] || return 1
346
+ jq -e \
347
+ --arg inputId "$input_id" \
348
+ '.schemaVersion == 1
349
+ and ((.entries // null) | type == "array")
350
+ and all(.entries[];
351
+ type == "object"
352
+ and ((.query // null) | type == "string")
353
+ and ((.inputId // null) | type == "string")
354
+ )
355
+ and any(.entries[]; .inputId == $inputId)' \
356
+ "$file" >/dev/null 2>&1
357
+ }
358
+
359
+ mark_mcc_agent_review_issued() {
360
+ local product_type="$1" sales_code="$2" query="$3" input_id="$4"
361
+ local file dir lock tmp
362
+ file="$(mcc_agent_review_file "$product_type" "$sales_code")" || return 1
363
+ dir="${file%/*}"
364
+ lock="${file}.lock"
365
+ if ! mkdir "$lock" 2>/dev/null; then
366
+ return 1
367
+ fi
368
+ if [ -e "$file" ] && { [ ! -f "$file" ] || [ -L "$file" ]; }; then
369
+ rmdir "$lock" 2>/dev/null || true
370
+ return 1
371
+ fi
372
+ tmp="$(mktemp "$dir/.mcc-agent-reviews.XXXXXX" 2>/dev/null)" || {
373
+ rmdir "$lock" 2>/dev/null || true
374
+ return 1
375
+ }
376
+ chmod 600 "$tmp" 2>/dev/null || true
377
+ if [ -f "$file" ]; then
378
+ if ! jq -ce \
379
+ --arg query "$query" \
380
+ --arg inputId "$input_id" '
381
+ if .schemaVersion != 1 or ((.entries // null) | type != "array") then
382
+ error("invalid MCC agent review state")
383
+ elif any(.entries[]; .query == $query and .inputId == $inputId) then
384
+ .
385
+ else
386
+ .entries += [{query: $query, inputId: $inputId}]
387
+ end
388
+ ' "$file" >"$tmp" 2>/dev/null; then
389
+ rm -f "$tmp" 2>/dev/null || true
390
+ rmdir "$lock" 2>/dev/null || true
391
+ return 1
392
+ fi
393
+ else
394
+ if ! jq -cn \
395
+ --arg query "$query" \
396
+ --arg inputId "$input_id" \
397
+ '{schemaVersion: 1, entries: [{query: $query, inputId: $inputId}]}' \
398
+ >"$tmp" 2>/dev/null; then
399
+ rm -f "$tmp" 2>/dev/null || true
400
+ rmdir "$lock" 2>/dev/null || true
401
+ return 1
402
+ fi
403
+ fi
404
+ if ! mv "$tmp" "$file" 2>/dev/null; then
405
+ rm -f "$tmp" 2>/dev/null || true
406
+ rmdir "$lock" 2>/dev/null || true
407
+ return 1
408
+ fi
409
+ rmdir "$lock" 2>/dev/null || true
410
+ }
411
+
412
+ claim_mcc_semantic_retry() {
413
+ local product_type="$1" sales_code="$2" query="$3" input_id="$4" semantic_terms_json="$5"
414
+ local file dir lock entry original_query retry_count next_retry_count now tmp lock_acquired=false
415
+ local current_terms previous_terms missing_previous_terms new_terms
416
+ file="$(mcc_semantic_retry_file "$product_type" "$sales_code")" || return 2
417
+ dir="${file%/*}"
418
+ lock="${file}.lock"
419
+ current_terms="$(printf '%s' "$semantic_terms_json" | jq -ce '
420
+ select(type == "array" and all(.[]; type == "string")) | unique | sort
421
+ ' 2>/dev/null)" || return 2
422
+
423
+ # Claim and persist one retry under a bounded private lock. Lock contention
424
+ # is treated as exhausted so a broken local state cannot cause a retry loop.
425
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
426
+ if mkdir "$lock" 2>/dev/null; then
427
+ lock_acquired=true
428
+ break
429
+ fi
430
+ sleep 0.05
431
+ done
432
+ [ "$lock_acquired" = true ] || return 2
433
+
434
+ if [ -e "$file" ]; then
435
+ if [ ! -f "$file" ] || [ -L "$file" ]; then
436
+ rmdir "$lock" 2>/dev/null || true
437
+ return 2
438
+ fi
439
+ now="$(date +%s 2>/dev/null)" || {
440
+ rmdir "$lock" 2>/dev/null || true
441
+ return 2
442
+ }
443
+ entry="$(jq -cr \
444
+ --arg inputId "$input_id" \
445
+ '
446
+ if .schemaVersion == 1
447
+ and ((.entries // null) | type == "array")
448
+ and all(.entries[];
449
+ type == "object"
450
+ and ((.inputId // null) | type == "string")
451
+ and ((.query // null) | type == "string")
452
+ and ((.count // null) | type == "number")
453
+ and (.count >= 0)
454
+ and ((.count | floor) == .count)
455
+ and ((.updatedAtEpoch // null) | type == "number")
456
+ and ((.semanticTerms // []) | type == "array")
457
+ and all((.semanticTerms // [])[]; type == "string")
458
+ )
459
+ then
460
+ ([.entries[] |
461
+ select(
462
+ .inputId == $inputId
463
+ ) |
464
+ {query, count, updatedAtEpoch, semanticTerms: (.semanticTerms // [])}
465
+ ] | sort_by(.updatedAtEpoch) | last) // null
466
+ else
467
+ error("invalid MCC semantic retry state")
468
+ end
469
+ ' "$file" 2>/dev/null)" || {
470
+ rmdir "$lock" 2>/dev/null || true
471
+ return 2
472
+ }
473
+ else
474
+ entry='null'
475
+ fi
476
+
477
+ if [ "$entry" = "null" ]; then
478
+ original_query="$query"
479
+ retry_count=0
480
+ previous_terms='[]'
481
+ else
482
+ original_query="$(printf '%s' "$entry" | jq -r '.query // empty' 2>/dev/null)" || original_query=""
483
+ retry_count="$(printf '%s' "$entry" | jq -r '.count // -1' 2>/dev/null)" || retry_count=-1
484
+ previous_terms="$(printf '%s' "$entry" | jq -c '.semanticTerms // []' 2>/dev/null)" || previous_terms=""
485
+ if [ "$original_query" != "$query" ]; then
486
+ rmdir "$lock" 2>/dev/null || true
487
+ return 2
488
+ fi
489
+ [ -n "$previous_terms" ] || {
490
+ rmdir "$lock" 2>/dev/null || true
491
+ return 2
492
+ }
493
+ missing_previous_terms="$(jq -nr \
494
+ --argjson previous "$previous_terms" \
495
+ --argjson current "$current_terms" \
496
+ '($previous - $current) | length' 2>/dev/null)" || {
497
+ rmdir "$lock" 2>/dev/null || true
498
+ return 2
499
+ }
500
+ new_terms="$(jq -nr \
501
+ --argjson previous "$previous_terms" \
502
+ --argjson current "$current_terms" \
503
+ '($current - $previous) | length' 2>/dev/null)" || {
504
+ rmdir "$lock" 2>/dev/null || true
505
+ return 2
506
+ }
507
+ if [ "$missing_previous_terms" -ne 0 ] || [ "$new_terms" -eq 0 ]; then
508
+ # A semantic retry must be cumulative and add at least one new term.
509
+ # Do not consume a retry budget for an unchanged or replaced term set.
510
+ rmdir "$lock" 2>/dev/null || true
511
+ return 3
512
+ fi
513
+ fi
514
+ case "$retry_count" in
515
+ ''|*[!0-9]*) retry_count="$MCC_SEMANTIC_RETRY_LIMIT" ;;
516
+ esac
517
+ if [ "$retry_count" -ge "$MCC_SEMANTIC_RETRY_LIMIT" ]; then
518
+ rmdir "$lock" 2>/dev/null || true
519
+ return 2
520
+ fi
521
+ next_retry_count=$((retry_count + 1))
522
+ now="$(date +%s 2>/dev/null)" || {
523
+ rmdir "$lock" 2>/dev/null || true
524
+ return 2
525
+ }
526
+ tmp="$(mktemp "$dir/.mcc-semantic-retries.XXXXXX" 2>/dev/null)" || {
527
+ rmdir "$lock" 2>/dev/null || true
528
+ return 2
529
+ }
530
+ chmod 600 "$tmp" 2>/dev/null || true
531
+ if [ -f "$file" ] && [ ! -L "$file" ]; then
532
+ if ! jq -ce \
533
+ --arg query "$original_query" \
534
+ --arg inputId "$input_id" \
535
+ --argjson count "$next_retry_count" \
536
+ --argjson now "$now" \
537
+ --argjson semanticTerms "$current_terms" '
538
+ if .schemaVersion != 1 or ((.entries // null) | type != "array") then
539
+ error("invalid MCC semantic retry state")
540
+ else
541
+ .entries = (
542
+ [.entries[] | select(.inputId != $inputId)]
543
+ + [{
544
+ query: $query,
545
+ inputId: $inputId,
546
+ count: $count,
547
+ updatedAtEpoch: $now,
548
+ semanticTerms: $semanticTerms
549
+ }]
550
+ )
551
+ | .entries |= sort_by(.updatedAtEpoch)
552
+ end
553
+ ' "$file" >"$tmp" 2>/dev/null; then
554
+ rm -f "$tmp" 2>/dev/null || true
555
+ rmdir "$lock" 2>/dev/null || true
556
+ return 2
557
+ fi
558
+ elif [ -e "$file" ]; then
559
+ rm -f "$tmp" 2>/dev/null || true
560
+ rmdir "$lock" 2>/dev/null || true
561
+ return 2
562
+ else
563
+ if ! jq -cn \
564
+ --arg query "$original_query" \
565
+ --arg inputId "$input_id" \
566
+ --argjson count "$next_retry_count" \
567
+ --argjson now "$now" \
568
+ --argjson semanticTerms "$current_terms" \
569
+ '{
570
+ schemaVersion: 1,
571
+ entries: [{
572
+ query: $query,
573
+ inputId: $inputId,
574
+ count: $count,
575
+ updatedAtEpoch: $now,
576
+ semanticTerms: $semanticTerms
577
+ }]
578
+ }' >"$tmp" 2>/dev/null; then
579
+ rm -f "$tmp" 2>/dev/null || true
580
+ rmdir "$lock" 2>/dev/null || true
581
+ return 2
582
+ fi
583
+ fi
584
+ if ! mv "$tmp" "$file" 2>/dev/null; then
585
+ rm -f "$tmp" 2>/dev/null || true
586
+ rmdir "$lock" 2>/dev/null || true
587
+ return 2
588
+ fi
589
+ rmdir "$lock" 2>/dev/null || true
590
+ printf '%s' "$next_retry_count"
591
+ }
592
+
593
+ get_mcc_tree() {
594
+ local product_type="$1" sales_code="$2" cached tree cached_rows
595
+ cached="$(read_mcc_tree_cache "$product_type" "$sales_code" 2>/dev/null || true)"
596
+ if [ -n "$cached" ]; then
597
+ cached_rows="$(printf '%s' "$cached" | mcc_rows_from_tree 2>/dev/null || true)"
598
+ if printf '%s' "$cached_rows" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then
599
+ printf '%s' "$cached"
600
+ return 0
601
+ fi
602
+ fi
603
+ capture_or_exit tree query_mcc_tree "$sales_code"
604
+ write_mcc_tree_cache "$product_type" "$sales_code" "$tree"
605
+ printf '%s' "$tree"
606
+ }
607
+
608
+ query_mcc_tree() {
609
+ local sales_code="$1" request_json raw retry_rc business rows
610
+ request_json=$(jq -n \
611
+ --arg salesCode "$sales_code" \
612
+ '{request:{channelCode:"B_SK_SH_RPC",salesProductCodes:[$salesCode]},ctx:{}}') || return 1
613
+
614
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
615
+ run_network_retry raw read query_mcc -- alipay-cli mcp call ar-support.queryMcc \
616
+ -d "$request_json" --json
617
+ retry_rc=$?
618
+ if [ "$retry_rc" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
619
+ exit "$retry_rc"
620
+ fi
621
+ [ "$retry_rc" -eq 0 ] || network_failure
622
+ ensure_mcc_cli_success "$raw" || mcc_query_failed
623
+ business=$(extract_business_json "$raw") || {
624
+ mcc_query_failed
625
+ }
626
+ if ! printf '%s' "$business" | jq -e '.success == true and (.resultObj.childrenNodes | type == "array")' >/dev/null 2>&1; then
627
+ mcc_query_failed
628
+ fi
629
+ rows="$(printf '%s' "$business" | mcc_rows_from_tree 2>/dev/null || true)"
630
+ if ! printf '%s' "$rows" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then
631
+ mcc_query_failed
632
+ fi
633
+ printf '%s' "$business"
634
+ }
635
+
636
+ mcc_rows_from_tree() {
637
+ jq -ce '
638
+ def nonblank_string: type == "string" and test("\\S");
639
+ def mcc_row($primary):
640
+ . as $node |
641
+ if (($node.code? | type) != "string" or ($node.code | test("^B[0-9]{4}$") | not)) then
642
+ error("queryMcc returned invalid selectable mcc code")
643
+ elif (($node.name? | nonblank_string) | not) then
644
+ error("queryMcc returned missing mcc name")
645
+ elif (($node.description? | nonblank_string) | not) then
646
+ error("queryMcc returned missing mcc description")
647
+ elif (($node.needSpecialQual? | type) != "boolean") then
648
+ error("queryMcc returned invalid needSpecialQual")
649
+ elif (($node.childrenNodes? // []) | type) != "array"
650
+ or (($node.childrenNodes? // []) | length) > 0 then
651
+ error("queryMcc returned invalid selectable mcc hierarchy")
652
+ else
653
+ {
654
+ fullCode: ($primary + "_" + $node.code),
655
+ mccCode: $node.code,
656
+ treeName: $node.name,
657
+ treeDescription: $node.description,
658
+ treeNeedSpecialQual: $node.needSpecialQual
659
+ }
660
+ end;
661
+ [
662
+ .resultObj.childrenNodes[]? |
663
+ . as $primary |
664
+ if (($primary.code? | type) != "string" or ($primary.code | test("^A[0-9]{4}$") | not)) then
665
+ error("queryMcc returned invalid primary mcc code")
666
+ elif (($primary.childrenNodes? | type) != "array") then
667
+ error("queryMcc returned invalid primary mcc hierarchy")
668
+ else
669
+ $primary.childrenNodes[]? | mcc_row($primary.code)
670
+ end
671
+ ] as $rows |
672
+ if ($rows | length) == 0 then
673
+ error("queryMcc returned no selectable mcc")
674
+ elif (($rows | map(.fullCode) | unique | length) != ($rows | length)) then
675
+ error("queryMcc returned duplicate selectable mcc")
676
+ else
677
+ ($rows
678
+ | sort_by(.fullCode)
679
+ | to_entries
680
+ | map(.value + {mccRef: ("mcc-" + ((.key + 1) | tostring))}))
681
+ end
682
+ ' 2>/dev/null
683
+ }
684
+
685
+ normalize_mcc_tree_catalog() {
686
+ local rows_json="$1"
687
+ jq -cn --argjson rows "$rows_json" '
688
+ ($rows |
689
+ map({
690
+ mccCode: .fullCode,
691
+ mccName: .treeName,
692
+ mccDescription: .treeDescription,
693
+ needSpecialQual: .treeNeedSpecialQual,
694
+ qualificationGroups: []
695
+ }) |
696
+ sort_by(.mccCode) |
697
+ to_entries |
698
+ map(.value + {mccRef: ("mcc-" + ((.key + 1) | tostring))}))
699
+ ' 2>/dev/null
700
+ }
701
+
702
+ rows_for_full_codes() {
703
+ local rows_json="$1" full_codes_json="$2"
704
+ jq -cn --argjson rows "$rows_json" --argjson fullCodes "$full_codes_json" '
705
+ ($fullCodes | unique) as $wanted |
706
+ [
707
+ $rows[] |
708
+ select(.fullCode as $code | $wanted | index($code))
709
+ ]
710
+ ' 2>/dev/null
711
+ }
712
+
713
+ query_mcc_details() {
714
+ local rows_json="$1" mcc_codes_json total details
715
+ mcc_codes_json=$(printf '%s' "$rows_json" | jq -c '[.[].mccCode] | unique' 2>/dev/null) || return 1
716
+ if ! printf '%s' "$mcc_codes_json" | jq -e 'length > 0' >/dev/null 2>&1; then
717
+ mcc_query_failed
718
+ fi
719
+ total=$(printf '%s' "$mcc_codes_json" | jq -r 'length' 2>/dev/null) || mcc_query_failed
720
+ [ "$total" -le 8 ] || mcc_query_failed
721
+ capture_or_exit details query_mcc_detail_chunk "$mcc_codes_json"
722
+ printf '%s' "$details"
723
+ }
724
+
725
+ query_mcc_detail_chunk() {
726
+ local mcc_codes_json="$1" request_json raw retry_rc business
727
+ request_json=$(jq -n --argjson mccCodes "$mcc_codes_json" '{request:{mccCodes:$mccCodes}}') || return 1
728
+
729
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
730
+ run_network_retry raw read batch_query_mcc -- alipay-cli mcp call mccsearchservice.batchQueryMcc \
731
+ -d "$request_json" --json
732
+ retry_rc=$?
733
+ if [ "$retry_rc" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
734
+ exit "$retry_rc"
735
+ fi
736
+ [ "$retry_rc" -eq 0 ] || network_failure
737
+ ensure_mcc_cli_success "$raw" || mcc_query_failed
738
+ business=$(extract_business_json "$raw") || {
739
+ mcc_query_failed
740
+ }
741
+ if ! printf '%s' "$business" | jq -e '.success == true and (.mccDetails | type == "array") and (.truncated // false | not)' >/dev/null 2>&1; then
742
+ mcc_query_failed
743
+ fi
744
+ printf '%s' "$business"
745
+ }
746
+
747
+ mcc_match_error_kind() {
748
+ case "$1" in
749
+ *agent-candidate-refs-json*) printf 'INVALID_AGENT_CANDIDATE_REFS' ;;
750
+ *semantic-terms-json*) printf 'INVALID_SEMANTIC_TERMS' ;;
751
+ *mcc-catalog-json*) printf 'INVALID_DYNAMIC_CATALOG' ;;
752
+ *) printf 'MATCHER_RUNTIME_ERROR' ;;
753
+ esac
754
+ }
755
+
756
+ normalize_mcc_catalog() {
757
+ local rows_json="$1" details_json="$2"
758
+ jq -cn --argjson rows "$rows_json" --argjson details "$details_json" '
759
+ ($details.mccDetails // []) as $detailList |
760
+ ($rows | map(select(.treeNeedSpecialQual == true) | .mccCode) | unique) as $requiredCodes |
761
+ ($detailList | map(select(.success != true) | .mccCode) | unique) as $failedCodes |
762
+ if ($failedCodes | length) > 0 then
763
+ error("batchQueryMcc returned failed mcc details")
764
+ else
765
+ ($detailList | map(.mccCode) | unique) as $detailCodes |
766
+ ($requiredCodes - $detailCodes) as $missingCodes |
767
+ ($detailCodes - $requiredCodes) as $unexpectedCodes |
768
+ if (($detailCodes | length) != ($detailList | length)) then
769
+ error("batchQueryMcc returned duplicate mcc details")
770
+ elif ($missingCodes | length) > 0 then
771
+ error("batchQueryMcc missed required qualification details")
772
+ elif ($unexpectedCodes | length) > 0 then
773
+ error("batchQueryMcc returned unrequested mcc details")
774
+ else
775
+ (reduce $detailList[] as $detail ({}; .[$detail.mccCode] = $detail)) as $detailByCode |
776
+ [
777
+ $rows[] |
778
+ . as $row |
779
+ ($detailByCode[$row.mccCode]) as $detail |
780
+ {
781
+ mccCode: $row.fullCode,
782
+ mccName: $row.treeName,
783
+ mccDescription: $row.treeDescription,
784
+ needSpecialQual: $row.treeNeedSpecialQual,
785
+ mccRef: $row.mccRef,
786
+ qualificationGroups: (if $row.treeNeedSpecialQual then
787
+ (($detail.qualificationGroups // []) | map({
788
+ qualifications: ((.qualifications // []) | map({
789
+ attaType: (if (.attaType == null or .attaType == "") then null else (.attaType | tostring) end),
790
+ qualId: (if (.qualId == null) then null else (.qualId | tostring) end),
791
+ qualName: ((.qualName // "") | tostring),
792
+ qualType: (if (.qualType == null) then null else (.qualType | tostring) end),
793
+ status: (if (.status == null) then null else (.status | tostring) end)
794
+ }))
795
+ }))
796
+ else
797
+ []
798
+ end)
799
+ }
800
+ ] as $catalog |
801
+ if any($catalog[];
802
+ (.needSpecialQual == true) !=
803
+ (([.qualificationGroups[]? | select((.qualifications | length) > 0)] | length) > 0)
804
+ ) or any($catalog[]; any(.qualificationGroups[]?; (.qualifications | length) == 0)) then
805
+ error("batchQueryMcc returned inconsistent qualification groups")
806
+ else
807
+ $catalog
808
+ end
809
+ end
810
+ end
811
+ ' 2>/dev/null
812
+ }
813
+
814
+ run_mcc_match_codes() {
815
+ local query="$1" product_type="$2" root_intent="$3" catalog="$4" semantic_terms_json="$5" agent_candidate_refs_json="${6:-}" runner_out runner_err runner_rc line resolution=""
816
+ local runner_error error_kind
817
+ runner_out=$(mktemp "${TMPDIR:-/tmp}/mcc_match_out.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
818
+ runner_err=$(mktemp "${TMPDIR:-/tmp}/mcc_match_err.XXXXXX") || { rm -f "$runner_out"; echo "❌ 创建临时文件失败"; exit 1; }
819
+ local node_args=(
820
+ "$ONBOARDING_RUNNER" mcc-match-codes
821
+ --query "$query" \
822
+ --product-type "$product_type" \
823
+ --root-intent "$root_intent" \
824
+ --mcc-catalog-json "$catalog" \
825
+ --semantic-terms-json "$semantic_terms_json"
826
+ )
827
+ if [ -n "$agent_candidate_refs_json" ]; then
828
+ node_args+=(--agent-candidate-refs-json "$agent_candidate_refs_json")
829
+ fi
830
+ "${NODE_BINARY:-node}" "${node_args[@]}" >"$runner_out" 2>"$runner_err"
831
+ runner_rc=$?
832
+ if [ "$runner_rc" -ne 0 ] || [ -s "$runner_out" ]; then
833
+ runner_error="$(sed -n 's/^ONBOARDING_MESSAGE_ERROR://p' "$runner_err" | sed -n '1p')"
834
+ error_kind="$(mcc_match_error_kind "$runner_error")"
835
+ if [ "$error_kind" = "INVALID_AGENT_CANDIDATE_REFS" ] && [ -n "$agent_candidate_refs_json" ]; then
836
+ rm -f "$runner_out" "$runner_err"
837
+ emit_control "MCC_AGENT_CANDIDATE_REFS_IGNORED=INVALID"
838
+ run_mcc_match_codes "$query" "$product_type" "$root_intent" "$catalog" "$semantic_terms_json"
839
+ return $?
840
+ fi
841
+ rm -f "$runner_out" "$runner_err"
842
+ emit_control "MCC_MATCH_ERROR=$error_kind"
843
+ mcc_query_failed
844
+ fi
845
+ while IFS= read -r line; do
846
+ case "$line" in
847
+ ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=*)
848
+ resolution="${line#ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=}" ;;
849
+ esac
850
+ done <"$runner_err"
851
+ rm -f "$runner_out" "$runner_err"
852
+ [ -n "$resolution" ] || mcc_query_failed
853
+ printf '%s' "$resolution"
854
+ }
855
+
856
+ validate_mcc_selection_cache() {
857
+ local query="$1" product_type="$2" selection_receipt="$3" catalog="$4" semantic_terms_json="$5"
858
+ "${NODE_BINARY:-node}" "$ONBOARDING_RUNNER" mcc-validate-selection-cache \
859
+ --query "$query" \
860
+ --product-type "$product_type" \
861
+ --selection-receipt "$selection_receipt" \
862
+ --mcc-catalog-json "$catalog" \
863
+ --semantic-terms-json "$semantic_terms_json" >/dev/null 2>&1
864
+ }
865
+
866
+ run_resolve_plan() {
867
+ local query="" product_type="" root_intent="" semantic_terms_json='[]' agent_candidate_refs_json="" select_value="" selection_receipt="" input_id="" sales_code tree rows tree_catalog resolution match_type semantic_status full_codes candidate_rows qualification_rows details catalog selection_cache="" used_selection_cache=false stale_selection=false resolved_semantic_terms='[]' retry_rc=0 next_retry_count=0 retry_reason="" retry_payload="" agent_review_required="false" agent_review_payload=""
868
+ shift
869
+ while [[ $# -gt 0 ]]; do
870
+ case "$1" in
871
+ --query)
872
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
873
+ query="$2"; shift 2 ;;
874
+ --product-type)
875
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
876
+ product_type="$2"; shift 2 ;;
877
+ --root-intent)
878
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
879
+ root_intent="$2"; shift 2 ;;
880
+ --semantic-terms-json)
881
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
882
+ semantic_terms_json="$2"; shift 2 ;;
883
+ --agent-candidate-refs-json)
884
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
885
+ agent_candidate_refs_json="$2"; shift 2 ;;
886
+ --select)
887
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
888
+ select_value="$2"; shift 2 ;;
889
+ --selection-receipt)
890
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
891
+ selection_receipt="$2"; shift 2 ;;
892
+ --input-id)
893
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
894
+ input_id="$2"; shift 2 ;;
895
+ *)
896
+ echo "❌ 未知参数: $1"; exit 1 ;;
897
+ esac
898
+ done
899
+ if [ -z "$query" ] || [ -z "$product_type" ]; then
900
+ echo "❌ 缺少 --query 或 --product-type"
901
+ exit 1
902
+ fi
903
+ case "$root_intent" in
904
+ onboarding_only|full_process) ;;
905
+ *)
906
+ echo "❌ 缺少或非法 --root-intent,仅支持 onboarding_only、full_process"
907
+ exit 1
908
+ ;;
909
+ esac
910
+ semantic_terms_json=$(printf '%s' "$semantic_terms_json" | jq -ce '
911
+ select(type == "array" and length <= 12 and all(.[]; type == "string"))
912
+ ' 2>/dev/null) || {
913
+ echo "❌ --semantic-terms-json 必须是最多 12 项的 JSON 字符串数组"
914
+ exit 1
915
+ }
916
+ sales_code=$(product_sales_code "$product_type") || {
917
+ echo "❌ product-type 非法,仅支持 aipay|webpay|apppay"
918
+ exit 1
919
+ }
920
+ init_alipay_cli_product "$product_type" || exit 1
921
+
922
+ if [ -n "$selection_receipt" ]; then
923
+ selection_cache="$(read_mcc_selection_cache "$product_type" "$sales_code" "$query" "$selection_receipt" 2>/dev/null || true)"
924
+ if [ -n "$selection_cache" ]; then
925
+ candidate_rows=$(printf '%s' "$selection_cache" | jq -c '.candidateRows' 2>/dev/null) || mcc_query_failed
926
+ details=$(printf '%s' "$selection_cache" | jq -c '.details' 2>/dev/null) || mcc_query_failed
927
+ catalog=$(normalize_mcc_catalog "$candidate_rows" "$details" 2>/dev/null || true)
928
+ if [ -n "$catalog" ] && validate_mcc_selection_cache "$query" "$product_type" "$selection_receipt" "$catalog" "$semantic_terms_json"; then
929
+ used_selection_cache=true
930
+ else
931
+ catalog=""
932
+ candidate_rows=""
933
+ details=""
934
+ fi
935
+ fi
936
+ [ "$used_selection_cache" = true ] || stale_selection=true
937
+ fi
938
+
939
+ if [ "$used_selection_cache" != true ]; then
940
+ require_alipay_cli_local_state_access_or_emit || exit "$?"
941
+ capture_or_exit tree get_mcc_tree "$product_type" "$sales_code"
942
+ rows=$(printf '%s' "$tree" | mcc_rows_from_tree) || {
943
+ mcc_query_failed
944
+ }
945
+ tree_catalog=$(normalize_mcc_tree_catalog "$rows") || {
946
+ mcc_query_failed
947
+ }
948
+ capture_or_exit resolution run_mcc_match_codes "$query" "$product_type" "$root_intent" "$tree_catalog" "$semantic_terms_json" "$agent_candidate_refs_json"
949
+ match_type=$(printf '%s' "$resolution" | jq -r '.matchType // ""' 2>/dev/null) || mcc_query_failed
950
+ semantic_status=$(printf '%s' "$resolution" | jq -r '.semanticStatus // ""' 2>/dev/null) || mcc_query_failed
951
+ resolved_semantic_terms=$(printf '%s' "$resolution" | jq -c '.semanticTerms // []' 2>/dev/null) || mcc_query_failed
952
+ agent_review_required=$(printf '%s' "$resolution" | jq -r '.agentReview.required // false' 2>/dev/null) || mcc_query_failed
953
+ if [ -n "$input_id" ] && [ -n "$agent_candidate_refs_json" ]; then
954
+ mark_mcc_agent_review_issued "$product_type" "$sales_code" "$query" "$input_id" || true
955
+ fi
956
+ if [ "$agent_review_required" = "true" ] && [ -z "$agent_candidate_refs_json" ] && [ -n "$input_id" ]; then
957
+ agent_review_payload=$(printf '%s' "$resolution" | jq -c '.agentReview' 2>/dev/null) || mcc_query_failed
958
+ if ! mcc_agent_review_issued "$product_type" "$sales_code" "$input_id"; then
959
+ mark_mcc_agent_review_issued "$product_type" "$sales_code" "$query" "$input_id" || true
960
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
961
+ emit_control "MCC_RESOLUTION=$(printf '%s' "$resolution" | jq -c 'del(.agentReview)' 2>/dev/null)"
962
+ emit_control "MCC_AGENT_REVIEW_REQUIRED=$agent_review_payload"
963
+ return 0
964
+ fi
965
+ fi
966
+ if [ "$match_type" = "NONE" ] \
967
+ && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_NOT_FOUND" ] \
968
+ && [ "$semantic_status" != "DYNAMIC_CATALOG_CODE_AMBIGUOUS" ]; then
969
+ if [ -n "$input_id" ]; then
970
+ next_retry_count="$(claim_mcc_semantic_retry \
971
+ "$product_type" "$sales_code" "$query" "$input_id" "$resolved_semantic_terms" 2>/dev/null)"
972
+ retry_rc=$?
973
+ else
974
+ # A retry without a message identity cannot be bound to one user input.
975
+ # Keep the bounded retry contract closed instead of sharing a placeholder.
976
+ retry_rc=2
977
+ fi
978
+ if [ "$retry_rc" -eq 0 ]; then
979
+ if [ "$semantic_terms_json" = "[]" ]; then
980
+ retry_reason="MISSING_SEMANTIC_TERMS"
981
+ else
982
+ retry_reason="NO_MATCH"
983
+ fi
984
+ retry_payload=$(jq -cn \
985
+ --argjson attempt "$next_retry_count" \
986
+ --argjson maxAttempts "$MCC_SEMANTIC_RETRY_LIMIT" \
987
+ --argjson remainingAttempts "$((MCC_SEMANTIC_RETRY_LIMIT - next_retry_count))" \
988
+ --arg reason "$retry_reason" \
989
+ '{attempt: $attempt, maxAttempts: $maxAttempts, remainingAttempts: $remainingAttempts, reason: $reason}') \
990
+ || mcc_query_failed
991
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
992
+ emit_control "MCC_RESOLUTION=$resolution"
993
+ emit_control "MCC_SEMANTIC_RETRY_REQUIRED=$retry_payload"
994
+ return 0
995
+ fi
996
+ fi
997
+ if [ "$match_type" = "NONE" ]; then
998
+ catalog="$tree_catalog"
999
+ else
1000
+ full_codes=$(printf '%s' "$resolution" | jq -c '.mergedCandidateCodes // []' 2>/dev/null) || mcc_query_failed
1001
+ fi
1002
+ if [ -z "${catalog:-}" ]; then
1003
+ if ! printf '%s' "$full_codes" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then
1004
+ catalog="$tree_catalog"
1005
+ else
1006
+ candidate_rows=$(rows_for_full_codes "$rows" "$full_codes") || mcc_query_failed
1007
+ if ! printf '%s' "$candidate_rows" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then
1008
+ mcc_query_failed
1009
+ fi
1010
+ qualification_rows=$(printf '%s' "$candidate_rows" | jq -c '[.[] | select(.treeNeedSpecialQual == true)]' 2>/dev/null) || mcc_query_failed
1011
+ if printf '%s' "$qualification_rows" | jq -e 'length > 0' >/dev/null 2>&1; then
1012
+ capture_or_exit details query_mcc_details "$qualification_rows"
1013
+ else
1014
+ details='null'
1015
+ fi
1016
+ catalog=$(normalize_mcc_catalog "$candidate_rows" "$details") || {
1017
+ mcc_query_failed
1018
+ }
1019
+ fi
1020
+ fi
1021
+ fi
1022
+ if [ -n "$agent_candidate_refs_json" ] && ! printf '%s' "$catalog" | jq -e \
1023
+ --argjson refs "$agent_candidate_refs_json" '
1024
+ ([.[] | .mccRef?] | map(select(type == "string"))) as $available |
1025
+ ($refs | type == "array" and all(.[]; . as $ref | $available | index($ref) != null))
1026
+ ' >/dev/null 2>&1; then
1027
+ # An invalid Agent review may have already fallen back to local matches.
1028
+ # Do not pass that invalid reference into the reduced final catalog.
1029
+ agent_candidate_refs_json=""
1030
+ fi
1031
+ node_args=(
1032
+ "$ONBOARDING_RUNNER" mcc-resolve-plan
1033
+ --query "$query"
1034
+ --product-type "$product_type"
1035
+ --root-intent "$root_intent"
1036
+ --mcc-catalog-json "$catalog"
1037
+ --semantic-terms-json "$semantic_terms_json"
1038
+ )
1039
+ if [ -n "$input_id" ]; then
1040
+ node_args+=(--input-id "$input_id")
1041
+ fi
1042
+ if [ -n "$agent_candidate_refs_json" ]; then
1043
+ node_args+=(--agent-candidate-refs-json "$agent_candidate_refs_json")
1044
+ fi
1045
+ if [ "$stale_selection" = true ]; then
1046
+ node_args+=(--selection-state STALE)
1047
+ elif [ -n "$select_value" ] || [ -n "$selection_receipt" ]; then
1048
+ if [ -z "$select_value" ] || [ -z "$selection_receipt" ]; then
1049
+ echo "❌ --select 与 --selection-receipt 必须同时提供"
1050
+ exit 1
1051
+ fi
1052
+ node_args+=(--select "$select_value" --selection-receipt "$selection_receipt")
1053
+ fi
1054
+ local runner_out runner_err runner_rc
1055
+ runner_out=$(mktemp "${TMPDIR:-/tmp}/mcc_runner_out.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
1056
+ runner_err=$(mktemp "${TMPDIR:-/tmp}/mcc_runner_err.XXXXXX") || { rm -f "$runner_out"; echo "❌ 创建临时文件失败"; exit 1; }
1057
+ "${NODE_BINARY:-node}" "${node_args[@]}" >"$runner_out" 2>"$runner_err"
1058
+ runner_rc=$?
1059
+ if [ "$runner_rc" -ne 0 ]; then
1060
+ rm -f "$runner_out" "$runner_err"
1061
+ mcc_query_failed
1062
+ fi
1063
+ if { [ -z "$selection_receipt" ] || [ "$stale_selection" = true ]; } \
1064
+ && [ -n "${candidate_rows:-}" ] && [ -n "${details:-}" ]; then
1065
+ local generated_selection_receipt
1066
+ generated_selection_receipt=$(sed -n 's/^ALIPAY_AIPAY_INTERNAL:MCC_SELECTION_RECEIPT=//p' "$runner_err")
1067
+ if [ "$(printf '%s\n' "$generated_selection_receipt" | sed '/^$/d' | wc -l | tr -d ' ')" = "1" ]; then
1068
+ write_mcc_selection_cache "$product_type" "$sales_code" "$query" "$generated_selection_receipt" "$resolved_semantic_terms" "$candidate_rows" "$details"
1069
+ fi
1070
+ fi
1071
+ emit_control "MCC_QUERY_RESULT=SUCCESS"
1072
+ cat "$runner_out"
1073
+ cat "$runner_err" >&2
1074
+ rm -f "$runner_out" "$runner_err"
1075
+ }
1076
+
1077
+ case "${1:-}" in
1078
+ resolve-plan)
1079
+ run_resolve_plan "$@"
1080
+ ;;
1081
+ *)
1082
+ echo "用法: bash mcc.sh resolve-plan --query <经营描述|MCC编码> --product-type <aipay|webpay|apppay> --root-intent <onboarding_only|full_process> [--semantic-terms-json <JSON数组>] [--agent-candidate-refs-json <JSON数组>] [--select <MCC编码> --selection-receipt <receipt>] [--input-id <USER_INPUT_ID>]"
1083
+ exit 1
1084
+ ;;
1085
+ esac