@alipay/alipay-aipay 1.8.4 → 1.8.6

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.
@@ -10,6 +10,7 @@ import { getCommandContext, getAllCommandContexts, detectCurrentAgentTarget, DEF
10
10
  import { installSkillFiles, readVersionRecord, writeVersionRecordWithFallback, } from '../utils/file.js';
11
11
  import { checkAlipayCli } from '../utils/alipay-cli.js';
12
12
  import { checkJq, isJqRequiredPlatform, resolveManagedJqAsset } from '../utils/jq.js';
13
+ import { createUpdatedProfileContent, DEFAULT_CHINFO } from '../utils/profile.js';
13
14
  function resolveInstallPlan(cwd, target, channel) {
14
15
  if (target !== 'all') {
15
16
  const context = getCommandContext(cwd, target, channel);
@@ -30,6 +31,7 @@ export async function installCommand(cwd, options = {}) {
30
31
  const { skillName: SKILL_NAME, displayName } = getChannelConfig(channel);
31
32
  const SKILLS = [{ name: SKILL_NAME, displayName }];
32
33
  const target = options.target ?? 'all';
34
+ const internalChannelInstall = process.env.ALIPAY_AIPAY_CHANNEL_OFFER_INSTALL === '1';
33
35
  const deferWindowsTools = process.platform === 'win32' && !options.skipTools;
34
36
  const skipTools = (options.skipTools ?? false) || deferWindowsTools;
35
37
  const jqRequired = isJqRequiredPlatform();
@@ -120,6 +122,12 @@ export async function installCommand(cwd, options = {}) {
120
122
  for (const ctx of contexts) {
121
123
  const targetSkillDir = path.join(ctx.targetDir, SKILL_NAME);
122
124
  try {
125
+ const profileContent = internalChannelInstall
126
+ ? undefined
127
+ : await createUpdatedProfileContent(targetSkillDir, {
128
+ chinfo: options.chinfo ?? DEFAULT_CHINFO,
129
+ vid: options.vid,
130
+ });
123
131
  const existingRecord = await readVersionRecord(ctx);
124
132
  if (existingRecord) {
125
133
  console.log(chalk.yellow(`检测到已有安装记录,直接覆盖更新: ${ctx.targetDirLabel}/${SKILL_NAME}/`));
@@ -127,7 +135,7 @@ export async function installCommand(cwd, options = {}) {
127
135
  console.log('');
128
136
  }
129
137
  try {
130
- await installSkillFiles(PACKAGE_ROOT, ctx.targetDir, channel);
138
+ await installSkillFiles(PACKAGE_ROOT, ctx.targetDir, channel, { profileContent });
131
139
  }
132
140
  catch (error) {
133
141
  const failure = error;
@@ -138,7 +146,7 @@ export async function installCommand(cwd, options = {}) {
138
146
  const sessionModule = pathToFileURL(path.join(PACKAGE_ROOT, 'skill', 'references', 'normal', 'scripts', 'session_skill.mjs')).href;
139
147
  const { createSessionSkill } = await import(sessionModule);
140
148
  const receipt = createSessionSkill({ sourceDir: path.join(PACKAGE_ROOT, channel === 'pre' ? 'skill-pre' : 'skill'), channel, version,
141
- packageVersion: JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version,
149
+ packageVersion: JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version, profileContent,
142
150
  targetDir: targetSkillDir });
143
151
  spinner.warn(chalk.yellow('原 Skill 目录未更新;已准备仅供当前会话加载的新版副本。'));
144
152
  console.log(`请当前 Agent 读取 ${path.join(receipt.skillDir, 'SKILL.md')} 并按该版本启动协议继续;后续命令使用此目录。下次会话仍可能加载原目录旧版。`);
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { installCurrentSkillCommand } from './commands/install-current.js';
7
7
  import { uninstallCommand } from './commands/uninstall.js';
8
8
  import { updateCommand } from './commands/update.js';
9
9
  import { prepareToolsCommand } from './commands/prepare-tools.js';
10
+ import { parseChinfo, parseVid } from './utils/profile.js';
10
11
  const program = new Command();
11
12
  function parseTarget(value) {
12
13
  if (value === 'agents' || value === 'codex' || value === 'claude' || value === 'auto' || value === 'all') {
@@ -26,6 +27,8 @@ program
26
27
  .option('--channel <channel>', 'Skill 渠道: stable | pre', parseChannel, 'stable')
27
28
  .description('安装支付宝 AI 付接入 Skill(Skill 文件优先完成,工具准备不阻塞安装)')
28
29
  .option('--target <target>', '安装目标: all | agents | codex | claude | auto', 'all')
30
+ .option('--chinfo <chinfo>', 'Skill 获取来源标识', parseChinfo)
31
+ .option('--vid <vid>', 'Skill 访问来源标识', parseVid)
29
32
  .option('--with-jq', '兼容参数;macOS/Linux 缺少 jq 时默认已在 detached 后台准备')
30
33
  .option('--skip-tools', '只安装 Skill 文件,跳过 alipay-cli、jq 检查与安装')
31
34
  .action(async (options) => {
@@ -33,6 +36,8 @@ program
33
36
  target: parseTarget(options.target),
34
37
  channel: options.channel,
35
38
  skipTools: Boolean(options.skipTools),
39
+ chinfo: options.chinfo,
40
+ vid: options.vid,
36
41
  });
37
42
  });
38
43
  // uninstall 命令
@@ -69,7 +74,7 @@ program
69
74
  });
70
75
  // 默认行为:无参数时直接执行安装流程
71
76
  if (process.argv.length === 2) {
72
- installCommand(process.cwd(), { target: 'all', skipTools: false });
77
+ installCommand(process.cwd(), { target: 'all', skipTools: false, chinfo: 'origin' });
73
78
  }
74
79
  else {
75
80
  program.parse();
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import { randomUUID } from 'crypto';
4
4
  import { setTimeout as delay } from 'timers/promises';
5
5
  import { getChannelConfig, getSkillVersion, readInstalledChannel } from './channels.js';
6
+ import { PROFILE_FILE, readPreservedProfile } from './profile.js';
6
7
  /**
7
8
  * 检查目录是否存在
8
9
  */
@@ -236,8 +237,8 @@ export async function writeVersionRecordWithFallback(ctx, record) {
236
237
  * 安装 Skill 文件到目标目录
237
238
  * 从包内 skill/ 目录复制 SKILL.md + references/ 到目标 skills 目录
238
239
  */
239
- export async function installSkillFiles(packageRoot, targetDir, channel = 'stable') {
240
- return installSkillFilesAtPath(packageRoot, path.join(targetDir, getChannelConfig(channel).skillName), { channel });
240
+ export async function installSkillFiles(packageRoot, targetDir, channel = 'stable', options = {}) {
241
+ return installSkillFilesAtPath(packageRoot, path.join(targetDir, getChannelConfig(channel).skillName), { ...options, channel });
241
242
  }
242
243
  // Directory replacement can be briefly refused by Windows. Keep the original
243
244
  // transaction/rollback semantics; never fall back to mixing files in place.
@@ -267,6 +268,10 @@ export async function installSkillFilesAtPath(packageRoot, skillTargetDir, optio
267
268
  const channel = options.channel ?? 'stable';
268
269
  const { skillName: SKILL_NAME, sourceDir: SKILL_DIR } = getChannelConfig(channel);
269
270
  const transactionId = options.transactionId;
271
+ const suppliedProfile = options.profileContent;
272
+ if (suppliedProfile && suppliedProfile.length > 64 * 1024) {
273
+ throw new Error(`${PROFILE_FILE} 内容过大`);
274
+ }
270
275
  if (transactionId && !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(transactionId)) {
271
276
  throw new Error('无效的 Skill 安装事务 ID');
272
277
  }
@@ -305,9 +310,13 @@ export async function installSkillFilesAtPath(packageRoot, skillTargetDir, optio
305
310
  let previousInstallMoved = false;
306
311
  let stagingCommitted = false;
307
312
  try {
313
+ const profileContent = suppliedProfile ?? await readPreservedProfile(resolvedSkillTargetDir);
308
314
  await fs.copyFile(path.join(skillSourceDir, 'SKILL.md'), path.join(stagingDir, 'SKILL.md'));
309
315
  await copyDirWithPerms(path.join(skillSourceDir, 'references'), path.join(stagingDir, 'references'));
310
316
  await fs.copyFile(path.join(skillSourceDir, 'VERSION'), path.join(stagingDir, 'VERSION'));
317
+ if (profileContent) {
318
+ await fs.writeFile(path.join(stagingDir, PROFILE_FILE), profileContent, { mode: 0o600 });
319
+ }
311
320
  if (await pathExists(resolvedSkillTargetDir)) {
312
321
  try {
313
322
  await renameInstallDirectory(resolvedSkillTargetDir, backupDir);
@@ -0,0 +1,63 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export const PROFILE_FILE = 'PROFILE';
4
+ export const DEFAULT_CHINFO = 'origin';
5
+ export const MAX_PROFILE_BYTES = 64 * 1024;
6
+ function parseProfileValue(value, option) {
7
+ if (typeof value !== 'string' || value.trim() !== value || [...value].length < 1 || [...value].length > 128
8
+ || /[\p{Cc}\p{Cs}]/u.test(value)) {
9
+ throw new Error(`无效的 --${option}:必须是 1-128 个字符,且不能包含首尾空白或控制字符`);
10
+ }
11
+ return value;
12
+ }
13
+ export function parseChinfo(value) {
14
+ return parseProfileValue(value, 'chinfo');
15
+ }
16
+ export function parseVid(value) {
17
+ return parseProfileValue(value, 'vid');
18
+ }
19
+ export function createProfileContent(chinfo, vid) {
20
+ const profile = { chinfo: parseChinfo(chinfo) };
21
+ if (vid !== undefined)
22
+ profile.vid = parseVid(vid);
23
+ return Buffer.from(`${JSON.stringify(profile, null, 2)}\n`, 'utf8');
24
+ }
25
+ export async function createUpdatedProfileContent(skillDir, updates) {
26
+ const previous = await readPreservedProfile(skillDir);
27
+ let profile = {};
28
+ if (previous) {
29
+ try {
30
+ const parsed = JSON.parse(previous.toString('utf8'));
31
+ if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
32
+ profile = { ...parsed };
33
+ }
34
+ }
35
+ catch {
36
+ // A user-initiated install repairs an unreadable PROFILE with the current fields.
37
+ }
38
+ }
39
+ profile.chinfo = parseChinfo(updates.chinfo);
40
+ if (updates.vid !== undefined)
41
+ profile.vid = parseVid(updates.vid);
42
+ return Buffer.from(`${JSON.stringify(profile, null, 2)}\n`, 'utf8');
43
+ }
44
+ export async function readPreservedProfile(skillDir) {
45
+ const profilePath = path.join(skillDir, PROFILE_FILE);
46
+ let stat;
47
+ try {
48
+ stat = await fs.lstat(profilePath);
49
+ }
50
+ catch (error) {
51
+ if (error.code === 'ENOENT')
52
+ return null;
53
+ throw error;
54
+ }
55
+ if (!stat.isFile() || stat.isSymbolicLink()) {
56
+ throw new Error(`已有 ${PROFILE_FILE} 必须是普通文件,无法安全保留`);
57
+ }
58
+ if (stat.size > MAX_PROFILE_BYTES) {
59
+ throw new Error(`已有 ${PROFILE_FILE} 超过 ${MAX_PROFILE_BYTES} 字节,无法安全保留`);
60
+ }
61
+ return fs.readFile(profilePath);
62
+ }
63
+ //# sourceMappingURL=profile.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
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.8.0",
59
- "pre": "1.8.3-pre"
58
+ "stable": "1.8.2",
59
+ "pre": "1.8.5-pre"
60
60
  }
61
61
  }
package/skill/SKILL.md CHANGED
@@ -33,6 +33,8 @@ powershell.exe -NoProfile -NonInteractive -File "<SKILL_DIR>/references/normal/s
33
33
 
34
34
  本目录的 `references/normal/json/release-channel.json` 固定当前渠道;正式版为 `stable`,预发版为 `pre`。自更新只比较 npm latest 元数据中的本渠道 Skill 版本与本地 `VERSION`,不以 npm 包版本判断是否需要更新,不切换或扫描另一渠道。缺少合法渠道元数据时保留当前版本。 本地 beta 测试版本固定使用当前版本,不查询 npm、不自动升级或转正;常规版本只接收本渠道非 beta 版本。此判断由同一自更新命令完成,Agent 不自行判断或修改 VERSION。
35
35
 
36
+ 用户主动执行公开 npm `install` 时,安装器在已安装 Skill 根目录创建或字段级合并与 `VERSION` 并列的 `PROFILE`;`chinfo` 显式传值时更新为该值,未传时更新为 `origin`,只有显式 `--vid <值>` 才新增或更新 `vid`,未传时保留已有 `vid`,其他已有扩展字段也保留。npm 包内的 `skill/`、`skill-pre/` 源目录不得预置 `PROFILE`。自更新、普通 `update`、内部 `install-current` 和 Windows 会话副本只原样迁移已有普通 `PROFILE`:原来有则保留全部 JSON 字段,原来没有则继续没有,禁止因内部使用 npm/npx 创建 `origin` 或修改 `vid`。
37
+
36
38
  本次 Agent 对话首次使用 `alipay-aipay` 时,先把本文件所在目录解析为规范化绝对路径 `<SKILL_DIR>`,记录一次性事实 `ALIPAY_AIPAY_SELF_UPDATE_CHECKED=true`,再执行以下单条命令。`<SKILL_DIR>` 是执行时替换的路径占位符;禁止用 shell 变量赋值、命令替换或依赖特定 shell:
37
39
 
38
40
  ```text
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.8.0
1
+ 1.8.2
@@ -163,7 +163,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message win
163
163
  资料校验通过后必须建立适用的版本控制忽略和访问保护。该例外只适用于当前沙箱资料;生产环境应用私钥和密码禁止提供给 Agent。Agent 不得在回复、摘要或普通日志中复述收到的值;仅 AI 网页应用收款在本步骤 5 已校验后,允许登记沙箱摘要展示买家账号及两个密码,最终 summary 只展示这三项。
164
164
 
165
165
  5. Unix/macOS/Linux 的 `ensure` 只依据同一目标项目的 `.alipay-sandbox.json` 当前事实分流:文件不存在时执行既有 `create`;文件存在或为符号链接时执行既有 `verify` 安全路径。已存在配置不得再次调用 `createAnonymousSandbox`、不得覆盖,也不得因为中断重来要求用户删除配置;无效、不安全或被 Git 跟踪时不得使用,脚本按待配置终态统一输出后继续代码开发。
166
- 6. 只有 Unix/macOS/Linux 创建分支会调用冻结的 `alipay-anonymous-sandbox.createAnonymousSandbox`、`{"request":{"appType":"PUBLICAPP"}}`、`PLATFORM`、`PLATFORM_ID`、`ALIPAYCLI_BIZ_INFO` 环境变量和 `result.content[0].text -> success -> data` 解包逻辑;`ALIPAYCLI_BIZ_INFO` 固定标识当前调用方 Skill、当前生效版本和匿名 `skill_user_id`,禁止传入 `PRODUCT`。Windows 不调用该方法或 `sandbox_config.sh`。
166
+ 6. 只有 Unix/macOS/Linux 创建分支会调用冻结的 `alipay-anonymous-sandbox.createAnonymousSandbox`、`{"request":{"appType":"PUBLICAPP"}}`、`PLATFORM`、`PLATFORM_ID`、`ALIPAYCLI_BIZ_INFO` 环境变量和 `result.content[0].text -> success -> data` 解包逻辑;`ALIPAYCLI_BIZ_INFO` 固定标识当前调用方 Skill、当前生效版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选的 `chinfo`、`vid`,各字段缺少时分别省略且不回填,禁止传入 `PRODUCT`。Windows 不调用该方法或 `sandbox_config.sh`。
167
167
  7. Unix/macOS/Linux 只接受以下互斥最终结果和一个临时恢复结果;待配置不算成功:
168
168
  - `READY`:退出码为 0;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_ENSURE_ACTION=CREATED|VERIFIED`、`SANDBOX_CONFIG_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_READY` 内部事实。
169
169
  - `CREATE_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/CREATE` 正文,并产生唯一 `ALIPAY_AIPAY_INTERNAL:CUSTOMER_MESSAGE_DELIVERY_REQUIRED=sandbox.configuration.pending`;另有唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_CREATE` 内部事实;不得出现 READY 标记。
@@ -18,7 +18,7 @@ which alipay-cli 2>/dev/null && alipay-cli version
18
18
 
19
19
  **重要:工具返回的 `data` 只是候选沙箱配置;必须先按 `sandbox-setup-guide.md` 完成字段完整性校验,校验通过后再写入正式本地配置文件。创建阶段不对客输出字段核对表、独立校验结论或沙箱环境摘要表;代码开发完成并通过配置后置校验后,才按该指南输出一张沙箱环境摘要表。该结果不代表沙箱支付测试通过。privateKey、publicKey 等密钥字段不得展开。**
20
20
 
21
- `createAnonymousSandbox` 的 MCP 请求体只有业务入参 `request.appType`,当前固定为 `PUBLICAPP`。`PLATFORM`、`PLATFORM_ID` 和 `ALIPAYCLI_BIZ_INFO` 是 alipay-cli 上下文环境变量,禁止写入 `--data` 的 JSON。匿名沙箱创建不依赖本轮最终选择的支付产品,因此不得为了创建沙箱先编造或强行确认 `PRODUCT`。
21
+ `createAnonymousSandbox` 的 MCP 请求体只有业务入参 `request.appType`,当前固定为 `PUBLICAPP`。`PLATFORM`、`PLATFORM_ID` 和 `ALIPAYCLI_BIZ_INFO` 是 alipay-cli 上下文环境变量,禁止写入 `--data` 的 JSON。匿名沙箱创建不依赖本轮最终选择的支付产品,因此不得为了创建沙箱先编造或强行确认 `PRODUCT`。 下列命令展示无合法 `PROFILE.chinfo` 和 `PROFILE.vid` 的技能市场来源示例;存在合法值时 `common.sh` 会在同一 JSON 中分别追加 `"chinfo":"<来源>"` 和 `"vid":"<来源>"`。
22
22
 
23
23
  ```bash
24
24
  PLATFORM="<PLATFORM>" \
@@ -41,7 +41,7 @@ alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox \
41
41
  |------|------|----------|
42
42
  | `PLATFORM` | AI 编程工具识别结果 | 参考通用脚本 `../../../normal/scripts/detect_dev_tool.sh` 识别当前工具后使用其输出值;缺失时使用 `unknown`。例如 Codex 环境检测结果为 `codex` |
43
43
  | `PLATFORM_ID` | 第三方项目 ID;缺失时为当前逻辑 `run_id` | 由 `sandbox_config.sh` 通过 `common.sh` 读取,遥测开启或关闭都不影响该值;第三方项目 ID 只用于项目维度,不能生成逻辑 `run_id`,缺失时才保持 `PLATFORM_ID <- run_id` fallback,且不阻断沙箱创建 |
44
- | `ALIPAYCLI_BIZ_INFO` | 当前生效 Skill 的 `VERSION` 与匿名机器哈希 | 由 `common.sh` 动态生成 JSON:`{"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;`skill` 固定,版本统一带 `v` 前缀,`skill_user_id` 复用 telemetry 的 `machineUserID`,极端环境无法取得合法 `machine_` 标识时省略该字段;禁止由 Agent 手写或沿用外部同名值 |
44
+ | `ALIPAYCLI_BIZ_INFO` | 当前生效 Skill 的 `VERSION`、匿名机器哈希及可选来源字段 | 由 `common.sh` 动态生成 JSON:基础字段为 `{"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;Skill 根目录存在合法 `PROFILE.chinfo` 或 `PROFILE.vid` 时分别追加对应字段,任一字段缺少时仅省略该字段,且不以 `PLATFORM` 回填 `chinfo`。`skill_user_id` 复用 telemetry 的 `machineUserID`,极端环境无法取得合法 `machine_` 标识时省略该字段;禁止由 Agent 手写或沿用外部同名值 |
45
45
  | `PRODUCT` | 不再传入 | 该 MCP 方法当前不需要产品上下文;不得把产品名写入 `--data` 或 CLI 环境变量 |
46
46
 
47
47
  **Codex 环境示例:**
@@ -141,12 +141,12 @@ source ~/.bashrc # Linux/bash
141
141
 
142
142
  > ⚠️ **强制规范:检测结果仅供内部使用,禁止向用户输出!**
143
143
 
144
- AI 工具由 `scripts/detect_dev_tool.sh` 检测;`init_alipay_cli_context` 设置 `DEV_TOOL_NAME`、`PLATFORM`。`run_id` 在根意图与产品首次唯一确定时由 runtime 生成 UUID;同一组合的 active run 复用,组合变化或旧 run 已关闭时生成新 UUID,第三方项目 ID 不参与生成。`common.sh` 为业务 `PLATFORM_ID` 优先使用有效的第三方项目 ID(`COZE_PROJECT_ID`、`MEOO_PROJECT_ID` 或 MIAODA 的 `VITE_APP_ID`/`PLATFORM_ID`),缺失时回退当前 `run_id`;遥测开关不改变该回退规则。脚本还从当前生效 Skill 的 `VERSION` 和同源 `machineUserID` 动态生成 `ALIPAYCLI_BIZ_INFO={"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;极端环境无法取得合法 `machine_` 标识时只省略 `skill_user_id`,不写 `anonymous_uid` 且不阻断业务调用。所有携带 `PLATFORM` 的 Skill 业务 CLI 调用均携带该 JSON,外部同名值会被覆盖;`alipay-cli version` 等不携带 `PLATFORM` 的安装和环境探测不注入该变量。产品确认后,Onboarding 还严格映射 `aipay|webpay|apppay` 为 `PRODUCT=AIPAY|WEBPAY|APPPAY`,其环境检查、登录、查询、写入和上传均携带该值,但 `PRODUCT` 和 `ALIPAYCLI_BIZ_INFO` 都不写入 MCP JSON;Integration 产品确定前的环境检查和快速沙箱不传 `PRODUCT`。
144
+ AI 工具由 `scripts/detect_dev_tool.sh` 检测;`init_alipay_cli_context` 设置 `DEV_TOOL_NAME`、`PLATFORM`。`run_id` 在根意图与产品首次唯一确定时由 runtime 生成 UUID;同一组合的 active run 复用,组合变化或旧 run 已关闭时生成新 UUID,第三方项目 ID 不参与生成。`common.sh` 为业务 `PLATFORM_ID` 优先使用有效的第三方项目 ID(`COZE_PROJECT_ID`、`MEOO_PROJECT_ID` 或 MIAODA 的 `VITE_APP_ID`/`PLATFORM_ID`),缺失时回退当前 `run_id`;遥测开关不改变该回退规则。脚本从当前生效 Skill 的 `VERSION` 和同源 `machineUserID` 动态生成 `ALIPAYCLI_BIZ_INFO={"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;若 Skill 根目录存在普通、合法且不超过 64 KiB 的 `PROFILE`,则分别校验可选 `chinfo` 和 `vid` 是否为 1 至 128 个无首尾空白和控制字符的 Unicode 字符,并把各自合法值追加到同一 JSON。任一字段缺少或无效时仅省略该字段;`PROFILE` 无法解析时两者都省略,且不得用 `PLATFORM` 回填 `chinfo`;极端环境无法取得合法 `machine_` 标识时只省略 `skill_user_id`,不写 `anonymous_uid` 且不阻断业务调用。所有携带 `PLATFORM` 的 Skill 业务 CLI 调用均携带该 JSON,外部同名值会被覆盖;`alipay-cli version` 等不携带 `PLATFORM` 的安装和环境探测不注入该变量。产品确认后,Onboarding 还严格映射 `aipay|webpay|apppay` 为 `PRODUCT=AIPAY|WEBPAY|APPPAY`,其环境检查、登录、查询、写入和上传均携带该值,但 `PRODUCT` 和 `ALIPAYCLI_BIZ_INFO` 都不写入 MCP JSON;Integration 产品确定前的环境检查和快速沙箱不传 `PRODUCT`。
145
145
 
146
146
  ```
147
147
  ❌ 禁止:向用户输出"环境检查完成,检测到您正在使用 Claude Code 环境"等检测结果
148
148
  ❌ 禁止:向用户透出任何关于 AI 编程工具检测的信息
149
- ✅ 正确:检测结果仅供内部使用,只用于脚本设置 `PLATFORM`;`PLATFORM_ID` 优先使用有效第三方项目 ID,缺失时回退当前 `run_id`;`ALIPAYCLI_BIZ_INFO` 由当前 Skill 版本和匿名 `skill_user_id` 派生,Onboarding 的 `PRODUCT` 由已确认产品映射
149
+ ✅ 正确:检测结果仅供内部使用,只用于脚本设置 `PLATFORM`;`PLATFORM_ID` 优先使用有效第三方项目 ID,缺失时回退当前 `run_id`;`ALIPAYCLI_BIZ_INFO` 由当前 Skill 版本、匿名 `skill_user_id` 及可选 `PROFILE.chinfo`、`PROFILE.vid` 派生,各字段缺少时分别省略;Onboarding 的 `PRODUCT` 由已确认产品映射
150
150
  ✅ 正确:静默完成检测,直接进入下一步流程
151
151
  ```
152
152
 
@@ -255,10 +255,11 @@ init_platform_id() {
255
255
  }
256
256
 
257
257
  init_alipay_cli_biz_info() {
258
- local common_dir version_file version skill_user_id
258
+ local common_dir skill_dir version_file version skill_user_id profile_script merged
259
259
 
260
260
  common_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
261
- version_file="${common_dir}/../../../VERSION"
261
+ skill_dir="$(cd "${common_dir}/../../.." && pwd)"
262
+ version_file="${skill_dir}/VERSION"
262
263
  if [ ! -f "$version_file" ]; then
263
264
  echo "❌ 无法读取当前 alipay-aipay Skill 版本" >&2
264
265
  return 1
@@ -271,11 +272,14 @@ init_alipay_cli_biz_info() {
271
272
  fi
272
273
 
273
274
  skill_user_id="$(machine_user_id_for_biz_info "$common_dir" || true)"
275
+ profile_script="${common_dir}/profile.mjs"
274
276
  if [[ "$skill_user_id" =~ ^machine_[0-9a-f]{32}$ ]]; then
275
277
  printf -v ALIPAYCLI_BIZ_INFO '{"skill":"alipay-aipay","skill_version":"v%s","skill_user_id":"%s"}' "$version" "$skill_user_id"
276
278
  else
277
279
  printf -v ALIPAYCLI_BIZ_INFO '{"skill":"alipay-aipay","skill_version":"v%s"}' "$version"
278
280
  fi
281
+ merged="$(node "$profile_script" merge-biz-info --skill-dir "$skill_dir" --base-json "$ALIPAYCLI_BIZ_INFO" 2>/dev/null || true)"
282
+ if [ -n "$merged" ]; then ALIPAYCLI_BIZ_INFO="$merged"; fi
279
283
  export ALIPAYCLI_BIZ_INFO
280
284
  }
281
285
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const maxProfileBytes = 64 * 1024;
9
+ const validProfileValue = value => typeof value === 'string' && value.trim() === value
10
+ && [...value].length >= 1 && [...value].length <= 128 && !/[\p{Cc}\p{Cs}]/u.test(value);
11
+
12
+ function readProfileFields(skillDir) {
13
+ try {
14
+ if (typeof skillDir !== 'string' || !path.isAbsolute(skillDir)) return {};
15
+ const file = path.join(skillDir, 'PROFILE');
16
+ const stat = fs.lstatSync(file);
17
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxProfileBytes) return {};
18
+ const profile = JSON.parse(fs.readFileSync(file, 'utf8'));
19
+ if (!profile || Array.isArray(profile) || typeof profile !== 'object') return {};
20
+ return {
21
+ chinfo: validProfileValue(profile.chinfo) ? profile.chinfo : '',
22
+ vid: validProfileValue(profile.vid) ? profile.vid : '',
23
+ };
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ export function readProfileChinfo(skillDir) { return readProfileFields(skillDir).chinfo || ''; }
30
+ export function readProfileVid(skillDir) { return readProfileFields(skillDir).vid || ''; }
31
+
32
+ export function runProfileCli(argv = process.argv) {
33
+ if (argv.length === 5 && argv[2] === 'read-chinfo' && argv[3] === '--skill-dir') {
34
+ process.stdout.write(readProfileChinfo(argv[4]));
35
+ return 0;
36
+ }
37
+ if (argv.length === 7 && argv[2] === 'merge-biz-info' && argv[3] === '--skill-dir' && argv[5] === '--base-json') {
38
+ try {
39
+ const base = JSON.parse(argv[6]);
40
+ if (!base || Array.isArray(base) || typeof base !== 'object') return 1;
41
+ const fields = readProfileFields(argv[4]);
42
+ if (fields.chinfo) base.chinfo = fields.chinfo;
43
+ if (fields.vid) base.vid = fields.vid;
44
+ process.stdout.write(JSON.stringify(base));
45
+ return 0;
46
+ } catch { return 1; }
47
+ }
48
+ return 1;
49
+ }
50
+
51
+ if (process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
52
+ process.exitCode = runProfileCli();
53
+ }
@@ -35,6 +35,7 @@ function filesUnder(root) {
35
35
  else throw Error('Non-regular session Skill file');
36
36
  };
37
37
  for (const entry of ['SKILL.md','VERSION','references']) walk(entry);
38
+ if (fs.existsSync(path.join(root,'PROFILE'))) walk('PROFILE');
38
39
  return files;
39
40
  }
40
41
  function identity(root, channel, version) {
@@ -48,15 +49,20 @@ function identity(root, channel, version) {
48
49
  (channel==='pre'?'alipay-aipay-pre':'alipay-aipay')) throw Error('Session Skill identity mismatch');
49
50
  }
50
51
 
51
- export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, home = os.homedir()}) {
52
+ export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, profileContent, home = os.homedir()}) {
52
53
  identity(sourceDir, channel, version);
53
54
  if (!semver.test(packageVersion) || !path.isAbsolute(targetDir)) throw Error('Invalid session install source');
54
- const manifest = filesUnder(sourceDir);
55
55
  const root = privateRoot(home);
56
56
  const skillDir = path.join(root, `${channel}-${version}-${randomUUID()}`);
57
57
  fs.mkdirSync(skillDir, {mode:0o700});
58
58
  try {
59
59
  for (const entry of ['SKILL.md','VERSION','references']) fs.cpSync(path.join(sourceDir,entry),path.join(skillDir,entry),{recursive:true,errorOnExist:true,force:false});
60
+ const selectedProfile = profileContent === undefined ? readProfile(targetDir) : profileContent;
61
+ if (selectedProfile !== null && selectedProfile !== undefined) {
62
+ if (!(Buffer.isBuffer(selectedProfile) || selectedProfile instanceof Uint8Array) || selectedProfile.length > 64 * 1024) throw Error('Invalid PROFILE content');
63
+ fs.writeFileSync(path.join(skillDir,'PROFILE'),selectedProfile,{flag:'wx',mode:0o600});
64
+ }
65
+ const manifest = filesUnder(skillDir);
60
66
  const receipt = {schemaVersion:1, scope:'session', channel, version, packageVersion,
61
67
  skillDir:fs.realpathSync(skillDir), targetDir:path.resolve(targetDir)};
62
68
  fs.writeFileSync(path.join(skillDir,receiptName),JSON.stringify({...receipt,manifest}),{flag:'wx',mode:0o600});
@@ -68,6 +74,15 @@ export function createSessionSkill({sourceDir, channel, version, packageVersion,
68
74
  }
69
75
  }
70
76
 
77
+ function readProfile(skillDir) {
78
+ const file=path.join(skillDir,'PROFILE');
79
+ if (!fs.existsSync(file)) return null;
80
+ plain(file);
81
+ const stat=fs.lstatSync(file);
82
+ if (stat.size > 64 * 1024) throw Error('PROFILE is too large');
83
+ return fs.readFileSync(file);
84
+ }
85
+
71
86
  export function verifySessionSkill(receipt, {home = os.homedir(), channel, version, packageVersion, targetDir} = {}) {
72
87
  if (!receipt || receipt.schemaVersion!==1 || receipt.scope!=='session' || !['stable','pre'].includes(receipt.channel) || !semver.test(receipt.version||'') || !path.isAbsolute(receipt.skillDir||'') ||
73
88
  !path.isAbsolute(receipt.targetDir||'') || !semver.test(receipt.packageVersion||'')) throw Error('Invalid session receipt');
@@ -4,6 +4,8 @@
4
4
 
5
5
  唯一 stderr 终态:`SELF_UPDATE:CURRENT=<合法 SemVer>`、`SELF_UPDATE:UPDATED=<合法 SemVer>`、`SELF_UPDATE:SESSION_READY=<合法 SemVer>`、`SELF_UPDATE:RETRY_WITH_NETWORK`、`SELF_UPDATE:SKIPPED_NETWORK_RESTRICTED`、`SELF_UPDATE:CHECK_FAILED`、`SELF_UPDATE:INSTALL_FAILED`、`SELF_UPDATE:VERIFY_FAILED`。`UPDATED` 用原子替换后的新版,除 `SESSION_READY` 外其余用当前可用版;marker 异常读当前入口,不得称最新或更新成功;停止读旧版,读取同一根目录的新版入口。仅 `RETRY_WITH_NETWORK` 可取权后原样重试一次,拒绝、不支持或再次返回时读当前入口;一次网络恢复外,不重查、取权、用 `sudo`、要求用户操作或写更新状态。每次只查一次 npm latest 的 `version` 和 `alipayAipayChannels`;只比较本渠道 Skill 版本,npm 包版本仅用于精确下载;`VERSION` 缺失、非法、落后或业务入口缺失时只从查询所得精确 npm 包版本安装当前渠道一次。渠道由当前目录的 `references/normal/json/release-channel.json` 与 Skill 名称共同校验;旧正式版缺少渠道文件时只兼容 `stable`,预发版禁止回退正式版。缺少或非法的远端渠道元数据返回 `CHECK_FAILED`,不得按 npm 包版本兜底。不调用公开 `install --target all`;超时终止同一次调用的进程树并清理。
6
6
 
7
+ 公开 npm `install` 是 `PROFILE` 的唯一创建入口:`chinfo` 显式传值时更新为该来源,未传时更新为 `origin`;只有显式 `--vid <值>` 才新增或更新 `vid`,未传时保留已有 `vid`,其他已有扩展字段也保留。自更新只原样迁移当前 Skill 根目录已有的普通 `PROFILE`;不存在时新版及 Windows 会话副本都保持不存在。内部 `npx ... install-current` 不属于用户主动安装,禁止创建 `origin` 或修改 `vid`。源包的 `skill/` 与 `skill-pre/` 不包含 `PROFILE`。
8
+
7
9
  本地正式渠道的 `X.Y.Z-beta`、`X.Y.Z-betaN`(兼容 `-beta.N`)和预发渠道的 `X.Y.Z-pre-beta`、`X.Y.Z-pre-betaN` 均为固定测试版本。完整安装直接返回 `CURRENT` 且不查询 npm;业务入口损坏时返回 `VERIFY_FAILED`,仍不下载修复或转正。更换版本必须由测试人员显式执行 npm 安装器的 `install`,普通 `update` 同样不推进 beta。正常版本分别为 `X.Y.Z` 与 `X.Y.Z-pre`,远端对应渠道若为 beta 或名称不匹配,返回 `CHECK_FAILED`,不自动安装。beta 不参与版本排序,无自动追踪和自动转正。
8
10
 
9
11
  `SESSION_READY` 是 Windows 目录占用或权限失败的有限恢复:只有替换前的备份/暂存准备失败,安装器才从本次精确 npm 包创建并逐文件 SHA-256 复核独立副本;不混合覆盖原目录,不更新全局安装记录。必须同时取得唯一 `ALIPAY_AIPAY_INTERNAL:SELF_UPDATE_SESSION_SKILL_DIR=<JSON 字符串>`,用已核验的绝对路径重绑 `SKILL_DIR`,明确告知“全局目录未更新,当前会话使用已校验的新版本;下次会话仍可能加载旧版”。废弃旧 loaded/CLI 刷新事实,保留本渠道自更新已检查事实,立即读取新路径的 `SKILL.md` 并继续其余启动协议,禁止再次自更新。内测版仍需按新路径/版本完成声明及服务/采集选择,安装意图不得代替采集同意。路径 marker 缺失、冲突或新入口不可读时,不切换、不声称更新成功,使用仍可用的原目录或说明入口不可用。
@@ -153,7 +153,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-execute-read
153
153
 
154
154
  若组合动作本身不可用,原子 fallback 固定为先执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding execute --product-type "$PRODUCT_TYPE" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`,再执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env check --product-type "$PRODUCT_TYPE" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`;fallback 不得重复 stage 或重复环境检查。
155
155
 
156
- 签约脚本仍通过 `error_handler.sh -> common.sh` 初始化 `DEV_TOOL_NAME`、`PLATFORM`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本和匿名 `skill_user_id` 的 `ALIPAYCLI_BIZ_INFO`,以及当前产品 `PRODUCT`。有任务工具时只内部记录实际阶段,不维护任务数量;只保留当前对话最小上下文,不创建业务进度文件。
156
+ 签约脚本仍通过 `error_handler.sh -> common.sh` 初始化 `DEV_TOOL_NAME`、`PLATFORM`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选 `chinfo`、`vid` 的 `ALIPAYCLI_BIZ_INFO`,以及当前产品 `PRODUCT`。缺少 `chinfo` 或 `vid` 时分别保持省略,不以 `PLATFORM` 回填。有任务工具时只内部记录实际阶段,不维护任务数量;只保留当前对话最小上下文,不创建业务进度文件。
157
157
 
158
158
  本步骤成功时不输出“环境检查完成”“任务已创建”等对客消息,直接进 Step 3;只有安装失败、权限不足或缺工具时才输出实际问题和恢复动作。工具能力差异不得省略任务、步骤或完成状态。
159
159
 
@@ -242,7 +242,7 @@ Step 5 直接使用对应 phase 的完整命令;仅出现脚本异常、需要
242
242
  2. 服务:仅按量付费由 runner 执行原 `service.sh list`;其他产品 envelope 固定为 `NOT_APPLICABLE`。成功候选只取 envelope 的 `service.candidates`。
243
243
  3. 应用:runner 执行原 `app.sh list --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE"`。成功候选和 `WEBAPP|MOBILEAPP` 只取 envelope 的 application 分支;不得宣称返回应用已与当前支付产品绑定。
244
244
 
245
- > ⚠️ 脚本通过 `error_handler.sh` 间接初始化 `DEV_TOOL_NAME`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本和匿名 `skill_user_id` 对应的 `ALIPAYCLI_BIZ_INFO`,以及 CLI `PRODUCT`;签约查询必须传入当前产品的 `--sales-code` 和 `--product-type`(aipay|webpay|apppay)做一致性校验。AI 网页应用收款和 AI 移动应用收款使用不同 salesCode,禁止混用。
245
+ > ⚠️ 脚本通过 `error_handler.sh` 间接初始化 `DEV_TOOL_NAME`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选 `chinfo`、`vid` 对应的 `ALIPAYCLI_BIZ_INFO`,以及 CLI `PRODUCT`;缺少任一来源字段时分别保持省略。签约查询必须传入当前产品的 `--sales-code` 和 `--product-type`(aipay|webpay|apppay)做一致性校验。AI 网页应用收款和 AI 移动应用收款使用不同 salesCode,禁止混用。
246
246
 
247
247
  各适用查询仍是独立 CLI/MCP 调用,runner 只编排现有脚本,必须分别完成原有错误检测,禁止伪造合并 MCP。失败后按错误类型处理:
248
248
 
@@ -32,6 +32,8 @@ powershell.exe -NoProfile -NonInteractive -File "<SKILL_DIR>/references/normal/s
32
32
 
33
33
  本目录的 `references/normal/json/release-channel.json` 固定当前渠道;正式版为 `stable`,预发版为 `pre`。自更新只比较 npm latest 元数据中的本渠道 Skill 版本与本地 `VERSION`,不以 npm 包版本判断是否需要更新,不切换或扫描另一渠道。缺少合法渠道元数据时保留当前版本。 本地 beta 测试版本固定使用当前版本,不查询 npm、不自动升级或转正;常规版本只接收本渠道非 beta 版本。此判断由同一自更新命令完成,Agent 不自行判断或修改 VERSION。
34
34
 
35
+ 用户主动执行公开 npm `install` 时,安装器在已安装 Skill 根目录创建或字段级合并与 `VERSION` 并列的 `PROFILE`;`chinfo` 显式传值时更新为该值,未传时更新为 `origin`,只有显式 `--vid <值>` 才新增或更新 `vid`,未传时保留已有 `vid`,其他已有扩展字段也保留。npm 包内的 `skill/`、`skill-pre/` 源目录不得预置 `PROFILE`。自更新、普通 `update`、内部 `install-current` 和 Windows 会话副本只原样迁移已有普通 `PROFILE`:原来有则保留全部 JSON 字段,原来没有则继续没有,禁止因内部使用 npm/npx 创建 `origin` 或修改 `vid`。
36
+
35
37
  本次 Agent 对话首次使用 `alipay-aipay-pre` 时,先把本文件所在目录解析为规范化绝对路径 `<SKILL_DIR>`,记录一次性事实 `ALIPAY_AIPAY_PRE_SELF_UPDATE_CHECKED=true`,再执行以下单条命令。`<SKILL_DIR>` 是执行时替换的路径占位符;禁止用 shell 变量赋值、命令替换或依赖特定 shell:
36
38
 
37
39
  ```text
package/skill-pre/VERSION CHANGED
@@ -1 +1 @@
1
- 1.8.3-pre
1
+ 1.8.5-pre
@@ -165,7 +165,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message win
165
165
  资料校验通过后必须建立适用的版本控制忽略和访问保护。该例外只适用于当前沙箱资料;生产环境应用私钥和密码禁止提供给 Agent。Agent 不得在回复、摘要或普通日志中复述收到的值;仅 AI 网页应用收款在本步骤 5 已校验后,允许登记沙箱摘要展示买家账号及两个密码,最终 summary 只展示这三项。
166
166
 
167
167
  5. Unix/macOS/Linux 或 Windows READY 的 `ensure` 只依据同一目标项目的 `.alipay-sandbox.json` 当前事实分流:文件不存在时执行既有 `create`;文件存在或为符号链接时执行既有 `verify` 安全路径。已存在配置不得再次调用 `createAnonymousSandbox`、不得覆盖,也不得因为中断重来要求用户删除配置;无效、不安全或被 Git 跟踪时不得使用,脚本按待配置终态统一输出后继续代码开发。
168
- 6. 只有 Unix/macOS/Linux 或 Windows READY 创建分支会调用冻结的 `alipay-anonymous-sandbox.createAnonymousSandbox`、`{"request":{"appType":"PUBLICAPP"}}`、`PLATFORM`、`PLATFORM_ID`、`ALIPAYCLI_BIZ_INFO` 环境变量和 `result.content[0].text -> success -> data` 解包逻辑;`ALIPAYCLI_BIZ_INFO` 固定标识当前调用方 Skill、当前生效版本和匿名 `skill_user_id`,禁止传入 `PRODUCT`。Windows LEGACY/CODE_ONLY 不调用该方法或 `sandbox_config.sh`。
168
+ 6. 只有 Unix/macOS/Linux 或 Windows READY 创建分支会调用冻结的 `alipay-anonymous-sandbox.createAnonymousSandbox`、`{"request":{"appType":"PUBLICAPP"}}`、`PLATFORM`、`PLATFORM_ID`、`ALIPAYCLI_BIZ_INFO` 环境变量和 `result.content[0].text -> success -> data` 解包逻辑;`ALIPAYCLI_BIZ_INFO` 固定标识当前调用方 Skill、当前生效版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选的 `chinfo`、`vid`,各字段缺少时分别省略且不回填,禁止传入 `PRODUCT`。Windows LEGACY/CODE_ONLY 不调用该方法或 `sandbox_config.sh`。
169
169
  7. Unix/macOS/Linux 或 Windows READY 只接受以下互斥最终结果和一个临时恢复结果;待配置不算成功:
170
170
  - `READY`:退出码为 0;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_ENSURE_ACTION=CREATED|VERIFIED`、`SANDBOX_CONFIG_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_READY` 内部事实。
171
171
  - `CREATE_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/CREATE` 正文,并产生唯一 `ALIPAY_AIPAY_INTERNAL:CUSTOMER_MESSAGE_DELIVERY_REQUIRED=sandbox.configuration.pending`;另有唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_CREATE` 内部事实;不得出现 READY 标记。
@@ -18,7 +18,7 @@ which alipay-cli 2>/dev/null && alipay-cli version
18
18
 
19
19
  **重要:工具返回的 `data` 只是候选沙箱配置;必须先按 `sandbox-setup-guide.md` 完成字段完整性校验,校验通过后再写入正式本地配置文件。创建阶段不对客输出字段核对表、独立校验结论或沙箱环境摘要表;代码开发完成并通过配置后置校验后,才按该指南输出一张沙箱环境摘要表。该结果不代表沙箱支付测试通过。privateKey、publicKey 等密钥字段不得展开。**
20
20
 
21
- `createAnonymousSandbox` 的 MCP 请求体只有业务入参 `request.appType`,当前固定为 `PUBLICAPP`。`PLATFORM`、`PLATFORM_ID` 和 `ALIPAYCLI_BIZ_INFO` 是 alipay-cli 上下文环境变量,禁止写入 `--data` 的 JSON。匿名沙箱创建不依赖本轮最终选择的支付产品,因此不得为了创建沙箱先编造或强行确认 `PRODUCT`。
21
+ `createAnonymousSandbox` 的 MCP 请求体只有业务入参 `request.appType`,当前固定为 `PUBLICAPP`。`PLATFORM`、`PLATFORM_ID` 和 `ALIPAYCLI_BIZ_INFO` 是 alipay-cli 上下文环境变量,禁止写入 `--data` 的 JSON。匿名沙箱创建不依赖本轮最终选择的支付产品,因此不得为了创建沙箱先编造或强行确认 `PRODUCT`。 下列命令展示无合法 `PROFILE.chinfo` 和 `PROFILE.vid` 的技能市场来源示例;存在合法值时 `common.sh` 会在同一 JSON 中分别追加 `"chinfo":"<来源>"` 和 `"vid":"<来源>"`。
22
22
 
23
23
  ```bash
24
24
  PLATFORM="<PLATFORM>" \
@@ -41,7 +41,7 @@ alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox \
41
41
  |------|------|----------|
42
42
  | `PLATFORM` | AI 编程工具识别结果 | 参考通用脚本 `../../../normal/scripts/detect_dev_tool.sh` 识别当前工具后使用其输出值;缺失时使用 `unknown`。例如 Codex 环境检测结果为 `codex` |
43
43
  | `PLATFORM_ID` | 第三方项目 ID;缺失时为当前逻辑 `run_id` | 由 `sandbox_config.sh` 通过 `common.sh` 读取,遥测开启或关闭都不影响该值;第三方项目 ID 只用于项目维度,不能生成逻辑 `run_id`,缺失时才保持 `PLATFORM_ID <- run_id` fallback,且不阻断沙箱创建 |
44
- | `ALIPAYCLI_BIZ_INFO` | 当前生效 Skill 的 `VERSION` 与匿名机器哈希 | 由 `common.sh` 动态生成 JSON:`{"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;`skill` 固定,版本统一带 `v` 前缀,`skill_user_id` 复用 telemetry 的 `machineUserID`,极端环境无法取得合法 `machine_` 标识时省略该字段;禁止由 Agent 手写或沿用外部同名值 |
44
+ | `ALIPAYCLI_BIZ_INFO` | 当前生效 Skill 的 `VERSION`、匿名机器哈希及可选来源字段 | 由 `common.sh` 动态生成 JSON:基础字段为 `{"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;Skill 根目录存在合法 `PROFILE.chinfo` 或 `PROFILE.vid` 时分别追加对应字段,任一字段缺少时仅省略该字段,且不以 `PLATFORM` 回填 `chinfo`。`skill_user_id` 复用 telemetry 的 `machineUserID`,极端环境无法取得合法 `machine_` 标识时省略该字段;禁止由 Agent 手写或沿用外部同名值 |
45
45
  | `PRODUCT` | 不再传入 | 该 MCP 方法当前不需要产品上下文;不得把产品名写入 `--data` 或 CLI 环境变量 |
46
46
 
47
47
  **Codex 环境示例:**
@@ -143,12 +143,12 @@ source ~/.bashrc # Linux/bash
143
143
 
144
144
  > ⚠️ **强制规范:检测结果仅供内部使用,禁止向用户输出!**
145
145
 
146
- AI 工具由 `scripts/detect_dev_tool.sh` 检测;`init_alipay_cli_context` 设置 `DEV_TOOL_NAME`、`PLATFORM`。`run_id` 在根意图与产品首次唯一确定时由 runtime 生成 UUID;同一组合的 active run 复用,组合变化或旧 run 已关闭时生成新 UUID,第三方项目 ID 不参与生成。`common.sh` 为业务 `PLATFORM_ID` 优先使用有效的第三方项目 ID(`COZE_PROJECT_ID`、`MEOO_PROJECT_ID` 或 MIAODA 的 `VITE_APP_ID`/`PLATFORM_ID`),缺失时回退当前 `run_id`;遥测开关不改变该回退规则。脚本还从当前生效 Skill 的 `VERSION` 和同源 `machineUserID` 动态生成 `ALIPAYCLI_BIZ_INFO={"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;极端环境无法取得合法 `machine_` 标识时只省略 `skill_user_id`,不写 `anonymous_uid` 且不阻断业务调用。所有携带 `PLATFORM` 的 Skill 业务 CLI 调用均携带该 JSON,外部同名值会被覆盖;`alipay-cli version` 等不携带 `PLATFORM` 的安装和环境探测不注入该变量。产品确认后,Onboarding 还严格映射 `aipay|webpay|apppay` 为 `PRODUCT=AIPAY|WEBPAY|APPPAY`,其环境检查、登录、查询、写入和上传均携带该值,但 `PRODUCT` 和 `ALIPAYCLI_BIZ_INFO` 都不写入 MCP JSON;Integration 产品确定前的环境检查和快速沙箱不传 `PRODUCT`。
146
+ AI 工具由 `scripts/detect_dev_tool.sh` 检测;`init_alipay_cli_context` 设置 `DEV_TOOL_NAME`、`PLATFORM`。`run_id` 在根意图与产品首次唯一确定时由 runtime 生成 UUID;同一组合的 active run 复用,组合变化或旧 run 已关闭时生成新 UUID,第三方项目 ID 不参与生成。`common.sh` 为业务 `PLATFORM_ID` 优先使用有效的第三方项目 ID(`COZE_PROJECT_ID`、`MEOO_PROJECT_ID` 或 MIAODA 的 `VITE_APP_ID`/`PLATFORM_ID`),缺失时回退当前 `run_id`;遥测开关不改变该回退规则。脚本从当前生效 Skill 的 `VERSION` 和同源 `machineUserID` 动态生成 `ALIPAYCLI_BIZ_INFO={"skill":"alipay-aipay","skill_version":"v<当前 VERSION>","skill_user_id":"machine_<32位匿名机器哈希>"}`;若 Skill 根目录存在普通、合法且不超过 64 KiB 的 `PROFILE`,则分别校验可选 `chinfo` 和 `vid` 是否为 1 至 128 个无首尾空白和控制字符的 Unicode 字符,并把各自合法值追加到同一 JSON。任一字段缺少或无效时仅省略该字段;`PROFILE` 无法解析时两者都省略,且不得用 `PLATFORM` 回填 `chinfo`;极端环境无法取得合法 `machine_` 标识时只省略 `skill_user_id`,不写 `anonymous_uid` 且不阻断业务调用。所有携带 `PLATFORM` 的 Skill 业务 CLI 调用均携带该 JSON,外部同名值会被覆盖;`alipay-cli version` 等不携带 `PLATFORM` 的安装和环境探测不注入该变量。产品确认后,Onboarding 还严格映射 `aipay|webpay|apppay` 为 `PRODUCT=AIPAY|WEBPAY|APPPAY`,其环境检查、登录、查询、写入和上传均携带该值,但 `PRODUCT` 和 `ALIPAYCLI_BIZ_INFO` 都不写入 MCP JSON;Integration 产品确定前的环境检查和快速沙箱不传 `PRODUCT`。
147
147
 
148
148
  ```
149
149
  ❌ 禁止:向用户输出"环境检查完成,检测到您正在使用 Claude Code 环境"等检测结果
150
150
  ❌ 禁止:向用户透出任何关于 AI 编程工具检测的信息
151
- ✅ 正确:检测结果仅供内部使用,只用于脚本设置 `PLATFORM`;`PLATFORM_ID` 优先使用有效第三方项目 ID,缺失时回退当前 `run_id`;`ALIPAYCLI_BIZ_INFO` 由当前 Skill 版本和匿名 `skill_user_id` 派生,Onboarding 的 `PRODUCT` 由已确认产品映射
151
+ ✅ 正确:检测结果仅供内部使用,只用于脚本设置 `PLATFORM`;`PLATFORM_ID` 优先使用有效第三方项目 ID,缺失时回退当前 `run_id`;`ALIPAYCLI_BIZ_INFO` 由当前 Skill 版本、匿名 `skill_user_id` 及可选 `PROFILE.chinfo`、`PROFILE.vid` 派生,各字段缺少时分别省略;Onboarding 的 `PRODUCT` 由已确认产品映射
152
152
  ✅ 正确:静默完成检测,直接进入下一步流程
153
153
  ```
154
154
 
@@ -265,10 +265,11 @@ init_platform_id() {
265
265
  }
266
266
 
267
267
  init_alipay_cli_biz_info() {
268
- local common_dir version_file version skill_user_id
268
+ local common_dir skill_dir version_file version skill_user_id profile_script merged
269
269
 
270
270
  common_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
271
- version_file="${common_dir}/../../../VERSION"
271
+ skill_dir="$(cd "${common_dir}/../../.." && pwd)"
272
+ version_file="${skill_dir}/VERSION"
272
273
  if [ ! -f "$version_file" ]; then
273
274
  echo "❌ 无法读取当前 alipay-aipay Skill 版本" >&2
274
275
  return 1
@@ -281,11 +282,14 @@ init_alipay_cli_biz_info() {
281
282
  fi
282
283
 
283
284
  skill_user_id="$(machine_user_id_for_biz_info "$common_dir" || true)"
285
+ profile_script="${common_dir}/profile.mjs"
284
286
  if [[ "$skill_user_id" =~ ^machine_[0-9a-f]{32}$ ]]; then
285
287
  printf -v ALIPAYCLI_BIZ_INFO '{"skill":"alipay-aipay","skill_version":"v%s","skill_user_id":"%s"}' "$version" "$skill_user_id"
286
288
  else
287
289
  printf -v ALIPAYCLI_BIZ_INFO '{"skill":"alipay-aipay","skill_version":"v%s"}' "$version"
288
290
  fi
291
+ merged="$(node "$profile_script" merge-biz-info --skill-dir "$skill_dir" --base-json "$ALIPAYCLI_BIZ_INFO" 2>/dev/null || true)"
292
+ if [ -n "$merged" ]; then ALIPAYCLI_BIZ_INFO="$merged"; fi
289
293
  export ALIPAYCLI_BIZ_INFO
290
294
  }
291
295
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const maxProfileBytes = 64 * 1024;
9
+ const validProfileValue = value => typeof value === 'string' && value.trim() === value
10
+ && [...value].length >= 1 && [...value].length <= 128 && !/[\p{Cc}\p{Cs}]/u.test(value);
11
+
12
+ function readProfileFields(skillDir) {
13
+ try {
14
+ if (typeof skillDir !== 'string' || !path.isAbsolute(skillDir)) return {};
15
+ const file = path.join(skillDir, 'PROFILE');
16
+ const stat = fs.lstatSync(file);
17
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxProfileBytes) return {};
18
+ const profile = JSON.parse(fs.readFileSync(file, 'utf8'));
19
+ if (!profile || Array.isArray(profile) || typeof profile !== 'object') return {};
20
+ return {
21
+ chinfo: validProfileValue(profile.chinfo) ? profile.chinfo : '',
22
+ vid: validProfileValue(profile.vid) ? profile.vid : '',
23
+ };
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ export function readProfileChinfo(skillDir) { return readProfileFields(skillDir).chinfo || ''; }
30
+ export function readProfileVid(skillDir) { return readProfileFields(skillDir).vid || ''; }
31
+
32
+ export function runProfileCli(argv = process.argv) {
33
+ if (argv.length === 5 && argv[2] === 'read-chinfo' && argv[3] === '--skill-dir') {
34
+ process.stdout.write(readProfileChinfo(argv[4]));
35
+ return 0;
36
+ }
37
+ if (argv.length === 7 && argv[2] === 'merge-biz-info' && argv[3] === '--skill-dir' && argv[5] === '--base-json') {
38
+ try {
39
+ const base = JSON.parse(argv[6]);
40
+ if (!base || Array.isArray(base) || typeof base !== 'object') return 1;
41
+ const fields = readProfileFields(argv[4]);
42
+ if (fields.chinfo) base.chinfo = fields.chinfo;
43
+ if (fields.vid) base.vid = fields.vid;
44
+ process.stdout.write(JSON.stringify(base));
45
+ return 0;
46
+ } catch { return 1; }
47
+ }
48
+ return 1;
49
+ }
50
+
51
+ if (process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
52
+ process.exitCode = runProfileCli();
53
+ }
@@ -35,6 +35,7 @@ function filesUnder(root) {
35
35
  else throw Error('Non-regular session Skill file');
36
36
  };
37
37
  for (const entry of ['SKILL.md','VERSION','references']) walk(entry);
38
+ if (fs.existsSync(path.join(root,'PROFILE'))) walk('PROFILE');
38
39
  return files;
39
40
  }
40
41
  function identity(root, channel, version) {
@@ -48,15 +49,20 @@ function identity(root, channel, version) {
48
49
  (channel==='pre'?'alipay-aipay-pre':'alipay-aipay')) throw Error('Session Skill identity mismatch');
49
50
  }
50
51
 
51
- export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, home = os.homedir()}) {
52
+ export function createSessionSkill({sourceDir, channel, version, packageVersion, targetDir, profileContent, home = os.homedir()}) {
52
53
  identity(sourceDir, channel, version);
53
54
  if (!semver.test(packageVersion) || !path.isAbsolute(targetDir)) throw Error('Invalid session install source');
54
- const manifest = filesUnder(sourceDir);
55
55
  const root = privateRoot(home);
56
56
  const skillDir = path.join(root, `${channel}-${version}-${randomUUID()}`);
57
57
  fs.mkdirSync(skillDir, {mode:0o700});
58
58
  try {
59
59
  for (const entry of ['SKILL.md','VERSION','references']) fs.cpSync(path.join(sourceDir,entry),path.join(skillDir,entry),{recursive:true,errorOnExist:true,force:false});
60
+ const selectedProfile = profileContent === undefined ? readProfile(targetDir) : profileContent;
61
+ if (selectedProfile !== null && selectedProfile !== undefined) {
62
+ if (!(Buffer.isBuffer(selectedProfile) || selectedProfile instanceof Uint8Array) || selectedProfile.length > 64 * 1024) throw Error('Invalid PROFILE content');
63
+ fs.writeFileSync(path.join(skillDir,'PROFILE'),selectedProfile,{flag:'wx',mode:0o600});
64
+ }
65
+ const manifest = filesUnder(skillDir);
60
66
  const receipt = {schemaVersion:1, scope:'session', channel, version, packageVersion,
61
67
  skillDir:fs.realpathSync(skillDir), targetDir:path.resolve(targetDir)};
62
68
  fs.writeFileSync(path.join(skillDir,receiptName),JSON.stringify({...receipt,manifest}),{flag:'wx',mode:0o600});
@@ -68,6 +74,15 @@ export function createSessionSkill({sourceDir, channel, version, packageVersion,
68
74
  }
69
75
  }
70
76
 
77
+ function readProfile(skillDir) {
78
+ const file=path.join(skillDir,'PROFILE');
79
+ if (!fs.existsSync(file)) return null;
80
+ plain(file);
81
+ const stat=fs.lstatSync(file);
82
+ if (stat.size > 64 * 1024) throw Error('PROFILE is too large');
83
+ return fs.readFileSync(file);
84
+ }
85
+
71
86
  export function verifySessionSkill(receipt, {home = os.homedir(), channel, version, packageVersion, targetDir} = {}) {
72
87
  if (!receipt || receipt.schemaVersion!==1 || receipt.scope!=='session' || !['stable','pre'].includes(receipt.channel) || !semver.test(receipt.version||'') || !path.isAbsolute(receipt.skillDir||'') ||
73
88
  !path.isAbsolute(receipt.targetDir||'') || !semver.test(receipt.packageVersion||'')) throw Error('Invalid session receipt');
@@ -4,6 +4,8 @@
4
4
 
5
5
  唯一 stderr 终态:`SELF_UPDATE:CURRENT=<合法 SemVer>`、`SELF_UPDATE:UPDATED=<合法 SemVer>`、`SELF_UPDATE:SESSION_READY=<合法 SemVer>`、`SELF_UPDATE:RETRY_WITH_NETWORK`、`SELF_UPDATE:SKIPPED_NETWORK_RESTRICTED`、`SELF_UPDATE:CHECK_FAILED`、`SELF_UPDATE:INSTALL_FAILED`、`SELF_UPDATE:VERIFY_FAILED`。`UPDATED` 用原子替换后的新版,除 `SESSION_READY` 外其余用当前可用版;marker 异常读当前入口,不得称最新或更新成功;停止读旧版,读取同一根目录的新版入口。仅 `RETRY_WITH_NETWORK` 可取权后原样重试一次,拒绝、不支持或再次返回时读当前入口;一次网络恢复外,不重查、取权、用 `sudo`、要求用户操作或写更新状态。每次只查一次 npm latest 的 `version` 和 `alipayAipayChannels`;只比较本渠道 Skill 版本,npm 包版本仅用于精确下载;`VERSION` 缺失、非法、落后或业务入口缺失时只从查询所得精确 npm 包版本安装当前渠道一次。渠道由当前目录的 `references/normal/json/release-channel.json` 与 Skill 名称共同校验;旧正式版缺少渠道文件时只兼容 `stable`,预发版禁止回退正式版。缺少或非法的远端渠道元数据返回 `CHECK_FAILED`,不得按 npm 包版本兜底。不调用公开 `install --target all`;超时终止同一次调用的进程树并清理。
6
6
 
7
+ 公开 npm `install` 是 `PROFILE` 的唯一创建入口:`chinfo` 显式传值时更新为该来源,未传时更新为 `origin`;只有显式 `--vid <值>` 才新增或更新 `vid`,未传时保留已有 `vid`,其他已有扩展字段也保留。自更新只原样迁移当前 Skill 根目录已有的普通 `PROFILE`;不存在时新版及 Windows 会话副本都保持不存在。内部 `npx ... install-current` 不属于用户主动安装,禁止创建 `origin` 或修改 `vid`。源包的 `skill/` 与 `skill-pre/` 不包含 `PROFILE`。
8
+
7
9
  本地正式渠道的 `X.Y.Z-beta`、`X.Y.Z-betaN`(兼容 `-beta.N`)和预发渠道的 `X.Y.Z-pre-beta`、`X.Y.Z-pre-betaN` 均为固定测试版本。完整安装直接返回 `CURRENT` 且不查询 npm;业务入口损坏时返回 `VERIFY_FAILED`,仍不下载修复或转正。更换版本必须由测试人员显式执行 npm 安装器的 `install`,普通 `update` 同样不推进 beta。正常版本分别为 `X.Y.Z` 与 `X.Y.Z-pre`,远端对应渠道若为 beta 或名称不匹配,返回 `CHECK_FAILED`,不自动安装。beta 不参与版本排序,无自动追踪和自动转正。
8
10
 
9
11
  `SESSION_READY` 是 Windows 目录占用或权限失败的有限恢复:只有替换前的备份/暂存准备失败,安装器才从本次精确 npm 包创建并逐文件 SHA-256 复核独立副本;不混合覆盖原目录,不更新全局安装记录。必须同时取得唯一 `ALIPAY_AIPAY_INTERNAL:SELF_UPDATE_SESSION_SKILL_DIR=<JSON 字符串>`,用已核验的绝对路径重绑 `SKILL_DIR`,明确告知“全局目录未更新,当前会话使用已校验的新版本;下次会话仍可能加载旧版”。废弃旧 loaded/CLI 刷新事实,保留本渠道自更新已检查事实,立即读取新路径的 `SKILL.md` 并继续其余启动协议,禁止再次自更新。内测版仍需按新路径/版本完成声明及服务/采集选择,安装意图不得代替采集同意。路径 marker 缺失、冲突或新入口不可读时,不切换、不声称更新成功,使用仍可用的原目录或说明入口不可用。
@@ -158,7 +158,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-execute-read
158
158
 
159
159
  若组合动作本身不可用,原子 fallback 固定为先执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding execute --product-type "$PRODUCT_TYPE" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`,再执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env check --product-type "$PRODUCT_TYPE" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"`;fallback 不得重复 stage 或重复环境检查。
160
160
 
161
- 签约脚本仍通过 `error_handler.sh -> common.sh` 初始化 `DEV_TOOL_NAME`、`PLATFORM`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本和匿名 `skill_user_id` 的 `ALIPAYCLI_BIZ_INFO`,以及当前产品 `PRODUCT`。有任务工具时只内部记录实际阶段,不维护任务数量;只保留当前对话最小上下文,不创建业务进度文件。
161
+ 签约脚本仍通过 `error_handler.sh -> common.sh` 初始化 `DEV_TOOL_NAME`、`PLATFORM`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选 `chinfo`、`vid` 的 `ALIPAYCLI_BIZ_INFO`,以及当前产品 `PRODUCT`。缺少 `chinfo` 或 `vid` 时分别保持省略,不以 `PLATFORM` 回填。有任务工具时只内部记录实际阶段,不维护任务数量;只保留当前对话最小上下文,不创建业务进度文件。
162
162
 
163
163
  本步骤成功时不输出“环境检查完成”“任务已创建”等对客消息,直接进 Step 3;只有安装失败、权限不足或缺工具时才输出实际问题和恢复动作。工具能力差异不得省略任务、步骤或完成状态。
164
164
 
@@ -249,7 +249,7 @@ Step 5 直接使用对应 phase 的完整命令;仅出现脚本异常、需要
249
249
  2. 服务:仅按量付费由 runner 执行原 `service.sh list`;其他产品 envelope 固定为 `NOT_APPLICABLE`。成功候选只取 envelope 的 `service.candidates`。
250
250
  3. 应用:runner 执行原 `app.sh list --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE"`。成功候选和 `WEBAPP|MOBILEAPP` 只取 envelope 的 application 分支;不得宣称返回应用已与当前支付产品绑定。
251
251
 
252
- > ⚠️ 脚本通过 `error_handler.sh` 间接初始化 `DEV_TOOL_NAME`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本和匿名 `skill_user_id` 对应的 `ALIPAYCLI_BIZ_INFO`,以及 CLI `PRODUCT`;签约查询必须传入当前产品的 `--sales-code` 和 `--product-type`(aipay|webpay|apppay)做一致性校验。AI 网页应用收款和 AI 移动应用收款使用不同 salesCode,禁止混用。
252
+ > ⚠️ 脚本通过 `error_handler.sh` 间接初始化 `DEV_TOOL_NAME`、优先取有效第三方项目 ID、缺失时取当前 `run_id` 的 `PLATFORM_ID`、当前 Skill 版本、匿名 `skill_user_id` 及合法 `PROFILE` 中可选 `chinfo`、`vid` 对应的 `ALIPAYCLI_BIZ_INFO`,以及 CLI `PRODUCT`;缺少任一来源字段时分别保持省略。签约查询必须传入当前产品的 `--sales-code` 和 `--product-type`(aipay|webpay|apppay)做一致性校验。AI 网页应用收款和 AI 移动应用收款使用不同 salesCode,禁止混用。
253
253
 
254
254
  各适用查询仍是独立 CLI/MCP 调用,runner 只编排现有脚本,必须分别完成原有错误检测,禁止伪造合并 MCP。失败后按错误类型处理:
255
255