@alipay/alipay-aipay 1.7.8 → 1.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
  # 正式版(默认)
11
11
  npx -y @alipay/alipay-aipay@latest install
12
12
 
13
- # 预发版
13
+ # 内测版
14
14
  npx -y @alipay/alipay-aipay@latest install --channel pre
15
15
  ```
16
16
 
17
- 两份 Skill 可并存:正式版安装到 Agent 的 `skills/alipay-aipay/`,预发版安装到 `skills/alipay-aipay-pre/`。预发版使用独立名称 `alipay-aipay-pre`,在对话中明确点名或选择预发版使用。`--target` 的 Agent 目标选择规则不变。
17
+ 两份 Skill 可并存:正式版安装到 Agent 的 `skills/alipay-aipay/`,内测版安装到 `skills/alipay-aipay-pre/`。内测版使用独立名称 `alipay-aipay-pre`,在对话中明确点名或选择内测版使用。`--target` 的 Agent 目标选择规则不变。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "支付宝 AI 付接入 Skill 安装包 - 优先安装 Agent Skill,并在 macOS/Linux 后台准备用户级 CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,7 @@
55
55
  "typescript": "^5.3.3"
56
56
  },
57
57
  "alipayAipayChannels": {
58
- "stable": "1.7.4",
59
- "pre": "1.7.7-pre"
58
+ "stable": "1.7.5",
59
+ "pre": "1.7.8-pre"
60
60
  }
61
61
  }
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.4
1
+ 1.7.5
@@ -1 +1 @@
1
- {"schemaVersion":1,"rules":[{"policyId":"P-MCP-CONTRACT-FROZEN","scope":"all","severity":"critical","text":"只用当前 Skill 已验证的 MCP 脚本契约;禁止推断、改名或尝试近似方法。","runtimeText":"仅用已验证脚本;禁推断或尝试近似方法。"},{"policyId":"P-AUTH-BEFORE-ONBOARDING","scope":"onboarding","severity":"critical","text":"登录、scope 和 MCC 校验通过前,禁止 onboarding 查询或写入。","runtimeText":"登录/scope/MCC 未通过前禁查询和写入。"},{"policyId":"P-SIGNING-WRITE","scope":"onboarding.signing","severity":"critical","text":"签约材料校验通过后直接提交,不增加确认;缺失或非法时禁止提交。"},{"policyId":"P-SERVICE-WRITE","scope":"onboarding.service","severity":"critical","text":"服务创建摘要送达后直接创建;服务修改须取得绑定当前脱敏摘要的明确确认。"},{"policyId":"P-APPLICATION-WRITE","scope":"onboarding.application","severity":"critical","text":"完整新建材料或非空候选后的明确“新建”已构成创建确认;快照未变时不追加确认,公钥确认后直接提审。"},{"policyId":"P-WRITE-UNKNOWN","scope":"onboarding.write","severity":"critical","text":"非幂等写入结果不明时只读核验;仍无法确认则进入 UNKNOWN,禁止重试写入。"},{"policyId":"P-PARTIAL-RESULT","scope":"onboarding","severity":"critical","text":"签约、服务、应用分支独立记录,失败不回滚或覆盖其他成功结果。"},{"policyId":"P-MATERIAL-CATEGORY","scope":"onboarding.materials","severity":"required","text":"每轮只展示一个材料类别;同类一次收齐,已提供材料不得重复索取。"},{"policyId":"P-KEY-NO-PRIVATE","scope":"onboarding.application","severity":"critical","text":"禁止请求、处理、保存或输出生产私钥,也不提供密钥生成命令。"},{"policyId":"P-KEY-PUBLIC-USER","scope":"onboarding.application","severity":"critical","text":"应用公钥只能由用户提供;缺少时禁止 createKeyConfirmPage,也不得补全、改写或添加 PEM 头尾。"},{"policyId":"P-AUTH-URL","scope":"onboarding.authorization","severity":"critical","text":"授权链接只由 auth.sh 构造并校验固定域名、路径和参数;禁用 verification_url。"},{"policyId":"P-KEY-URL","scope":"onboarding.application","severity":"critical","text":"公钥确认只展示 createKeyConfirmPage 返回并校验的裸 URL;禁止二维码链接和 alipays://。"},{"policyId":"P-AUTH-URL-AUTO-OPEN","scope":"onboarding.authorization","severity":"critical","text":"授权 URL 交付后调用受控 opener;失败时保留同一裸 URL 和复制兜底。"},{"policyId":"P-KEY-URL-AUTO-OPEN","scope":"onboarding.application","severity":"critical","text":"公钥 URL 交付后调用受控 opener;失败时保留同一裸 URL 和复制兜底。"},{"policyId":"P-KEY-TOOL-DOWNLOAD","scope":"onboarding.application","severity":"critical","text":"新建 WEBAPP 或 MOBILEAPP IOS|ANDROID|ALL 在首次等待或写入前必须展示 https://opendocs.alipay.com/isv/02kipk;HarmonyOS 除外。仅空候选且缺公钥时自动下载,非空候选只在用户要求时下载;只下载,不安装、启动或生成密钥。"},{"policyId":"P-A2M-SANDBOX","scope":"integration.aipay","severity":"critical","text":"AI 按量付费沙箱 serviceId 固定为 api_mock_service_id,SDK 使用沙箱网关;生产网关和真实 serviceId 只用于正式配置。","runtimeText":"A2M 沙箱固定 api_mock_service_id 与沙箱网关;生产值仅用于正式配置。"},{"policyId":"P-A2M-SUCCESS","scope":"integration.aipay","severity":"critical","text":"自动联调只有在 HTTP 200、非空可归属资源、无明确业务失败且适用 Payment-Validation 有效时通过。","runtimeText":"联调仅在 HTTP 200、资源可归属、无业务失败且适用回执有效时通过。"},{"policyId":"P-SANDBOX-CREDENTIAL-EXCEPTION","scope":"integration.sandbox","severity":"critical","text":"Unix/macOS/Linux 只允许快速沙箱实际返回且已校验的买家登录/支付密码在沙箱摘要和付款体验中经 stdin 临时渲染。Windows 手工沙箱允许用户在 sandbox.windows.manual_setup 后提供当前沙箱应用的 APPID、支付宝公钥、应用公钥、与当前开发语言匹配的应用私钥和沙箱账号信息;Agent 只能将其用于已确认目标项目的受保护配置,不得在回复、摘要、普通日志或业务状态中复述或另存原文。两类例外均不得扩展到生产环境账号、应用私钥或密码。","runtimeText":"仅登记且已校验的快速沙箱或 Windows 手工沙箱资料可按 flow 用于受保护项目;禁复述、另存或扩展到生产值。"},{"policyId":"P-SENSITIVE-STATE","scope":"all","severity":"critical","text":"用户公钥仅用于当前 app key/verify-key/verify-key-and-audit 调用,禁止在回复、摘要、普通日志、跨会话状态或宽权限临时文件中复述或保存;除登记的沙箱例外外,私钥、token、Payment-Proof、签名串、完整支付表单和未脱敏响应同样不得进入这些位置;临时 URL 仅可由登记动作校验后在该动作正文展示。","runtimeText":"公钥限当前 app 调用且禁复述/保存;敏感值禁入回复/摘要/日志/状态/宽权限文件;临时 URL 仅由登记动作展示。"},{"policyId":"P-PROJECT-PROTECT","scope":"integration","severity":"critical","text":"不得清空、覆盖或删除用户既有项目;只在已确认目标路径和范围内做必要修改。","runtimeText":"禁清空/覆盖/删除既有项目;仅改已确认路径和范围。"},{"policyId":"P-CUSTOMER-OUTPUT-EXCLUSIVE","scope":"all","severity":"critical","text":"登记 runner/script 的 stdout 是唯一对客正文,禁止增删改写;阻塞消息送达后立即等待用户。","runtimeText":"登记 stdout 原文是唯一正文;阻塞送达后立即等待。"},{"policyId":"P-ONBOARDING-CONTROL-CHANNEL","scope":"onboarding","severity":"critical","text":"剔除 ALIPAY_AIPAY_INTERNAL 内部行后再处理 stdout;内部行禁止对客,正文为空时直接转移。","runtimeText":"内部行禁对客;正文为空按唯一终态转移。"},{"policyId":"P-AUTH-PAGE-DELIVERY-BARRIER","scope":"onboarding.authorization","severity":"critical","text":"`auth.page` stdout 必须先独立送达,再 opener 和有限轮询;不得合并 pending 或收口,也不等待确认。"},{"policyId":"P-KEY-PAGE-DELIVERY-BARRIER","scope":"onboarding.application","severity":"critical","text":"`application.key.page` stdout 必须先独立送达,再 opener 和有限轮询;不得合并 pending 或收口,也不等待确认。"},{"policyId":"P-DETERMINISTIC-HANDOFF","scope":"all","severity":"critical","text":"只执行 flow 动作处登记命令并按唯一终态立即转移;禁止追加命令重复解析,失败只走登记恢复。","runtimeText":"仅执行登记命令并按唯一终态转移;禁追加解析;失败只走登记恢复。"}],"officialUrls":{"aipaySite":"https://aipay.alipay.com/","aipaySkillSite":"https://aipay.alipay.com/?from=alipay-aipay-skill","merchantPlatform":"https://b.alipay.com/page/portal/home","openPlatform":"https://open.alipay.com/","openPlatformHome":"https://open.alipay.com","technicalSupport":"https://opensupport.alipay.com/support/intelligent-services?form=payskill","keyToolManual":"https://opendocs.alipay.com/isv/02kipk","keyToolMac":"https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=mac&channelType=WEB","keyToolWindows":"https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=win&channelType=WEB","androidSandboxAlipay":"https://mdn.alipayobjects.com/sandboxsys/afts/img/_itHRrdOD9oAAAAAAAAAAAAADgSLAQBr/original","sandboxToolFallback":"https://open.alipay.com/develop/sandbox/tool","sandboxGateway":"https://openapi-sandbox.dl.alipaydev.com/gateway.do","productionGateway":"https://openapi.alipay.com/gateway.do","openPlatformPaySkill":"https://open.alipay.com?form=payskill","traditionalPaymentSkill":"https://github.com/alipay/ai/tree/main/skills","dingtalkSupportGroupQr":"https://mdn.alipayobjects.com/huamei_5fajmi/afts/img/MlFpRKmYyLIAAAAARJAAAAgADlahAQFr/original","wecomCommunityGroupQr":"https://mdn.alipayobjects.com/huamei_5fajmi/afts/img/Cz0TTZVO7_4AAAAAQDAAAAgADlahAQFr/fmt.webp"},"authorizationProducts":[{"productName":"AI 按量付费","salesCode":"I1080300001000160457","scope":"app:all,machine_pay:write,agmnt:write"},{"productName":"AI 网页应用收款","salesCode":"I1080300001000041203","scope":"app:all,fast_instant_trade_pay:write"},{"productName":"AI 移动应用收款","salesCode":"I1080300001000041313","scope":"app:all,auth_alipay_apppay:write"}],"temporaryUrlAllowlist":{"authorization":{"host":"aipay.alipay.com","path":"/cli-auth","requiredQueryParams":["deviceCode","productCode","mccCode"],"optionalQueryParams":["platform"]},"publicKeyConfirmation":{"host":"aipay.alipay.com","path":"/public-key-confirm","requiredQueryParams":["keyConfirmToken"],"optionalQueryParams":[]},"windowsOnboardingRedirect":{"host":"aipay.alipay.com","path":"/","requiredQueryParams":["from","reason","platform_id"],"optionalQueryParams":[]}}}
1
+ {"schemaVersion":1,"rules":[{"policyId":"P-MCP-CONTRACT-FROZEN","scope":"all","severity":"critical","text":"只用当前 Skill 已验证的 MCP 脚本契约;禁止推断、改名或尝试近似方法。","runtimeText":"仅用已验证脚本;禁推断或尝试近似方法。"},{"policyId":"P-AUTH-BEFORE-ONBOARDING","scope":"onboarding","severity":"critical","text":"登录、scope 和 MCC 校验通过前,禁止 onboarding 查询或写入。","runtimeText":"登录/scope/MCC 未通过前禁查询和写入。"},{"policyId":"P-SIGNING-WRITE","scope":"onboarding.signing","severity":"critical","text":"签约材料校验通过后直接提交,不增加确认;缺失或非法时禁止提交。"},{"policyId":"P-SERVICE-WRITE","scope":"onboarding.service","severity":"critical","text":"服务创建摘要送达后直接创建;服务修改须取得绑定当前脱敏摘要的明确确认。"},{"policyId":"P-APPLICATION-WRITE","scope":"onboarding.application","severity":"critical","text":"完整新建材料或非空候选后的明确“新建”已构成创建确认;快照未变时不追加确认,公钥确认后直接提审。"},{"policyId":"P-WRITE-UNKNOWN","scope":"onboarding.write","severity":"critical","text":"非幂等写入结果不明时只读核验;仍无法确认则进入 UNKNOWN,禁止重试写入。"},{"policyId":"P-PARTIAL-RESULT","scope":"onboarding","severity":"critical","text":"签约、服务、应用分支独立记录,失败不回滚或覆盖其他成功结果。"},{"policyId":"P-MATERIAL-CATEGORY","scope":"onboarding.materials","severity":"required","text":"每轮只展示一个材料类别;同类一次收齐,已提供材料不得重复索取。"},{"policyId":"P-KEY-NO-PRIVATE","scope":"onboarding.application","severity":"critical","text":"禁止请求、处理、保存或输出生产私钥,也不提供密钥生成命令。"},{"policyId":"P-KEY-PUBLIC-USER","scope":"onboarding.application","severity":"critical","text":"应用公钥只能由用户提供;缺少时禁止 createKeyConfirmPage,也不得补全、改写或添加 PEM 头尾。"},{"policyId":"P-AUTH-URL","scope":"onboarding.authorization","severity":"critical","text":"授权链接只由 auth.sh 构造并校验固定域名、路径和参数;禁用 verification_url。"},{"policyId":"P-KEY-URL","scope":"onboarding.application","severity":"critical","text":"公钥确认只展示 createKeyConfirmPage 返回并校验的裸 URL;禁止二维码链接和 alipays://。"},{"policyId":"P-AUTH-URL-AUTO-OPEN","scope":"onboarding.authorization","severity":"critical","text":"授权 URL 交付后调用受控 opener;失败时保留同一裸 URL 和复制兜底。"},{"policyId":"P-KEY-URL-AUTO-OPEN","scope":"onboarding.application","severity":"critical","text":"公钥 URL 交付后调用受控 opener;失败时保留同一裸 URL 和复制兜底。"},{"policyId":"P-KEY-TOOL-DOWNLOAD","scope":"onboarding.application","severity":"critical","text":"新建 WEBAPP 或 MOBILEAPP IOS|ANDROID|ALL 在首次等待或写入前必须展示 https://opendocs.alipay.com/isv/02kipk;HarmonyOS 除外。仅空候选且缺公钥时自动下载,非空候选只在用户要求时下载;只下载,不安装、启动或生成密钥。"},{"policyId":"P-A2M-SANDBOX","scope":"integration.aipay","severity":"critical","text":"AI 按量付费沙箱 serviceId 固定为 api_mock_service_id,SDK 使用沙箱网关;生产网关和真实 serviceId 只用于正式配置。","runtimeText":"A2M 沙箱固定 api_mock_service_id 与沙箱网关;生产值仅用于正式配置。"},{"policyId":"P-A2M-SUCCESS","scope":"integration.aipay","severity":"critical","text":"自动联调只有在 HTTP 200、非空可归属资源、无明确业务失败且适用 Payment-Validation 有效时通过。","runtimeText":"联调仅在 HTTP 200、资源可归属、无业务失败且适用回执有效时通过。"},{"policyId":"P-SANDBOX-CREDENTIAL-EXCEPTION","scope":"integration.sandbox","severity":"critical","text":"Unix/macOS/Linux 只允许快速沙箱实际返回且已校验的买家登录/支付密码在沙箱摘要和付款体验中经 stdin 临时渲染。Windows 手工沙箱允许用户在 sandbox.windows.manual_setup 后提供当前沙箱应用的 APPID、支付宝公钥、应用公钥、与当前开发语言匹配的应用私钥和沙箱账号信息;Agent 只能将其用于已确认目标项目的受保护配置,不得在回复、摘要、普通日志或业务状态中复述或另存原文。两类例外均不得扩展到生产环境账号、应用私钥或密码。","runtimeText":"仅登记且已校验的快速沙箱或 Windows 手工沙箱资料可按 flow 用于受保护项目;禁复述、另存或扩展到生产值。"},{"policyId":"P-SENSITIVE-STATE","scope":"all","severity":"critical","text":"用户公钥仅用于当前 app key/verify-key/verify-key-and-audit 调用,禁止在回复、摘要、普通日志、跨会话状态或宽权限临时文件中复述或保存;除登记的沙箱例外外,私钥、token、Payment-Proof、签名串、完整支付表单和未脱敏响应同样不得进入这些位置;临时 URL 仅可由登记动作校验后在该动作正文展示。","runtimeText":"公钥限当前 app 调用且禁复述/保存;敏感值禁入回复/摘要/日志/状态/宽权限文件;临时 URL 仅由登记动作展示。"},{"policyId":"P-PROJECT-PROTECT","scope":"integration","severity":"critical","text":"不得清空、覆盖或删除用户既有项目;只在已确认目标路径和范围内做必要修改。","runtimeText":"禁清空/覆盖/删除既有项目;仅改已确认路径和范围。"},{"policyId":"P-CUSTOMER-OUTPUT-EXCLUSIVE","scope":"all","severity":"critical","text":"登记 runner/script 的 stdout 是唯一对客正文,禁止增删改写;阻塞消息送达后立即等待用户。","runtimeText":"登记 stdout 原文是唯一正文;阻塞送达后立即等待。"},{"policyId":"P-ONBOARDING-CONTROL-CHANNEL","scope":"onboarding","severity":"critical","text":"剔除 ALIPAY_AIPAY_INTERNAL 内部行后再处理 stdout;内部行禁止对客,正文为空时直接转移。","runtimeText":"内部行禁对客;正文为空按唯一终态转移。"},{"policyId":"P-AUTH-PAGE-DELIVERY-BARRIER","scope":"onboarding.authorization","severity":"critical","text":"`auth.page` stdout 必须先独立送达,再 opener 和有限轮询;不得合并 pending 或收口,也不等待确认。"},{"policyId":"P-KEY-PAGE-DELIVERY-BARRIER","scope":"onboarding.application","severity":"critical","text":"`application.key.page` stdout 必须先独立送达,再 opener 和有限轮询;不得合并 pending 或收口,也不等待确认。"},{"policyId":"P-DETERMINISTIC-HANDOFF","scope":"all","severity":"critical","text":"只执行 flow 动作处登记命令并按唯一终态立即转移;禁止追加命令重复解析,失败只走登记恢复。","runtimeText":"仅执行登记命令并按唯一终态转移;禁追加解析;失败只走登记恢复。"}],"officialUrls":{"aipaySite":"https://aipay.alipay.com/","aipaySkillSite":"https://aipay.alipay.com/?from=alipay-aipay-skill","merchantPlatform":"https://b.alipay.com/page/portal/home","openPlatform":"https://open.alipay.com/","openPlatformHome":"https://open.alipay.com","technicalSupport":"https://opensupport.alipay.com/support/intelligent-services?form=payskill","keyToolManual":"https://opendocs.alipay.com/isv/02kipk","keyToolMac":"https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=mac&channelType=WEB","keyToolWindows":"https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=win&channelType=WEB","androidSandboxAlipay":"https://mdn.alipayobjects.com/sandboxsys/afts/img/_itHRrdOD9oAAAAAAAAAAAAADgSLAQBr/original","sandboxToolFallback":"https://open.alipay.com/develop/sandbox/tool","sandboxGateway":"https://openapi-sandbox.dl.alipaydev.com/gateway.do","productionGateway":"https://openapi.alipay.com/gateway.do","openPlatformPaySkill":"https://open.alipay.com?form=payskill","traditionalPaymentSkill":"https://github.com/alipay/ai/tree/main/skills","dingtalkSupportGroupQr":"https://mdn.alipayobjects.com/huamei_5fajmi/afts/img/MlFpRKmYyLIAAAAARJAAAAgADlahAQFr/original","wecomCommunityGroupQr":"https://mdn.alipayobjects.com/huamei_5fajmi/afts/img/Cz0TTZVO7_4AAAAAQDAAAAgADlahAQFr/fmt.webp"},"authorizationProducts":[{"productName":"AI 按量付费","salesCode":"I1080300001000160457","scope":"app:all,machine_pay:write,agmnt:write"},{"productName":"AI 网页应用收款","salesCode":"I1080300001000041203","scope":"app:all,fast_instant_trade_pay:write"},{"productName":"AI 移动应用收款","salesCode":"I1080300001000041313","scope":"app:all,auth_alipay_apppay:write"}],"temporaryUrlAllowlist":{"authorization":{"host":"aipay.alipay.com","path":"/cli-auth","requiredQueryParams":["deviceCode","productCode","mccCode"],"optionalQueryParams":["platform"]},"publicKeyConfirmation":[{"host":"aipay.alipay.com","path":"/public-key-confirm","requiredQueryParams":["keyConfirmToken"],"optionalQueryParams":[]},{"host":"open.alipay.com","path":"/ai/ai-resource-center/public-key-confirm","requiredQueryParams":["keyConfirmToken"],"optionalQueryParams":[]}],"windowsOnboardingRedirect":{"host":"aipay.alipay.com","path":"/","requiredQueryParams":["from","reason","platform_id"],"optionalQueryParams":[]}}}
@@ -18,8 +18,10 @@ const policy = JSON.parse(fs.readFileSync(policyFile, "utf8"));
18
18
  if (/[\u0000-\u0020\u007f`()\[\]<>|]/.test(raw)) process.exit(1);
