@aragon-agent/cli 0.6.38 → 0.6.40
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/CHANGELOG.md +263 -0
- package/README.md +544 -11
- package/dist/agent/controller.d.ts +245 -10
- package/dist/agent/controller.js +367 -22
- package/dist/agent/model-profile-settings.js +12 -1
- package/dist/agent/plan-prompt.d.ts +1 -3
- package/dist/agent/plan-prompt.js +13 -7
- package/dist/agent/reducer.d.ts +70 -2
- package/dist/agent/reducer.js +59 -6
- package/dist/agent/skill-triage-prompt.d.ts +42 -0
- package/dist/agent/skill-triage-prompt.js +74 -0
- package/dist/agent/system-prompt.d.ts +28 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +106 -32
- package/dist/commands/builtins.js +24 -3
- package/dist/commands/keys-command.d.ts +23 -0
- package/dist/commands/keys-command.js +38 -0
- package/dist/commands/registry.d.ts +39 -0
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +25 -0
- package/dist/commands/subagent-command.js +242 -0
- package/dist/commands/task-command.d.ts +39 -0
- package/dist/commands/task-command.js +102 -0
- package/dist/compaction/command.js +16 -3
- package/dist/compaction/compactor.d.ts +25 -0
- package/dist/compaction/compactor.js +111 -10
- package/dist/compaction/digest.d.ts +24 -1
- package/dist/compaction/digest.js +139 -17
- package/dist/compaction/limits.d.ts +21 -0
- package/dist/compaction/limits.js +21 -0
- package/dist/compaction/reason-text.d.ts +36 -0
- package/dist/compaction/reason-text.js +78 -0
- package/dist/compaction/types.d.ts +82 -14
- package/dist/compaction/wiring.d.ts +13 -8
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +23 -0
- package/dist/config/cli-commands.js +36 -0
- package/dist/config/env.js +17 -4
- package/dist/config/load.js +55 -3
- package/dist/config/schema.d.ts +125 -10
- package/dist/config/schema.js +138 -14
- package/dist/config/store.js +23 -3
- package/dist/diagnostics/doctor.d.ts +9 -0
- package/dist/diagnostics/doctor.js +34 -0
- package/dist/diagnostics/info.d.ts +71 -0
- package/dist/diagnostics/info.js +91 -5
- package/dist/exec/bridge/caller-tools.d.ts +96 -0
- package/dist/exec/bridge/caller-tools.js +213 -0
- package/dist/exec/bridge/decisions.d.ts +78 -0
- package/dist/exec/bridge/decisions.js +116 -0
- package/dist/exec/bridge/human-answers.d.ts +38 -0
- package/dist/exec/bridge/human-answers.js +164 -0
- package/dist/exec/bridge/limits.d.ts +68 -0
- package/dist/exec/bridge/limits.js +68 -0
- package/dist/exec/bridge/ndjson.d.ts +17 -0
- package/dist/exec/bridge/ndjson.js +83 -0
- package/dist/exec/bridge/pending.d.ts +70 -0
- package/dist/exec/bridge/pending.js +132 -0
- package/dist/exec/bridge/protocol.d.ts +346 -0
- package/dist/exec/bridge/protocol.js +256 -0
- package/dist/exec/bridge/server.d.ts +142 -0
- package/dist/exec/bridge/server.js +502 -0
- package/dist/exec/bridge/source.d.ts +58 -0
- package/dist/exec/bridge/source.js +145 -0
- package/dist/exec/bridge/writer.d.ts +26 -0
- package/dist/exec/bridge/writer.js +156 -0
- package/dist/exec/cli-commands.js +30 -2
- package/dist/exec/emitter.d.ts +16 -2
- package/dist/exec/emitter.js +26 -6
- package/dist/exec/events.d.ts +465 -10
- package/dist/exec/events.js +47 -1
- package/dist/exec/hooks/dispatch.d.ts +76 -0
- package/dist/exec/hooks/dispatch.js +169 -0
- package/dist/exec/hooks/index.d.ts +83 -0
- package/dist/exec/hooks/index.js +205 -0
- package/dist/exec/hooks/limits.d.ts +56 -0
- package/dist/exec/hooks/limits.js +49 -0
- package/dist/exec/hooks/registry.d.ts +49 -0
- package/dist/exec/hooks/registry.js +91 -0
- package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
- package/dist/exec/hooks/tool-wrapper.js +154 -0
- package/dist/exec/hooks/turn.d.ts +61 -0
- package/dist/exec/hooks/turn.js +59 -0
- package/dist/exec/hooks/validate.d.ts +85 -0
- package/dist/exec/hooks/validate.js +310 -0
- package/dist/exec/index.d.ts +130 -4
- package/dist/exec/index.js +501 -10
- package/dist/exec/options.d.ts +124 -2
- package/dist/exec/options.js +413 -6
- package/dist/exec/permission.d.ts +9 -1
- package/dist/exec/permission.js +7 -1
- package/dist/exec/request/limits.d.ts +51 -0
- package/dist/exec/request/limits.js +49 -0
- package/dist/exec/request/scope.d.ts +106 -0
- package/dist/exec/request/scope.js +149 -0
- package/dist/exec/result/limits.d.ts +74 -0
- package/dist/exec/result/limits.js +72 -0
- package/dist/exec/result/prompt.d.ts +28 -0
- package/dist/exec/result/prompt.js +74 -0
- package/dist/exec/result/schema.d.ts +99 -0
- package/dist/exec/result/schema.js +464 -0
- package/dist/exec/result/sink.d.ts +56 -0
- package/dist/exec/result/sink.js +50 -0
- package/dist/exec/result/tool.d.ts +37 -0
- package/dist/exec/result/tool.js +118 -0
- package/dist/exec/runner.d.ts +395 -1
- package/dist/exec/runner.js +860 -8
- package/dist/exec/threads/aggregate.d.ts +34 -0
- package/dist/exec/threads/aggregate.js +30 -0
- package/dist/exec/threads/capacity.d.ts +23 -0
- package/dist/exec/threads/capacity.js +38 -0
- package/dist/exec/threads/dispatch-source.d.ts +60 -0
- package/dist/exec/threads/dispatch-source.js +106 -0
- package/dist/exec/threads/execute-threads.d.ts +53 -0
- package/dist/exec/threads/execute-threads.js +234 -0
- package/dist/exec/threads/gate.d.ts +42 -0
- package/dist/exec/threads/gate.js +81 -0
- package/dist/exec/threads/identity.d.ts +36 -0
- package/dist/exec/threads/identity.js +67 -0
- package/dist/exec/threads/interrupt.d.ts +29 -0
- package/dist/exec/threads/interrupt.js +79 -0
- package/dist/exec/threads/limits.d.ts +58 -0
- package/dist/exec/threads/limits.js +35 -0
- package/dist/exec/threads/outcome.d.ts +47 -0
- package/dist/exec/threads/outcome.js +26 -0
- package/dist/exec/threads/registry.d.ts +81 -0
- package/dist/exec/threads/registry.js +427 -0
- package/dist/exec/threads/runtime.d.ts +107 -0
- package/dist/exec/threads/runtime.js +523 -0
- package/dist/exec/threads/thread-source.d.ts +20 -0
- package/dist/exec/threads/thread-source.js +134 -0
- package/dist/fast/limits.d.ts +54 -0
- package/dist/fast/limits.js +54 -0
- package/dist/fast/summary-call.d.ts +72 -0
- package/dist/fast/summary-call.js +133 -0
- package/dist/fast/summary-prompt.d.ts +106 -0
- package/dist/fast/summary-prompt.js +320 -0
- package/dist/fast/task-summary-types.d.ts +66 -0
- package/dist/fast/task-summary-types.js +17 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/limits.d.ts +7 -0
- package/dist/proc/limits.js +7 -0
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- package/dist/proc/output-streams.d.ts +50 -0
- package/dist/proc/output-streams.js +54 -0
- package/dist/skills/service.d.ts +41 -0
- package/dist/skills/service.js +62 -0
- package/dist/skills/triage.d.ts +194 -0
- package/dist/skills/triage.js +393 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/limits.d.ts +21 -1
- package/dist/team/limits.js +21 -1
- package/dist/team/normalize.d.ts +15 -0
- package/dist/team/normalize.js +21 -4
- package/dist/team/prompt.d.ts +6 -3
- package/dist/team/prompt.js +9 -5
- package/dist/team/runtime.d.ts +9 -0
- package/dist/team/runtime.js +8 -0
- package/dist/team/subagent.d.ts +15 -0
- package/dist/team/subagent.js +45 -1
- package/dist/team/task-tool.js +11 -7
- package/dist/tools/bash-tool.d.ts +10 -0
- package/dist/tools/bash-tool.js +71 -6
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/index.d.ts +93 -1
- package/dist/tools/index.js +112 -3
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/tools/tool-gate.d.ts +100 -0
- package/dist/tools/tool-gate.js +87 -0
- package/dist/ui/App.js +238 -12
- package/dist/ui/Header.d.ts +28 -16
- package/dist/ui/Header.js +21 -19
- package/dist/ui/StatusBar.d.ts +1 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +11 -1
- package/dist/ui/entries/CompactionCard.js +65 -4
- package/dist/ui/interaction-hints.js +6 -0
- package/dist/ui/layout/OverlayFrame.d.ts +18 -0
- package/dist/ui/layout/OverlayFrame.js +19 -4
- package/dist/ui/layout/header-hints.d.ts +55 -62
- package/dist/ui/layout/header-hints.js +59 -166
- package/dist/ui/layout/header-left.d.ts +115 -0
- package/dist/ui/layout/header-left.js +206 -0
- package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
- package/dist/ui/layout/shortcut-panel-layout.js +321 -0
- package/dist/ui/layout/status-detail-layout.js +5 -1
- package/dist/ui/layout/status-layout.d.ts +10 -0
- package/dist/ui/layout/status-layout.js +18 -1
- package/dist/ui/layout/task-summary-cell.d.ts +61 -0
- package/dist/ui/layout/task-summary-cell.js +65 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
- package/dist/ui/overlays/HelpOverlay.js +29 -2
- package/dist/ui/overlays/SettingsScreen.js +29 -5
- package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
- package/dist/ui/overlays/ShortcutPanel.js +106 -0
- package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
- package/dist/ui/overlays/TeamFanoutPage.js +78 -0
- package/dist/ui/overlays/model-profile-state.d.ts +19 -1
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
- package/dist/ui/overlays/team-fanout-state.js +97 -0
- package/dist/ui/shortcuts.d.ts +81 -0
- package/dist/ui/shortcuts.js +221 -0
- package/package.json +2 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/subagent` — parse the user's arguments and compile them into ONE user turn
|
|
3
|
+
* (subagent-fanout 3.5.2 / 3.5.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope, and this module
|
|
6
|
+
* also produces text a model reads in a `cmd.exe` terminal.
|
|
7
|
+
*
|
|
8
|
+
* WHY A PROMPT AND NOT A DIRECT `dispatch()` CALL (D-8 / 3.5.1, option C). The
|
|
9
|
+
* alternative — the command splits the brief itself and calls
|
|
10
|
+
* `TeamRuntime.dispatch()` — needs a SECOND dispatch entry point (breaking I-1),
|
|
11
|
+
* a run lifecycle the command does not own (Esc, the lead's idle watchdog, the
|
|
12
|
+
* report rendered into the transcript, spend accounting, the concurrency lock),
|
|
13
|
+
* and a planner that cannot see the conversation. Compiling the user's intent
|
|
14
|
+
* into a normal user turn keeps all of that on the one path that already has it.
|
|
15
|
+
* The cost is that forcing is INSTRUCTION-LEVEL rather than mechanical, which is
|
|
16
|
+
* why the command body prints a plan ("the next turn dispatches them") instead
|
|
17
|
+
* of claiming results (D-10).
|
|
18
|
+
*
|
|
19
|
+
* TWO PURE FUNCTIONS, NO IO: parse and compile. Both are deterministic, so the
|
|
20
|
+
* whole surface is unit-testable without a terminal, a config file or a model.
|
|
21
|
+
*/
|
|
22
|
+
import { TEAM_LIMITS } from './limits.js';
|
|
23
|
+
import { toSlug } from './normalize.js';
|
|
24
|
+
/**
|
|
25
|
+
* Bumped whenever the directive's wording changes; the string appears VERBATIM
|
|
26
|
+
* on the directive's first line so a behaviour report ties to one revision with
|
|
27
|
+
* a single grep (D-15). The `-YYYY-MM` suffix is part of the version, not a
|
|
28
|
+
* comment: `v1` alone is easy to confuse with the other counters in this package.
|
|
29
|
+
*/
|
|
30
|
+
export const FORCED_DISPATCH_VERSION = 'v1-2026-10';
|
|
31
|
+
/**
|
|
32
|
+
* A DECIMAL POSITIVE INTEGER, and nothing else.
|
|
33
|
+
*
|
|
34
|
+
* `0` is deliberately NOT accepted (R-P1-2). Writing `/subagent -n 0` means the
|
|
35
|
+
* user mistyped; silently clamping it to 1 would rewrite their intent, which is
|
|
36
|
+
* the one thing a clamp must never do. `-n 12` is different: 12 is unambiguous
|
|
37
|
+
* and merely exceeds the ceiling, so it is accepted and DISCLOSED as narrowed.
|
|
38
|
+
*/
|
|
39
|
+
const COUNT_PATTERN = /^[1-9][0-9]*$/;
|
|
40
|
+
/** The flag name of a token, or `null` for a plain word (part of the brief). */
|
|
41
|
+
function flagName(word) {
|
|
42
|
+
if (word.length < 2 || !word.startsWith('-'))
|
|
43
|
+
return null;
|
|
44
|
+
return word.replace(/^--?/, '').toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
/** The brief, clipped with a VISIBLE marker (`forcedBriefChars`). */
|
|
47
|
+
function clippedBrief(brief) {
|
|
48
|
+
const limit = TEAM_LIMITS.forcedBriefChars;
|
|
49
|
+
return brief.length > limit ? `${brief.slice(0, limit)}\n[brief truncated]` : brief;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse `/subagent` arguments. ONE parameter (R-P1-1): the structural bounds are
|
|
53
|
+
* constants this module imports, not a caller's opinion.
|
|
54
|
+
*
|
|
55
|
+
* NEVER THROWS and never guesses. Every malformed input maps to one of the four
|
|
56
|
+
* errors the command body has a sentence for, because "we did something
|
|
57
|
+
* reasonable with your typo" is indistinguishable from a bug at the terminal.
|
|
58
|
+
*/
|
|
59
|
+
export function parseSubagentArgs(raw) {
|
|
60
|
+
const words = raw.split(/\s+/).filter((word) => word.length > 0);
|
|
61
|
+
const brief = [];
|
|
62
|
+
const parts = [];
|
|
63
|
+
let count = null;
|
|
64
|
+
let readOnly = false;
|
|
65
|
+
// `--parts` was SEEN, even if every item it carried cleaned away. The conflict
|
|
66
|
+
// rule is about the two flags appearing together, not about the usable parts
|
|
67
|
+
// that survived: `-n 3 --parts ,,,` still names two sources for one number, and
|
|
68
|
+
// silently obeying the count would drop a flag the user typed.
|
|
69
|
+
let sawParts = false;
|
|
70
|
+
for (let index = 0; index < words.length;) {
|
|
71
|
+
const word = words[index];
|
|
72
|
+
const flag = flagName(word);
|
|
73
|
+
if (flag === null) {
|
|
74
|
+
brief.push(word);
|
|
75
|
+
index += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (flag === 'n' || flag === 'count') {
|
|
79
|
+
const value = words[index + 1];
|
|
80
|
+
if (value === undefined || !COUNT_PATTERN.test(value))
|
|
81
|
+
return { ok: false, error: 'count' };
|
|
82
|
+
count = Number(value);
|
|
83
|
+
index += 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (flag === 'parts') {
|
|
87
|
+
const value = words[index + 1];
|
|
88
|
+
// A flag with no value is unusable rather than guessable, so it is
|
|
89
|
+
// reported as a bad option instead of being read as "no parts, then".
|
|
90
|
+
if (value === undefined)
|
|
91
|
+
return { ok: false, error: 'flag', token: word };
|
|
92
|
+
sawParts = true;
|
|
93
|
+
// A part that cleans to nothing is DROPPED rather than kept as an empty
|
|
94
|
+
// label; if every part cleans away the flag is treated as absent, because
|
|
95
|
+
// an empty part list cannot narrow a dispatch the brief still describes.
|
|
96
|
+
for (const item of value.split(',')) {
|
|
97
|
+
const slug = toSlug(item);
|
|
98
|
+
if (slug.length > 0 && !parts.includes(slug))
|
|
99
|
+
parts.push(slug);
|
|
100
|
+
}
|
|
101
|
+
index += 2;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (flag === 'read-only') {
|
|
105
|
+
readOnly = true;
|
|
106
|
+
index += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// `-n=6` lands here: ONE spelling per option, so the `=` form is an unknown
|
|
110
|
+
// token rather than a second grammar with its own edge cases.
|
|
111
|
+
return { ok: false, error: 'flag', token: word };
|
|
112
|
+
}
|
|
113
|
+
// Count and parts answer the same question, so a request carrying both is
|
|
114
|
+
// refused instead of resolved in one direction the user did not choose.
|
|
115
|
+
if (count !== null && sawParts)
|
|
116
|
+
return { ok: false, error: 'conflict' };
|
|
117
|
+
const text = brief.join(' ').trim();
|
|
118
|
+
if (text.length === 0)
|
|
119
|
+
return { ok: false, error: 'brief' };
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
args: {
|
|
123
|
+
brief: text,
|
|
124
|
+
count,
|
|
125
|
+
parts: parts.slice(0, TEAM_LIMITS.forcedPartsMax),
|
|
126
|
+
partsRequested: parts.length,
|
|
127
|
+
readOnly,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The sentence naming how many children to start.
|
|
133
|
+
*
|
|
134
|
+
* THE PERMISSION CLAUSE IS NOT OPTIONAL IN EITHER BRANCH. `--parts` REPLACES the
|
|
135
|
+
* count sentence, so a `readOnly` qualifier written only into the count branch
|
|
136
|
+
* disappears exactly when both flags are used - and `/subagent --read-only
|
|
137
|
+
* --parts api,ui <brief>` is one of the three examples the README advertises. The
|
|
138
|
+
* children would then be writeable while the notice said a read-only fan-out was
|
|
139
|
+
* forced (AC-13).
|
|
140
|
+
*/
|
|
141
|
+
function callSentence(request) {
|
|
142
|
+
const { count, readOnly, parts } = request;
|
|
143
|
+
if (parts.length > 0) {
|
|
144
|
+
const labels = 'Use those exact labels';
|
|
145
|
+
return `Call the \`task\` tool ONCE with one subagent per part: ${parts.join(', ')}. `
|
|
146
|
+
+ (readOnly ? `${labels}; every spec readOnly: true.` : `${labels}.`);
|
|
147
|
+
}
|
|
148
|
+
const noun = count === 1 ? 'subagent' : 'subagents';
|
|
149
|
+
const permission = readOnly ? ', all readOnly: true' : '';
|
|
150
|
+
return `Call the \`task\` tool ONCE with exactly ${count} ${noun}${permission}.`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The ONE user turn a `/subagent` invocation submits. Same input, same bytes -
|
|
154
|
+
* which is what makes the snapshot tests meaningful.
|
|
155
|
+
*
|
|
156
|
+
* THE ORDER OF THE SENTENCES IS THE ORDER OF THE MODEL'S DECISIONS: what to do
|
|
157
|
+
* (call `task` ONCE), what it must not do instead (work alone), then the lucky
|
|
158
|
+
* case (the brief does not split), then the brief itself, then the disclosure
|
|
159
|
+
* lines, then the per-spec rules.
|
|
160
|
+
*/
|
|
161
|
+
export function buildForcedDirective(request) {
|
|
162
|
+
const { brief, count, parts, maxSubagents, clamped, requested, planMode } = request;
|
|
163
|
+
// ONE SENTENCE PER LINE, and deliberately not hard-wrapped: the contract in
|
|
164
|
+
// 6.4 is checked by grepping phrases (`Call the `task` tool ONCE`, `Use those
|
|
165
|
+
// exact labels`), and a wrapper that split one of them across a newline would
|
|
166
|
+
// make the document and the artifact disagree about the same bytes.
|
|
167
|
+
const lines = [
|
|
168
|
+
`[forced team dispatch ${FORCED_DISPATCH_VERSION}] The user is forcing a parallel team `
|
|
169
|
+
+ 'on this brief.',
|
|
170
|
+
callSentence(request),
|
|
171
|
+
'Then act on the combined report. Do not do the work yourself; the subagents exist to do it.',
|
|
172
|
+
];
|
|
173
|
+
// NO "at least 2" FALLBACK FOR A SINGLE CHILD (R-P1-4): a sentence asking for
|
|
174
|
+
// at least two would be the louder instruction in a directive that asked for
|
|
175
|
+
// exactly one, and the model would follow it.
|
|
176
|
+
if (count > 1 && parts.length === 0) {
|
|
177
|
+
lines.push(`If the brief genuinely cannot be split into ${count} independent parts, use as many `
|
|
178
|
+
+ 'as it splits into (at least 2) and say why in one line.');
|
|
179
|
+
}
|
|
180
|
+
lines.push('', 'Brief:', clippedBrief(brief), '');
|
|
181
|
+
// A PARTS LIST CAN BE NARROWED BY TWO DIFFERENT CEILINGS (R-P1-3), and the
|
|
182
|
+
// spec's sentence - "the configured maximum is N" - is only true when the
|
|
183
|
+
// POLICY ceiling is the one that bit. `parts.length >= maxSubagents` says it
|
|
184
|
+
// is; below that the structural `forcedPartsMax` did the cutting, and naming
|
|
185
|
+
// the configured maximum would point the user at a number they can change to
|
|
186
|
+
// fix a limit they cannot.
|
|
187
|
+
if (clamped) {
|
|
188
|
+
if (parts.length > 0) {
|
|
189
|
+
lines.push(parts.length >= maxSubagents
|
|
190
|
+
? `You asked for ${requested} parts; the configured maximum is ${maxSubagents}, `
|
|
191
|
+
+ 'so the remaining parts are folded into these.'
|
|
192
|
+
: `You asked for ${requested} parts; only ${parts.length} fit in one directive, `
|
|
193
|
+
+ 'so the remaining parts are folded into these.');
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
lines.push(`You asked for ${requested}; the configured maximum is ${maxSubagents}, `
|
|
197
|
+
+ `so use ${count}.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (planMode) {
|
|
201
|
+
lines.push('Plan mode is on; if the task tool is unavailable this turn, say so instead of '
|
|
202
|
+
+ 'doing the work alone.');
|
|
203
|
+
}
|
|
204
|
+
lines.push('Rules for each spec: a self-contained prompt (the child has no memory of this '
|
|
205
|
+
+ `conversation), a description of at most ${TEAM_LIMITS.descriptionChars} chars, a short `
|
|
206
|
+
+ 'slug label, and a disjoint set of files per child.');
|
|
207
|
+
return lines.join('\n');
|
|
208
|
+
}
|
package/dist/team/limits.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export declare const TEAM_LIMITS: {
|
|
|
29
29
|
* The requirement's ceiling (R-g). Enforced in `clampTeamConfig` AND in
|
|
30
30
|
* `normalizeSubagentSpecs`, so neither a hand-edited file nor a model asking
|
|
31
31
|
* for thirty can exceed it.
|
|
32
|
+
*
|
|
33
|
+
* 10 IS THE CEILING, 8 IS THE DEFAULT (`DEFAULT_TEAM_CONFIG`): different
|
|
34
|
+
* numbers on purpose (subagent-fanout D-1 / D-2). The default is a policy
|
|
35
|
+
* choice the user may move in either direction from the settings screen; this
|
|
36
|
+
* bound is what no user may move.
|
|
32
37
|
*/
|
|
33
38
|
readonly hardMaxSubagents: 10;
|
|
34
39
|
/** `SubagentSpec.description`, clamped (never rejected). */
|
|
@@ -36,6 +41,21 @@ export declare const TEAM_LIMITS: {
|
|
|
36
41
|
/** `SubagentSpec.prompt`, clamped. The child cannot see the conversation, so
|
|
37
42
|
* this has to be generous enough to carry a whole brief. */
|
|
38
43
|
readonly promptChars: 8000;
|
|
44
|
+
/**
|
|
45
|
+
* The brief `/subagent <brief>` copies into its forced-directive turn
|
|
46
|
+
* (subagent-fanout 3.5.4 / R-10). CHARACTERS, not bytes, because this bound
|
|
47
|
+
* feeds a user-visible truncation marker rather than a wire budget: past it
|
|
48
|
+
* the directive says `[brief truncated]` so a clipped instruction is never
|
|
49
|
+
* mistaken for a complete one.
|
|
50
|
+
*/
|
|
51
|
+
readonly forcedBriefChars: 4000;
|
|
52
|
+
/**
|
|
53
|
+
* Parts `/subagent --parts a,b,c` may name BEFORE `maxSubagents` narrows them
|
|
54
|
+
* (subagent-fanout 3.5.2 rule 3). The second, smaller ceiling is the policy
|
|
55
|
+
* number and belongs to the command body; this one only bounds what one
|
|
56
|
+
* argument can carry.
|
|
57
|
+
*/
|
|
58
|
+
readonly forcedPartsMax: 8;
|
|
39
59
|
/** Slugified `SubagentSpec.label`. Long enough to be meaningful in the panel,
|
|
40
60
|
* short enough that five of them fit one row of an 80-column terminal. */
|
|
41
61
|
readonly labelChars: 12;
|
|
@@ -245,4 +265,4 @@ export declare const TEAM_SUBAGENT_TOOL_NAMES: ReadonlySet<string>;
|
|
|
245
265
|
* Bumped whenever the wording of either prompt block changes, so a behaviour
|
|
246
266
|
* report can be tied to a block revision with one grep.
|
|
247
267
|
*/
|
|
248
|
-
export declare const TEAM_BLOCK_VERSION = "
|
|
268
|
+
export declare const TEAM_BLOCK_VERSION = "v2-2026-10";
|
package/dist/team/limits.js
CHANGED
|
@@ -36,6 +36,11 @@ export const TEAM_LIMITS = {
|
|
|
36
36
|
* The requirement's ceiling (R-g). Enforced in `clampTeamConfig` AND in
|
|
37
37
|
* `normalizeSubagentSpecs`, so neither a hand-edited file nor a model asking
|
|
38
38
|
* for thirty can exceed it.
|
|
39
|
+
*
|
|
40
|
+
* 10 IS THE CEILING, 8 IS THE DEFAULT (`DEFAULT_TEAM_CONFIG`): different
|
|
41
|
+
* numbers on purpose (subagent-fanout D-1 / D-2). The default is a policy
|
|
42
|
+
* choice the user may move in either direction from the settings screen; this
|
|
43
|
+
* bound is what no user may move.
|
|
39
44
|
*/
|
|
40
45
|
hardMaxSubagents: HARD_MAX_SUBAGENTS,
|
|
41
46
|
// --- Spec normalization (§3.3.1) ---
|
|
@@ -44,6 +49,21 @@ export const TEAM_LIMITS = {
|
|
|
44
49
|
/** `SubagentSpec.prompt`, clamped. The child cannot see the conversation, so
|
|
45
50
|
* this has to be generous enough to carry a whole brief. */
|
|
46
51
|
promptChars: 8000,
|
|
52
|
+
/**
|
|
53
|
+
* The brief `/subagent <brief>` copies into its forced-directive turn
|
|
54
|
+
* (subagent-fanout 3.5.4 / R-10). CHARACTERS, not bytes, because this bound
|
|
55
|
+
* feeds a user-visible truncation marker rather than a wire budget: past it
|
|
56
|
+
* the directive says `[brief truncated]` so a clipped instruction is never
|
|
57
|
+
* mistaken for a complete one.
|
|
58
|
+
*/
|
|
59
|
+
forcedBriefChars: 4000,
|
|
60
|
+
/**
|
|
61
|
+
* Parts `/subagent --parts a,b,c` may name BEFORE `maxSubagents` narrows them
|
|
62
|
+
* (subagent-fanout 3.5.2 rule 3). The second, smaller ceiling is the policy
|
|
63
|
+
* number and belongs to the command body; this one only bounds what one
|
|
64
|
+
* argument can carry.
|
|
65
|
+
*/
|
|
66
|
+
forcedPartsMax: 8,
|
|
47
67
|
/** Slugified `SubagentSpec.label`. Long enough to be meaningful in the panel,
|
|
48
68
|
* short enough that five of them fit one row of an 80-column terminal. */
|
|
49
69
|
labelChars: 12,
|
|
@@ -259,4 +279,4 @@ export const TEAM_SUBAGENT_TOOL_NAMES = new Set(['team_send', 'team_wait']);
|
|
|
259
279
|
* Bumped whenever the wording of either prompt block changes, so a behaviour
|
|
260
280
|
* report can be tied to a block revision with one grep.
|
|
261
281
|
*/
|
|
262
|
-
export const TEAM_BLOCK_VERSION = '
|
|
282
|
+
export const TEAM_BLOCK_VERSION = 'v2-2026-10';
|
package/dist/team/normalize.d.ts
CHANGED
|
@@ -30,6 +30,21 @@ export interface NormalizedSpecs {
|
|
|
30
30
|
/** How many entries the model asked for, before dropping and capping. */
|
|
31
31
|
requested: number;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The slug rule for a subagent label, EXPORTED because a second caller needs the
|
|
35
|
+
* same bytes: `/subagent --parts api,UI,tests` cleans its parts through this
|
|
36
|
+
* function rather than through a copy of the regex (subagent-fanout R-9 /
|
|
37
|
+
* R-P1-6). Two copies of `[^a-z0-9-]` would drift the first time either side
|
|
38
|
+
* gains a rule, and the user would see `UI` accepted by one and rewritten by the
|
|
39
|
+
* other.
|
|
40
|
+
*
|
|
41
|
+
* MAY RETURN THE EMPTY STRING. That is the difference from `slugLabel` below:
|
|
42
|
+
* an empty result is meaningful here ("this part cleans to nothing, drop it"),
|
|
43
|
+
* whereas a spec's label needs the positional fallback so a child is never
|
|
44
|
+
* nameless in the panel. The fallback is therefore the CALLER's decision, and
|
|
45
|
+
* this function stays a pure transformation.
|
|
46
|
+
*/
|
|
47
|
+
export declare function toSlug(raw: unknown): string;
|
|
33
48
|
/**
|
|
34
49
|
* Repair and bound a raw `subagents` array.
|
|
35
50
|
*
|
package/dist/team/normalize.js
CHANGED
|
@@ -33,16 +33,33 @@ function clamp(text, maxChars) {
|
|
|
33
33
|
return trimmed.length > maxChars ? trimmed.slice(0, maxChars) : trimmed;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* The slug rule for a subagent label, EXPORTED because a second caller needs the
|
|
37
|
+
* same bytes: `/subagent --parts api,UI,tests` cleans its parts through this
|
|
38
|
+
* function rather than through a copy of the regex (subagent-fanout R-9 /
|
|
39
|
+
* R-P1-6). Two copies of `[^a-z0-9-]` would drift the first time either side
|
|
40
|
+
* gains a rule, and the user would see `UI` accepted by one and rewritten by the
|
|
41
|
+
* other.
|
|
42
|
+
*
|
|
43
|
+
* MAY RETURN THE EMPTY STRING. That is the difference from `slugLabel` below:
|
|
44
|
+
* an empty result is meaningful here ("this part cleans to nothing, drop it"),
|
|
45
|
+
* whereas a spec's label needs the positional fallback so a child is never
|
|
46
|
+
* nameless in the panel. The fallback is therefore the CALLER's decision, and
|
|
47
|
+
* this function stays a pure transformation.
|
|
38
48
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
49
|
+
export function toSlug(raw) {
|
|
50
|
+
return asString(raw)
|
|
41
51
|
.trim()
|
|
42
52
|
.toLowerCase()
|
|
43
53
|
.replace(/[^a-z0-9-]+/g, '-')
|
|
44
54
|
.replace(/^-+|-+$/g, '')
|
|
45
55
|
.slice(0, TEAM_LIMITS.labelChars);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Slugify a label to `[a-z0-9-]{1,12}`. An empty result falls back to the
|
|
59
|
+
* positional default so a child is never nameless in the panel.
|
|
60
|
+
*/
|
|
61
|
+
function slugLabel(raw, index) {
|
|
62
|
+
const slug = toSlug(raw);
|
|
46
63
|
return slug.length > 0 ? slug : `a${index + 1}`;
|
|
47
64
|
}
|
|
48
65
|
/**
|
package/dist/team/prompt.d.ts
CHANGED
|
@@ -24,10 +24,13 @@ export interface TeamBlockParams {
|
|
|
24
24
|
* for on EVERY turn of every team-enabled session, so anything that does not
|
|
25
25
|
* change a delegation decision does not belong in it.
|
|
26
26
|
*
|
|
27
|
-
* "Two to four is
|
|
27
|
+
* "Two to four is usually enough" is where the requirement's "shu liang zi shi
|
|
28
28
|
* ying" (adaptive count) actually lives. The ceiling is mechanical and enforced
|
|
29
|
-
* elsewhere
|
|
30
|
-
* that informs it.
|
|
29
|
+
* elsewhere (`team.maxSubagents`, quoted in the first paragraph); the CHOICE is
|
|
30
|
+
* the model's, and this paragraph is the only thing that informs it. It names a
|
|
31
|
+
* USUAL size rather than the maximum on purpose: the default fan-out moved to 8
|
|
32
|
+
* (subagent-fanout D-1), and a block that told the model "eight is normal"
|
|
33
|
+
* would turn every two-part job into an eight-agent bill.
|
|
31
34
|
*/
|
|
32
35
|
export declare function buildTeamBlock(params: TeamBlockParams): string;
|
|
33
36
|
export interface SubagentBlockParams {
|
package/dist/team/prompt.js
CHANGED
|
@@ -20,10 +20,13 @@ export { TEAM_BLOCK_VERSION };
|
|
|
20
20
|
* for on EVERY turn of every team-enabled session, so anything that does not
|
|
21
21
|
* change a delegation decision does not belong in it.
|
|
22
22
|
*
|
|
23
|
-
* "Two to four is
|
|
23
|
+
* "Two to four is usually enough" is where the requirement's "shu liang zi shi
|
|
24
24
|
* ying" (adaptive count) actually lives. The ceiling is mechanical and enforced
|
|
25
|
-
* elsewhere
|
|
26
|
-
* that informs it.
|
|
25
|
+
* elsewhere (`team.maxSubagents`, quoted in the first paragraph); the CHOICE is
|
|
26
|
+
* the model's, and this paragraph is the only thing that informs it. It names a
|
|
27
|
+
* USUAL size rather than the maximum on purpose: the default fan-out moved to 8
|
|
28
|
+
* (subagent-fanout D-1), and a block that told the model "eight is normal"
|
|
29
|
+
* would turn every two-part job into an eight-agent bill.
|
|
27
30
|
*/
|
|
28
31
|
export function buildTeamBlock(params) {
|
|
29
32
|
const max = Math.max(1, Math.floor(params.maxSubagents));
|
|
@@ -39,8 +42,9 @@ export function buildTeamBlock(params) {
|
|
|
39
42
|
'',
|
|
40
43
|
'Do not delegate when: one part needs another part\'s result (do those in order',
|
|
41
44
|
'yourself); two parts would edit the same file; the whole job is a few edits.',
|
|
42
|
-
'One well-scoped subagent is better than four vague ones. Two to four is
|
|
43
|
-
'
|
|
45
|
+
'One well-scoped subagent is better than four vague ones. Two to four is',
|
|
46
|
+
'usually enough; go wider only when the parts are genuinely independent - a',
|
|
47
|
+
'narrow fan-out is cheaper and easier to read.',
|
|
44
48
|
'',
|
|
45
49
|
'Each subagent starts with no memory of this conversation, so its prompt must',
|
|
46
50
|
'carry everything it needs: the goal, the files or areas it owns, and what to',
|
package/dist/team/runtime.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type { TeamHumanQueue } from './human-queue.js';
|
|
|
36
36
|
import { type OverseerProvider } from './overseer.js';
|
|
37
37
|
import { type SubagentAgentFactory, type SubagentDeps } from './subagent.js';
|
|
38
38
|
import type { DispatchOutcome, SubagentSpec, TeamEventListener, TeamSnapshot } from './types.js';
|
|
39
|
+
import type { ToolGate } from '../tools/tool-gate.js';
|
|
39
40
|
export interface TeamRuntimeDeps {
|
|
40
41
|
/** Read LIVE: a settings-screen model or key edit must reach the next dispatch. */
|
|
41
42
|
getConfig: () => CliConfig;
|
|
@@ -64,6 +65,14 @@ export interface TeamRuntimeDeps {
|
|
|
64
65
|
sourceNames: string[];
|
|
65
66
|
}) => void;
|
|
66
67
|
humanQueue?: TeamHumanQueue;
|
|
68
|
+
/**
|
|
69
|
+
* The lead's caller-approval gate, one instance per child (headless-host-bridge
|
|
70
|
+
* section 2.3.7). Absent outside bridge mode, which is what keeps a dispatch's
|
|
71
|
+
* tool arrays identical to today's everywhere else.
|
|
72
|
+
*/
|
|
73
|
+
toolGateFor?: (label: string) => ToolGate;
|
|
74
|
+
/** The two-phase executor ceiling for a gated tool; see `gatedToolTimeouts`. */
|
|
75
|
+
decisionBudgetMs?: number;
|
|
67
76
|
/** Test seam (§8.1): a stub factory keeps the whole scheduler off the network. */
|
|
68
77
|
agentFactory?: SubagentAgentFactory;
|
|
69
78
|
/**
|
package/dist/team/runtime.js
CHANGED
|
@@ -465,6 +465,14 @@ export class TeamRuntime {
|
|
|
465
465
|
: {}),
|
|
466
466
|
confirmTools: config.confirmTools,
|
|
467
467
|
...(this.deps.humanQueue ? { humanQueue: this.deps.humanQueue } : {}),
|
|
468
|
+
// Site two of two, in the same shape as the policy and compaction lines
|
|
469
|
+
// around it: declaring it on `TeamRuntimeDeps` and forgetting this line
|
|
470
|
+
// type-checks and forwards nothing, and the failure is silent - every child
|
|
471
|
+
// would run unapproved while the caller believes it is approving.
|
|
472
|
+
...(this.deps.toolGateFor ? { toolGateFor: this.deps.toolGateFor } : {}),
|
|
473
|
+
...(this.deps.decisionBudgetMs !== undefined
|
|
474
|
+
? { decisionBudgetMs: this.deps.decisionBudgetMs }
|
|
475
|
+
: {}),
|
|
468
476
|
agentFactory: this.deps.agentFactory ?? defaultSubagentFactory,
|
|
469
477
|
...(this.deps.review ? { review: this.deps.review } : {}),
|
|
470
478
|
// Site two of two, and the omission is silent in the same way the policy
|
package/dist/team/subagent.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { ModelRole } from '../config/model-profiles.js';
|
|
|
14
14
|
import { type AgentConfig, type AgentEvent, type AgentTool, type Message, type ProviderRegistry, type SkillRegistry, type TokenUsage, type ToolPolicyDecision, type ToolPolicyVerdict } from '@aragon-agent/core';
|
|
15
15
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
16
16
|
import type { ChildContextManagerProvider } from '../compaction/child.js';
|
|
17
|
+
import type { ToolGate } from '../tools/tool-gate.js';
|
|
17
18
|
import type { CliConfig } from '../config/schema.js';
|
|
18
19
|
import type { ToolPermission } from '../exec/permission.js';
|
|
19
20
|
import type { ChildReviewProvider } from './child-reviewer.js';
|
|
@@ -123,6 +124,20 @@ export interface SubagentDeps {
|
|
|
123
124
|
* `CompactionWiring.childFactory()`, which returns `null` when the key is off.
|
|
124
125
|
*/
|
|
125
126
|
contextManagerFor?: ChildContextManagerProvider;
|
|
127
|
+
/**
|
|
128
|
+
* The caller's approval gate for THIS child (headless-host-bridge section 2.3.7).
|
|
129
|
+
*
|
|
130
|
+
* A FACTORY, NOT A GATE: every child gets its own instance because the scope -
|
|
131
|
+
* `{id,label,depth}` - is what tells the caller which subagent is asking, and one
|
|
132
|
+
* shared instance would report every child as the lead. The pending table behind
|
|
133
|
+
* it IS shared, so the capacity ceiling counts a whole fan-out together.
|
|
134
|
+
*
|
|
135
|
+
* ABSENT EVERYWHERE BUT BRIDGE MODE, which is what keeps a child's tool array
|
|
136
|
+
* identical to today's for `-p`, the TUI and every non-bridged dispatch.
|
|
137
|
+
*/
|
|
138
|
+
toolGateFor?: (label: string) => ToolGate;
|
|
139
|
+
/** Added to each gated tool's executor timeout; see `gatedToolTimeouts`. */
|
|
140
|
+
decisionBudgetMs?: number;
|
|
126
141
|
}
|
|
127
142
|
export interface SubagentHooks {
|
|
128
143
|
/** Coalesced at the source (§5.2); phase transitions always pass. */
|
package/dist/team/subagent.js
CHANGED
|
@@ -14,7 +14,7 @@ import { Agent, createSkillFindTool, } from '@aragon-agent/core';
|
|
|
14
14
|
import { formatStreamError } from '../agent/reducer.js';
|
|
15
15
|
import { buildSystemPrompt } from '../agent/system-prompt.js';
|
|
16
16
|
import { buildFastBlock } from '../fast/prompt.js';
|
|
17
|
-
import { createBuiltinTools } from '../tools/index.js';
|
|
17
|
+
import { createBuiltinTools, gatedToolTimeouts, } from '../tools/index.js';
|
|
18
18
|
import { describeToolActivity, pickActivityArgs, sanitizeActivity } from './activity.js';
|
|
19
19
|
import { TEAM_LIMITS, TEAM_SUBAGENT_TOOL_NAMES } from './limits.js';
|
|
20
20
|
import { appendTimeline } from './timeline.js';
|
|
@@ -24,6 +24,26 @@ import { makeTeamSend, makeTeamWait, withMailboxTail } from './comm-tools.js';
|
|
|
24
24
|
import { buildSubagentBlock } from './prompt.js';
|
|
25
25
|
/** The default factory: a real `Agent` from `@aragon-agent/core`. */
|
|
26
26
|
export const defaultSubagentFactory = (config) => new Agent(config);
|
|
27
|
+
/**
|
|
28
|
+
* Hold a child's idle watchdog across a caller wait.
|
|
29
|
+
*
|
|
30
|
+
* The child's watchdog is its own (that is the point of a separate ceiling), so a
|
|
31
|
+
* four-minute approval would otherwise abort the child and take the dispatch with
|
|
32
|
+
* it - R-1, one level down. `finally` is what makes "paused" never outlive the
|
|
33
|
+
* wait, including on an abort.
|
|
34
|
+
*/
|
|
35
|
+
function pauseChildWatchdog(getAgent) {
|
|
36
|
+
return async (fn) => {
|
|
37
|
+
const agent = getAgent();
|
|
38
|
+
agent?.pauseIdleWatchdog();
|
|
39
|
+
try {
|
|
40
|
+
return await fn();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
agent?.resumeIdleWatchdog();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
27
47
|
/** The final assistant text of a turn, or `''` when the turn was tool calls only. */
|
|
28
48
|
function assistantText(message) {
|
|
29
49
|
if (!message || !Array.isArray(message.content))
|
|
@@ -59,6 +79,21 @@ export function buildSubagentTools(spec, deps, getAgent) {
|
|
|
59
79
|
const tools = createBuiltinTools({
|
|
60
80
|
getCwd: deps.getCwd,
|
|
61
81
|
confirmTools: deps.confirmTools,
|
|
82
|
+
// THE CHILD ASKS THE CALLER TOO (section 2.3.7). Without this the approval
|
|
83
|
+
// gate would hold for the lead and be bypassed by one `task` call - the hole
|
|
84
|
+
// the `--deny-tool` pass-through above records, one feature later and with a
|
|
85
|
+
// human on the other end of it who believes they are approving everything.
|
|
86
|
+
//
|
|
87
|
+
// The watchdog pause is the CHILD'S OWN, never `TeamHumanQueue`'s: that queue
|
|
88
|
+
// is `--confirm`'s, and nesting a second pause is exactly what
|
|
89
|
+
// `withPausedWatchdog` forbids. `getAgent()` is the same live handle the queue
|
|
90
|
+
// and `team_wait` already use.
|
|
91
|
+
...(deps.toolGateFor
|
|
92
|
+
? {
|
|
93
|
+
toolGate: deps.toolGateFor(spec.label),
|
|
94
|
+
withToolWait: pauseChildWatchdog(getAgent),
|
|
95
|
+
}
|
|
96
|
+
: {}),
|
|
62
97
|
// Queued through the ONE human slot, with this child's watchdog paused for
|
|
63
98
|
// the whole queue wait (§3.11 / I-4 / P0-4).
|
|
64
99
|
...(deps.confirmTools && deps.humanQueue
|
|
@@ -253,6 +288,15 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
253
288
|
...(deps.config.maxTokens !== undefined ? { maxTokens: deps.config.maxTokens } : {}),
|
|
254
289
|
timeouts: {
|
|
255
290
|
toolTimeout: deps.config.toolTimeoutMs,
|
|
291
|
+
// THE SAME TWO PHASES THE LEAD GETS (section 2.3.4). A child that asks the
|
|
292
|
+
// caller and waits is billed for the wait out of its tool's budget unless
|
|
293
|
+
// this is here, and the symptom would be "the approved command was killed
|
|
294
|
+
// mid-run" only inside a fan-out.
|
|
295
|
+
...(deps.toolGateFor && deps.decisionBudgetMs !== undefined
|
|
296
|
+
? {
|
|
297
|
+
toolTimeoutOverrides: gatedToolTimeouts(deps.config.toolTimeoutMs, deps.decisionBudgetMs),
|
|
298
|
+
}
|
|
299
|
+
: {}),
|
|
256
300
|
// A child keeps its OWN idle watchdog, and that is a feature: a wedged
|
|
257
301
|
// child is aborted by its own ceiling without taking the dispatch with it
|
|
258
302
|
// (§3.8). The two places a child legitimately blocks on something other
|
package/dist/team/task-tool.js
CHANGED
|
@@ -49,11 +49,11 @@ const TASK_DESCRIPTION = 'Run several independent subagents in parallel and get
|
|
|
49
49
|
'result (do those yourself, in order), when two parts would edit the same file, or for ' +
|
|
50
50
|
'a change small enough to just make. Subagents cannot dispatch further subagents and ' +
|
|
51
51
|
'cannot ask the user anything. Give each a description of at most ' +
|
|
52
|
-
`${TEAM_LIMITS.descriptionChars} characters and a complete prompt.
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'or
|
|
56
|
-
'records every intervention.';
|
|
52
|
+
`${TEAM_LIMITS.descriptionChars} characters and a complete prompt. The current fan-out ` +
|
|
53
|
+
'maximum and concurrency are stated in <team_mode>; extra entries beyond it are ' +
|
|
54
|
+
'dropped, so keep within it. A supervisor watches the fan-out: a child that ' +
|
|
55
|
+
'stalls or runs very long may be nudged with guidance or replaced by a fresh ' +
|
|
56
|
+
'attempt, and the report\'s Supervisor section records every intervention.';
|
|
57
57
|
export function createTaskTool(deps) {
|
|
58
58
|
return {
|
|
59
59
|
name: 'task',
|
|
@@ -64,8 +64,12 @@ export function createTaskTool(deps) {
|
|
|
64
64
|
properties: {
|
|
65
65
|
subagents: {
|
|
66
66
|
type: 'array',
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// NO NUMBER HERE (subagent-fanout D-14): the width is a user setting
|
|
68
|
+
// now (`team.maxSubagents`, 8 by default, 10 at most), so a literal
|
|
69
|
+
// "2-5" in the schema would contradict the block the model reads in
|
|
70
|
+
// the very same request. `<team_mode>` is the single authority.
|
|
71
|
+
description: 'Independent subagents to run in parallel in ONE call. See <team_mode> for ' +
|
|
72
|
+
'the current maximum and concurrency; extras are dropped.',
|
|
69
73
|
items: {
|
|
70
74
|
type: 'object',
|
|
71
75
|
properties: {
|
|
@@ -19,12 +19,22 @@
|
|
|
19
19
|
* inheriting the pipe — `Start-Process`, `&`, `nohup`, and every
|
|
20
20
|
* `npm run dev` that re-execs — keeps that pipe open after the shell is
|
|
21
21
|
* long gone. The shell exited; the promise did not.
|
|
22
|
+
*
|
|
22
23
|
* 2. `stdio` defaulted to `'pipe'` for stdin, so a command that PROMPTS —
|
|
23
24
|
* `npm init`, a package manager asking to install something, `sudo`, a git
|
|
24
25
|
* credential prompt — blocked on a read that could never be satisfied.
|
|
25
26
|
* 3. An abort killed the tree and then went on waiting for a `'close'` that a
|
|
26
27
|
* detached grandchild was still holding.
|
|
27
28
|
*
|
|
29
|
+
* WHY THE DRAIN WINDOW IS A BOUND AND NOT A VERDICT. The first build called
|
|
30
|
+
* anything still unsettled when that window closed "a held pipe" and printed
|
|
31
|
+
* the `'exit'` drain note on the strength of it. Under load the two disagree:
|
|
32
|
+
* the release gate caught a command that had printed two lines and exited
|
|
33
|
+
* immediately being reported as holding its output stream, because the timers
|
|
34
|
+
* phase came round before libuv delivered the close notification. The note is a
|
|
35
|
+
* claim about STATE, so it is decided by asking the streams
|
|
36
|
+
* (`proc/output-streams.ts`) instead of guessing from a clock.
|
|
37
|
+
*
|
|
28
38
|
* The happy path is BYTE-IDENTICAL to the pre-change build: `$ cmd\n<output>\n
|
|
29
39
|
* [exit code N]`, same truncation, same footer. That is what keeps the existing
|
|
30
40
|
* `bash` assertions meaningful rather than rewritten.
|