@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,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/subagent` — force the next turn to fan out into a parallel team
|
|
3
|
+
* (subagent-fanout 3.5 / 5.2).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/commands/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THIS COMMAND IS AND IS NOT (D-8 / D-9 / D-10). It does not dispatch: it
|
|
8
|
+
* parses the user's intent, refuses honestly when forcing cannot work, prints
|
|
9
|
+
* ONE line saying what will happen next, and submits a normal user turn. The
|
|
10
|
+
* lead then calls `task` on the one dispatch path that already exists, so Esc,
|
|
11
|
+
* the watchdog pause, the report, spend accounting and the concurrency lock all
|
|
12
|
+
* keep working without a second implementation. The consequence the user must
|
|
13
|
+
* be told about is that forcing is INSTRUCTION-LEVEL: the notice says "the next
|
|
14
|
+
* turn dispatches them", never "N subagents started".
|
|
15
|
+
*
|
|
16
|
+
* THE GUARD ORDER IS A CONTRACT (R-P1-10 / R-P1-11): status -> capability ->
|
|
17
|
+
* parse. A `--no-team` session typing `/subagent -n x` is told the session has
|
|
18
|
+
* no team capability, because a syntax hint would send them to fix the one thing
|
|
19
|
+
* that is not wrong.
|
|
20
|
+
*/
|
|
21
|
+
import { buildForcedDirective, parseSubagentArgs } from '../team/forced-dispatch.js';
|
|
22
|
+
import { subagentModelSummary } from '../ui/overlays/model-profile-state.js';
|
|
23
|
+
/**
|
|
24
|
+
* The default count when the user names none (D-11).
|
|
25
|
+
*
|
|
26
|
+
* FOUR, not `maxSubagents`: `<team_mode>` tells the model two to four is usually
|
|
27
|
+
* enough, so "force one dispatch" must not silently become the most expensive
|
|
28
|
+
* call the session can make. The status line advertises the real ceiling so the
|
|
29
|
+
* user who wants the widest fan-out knows which flag to add.
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_FORCED_COUNT = 4;
|
|
32
|
+
/** `off (--no-team)` / `off` / `on`: the three states the status line tells apart. */
|
|
33
|
+
function teamState(ctx) {
|
|
34
|
+
if (!ctx.controller.isTeamRegistered())
|
|
35
|
+
return 'off (--no-team)';
|
|
36
|
+
return ctx.controller.isTeamEnabled() ? 'on' : 'off';
|
|
37
|
+
}
|
|
38
|
+
/** The subagent model line, phrased by the same authority `/team status` uses. */
|
|
39
|
+
function modelLine(ctx) {
|
|
40
|
+
const config = ctx.controller.getConfig();
|
|
41
|
+
const team = ctx.controller.getTeamConfig();
|
|
42
|
+
const fast = config.fast;
|
|
43
|
+
return subagentModelSummary({
|
|
44
|
+
mode: team.model,
|
|
45
|
+
modelProfileId: team.modelProfileId,
|
|
46
|
+
mainConnection: `${config.provider}:${config.model}`,
|
|
47
|
+
fastConnection: fast.enabled && fast.model
|
|
48
|
+
? `${fast.provider || config.provider}:${fast.model}` : null,
|
|
49
|
+
profiles: config.modelProfiles,
|
|
50
|
+
hasKey: (id) => ctx.controller.hasApiKey(id),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The no-argument answer: five lines, honest in every session state.
|
|
55
|
+
*
|
|
56
|
+
* IT RUNS BEFORE EVERY GUARD (R-P1-11). "How wide can I go?" is a question, not
|
|
57
|
+
* a request, and a question deserves an answer even in a `--no-team` session -
|
|
58
|
+
* which is exactly where the user is most likely to ask it.
|
|
59
|
+
*/
|
|
60
|
+
function reportStatus(ctx) {
|
|
61
|
+
const team = ctx.controller.getTeamConfig();
|
|
62
|
+
const max = team.maxSubagents;
|
|
63
|
+
// The bracket's bounds are the LIVE width, never a literal: at capacity 2 this
|
|
64
|
+
// line has to read `<2-2>`, or the status line teaches a flag that arrives
|
|
65
|
+
// narrowed every time.
|
|
66
|
+
const low = Math.min(2, max);
|
|
67
|
+
const lines = [
|
|
68
|
+
`Forced fan-out: /subagent [-n <${low}-${max}>] [--parts a,b,c] [--read-only] <brief>`,
|
|
69
|
+
`Capacity: ${max} per dispatch, ${team.maxConcurrent} at once `
|
|
70
|
+
+ '(team.maxSubagents / team.maxConcurrent)',
|
|
71
|
+
`Subagent model: ${modelLine(ctx)}`,
|
|
72
|
+
`Supervisor: ${team.overseer ? 'on' : 'off'}. Team mode: ${teamState(ctx)}.`,
|
|
73
|
+
];
|
|
74
|
+
// Disclosed, never refused (R-P2-6): plan mode may remove `task` from this
|
|
75
|
+
// turn's tool array, and a user who is told so will not read the silence as a
|
|
76
|
+
// broken command.
|
|
77
|
+
if (ctx.controller.getAgentMode() === 'plan') {
|
|
78
|
+
lines.push('Plan mode is on: the task tool may be unavailable this turn.');
|
|
79
|
+
}
|
|
80
|
+
ctx.notify('info', lines.join('\n'));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Guard 5's sentence. The provider is named because "no API key" alone leaves
|
|
84
|
+
* the user guessing which of their configured providers is the empty one.
|
|
85
|
+
*/
|
|
86
|
+
function reportMissingKey(ctx, team) {
|
|
87
|
+
const config = ctx.controller.getConfig();
|
|
88
|
+
const resolution = ctx.controller.subagentModelResolution();
|
|
89
|
+
const provider = resolution.effective === 'main'
|
|
90
|
+
? config.provider : resolution.ref.providerId;
|
|
91
|
+
const note = team.model === 'fast' && !ctx.controller.hasApiKey(undefined, 'fast')
|
|
92
|
+
? ' Fast tier has no key either; children will fail and the report will say so.'
|
|
93
|
+
: '';
|
|
94
|
+
ctx.notify('warn', `No API key for ${provider}. Set one with /settings or an environment variable.${note}`);
|
|
95
|
+
}
|
|
96
|
+
/** The five capability refusals, in the fixed order 3.5.3 records. */
|
|
97
|
+
function refused(ctx, team) {
|
|
98
|
+
if (!ctx.controller.isTeamRegistered()) {
|
|
99
|
+
ctx.notify('warn', 'Team mode is off for this session (started with --no-team). '
|
|
100
|
+
+ 'Restart without --no-team to use /subagent.');
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (!ctx.controller.isTeamEnabled()) {
|
|
104
|
+
ctx.notify('warn', 'Team mode is off. Run /team on, then /subagent again.');
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// The team lock and the lead's turn are separate refusals for one reason: both
|
|
108
|
+
// would collide with `task`'s own BUSY_REFUSAL, and the user would see a failed
|
|
109
|
+
// tool card instead of a sentence explaining why nothing ran.
|
|
110
|
+
if (ctx.controller.isTeamBusy()) {
|
|
111
|
+
ctx.notify('warn', 'A team dispatch is already running. '
|
|
112
|
+
+ 'Wait for its report, or press Esc to stop it.');
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (ctx.controller.isRunning()) {
|
|
116
|
+
ctx.notify('warn', 'A turn is in progress. Press Esc to stop it, then run /subagent again.');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (!ctx.controller.hasApiKey()) {
|
|
120
|
+
reportMissingKey(ctx, team);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/** The four parse failures, each with the one sentence that fixes it. */
|
|
126
|
+
function reportParseError(ctx, parsed) {
|
|
127
|
+
if (parsed.error === 'count') {
|
|
128
|
+
ctx.notify('warn', '/subagent: -n needs a number of 1 or more, e.g. -n 4.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (parsed.error === 'conflict') {
|
|
132
|
+
ctx.notify('warn', '/subagent: use -n or --parts, not both.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (parsed.error === 'flag') {
|
|
136
|
+
ctx.notify('warn', `/subagent: unknown option "${parsed.token ?? ''}". Try /subagent for usage.`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
ctx.notify('warn', '/subagent <brief> - describe the work; add -n <count> to fix the fan-out.');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Narrow the parsed intent against the LIVE width.
|
|
143
|
+
*
|
|
144
|
+
* TWO CEILINGS, IN THIS ORDER (R-P1-3): the structural `forcedPartsMax` bounds
|
|
145
|
+
* what one argument may carry (the parser already applied it), and the policy
|
|
146
|
+
* `maxSubagents` bounds what this session may dispatch - which only this layer
|
|
147
|
+
* can apply, because only it can read the live config.
|
|
148
|
+
*/
|
|
149
|
+
function planForced(args, team) {
|
|
150
|
+
const max = team.maxSubagents;
|
|
151
|
+
if (args.parts.length > 0) {
|
|
152
|
+
// A PARTS LIST STARTS WITH A CHILD PER SURVIVING PART, and `requested` is the
|
|
153
|
+
// number the user TYPED - not the structural 8 the parser already applied.
|
|
154
|
+
// Quoting the post-truncation count made the old notice tell a user who asked
|
|
155
|
+
// for eleven parts that they had asked for eight (R-4: the notice and the
|
|
156
|
+
// directive must both name the user's number).
|
|
157
|
+
const parts = args.parts.slice(0, max);
|
|
158
|
+
return {
|
|
159
|
+
brief: args.brief,
|
|
160
|
+
parts,
|
|
161
|
+
partsAdmitted: args.parts.length,
|
|
162
|
+
partsRequested: args.partsRequested,
|
|
163
|
+
requested: args.partsRequested,
|
|
164
|
+
count: parts.length,
|
|
165
|
+
readOnly: args.readOnly,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const requested = args.count ?? Math.min(DEFAULT_FORCED_COUNT, max);
|
|
169
|
+
return {
|
|
170
|
+
brief: args.brief,
|
|
171
|
+
parts: [],
|
|
172
|
+
partsAdmitted: 0,
|
|
173
|
+
partsRequested: 0,
|
|
174
|
+
requested,
|
|
175
|
+
count: Math.max(1, Math.min(requested, max)),
|
|
176
|
+
readOnly: args.readOnly,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** The one line that says what the NEXT turn will do - a plan, not a result. */
|
|
180
|
+
function planNotice(planned, team) {
|
|
181
|
+
const noun = planned.count === 1 ? 'subagent' : 'subagents';
|
|
182
|
+
const head = `Forcing a fan-out of ${planned.count} ${noun}`;
|
|
183
|
+
const followUp = `${head} (max ${team.maxSubagents}) - the next turn dispatches them. `
|
|
184
|
+
+ 'Esc stops the turn.';
|
|
185
|
+
// TWO CEILINGS, TWO SENTENCES. `forcedPartsMax` bounds what one argument may
|
|
186
|
+
// carry and `team.maxSubagents` bounds what may be dispatched; naming the wrong
|
|
187
|
+
// one sends the user to change a number that is not the reason.
|
|
188
|
+
if (planned.parts.length > 0) {
|
|
189
|
+
if (planned.parts.length < planned.partsAdmitted) {
|
|
190
|
+
return `${head} (asked for ${planned.partsRequested} parts; `
|
|
191
|
+
+ `team.maxSubagents is ${team.maxSubagents}).`;
|
|
192
|
+
}
|
|
193
|
+
if (planned.partsAdmitted < planned.partsRequested) {
|
|
194
|
+
return `${head} (asked for ${planned.partsRequested} parts, `
|
|
195
|
+
+ `the first ${planned.parts.length} are used).`;
|
|
196
|
+
}
|
|
197
|
+
return followUp;
|
|
198
|
+
}
|
|
199
|
+
if (planned.requested > planned.count) {
|
|
200
|
+
return `${head} (asked for ${planned.requested}; `
|
|
201
|
+
+ `team.maxSubagents is ${team.maxSubagents}).`;
|
|
202
|
+
}
|
|
203
|
+
return followUp;
|
|
204
|
+
}
|
|
205
|
+
/** The command body; `registerSubagentCommand` is the only registration site. */
|
|
206
|
+
export function runSubagentCommand(ctx) {
|
|
207
|
+
if (ctx.args.trim().length === 0) {
|
|
208
|
+
reportStatus(ctx);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const team = ctx.controller.getTeamConfig();
|
|
212
|
+
if (refused(ctx, team))
|
|
213
|
+
return;
|
|
214
|
+
const parsed = parseSubagentArgs(ctx.args);
|
|
215
|
+
if (!parsed.ok) {
|
|
216
|
+
reportParseError(ctx, parsed);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const planned = planForced(parsed.args, team);
|
|
220
|
+
const directive = buildForcedDirective({
|
|
221
|
+
brief: planned.brief,
|
|
222
|
+
count: planned.count,
|
|
223
|
+
readOnly: planned.readOnly,
|
|
224
|
+
parts: planned.parts,
|
|
225
|
+
maxSubagents: team.maxSubagents,
|
|
226
|
+
clamped: planned.requested > planned.count,
|
|
227
|
+
requested: planned.requested,
|
|
228
|
+
planMode: ctx.controller.getAgentMode() === 'plan',
|
|
229
|
+
});
|
|
230
|
+
ctx.notify('info', planNotice(planned, team));
|
|
231
|
+
// `userInitiated: false`: a directive the user did not type belongs in neither
|
|
232
|
+
// the prompt history nor the hint-fade counter (todo-plan-followthrough C-12).
|
|
233
|
+
ctx.submit(directive, { userInitiated: false });
|
|
234
|
+
}
|
|
235
|
+
/** Registered from `builtins.ts` with one line, next to every other subsystem. */
|
|
236
|
+
export function registerSubagentCommand(registry) {
|
|
237
|
+
registry.register({
|
|
238
|
+
name: 'subagent',
|
|
239
|
+
description: 'Force the next turn to fan out into a parallel team',
|
|
240
|
+
run: (ctx) => { runSubagentCommand(ctx); },
|
|
241
|
+
});
|
|
242
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/task` and `/task refresh` (tui-shortcut-drawer-and-task-summary §4.7).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/commands/**` is inside the glyph scanner's scope.
|
|
5
|
+
*
|
|
6
|
+
* WHY "task summary" AND NEVER BARE "task": this package already has a TOOL named
|
|
7
|
+
* `task` (the subagent dispatcher, `team/task-tool.ts`), and `HelpOverlay`'s team
|
|
8
|
+
* table prints that name. A help line that said only "task" would make two unrelated
|
|
9
|
+
* things share one word on the same screen.
|
|
10
|
+
*
|
|
11
|
+
* WHY A NOTICE AND NOT AN OVERLAY: three lines of provenance do not deserve a
|
|
12
|
+
* frame, and the guaranteed reporting surface for "why is there no label" has to be
|
|
13
|
+
* somewhere the user can scroll back to - a toast disappears.
|
|
14
|
+
*
|
|
15
|
+
* BOTH COMMANDS ARE REGISTERED UNCONDITIONALLY, even with
|
|
16
|
+
* `taskSummary.enabled = false`: they are the only surface that can EXPLAIN the
|
|
17
|
+
* switch, and a missing command answers "unknown command" instead of "off" (the
|
|
18
|
+
* `/prompts` D-19 rule).
|
|
19
|
+
*/
|
|
20
|
+
import type { SlashCommand } from './registry.js';
|
|
21
|
+
/** One line per fact, or a single honest line when there is no label. */
|
|
22
|
+
export declare function formatTaskSummaryReport(input: {
|
|
23
|
+
view: {
|
|
24
|
+
text: string;
|
|
25
|
+
tone: 'model' | 'fallback';
|
|
26
|
+
reason: string | null;
|
|
27
|
+
by: string;
|
|
28
|
+
at: number;
|
|
29
|
+
} | null;
|
|
30
|
+
pending: boolean;
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
now: number;
|
|
33
|
+
}): string[];
|
|
34
|
+
/** `''` for a bare `/task`, `'refresh'` for `/task refresh`, else the raw argument. */
|
|
35
|
+
export declare function parseTaskArgs(args: string): {
|
|
36
|
+
action: 'show' | 'refresh' | 'unknown';
|
|
37
|
+
raw: string;
|
|
38
|
+
};
|
|
39
|
+
export declare const taskCommand: SlashCommand;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/task` and `/task refresh` (tui-shortcut-drawer-and-task-summary §4.7).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/commands/**` is inside the glyph scanner's scope.
|
|
5
|
+
*
|
|
6
|
+
* WHY "task summary" AND NEVER BARE "task": this package already has a TOOL named
|
|
7
|
+
* `task` (the subagent dispatcher, `team/task-tool.ts`), and `HelpOverlay`'s team
|
|
8
|
+
* table prints that name. A help line that said only "task" would make two unrelated
|
|
9
|
+
* things share one word on the same screen.
|
|
10
|
+
*
|
|
11
|
+
* WHY A NOTICE AND NOT AN OVERLAY: three lines of provenance do not deserve a
|
|
12
|
+
* frame, and the guaranteed reporting surface for "why is there no label" has to be
|
|
13
|
+
* somewhere the user can scroll back to - a toast disappears.
|
|
14
|
+
*
|
|
15
|
+
* BOTH COMMANDS ARE REGISTERED UNCONDITIONALLY, even with
|
|
16
|
+
* `taskSummary.enabled = false`: they are the only surface that can EXPLAIN the
|
|
17
|
+
* switch, and a missing command answers "unknown command" instead of "off" (the
|
|
18
|
+
* `/prompts` D-19 rule).
|
|
19
|
+
*/
|
|
20
|
+
/** One line per fact, or a single honest line when there is no label. */
|
|
21
|
+
export function formatTaskSummaryReport(input) {
|
|
22
|
+
if (!input.enabled) {
|
|
23
|
+
return [
|
|
24
|
+
'Task summary is off (config taskSummary.enabled = false).',
|
|
25
|
+
'Turn it back on with: aragon config set taskSummary.enabled true',
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
if (input.view === null) {
|
|
29
|
+
if (input.pending)
|
|
30
|
+
return ['Task summary: still being generated.'];
|
|
31
|
+
return [
|
|
32
|
+
'No task summary yet.',
|
|
33
|
+
'It appears after the first prompt of a task; /task refresh regenerates it.',
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
const age = formatAge(input.now - input.view.at);
|
|
37
|
+
const source = input.view.tone === 'fallback'
|
|
38
|
+
? `your own words (${input.view.reason ?? 'fallback'})`
|
|
39
|
+
: input.view.by;
|
|
40
|
+
return [
|
|
41
|
+
`Task summary: ${input.view.text}`,
|
|
42
|
+
`Source: ${source}${age === '' ? '' : `, ${age}`}`,
|
|
43
|
+
'Regenerate with /task refresh, or open the drawer (Ctrl+G) to see why.',
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
/** `12s ago` / `3m ago` / `1h ago`, or `''` for a clock that went backwards. */
|
|
47
|
+
function formatAge(ageMs) {
|
|
48
|
+
if (!Number.isFinite(ageMs) || ageMs < 0)
|
|
49
|
+
return '';
|
|
50
|
+
const seconds = Math.floor(ageMs / 1000);
|
|
51
|
+
if (seconds < 60)
|
|
52
|
+
return `${seconds}s ago`;
|
|
53
|
+
const minutes = Math.floor(seconds / 60);
|
|
54
|
+
if (minutes < 60)
|
|
55
|
+
return `${minutes}m ago`;
|
|
56
|
+
return `${Math.floor(minutes / 60)}h ago`;
|
|
57
|
+
}
|
|
58
|
+
/** `''` for a bare `/task`, `'refresh'` for `/task refresh`, else the raw argument. */
|
|
59
|
+
export function parseTaskArgs(args) {
|
|
60
|
+
const raw = args.trim();
|
|
61
|
+
if (raw.length === 0)
|
|
62
|
+
return { action: 'show', raw };
|
|
63
|
+
if (raw.toLowerCase() === 'refresh')
|
|
64
|
+
return { action: 'refresh', raw };
|
|
65
|
+
return { action: 'unknown', raw };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The command body: parse, report, or regenerate.
|
|
69
|
+
*
|
|
70
|
+
* NAMED FOR WHAT IT DOES, not for the port it touches (review fix RF-5): the old
|
|
71
|
+
* name described a variable, and this function is the whole of `/task`.
|
|
72
|
+
*/
|
|
73
|
+
function runTaskCommand(ctx) {
|
|
74
|
+
if (!ctx.taskSummary) {
|
|
75
|
+
ctx.notify('warn', 'The task summary is not available in this build.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const port = ctx.taskSummary;
|
|
79
|
+
const { action, raw } = parseTaskArgs(ctx.args);
|
|
80
|
+
if (action === 'unknown') {
|
|
81
|
+
ctx.notify('warn', `Unknown argument "${raw}" - use /task or /task refresh.`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (action === 'refresh') {
|
|
85
|
+
const anchor = port.refresh();
|
|
86
|
+
if (anchor === '') {
|
|
87
|
+
ctx.notify('info', 'No prompt to summarise yet - send one first.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
ctx.notify('info', 'Regenerating the task summary...');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const state = port.read();
|
|
94
|
+
for (const line of formatTaskSummaryReport({ ...state, now: Date.now() })) {
|
|
95
|
+
ctx.notify('info', line);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export const taskCommand = {
|
|
99
|
+
name: 'task',
|
|
100
|
+
description: 'Show the header task summary (/task refresh regenerates it)',
|
|
101
|
+
run: runTaskCommand,
|
|
102
|
+
};
|
|
@@ -16,6 +16,7 @@ import { parseThresholdInput } from '../config/schema.js';
|
|
|
16
16
|
import { formatCost, formatTokens } from '../agent/usage.js';
|
|
17
17
|
import { computeCost } from '../agent/usage.js';
|
|
18
18
|
import { COMPACTION_LIMITS } from './limits.js';
|
|
19
|
+
import { compactionReasonLine } from './reason-text.js';
|
|
19
20
|
import { findArchive, listArchives } from './archive.js';
|
|
20
21
|
/**
|
|
21
22
|
* `/compact status` — THE GUARANTEED REPORTING SURFACE (§6.5 / AC-17).
|
|
@@ -46,7 +47,7 @@ export function formatCompactionStatus(ctx) {
|
|
|
46
47
|
return lines.join('\n');
|
|
47
48
|
}
|
|
48
49
|
const state = snapshot.selfDisabled
|
|
49
|
-
? `self-disabled (${snapshot.selfDisabledReason ?? 'no progress'})`
|
|
50
|
+
? `self-disabled (${compactionReasonLine(snapshot.selfDisabledReason ?? 'no progress')})`
|
|
50
51
|
: controller.isCompactionEnabled()
|
|
51
52
|
? snapshot.live
|
|
52
53
|
? 'on'
|
|
@@ -61,7 +62,13 @@ export function formatCompactionStatus(ctx) {
|
|
|
61
62
|
`[${p.source === 'usage' ? 'measured' : 'estimated'}]`);
|
|
62
63
|
lines.push(` Automatic trigger: occupancy >= ${(config.threshold * 100).toFixed(2)}%; ` +
|
|
63
64
|
`manual: /compact (${Math.ceil(p.contextWindow * config.threshold)} tokens)`);
|
|
65
|
+
// TWO LINES, BECAUSE THE TAIL GUARANTEE IS NOW CONDITIONAL (never-fires B).
|
|
66
|
+
// `keepRecentTurns` stays the promise; the second line is what happens when a run
|
|
67
|
+
// has too few user turns for that promise to be satisfiable, and saying it here
|
|
68
|
+
// is what makes the card's `kept M of N` readable rather than surprising.
|
|
64
69
|
lines.push(` Keeps: the original task + ${config.keepRecentTurns} recent turns, verbatim`);
|
|
70
|
+
lines.push(` Relaxes to: the last ${COMPACTION_LIMITS.minRetainedMessages} messages ` +
|
|
71
|
+
'when a run has too few user turns for that');
|
|
65
72
|
lines.push(` Summarizer: ${snapshot.model || '(none)'}` +
|
|
66
73
|
(config.useFastTier ? ' (fast tier preferred)' : ' (fast tier not used)'));
|
|
67
74
|
lines.push(' On summarize failure: ' + (config.onFailure === 'truncate'
|
|
@@ -79,8 +86,14 @@ export function formatCompactionStatus(ctx) {
|
|
|
79
86
|
if (declined > 0) {
|
|
80
87
|
sessionNotes.push(`${declined} ${declined === 1 ? 'checkpoint' : 'checkpoints'} declined`);
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
// ATTEMPTS, THEN APPLIED (never-fires C). `compactions` counts every card the
|
|
90
|
+
// user saw, which now includes the ones that declined, so reporting it alone
|
|
91
|
+
// would overstate how much the feature actually achieved.
|
|
92
|
+
const attempts = snapshot.applied === undefined
|
|
93
|
+
? `${snapshot.compactions} ${snapshot.compactions === 1 ? 'compaction' : 'compactions'}`
|
|
94
|
+
: `${snapshot.compactions} ${snapshot.compactions === 1 ? 'attempt' : 'attempts'}` +
|
|
95
|
+
` (${snapshot.applied} applied)`;
|
|
96
|
+
lines.push(` This session: ${attempts}` +
|
|
84
97
|
(sessionNotes.length > 0 ? ` (${sessionNotes.join(', ')})` : '') +
|
|
85
98
|
`, ${formatTokens(snapshot.tokensReclaimed)} tokens reclaimed`);
|
|
86
99
|
const cost = snapshot.pricingUnknown
|
|
@@ -93,6 +93,7 @@ export declare class Compactor implements ContextManager {
|
|
|
93
93
|
private compactionsThisRun;
|
|
94
94
|
private lastCompactionTurn;
|
|
95
95
|
private compactions;
|
|
96
|
+
private appliedCompactions;
|
|
96
97
|
private declined;
|
|
97
98
|
private tokensReclaimed;
|
|
98
99
|
private sessionUsage;
|
|
@@ -132,8 +133,31 @@ export declare class Compactor implements ContextManager {
|
|
|
132
133
|
private callOnce;
|
|
133
134
|
private checkCandidate;
|
|
134
135
|
private isCurrent;
|
|
136
|
+
/**
|
|
137
|
+
* Open the card for THIS attempt (C).
|
|
138
|
+
*
|
|
139
|
+
* CALLED FROM `compact()`'S ENTRY, so the two pre-model rejections are visible
|
|
140
|
+
* too. The old position - inside `callOnce` - made an attempt that never called
|
|
141
|
+
* a model indistinguishable from a checkpoint that never fired, which is exactly
|
|
142
|
+
* how this feature spent 96 attempts without a single user-visible symptom.
|
|
143
|
+
*
|
|
144
|
+
* `state.index !== undefined` keeps it idempotent: `callOnce` no longer calls it,
|
|
145
|
+
* but the guard stays so a future re-announce cannot open a second card for one
|
|
146
|
+
* attempt.
|
|
147
|
+
*/
|
|
135
148
|
private announce;
|
|
149
|
+
/** Publish the current phase, for the live card only. */
|
|
150
|
+
private phase;
|
|
136
151
|
private keep;
|
|
152
|
+
/**
|
|
153
|
+
* The relaxation disclosure, as record fields.
|
|
154
|
+
*
|
|
155
|
+
* ONE PLACE BUILDS IT, so the card, the reducer and the diagnostic log cannot
|
|
156
|
+
* describe one relaxed cut three different ways. Absent entirely when the first
|
|
157
|
+
* planning pass found a cut, which is what keeps a normal compaction's record
|
|
158
|
+
* byte-identical to the round before this change.
|
|
159
|
+
*/
|
|
160
|
+
private relaxedFields;
|
|
137
161
|
private finishRecord;
|
|
138
162
|
/** Commit only the engine/host verdict; candidates have no authority before this point. */
|
|
139
163
|
settleOperation(verdict: {
|
|
@@ -152,6 +176,7 @@ export declare class Compactor implements ContextManager {
|
|
|
152
176
|
sessionTotals(): {
|
|
153
177
|
selfDisabledReason?: string | undefined;
|
|
154
178
|
compactions: number;
|
|
179
|
+
applied: number;
|
|
155
180
|
declined: number;
|
|
156
181
|
tokensReclaimed: number;
|
|
157
182
|
usage: TokenUsage;
|