@alipay/alipay-aipay 1.8.4 → 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.
- package/dist/commands/install.js +10 -2
- package/dist/index.js +6 -1
- package/dist/utils/file.js +11 -2
- package/dist/utils/profile.js +64 -0
- package/package.json +3 -3
- package/skill/SKILL.md +2 -0
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +1 -1
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
- package/skill/references/normal/alipay-cli-env.md +2 -2
- package/skill/references/normal/scripts/common.sh +6 -2
- package/skill/references/normal/scripts/profile.mjs +53 -0
- package/skill/references/normal/scripts/session_skill.mjs +17 -2
- package/skill/references/normal/self-update.md +2 -0
- package/skill/references/onboarding/flow.md +2 -2
- package/skill-pre/SKILL.md +2 -0
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/integration/flow.md +1 -1
- package/skill-pre/references/integration/modules/sandbox/alipay-sandbox-tool.md +2 -2
- package/skill-pre/references/normal/alipay-cli-env.md +2 -2
- package/skill-pre/references/normal/scripts/common.sh +6 -2
- package/skill-pre/references/normal/scripts/profile.mjs +53 -0
- package/skill-pre/references/normal/scripts/session_skill.mjs +17 -2
- package/skill-pre/references/normal/self-update.md +2 -0
- package/skill-pre/references/onboarding/flow.md +2 -2
package/dist/commands/install.js
CHANGED
|
@@ -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();
|
package/dist/utils/file.js
CHANGED
|
@@ -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
|
+
"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.8.
|
|
59
|
-
"pre": "1.8.
|
|
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.8.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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) || profile.schemaVersion !== 1) 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
|
|
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
|
|
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
|
|
package/skill-pre/SKILL.md
CHANGED
|
@@ -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.
|
|
1
|
+
1.8.4-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
|
|
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
|
|
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
|
|
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`,并且其中 `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`。
|
|
147
147
|
|
|
148
148
|
```
|
|
149
149
|
❌ 禁止:向用户输出"环境检查完成,检测到您正在使用 Claude Code 环境"等检测结果
|
|
150
150
|
❌ 禁止:向用户透出任何关于 AI 编程工具检测的信息
|
|
151
|
-
✅ 正确:检测结果仅供内部使用,只用于脚本设置 `PLATFORM`;`PLATFORM_ID` 优先使用有效第三方项目 ID,缺失时回退当前 `run_id`;`ALIPAYCLI_BIZ_INFO` 由当前 Skill
|
|
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
|
-
|
|
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) || profile.schemaVersion !== 1) 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
|
|
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
|
|
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
|
|