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/web/app.js
CHANGED
|
@@ -24,8 +24,8 @@ function node(tag, attributes = {}, ...children) {
|
|
|
24
24
|
return element;
|
|
25
25
|
}
|
|
26
26
|
function replace(id, ...children) { $(id).replaceChildren(...children.flat()); }
|
|
27
|
-
function installed() { return
|
|
28
|
-
function activeMode() { return
|
|
27
|
+
function installed() { return state.status?.installation; }
|
|
28
|
+
function activeMode() { return state.status?.mode ?? null; }
|
|
29
29
|
function dirty() { return Boolean(state.saved && state.draft && !same(state.saved.levels, state.draft.levels)); }
|
|
30
30
|
function current() { return state.selected === activeMode()?.id; }
|
|
31
31
|
function custom() { return state.saved?.kind === 'custom'; }
|
|
@@ -37,12 +37,7 @@ function displayValue(value) { return value === null || value === undefined ? '
|
|
|
37
37
|
class ActionError extends Error {
|
|
38
38
|
constructor(code, message, response = null) { super(message); this.code = code; this.response = response; }
|
|
39
39
|
}
|
|
40
|
-
function modeCommand(selector) {
|
|
41
|
-
return state.modes.find(mode => mode.id === selector)?.command ?? selector;
|
|
42
|
-
}
|
|
43
40
|
async function request(action, params = {}, allowPartial = false) {
|
|
44
|
-
if (params.mode) params = { ...params, mode: modeCommand(params.mode) };
|
|
45
|
-
if (params.from) params = { ...params, from: modeCommand(params.from) };
|
|
46
41
|
let response, payload;
|
|
47
42
|
try {
|
|
48
43
|
response = await fetch('/api/actions', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-Delegation-Token': state.bootstrap.csrf_token }, body: JSON.stringify({ action, params }) });
|
|
@@ -78,7 +73,7 @@ function busy(value, text = '') {
|
|
|
78
73
|
for (const button of $('dialog-actions').querySelectorAll('button')) button.disabled = value;
|
|
79
74
|
}
|
|
80
75
|
function writeBlocked() {
|
|
81
|
-
const integrity = state.status?.integrity
|
|
76
|
+
const integrity = state.status?.integrity;
|
|
82
77
|
return !state.connected || !state.revision || state.stale || integrity === 'drifted' || installed() === 'invalid' || (state.status?.diagnostics ?? []).some(item => item.status === 'fail' || ['RULES_SHADOWED', 'ADAPTER_UNSUPPORTED'].includes(item.code));
|
|
83
78
|
}
|
|
84
79
|
|
|
@@ -88,9 +83,9 @@ async function readBatch(preferred) {
|
|
|
88
83
|
const status = (await request('status', { details: true }, true)).report.data;
|
|
89
84
|
const listing = (await request('list', {}, true)).report.data;
|
|
90
85
|
if (!status || !Array.isArray(listing?.modes)) throw new ActionError('COMMAND_RESULT_INVALID', '配置查询缺少状态或档位列表。');
|
|
91
|
-
const active =
|
|
92
|
-
const selected = listing.modes.some(mode => mode.id === preferred) ? preferred : active ?? listing.modes.find(mode => mode.id === '
|
|
93
|
-
const shown = selected ? (await request('show', { mode:
|
|
86
|
+
const active = status.mode?.id;
|
|
87
|
+
const selected = listing.modes.some(mode => mode.id === preferred) ? preferred : active ?? listing.modes.find(mode => mode.id === 'mid')?.id ?? listing.modes[0]?.id;
|
|
88
|
+
const shown = selected ? (await request('show', { mode: selected }, true)).report.data : null;
|
|
94
89
|
const revision = status.configuration_revision;
|
|
95
90
|
if (revision && revision === listing.configuration_revision && (!shown || revision === shown.configuration_revision)) {
|
|
96
91
|
if (shown && (!shown.definition?.levels || LEVELS.some(level => !shown.definition.levels[level]))) throw new ActionError('COMMAND_RESULT_INVALID', '档位报告缺少完整 L1–L4 定义。');
|
|
@@ -140,8 +135,6 @@ async function refresh({ selected = state.selected, compare = false, preserve =
|
|
|
140
135
|
}
|
|
141
136
|
|
|
142
137
|
function commandArguments(action, params) {
|
|
143
|
-
if (params.mode) params = { ...params, mode: modeCommand(params.mode) };
|
|
144
|
-
if (params.from) params = { ...params, from: modeCommand(params.from) };
|
|
145
138
|
const args = [];
|
|
146
139
|
if (['set', 'create', 'configure'].includes(action)) args.push('mode', action);
|
|
147
140
|
else if (action === 'models') args.push('models', 'check');
|
|
@@ -165,7 +158,7 @@ function commandText(argv) { return ['sub-d', ...argv.map(value => "'" + String(
|
|
|
165
158
|
function nextWrite() {
|
|
166
159
|
if (!state.saved) return null;
|
|
167
160
|
const params = { mode: state.selected, expectRevision: state.revision };
|
|
168
|
-
if (installed() === 'not_installed'
|
|
161
|
+
if (installed() === 'not_installed') return { action: 'setup', params };
|
|
169
162
|
if (dirty()) return { action: 'configure', params: { ...params, definition: { levels: clone(state.draft.levels) } } };
|
|
170
163
|
if (!current()) return { action: 'set', params };
|
|
171
164
|
return null;
|
|
@@ -289,8 +282,8 @@ function showTab(tab) {
|
|
|
289
282
|
function renderModePicker() {
|
|
290
283
|
const focusedMode = $('mode-radios').contains(document.activeElement) ? document.activeElement.id : null;
|
|
291
284
|
const isCustom = state.modes.find(mode => mode.id === state.selected)?.kind === 'custom';
|
|
292
|
-
const descriptions = {
|
|
293
|
-
const radios = state.modes.filter(mode => mode.kind !== 'custom').map(mode => ({ value: mode.id, label: `${mode.name} · ${mode.
|
|
285
|
+
const descriptions = { low: '简单任务,优先节省', mid: '日常任务,兼顾质量', fire: '复杂任务,优先质量', custom: '自行设置模型与备用' };
|
|
286
|
+
const radios = state.modes.filter(mode => mode.kind !== 'custom').map(mode => ({ value: mode.id, label: `${mode.name} · ${mode.id}` }));
|
|
294
287
|
radios.push({ value: 'custom', label: '自定义' });
|
|
295
288
|
replace('mode-radios', radios.map(option => node('label', {}, node('input', { id: `mode-${option.value}`, type: 'radio', name: 'mode', value: option.value, 'aria-label': option.label, 'aria-describedby': `mode-${option.value}-note`, checked: option.value === 'custom' ? isCustom : state.selected === option.value, disabled: state.busy || !state.connected, on: { change: () => { if (option.value !== 'custom') selectMode(option.value); else { const target = state.modes.find(mode => mode.kind === 'custom'); if (target) selectMode(target.id); else { renderModePicker(); createMode(); } } } } }), node('span', { class: 'mode-name' }, option.label), node('span', { id: `mode-${option.value}-note`, class: 'mode-description' }, descriptions[option.value] ?? '查看模型分配'))));
|
|
296
289
|
$('custom-picker').hidden = !isCustom;
|
|
@@ -359,7 +352,7 @@ function updateLevelSummary(level) {
|
|
|
359
352
|
}
|
|
360
353
|
function renderChanges() {
|
|
361
354
|
if (!state.draft) { $('changes-count').textContent = '暂无可编辑的定义'; replace('change-list'); return; }
|
|
362
|
-
const setup = installed() === 'not_installed'
|
|
355
|
+
const setup = installed() === 'not_installed';
|
|
363
356
|
const old = dirty() || current() ? state.saved : activeMode();
|
|
364
357
|
const changes = LEVELS.filter(level => setup || !same(old?.levels?.[level], state.draft.levels[level]));
|
|
365
358
|
$('changes-count').textContent = changes.length ? `${setup ? '将配置' : '变化涉及'} ${changes.length} 个任务等级` : current() ? '尚未修改' : '模型组合不变,将切换档位';
|
|
@@ -377,20 +370,18 @@ function renderControls() {
|
|
|
377
370
|
const blocked = state.busy || writeBlocked();
|
|
378
371
|
const operation = nextWrite();
|
|
379
372
|
$('draft-label').hidden = !operation;
|
|
380
|
-
$('apply').textContent = state.busy ? '请等待操作完成…' : installed() === 'not_installed' ? '配置 Codex' :
|
|
373
|
+
$('apply').textContent = state.busy ? '请等待操作完成…' : installed() === 'not_installed' ? '配置 Codex' : dirty() ? current() ? '保存并应用' : '保存档位' : !current() && state.saved ? '设为当前档位' : '无修改';
|
|
381
374
|
$('apply').disabled = blocked || !operation;
|
|
382
375
|
$('preview').disabled = blocked || !operation;
|
|
383
|
-
$('new-mode').disabled = blocked ||
|
|
384
|
-
$('copy-mode').disabled = blocked || !state.saved ||
|
|
376
|
+
$('new-mode').disabled = blocked || installed() !== 'installed';
|
|
377
|
+
$('copy-mode').disabled = blocked || !state.saved || installed() !== 'installed';
|
|
385
378
|
$('cancel-draft').hidden = !dirty(); $('cancel-draft').disabled = state.busy;
|
|
386
379
|
$('cancel-selection').hidden = !activeMode() || current() || dirty(); $('cancel-selection').disabled = state.busy;
|
|
387
|
-
const version = state.status?.configuration_version ?? state.status?.installation?.version;
|
|
388
|
-
$('upgrade').hidden = !version || version === state.bootstrap?.cli_version || installed() !== 'installed'; $('upgrade').disabled = blocked;
|
|
389
380
|
$('refresh').disabled = state.busy;
|
|
390
381
|
for (const id of ['custom-mode', 'explain', 'explain-mode', 'explain-level', 'explain-role', 'check-models', 'verify', 'verify-model', 'verify-effort', 'close-preview', 'compare']) $(id).disabled = state.busy || !state.connected;
|
|
391
382
|
for (const input of document.querySelectorAll('#levels input, #mode-radios input, #verify-form input[type=radio]')) input.disabled = state.busy || !state.connected;
|
|
392
383
|
for (const link of document.querySelectorAll('.tabs a')) link.setAttribute('aria-disabled', String(state.busy));
|
|
393
|
-
const restoration = state.status?.restoration?.status
|
|
384
|
+
const restoration = state.status?.restoration?.status;
|
|
394
385
|
$('restore-preview').disabled = state.busy || !state.connected || !state.revision || state.stale || !['available'].includes(restoration);
|
|
395
386
|
$('restore-confirm').disabled = state.busy || !state.restorePreview || state.restorePreview.revision !== state.revision;
|
|
396
387
|
}
|
|
@@ -411,15 +402,15 @@ function renderFiles(id, data) {
|
|
|
411
402
|
function renderRules() {
|
|
412
403
|
replace('role-list', (state.status?.roles ?? []).map(role => node('div', { class: 'role' }, node('h4', {}, `${ROLE_LABELS[role.name] ?? role.name} · ${role.name}`), node('p', {}, `${role.configured_model ?? role.model ?? '模型未固定'} / ${role.configured_effort ?? role.effort ?? '推理强度未固定'}`), node('p', {}, role.name === 'task_scan' || role.name === 'task_review' ? '只读职责;不获得项目写入权限。' : role.name === 'task_execute' ? '仅在本次明确范围内产出,写入范围须独占。' : '按照本次明确授权与原职责执行。'), role.source ? node('p', { class: 'path' }, `来源:${role.source}`) : null)));
|
|
413
404
|
if (!state.status?.roles?.length) replace('role-list', node('p', { class: 'muted' }, '未安装本工具的角色。可在档位页先预览并配置。'));
|
|
414
|
-
const summary = state.status?.rules?.summary ?? ['用户与宿主授权优先', '禁止递归委派', '
|
|
405
|
+
const summary = state.status?.rules?.summary ?? ['用户与宿主授权优先', '禁止递归委派', '明确独占写入', '外部写入交回主代理', '局部阻断与最小充分验证', '保留专业规则、预算和高影响裁决边界'];
|
|
415
406
|
replace('rule-summary', node('ul', {}, summary.map(item => node('li', {}, typeof item === 'string' ? item : displayValue(item)))));
|
|
416
407
|
replace('sources', node('p', { class: 'muted' }, `项目查询范围:${state.bootstrap?.project ?? '服务启动时的工作目录'}`), node('pre', {}, JSON.stringify({ rules: state.status?.rules, related_config: state.status?.related_config, inspection: state.status?.inspection, diagnostics: state.status?.diagnostics }, null, 2)));
|
|
417
408
|
const previous = $('explain-mode').value;
|
|
418
409
|
replace('explain-mode', state.modes.map(mode => node('option', { value: mode.id, selected: mode.id === (previous || state.selected) }, mode.name)));
|
|
419
410
|
}
|
|
420
411
|
function renderRestore() {
|
|
421
|
-
const restoration = state.status?.restoration?.status
|
|
422
|
-
const messages = { available: '安装前恢复记录可用。仍需先预览并通过完整预检。', not_installed: '未安装本工具,无需恢复。',
|
|
412
|
+
const restoration = state.status?.restoration?.status;
|
|
413
|
+
const messages = { available: '安装前恢复记录可用。仍需先预览并通过完整预检。', not_installed: '未安装本工具,无需恢复。', invalid: '恢复记录无效。自动恢复已阻止,请核对诊断与原备份。' };
|
|
423
414
|
$('restore-capability').textContent = messages[restoration] ?? '恢复能力未确认,请查看状态诊断。';
|
|
424
415
|
$('restore-target').textContent = `绑定 home:${state.bootstrap?.home ?? ''}`;
|
|
425
416
|
const customs = state.modes.filter(mode => mode.kind === 'custom');
|
|
@@ -457,8 +448,8 @@ function renderModels() {
|
|
|
457
448
|
function render() {
|
|
458
449
|
const active = activeMode();
|
|
459
450
|
$('configured').dataset.state = installed();
|
|
460
|
-
$('versions').textContent = `工具 ${state.bootstrap?.cli_version ?? '未知'} · 已安装配置 ${state.status?.configuration_version ??
|
|
461
|
-
$('configured').textContent =
|
|
451
|
+
$('versions').textContent = `工具 ${state.bootstrap?.cli_version ?? '未知'} · 已安装配置 ${state.status?.configuration_version ?? '未安装'}`;
|
|
452
|
+
$('configured').textContent = active ? `已配置:${active.name}` : '尚未配置本工具';
|
|
462
453
|
$('runtime').hidden = installed() !== 'installed';
|
|
463
454
|
$('runtime').textContent = '使用已配置规则前请重启 Codex;运行生效未验证。';
|
|
464
455
|
$('editing-title').textContent = state.saved ? `${custom() ? '正在编辑' : '正在查看'}:${state.saved.name}` : '四级模型预设';
|
|
@@ -481,7 +472,7 @@ async function createMode() {
|
|
|
481
472
|
source.addEventListener('change', () => { preview.hidden = true; });
|
|
482
473
|
const valid = () => {
|
|
483
474
|
const normalized = name.value.trim().normalize('NFC');
|
|
484
|
-
if (!validText(normalized, 80) || state.modes.some(mode => mode.id === normalized || mode.name === normalized
|
|
475
|
+
if (!validText(normalized, 80) || state.modes.some(mode => mode.id === normalized || mode.name === normalized)) {
|
|
485
476
|
error.textContent = '名称须为唯一的 1–80 个字符,不能与内置命令同名或包含控制字符。'; error.hidden = false; name.focus(); return false;
|
|
486
477
|
}
|
|
487
478
|
return true;
|
|
@@ -558,13 +549,6 @@ $('preview').addEventListener('click', () => apply(true));
|
|
|
558
549
|
$('cancel-draft').addEventListener('click', async () => { state.draft = clone(state.saved); state.preview = null; if (state.stale) await refresh({ preserve: false }); else render(); });
|
|
559
550
|
$('cancel-selection').addEventListener('click', () => selectMode(activeMode().id));
|
|
560
551
|
$('close-preview').addEventListener('click', () => { state.preview = null; renderPreview(); $('preview').focus(); });
|
|
561
|
-
$('upgrade').addEventListener('click', async () => {
|
|
562
|
-
if (dirty()) { showError(new ActionError('UNSAVED_DRAFT', '请先保存或取消草稿,再预览升级配置。')); return; }
|
|
563
|
-
const params = { mode: activeMode()?.id ?? state.selected, expectRevision: state.revision };
|
|
564
|
-
if (!await performWrite('setup', params, { preview: true })) return;
|
|
565
|
-
const answer = await dialog({ title: '升级已安装配置?', description: '以下是 CLI 预览的文件变化。保留已选择的档位与自定义定义;升级后请重启 Codex。', content: node('pre', {}, JSON.stringify(state.preview.response.report.data?.changes ?? state.preview.response.report.data?.changed_files ?? [], null, 2)), choices: [{ label: '返回查看预览', value: null }, { label: '升级配置', value: true, class: 'primary' }] });
|
|
566
|
-
if (answer) await performWrite('setup', params);
|
|
567
|
-
});
|
|
568
552
|
$('copy-definition').addEventListener('click', async () => {
|
|
569
553
|
try { await navigator.clipboard.writeText(JSON.stringify({ levels: state.draft.levels }, null, 2)); $('copy-status').textContent = '完整四级 JSON 已复制。'; }
|
|
570
554
|
catch { $('copy-status').textContent = '浏览器未允许复制,请展开以下 JSON 手动复制。'; $('command').textContent += `\n\n${JSON.stringify({ levels: state.draft.levels }, null, 2)}`; }
|
package/web/index.html
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
<div class="changes-heading"><h2 id="changes-title">本次变更</h2><span id="draft-label" class="draft-label" hidden>待确认</span></div>
|
|
62
62
|
<p id="changes-count" class="muted">尚未修改</p><div id="change-list"></div>
|
|
63
63
|
<p id="save-note" class="save-note"></p>
|
|
64
|
-
<div class="actions"><button id="apply" type="button" class="primary" disabled>无修改</button><button id="cancel-draft" type="button" class="secondary" hidden>取消修改</button><button id="cancel-selection" type="button" class="secondary" hidden>取消选择</button><button id="preview" type="button" class="secondary" disabled>预览文件变更</button
|
|
64
|
+
<div class="actions"><button id="apply" type="button" class="primary" disabled>无修改</button><button id="cancel-draft" type="button" class="secondary" hidden>取消修改</button><button id="cancel-selection" type="button" class="secondary" hidden>取消选择</button><button id="preview" type="button" class="secondary" disabled>预览文件变更</button></div>
|
|
65
65
|
<details id="command-details" class="technical"><summary>查看终端命令</summary><p id="command-label" class="muted">将执行的命令(尚未执行)</p><pre id="command"></pre><button id="copy-definition" type="button" class="secondary" hidden>复制完整四级 JSON</button><p id="copy-status" class="muted" role="status"></p></details>
|
|
66
66
|
</aside>
|
|
67
67
|
</div>
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "Subagent Model Selection"
|
|
3
|
-
short_description: "Choose subagent models by task difficulty, scope, and budget"
|
|
4
|
-
default_prompt: "Use $subagent-model-selection to choose models and reasoning effort for authorized delegation while respecting my model preferences and budget."
|
|
5
|
-
policy:
|
|
6
|
-
allow_implicit_invocation: true
|
package/install.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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())
|
|
File without changes
|
|
File without changes
|