@aragon-agent/cli 0.6.39 → 0.6.42
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 +109 -0
- package/README.md +126 -20
- package/dist/agent/controller.d.ts +139 -14
- package/dist/agent/controller.js +299 -40
- 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 +29 -1
- package/dist/agent/reducer.js +34 -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 +33 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +21 -5
- package/dist/commands/builtins.js +28 -185
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +30 -0
- package/dist/commands/subagent-command.js +39 -0
- package/dist/commands/team-command.d.ts +49 -0
- package/dist/commands/team-command.js +259 -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/load.js +50 -3
- package/dist/config/model-kits.d.ts +2 -1
- package/dist/config/schema.d.ts +82 -6
- package/dist/config/schema.js +107 -9
- package/dist/config/store.js +10 -4
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- 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/dispatch-gate.d.ts +86 -0
- package/dist/team/dispatch-gate.js +75 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/forced-mode.d.ts +116 -0
- package/dist/team/forced-mode.js +156 -0
- package/dist/team/forced-prompt.d.ts +48 -0
- package/dist/team/forced-prompt.js +84 -0
- package/dist/team/limits.d.ts +61 -1
- package/dist/team/limits.js +69 -1
- package/dist/team/model.d.ts +5 -5
- package/dist/team/model.js +5 -5
- 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/task-tool.js +11 -7
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/ui/App.js +130 -11
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +2 -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/layout/status-layout.d.ts +27 -0
- package/dist/ui/layout/status-layout.js +27 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -4
- package/dist/ui/overlays/SettingsScreen.d.ts +14 -0
- package/dist/ui/overlays/SettingsScreen.js +39 -5
- package/dist/ui/overlays/TeamFanoutPage.d.ts +51 -0
- package/dist/ui/overlays/TeamFanoutPage.js +90 -0
- package/dist/ui/overlays/kit-settings-state.js +3 -2
- package/dist/ui/overlays/model-profile-state.d.ts +20 -2
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +106 -0
- package/dist/ui/overlays/team-fanout-state.js +155 -0
- package/package.json +81 -81
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,44 @@ 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";
|
|
269
|
+
/**
|
|
270
|
+
* The version string on `<forced_team_mode>`'s first line.
|
|
271
|
+
*
|
|
272
|
+
* The block's words are a CONTRACT with the model, so a behaviour report ("it
|
|
273
|
+
* still did the work itself") has to be tieable to one revision. Kept beside
|
|
274
|
+
* `TEAM_BLOCK_VERSION` rather than reusing it: the two blocks answer different
|
|
275
|
+
* questions ("can dispatch" versus "must dispatch") and a shared number would
|
|
276
|
+
* name neither revision.
|
|
277
|
+
*/
|
|
278
|
+
export declare const FORCED_TEAM_BLOCK_VERSION = "v1-2026-10";
|
|
279
|
+
/**
|
|
280
|
+
* Length ceiling of the `<forced_team_mode>` text.
|
|
281
|
+
*
|
|
282
|
+
* THE FIRST BLOCK LENGTH IN THIS SUBSYSTEM THAT A TEST ASSERTS: the `<team_mode>`
|
|
283
|
+
* block's 1200 is a comment in `team/prompt.ts`, and unifying the two is a
|
|
284
|
+
* separate small refactor (deliberately out of scope here). Looser than 1200
|
|
285
|
+
* because this block is only ever paid for while the session is ARMED, and it
|
|
286
|
+
* carries three layers of instruction rather than one.
|
|
287
|
+
*/
|
|
288
|
+
export declare const FORCED_TEAM_BLOCK_MAX_CHARS = 1400;
|
|
289
|
+
/**
|
|
290
|
+
* Fallback nudges per user turn. ONE, and not configurable.
|
|
291
|
+
*
|
|
292
|
+
* The gate exists because the first two layers (the directive and the prompt
|
|
293
|
+
* block) both depend on the model cooperating. A second nudge would double the
|
|
294
|
+
* worst case of a model that is simply not going to dispatch - at N children per
|
|
295
|
+
* attempt - and D-8's whole argument is that the mechanical layer must be
|
|
296
|
+
* bounded by something other than the model's willingness.
|
|
297
|
+
*/
|
|
298
|
+
export declare const FORCED_NUDGE_LIMIT = 1;
|
|
299
|
+
/**
|
|
300
|
+
* Grace window before the fallback nudge fires, in milliseconds.
|
|
301
|
+
*
|
|
302
|
+
* SAME ORDER AS THE TODO FOLLOW-THROUGH GRACE, for a structural reason rather
|
|
303
|
+
* than a stylistic one: both continuations share ONE `followTimer` in `App`
|
|
304
|
+
* (I-9), so "at most one armed continuation" is only explicable while the two
|
|
305
|
+
* numbers stay in the same order of magnitude. **Changing todo's grace means
|
|
306
|
+
* re-reading this constant.**
|
|
307
|
+
*/
|
|
308
|
+
export declare const FORCED_NUDGE_GRACE_MS = 3000;
|
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,52 @@ 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';
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Forced team mode (`/team <brief>`, team-command-forced-dispatch §6.5).
|
|
285
|
+
//
|
|
286
|
+
// FOUR STRUCTURAL NUMBERS, here rather than in the modules that read them for
|
|
287
|
+
// the reason this file's header records: none of them is a user preference, and
|
|
288
|
+
// a `config.json` key for any of them would make "the model ignored the
|
|
289
|
+
// instruction" tunable by the person the instruction exists to protect.
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
/**
|
|
292
|
+
* The version string on `<forced_team_mode>`'s first line.
|
|
293
|
+
*
|
|
294
|
+
* The block's words are a CONTRACT with the model, so a behaviour report ("it
|
|
295
|
+
* still did the work itself") has to be tieable to one revision. Kept beside
|
|
296
|
+
* `TEAM_BLOCK_VERSION` rather than reusing it: the two blocks answer different
|
|
297
|
+
* questions ("can dispatch" versus "must dispatch") and a shared number would
|
|
298
|
+
* name neither revision.
|
|
299
|
+
*/
|
|
300
|
+
export const FORCED_TEAM_BLOCK_VERSION = 'v1-2026-10';
|
|
301
|
+
/**
|
|
302
|
+
* Length ceiling of the `<forced_team_mode>` text.
|
|
303
|
+
*
|
|
304
|
+
* THE FIRST BLOCK LENGTH IN THIS SUBSYSTEM THAT A TEST ASSERTS: the `<team_mode>`
|
|
305
|
+
* block's 1200 is a comment in `team/prompt.ts`, and unifying the two is a
|
|
306
|
+
* separate small refactor (deliberately out of scope here). Looser than 1200
|
|
307
|
+
* because this block is only ever paid for while the session is ARMED, and it
|
|
308
|
+
* carries three layers of instruction rather than one.
|
|
309
|
+
*/
|
|
310
|
+
export const FORCED_TEAM_BLOCK_MAX_CHARS = 1400;
|
|
311
|
+
/**
|
|
312
|
+
* Fallback nudges per user turn. ONE, and not configurable.
|
|
313
|
+
*
|
|
314
|
+
* The gate exists because the first two layers (the directive and the prompt
|
|
315
|
+
* block) both depend on the model cooperating. A second nudge would double the
|
|
316
|
+
* worst case of a model that is simply not going to dispatch - at N children per
|
|
317
|
+
* attempt - and D-8's whole argument is that the mechanical layer must be
|
|
318
|
+
* bounded by something other than the model's willingness.
|
|
319
|
+
*/
|
|
320
|
+
export const FORCED_NUDGE_LIMIT = 1;
|
|
321
|
+
/**
|
|
322
|
+
* Grace window before the fallback nudge fires, in milliseconds.
|
|
323
|
+
*
|
|
324
|
+
* SAME ORDER AS THE TODO FOLLOW-THROUGH GRACE, for a structural reason rather
|
|
325
|
+
* than a stylistic one: both continuations share ONE `followTimer` in `App`
|
|
326
|
+
* (I-9), so "at most one armed continuation" is only explicable while the two
|
|
327
|
+
* numbers stay in the same order of magnitude. **Changing todo's grace means
|
|
328
|
+
* re-reading this constant.**
|
|
329
|
+
*/
|
|
330
|
+
export const FORCED_NUDGE_GRACE_MS = 3000;
|
package/dist/team/model.d.ts
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope.
|
|
6
6
|
*
|
|
7
7
|
* EVERY CONSUMER READS THE CHILD'S MODEL THROUGH THIS FUNCTION AND NOWHERE
|
|
8
|
-
* ELSE — `createSubagent`, the `task` report header
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* ELSE — `createSubagent`, the `task` report header and the settings root row —
|
|
9
|
+
* following the `resolveFastTier` precedent one directory over: three call
|
|
10
|
+
* sites each recomputing "which model do children run on?" from raw
|
|
11
11
|
* config keys is exactly how the report ends up naming a model the children
|
|
12
12
|
* silently fell back from.
|
|
13
13
|
*
|
|
14
14
|
* IT IS RE-RUN ON EVERY DISPATCH AND EVERY RENDER, NOT ONCE (rule 8 / RV-3 of
|
|
15
15
|
* the fast tier, inherited here): `team.model`, the fast tier and the profile
|
|
16
|
-
* library are all LIVE keys a settings save
|
|
17
|
-
* rewrite mid-session. A resolution captured at construction would be a
|
|
16
|
+
* library are all LIVE keys a settings save (the `Subagent model` row), `/fast
|
|
17
|
+
* off` or a kit can rewrite mid-session. A resolution captured at construction would be a
|
|
18
18
|
* resolution describing a session that no longer exists.
|
|
19
19
|
*
|
|
20
20
|
* A CHILD IS NEVER LEFT WITHOUT A MODEL (D-4). Every failure mode — fast tier
|
package/dist/team/model.js
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope.
|
|
6
6
|
*
|
|
7
7
|
* EVERY CONSUMER READS THE CHILD'S MODEL THROUGH THIS FUNCTION AND NOWHERE
|
|
8
|
-
* ELSE — `createSubagent`, the `task` report header
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* ELSE — `createSubagent`, the `task` report header and the settings root row —
|
|
9
|
+
* following the `resolveFastTier` precedent one directory over: three call
|
|
10
|
+
* sites each recomputing "which model do children run on?" from raw
|
|
11
11
|
* config keys is exactly how the report ends up naming a model the children
|
|
12
12
|
* silently fell back from.
|
|
13
13
|
*
|
|
14
14
|
* IT IS RE-RUN ON EVERY DISPATCH AND EVERY RENDER, NOT ONCE (rule 8 / RV-3 of
|
|
15
15
|
* the fast tier, inherited here): `team.model`, the fast tier and the profile
|
|
16
|
-
* library are all LIVE keys a settings save
|
|
17
|
-
* rewrite mid-session. A resolution captured at construction would be a
|
|
16
|
+
* library are all LIVE keys a settings save (the `Subagent model` row), `/fast
|
|
17
|
+
* off` or a kit can rewrite mid-session. A resolution captured at construction would be a
|
|
18
18
|
* resolution describing a session that no longer exists.
|
|
19
19
|
*
|
|
20
20
|
* A CHILD IS NEVER LEFT WITHOUT A MODEL (D-4). Every failure mode — fast tier
|
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/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: {
|
|
@@ -137,6 +137,23 @@ export declare const PLAN_LIMITS: {
|
|
|
137
137
|
readonly maxRisks: 10;
|
|
138
138
|
readonly maxOpenQuestions: 10;
|
|
139
139
|
};
|
|
140
|
+
/**
|
|
141
|
+
* The STORAGE bound of the `ask_user` de-duplication guard — NOT a refusal
|
|
142
|
+
* condition, and the distinction is the whole point.
|
|
143
|
+
*
|
|
144
|
+
* Storage is memory hygiene: a turn that asks a thousand DISTINCT question sets
|
|
145
|
+
* is pathological, but it is still the user's turn, and Esc interrupts it. The
|
|
146
|
+
* refusal rule is semantic: "never ask the same answered questions twice". Folding
|
|
147
|
+
* the bound into the refusal rule would restore the round cap this feature
|
|
148
|
+
* deletes, with a different number written on it.
|
|
149
|
+
*
|
|
150
|
+
* Owned here rather than in `plan-tools.ts` (the caller) or `controller.ts` (the
|
|
151
|
+
* state holder) because this file is already the single authority on the
|
|
152
|
+
* structural limits of human input, beside `QUESTION_LIMITS` / `PLAN_LIMITS`.
|
|
153
|
+
*/
|
|
154
|
+
export declare const ASK_GUARD_LIMITS: {
|
|
155
|
+
readonly maxRemembered: 128;
|
|
156
|
+
};
|
|
140
157
|
/**
|
|
141
158
|
* Trim to `max` characters, preferring a word boundary within the last
|
|
142
159
|
* `WORD_BOUNDARY_WINDOW` characters. Cutting a sentence mid-word reads as
|
|
@@ -165,13 +182,29 @@ export declare function normalizePlan(raw: unknown): NormalizedPlan | null;
|
|
|
165
182
|
export interface AnswersResultShape {
|
|
166
183
|
answers: Answer[];
|
|
167
184
|
cancelled: boolean;
|
|
168
|
-
roundsUsed: number;
|
|
169
|
-
roundsRemaining: number;
|
|
170
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* A question SET's identity: the question texts, trimmed, lowercased, whitespace
|
|
188
|
+
* collapsed, sorted, joined.
|
|
189
|
+
*
|
|
190
|
+
* `id` and `header` are excluded on purpose — a model that re-asks the same thing
|
|
191
|
+
* under a new id is asking the same thing, and the ids are the model's own
|
|
192
|
+
* bookkeeping rather than part of what the user reads.
|
|
193
|
+
*
|
|
194
|
+
* The fingerprint is stable because `normalizeQuestions` already ran on the TOOL
|
|
195
|
+
* side: header truncation and option trimming cannot make the same question look
|
|
196
|
+
* different here. Worst case is 5 questions x 300 chars = 1,500 chars.
|
|
197
|
+
*/
|
|
198
|
+
export declare function questionFingerprint(questions: NormalizedQuestion[]): string;
|
|
171
199
|
/**
|
|
172
200
|
* Render the `ask_user` result: one human-readable line so the transcript
|
|
173
201
|
* preview is legible without parsing, then the JSON the model reads.
|
|
174
202
|
*
|
|
203
|
+
* CARRIES NO ROUND COUNTER, deliberately (D-4). The model used to be told
|
|
204
|
+
* `roundsUsed` / `roundsRemaining` on every call, which is a per-turn reminder
|
|
205
|
+
* that it is running out — and an agent that reads "you have 1 round left"
|
|
206
|
+
* behaves like one. There is no limit any more, so there is nothing to report.
|
|
207
|
+
*
|
|
175
208
|
* `asked` is passed separately rather than derived from `answers.length`
|
|
176
209
|
* because a cancelled wizard returns fewer answers than it asked for, and
|
|
177
210
|
* "answered 1 of 3" is the whole information content of that line.
|
|
@@ -51,6 +51,21 @@ export const PLAN_LIMITS = {
|
|
|
51
51
|
maxRisks: 10,
|
|
52
52
|
maxOpenQuestions: 10,
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* The STORAGE bound of the `ask_user` de-duplication guard — NOT a refusal
|
|
56
|
+
* condition, and the distinction is the whole point.
|
|
57
|
+
*
|
|
58
|
+
* Storage is memory hygiene: a turn that asks a thousand DISTINCT question sets
|
|
59
|
+
* is pathological, but it is still the user's turn, and Esc interrupts it. The
|
|
60
|
+
* refusal rule is semantic: "never ask the same answered questions twice". Folding
|
|
61
|
+
* the bound into the refusal rule would restore the round cap this feature
|
|
62
|
+
* deletes, with a different number written on it.
|
|
63
|
+
*
|
|
64
|
+
* Owned here rather than in `plan-tools.ts` (the caller) or `controller.ts` (the
|
|
65
|
+
* state holder) because this file is already the single authority on the
|
|
66
|
+
* structural limits of human input, beside `QUESTION_LIMITS` / `PLAN_LIMITS`.
|
|
67
|
+
*/
|
|
68
|
+
export const ASK_GUARD_LIMITS = { maxRemembered: 128 };
|
|
54
69
|
/** How far back a truncation will look for a space before cutting hard. */
|
|
55
70
|
const WORD_BOUNDARY_WINDOW = 12;
|
|
56
71
|
// ---------------------------------------------------------------------------
|
|
@@ -208,10 +223,33 @@ export function normalizePlan(raw) {
|
|
|
208
223
|
openQuestions: nonEmptyStrings(src.openQuestions, PLAN_LIMITS.maxOpenQuestions),
|
|
209
224
|
};
|
|
210
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* A question SET's identity: the question texts, trimmed, lowercased, whitespace
|
|
228
|
+
* collapsed, sorted, joined.
|
|
229
|
+
*
|
|
230
|
+
* `id` and `header` are excluded on purpose — a model that re-asks the same thing
|
|
231
|
+
* under a new id is asking the same thing, and the ids are the model's own
|
|
232
|
+
* bookkeeping rather than part of what the user reads.
|
|
233
|
+
*
|
|
234
|
+
* The fingerprint is stable because `normalizeQuestions` already ran on the TOOL
|
|
235
|
+
* side: header truncation and option trimming cannot make the same question look
|
|
236
|
+
* different here. Worst case is 5 questions x 300 chars = 1,500 chars.
|
|
237
|
+
*/
|
|
238
|
+
export function questionFingerprint(questions) {
|
|
239
|
+
return questions
|
|
240
|
+
.map((q) => q.question.trim().toLowerCase().replace(/\s+/g, ' '))
|
|
241
|
+
.sort()
|
|
242
|
+
.join(' | ');
|
|
243
|
+
}
|
|
211
244
|
/**
|
|
212
245
|
* Render the `ask_user` result: one human-readable line so the transcript
|
|
213
246
|
* preview is legible without parsing, then the JSON the model reads.
|
|
214
247
|
*
|
|
248
|
+
* CARRIES NO ROUND COUNTER, deliberately (D-4). The model used to be told
|
|
249
|
+
* `roundsUsed` / `roundsRemaining` on every call, which is a per-turn reminder
|
|
250
|
+
* that it is running out — and an agent that reads "you have 1 round left"
|
|
251
|
+
* behaves like one. There is no limit any more, so there is nothing to report.
|
|
252
|
+
*
|
|
215
253
|
* `asked` is passed separately rather than derived from `answers.length`
|
|
216
254
|
* because a cancelled wizard returns fewer answers than it asked for, and
|
|
217
255
|
* "answered 1 of 3" is the whole information content of that line.
|
|
@@ -17,22 +17,30 @@
|
|
|
17
17
|
* selected by whether an optional install step succeeded.
|
|
18
18
|
*/
|
|
19
19
|
import { type AgentTool } from '@aragon-agent/core';
|
|
20
|
-
import { type HumanInputGate } from './human-input.js';
|
|
21
|
-
/** The outcome of trying to spend one `ask_user` round. */
|
|
22
|
-
export interface AskRoundTicket {
|
|
23
|
-
ok: boolean;
|
|
24
|
-
used: number;
|
|
25
|
-
remaining: number;
|
|
26
|
-
max: number;
|
|
27
|
-
}
|
|
20
|
+
import { type HumanInputGate, type NormalizedQuestion } from './human-input.js';
|
|
28
21
|
export interface PlanToolsOptions {
|
|
29
22
|
gate: HumanInputGate;
|
|
30
23
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
24
|
+
* Read-only: has this exact question SET already been ANSWERED in this turn?
|
|
25
|
+
*
|
|
26
|
+
* Called BEFORE `gate.request`, and it must have no side effects — that is why
|
|
27
|
+
* the guard is two callbacks rather than one. A single read-and-write function
|
|
28
|
+
* can only be placed before the question (which records an answer nobody gave)
|
|
29
|
+
* or after it (which cannot prevent a repeat). Both are wrong, and the second
|
|
30
|
+
* one silently turns "cancelled rounds stay askable" into a lie.
|
|
31
|
+
*/
|
|
32
|
+
hasAnsweredQuestions: (questions: NormalizedQuestion[]) => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Write-only: remember that this question SET was answered. Called AFTER
|
|
35
|
+
* `gate.request`, and NEVER when the round was cancelled.
|
|
36
|
+
*
|
|
37
|
+
* Cancellation is not consent. `Esc` may be a mis-touch, and turning a
|
|
38
|
+
* mis-touch into "this question may never be asked again" forces the model to
|
|
39
|
+
* guess exactly when it most needs to ask — the behaviour this feature exists
|
|
40
|
+
* to remove. The cheap failure (asking once more) is preferable to the
|
|
41
|
+
* expensive one (information lost for the rest of the turn).
|
|
34
42
|
*/
|
|
35
|
-
|
|
43
|
+
noteAnsweredQuestions: (questions: NormalizedQuestion[]) => void;
|
|
36
44
|
/**
|
|
37
45
|
* Run `fn` with the idle watchdog paused. `try/finally` inside, so a throw in
|
|
38
46
|
* the overlay path cannot leave the watchdog disarmed for the rest of the run.
|
package/dist/tools/plan-tools.js
CHANGED
|
@@ -25,18 +25,25 @@ const NO_INTERACTIVE_USER = 'No interactive user is attached (-p/--print mode).
|
|
|
25
25
|
'state your assumptions explicitly and continue.';
|
|
26
26
|
const NO_INTERACTIVE_REVIEWER = 'No interactive reviewer is attached. Emit the full plan as your final answer ' +
|
|
27
27
|
'in markdown instead.';
|
|
28
|
-
function roundCapRefusal(used) {
|
|
29
|
-
return (`You have already asked ${used} rounds of questions (the configured maximum). ` +
|
|
30
|
-
'Proceed with your best assumptions and state them in the plan.');
|
|
31
|
-
}
|
|
32
28
|
// ---------------------------------------------------------------------------
|
|
33
29
|
// ask_user
|
|
34
30
|
// ---------------------------------------------------------------------------
|
|
35
31
|
const ASK_USER_DESCRIPTION = 'Ask the user 1-5 multiple-choice questions when a decision would change the work. ' +
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
32
|
+
'There is no limit on how many rounds you ask: call this as many times as the work ' +
|
|
33
|
+
'needs, one batch at a time. Give each question a stable id, a header of at most 12 ' +
|
|
34
|
+
'characters, and 2-4 options; exactly one option must set recommended:true, with the ' +
|
|
35
|
+
'reason in its description. Extra questions or options beyond the per-call limits are ' +
|
|
36
|
+
'dropped, so keep each round inside them. Do not ask anything you could answer by ' +
|
|
37
|
+
'reading the workspace, and never re-ask a question this turn already answered.';
|
|
38
|
+
/**
|
|
39
|
+
* The de-duplication notice, as a NON-error result.
|
|
40
|
+
*
|
|
41
|
+
* `textResult`, not `errorResult`: a tool failure invites the model to retry the
|
|
42
|
+
* same call, and its only possible outcome is the same refusal. This is the same
|
|
43
|
+
* reasoning as the cancellation path below.
|
|
44
|
+
*/
|
|
45
|
+
const DUPLICATE_ROUND_RESULT = 'You already asked this exact set of questions in this turn and the user answered. ' +
|
|
46
|
+
'Use those answers; if you need something else, ask a different question.';
|
|
40
47
|
function makeAskUser(opts) {
|
|
41
48
|
return {
|
|
42
49
|
name: 'ask_user',
|
|
@@ -91,9 +98,10 @@ function makeAskUser(opts) {
|
|
|
91
98
|
if (questions.length === 0) {
|
|
92
99
|
return errorResult('No usable questions: each needs 2-4 distinct options.');
|
|
93
100
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
// BEFORE the wait: a set the user has already answered must not open an
|
|
102
|
+
// overlay a second time. Read-only — nothing is recorded here.
|
|
103
|
+
if (opts.hasAnsweredQuestions(questions))
|
|
104
|
+
return textResult(DUPLICATE_ROUND_RESULT);
|
|
97
105
|
const response = await opts.withHumanWait(() => opts.gate.request({ kind: 'questions', questions }, ctx.signal));
|
|
98
106
|
// `null` covers cancel, unmount, abort AND timeout. From the model's point
|
|
99
107
|
// of view an unanswered question and an abandoned one are the same
|
|
@@ -102,12 +110,11 @@ function makeAskUser(opts) {
|
|
|
102
110
|
// invites a retry loop.
|
|
103
111
|
const cancelled = response === null || response.kind !== 'answers' || response.cancelled;
|
|
104
112
|
const answers = response !== null && response.kind === 'answers' ? response.answers : [];
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}, questions.length));
|
|
113
|
+
// AFTER the answer, and only for a real answer. A cancelled round leaves
|
|
114
|
+
// the set askable: mis-touch must not cost the user information.
|
|
115
|
+
if (!cancelled)
|
|
116
|
+
opts.noteAnsweredQuestions(questions);
|
|
117
|
+
return textResult(formatAnswersResult({ answers, cancelled }, questions.length));
|
|
111
118
|
},
|
|
112
119
|
};
|
|
113
120
|
}
|