19
19
  let url;
20
20
  try { url = new URL(raw); } catch { process.exit(1); }
21
- const allowed = policy.temporaryUrlAllowlist[kind === "authorization" ? "authorization" : "publicKeyConfirmation"];
22
- if (!allowed || url.protocol !== "https:" || url.hostname !== allowed.host || url.pathname !== allowed.path || url.username || url.password || url.port || url.hash) process.exit(1);
21
+ const entries = policy.temporaryUrlAllowlist[kind === "authorization" ? "authorization" : "publicKeyConfirmation"];
22
+ const allowed = (Array.isArray(entries) ? entries : [entries]).find((entry) =>
23
+ entry && url.hostname === entry.host && url.pathname === entry.path);
24
+ if (!allowed || url.protocol !== "https:" || url.username || url.password || url.port || url.hash) process.exit(1);
23
25
  if (kind === "authorization") {
24
26
  const required = allowed.requiredQueryParams;
25
27
  const optional = allowed.optionalQueryParams;
@@ -343,8 +343,10 @@ function validateUrl(value, policy, urlKind, expected = {}) {
343
343
  } catch {
344
344
  throw new Error('officialTemporaryUrl 不是合法 URL');
345
345
  }
346
- const allowed = policy.temporaryUrlAllowlist[urlKind];
347
- if (!allowed || parsed.protocol !== 'https:' || parsed.hostname !== allowed.host || parsed.pathname !== allowed.path || parsed.username || parsed.password || parsed.port || parsed.hash) {
346
+ const entries = policy.temporaryUrlAllowlist[urlKind];
347
+ const allowed = (Array.isArray(entries) ? entries : [entries]).find((entry) =>
348
+ entry && parsed.hostname === entry.host && parsed.pathname === entry.path);
349
+ if (!allowed || parsed.protocol !== 'https:' || parsed.username || parsed.password || parsed.port || parsed.hash) {
348
350
  throw new Error(`officialTemporaryUrl 不符合 ${urlKind} allowlist`);
349
351
  }
350
352
  if (urlKind === 'authorization') {
@@ -307,17 +307,14 @@ function removeTransactionPath(target) {
307
307
 
308
308
  function reconcileInstallTransaction(skillDir, transactionId, channel) {
309
309
  const artifacts = transactionArtifacts(skillDir, transactionId, channel);
310
- let current = readLocalVersion(skillDir);
311
- if (!current && isRecoverableSkillRoot(artifacts.backup)) {
310
+ if (isRecoverableSkillRoot(artifacts.backup)) {
312
311
  try {
313
312
  removeTransactionPath(skillDir);
314
313
  fs.renameSync(artifacts.backup, skillDir);
315
314
  } catch {}
316
- current = readLocalVersion(skillDir);
317
315
  }
318
316
  removeTransactionPath(artifacts.staging);
319
- if (isRecoverableSkillRoot(skillDir)) removeTransactionPath(artifacts.backup);
320
- return current;
317
+ return { version: readLocalVersion(skillDir), backupPresent: fs.existsSync(artifacts.backup) };
321
318
  }
322
319
 
323
320
  async function installVersion(skillDir, packageVersion, channel) {
@@ -351,10 +348,11 @@ async function installVersion(skillDir, packageVersion, channel) {
351
348
  } catch { /* An invalid receipt never permits a session switch. */ }
352
349
  }
353
350
  }
351
+ const reconciled = reconcileInstallTransaction(skillDir, transactionId, channel);
354
352
  return {
355
353
  ok: result.status === 0 && !result.error,
356
354
  reason: result.status === 0 && !result.error ? 'ok' : packageManagerFailureKind(result),
357
- version: reconcileInstallTransaction(skillDir, transactionId, channel),
355
+ ...reconciled,
358
356
  session,
359
357
  };
360
358
  }
@@ -395,8 +393,9 @@ export async function runSelfUpdateCli(argv = process.argv) {
395
393
  if (installed.session && installed.session.version===latest.raw) {
396
394
  return {kind:'SESSION_READY',version:latest,skillDir:installed.session.skillDir};
397
395
  }
396
+ if (installed.backupPresent) return { kind: 'INSTALL_FAILED' };
398
397
  const updated = installed.version || readLocalVersion(skillDir);
399
- if (updated && readChannel(skillDir) === channel && compareVersions(updated, latest) >= 0) return { kind: 'UPDATED', version: updated };
398
+ if (installed.ok && updated && readChannel(skillDir) === channel && compareVersions(updated, latest) >= 0) return { kind: 'UPDATED', version: updated };
400
399
  if (installed.reason === 'network') return { kind: 'RETRY_WITH_NETWORK' };
401
400
  if (installed.reason === 'local') return { kind: 'CHECK_FAILED' };
402
401
  return { kind: installed.ok ? 'VERIFY_FAILED' : 'INSTALL_FAILED' };
@@ -158,8 +158,6 @@ function Invoke-NodeBootstrap([string]$Action, [string]$Root) {
158
158
  if ($MyInvocation.InvocationName -ne '.') {
159
159
  $ErrorActionPreference = 'Stop'
160
160
  try {
161
- . (Join-Path $PSScriptRoot 'architecture.ps1')
162
- if ((Get-AipayNativeArchitecture) -ne 'x64') { throw 'UNSUPPORTED_PLATFORM' }
163
161
  $localData = $env:LOCALAPPDATA
164
162
  if (-not $localData) { $localData = [Environment]::GetFolderPath('LocalApplicationData') }
165
163
  if (-not $localData -or -not [IO.Path]::IsPathRooted($localData)) { throw 'STORAGE_FAILED' }
package/skill-pre/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.7-pre
1
+ 1.7.8-pre
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync as nativeSpawn } from 'node:child_process';
4
- const { spawnSync } = process.platform === 'win32'
5
- ? await import('./windows/execution.mjs')
6
- : { spawnSync: nativeSpawn };
7
4
  import path from 'node:path';
8
5
  import process from 'node:process';
9
6
  import { fileURLToPath } from 'node:url';
@@ -69,7 +66,10 @@ function cliArgv(args) {
69
66
  return [process.execPath, fileURLToPath(import.meta.url), ...args];
70
67
  }
71
68
 
72
- function runShell(script, cwd, args) {
69
+ async function runShell(script, cwd, args) {
70
+ const spawnSync = process.platform === 'win32'
71
+ ? (await import('./windows/execution.mjs')).spawnSync
72
+ : nativeSpawn;
73
73
  const captureWindowsSandbox = process.platform === 'win32' && path.basename(script) === 'sandbox_config.sh';
74
74
  const result = spawnSync('bash', [script, ...args], {
75
75
  cwd,
@@ -307,17 +307,14 @@ function removeTransactionPath(target) {
307
307
 
308
308
  function reconcileInstallTransaction(skillDir, transactionId, channel) {
309
309
  const artifacts = transactionArtifacts(skillDir, transactionId, channel);
310
- let current = readLocalVersion(skillDir);
311
- if (!current && isRecoverableSkillRoot(artifacts.backup)) {
310
+ if (isRecoverableSkillRoot(artifacts.backup)) {
312
311
  try {
313
312
  removeTransactionPath(skillDir);
314
313
  fs.renameSync(artifacts.backup, skillDir);
315
314
  } catch {}
316
- current = readLocalVersion(skillDir);
317
315
  }
318
316
  removeTransactionPath(artifacts.staging);
319
- if (isRecoverableSkillRoot(skillDir)) removeTransactionPath(artifacts.backup);
320
- return current;
317
+ return { version: readLocalVersion(skillDir), backupPresent: fs.existsSync(artifacts.backup) };
321
318
  }
322
319
 
323
320
  async function installVersion(skillDir, packageVersion, channel) {
@@ -351,10 +348,11 @@ async function installVersion(skillDir, packageVersion, channel) {
351
348
  } catch { /* An invalid receipt never permits a session switch. */ }
352
349
  }
353
350
  }
351
+ const reconciled = reconcileInstallTransaction(skillDir, transactionId, channel);
354
352
  return {
355
353
  ok: result.status === 0 && !result.error,
356
354
  reason: result.status === 0 && !result.error ? 'ok' : packageManagerFailureKind(result),
357
- version: reconcileInstallTransaction(skillDir, transactionId, channel),
355
+ ...reconciled,
358
356
  session,
359
357
  };
360
358
  }
@@ -395,8 +393,9 @@ export async function runSelfUpdateCli(argv = process.argv) {
395
393
  if (installed.session && installed.session.version===latest.raw) {
396
394
  return {kind:'SESSION_READY',version:latest,skillDir:installed.session.skillDir};
397
395
  }
396
+ if (installed.backupPresent) return { kind: 'INSTALL_FAILED' };
398
397
  const updated = installed.version || readLocalVersion(skillDir);
399
- if (updated && readChannel(skillDir) === channel && compareVersions(updated, latest) >= 0) return { kind: 'UPDATED', version: updated };
398
+ if (installed.ok && updated && readChannel(skillDir) === channel && compareVersions(updated, latest) >= 0) return { kind: 'UPDATED', version: updated };
400
399
  if (installed.reason === 'network') return { kind: 'RETRY_WITH_NETWORK' };
401
400
  if (installed.reason === 'local') return { kind: 'CHECK_FAILED' };
402
401
  return { kind: installed.ok ? 'VERIFY_FAILED' : 'INSTALL_FAILED' };
@@ -1,14 +1,18 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import {run, fail} from './host.mjs';
4
- const managedGitVersion=JSON.parse(fs.readFileSync(new URL('./policy.json',import.meta.url),'utf8')).git.version;
4
+ function managedGitVersion() {
5
+ try { return JSON.parse(fs.readFileSync(new URL('./policy.json',import.meta.url),'utf8')).git.version; }
6
+ catch { return ''; }
7
+ }
5
8
 
6
9
  export function gitCandidates(env = process.env) {
7
10
  if (env.ALIPAY_GIT_BASH) return [env.ALIPAY_GIT_BASH];
11
+ const managedVersion = env.LOCALAPPDATA ? managedGitVersion() : '';
8
12
  const candidates = [
9
13
  path.win32.join(env.ProgramFiles || 'C:/Program Files', 'Git/bin/bash.exe'),
10
14
  ...(env.LOCALAPPDATA ? [path.win32.join(env.LOCALAPPDATA, 'Programs/Git/bin/bash.exe'),
11
- path.win32.join(env.LOCALAPPDATA, `Programs/Git-Aipay-${managedGitVersion}/bin/bash.exe`)] : [])
15
+ ...(managedVersion ? [path.win32.join(env.LOCALAPPDATA, `Programs/Git-Aipay-${managedVersion}/bin/bash.exe`)] : [])] : [])
12
16
  ];
13
17
  const search = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] || '';
14
18
  for (const directory of search.split(';').filter(p => path.win32.isAbsolute(p))) {
@@ -1,14 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
- import {policy,run,fail} from './preflight.mjs';
4
+ import {loadPolicy,run,fail} from './preflight.mjs';
5
5
  import {findGitBash} from './git-discovery.mjs';
6
6
  import {powershell,psEnv} from './host.mjs';
7
7
 
8
8
  export function installGitOfficial({env=process.env,execute=run,exists=fs.existsSync,
9
9
  read=fs.readFileSync,remove=fs.rmSync,discover=findGitBash,root,
10
- signatureCommand=powershell,beforeAttempt=()=>{}}={}) {
11
- const asset=policy.git;
10
+ signatureCommand=powershell,beforeAttempt=()=>{},asset=loadPolicy('Git Bash').git}={}) {
12
11
  if(!root || !env.LOCALAPPDATA || !path.win32.isAbsolute(env.LOCALAPPDATA) ||
13
12
  !/^https:\/\/github\.com\/git-for-windows\/git\/releases\/download\/v[0-9.]+\.windows\.[0-9]+\/Git-[0-9.]+-64-bit\.exe$/u.test(asset.url) ||
14
13
  !/^[a-f0-9]{64}$/u.test(asset.sha256)) fail('GIT_ASSET_INVALID','Git Bash');
@@ -5,7 +5,18 @@ import crypto from 'node:crypto';
5
5
  import {fileURLToPath} from 'node:url';
6
6
 
7
7
  export const directory = path.dirname(fileURLToPath(import.meta.url));
8
- export const policy = JSON.parse(fs.readFileSync(path.join(directory,'policy.json'),'utf8'));
8
+ export function loadPolicy(dependency, read = fs.readFileSync) {
9
+ try {
10
+ const policy = JSON.parse(read(path.join(directory,'policy.json'),'utf8'));
11
+ const asset = dependency === 'Git Bash' ? policy?.git : policy?.jq;
12
+ if (!asset || typeof asset !== 'object' || typeof asset.url !== 'string' ||
13
+ typeof asset.sha256 !== 'string' || (dependency === 'Git Bash' && typeof asset.version !== 'string')) {
14
+ throw new Error('incomplete policy');
15
+ }
16
+ return policy;
17
+ }
18
+ catch { fail('POLICY_UNAVAILABLE',dependency); }
19
+ }
9
20
  const slash = p => p.replaceAll('\\','/');
10
21
  let invocationContext;
11
22
  import {fail, run, checkWritable} from './host.mjs';
@@ -69,7 +80,7 @@ export function locateJq() {
69
80
  }
70
81
  export function locate() {
71
82
  if(process.platform!=='win32') fail('WINDOWS_ADAPTER_REQUIRES_WINDOWS');
72
- if(Number(process.versions.node.split('.')[0])<policy.nodeMinimumMajor) fail('DEPENDENCY_VERSION','Node');
83
+ if(Number(process.versions.node.split('.')[0])<18) fail('DEPENDENCY_VERSION','Node');
73
84
  requireWindowsX64Node();
74
85
  const foundGit=findGitBash();
75
86
  if(!foundGit) fail('DEPENDENCY_MISSING','Git Bash');
@@ -7,7 +7,7 @@ import {findGitBash} from './git-discovery.mjs';
7
7
  import {findWinget} from './winget.mjs';
8
8
  import {installGitOfficial} from './git-install.mjs';
9
9
  import {powershell} from './host.mjs';
10
- import {policy,run,stateRoot,readState,saveState,withLock,ensureReady,locateJq,fail,report} from './preflight.mjs';
10
+ import {loadPolicy,run,stateRoot,readState,saveState,withLock,ensureReady,locateJq,fail,report} from './preflight.mjs';
11
11
 
12
12
  export function claimPreparation(state,stage,{source='official',repair=false,authorized=false}={}) {
13
13
  if(!authorized) fail('PREPARATION_AUTHORIZATION_REQUIRED');
@@ -65,7 +65,7 @@ export function prepareDependencies(state,{authorized=false,repair=false,save=()
65
65
  gitReady=()=>findGitBash(),jqReady=()=>locateJq(),
66
66
  cliReady=()=>{const cli=path.join(os.homedir(),'.local/bin/alipay-cli.exe');return fs.existsSync(cli)&&run(cli,['version']).status===0;},
67
67
  installWinget=installGitWithWinget,installOfficial=installGitOfficial,
68
- prepareJq=(beforeAttempt)=>{requireAssetDownloader();beforeAttempt();installAsset('jq',policy.jq);},
68
+ prepareJq=(beforeAttempt)=>{const asset=loadPolicy('jq').jq;requireAssetDownloader();beforeAttempt();installAsset('jq',asset);},
69
69
  prepareCli=(beforeAttempt)=>{powershell();beforeAttempt();if(refreshWindowsCli()!=='READY')fail('CLI_INSTALL_FAILED','alipay-cli');}
70
70
  }={}) {
71
71
  if(!authorized) fail('PREPARATION_AUTHORIZATION_REQUIRED');
@@ -6,12 +6,15 @@ const prefix = 'ALIPAY_AIPAY_INTERNAL:';
6
6
 
7
7
  // Optional tool failures do not prevent independent project code development.
8
8
  function codeOnlyFailure(error) {
9
+ if (error.reason === 'SHELL_TRANSPORT_FAILED' && error.dependency !== 'Node') return true;
10
+ if (error.reason === 'NOT_GIT_BASH' && error.dependency === 'ALIPAY_GIT_BASH') return true;
11
+ if (error.dependency === 'Node') return false;
9
12
  const tools = ['Git Bash', 'git', 'jq', 'alipay-cli', 'cli', 'curl', 'PowerShell'];
10
13
  if (!tools.includes(error.dependency)) return false;
11
14
  return ['DEPENDENCY_MISSING', 'DEPENDENCY_VERSION', 'EXECUTION_FAILED',
12
15
  'WINGET_UNAVAILABLE', 'GIT_INSTALL_FAILED', 'GIT_VERIFY_FAILED', 'CLI_INSTALL_FAILED',
13
16
  'DOWNLOAD_FAILED', 'INTEGRITY_FAILED', 'SIGNATURE_FAILED', 'RETRY_BUDGET_EXHAUSTED',
14
- 'USER_SCOPE_REQUIRED'].includes(error.reason);
17
+ 'USER_SCOPE_REQUIRED', 'POLICY_UNAVAILABLE'].includes(error.reason);
15
18
  }
16
19
 
17
20
  // This is a routing result, never proof of a successful payment or onboarding.
@@ -34,11 +34,11 @@ stdout 为空、原始安装器输出不对客,只映射根协议的唯一刷
34
34
 
35
35
  CLI 刷新策略与非 Windows 保持同一会话预算,不锁定 0.1.7 或其哈希;Windows 交由正式 PowerShell 安装器选择版本。jq 优先复用能通过版本和实际 filter 检查的既有原生工具;缺失或不可用时安装固定 1.7.1,官方 HTTPS 资产和 SHA-256 位于 `scripts/windows/policy.json`。Node 优先复用当前可运行的 Windows x64 Node >=18;缺失时由根启动器的 PowerShell 引导准备官方 LTS 用户级 ZIP,SHA-256 验证后返回绝对可执行路径,首次一次、明确修复最多一次。Git Bash 从常见目录和本机 PATH 的 Git 安装布局定位,也可由 `ALIPAY_GIT_BASH` 指定绝对路径;必须验证 Git for Windows 版本、cygpath 和 Bash 的 MINGW/MSYS 平台,不使用 System32/WSL Bash,显式指定错误时停止而不偷偷换工具。
36
36
 
37
- 初次缺 Git Bash、jq 或本机 CLI 无法启动且当前任务授权准备时,按 Git Bash → jq → 必要的 CLI 修复 → 完整复检执行。CLI 已可用时不重复安装。Git 优先复用已有可运行的 Git for Windows;缺失时优先调用用户级 winget,无法运行或未通过复检时使用 `scripts/windows/policy.json` 固定的 Git for Windows 官方 x64 安装包(当前 2.56.0.2)。官方包下载后先校验固定 SHA-256,再由系统 PowerShell 验证 Authenticode 有效,之后安装到 `%LOCALAPPDATA%/Programs/Git-Aipay-<version>`,并实测 git、bash、cygpath。下载或校验失败不运行安装包。两种来源均不可用时保持 BLOCKED,展示原因及官方手工安装路径,不静默跳站点。
37
+ 初次缺 Git Bash、jq 或本机 CLI 无法启动且当前任务授权准备时,按 Git Bash → jq → 必要的 CLI 修复 → 完整复检执行。CLI 已可用时不重复安装。Git 优先复用已有可运行的 Git for Windows;缺失时优先调用用户级 winget,无法运行或未通过复检时使用 `scripts/windows/policy.json` 固定的 Git for Windows 官方 x64 安装包(当前 2.56.0.2)。官方包下载后先校验固定 SHA-256,再由系统 PowerShell 验证 Authenticode 有效,之后安装到 `%LOCALAPPDATA%/Programs/Git-Aipay-<version>`,并实测 git、bash、cygpath。下载或校验失败不运行安装包。两种来源均不可用时按根意图返回 CODE_ONLY 或 BLOCKED,展示原因及官方手工安装路径,不静默跳站点。
38
38
 
39
- jq 使用固定 1.7.1 官方资产和 SHA-256,已有健康版本直接复用;CLI 仍使用正式 PowerShell 安装器选择当前版本。每个依赖和 Git 的两种安装来源分别记录实际开始的安装尝试,确认来源前的只读探测不消耗预算;Git 的 winget 失败可在同次准备中尝试官方固定包。初次每个来源最多一次,用户明确修复后每个来源最多再一次;失败阶段可续跑,已健康的阶段不重装。状态记录不含业务材料;复检始终只读且不消耗安装预算。网络、实际权限、签名或完整性失败保持 BLOCKED,不轮换未经验证的镜像、使用 WSL、提权或修改执行策略。
39
+ jq 使用固定 1.7.1 官方资产和 SHA-256,已有健康版本直接复用;CLI 仍使用正式 PowerShell 安装器选择当前版本。每个依赖和 Git 的两种安装来源分别记录实际开始的安装尝试,确认来源前的只读探测不消耗预算;Git 的 winget 失败可在同次准备中尝试官方固定包。初次每个来源最多一次,用户明确修复后每个来源最多再一次;失败阶段可续跑,已健康的阶段不重装。状态记录不含业务材料;复检始终只读且不消耗安装预算。可选工具的网络、签名或完整性失败按第 21 行分流;实际存储故障仍为 BLOCKED。不轮换未经验证的镜像、使用 WSL、提权或修改执行策略。
40
40
 
41
- 环境诊断和次数保存在 `%LOCALAPPDATA%/alipay-aipay-pre/windows/environment.json`;旧版全局次数无法归属具体安装阶段,迁移时保留阻断原因并从新版本的有限分阶段预算重新计数。锁短时占用或状态损坏时,完整工具检查通过仍可 READY;自动安装保持阻断,不删除锁或状态绕过预算。用户修好依赖后,只读复检可再次执行,无论此前安装预算是否用尽:
41
+ 环境诊断和次数保存在 `%LOCALAPPDATA%/alipay-aipay-pre/windows/environment.json`;旧版全局次数无法归属具体安装阶段,迁移时保留阻断原因并从新版本的有限分阶段预算重新计数。锁短时占用或状态损坏时,完整工具检查通过仍可 READY;自动安装保持阻断,不删除锁或状态绕过预算。CODE_ONLY 的恢复使用第 23 行 `windows-entry`;以下仅适用于 BLOCKED。用户修好依赖后,只读复检可再次执行,无论此前安装预算是否用尽:
42
42
 
43
43
  ```text
44
44
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env retry --repair-confirmed true --root-intent "<ROOT_INTENT>" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>" --windows-route BLOCKED
@@ -52,7 +52,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env prepare --authorize
52
52
 
53
53
  如果之前只是缺少授权或没有找到安装来源,尚未实际尝试该阶段,则使用同一命令但省略 `--repair-confirmed true`,继续首次准备;不能把新授权当成已消耗预算后的额外重试许可。
54
54
 
55
- 仅退出 0、唯一 `CLI_ENV:READY` 和唯一根意图匹配的 `WINDOWS_ROUTE=READY` 可更新分流。其他结果保持 BLOCKED,说明具体失败原因;不重建会话、重复计数或重放业务操作。用户明确选择站点或平台执行映射不支持时,可走原 LEGACY 分支;可恢复的依赖失败不能自动转站点。
55
+ 仅退出 0、唯一 `CLI_ENV:READY` 和唯一根意图匹配的 `WINDOWS_ROUTE=READY` 可恢复沙箱及开通能力。BLOCKED 恢复命令的其他结果保持 BLOCKED,说明具体失败原因;不重建会话、重复计数或重放业务操作。CODE_ONLY 按第 23 行重新取得入口分流。用户明确选择站点或平台执行映射不支持时,可走原 LEGACY 分支;可恢复的依赖失败不能自动转站点。
56
56
 
57
57
  ## 路径、权限与进程
58
58