subagent-delegation 0.3.0 → 0.5.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.
- package/README.md +55 -45
- package/VERSION +1 -1
- package/adapters/codex/modes.json +3 -3
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/SKILL.md +4 -4
- package/adapters/codex/skills/subagent-delegation/agents/openai.yaml +6 -0
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/AGENTS.fragment.md +1 -1
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/codex/agents/task-delegate.toml +2 -5
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/codex/agents/task-execute.toml +2 -5
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/codex/agents/task-review.toml +2 -5
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/codex/agents/task-scan.toml +2 -5
- package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/references/configuration.md +3 -2
- package/bin/subagent-delegation.mjs +5 -6
- package/docs/README.md +4 -4
- package/docs/implementation-review.md +22 -72
- package/docs/usage.md +24 -24
- package/lib/codex.mjs +9 -18
- package/lib/install.mjs +33 -53
- package/package.json +3 -3
- package/web/app.js +20 -36
- package/web/index.html +1 -1
- package/adapters/codex/skills/subagent-model-selection/agents/openai.yaml +0 -6
- package/install.py +0 -33
- /package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/assets/codex/config.toml +0 -0
- /package/adapters/codex/skills/{subagent-model-selection → subagent-delegation}/references/failure-policy.md +0 -0
package/docs/usage.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# 使用说明
|
|
2
2
|
|
|
3
|
-
`subagent-delegation` 0.
|
|
3
|
+
`subagent-delegation` 0.5.0。首版支持 Codex;Node.js 22+。命令、网页和生成的 Skill 使用同一套档位数据。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
终端命令为 `sub-d`,npm 包名为 `subagent-delegation`。内测版仅支持当前配置格式,不提供旧版本兼容、升级或迁移。更换版本前先用原版本 `restore`,再安装新版本并 `setup`;独立 home 全程使用原路径。
|
|
6
6
|
|
|
7
7
|
## 安装与配置
|
|
8
8
|
|
|
@@ -22,7 +22,9 @@ npm install -g subagent-delegation && sub-d setup --mode mid
|
|
|
22
22
|
|
|
23
23
|
目标目录优先级为 `--codex-home PATH`、`CODEX_HOME`、用户 `~/.codex`。空格路径需要 shell 引号。命令不会设置主代理模型、全局子代理默认、供应商、审批、沙箱、并发或子代理启用开关。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Skill 名称为 `subagent-delegation`,调用为 `$subagent-delegation`,安装目录为 `skills/subagent-delegation`。npm 安装命令获取已发布版本;当前内测开发代码通过 `npm pack` 构建本地包。
|
|
26
|
+
|
|
27
|
+
首次 setup 默认适中;同版本重复 setup 保留当前选择与首次恢复基线。npm 安装没有 postinstall 配置动作。`--dry-run` 不创建目标 home、锁或状态文件。
|
|
26
28
|
|
|
27
29
|
所有配置部署变化都提示:**配置已写入,请重启 Codex 后使用新委派规则。** 运行中的任务继续使用原上下文;工具不自动重启、停止任务或保证动态加载。
|
|
28
30
|
|
|
@@ -32,7 +34,7 @@ npm install -g subagent-delegation && sub-d setup --mode mid
|
|
|
32
34
|
|
|
33
35
|
| 命令 | 用途 |
|
|
34
36
|
| --- | --- |
|
|
35
|
-
| `setup [--mode MODE]` |
|
|
37
|
+
| `setup [--mode MODE]` | 首次配置或同版本重新配置,保留无关文件 |
|
|
36
38
|
| `mode set MODE` | 切换已安装 home 的档位 |
|
|
37
39
|
| `mode list` | 列出内置、自定义及当前选择 |
|
|
38
40
|
| `mode show MODE` | 查看四级主/备用、来源与定义版本,不激活 |
|
|
@@ -46,7 +48,7 @@ npm install -g subagent-delegation && sub-d setup --mode mid
|
|
|
46
48
|
| `serve [--port PORT] [--open]` | 前台启动本地设置页面 |
|
|
47
49
|
| `--help` / `--version` | 帮助及工具版本,可带 --json,不接受 home 参数 |
|
|
48
50
|
|
|
49
|
-
`MODE` 使用 `low`(经济)、`mid`(适中,首次配置默认)、`fire
|
|
51
|
+
`MODE` 使用 `low`(经济)、`mid`(适中,首次配置默认)、`fire`(性能);命令、JSON id、状态记录和网页使用同一值,中文用于说明含义。自定义档位可使用内部 id 或唯一名称。职责为 `task_scan`、`task_execute`、`task_review`、`task_delegate`,explain 省略职责时默认 task_delegate。默认 explain 使用当前有效档位;未安装时需明确 `--mode mid` 等内置预览。
|
|
50
52
|
|
|
51
53
|
名称以 `-` 开头时,在全部选项后使用 `--`:
|
|
52
54
|
|
|
@@ -68,13 +70,13 @@ sub-d explain --level L4 --role task_review
|
|
|
68
70
|
|
|
69
71
|
详细查询分开呈现已配置值、预设值、潜在覆盖与运行未验证。检查相关项目及祖先目录,不扫描全电脑;不输出账号凭据、私有指令、完整 TOML 或认证记录。范围外注册路径与未确定的 profile 只报告候选及未检查原因,不跟随任意文件。
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
同一档位重复切换不重写;仅保存非活动定义不要求重启。配置版本或格式不匹配时,读取与写入都会被拒绝,不做自动转换。
|
|
72
74
|
|
|
73
75
|
无需全局安装的 npm 查询,以及本地包查询:
|
|
74
76
|
|
|
75
77
|
```sh
|
|
76
|
-
npm exec --package=subagent-delegation@0.
|
|
77
|
-
npm exec --package="/path/to/subagent-delegation-0.
|
|
78
|
+
npm exec --package=subagent-delegation@0.5.0 -- sub-d status --details
|
|
79
|
+
npm exec --package="/path/to/subagent-delegation-0.5.0.tgz" -- sub-d status --details
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
## 任务等级与委派职责
|
|
@@ -97,7 +99,9 @@ npm exec --package="/path/to/subagent-delegation-0.3.0.tgz" -- sub-d status --de
|
|
|
97
99
|
|
|
98
100
|
等级与职责互相独立。例如经济模式下复杂产出升为 L3 时,不能继续用固定 Luna 的 task_execute 覆盖选择;应使用 task_delegate 请求 Sol/medium,同时保留范围内产出的权限契约。继承需核对宿主全局默认,省略参数不证明继承。
|
|
99
101
|
|
|
100
|
-
AGENTS 管理入口要求启动时读取 Skill,每次创建、重新委派或模型变化前按 Skill
|
|
102
|
+
AGENTS 管理入口要求启动时读取 Skill,每次创建、重新委派或模型变化前按 Skill 检查。用户选择、预算、宿主和项目规则优先,工具不自动授权委派。主代理保留最终设计、共享裁决、集成、关键验证及完成声明。子代理禁止递归、外部环境和代理配置改动;独占写入、局部适配/阻断、最小充分验证及 PARTIAL 返回在全部档位保留。
|
|
103
|
+
|
|
104
|
+
本工具不额外限制 Git 操作或图像生成,也不设置 `features.image_generation`。实际工具能力与操作授权遵循宿主、用户和项目规则。
|
|
101
105
|
|
|
102
106
|
## 备用与模型不可用
|
|
103
107
|
|
|
@@ -111,7 +115,7 @@ AGENTS 管理入口要求启动时读取 Skill,每次创建、重新委派或
|
|
|
111
115
|
|
|
112
116
|
Astra 不可用:先区分认证、权限、额度、临时故障或能力不支持。允许备用且全部条件满足才用已配置 Sol/high;用户强制只用 Astra、预算不允许或旧 worker 停止未知时,受影响部分 BLOCKED,继续其他独立工作。缺资料或工具故障先补前提,不能靠强模型掩盖。
|
|
113
117
|
|
|
114
|
-
每任务最多三次父代理发起的委派,同一组合最多额外一次;累计等待不超过 30 秒并遵守 Retry-After。宿主已重试或次数未知时不追加。异常或部分结果标 PARTIAL,不自动撤销其他人的修改。完整分类见 [失败处理](../adapters/codex/skills/subagent-
|
|
118
|
+
每任务最多三次父代理发起的委派,同一组合最多额外一次;累计等待不超过 30 秒并遵守 Retry-After。宿主已重试或次数未知时不追加。异常或部分结果标 PARTIAL,不自动撤销其他人的修改。完整分类见 [失败处理](../adapters/codex/skills/subagent-delegation/references/failure-policy.md)。
|
|
115
119
|
|
|
116
120
|
## 自定义档位
|
|
117
121
|
|
|
@@ -123,7 +127,7 @@ sub-d mode configure "我的研究方案" \
|
|
|
123
127
|
sub-d mode set "我的研究方案"
|
|
124
128
|
```
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
自定义名称不能与内置档位的 id、中文名或现有自定义名称/id 重复;自定义档位可按名称或 custom-* id 选择。
|
|
127
131
|
|
|
128
132
|
名称 trim + Unicode NFC 归一化,1–80 个码点,不能含控制字符、无效 Unicode 或与既有名称/id 混淆。名称只用于显示与查找,不拼文件路径;内部 id 为 UUID。模型 1–200、effort 1–40 个码点,拒绝无效 Unicode、控制字符和首尾空白。目录未知不等于标识无效。
|
|
129
133
|
|
|
@@ -162,7 +166,7 @@ setup、切换、活动自定义部署实际改变后,解锁再查询所选 ho
|
|
|
162
166
|
|
|
163
167
|
分别查看:目录是否完整、effort 是否支持、账号实际请求权限、可信实际身份。目录列出模型不证明请求成功;目录缺项仍 unknown。桌面会话、启动参数、profile 与 CLI 上下文匹配未核验,检查结果不能冒充桌面账号能力。
|
|
164
168
|
|
|
165
|
-
**0.
|
|
169
|
+
**0.5.0 的实际推理门禁继续关闭。** 已核验的 Codex CLI 0.156.1 无法证明禁用全部工具/委派及限定外部读取,因此 `--verify` 返回 exit 1、`PROBE_ISOLATION_UNSUPPORTED`,不创建线程或发送推理。没有有效当前档位且无显式目标时先报 `PROBE_TARGET_REQUIRED`。未来只有宿主强制隔离得到验证才能启用;页面仍保留显式额度确认,不默认调用。
|
|
166
170
|
|
|
167
171
|
## 本地网页
|
|
168
172
|
|
|
@@ -205,13 +209,14 @@ JSON stdout 只有统一 envelope:
|
|
|
205
209
|
- `RULES_SHADOWED`:非空 home AGENTS.override.md 遮蔽入口。工具不改 override;用户自行合并原规则并停用 override,或选择未遮蔽 home。
|
|
206
210
|
- `MANAGED_FILE_DRIFT` / `MANAGED_RULES_DRIFT`:人工改动、删除或 chmod;保留用户内容,核对后重新操作,无 force 覆盖开关。
|
|
207
211
|
- `BUSY`:有写入锁或读取持续变化。先确认其他命令已停止;不要自动删除锁。进程崩溃后可能需人工核对锁与残留。
|
|
208
|
-
- `VERSION_MISMATCH
|
|
209
|
-
- `
|
|
212
|
+
- `VERSION_MISMATCH`:配置版本与当前工具不一致;用原版本 restore 后再重新配置,当前工具不迁移或覆盖。
|
|
213
|
+
- `STATE_INVALID`:状态格式、路径或档位引用不受支持,保持文件不变。
|
|
214
|
+
- `RESTORE_METADATA_INVALID`:恢复记录缺失或损坏,使用原备份或人工核对,不猜测恢复。
|
|
210
215
|
- `MODEL_ACCESS_UNKNOWN` / `MODEL_NOT_LISTED`:权限未知;不能当作可用或不可用的最终证明。
|
|
211
216
|
|
|
212
217
|
配置文件采用逐文件原子替换、共同独占锁、完整观察复读、状态最后写入和普通失败回退。回退不覆盖并发编辑,残留单独报告。多文件在进程崩溃/断电时并非一个原子事务;不要在应用期间手工修改管理文件。
|
|
213
218
|
|
|
214
|
-
##
|
|
219
|
+
## 恢复与卸载
|
|
215
220
|
|
|
216
221
|
```sh
|
|
217
222
|
sub-d restore --dry-run
|
|
@@ -221,16 +226,11 @@ npm uninstall -g subagent-delegation
|
|
|
221
226
|
|
|
222
227
|
restore 后重启 Codex。它不会卸载 npm 工具、删除整个 home、清理账号或重启应用。npm 卸载也不会代做 restore。
|
|
223
228
|
|
|
224
|
-
|
|
229
|
+
首次 setup 记录受影响文件的原存在状态与普通权限,仅允许接管同内容公开工具资产;AGENTS 只记录管理区块与校验元数据,不保存完整私有正文、config 或认证文件。同版本档位切换与自定义配置保留首次基线。
|
|
225
230
|
|
|
226
231
|
恢复清理本工具新增 Skill、四角色、规则区块、当前档位、全部自定义与元数据;保留用户原文件和后续标记外正文。只非递归删除本工具创建的空目录,home 与无关文件保留。冲突先完整预检,失败不猜测删除。
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
npm ci --ignore-scripts
|
|
232
|
-
python3 install.py --codex-home "/path/to/codex-home" --dry-run
|
|
233
|
-
```
|
|
233
|
+
工具统一使用 Node.js 22+ 和 npm,不需要 Python;安装与配置入口为 `sub-d setup`。
|
|
234
234
|
|
|
235
235
|
## 开发验证
|
|
236
236
|
|
|
@@ -240,7 +240,7 @@ npm test
|
|
|
240
240
|
npm pack
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
`npm test` 验证安装、档位、查询、恢复和本地服务;安装保护用例覆盖用户配置保留、冲突、符号链接、失败回退及输出脱敏。
|
|
244
244
|
|
|
245
245
|
浏览器集成脚本不安装额外依赖;使用开发环境已存在的 Playwright 和 Chrome:
|
|
246
246
|
|
|
@@ -248,4 +248,4 @@ npm pack
|
|
|
248
248
|
PLAYWRIGHT_MODULE=/path/to/playwright CHROME_PATH=/path/to/chrome npm run test:browser
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
跨平台 API 设计不代替实机验收;Linux/Windows、其他 Codex
|
|
251
|
+
跨平台 API 设计不代替实机验收;Linux/Windows、其他 Codex 客户端及真实账号推理仍需单独验证。当前验证结果与边界见 [实施验收记录](implementation-review.md)。
|
package/lib/codex.mjs
CHANGED
|
@@ -12,7 +12,6 @@ export const END = '<!-- subagent-delegation:end -->';
|
|
|
12
12
|
const policy = JSON.parse(await readFile(new URL('../adapters/codex/modes.json', import.meta.url), 'utf8'));
|
|
13
13
|
export const FALLBACK_CONDITIONS = policy.fallback_conditions;
|
|
14
14
|
export const BUILTINS = policy.modes.map(mode => ({ ...mode, kind: 'builtin', fallback_conditions: FALLBACK_CONDITIONS }));
|
|
15
|
-
export const MODE_COMMANDS = { low: 'economy', mid: 'balanced', fire: 'performance' };
|
|
16
15
|
|
|
17
16
|
export function argumentError(message, code = 'INVALID_ARGUMENT') {
|
|
18
17
|
return Object.assign(new Error(message), { code, exitCode: 2 });
|
|
@@ -46,22 +45,14 @@ export function validateDefinition(input) {
|
|
|
46
45
|
}));
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
function
|
|
48
|
+
export function resolveDefinition(selector, customModes = []) {
|
|
49
|
+
const name = typeof selector === 'string' ? selector.trim().normalize('NFC') : selector;
|
|
50
|
+
const matches = [...BUILTINS.filter(mode => mode.id === selector), ...customModes.filter(mode => mode.id === selector || mode.name === name)];
|
|
50
51
|
if (matches.length !== 1) throw argumentError('档位不存在或名称不唯一;内置档位请用 low、mid、fire,自定义档位请用 mode list 显示的 id 或名称。', 'MODE_NOT_FOUND');
|
|
51
52
|
const mode = matches[0];
|
|
52
53
|
return { ...mode, levels: validateDefinition({ levels: mode.levels }), fallback_conditions: FALLBACK_CONDITIONS };
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
export function resolveDefinition(selector, customModes = []) {
|
|
56
|
-
if (Object.hasOwn(MODE_COMMANDS, selector)) return checkedDefinition(BUILTINS.filter(mode => mode.id === MODE_COMMANDS[selector]));
|
|
57
|
-
const name = typeof selector === 'string' ? selector.trim().normalize('NFC') : selector;
|
|
58
|
-
return checkedDefinition(customModes.filter(mode => mode.id === selector || mode.name === name));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function resolveStoredDefinition(id, customModes = []) {
|
|
62
|
-
return checkedDefinition([...BUILTINS, ...customModes].filter(mode => mode.id === id));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
56
|
export function explainSelection({ definition, level, role = 'task_delegate' }) {
|
|
66
57
|
if (!LEVELS.includes(level) || !Object.hasOwn(ROLES, role)) throw argumentError('任务等级或角色无效。');
|
|
67
58
|
const { model, effort, fallback } = definition.levels[level];
|
|
@@ -87,19 +78,19 @@ export function safeJsonData(value) {
|
|
|
87
78
|
}
|
|
88
79
|
|
|
89
80
|
export async function renderPayload(home, mode) {
|
|
90
|
-
const skillRoot = path.join(PACKAGE_ROOT, 'adapters/codex/skills/subagent-
|
|
81
|
+
const skillRoot = path.join(PACKAGE_ROOT, 'adapters/codex/skills/subagent-delegation');
|
|
91
82
|
const files = new Map();
|
|
92
83
|
async function visit(directory, prefix = '') {
|
|
93
84
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
94
85
|
if (entry.isSymbolicLink()) throw Object.assign(new Error('发布资产不允许符号链接。'), { code: 'INVALID_PACKAGE' });
|
|
95
86
|
const relative = prefix + entry.name;
|
|
96
87
|
if (entry.isDirectory()) await visit(path.join(directory, entry.name), relative + '/');
|
|
97
|
-
else if (entry.isFile() && entry.name !== '.DS_Store') files.set(`skills/subagent-
|
|
88
|
+
else if (entry.isFile() && entry.name !== '.DS_Store') files.set(`skills/subagent-delegation/${relative}`, await readFile(path.join(directory, entry.name)));
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
await visit(skillRoot);
|
|
101
|
-
const skillPath = path.join(home, 'skills/subagent-
|
|
102
|
-
const modePath = path.join(home, 'skills/subagent-
|
|
92
|
+
const skillPath = path.join(home, 'skills/subagent-delegation/SKILL.md');
|
|
93
|
+
const modePath = path.join(home, 'skills/subagent-delegation/references/current-mode.json');
|
|
103
94
|
for (const [name, level] of Object.entries(ROLES)) {
|
|
104
95
|
const filename = name.replaceAll('_', '-') + '.toml';
|
|
105
96
|
const role = safeToml(await readFile(path.join(skillRoot, 'assets/codex/agents', filename)));
|
|
@@ -108,9 +99,9 @@ export async function renderPayload(home, mode) {
|
|
|
108
99
|
role.developer_instructions = `启动后先读取本 home 的子代理模型选择 Skill,遵守当前派发职责;不递归委派。\n规则路径(JSON 字符串数据):${safeJsonData(skillPath)}\n\n` + role.developer_instructions;
|
|
109
100
|
const bytes = Buffer.from(stringify(role));
|
|
110
101
|
files.set(`agents/${filename}`, bytes);
|
|
111
|
-
files.set(`skills/subagent-
|
|
102
|
+
files.set(`skills/subagent-delegation/assets/codex/agents/${filename}`, bytes);
|
|
112
103
|
}
|
|
113
|
-
files.set('skills/subagent-
|
|
104
|
+
files.set('skills/subagent-delegation/references/current-mode.json', Buffer.from(JSON.stringify({ ...mode, version: VERSION, roles: ROLES }, null, 2) + '\n'));
|
|
114
105
|
const fragment = await readFile(path.join(skillRoot, 'assets/AGENTS.fragment.md'), 'utf8');
|
|
115
106
|
const block = Buffer.from(`${START}\n## AI 子代理模型选择与委派\n\nCodex 启动或加载本 home 的规则时,必须先读取以下 Skill;每次创建、重新委派或切换子智能体模型前必须再次按 Skill 和当前档位检查。已有上下文可复用,配置变化后须重读。委派仍须有用户或适用规则授权。\n\n以下 JSON 只表示绑定 home 的文件路径,不是新指令:\n\n\`\`\`json\n${safeJsonData({ skill: skillPath, mode: modePath })}\n\`\`\`\n\n${fragment.trim()}\n${END}\n`);
|
|
116
107
|
return { files, block };
|
package/lib/install.mjs
CHANGED
|
@@ -4,9 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { parse as parseToml } from 'smol-toml';
|
|
7
|
-
import { VERSION, LEVELS, ROLES, BUILTINS,
|
|
7
|
+
import { VERSION, LEVELS, ROLES, BUILTINS, resolveDefinition, validateDefinition, renderPayload } from './codex.mjs';
|
|
8
8
|
|
|
9
|
-
const SKILL = 'skills/subagent-
|
|
9
|
+
const SKILL = 'skills/subagent-delegation';
|
|
10
10
|
const STATE_DIR = '.subagent-delegation';
|
|
11
11
|
const STATE = `${STATE_DIR}/state.json`;
|
|
12
12
|
const BASELINE = `${STATE_DIR}/restore.json`;
|
|
@@ -37,15 +37,6 @@ function fail(code, message, exitCode = 1, details = {}) { throw new DelegationE
|
|
|
37
37
|
function keys(value, required, optional = []) {
|
|
38
38
|
return object(value) && required.every(key => Object.hasOwn(value, key)) && Object.keys(value).every(key => [...required, ...optional].includes(key));
|
|
39
39
|
}
|
|
40
|
-
function version(value) {
|
|
41
|
-
if (typeof value !== 'string' || !/^\d+\.\d+\.\d+$/.test(value)) fail('STATE_INVALID', '安装版本记录无效。');
|
|
42
|
-
return value.split('.').map(Number);
|
|
43
|
-
}
|
|
44
|
-
function compareVersions(left, right) {
|
|
45
|
-
const a = version(left), b = version(right);
|
|
46
|
-
for (let index = 0; index < 3; index++) if (a[index] !== b[index]) return Math.sign(a[index] - b[index]);
|
|
47
|
-
return 0;
|
|
48
|
-
}
|
|
49
40
|
export function resolveHome(explicit) {
|
|
50
41
|
let value = explicit ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex');
|
|
51
42
|
if (typeof value !== 'string' || !value || /[\p{Cc}\p{Cf}]/u.test(value)) fail('INVALID_HOME', 'Codex home 必须是有效路径。', 2);
|
|
@@ -162,17 +153,14 @@ function validateCustom(bytes) {
|
|
|
162
153
|
}
|
|
163
154
|
function validateState(bytes) {
|
|
164
155
|
if (bytes === null) return null;
|
|
165
|
-
const state = parseJson(bytes, 'STATE_INVALID', '
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const restore = state.restoration;
|
|
174
|
-
if (!keys(restore, ['status', 'baseline_sha256', 'retired_paths']) || !['available', 'legacy_unavailable'].includes(restore.status) || !Array.isArray(restore.retired_paths) || new Set(restore.retired_paths).size !== restore.retired_paths.length || !restore.retired_paths.every(owned) || restore.retired_paths.some(relative => Object.hasOwn(state.files, relative)) || (restore.status === 'available' ? !SHA.test(restore.baseline_sha256) : restore.baseline_sha256 !== null || restore.retired_paths.length)) fail('STATE_INVALID', '恢复状态字段无效。');
|
|
175
|
-
}
|
|
156
|
+
const state = parseJson(bytes, 'STATE_INVALID', '安装记录无效或格式不支持。');
|
|
157
|
+
const fields = ['schema_version', 'agent', 'version', 'files', 'rules_sha256', 'mode', 'mode_kind', 'instance_id', 'file_modes', 'rules_mode', 'restoration'];
|
|
158
|
+
if (state?.schema_version !== 2 || state.agent !== 'codex' || !keys(state, fields) || !object(state.files) || !SHA.test(state.rules_sha256)) fail('STATE_INVALID', '安装记录无效或格式不支持;本内测版本不提供升级迁移。');
|
|
159
|
+
if (state.version !== VERSION) fail('VERSION_MISMATCH', '配置版本与当前工具不一致;请先使用原版本 restore,再使用当前版本 setup。未修改任何文件。');
|
|
160
|
+
for (const [relative, digest] of Object.entries(state.files)) if (!owned(relative) || !SHA.test(digest)) fail('STATE_INVALID', '安装记录含非法路径或摘要。');
|
|
161
|
+
if (![...ROLE_PATHS, `${SKILL}/SKILL.md`, `${SKILL}/references/current-mode.json`].every(relative => Object.hasOwn(state.files, relative)) || typeof state.mode !== 'string' || !['builtin', 'custom'].includes(state.mode_kind) || !UUID.test(state.instance_id) || !object(state.file_modes) || !equal(Object.keys(state.files).sort(), Object.keys(state.file_modes).sort()) || !Object.values(state.file_modes).every(permission) || !permission(state.rules_mode)) fail('STATE_INVALID', '安装记录字段无效。');
|
|
162
|
+
const restore = state.restoration;
|
|
163
|
+
if (!keys(restore, ['status', 'baseline_sha256']) || restore.status !== 'available' || !SHA.test(restore.baseline_sha256)) fail('STATE_INVALID', '恢复状态字段无效。');
|
|
176
164
|
return state;
|
|
177
165
|
}
|
|
178
166
|
function base64(value, code = 'RESTORE_METADATA_INVALID') {
|
|
@@ -189,7 +177,7 @@ function validateBaseline(bytes, state) {
|
|
|
189
177
|
if (!owned(relative)) fail('RESTORE_METADATA_INVALID', '恢复记录路径不在白名单中。');
|
|
190
178
|
if (entry !== null && (!keys(entry, ['bytes_base64', 'sha256', 'mode']) || !permission(entry.mode) || !SHA.test(entry.sha256) || hash(base64(entry.bytes_base64)) !== entry.sha256)) fail('RESTORE_METADATA_INVALID', '恢复记录条目无效。');
|
|
191
179
|
}
|
|
192
|
-
const expected =
|
|
180
|
+
const expected = Object.keys(state.files).sort();
|
|
193
181
|
if (!equal(Object.keys(baseline.entries).sort(), expected)) fail('RESTORE_METADATA_INVALID', '恢复记录与当前管理文件不对应。');
|
|
194
182
|
const rules = baseline.rules;
|
|
195
183
|
if (!keys(rules, ['existed', 'mode', 'sha256', 'original_block_base64', 'separator_base64', 'original_prefix_sha256']) || typeof rules.existed !== 'boolean' || (rules.existed ? !permission(rules.mode) || !SHA.test(rules.sha256) : rules.mode !== null || rules.sha256 !== null) || !SHA.test(rules.original_prefix_sha256)) fail('RESTORE_METADATA_INVALID', '恢复规则记录无效。');
|
|
@@ -219,21 +207,21 @@ async function collect(home, { ignoreLock = false } = {}) {
|
|
|
219
207
|
const customModes = validateCustom((await file(CUSTOM)).bytes);
|
|
220
208
|
await file(`${SKILL}/references/current-mode.json`);
|
|
221
209
|
let mode = null;
|
|
222
|
-
if (state
|
|
223
|
-
try { mode =
|
|
210
|
+
if (state) {
|
|
211
|
+
try { mode = resolveDefinition(state.mode, customModes); } catch { fail('STATE_INVALID', '活动档位引用无效。'); }
|
|
224
212
|
if (mode.kind !== state.mode_kind) fail('STATE_INVALID', '活动档位类型记录无效。');
|
|
225
213
|
const installedBytes = (await file(`${SKILL}/references/current-mode.json`)).bytes;
|
|
226
214
|
if (installedBytes !== null) {
|
|
227
215
|
const installed = parseJson(installedBytes, 'STATE_INVALID', '已部署档位说明无效。');
|
|
228
216
|
if (!keys(installed, ['id', 'name', 'kind', 'levels', 'version', 'roles', 'fallback_conditions']) || installed.id !== state.mode || installed.kind !== state.mode_kind || safeText(installed.name) === null || [...installed.name].length > 80 || !Array.isArray(installed.fallback_conditions) || !installed.fallback_conditions.every(value => typeof value === 'string' && /^[A-Z_]+$/.test(value)) || !equal(installed.roles, ROLES)) fail('STATE_INVALID', '已部署档位说明无效。');
|
|
229
|
-
|
|
217
|
+
if (installed.version !== VERSION) fail('STATE_INVALID', '已部署档位说明的版本与当前工具不一致。');
|
|
230
218
|
let levels;
|
|
231
219
|
try { levels = validateDefinition({ levels: installed.levels }); } catch { fail('STATE_INVALID', '已部署档位等级定义无效。'); }
|
|
232
220
|
mode = { id: installed.id, name: installed.name, kind: installed.kind, levels, fallback_conditions: installed.fallback_conditions, definition_version: installed.version };
|
|
233
221
|
}
|
|
234
222
|
}
|
|
235
|
-
const candidates = await renderPayload(home, mode ??
|
|
236
|
-
const paths = new Set([...candidates.files.keys(), ...Object.keys(state?.files ?? {})
|
|
223
|
+
const candidates = await renderPayload(home, mode ?? resolveDefinition('mid'));
|
|
224
|
+
const paths = new Set([...candidates.files.keys(), ...Object.keys(state?.files ?? {})]);
|
|
237
225
|
for (const relative of paths) {
|
|
238
226
|
if (!owned(relative)) fail('STATE_INVALID', '管理路径不在白名单中。');
|
|
239
227
|
await file(relative);
|
|
@@ -253,18 +241,15 @@ async function collect(home, { ignoreLock = false } = {}) {
|
|
|
253
241
|
if (!state && (await file(BASELINE)).bytes !== null) fail('RESTORE_METADATA_INVALID', '恢复记录存在但安装状态缺失;请人工核对残留配置。');
|
|
254
242
|
if (!state && (await file(CUSTOM)).bytes !== null) fail('STATE_INVALID', '自定义档位记录存在但安装状态缺失。');
|
|
255
243
|
if (state) {
|
|
256
|
-
if (
|
|
244
|
+
if ((await file(STATE)).mode !== 0o600) diagnostics.push(diagnostic('STATE_PERMISSIONS_DRIFT', 'fail', path.join(home, STATE), '安装状态文件权限变化。'));
|
|
257
245
|
for (const [relative, digest] of Object.entries(state.files)) {
|
|
258
246
|
const actual = await file(relative);
|
|
259
|
-
if (actual.bytes === null || hash(actual.bytes) !== digest ||
|
|
247
|
+
if (actual.bytes === null || hash(actual.bytes) !== digest || actual.mode !== state.file_modes[relative]) diagnostics.push(diagnostic('MANAGED_FILE_DRIFT', 'fail', path.join(home, relative), '受管理文件被修改、删除或权限变化。'));
|
|
260
248
|
}
|
|
261
249
|
const rules = extractRules((await file('AGENTS.md')).bytes);
|
|
262
|
-
if (!rules || hash(rules.block) !== state.rules_sha256 ||
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
baseline = validateBaseline((await file(BASELINE)).bytes, state);
|
|
266
|
-
for (const relative of state.restoration.retired_paths) if ((await file(relative)).bytes !== null) diagnostics.push(diagnostic('RETIRED_FILE_DRIFT', 'fail', path.join(home, relative), '退场的管理路径被重新创建。'));
|
|
267
|
-
} else if ((await file(BASELINE)).bytes !== null) fail('RESTORE_METADATA_INVALID', '不应存在恢复记录;请人工核对。');
|
|
250
|
+
if (!rules || hash(rules.block) !== state.rules_sha256 || (await file('AGENTS.md')).mode !== state.rules_mode) diagnostics.push(diagnostic('MANAGED_RULES_DRIFT', 'fail', path.join(home, 'AGENTS.md'), '受管理区块被修改、移除或权限变化。'));
|
|
251
|
+
if ((await file(BASELINE)).mode !== 0o600) fail('RESTORE_METADATA_INVALID', '安装前恢复记录权限变化。');
|
|
252
|
+
baseline = validateBaseline((await file(BASELINE)).bytes, state);
|
|
268
253
|
if ((await file(CUSTOM)).bytes !== null && !Object.hasOwn(state.files, CUSTOM)) fail('STATE_INVALID', '自定义档位记录未登记所有权。');
|
|
269
254
|
}
|
|
270
255
|
let config = {};
|
|
@@ -294,7 +279,7 @@ async function collect(home, { ignoreLock = false } = {}) {
|
|
|
294
279
|
return snapshot;
|
|
295
280
|
}
|
|
296
281
|
function revision(snapshot) {
|
|
297
|
-
return hash(json({ instance: snapshot.state?.instance_id ??
|
|
282
|
+
return hash(json({ instance: snapshot.state?.instance_id ?? 'not_installed', files: [...snapshot.observedFiles].sort(([a], [b]) => a.localeCompare(b)).map(([name, value]) => [name, stamp(value)]), directories: [...snapshot.observedDirectories].sort(([a], [b]) => a.localeCompare(b)).map(([name, value]) => [name, stamp(value)]) }));
|
|
298
283
|
}
|
|
299
284
|
async function stillObserved(snapshot, ignoreLock = false) {
|
|
300
285
|
if (!ignoreLock && await lockExists(snapshot.home)) return false;
|
|
@@ -317,7 +302,7 @@ export async function readSnapshot(home, options = {}) {
|
|
|
317
302
|
}
|
|
318
303
|
}
|
|
319
304
|
function requireClean(snapshot, restore = false) {
|
|
320
|
-
const failures = snapshot.diagnostics.filter(value => value.status === 'fail' && (!restore || ['MANAGED_FILE_DRIFT', 'MANAGED_RULES_DRIFT', '
|
|
305
|
+
const failures = snapshot.diagnostics.filter(value => value.status === 'fail' && (!restore || ['MANAGED_FILE_DRIFT', 'MANAGED_RULES_DRIFT', 'STATE_PERMISSIONS_DRIFT'].includes(value.code)));
|
|
321
306
|
if (failures.length) fail(restore ? 'RESTORE_CONFLICT' : failures[0].code, restore ? '受管理内容存在冲突,未恢复任何文件。' : '受管理内容存在冲突,未覆盖任何文件。', 1, { paths: failures.map(value => value.source) });
|
|
322
307
|
}
|
|
323
308
|
function requireRevision(snapshot, expected) {
|
|
@@ -365,10 +350,8 @@ async function buildPlan(snapshot, action, options) {
|
|
|
365
350
|
requireRevision(snapshot, options.expectRevision);
|
|
366
351
|
requireClean(snapshot, action === 'restore');
|
|
367
352
|
const { state, home } = snapshot;
|
|
368
|
-
if (state && compareVersions(VERSION, state.version) < 0) fail('VERSION_TOO_OLD', '当前工具比已安装配置旧,拒绝覆盖或恢复。');
|
|
369
353
|
if (action !== 'setup' && action !== 'restore' && !state) fail('NOT_INSTALLED', '请先执行 setup 配置 Codex。');
|
|
370
354
|
if (!['setup', 'set', 'create', 'configure', 'restore'].includes(action)) fail('INVALID_ACTION', '不支持的写入动作。', 2);
|
|
371
|
-
if (!['setup', 'restore'].includes(action) && (state.schema_version !== 2 || state.version !== VERSION)) fail('VERSION_MISMATCH', '请先使用 setup 安全升级配置,再修改档位。');
|
|
372
355
|
const changes = new Map();
|
|
373
356
|
const plan = { snapshot, action, changes, mode: snapshot.mode, warnings: [], deploymentChanged: false, cleanupDirectories: [] };
|
|
374
357
|
const add = async (relative, after, mode) => {
|
|
@@ -380,7 +363,6 @@ async function buildPlan(snapshot, action, options) {
|
|
|
380
363
|
if (action === 'restore') {
|
|
381
364
|
plan.mode = null;
|
|
382
365
|
if (!state) return plan;
|
|
383
|
-
if (state.schema_version === 1 || state.restoration.status === 'legacy_unavailable') fail('RESTORE_BASELINE_UNAVAILABLE', '旧版没有安装前恢复基线;需要您自己的备份或人工核对原状。');
|
|
384
366
|
for (const [relative, entry] of Object.entries(snapshot.baseline.entries)) await add(relative, entry === null ? null : base64(entry.bytes_base64), entry?.mode);
|
|
385
367
|
const current = snapshot.observedFiles.get('AGENTS.md');
|
|
386
368
|
const found = extractRules(current.bytes);
|
|
@@ -404,11 +386,11 @@ async function buildPlan(snapshot, action, options) {
|
|
|
404
386
|
return plan;
|
|
405
387
|
}
|
|
406
388
|
let customModes = snapshot.customModes.map(mode => ({ ...mode, levels: structuredClone(mode.levels) }));
|
|
407
|
-
let desiredMode = snapshot.mode ?
|
|
389
|
+
let desiredMode = snapshot.mode ? resolveDefinition(snapshot.mode.id, customModes) : resolveDefinition('mid', customModes);
|
|
408
390
|
if (action === 'setup' && options.mode !== undefined || action === 'set') desiredMode = resolveDefinition(options.mode, customModes);
|
|
409
391
|
if (action === 'create') {
|
|
410
392
|
const name = normalizeName(options.name);
|
|
411
|
-
if (
|
|
393
|
+
if ([...BUILTINS, ...customModes].some(mode => [mode.name, mode.id].includes(name))) fail('NAME_CONFLICT', '档位名称与内置命令、既有档位或内部 id 重复。', 2);
|
|
412
394
|
const source = resolveDefinition(options.from ?? 'mid', customModes);
|
|
413
395
|
const definition = { id: `custom-${randomUUID()}`, name, kind: 'custom', levels: structuredClone(source.levels) };
|
|
414
396
|
customModes.push(definition); plan.created_mode = definition;
|
|
@@ -431,7 +413,7 @@ async function buildPlan(snapshot, action, options) {
|
|
|
431
413
|
levels = validateDefinition({ levels });
|
|
432
414
|
}
|
|
433
415
|
customModes = customModes.map(mode => mode.id === definition.id ? { ...mode, levels } : mode);
|
|
434
|
-
if (desiredMode.id === definition.id) desiredMode =
|
|
416
|
+
if (desiredMode.id === definition.id) desiredMode = resolveDefinition(definition.id, customModes);
|
|
435
417
|
}
|
|
436
418
|
const deploying = action === 'setup' || action === 'set' || action === 'configure' && desiredMode.id === resolveDefinition(options.mode, customModes).id;
|
|
437
419
|
if (deploying && snapshot.diagnostics.some(value => value.code === 'RULES_SHADOWED')) fail('RULES_SHADOWED', '非空 AGENTS.override.md 遮蔽本库规则;未写入部署配置。');
|
|
@@ -458,12 +440,10 @@ async function buildPlan(snapshot, action, options) {
|
|
|
458
440
|
if (!state) {
|
|
459
441
|
baseline = { schema_version: 1, agent: 'codex', entries: {}, rules: { existed: originalRules.bytes !== null, mode: originalRules.mode, sha256: originalRules.bytes === null ? null : hash(originalRules.bytes), original_block_base64: merged.found?.block.toString('base64') ?? null, separator_base64: merged.separator.toString('base64'), original_prefix_sha256: hash(merged.found?.before ?? originalRules.bytes ?? Buffer.alloc(0)) }, created_directories: directories };
|
|
460
442
|
}
|
|
461
|
-
if (baseline)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
const stateBytes = json({ schema_version: 2, agent: 'codex', version: VERSION, mode: desiredMode.id, mode_kind: desiredMode.kind, instance_id: state?.instance_id ?? randomUUID(), files: Object.fromEntries([...files].sort(([a], [b]) => a.localeCompare(b)).map(([relative, bytes]) => [relative, hash(bytes)])), file_modes: Object.fromEntries([...files.keys()].sort().map(relative => [relative, changes.get(relative)?.mode ?? snapshot.observedFiles.get(relative)?.mode ?? 0o600])), rules_mode: changes.get('AGENTS.md')?.mode ?? originalRules.mode ?? 0o600, rules_sha256: deploying ? hash(payload.block) : state.rules_sha256, restoration: baseline ? { status: 'available', baseline_sha256: hash(json(baseline)), retired_paths: Object.keys(baseline.entries).filter(relative => !files.has(relative)).sort() } : { status: 'legacy_unavailable', baseline_sha256: null, retired_paths: [] } });
|
|
443
|
+
for (const relative of files.keys()) if (!Object.hasOwn(baseline.entries, relative)) baseline.entries[relative] = managedEntry(snapshot.observedFiles.get(relative) ?? await observe(home, relative));
|
|
444
|
+
baseline.created_directories = [...new Set([...baseline.created_directories, ...directories])];
|
|
445
|
+
await add(BASELINE, json(baseline), 0o600);
|
|
446
|
+
const stateBytes = json({ schema_version: 2, agent: 'codex', version: VERSION, mode: desiredMode.id, mode_kind: desiredMode.kind, instance_id: state?.instance_id ?? randomUUID(), files: Object.fromEntries([...files].sort(([a], [b]) => a.localeCompare(b)).map(([relative, bytes]) => [relative, hash(bytes)])), file_modes: Object.fromEntries([...files.keys()].sort().map(relative => [relative, changes.get(relative)?.mode ?? snapshot.observedFiles.get(relative)?.mode ?? 0o600])), rules_mode: changes.get('AGENTS.md')?.mode ?? originalRules.mode ?? 0o600, rules_sha256: deploying ? hash(payload.block) : state.rules_sha256, restoration: { status: 'available', baseline_sha256: hash(json(baseline)) } });
|
|
467
447
|
await add(STATE, stateBytes, 0o600);
|
|
468
448
|
const ordered = new Map();
|
|
469
449
|
if (changes.has(BASELINE)) ordered.set(BASELINE, changes.get(BASELINE));
|
|
@@ -658,8 +638,8 @@ export async function statusReport(home, options = {}) {
|
|
|
658
638
|
for (const [name, profile] of Object.entries(snapshot.config.profiles ?? {})) if (object(profile) && safeText(name, 80) !== null) profileCandidates.push({ name, model: safeText(profile.model), effort: safeText(profile.model_reasoning_effort, 40), default_subagent_model: safeText(profile.agents?.default_subagent_model), default_subagent_reasoning_effort: safeText(profile.agents?.default_subagent_reasoning_effort, 40), source: path.join(snapshot.home, 'config.toml'), active: null });
|
|
659
639
|
if (profileCandidates.length) skippedPaths.push({ path: path.join(snapshot.home, 'config.toml'), reason: 'conditional_active_profile_unknown' });
|
|
660
640
|
for (const [relative, observed] of snapshot.observedDirectories) if (relative === 'agents' || relative.startsWith('agents/')) for (const [name, kind] of observed.entries ?? []) if (kind === 'symlink') skippedPaths.push({ path: safeSource(path.join(snapshot.home, relative, name)), reason: 'unfollowed_symlink' });
|
|
661
|
-
const rulesSummary = ['仅在用户和宿主授权下委派', '按任务难度和验证需求选择最小充分模型', '用户明确模型、预算和项目规则优先', '禁止递归委派', '
|
|
662
|
-
return { agent: 'codex', home: snapshot.home, cli_version: VERSION, configuration_version: state?.version ?? null, configuration_revision: snapshot.configuration_revision, installation:
|
|
641
|
+
const rulesSummary = ['仅在用户和宿主授权下委派', '按任务难度和验证需求选择最小充分模型', '用户明确模型、预算和项目规则优先', '禁止递归委派', '禁止修改生产、共享环境、第三方系统和代理配置', '明确独占写入', '先读取真实材料与验收条件', '等价局部差异自行适配', '核心前提失效只阻断受影响部分', '必要验证通过后停止,不擅自扩大检查', '未知退出状态、陈旧报告或部分结果标记 PARTIAL', '固定角色不适用时使用 task_delegate,权限与验收不变', '备用须满足原授权、预算、工具和能力;失败不得静默降级'];
|
|
642
|
+
return { agent: 'codex', home: snapshot.home, cli_version: VERSION, configuration_version: state?.version ?? null, configuration_revision: snapshot.configuration_revision, installation: state ? 'installed' : 'not_installed', integrity: snapshot.diagnostics.some(value => value.status === 'fail') ? 'drifted' : 'clean', mode: mode ? { id: mode.id, name: mode.name, kind: mode.kind, levels: mode.levels } : null, restoration: { status: state?.restoration.status ?? 'not_installed' }, roles: Object.entries(ROLES).map(([name, level]) => {
|
|
663
643
|
const relative = `agents/${name.replaceAll('_', '-')}.toml`;
|
|
664
644
|
const bytes = snapshot.observedFiles.get(relative).bytes;
|
|
665
645
|
let role = {};
|
|
@@ -670,7 +650,7 @@ export async function statusReport(home, options = {}) {
|
|
|
670
650
|
}
|
|
671
651
|
export async function listReport(home, options = {}) {
|
|
672
652
|
const snapshot = await readSnapshot(home, options);
|
|
673
|
-
return { configuration_revision: snapshot.configuration_revision, home: snapshot.home, modes: [...BUILTINS, ...snapshot.customModes].map(mode => ({ ...(mode.kind === 'builtin' && snapshot.mode?.id === mode.id ? snapshot.mode : mode),
|
|
653
|
+
return { configuration_revision: snapshot.configuration_revision, home: snapshot.home, modes: [...BUILTINS, ...snapshot.customModes].map(mode => ({ ...(mode.kind === 'builtin' && snapshot.mode?.id === mode.id ? snapshot.mode : mode), active: snapshot.mode?.id === mode.id, source: mode.kind === 'builtin' && snapshot.mode?.id === mode.id ? 'installed_snapshot' : mode.kind === 'builtin' ? 'package' : 'custom_definition', definition_version: mode.kind === 'builtin' && snapshot.mode?.id === mode.id ? snapshot.mode.definition_version ?? snapshot.state.version : mode.kind === 'builtin' ? VERSION : null })), diagnostics: snapshot.diagnostics };
|
|
674
654
|
}
|
|
675
655
|
export async function showReport(home, selector, options = {}) {
|
|
676
656
|
const snapshot = await readSnapshot(home, options);
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subagent-delegation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Local Codex subagent delegation profiles, safe configuration and restore.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "fanglinwei",
|
|
8
8
|
"repository": { "type": "git", "url": "https://github.com/fanglinwei/subagent-delegation.git" },
|
|
9
|
-
"bin": { "sub-d": "bin/subagent-delegation.mjs"
|
|
9
|
+
"bin": { "sub-d": "bin/subagent-delegation.mjs" },
|
|
10
10
|
"engines": { "node": ">=22" },
|
|
11
|
-
"files": ["bin/", "lib/", "web/", "adapters/", "VERSION", "README.md", "docs/README.md", "docs/usage.md", "docs/implementation-review.md", "LICENSE"
|
|
11
|
+
"files": ["bin/", "lib/", "web/", "adapters/", "VERSION", "README.md", "docs/README.md", "docs/usage.md", "docs/implementation-review.md", "LICENSE"],
|
|
12
12
|
"scripts": { "test": "node --test tests/*.test.mjs", "test:browser": "node tests/browser.mjs" },
|
|
13
13
|
"dependencies": { "smol-toml": "1.9.1" }
|
|
14
14
|
}
|