subagent-delegation 0.0.0-stage → 0.3.0

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.
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { realpath } from 'node:fs/promises';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { VERSION, LEVELS, ROLES, MODE_COMMANDS, argumentError, validateDefinition, validatePair, explainSelection, inspectProject } from '../lib/codex.mjs';
6
+
7
+ const HELP = `sub-d ${VERSION} — Codex 子代理委派配置
8
+ 需要 Node.js 22+。npm 安装仅安装命令;setup 显式配置 Codex。
9
+ 用法:sub-d <命令> [选项]。原命令 subagent-delegation 同样可用。
10
+
11
+ setup [--mode MODE] [--dry-run]
12
+ mode set MODE [--dry-run]
13
+ mode list | mode show MODE
14
+ mode create NAME [--from MODE] [--dry-run]
15
+ mode configure MODE --level L1|L2|L3|L4 --model MODEL --effort EFFORT
16
+ [--fallback-model MODEL --fallback-effort EFFORT | --clear-fallback]
17
+ mode configure MODE --definition-stdin [--dry-run]
18
+ status [--details] [--project PATH]
19
+ explain --level L1|L2|L3|L4 [--mode MODE] [--role ROLE]
20
+ models check [--verify] [--model MODEL --effort EFFORT]
21
+ restore [--dry-run]
22
+ serve [--port PORT] [--open]
23
+
24
+ 内置档位:low(经济)、mid(适中,首次配置默认)、fire(性能)。
25
+ 省略 --agent 时默认使用 codex;也可显式指定 --agent codex。
26
+ 目标目录支持 --codex-home PATH;默认 CODEX_HOME 或 ~/.codex。
27
+ 查询及写命令支持 --json;写命令支持 --expect-revision REV。
28
+ --help [--json] | --version [--json]
29
+ 配置部署改变后,请重启 Codex。目录检查不证明模型调用权限。
30
+ 完整说明:https://github.com/fanglinwei/subagent-delegation/blob/main/docs/usage.md`;
31
+
32
+ const strings = ['agent', 'codex-home', 'mode', 'from', 'level', 'model', 'effort', 'fallback-model', 'fallback-effort', 'expect-revision', 'role', 'project', 'port'];
33
+ const booleans = ['dry-run', 'json', 'details', 'clear-fallback', 'definition-stdin', 'verify', 'open', 'help', 'version'];
34
+ const optionTypes = Object.fromEntries([...strings.map(k => [k, { type: 'string' }]), ...booleans.map(k => [k, { type: 'boolean' }])]);
35
+ const common = ['agent', 'codex-home', 'json'];
36
+ const write = [...common, 'dry-run', 'expect-revision'];
37
+ const commands = {
38
+ setup: [...write, 'mode'], set: write, list: common, show: common,
39
+ create: [...write, 'from'], configure: [...write, 'level', 'model', 'effort', 'fallback-model', 'fallback-effort', 'clear-fallback', 'definition-stdin'],
40
+ status: [...common, 'details', 'project'], explain: [...common, 'level', 'mode', 'role'],
41
+ models: [...common, 'verify', 'model', 'effort'], restore: write,
42
+ serve: ['agent', 'codex-home', 'port', 'open'],
43
+ };
44
+
45
+ export function parseCommand(argv) {
46
+ let parsed;
47
+ try { parsed = parseArgs({ args: argv, options: optionTypes, allowPositionals: true, strict: true, tokens: true }); }
48
+ catch { throw argumentError('未知参数或缺少参数值;使用 --help 查看命令。'); }
49
+ const { values, positionals, tokens } = parsed;
50
+ const seen = new Set();
51
+ for (const token of tokens) if (token.kind === 'option') {
52
+ if (seen.has(token.name)) throw argumentError('参数不能重复。');
53
+ seen.add(token.name);
54
+ }
55
+ if (values.help || values.version) {
56
+ if (positionals.length || Object.keys(values).some(k => !['help', 'version', 'json'].includes(k)) || (values.help && values.version)) throw argumentError('帮助/版本仅支持 --json。');
57
+ return { action: values.help ? 'help' : 'version', values, options: {} };
58
+ }
59
+ let action = positionals[0], selector;
60
+ let count = 1;
61
+ if (action === 'mode') {
62
+ action = positionals[1]; count = 2;
63
+ if (!['set', 'list', 'show', 'create', 'configure'].includes(action)) throw argumentError('未知 mode 子命令。');
64
+ if (action !== 'list') { selector = positionals[2]; count = 3; }
65
+ } else if (action === 'models') {
66
+ if (positionals[1] !== 'check') throw argumentError('models 仅支持 check。');
67
+ count = 2;
68
+ }
69
+ if (!commands[action] || positionals.length !== count || ((count === 3) && !selector)) throw argumentError('命令或位置参数无效;使用 --help。');
70
+ if (Object.keys(values).some(k => !commands[action].includes(k))) throw argumentError('该命令不支持给定参数。');
71
+ if (values.agent !== undefined && values.agent !== 'codex') throw argumentError('首版仅支持 --agent codex。', 'AGENT_UNSUPPORTED');
72
+ if (values.level !== undefined && !LEVELS.includes(values.level)) throw argumentError('任务等级必须是 L1–L4。');
73
+ if (values.role !== undefined && !Object.hasOwn(ROLES, values.role)) throw argumentError('委派角色无效。');
74
+ if (action === 'explain' && !values.level) throw argumentError('explain 需要 --level。');
75
+ if (action === 'models' && Boolean(values.model) !== Boolean(values.effort)) throw argumentError('--model 与 --effort 必须成对。');
76
+ if (action === 'models' && values.model) validatePair({ model: values.model, effort: values.effort });
77
+ if (action === 'configure') {
78
+ const fields = ['level', 'model', 'effort', 'fallback-model', 'fallback-effort', 'clear-fallback'];
79
+ if (values['definition-stdin']) {
80
+ if (fields.some(k => Object.hasOwn(values, k))) throw argumentError('整表 stdin 与单级参数互斥。');
81
+ } else {
82
+ if (!values.level || !values.model || !values.effort) throw argumentError('单级配置需要 --level、--model、--effort。');
83
+ validatePair({ model: values.model, effort: values.effort });
84
+ if (Boolean(values['fallback-model']) !== Boolean(values['fallback-effort']) || (values['clear-fallback'] && values['fallback-model'])) throw argumentError('备用参数必须成对,且与 --clear-fallback 互斥。');
85
+ if (values['fallback-model']) validatePair({ model: values['fallback-model'], effort: values['fallback-effort'] });
86
+ }
87
+ }
88
+ if (action === 'serve' && values.port !== undefined && (!/^\d+$/.test(values.port) || (Number(values.port) !== 0 && (Number(values.port) < 1024 || Number(values.port) > 65535)))) throw argumentError('端口必须是 0 或 1024–65535。');
89
+ const options = Object.fromEntries(Object.entries(values).map(([key, value]) => [key.replace(/-([a-z])/g, (_, c) => c.toUpperCase()), value]));
90
+ if (action === 'create') options.name = selector;
91
+ else if (selector) options.mode = selector;
92
+ return { action, values, options };
93
+ }
94
+
95
+ async function readDefinition() {
96
+ const chunks = []; let length = 0;
97
+ for await (const chunk of process.stdin) {
98
+ length += chunk.length;
99
+ if (length > 65536) throw argumentError('整表输入超过 64 KiB。');
100
+ chunks.push(chunk);
101
+ }
102
+ let value;
103
+ try { value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks))); }
104
+ catch { throw argumentError('整表输入必须是有效 UTF-8 JSON。'); }
105
+ return { levels: validateDefinition(value) };
106
+ }
107
+
108
+ function terminalText(value) {
109
+ return value.replace(/[\p{Cc}\p{Cf}\p{Cs}]/gu, char => `\\u${char.codePointAt(0).toString(16).padStart(4, '0')}`);
110
+ }
111
+
112
+ function emit(data, errors, json) {
113
+ if (json) process.stdout.write(JSON.stringify({ report_schema_version: 1, data, errors }) + '\n');
114
+ else {
115
+ if (data) {
116
+ if (data.help) process.stdout.write(data.help + '\n');
117
+ else if (data.cli_version && Object.keys(data).length === 1) process.stdout.write(data.cli_version + '\n');
118
+ else process.stdout.write(renderHuman(data) + '\n');
119
+ }
120
+ for (const error of errors) process.stderr.write(`${terminalText(error.code)}: ${terminalText(error.message)}\n`);
121
+ }
122
+ }
123
+
124
+ function renderHuman(data) {
125
+ const lines = [];
126
+ const pair = value => value ? `${value.model} / ${value.effort}` : '无';
127
+ const modeId = mode => mode.kind === 'builtin' ? Object.keys(MODE_COMMANDS).find(command => MODE_COMMANDS[command] === mode.id) ?? mode.id : mode.id;
128
+ const levels = definition => {
129
+ for (const [level, value] of Object.entries(definition.levels || {})) lines.push(`${level} ${pair(value)} 备用:${pair(value.fallback)}`);
130
+ };
131
+ if (data.installation) {
132
+ lines.push(`Codex home:${data.home}`, `工具 ${data.cli_version} · 配置 ${data.configuration_version || '未安装'}`, `状态:${data.installation} · 完整性:${data.integrity}`, `已配置档位:${data.mode?.name || '无'}${data.mode ? ` (${modeId(data.mode)})` : ''}`, `恢复基线:${data.restoration.status}`);
133
+ if (data.mode) levels(data.mode);
134
+ if (data.rules.summary.length) {
135
+ lines.push('', '职责与规则:');
136
+ for (const role of data.roles) lines.push(`${role.name} ${role.configured_model || '未固定'} / ${role.configured_effort || '未固定'}`);
137
+ lines.push(...data.rules.summary.map(rule => `- ${rule}`), '', '相关配置与检查范围:', ...JSON.stringify({ related_config: data.related_config, inspection: data.inspection }, null, 2).split('\n'));
138
+ }
139
+ lines.push(...data.warnings);
140
+ } else if (data.result) {
141
+ lines.push(`操作:${data.action} · ${data.result} · ${data.configuration_status}`, `Codex home:${data.home}`, `档位:${data.mode?.name || '无'} · 变化文件:${data.changed_files?.length || 0}`);
142
+ if (data.created_mode) lines.push(`已创建:${data.created_mode.name} (${data.created_mode.id}),尚未设为当前档位。`);
143
+ if (data.result === 'preview') lines.push(...(data.changes || []).map(change => `${change.action} ${change.path}`));
144
+ if (data.custom_modes_removed?.length) lines.push(`清理自定义档位:${data.custom_modes_removed.join('、')}`);
145
+ if (data.restart?.message) lines.push(data.restart.message);
146
+ if (data.post_check?.status !== 'not_requested') lines.push(`账号目录后检查:${data.post_check?.status};目录不证明实际调用权限。`);
147
+ lines.push(...(data.warnings || []));
148
+ } else if (data.modes) {
149
+ lines.push(...data.modes.map(mode => `${mode.active ? '*' : ' '} ${mode.command ?? mode.id} ${mode.name} ${mode.kind}`));
150
+ } else if (data.definition) {
151
+ lines.push(`${data.definition.name} (${modeId(data.definition)}) · ${data.active ? '当前已配置' : '定义预览'}`, `来源:${data.source} · 定义版本:${data.definition_version || VERSION}`); levels(data.definition);
152
+ } else if (data.dispatch_role) {
153
+ lines.push(`${data.mode_name} · ${data.level}`, `主组合:${pair(data.primary)} · 备用:${pair(data.fallback)}`, `职责:${data.requested_role} → 委派角色:${data.dispatch_role}`, `权限契约:${data.permission_contract}`, `原因:${data.reason_codes.join('、')}`);
154
+ } else if (data.catalog) {
155
+ lines.push(`账号状态:${data.account.status} · 目录:${data.catalog.status} (${data.catalog.models.length} 个模型)`);
156
+ for (const value of data.checked_pairs) lines.push(`${pair(value)} · 参数:${value.parameter_support} · 调用权限:${value.access} · ${value.reason_code}`);
157
+ } else lines.push(JSON.stringify(data, null, 2));
158
+ for (const item of data.diagnostics || []) lines.push(`${item.code} [${item.status}] ${item.message}`);
159
+ if (data.runtime_verification) lines.push(`运行生效:${data.runtime_verification}`);
160
+ return lines.map(terminalText).join('\n');
161
+ }
162
+
163
+ async function main() {
164
+ const argv = process.argv.slice(2);
165
+ const json = argv.includes('--json');
166
+ try {
167
+ if (Number(process.versions.node.split('.')[0]) < 22) throw argumentError('需要 Node.js 22 或更新版本。', 'NODE_UNSUPPORTED');
168
+ const { action, options } = parseCommand(argv);
169
+ if (action === 'help' || action === 'version') { emit(action === 'help' ? { help: HELP } : { cli_version: VERSION }, [], json); return; }
170
+ if (action === 'configure' && options.definitionStdin) options.definition = await readDefinition();
171
+ const installer = await import('../lib/install.mjs');
172
+ const home = installer.resolveHome(options.codexHome);
173
+ if (action === 'serve') {
174
+ const { serveLocal } = await import('../lib/server.mjs');
175
+ const service = await serveLocal({ home, project: process.cwd(), port: Number(options.port || 0), open: Boolean(options.open) });
176
+ process.stdout.write(`本地配置页面:${service.url}\n目标 Codex home:${terminalText(home)}\n按 Ctrl+C 停止。\n`);
177
+ const stop = async () => { process.off('SIGINT', stop); process.off('SIGTERM', stop); await service.close(); };
178
+ process.on('SIGINT', stop); process.on('SIGTERM', stop);
179
+ return;
180
+ }
181
+ let data; let code = 0;
182
+ if (action === 'status') {
183
+ for (let attempt = 0; attempt < 2; attempt++) {
184
+ data = await installer.statusReport(home, options);
185
+ const related = await inspectProject(options.project || process.cwd());
186
+ if ((await installer.readSnapshot(home)).configuration_revision !== data.configuration_revision) {
187
+ if (!attempt) continue;
188
+ throw Object.assign(new Error('配置读取期间变化;请刷新。'), { code: 'BUSY', exitCode: 1 });
189
+ }
190
+ data.related_config.project_candidates = related.candidates;
191
+ data.inspection.checked_paths.push(...related.checked);
192
+ data.inspection.skipped_paths.push(...related.skipped);
193
+ data.diagnostics.push(...related.diagnostics);
194
+ break;
195
+ }
196
+ }
197
+ else if (action === 'list') data = await installer.listReport(home, options);
198
+ else if (action === 'show') data = await installer.showReport(home, options.mode, options);
199
+ else if (action === 'explain') {
200
+ let snapshot, shown;
201
+ for (let attempt = 0; attempt < 2; attempt++) {
202
+ snapshot = await installer.readSnapshot(home);
203
+ if (!options.mode && !snapshot.mode) throw Object.assign(new Error('尚无有效当前档位;先 setup,或用 --mode 明确预览内置档位。'), { code: 'NOT_INSTALLED', exitCode: 1 });
204
+ shown = await installer.showReport(home, options.mode);
205
+ if (snapshot.configuration_revision === shown.configuration_revision) break;
206
+ if (attempt) throw Object.assign(new Error('配置读取期间变化;请刷新。'), { code: 'BUSY', exitCode: 1 });
207
+ }
208
+ data = { configuration_revision: shown.configuration_revision, diagnostics: shown.diagnostics, source: shown.source, definition_version: shown.definition_version, ...explainSelection({ definition: shown.definition, level: options.level, role: options.role || 'task_delegate' }) };
209
+ } else if (action === 'models') {
210
+ const { checkAccountModels } = await import('../lib/models.mjs');
211
+ data = await checkAccountModels({ home, verify: Boolean(options.verify), target: options.model ? { model: options.model, effort: options.effort } : undefined });
212
+ code = data.exit_code || 0; delete data.exit_code;
213
+ } else {
214
+ data = await installer.mutate(home, action, options);
215
+ const target = data.post_check_target; delete data.post_check_target;
216
+ if (target && data.result === 'applied') {
217
+ try {
218
+ const { checkAccountModels } = await import('../lib/models.mjs');
219
+ const checked = await checkAccountModels({ home, selection: target });
220
+ data.post_check = { ...checked, status: checked.exit_code === 3 ? 'failed' : checked.exit_code ? 'incomplete' : 'passed' };
221
+ delete data.post_check.exit_code;
222
+ } catch { data.post_check = { status: 'failed', source_revision: data.revision_after, diagnostics: [{ code: 'MODEL_CHECK_FAILED', status: 'unknown', message: '配置已写入,账号目录检查未完成。' }] }; }
223
+ }
224
+ }
225
+ const errors = ['status', 'list', 'show', 'explain'].includes(action) ? (data.diagnostics || []).filter(item => item.status === 'fail').map(item => ({ code: item.code, message: item.message })) : [];
226
+ if (errors.length) code = 1;
227
+ emit(data, errors, json); process.exitCode = code;
228
+ } catch (error) {
229
+ const known = typeof error.code === 'string' && (error.exitCode === 1 || error.exitCode === 2 || error.exitCode === 3);
230
+ emit(error.details?.report || null, [{ code: known ? error.code : 'IO_ERROR', message: known ? error.message : '操作未完成;请检查路径、权限与配置格式。' }], json);
231
+ process.exitCode = known ? error.exitCode : 1;
232
+ }
233
+ }
234
+
235
+ if (process.argv[1] && import.meta.url === pathToFileURL(await realpath(process.argv[1])).href) await main();
package/docs/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # 文档导航
2
+
3
+ `subagent-delegation` 当前版本为 0.3.0。先阅读使用说明;计划、研究和静态稿保留设计依据,不代替当前版本的验收结果。
4
+
5
+ | 文档 | 用途 |
6
+ | --- | --- |
7
+ | [使用说明](usage.md) | 安装、命令、档位、模型检查、网页、冲突与恢复 |
8
+ | [实施验收记录](implementation-review.md) | 实测环境、五轮审查、测试与兼容边界 |
9
+ | [Skill](https://github.com/fanglinwei/subagent-delegation/blob/main/adapters/codex/skills/subagent-model-selection/SKILL.md) | 启动与每次委派前的简洁流程 |
10
+ | [开发计划](https://github.com/fanglinwei/subagent-delegation/blob/main/docs/superpowers/plans/2026-10-13-npm-delegation-modes.md) | 已确认的行为契约和原实施清单 |
11
+ | [网页规格](https://github.com/fanglinwei/subagent-delegation/blob/main/docs/superpowers/specs/2026-10-13-local-web-configuration-design.md) | 交互、命令映射及本地服务边界 |
12
+ | [参考项目比较](https://github.com/fanglinwei/subagent-delegation/blob/main/docs/research/2026-10-13-plan-review-reference-projects.md) | 固定提交的借鉴来源与宿主差异 |
13
+
14
+ npm 安装只安装工具;显式 setup 才配置绑定 home。网页保存或应用会执行实际 CLI。部署变化后重启 Codex。目录结果不证明调用权限。restore 只撤销本工具改动,旧版没有基线时不能自动恢复。
15
+
16
+ 桌面和移动静态稿保留在仓库 `docs/design/`,仅用来说明设计。实际页面随 npm 包交付,通过 `sub-d serve` 打开。当前版本仅验收 macOS 配置和接口;宿主加载、其他系统和真实推理分别记录未知范围。
@@ -0,0 +1,87 @@
1
+ # 0.3.0 发布验收
2
+
3
+ 日期:2026-10-14。以下为 0.3.0 的验证;后文 0.2.0 记录保留为历史依据。
4
+
5
+ - 新增短命令 `sub-d`,完整命令仍可用;Codex 为默认目标。
6
+ - 内置命令统一为 `low / mid / fire`,旧内部 id 保留在安装记录中;历史参数需更新,旧同名自定义档位可通过 custom-* id 选择。
7
+ - 本地网页重新设计,显示完整产品名 Subagent Delegation;保留草稿、预览、真实 CLI 应用、冲突保护与重启提醒。
8
+ - README 和使用说明补齐 npm 安装、0.3.0 升级、档位参数迁移与网页布局。
9
+
10
+ 验证环境:macOS、Node.js 24.19.0、既有 npm、Chrome 和 Playwright。所有配置测试使用临时 home。
11
+
12
+ | 检查 | 结果 |
13
+ | --- | --- |
14
+ | Node 测试 | 103 项通过,0 失败或跳过;升级恢复测试改用动态下一版本,继续验证版本保护 |
15
+ | Python 保护测试 | 10 项通过;默认 Node 20 不满足要求,切换 Node 24 后通过 |
16
+ | Chrome 集成 | setup、自定义创建/保存/激活、冲突保留与比较、模型检查错误、规则解释、恢复及键盘焦点通过;四页在 375/768/1280/1440px 无页面溢出,零脚本错误 |
17
+ | 隔离 npm 安装 | 两个命令入口均报告 0.3.0;0.2.0 → 0.3.0 保留活动自定义名称、模型和首次恢复基线;三档切换、包内网页与源文件一致、恢复原始 AGENTS 通过 |
18
+ | 发布包 | 27 个公开运行/文档文件,MIT;未加入开发测试、账号配置或安装生命周期脚本 |
19
+
20
+ 模型目录仍不证明请求权限;实际推理门禁保持关闭。未验证其他操作系统、全部 Node 22.x、桌面会话实际加载或读屏器。恢复与并发边界延续后文说明。
21
+
22
+ ---
23
+
24
+ # 0.2.0 实施与验收
25
+
26
+ 日期:2026-10-13。实现对应已确认计划,并补入用户要求:启动 Codex 时读取 Skill,每次创建、重新委派或模型变化前按当前档位检查。通过绑定 home 的 AGENTS 入口及角色指令要求执行,没有宿主委派拦截器。
27
+
28
+ ## 实现范围
29
+
30
+ | 计划任务 | 交付与验证入口 |
31
+ | --- | --- |
32
+ | 1–2:npm、数据与 Skill | bin、精确依赖、三档四级数据、简洁 Skill;cli/modes 测试 |
33
+ | 3–4:安装与切换 | 共同事务、修订号、所有权、基线、锁、普通失败回退及重启提示;install 测试 |
34
+ | 5–6:查询与自定义 | 安全字段投影、项目候选、解释、UUID 定义、整表 stdin 与活动部署;status/custom/cli 测试 |
35
+ | 7:模型检查 | 同 home 的限时 stdio RPC 账号/目录检查;models 模拟协议测试;实际推理门禁关闭 |
36
+ | 8:恢复 | 首次基线、标记外正文保留、全部自定义清理、安全回退;restore 测试 |
37
+ | 9–10:本地网页 | loopback、动作白名单、真实 CLI、版本冲突、四页面、原生表单;server 与 Chrome 集成 |
38
+ | 11:交付 | Python 兼容入口、本地 npm 包、README/使用说明、MIT;Python 基线与隔离安装验收 |
39
+
40
+ 主代理、供应商、审批、沙箱、并发及既有 default 不由本工具修改。四个子代理保留只读 Git、禁止递归、独占产出、局部适配/阻断、最小充分验证与 PARTIAL 返回规则。自定义模型只能修改主/备用组合。explain 省略职责时统一默认 task_delegate,避免由难度推导写权限;显式职责保持原权限契约。
41
+
42
+ Skill 的当前档位 JSON 由 setup 生成,源码不预设所有用户的活动档位。AGENTS 引用该 home 的实际路径,不擅自写共享 `~/.agents/skills`。原生 Skill 注册、显式规则读取与宿主强制执行分别判断。
43
+
44
+ ## 五轮审查
45
+
46
+ | 轮次 | 范围 | 发现与处理 |
47
+ | --- | --- | --- |
48
+ | R1 | 需求、接口、调用路径 | 修复未安装 explain 静默预览、查询漂移退出码、根规则/祖先候选及既有注册/全局子代理默认的诊断遗漏 |
49
+ | R2 | 输入、并发、恢复 | 修复临时文件准备后并发编辑覆盖、回滚晚到编辑丢失、最终漂移误报成功、恢复提交后查询失败误报未写入;补齐别名及无效 Unicode 校验 |
50
+ | R3 | 安全、隐私、权限、部署 | 修复终端控制字符注入;复核 Host/Origin/会话、CLI 白名单、符号链接、权限、RPC 脱敏及无推理分支 |
51
+ | R4 | 测试、兼容、包、CLI、网页 | 修复 npm bin 符号链接入口静默退出及 explain 丢失漂移诊断;隔离安装和 Chrome 操作通过 |
52
+ | R5 | 最终回归、diff、文档、许可 | 修复活动内置档位别名带空白时误读新版预设;核对有效 override 的 Unicode 空白语义、公开文档及 MIT |
53
+
54
+ 所有修复只用临时 home 或合成元数据。未以真实账号或生产配置作自动测试,没有强制覆盖路径。
55
+
56
+ ## 验证结果
57
+
58
+ 环境:macOS、Node.js 24.19.0;npm 打包/安装使用本机已安装 npm。Chrome 使用本机应用,Playwright 使用既有开发模块。要求为 Node 22+,没有声称单独验收全部 22.x。
59
+
60
+ | 检查 | 结果与证明范围 |
61
+ | --- | --- |
62
+ | `npm test` | 100 项通过,0 失败/跳过;配置、输入、并发、恢复、查询、服务及目录协议 |
63
+ | Python 保护基线 | 10 项通过;原安装器在 tests fixture,新入口调用 Node CLI |
64
+ | Chrome 集成 | setup、创建预览、保存/激活、冲突草稿、比较、解释、恢复及持续重启提醒通过 |
65
+ | 网页操作 | 键盘跳转、弹窗校验焦点、字段错误保留、375/768/1280/1440px、放大文字、零脚本错误通过;未作真实读屏实测 |
66
+ | 本地 npm 包 | 隔离 prefix/cache 安装通过,安装自身不配置;实际 bin、setup、切换、自定义、npm exec 查询/解释/切换/整表 stdin/恢复及再次 setup 通过;包内网页和真实 CLI 动作、SIGINT 停止通过 |
67
+ | 包与许可 | 运行资源、文档、MIT;无安装生命周期脚本;唯一依赖 smol-toml 1.9.1(BSD-3-Clause),未复制参考项目代码 |
68
+
69
+ 原计划复选框保留专项验收粒度,不概括勾选所有宿主/付费/跨平台条件。上表是当前交付证据;以下限制明确保留。
70
+
71
+ ## Codex 兼容门禁
72
+
73
+ 依据 Codex CLI **0.156.1** 的版本源码和导出协议。
74
+
75
+ - **G1 规则与角色入口**:核对独立角色 TOML 必需字段、递归发现及覆盖;阻止有效 home override 遮蔽,仍报告 RULES_LOAD_UNVERIFIED。源码证明不等于 App 或真实会话加载验收。
76
+ - **G2 账号与目录**:initialize、账号读取、分页、空账号/自定义供应商、超时、上限和脱敏经过模拟协议测试。配置后检查绑定已提交的模式、组合与修订号,失败不撤销写入。真实账号权限及桌面上下文未验证。
77
+ - **G3 受限实际调用**:协议不能证明禁用全部工具、MCP、委派及外部读取;readOnly 不满足隔离条件。--verify 在创建线程/推理之前返回 PROBE_ISOLATION_UNSUPPORTED,没有实际推理或“模型可用”承诺。
78
+
79
+ 版本来源:[角色 loader](https://github.com/openai/codex/blob/rust-v0.156.1/codex-rs/agent-roles/src/loader.rs)、[角色配置](https://github.com/openai/codex/blob/rust-v0.156.1/codex-rs/agent-roles/src/agent_role_config.rs)、[发现入口](https://github.com/openai/codex/blob/rust-v0.156.1/codex-rs/agent-roles/src/discovery.rs)、[配置 schema](https://github.com/openai/codex/blob/rust-v0.156.1/codex-rs/core/config.schema.json)。
80
+
81
+ 此版本 [home 规则读取器](https://github.com/openai/codex/blob/rust-v0.156.1/codex-rs/codex-home/src/instructions/mod.rs)完整读取全局规则;project_doc_max_bytes 只限制项目规则发现。因此不按项目预算错误拒绝全局追加;其他宿主的完整读取仍保留未知。
82
+
83
+ ## 借鉴与剩余边界
84
+
85
+ 借鉴解释理由、用户选择优先、首次恢复基线、完整交接和难度/职责分离。未引入外部分类型推理、供应商代理、任意 hook 或领域专有权限。固定提交及来源见 [参考项目比较](https://github.com/fanglinwei/subagent-delegation/blob/main/docs/research/2026-10-13-plan-review-reference-projects.md)。README 采用目的、安装、例子、边界、文档和许可的简洁组织。
86
+
87
+ 未验收 Linux、Windows、其他 Codex 版本、桌面会话加载、读屏器或真实推理。逐文件原子替换与普通失败回退不保证断电/崩溃时多文件整体原子;残留锁须核对后处理。并发保护不提供对恶意本机进程的内核级比较交换。Skill 执行依赖宿主遵守指令。
package/docs/usage.md ADDED
@@ -0,0 +1,251 @@
1
+ # 使用说明
2
+
3
+ `subagent-delegation` 0.3.0。首版支持 Codex;Node.js 22+。命令、网页和生成的 Skill 使用同一套档位数据。
4
+
5
+ 短命令为 `sub-d`,与原命令 `subagent-delegation` 完全等价。升级到 0.3.0 后执行 `sub-d setup --dry-run` 检查,再执行 `sub-d setup` 更新部署;保留当前档位、自定义定义和首次恢复基线。指定独立 home 时两次命令都需带原路径,完成后重启 Codex。npm 包名仍为 `subagent-delegation`。
6
+
7
+ ## 安装与配置
8
+
9
+ 推荐 npm registry 安装;GitHub 安装另外需要 Git,本地 `.tgz` 安装只需 Node/npm。
10
+
11
+ ```sh
12
+ npm install -g subagent-delegation
13
+ sub-d setup --mode mid --dry-run
14
+ sub-d setup --mode mid
15
+ ```
16
+
17
+ 一行安装并配置:
18
+
19
+ ```sh
20
+ npm install -g subagent-delegation && sub-d setup --mode mid
21
+ ```
22
+
23
+ 目标目录优先级为 `--codex-home PATH`、`CODEX_HOME`、用户 `~/.codex`。空格路径需要 shell 引号。命令不会设置主代理模型、全局子代理默认、供应商、审批、沙箱、并发或子代理启用开关。
24
+
25
+ 首次 setup 默认适中;已安装时保留当前选择。setup 也用于安全升级。npm 安装没有 postinstall 配置动作。`--dry-run` 不创建目标 home、锁或状态文件。
26
+
27
+ 所有配置部署变化都提示:**配置已写入,请重启 Codex 后使用新委派规则。** 运行中的任务继续使用原上下文;工具不自动重启、停止任务或保证动态加载。
28
+
29
+ ## 命令表
30
+
31
+ 默认智能体为 Codex;省略 `--agent` 与显式指定 `--agent codex` 等价。业务命令支持 `--codex-home PATH`。除 serve 外都支持 `--json`;写命令支持 `--dry-run` 和 `--expect-revision REV`。
32
+
33
+ | 命令 | 用途 |
34
+ | --- | --- |
35
+ | `setup [--mode MODE]` | 首次配置或升级,保留无关文件 |
36
+ | `mode set MODE` | 切换已安装 home 的档位 |
37
+ | `mode list` | 列出内置、自定义及当前选择 |
38
+ | `mode show MODE` | 查看四级主/备用、来源与定义版本,不激活 |
39
+ | `mode create NAME [--from MODE]` | 复制完整定义,默认从 mid,不自动激活 |
40
+ | `mode configure MODE --level Lx --model M --effort E` | 修改自定义一个等级 |
41
+ | `mode configure MODE --definition-stdin` | stdin 一次保存完整四级 |
42
+ | `status [--details] [--project PATH]` | 模式、角色、完整性、规则与覆盖诊断 |
43
+ | `explain --level Lx [--mode MODE] [--role ROLE]` | 静态解释任务等级、组合与职责角色,不调用 AI |
44
+ | `models check [--verify] [--model M --effort E]` | 账号/目录;verify 另受严格隔离门禁约束 |
45
+ | `restore` | 恢复首次配置前的受影响内容,清理本工具配置 |
46
+ | `serve [--port PORT] [--open]` | 前台启动本地设置页面 |
47
+ | `--help` / `--version` | 帮助及工具版本,可带 --json,不接受 home 参数 |
48
+
49
+ `MODE` 使用 `low`(经济)、`mid`(适中,首次配置默认)、`fire`(性能);内置档位仅接受这三个名称,中文用于说明含义。旧安装记录中的历史内部 id 仅供读取与恢复,不作为命令参数。自定义档位可使用内部 id 或唯一名称。职责为 `task_scan`、`task_execute`、`task_review`、`task_delegate`,explain 省略职责时默认 task_delegate。默认 explain 使用当前有效档位;未安装时需明确 `--mode mid` 等内置预览。
50
+
51
+ 名称以 `-` 开头时,在全部选项后使用 `--`:
52
+
53
+ ```sh
54
+ sub-d mode create -- '-我的档位'
55
+ ```
56
+
57
+ ## 查询与切换
58
+
59
+ ```sh
60
+ sub-d status
61
+ sub-d status --details --json
62
+ sub-d status --project "/path/to/project" --details
63
+ sub-d mode list
64
+ sub-d mode show fire
65
+ sub-d mode set low
66
+ sub-d explain --level L4 --role task_review
67
+ ```
68
+
69
+ 详细查询分开呈现已配置值、预设值、潜在覆盖与运行未验证。检查相关项目及祖先目录,不扫描全电脑;不输出账号凭据、私有指令、完整 TOML 或认证记录。范围外注册路径与未确定的 profile 只报告候选及未检查原因,不跟随任意文件。
70
+
71
+ 同一档位重复切换不重写;仅保存非活动定义不要求重启。配置版本与 CLI 版本不同,先 setup 升级,不能直接跨版本切换。
72
+
73
+ 无需全局安装的 npm 查询,以及本地包查询:
74
+
75
+ ```sh
76
+ npm exec --package=subagent-delegation@0.3.0 -- sub-d status --details
77
+ npm exec --package="/path/to/subagent-delegation-0.3.0.tgz" -- sub-d status --details
78
+ ```
79
+
80
+ ## 任务等级与委派职责
81
+
82
+ | 等级 | 判断依据 | 经济 | 适中 | 性能 |
83
+ | --- | --- | --- | --- | --- |
84
+ | L1 | 范围窄,容易直接核验 | Luna/high | Luna/high | Luna/high |
85
+ | L2 | 明确目标,多步产出 | Luna/high,仅容易核验 | Sol 6.1/medium | Sol 6.1/high |
86
+ | L3 | 复杂分析或关键独立复核 | Sol 6.1/medium | Sol 6.1/high | Astra/low |
87
+ | L4 | 困难推理、深层歧义、跨领域设计 | Sol 6.1/high | Sol 6.1/high | Astra/low |
88
+
89
+ 完整 id:`gpt-6-luna`、`gpt-6.1-sol`、`gpt-6-astra`。名称不证明价格、能力或账号权限。高影响结果需要独立证据与专业验证。
90
+
91
+ 四个职责:
92
+
93
+ - `task_scan`:L1 固定预设,只读取证。
94
+ - `task_execute`:L2 固定预设,只在明确独占范围产出。
95
+ - `task_review`:L3 固定预设,独立复核。
96
+ - `task_delegate`:不固定 model/effort,用于用户指定、继承、L4 或固定组合不匹配;保留原请求职责。
97
+
98
+ 等级与职责互相独立。例如经济模式下复杂产出升为 L3 时,不能继续用固定 Luna 的 task_execute 覆盖选择;应使用 task_delegate 请求 Sol/medium,同时保留范围内产出的权限契约。继承需核对宿主全局默认,省略参数不证明继承。
99
+
100
+ AGENTS 管理入口要求启动时读取 Skill,每次创建、重新委派或模型变化前按 Skill 检查。用户选择、预算、宿主和项目规则优先,工具不自动授权委派。主代理保留最终设计、共享裁决、Git 写操作、集成、关键验证及完成声明。子代理禁止递归、写 Git、外部环境和代理配置改动、图像生成;独占写入、局部适配/阻断、最小充分验证及 PARTIAL 返回在全部档位保留。
101
+
102
+ ## 备用与模型不可用
103
+
104
+ | 档位 | L1 备用 | L2 备用 | L3 备用 | L4 备用 |
105
+ | --- | --- | --- | --- | --- |
106
+ | 经济 | Sol 6.1/medium | Sol 6.1/medium | 无 | 无 |
107
+ | 适中 | Sol 6.1/medium | 无 | 无 | 无 |
108
+ | 性能 | Sol 6.1/medium | 无 | Sol 6.1/high | Sol 6.1/high |
109
+
110
+ 备用是候选,需要同时满足用户选择、原预算、宿主支持、原工具/输入能力、原职责与验收不变。写入任务还必须确认旧 worker 已停止并检查部分产出。CLI 不转发推理、不替父代理自动发备用、不换账号或供应商。
111
+
112
+ Astra 不可用:先区分认证、权限、额度、临时故障或能力不支持。允许备用且全部条件满足才用已配置 Sol/high;用户强制只用 Astra、预算不允许或旧 worker 停止未知时,受影响部分 BLOCKED,继续其他独立工作。缺资料或工具故障先补前提,不能靠强模型掩盖。
113
+
114
+ 每任务最多三次父代理发起的委派,同一组合最多额外一次;累计等待不超过 30 秒并遵守 Retry-After。宿主已重试或次数未知时不追加。异常或部分结果标 PARTIAL,不自动撤销其他人的修改。完整分类见 [失败处理](../adapters/codex/skills/subagent-model-selection/references/failure-policy.md)。
115
+
116
+ ## 自定义档位
117
+
118
+ ```sh
119
+ sub-d mode create "我的研究方案" --from fire
120
+ sub-d mode configure "我的研究方案" \
121
+ --level L4 --model gpt-6-astra --effort low \
122
+ --fallback-model gpt-6.1-sol --fallback-effort high
123
+ sub-d mode set "我的研究方案"
124
+ ```
125
+
126
+ 新建自定义档位不能命名为 `low`、`mid`、`fire`。若旧配置已有同名自定义档位,使用 `mode list` 显示的 custom-* id 选择;low、mid、fire 始终指向内置档位,原自定义配置保留。
127
+
128
+ 名称 trim + Unicode NFC 归一化,1–80 个码点,不能含控制字符、无效 Unicode 或与既有名称/id 混淆。名称只用于显示与查找,不拼文件路径;内部 id 为 UUID。模型 1–200、effort 1–40 个码点,拒绝无效 Unicode、控制字符和首尾空白。目录未知不等于标识无效。
129
+
130
+ 创建是完整快照,之后不跟随来源变化。内置档位不可改,先复制;不支持重命名、删除单个档位或增加任务等级。自定义也不能扩大权限。
131
+
132
+ 单级修改主组合时,未明确提供备用会清空原备用;完全相同的主组合重复保存保留已有备用。`--clear-fallback` 与备用参数互斥,主配置仍需完整。
133
+
134
+ 四级一次保存的 stdin 格式:
135
+
136
+ ```sh
137
+ sub-d mode configure "我的研究方案" --definition-stdin <<'JSON'
138
+ {
139
+ "levels": {
140
+ "L1": {"model": "gpt-6-luna", "effort": "high", "fallback": {"model": "gpt-6.1-sol", "effort": "medium"}},
141
+ "L2": {"model": "gpt-6.1-sol", "effort": "medium", "fallback": null},
142
+ "L3": {"model": "gpt-6.1-sol", "effort": "high", "fallback": null},
143
+ "L4": {"model": "gpt-6-astra", "effort": "low", "fallback": {"model": "gpt-6.1-sol", "effort": "high"}}
144
+ }
145
+ }
146
+ JSON
147
+ ```
148
+
149
+ 最高 64 KiB UTF-8 JSON;必须明确全部等级与备用/null,拒绝未知字段、缺级、半对参数或主备用相同。与单级参数互斥。活动定义与部署角色同一事务保存;非活动定义只保存,不激活。
150
+
151
+ ## 模型检查
152
+
153
+ ```sh
154
+ sub-d models check
155
+ sub-d models check --model gpt-6-astra --effort low --json
156
+ sub-d models check --verify
157
+ ```
158
+
159
+ setup、切换、活动自定义部署实际改变后,解锁再查询所选 home 的账号及模型目录;失败不撤销合法配置。查询和定义保存、restore、dry-run、无变化操作不会附带检查。
160
+
161
+ 检查通过 stdio app-server 在空临时目录进行,无认证刷新参数、无项目加载,不输出原始 RPC、stderr、邮箱、账号 ID 或凭据。分页最多 100 页,共享 30 秒期限,单帧 1 MiB、总接收 8 MiB。自定义供应商的空账号不直接判无权限。
162
+
163
+ 分别查看:目录是否完整、effort 是否支持、账号实际请求权限、可信实际身份。目录列出模型不证明请求成功;目录缺项仍 unknown。桌面会话、启动参数、profile 与 CLI 上下文匹配未核验,检查结果不能冒充桌面账号能力。
164
+
165
+ **0.3.0 的实际推理门禁继续关闭。** 已核验的 Codex CLI 0.156.1 无法证明禁用全部工具/委派及限定外部读取,因此 `--verify` 返回 exit 1、`PROBE_ISOLATION_UNSUPPORTED`,不创建线程或发送推理。没有有效当前档位且无显式目标时先报 `PROBE_TARGET_REQUIRED`。未来只有宿主强制隔离得到验证才能启用;页面仍保留显式额度确认,不默认调用。
166
+
167
+ ## 本地网页
168
+
169
+ ```sh
170
+ sub-d serve --codex-home "/path/to/codex-home" --open
171
+ sub-d serve --port 17321
172
+ ```
173
+
174
+ 只监听 `127.0.0.1`。默认端口 0 自动分配,指定端口允许 1024–65535;占用即失败。`--open` 打开浏览器,失败时手动复制终端 URL;Ctrl+C 等待正在执行的 CLI 安全完成后停止服务。
175
+
176
+ 服务绑定一个 home 和启动 cwd。换 home 时重新启动。四页入口为档位、委派规则、模型检查、恢复。先编辑草稿,再保存/应用;自定义四级由一个 stdin 命令事务提交。未保存的非活动定义不能直接激活。取消只清除草稿;已创建的档位会保留。
177
+
178
+ 桌面左侧导航切换页面,上方选择档位,中央查看或编辑 L1–L4 模型分配,右侧核对变更并应用或预览。手机使用顶部导航,变更确认放在模型配置下方。可用性证据和备用条件按需展开;仅选择档位不会写入配置,应用后仍需重启 Codex。
179
+
180
+ 页面通过白名单动作实际 spawn 本包 CLI,显示命令与结果;不直接写配置、不执行任意 shell。HTTP 使用精确 Host/Origin、会话 Cookie、随机 CSRF token、CSP;不支持远程监听、CORS、CDN、守护进程或自动排队。
181
+
182
+ 配置版本冲突保留草稿并零写入,点击“刷新后比较”后再决定保存。断线或结果未知不自动重试写操作,先刷新状态。页面关闭/重载可能丢失内存草稿,未使用浏览器持久存储。
183
+
184
+ ## JSON、退出码与冲突
185
+
186
+ JSON stdout 只有统一 envelope:
187
+
188
+ ```json
189
+ {"report_schema_version":1,"data":{},"errors":[]}
190
+ ```
191
+
192
+ 状态含 `configuration_revision`、CLI/配置版本、四级定义、角色、完整性与诊断。写报告含 `result`、`configuration_status`、变化路径、前后版本、后检查、`restart`、`runtime_verification`。`applied/written` 才是已写入;`preview/not_written` 是预览;失败分未写、已回退、部分残留,不能都当成功。
193
+
194
+ `--expect-revision` 是可选的终端乐观锁,网页写动作必须携带;版本不匹配报 `CONFIG_CHANGED`。来源版本不会被模型检查结束时的新配置版本替换。未确认的读取返回 null 版本,不猜测。
195
+
196
+ | 退出码 | 含义 |
197
+ | --- | --- |
198
+ | 0 | 命令完成;模型目录完整不等于调用已验证 |
199
+ | 1 | 关键配置/读写失败、检查未完成或安全门禁不支持 |
200
+ | 2 | 参数、定义或选择无效 |
201
+ | 3 | 完整目录可靠证明所请求参数不支持 |
202
+
203
+ 常见诊断:
204
+
205
+ - `RULES_SHADOWED`:非空 home AGENTS.override.md 遮蔽入口。工具不改 override;用户自行合并原规则并停用 override,或选择未遮蔽 home。
206
+ - `MANAGED_FILE_DRIFT` / `MANAGED_RULES_DRIFT`:人工改动、删除或 chmod;保留用户内容,核对后重新操作,无 force 覆盖开关。
207
+ - `BUSY`:有写入锁或读取持续变化。先确认其他命令已停止;不要自动删除锁。进程崩溃后可能需人工核对锁与残留。
208
+ - `VERSION_MISMATCH`:先用当前工具 setup 升级;禁止旧工具降级覆盖。
209
+ - `RESTORE_METADATA_INVALID` / `RESTORE_BASELINE_UNAVAILABLE`:恢复记录损坏或旧版没有原始基线,使用原备份或人工核对,不猜测恢复。
210
+ - `MODEL_ACCESS_UNKNOWN` / `MODEL_NOT_LISTED`:权限未知;不能当作可用或不可用的最终证明。
211
+
212
+ 配置文件采用逐文件原子替换、共同独占锁、完整观察复读、状态最后写入和普通失败回退。回退不覆盖并发编辑,残留单独报告。多文件在进程崩溃/断电时并非一个原子事务;不要在应用期间手工修改管理文件。
213
+
214
+ ## 恢复、迁移与卸载
215
+
216
+ ```sh
217
+ sub-d restore --dry-run
218
+ sub-d restore
219
+ npm uninstall -g subagent-delegation
220
+ ```
221
+
222
+ restore 后重启 Codex。它不会卸载 npm 工具、删除整个 home、清理账号或重启应用。npm 卸载也不会代做 restore。
223
+
224
+ 首次新版 setup 记录受影响文件的原存在状态与普通权限,仅允许接管同内容公开工具资产;AGENTS 只记录管理区块与校验元数据,不保存完整私有正文、config 或认证文件。切换/升级保留首次基线,新路径追加原值,退场路径保留原始恢复记录。
225
+
226
+ 恢复清理本工具新增 Skill、四角色、规则区块、当前档位、全部自定义与元数据;保留用户原文件和后续标记外正文。只非递归删除本工具创建的空目录,home 与无关文件保留。冲突先完整预检,失败不猜测删除。
227
+
228
+ 0.1.0 升级为 schema 2 时标 `legacy_unavailable`:摘要不能重建安装前内容,不把迁移当天配置冒充原始基线。旧 Python 命令现在是 Node setup 兼容入口,需要 Node 22+ 和已安装依赖:
229
+
230
+ ```sh
231
+ npm ci --ignore-scripts
232
+ python3 install.py --codex-home "/path/to/codex-home" --dry-run
233
+ ```
234
+
235
+ ## 开发验证
236
+
237
+ ```sh
238
+ npm ci --ignore-scripts
239
+ npm test
240
+ npm pack
241
+ ```
242
+
243
+ 原十项 Python 保护测试保存在测试 fixture 上,同时 CLI 用例检查新的兼容入口。可在具备 Python 3.11+ 时运行 `python3 -m unittest discover -s tests -v`。
244
+
245
+ 浏览器集成脚本不安装额外依赖;使用开发环境已存在的 Playwright 和 Chrome:
246
+
247
+ ```sh
248
+ PLAYWRIGHT_MODULE=/path/to/playwright CHROME_PATH=/path/to/chrome npm run test:browser
249
+ ```
250
+
251
+ 跨平台 API 设计不代替实机验收;Linux/Windows、其他 Codex 客户端及真实账号推理仍需单独验证。兼容依据和五轮审查见 [实施验收记录](implementation-review.md)。
package/install.py ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ """Compatibility entry: delegate the original install arguments to the npm CLI."""
3
+ import argparse
4
+ from pathlib import Path
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+
9
+
10
+ def main():
11
+ parser = argparse.ArgumentParser(description=__doc__)
12
+ parser.add_argument('--agent', choices=['codex'], default='codex')
13
+ parser.add_argument('--codex-home')
14
+ parser.add_argument('--dry-run', action='store_true')
15
+ args = parser.parse_args()
16
+ node = shutil.which('node')
17
+ if not node:
18
+ print('需要 Node.js 22+;请先安装 Node,然后运行 npm ci。', file=sys.stderr)
19
+ return 1
20
+ command = [node, str(Path(__file__).resolve().parent / 'bin/subagent-delegation.mjs'), 'setup', '--agent=codex']
21
+ if args.codex_home is not None:
22
+ command.append('--codex-home=' + args.codex_home)
23
+ if args.dry_run:
24
+ command.append('--dry-run')
25
+ try:
26
+ return subprocess.run(command, check=False).returncode
27
+ except OSError:
28
+ print('无法启动 Node CLI;请检查安装路径和权限。', file=sys.stderr)
29
+ return 1
30
+
31
+
32
+ if __name__ == '__main__':
33
+ raise SystemExit(main())