@alipay/alipay-aipay 1.8.3 → 1.8.5

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 (33) hide show
  1. package/dist/commands/install.js +10 -2
  2. package/dist/index.js +6 -1
  3. package/dist/utils/file.js +11 -2
  4. package/dist/utils/profile.js +64 -0
  5. package/package.json +3 -3
  6. package/skill/SKILL.md +2 -0
  7. package/skill/VERSION +1 -1
  8. package/skill/references/integration/flow.md +1 -1
  9. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
  10. package/skill/references/normal/alipay-cli-env.md +2 -2
  11. package/skill/references/normal/scripts/common.sh +6 -2
  12. package/skill/references/normal/scripts/onboarding_message_runner.mjs +80 -250
  13. package/skill/references/normal/scripts/profile.mjs +53 -0
  14. package/skill/references/normal/scripts/session_skill.mjs +17 -2
  15. package/skill/references/normal/self-update.md +2 -0
  16. package/skill/references/onboarding/flow.md +11 -11
  17. package/skill/references/onboarding/modules/product-sign.md +1 -1
  18. package/skill/references/onboarding/modules/scripts/mcc.sh +151 -290
  19. package/skill/references/runtime-entry.md +1 -1
  20. package/skill-pre/SKILL.md +2 -0
  21. package/skill-pre/VERSION +1 -1
  22. package/skill-pre/references/integration/flow.md +1 -1
  23. package/skill-pre/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
  24. package/skill-pre/references/normal/alipay-cli-env.md +2 -2
  25. package/skill-pre/references/normal/scripts/common.sh +6 -2
  26. package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +80 -250
  27. package/skill-pre/references/normal/scripts/profile.mjs +53 -0
  28. package/skill-pre/references/normal/scripts/session_skill.mjs +17 -2
  29. package/skill-pre/references/normal/self-update.md +2 -0
  30. package/skill-pre/references/onboarding/flow.md +11 -11
  31. package/skill-pre/references/onboarding/modules/product-sign.md +1 -1
  32. package/skill-pre/references/onboarding/modules/scripts/mcc.sh +151 -290
  33. package/skill-pre/references/runtime-entry.md +1 -1
@@ -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,64 @@
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 = { schemaVersion: 1, 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' && parsed.schemaVersion === 1) {
32
+ profile = { ...parsed };
33
+ }
34
+ }
35
+ catch {
36
+ // A user-initiated install repairs an unreadable PROFILE with the current fields.
37
+ }
38
+ }
39
+ profile.schemaVersion = 1;
40
+ profile.chinfo = parseChinfo(updates.chinfo);
41
+ if (updates.vid !== undefined)
42
+ profile.vid = parseVid(updates.vid);
43
+ return Buffer.from(`${JSON.stringify(profile, null, 2)}\n`, 'utf8');
44
+ }
45
+ export async function readPreservedProfile(skillDir) {
46
+ const profilePath = path.join(skillDir, PROFILE_FILE);
47
+ let stat;
48
+ try {
49
+ stat = await fs.lstat(profilePath);
50
+ }
51
+ catch (error) {
52
+ if (error.code === 'ENOENT')
53
+ return null;
54
+ throw error;
55
+ }
56
+ if (!stat.isFile() || stat.isSymbolicLink()) {
57
+ throw new Error(`已有 ${PROFILE_FILE} 必须是普通文件,无法安全保留`);
58
+ }
59
+ if (stat.size > MAX_PROFILE_BYTES) {
60
+ throw new Error(`已有 ${PROFILE_FILE} 超过 ${MAX_PROFILE_BYTES} 字节,无法安全保留`);
61
+ }
62
+ return fs.readFile(profilePath);
63
+ }
64
+ //# sourceMappingURL=profile.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
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.9",
59
- "pre": "1.8.2-pre"
58
+ "stable": "1.8.1",
59
+ "pre": "1.8.4-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.7.9
1
+ 1.8.1
@@ -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`,并且其中 `schemaVersion=1`,则分别校验可选 `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