@aragon-agent/cli 0.5.8 → 0.5.10
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 +1157 -747
- package/README.md +1552 -1163
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +245 -3
- package/dist/agent/reducer.js +490 -17
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +368 -3
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +426 -2
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +79 -2
- package/dist/config/cli-commands.js +185 -3
- package/dist/config/env.d.ts +15 -3
- package/dist/config/env.js +143 -3
- package/dist/config/load.d.ts +66 -0
- package/dist/config/load.js +107 -1
- package/dist/config/schema.d.ts +268 -2
- package/dist/config/schema.js +288 -1
- package/dist/config/store.js +32 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +11 -4
- package/dist/ui/App.js +359 -37
- package/dist/ui/BottomStatusRow.d.ts +65 -0
- package/dist/ui/BottomStatusRow.js +17 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +37 -0
- package/dist/ui/StatusBar.js +5 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +26 -3
- package/dist/ui/Transcript.js +76 -10
- package/dist/ui/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +20 -0
- package/dist/ui/entries/AssistantEntry.js +3 -2
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.js +6 -1
- package/dist/ui/entries/ToolCard.d.ts +38 -1
- package/dist/ui/entries/ToolCard.js +68 -5
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/virtual-window.d.ts +1 -1
- package/dist/ui/layout/virtual-window.js +109 -5
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/transcript-text.js +27 -0
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
package/dist/team/subagent.js
CHANGED
|
@@ -137,6 +137,9 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
137
137
|
const run = {
|
|
138
138
|
label: spec.label,
|
|
139
139
|
description: spec.description,
|
|
140
|
+
// FROM THE SPEC, which the normalizer already downgraded when the tier was
|
|
141
|
+
// unavailable — so this is what RAN, not what was asked for (§3.4).
|
|
142
|
+
tier: spec.tier,
|
|
140
143
|
phase: 'queued',
|
|
141
144
|
turns: 0,
|
|
142
145
|
toolCalls: 0,
|
|
@@ -151,11 +154,31 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
151
154
|
let agentRef = null;
|
|
152
155
|
const getAgent = () => agentRef;
|
|
153
156
|
const tools = buildSubagentTools(spec, deps, getAgent);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// THE TWO HARDCODED CONFIG READS THAT USED TO LIVE HERE NOW ROUTE THROUGH ONE
|
|
158
|
+
// RESOLVER (fast-model-tier §3.4). The fallback reproduces them exactly, so a
|
|
159
|
+
// caller that passes no `resolveTier` builds a byte-identical child.
|
|
160
|
+
//
|
|
161
|
+
// `thinkingLevel` is PER TIER and defaults to `'off'` for the fast tier
|
|
162
|
+
// (D-13): a "fast" model asked to think for 32 768 tokens is not fast, and
|
|
163
|
+
// inheriting the session's `xhigh` into a mechanical file-scan child would be
|
|
164
|
+
// the most expensive possible reading of the word.
|
|
165
|
+
//
|
|
166
|
+
// `maxTokens` is deliberately NOT per tier (RV-15). A child inherits
|
|
167
|
+
// `deps.config.maxTokens` below, which is an ambition sized for the main
|
|
168
|
+
// model; the adapter clamps it down to whatever the fast model actually
|
|
169
|
+
// accepts (`llm/provider.ts:44-52`), so a large inherited value degrades to
|
|
170
|
+
// the right number rather than to an HTTP 400.
|
|
171
|
+
const resolved = deps.resolveTier
|
|
172
|
+
? deps.resolveTier(spec.tier)
|
|
173
|
+
: {
|
|
174
|
+
ref: {
|
|
175
|
+
providerId: deps.config.provider,
|
|
176
|
+
modelId: deps.config.model,
|
|
177
|
+
...(deps.config.baseUrl ? { baseUrl: deps.config.baseUrl } : {}),
|
|
178
|
+
},
|
|
179
|
+
thinkingLevel: deps.config.thinkingLevel,
|
|
180
|
+
};
|
|
181
|
+
const model = resolved.ref;
|
|
159
182
|
const agent = deps.agentFactory({
|
|
160
183
|
systemPrompt: buildSystemPrompt({
|
|
161
184
|
cwd: deps.getCwd(),
|
|
@@ -174,7 +197,7 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
174
197
|
}),
|
|
175
198
|
model,
|
|
176
199
|
tools,
|
|
177
|
-
thinkingLevel:
|
|
200
|
+
thinkingLevel: resolved.thinkingLevel,
|
|
178
201
|
// The LEAD's registry instance, REUSED (P2-2). It holds adapter state, and
|
|
179
202
|
// constructing five of them per dispatch is pure waste.
|
|
180
203
|
providerRegistry: deps.providerRegistry,
|
|
@@ -194,18 +217,34 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
194
217
|
agentRef = agent;
|
|
195
218
|
let lastEmit = 0;
|
|
196
219
|
let lastPhase = run.phase;
|
|
220
|
+
/**
|
|
221
|
+
* The retry projection as it was last PUBLISHED (§6.10).
|
|
222
|
+
*
|
|
223
|
+
* A retry transition has to pass the throttle for the same reason a phase
|
|
224
|
+
* transition does, and it is not covered by the phase check: a child entering
|
|
225
|
+
* backoff stays `thinking`, so `retry_scheduled` arriving within a throttle
|
|
226
|
+
* window of the previous token would be COALESCED AWAY — and the next event is
|
|
227
|
+
* up to thirty seconds later, which is exactly the interval the row exists to
|
|
228
|
+
* explain. The row would then say `thinking` for the whole wait.
|
|
229
|
+
*/
|
|
230
|
+
let lastRetryKey = '';
|
|
197
231
|
// The tail of the text the child is writing right now (F-1). Bounded on every
|
|
198
232
|
// append: a long turn would otherwise grow this without limit, and it is the
|
|
199
233
|
// only per-token allocation this feature adds.
|
|
200
234
|
let textTail = '';
|
|
201
235
|
const emit = () => {
|
|
202
236
|
const now = Date.now();
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
// streaming
|
|
206
|
-
|
|
237
|
+
const retryKey = run.retry ? `${run.retry.attempt}/${run.retry.maxRetries}` : '';
|
|
238
|
+
// Phase and retry transitions always pass; everything else is coalesced. Five
|
|
239
|
+
// children streaming tokens must not push five React renders per token, and
|
|
240
|
+
// the streaming coalescer in `App.tsx` protects the transcript, not this path.
|
|
241
|
+
if (run.phase === lastPhase &&
|
|
242
|
+
retryKey === lastRetryKey &&
|
|
243
|
+
now - lastEmit < TEAM_LIMITS.agentUpdateThrottleMs) {
|
|
207
244
|
return;
|
|
245
|
+
}
|
|
208
246
|
lastPhase = run.phase;
|
|
247
|
+
lastRetryKey = retryKey;
|
|
209
248
|
lastEmit = now;
|
|
210
249
|
// Exactly one sanitize per PUBLISHED frame rather than one per token (P2-2):
|
|
211
250
|
// `emit()` throws away all but one result per throttle window, so two
|
|
@@ -238,6 +277,9 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
238
277
|
break;
|
|
239
278
|
case 'turn_end': {
|
|
240
279
|
run.turns += 1;
|
|
280
|
+
// A completed turn means whatever it retried, it recovered (§6.10). The
|
|
281
|
+
// row goes back to reporting its phase.
|
|
282
|
+
run.retry = undefined;
|
|
241
283
|
run.usage = {
|
|
242
284
|
inputTokens: run.usage.inputTokens + event.usage.inputTokens,
|
|
243
285
|
outputTokens: run.usage.outputTokens + event.usage.outputTokens,
|
|
@@ -288,6 +330,26 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
288
330
|
if (event.streamEvent.type === 'error') {
|
|
289
331
|
run.error = formatStreamError(event.streamEvent.error);
|
|
290
332
|
run.retryable = isRetryableStreamError(event.streamEvent.error);
|
|
333
|
+
// The ladder is over: an `error` reaching a subscriber means `withRetry`
|
|
334
|
+
// stopped retrying. Leaving the projection set would freeze the row at
|
|
335
|
+
// `retrying 10/10` on top of a failure (§6.10).
|
|
336
|
+
run.retry = undefined;
|
|
337
|
+
}
|
|
338
|
+
else if (event.streamEvent.type === 'retry_scheduled') {
|
|
339
|
+
// SET, and the phase is deliberately LEFT ALONE: the child is still
|
|
340
|
+
// `thinking` as far as the scheduler is concerned, and rewriting the
|
|
341
|
+
// phase would put a retry into `SubagentPhase` — a union the report, the
|
|
342
|
+
// panel and `canSend` all read for a different question.
|
|
343
|
+
run.retry = {
|
|
344
|
+
attempt: event.streamEvent.attempt,
|
|
345
|
+
maxRetries: event.streamEvent.maxRetries,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
else if (event.streamEvent.type === 'retry_attempt') {
|
|
349
|
+
run.retry = {
|
|
350
|
+
attempt: event.streamEvent.attempt,
|
|
351
|
+
maxRetries: event.streamEvent.maxRetries,
|
|
352
|
+
};
|
|
291
353
|
}
|
|
292
354
|
else if (event.streamEvent.type === 'text_delta') {
|
|
293
355
|
// O(1) per delta: one bounded concat and one bounded slice, and
|
package/dist/team/task-tool.d.ts
CHANGED
|
@@ -27,12 +27,38 @@ export interface TaskToolDeps {
|
|
|
27
27
|
runtime: TeamRuntime;
|
|
28
28
|
/** Read LIVE, never cached: `/team off` flips this, it does not unregister. */
|
|
29
29
|
isTeamEnabled: () => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether the fast tier was resolvable AT CONSTRUCTION (fast-model-tier §3.3 /
|
|
32
|
+
* C-2).
|
|
33
|
+
*
|
|
34
|
+
* DECIDED ONCE, because the tool array is built once and must never be
|
|
35
|
+
* rebuilt: `Agent.setTools()` mutates the live `ToolRegistry`, and
|
|
36
|
+
* `submit_plan` flips the session mode from INSIDE a tool execution. So the
|
|
37
|
+
* `model` property either exists in this schema for the whole session or never
|
|
38
|
+
* does — and with it absent, `TASK_DESCRIPTION` and the schema are
|
|
39
|
+
* BYTE-IDENTICAL to the pre-feature build (AC-3).
|
|
40
|
+
*
|
|
41
|
+
* Optional so every existing construction site and test compiles unchanged.
|
|
42
|
+
*/
|
|
43
|
+
fastRegistered?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether `model:"fast"` is honoured RIGHT NOW. Read LIVE, at dispatch time
|
|
46
|
+
* (RV-3) — the tier can stop resolving mid-session, and a downgrade is the
|
|
47
|
+
* honest observable either way.
|
|
48
|
+
*/
|
|
49
|
+
fastAvailable?: () => boolean;
|
|
30
50
|
maxSubagents: () => number;
|
|
31
51
|
/** Whether a usable key exists for the active provider right now (P2-3). */
|
|
32
52
|
hasApiKey: () => boolean;
|
|
33
53
|
activeProvider: () => string;
|
|
34
54
|
/** The lead's cost table, so the report header states real spend (R-5). */
|
|
35
55
|
modelCost: () => ModelCost | undefined;
|
|
56
|
+
/** The FAST model's cost table (fast-model-tier §3.4). Absent when the tier is
|
|
57
|
+
* off, which is what leaves the report byte-identical. */
|
|
58
|
+
fastModelCost?: () => ModelCost | undefined;
|
|
59
|
+
/** Whether the static price table has never heard of the fast model (C-11 /
|
|
60
|
+
* RV-4). `unknown` is rendered as unknown, NEVER as `$0.00`. */
|
|
61
|
+
fastPricingUnknown?: () => boolean;
|
|
36
62
|
/**
|
|
37
63
|
* Run the dispatch with the LEAD's idle watchdog paused, `try/finally` inside.
|
|
38
64
|
*
|
package/dist/team/task-tool.js
CHANGED
|
@@ -41,11 +41,37 @@ const TASK_DESCRIPTION = 'Run several independent subagents in parallel and get
|
|
|
41
41
|
'cannot ask the user anything. Give each a description of at most ' +
|
|
42
42
|
`${TEAM_LIMITS.descriptionChars} characters and a complete prompt. Extra entries beyond the ` +
|
|
43
43
|
'configured maximum are dropped, so keep within it.';
|
|
44
|
+
/**
|
|
45
|
+
* The one sentence appended when the fast tier exists (fast-model-tier §4.1).
|
|
46
|
+
*
|
|
47
|
+
* WHEN to use a tier is POLICY and lives here and in `<fast_tier>`, where the
|
|
48
|
+
* model actually reads it; the `enum` below is SHAPE and lives in the schema.
|
|
49
|
+
* That split is what keeps behaviour the same whether or not `ajv` — an OPTIONAL
|
|
50
|
+
* dependency of core — happened to install (C-8).
|
|
51
|
+
*/
|
|
52
|
+
const TASK_FAST_SENTENCE = ' Set model:"fast" on a subagent whose job is bulk reading, searching or summarizing; ' +
|
|
53
|
+
'leave it out for work that needs judgement.';
|
|
54
|
+
/**
|
|
55
|
+
* NOT `as const`: `AgentTool.parameters` is a `JSONSchema7`, whose `enum` is a
|
|
56
|
+
* MUTABLE `unknown[]`, and a readonly tuple is not assignable to it.
|
|
57
|
+
*/
|
|
58
|
+
const SUBAGENT_MODEL_PROPERTY = {
|
|
59
|
+
type: 'string',
|
|
60
|
+
enum: ['main', 'fast'],
|
|
61
|
+
default: 'main',
|
|
62
|
+
description: 'Which tier runs this subagent. "fast" is a cheaper, quicker model - use it for ' +
|
|
63
|
+
'mechanical, high-volume work (reading or searching many files, summarizing long ' +
|
|
64
|
+
'output, mechanical edits). Keep "main" for design, tricky debugging, and anything ' +
|
|
65
|
+
'that must be right the first time.',
|
|
66
|
+
};
|
|
44
67
|
export function createTaskTool(deps) {
|
|
68
|
+
const fastRegistered = deps.fastRegistered === true;
|
|
45
69
|
return {
|
|
46
70
|
name: 'task',
|
|
47
71
|
label: 'Dispatch subagents',
|
|
48
|
-
|
|
72
|
+
// BYTE-IDENTICAL WITHOUT THE TIER (AC-3): concatenation is conditional, so a
|
|
73
|
+
// default session's description is the exact string it always was.
|
|
74
|
+
description: fastRegistered ? `${TASK_DESCRIPTION}${TASK_FAST_SENTENCE}` : TASK_DESCRIPTION,
|
|
49
75
|
parameters: {
|
|
50
76
|
type: 'object',
|
|
51
77
|
properties: {
|
|
@@ -73,6 +99,11 @@ export function createTaskTool(deps) {
|
|
|
73
99
|
default: false,
|
|
74
100
|
description: 'Refuse all writes and shell for this subagent.',
|
|
75
101
|
},
|
|
102
|
+
// Spread rather than a `?:` property, so with the tier off the
|
|
103
|
+
// object has exactly the four keys it always had — `undefined`
|
|
104
|
+
// survives `JSON.stringify` as an absent key but not as an
|
|
105
|
+
// identical object, and AC-3 is asserted on the schema itself.
|
|
106
|
+
...(fastRegistered ? { model: SUBAGENT_MODEL_PROPERTY } : {}),
|
|
76
107
|
},
|
|
77
108
|
required: ['description', 'prompt'],
|
|
78
109
|
},
|
|
@@ -100,19 +131,32 @@ export function createTaskTool(deps) {
|
|
|
100
131
|
'yourself, or ask the user to set a key.');
|
|
101
132
|
}
|
|
102
133
|
// 4. Repair, never reject. Zero survivors is the single hard failure.
|
|
103
|
-
|
|
134
|
+
// `fastAvailable` is read HERE, at dispatch time, not captured at
|
|
135
|
+
// construction (RV-3): the tier can stop resolving mid-session, and the
|
|
136
|
+
// normalizer and the subagent factory must agree about that at the same
|
|
137
|
+
// instant or a child gets built against a provider with no key.
|
|
138
|
+
const { specs, requested, downgraded } = normalizeSubagentSpecs(params.subagents, deps.maxSubagents(), { fastAvailable: deps.fastAvailable?.() === true });
|
|
104
139
|
if (specs.length === 0) {
|
|
105
140
|
return errorResult('No usable subagent specs: each needs a description and a prompt.');
|
|
106
141
|
}
|
|
107
142
|
// 5. Pause the lead's watchdog for the whole dispatch (I-3).
|
|
108
|
-
const outcome = await deps.withPausedWatchdog(() => deps.runtime.dispatch(specs, requested, ctx.signal));
|
|
143
|
+
const outcome = await deps.withPausedWatchdog(() => deps.runtime.dispatch(specs, requested, ctx.signal, { downgraded }));
|
|
109
144
|
// 6. ALWAYS A NON-ERROR RESULT, abort and all-children-failed included.
|
|
110
145
|
// A cancellation or a partial result rendered as a tool FAILURE invites
|
|
111
146
|
// the model to retry the whole fan-out, which is the most expensive
|
|
112
147
|
// possible reaction (the same reasoning as `ask_user`'s cancelled
|
|
113
148
|
// shape, and D-14 one level up).
|
|
149
|
+
// TWO COST TABLES (§3.4 / R-7). Summing fast-tier tokens at the lead's
|
|
150
|
+
// price is not a rounding error: for a Haiku child under a Sonnet lead
|
|
151
|
+
// it over-reports by roughly an order of magnitude, and the whole
|
|
152
|
+
// justification for the feature is a number in this report.
|
|
114
153
|
const cost = deps.modelCost();
|
|
115
|
-
|
|
154
|
+
const fastCost = deps.fastModelCost?.();
|
|
155
|
+
return textResult(buildDispatchReport(outcome, {
|
|
156
|
+
...(cost ? { cost } : {}),
|
|
157
|
+
...(fastCost ? { fastCost } : {}),
|
|
158
|
+
...(deps.fastPricingUnknown?.() ? { fastPricingUnknown: true } : {}),
|
|
159
|
+
}));
|
|
116
160
|
},
|
|
117
161
|
};
|
|
118
162
|
}
|
package/dist/team/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* changes ZERO files under `packages/core/` (AC-12).
|
|
12
12
|
*/
|
|
13
13
|
import type { TokenUsage } from '@aragon-agent/core';
|
|
14
|
+
import type { FastTierName } from '../fast/types.js';
|
|
14
15
|
import type { ActivityArgs } from './activity.js';
|
|
15
16
|
/** One normalized delegation request. Produced only by `normalizeSubagentSpecs`. */
|
|
16
17
|
export interface SubagentSpec {
|
|
@@ -26,12 +27,34 @@ export interface SubagentSpec {
|
|
|
26
27
|
* permission than the session it was spawned from.
|
|
27
28
|
*/
|
|
28
29
|
readOnly: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Which model runs this child (fast-model-tier §3.4).
|
|
32
|
+
*
|
|
33
|
+
* THE WIRE NAME IS `model` AND THE INTERNAL NAME IS `tier`, on purpose: a
|
|
34
|
+
* model expects to see `model:"fast"`, while `SubagentSpec.model` would read
|
|
35
|
+
* as a model ID at every call site in this package.
|
|
36
|
+
*
|
|
37
|
+
* DELEGATION IS A MODEL CHOICE, NEVER A PERMISSION BOUNDARY (D-15 / I-4). A
|
|
38
|
+
* fast child gets the same tools, the same `--confirm` gate, the same
|
|
39
|
+
* plan-mode gate and the same skills ceiling as any other; only the model
|
|
40
|
+
* differs. Restricting a fast child's tools would look like security, would
|
|
41
|
+
* not be, and would make the report's `[fast]` rows mean two different things.
|
|
42
|
+
*/
|
|
43
|
+
tier: FastTierName;
|
|
29
44
|
}
|
|
30
45
|
export type SubagentPhase = 'queued' | 'starting' | 'thinking' | 'tool' | 'waiting' | 'done' | 'failed' | 'aborted';
|
|
31
46
|
/** Live, mutable record of one child. Projected into the UI and the report. */
|
|
32
47
|
export interface SubagentRun {
|
|
33
48
|
label: string;
|
|
34
49
|
description: string;
|
|
50
|
+
/**
|
|
51
|
+
* The tier this child actually ran on (fast-model-tier §3.4).
|
|
52
|
+
*
|
|
53
|
+
* WHAT RAN, NOT WHAT WAS ASKED FOR. A spec the normalizer downgraded arrives
|
|
54
|
+
* here as `'main'`, which is what makes the report's `[fast]` annotation and
|
|
55
|
+
* its second cost table agree with the bill.
|
|
56
|
+
*/
|
|
57
|
+
tier: FastTierName;
|
|
35
58
|
phase: SubagentPhase;
|
|
36
59
|
startedAt?: number;
|
|
37
60
|
endedAt?: number;
|
|
@@ -104,6 +127,42 @@ export interface SubagentRun {
|
|
|
104
127
|
* `formatStreamError` and must never become a machine-readable contract.
|
|
105
128
|
*/
|
|
106
129
|
retryable?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* The child's LIVE API-retry state (llm-api-retry-backoff §6.10), or absent when
|
|
132
|
+
* it is not in a backoff.
|
|
133
|
+
*
|
|
134
|
+
* THIS EXISTS BECAUSE A CHILD'S RETRY IS OTHERWISE INVISIBLE (R-14). Subagents
|
|
135
|
+
* inherit the lead's retry policy for free — `TeamRuntime` reuses the lead's
|
|
136
|
+
* registry instance — but a child's `retry_scheduled` goes to that CHILD's own
|
|
137
|
+
* `Agent` listeners and never reaches the lead's `ViewState`. Without this field
|
|
138
|
+
* a subagent spending three minutes in backoff shows no card, no chip and no
|
|
139
|
+
* countdown: the dispatch simply appears hung, which is the single worst reading
|
|
140
|
+
* of a mechanism whose whole claim is that waiting is now legible.
|
|
141
|
+
*
|
|
142
|
+
* The fix uses the channel that already exists rather than inventing a second
|
|
143
|
+
* card: `TeamCard` and `transcript-text` already render one row per child, and
|
|
144
|
+
* this rides on it. `ViewState.retry` stays LEAD-ONLY, and the lead's status-bar
|
|
145
|
+
* chip is deliberately NOT driven by children — `agents 3/5` already reports the
|
|
146
|
+
* dispatch, and a second aggregate would put two numbers on the bar that
|
|
147
|
+
* disagree about what "3" means.
|
|
148
|
+
*
|
|
149
|
+
* EPHEMERAL, exactly like `activity` and `activityArgs`: cleared on the child's
|
|
150
|
+
* next `turn_end` or terminal error, never persisted, and a settled team entry
|
|
151
|
+
* keeps only what the run finally did.
|
|
152
|
+
*
|
|
153
|
+
* NO `resumeAt`, DELIBERATELY, and this is the one place §6.10's sketch is not
|
|
154
|
+
* followed. A countdown needs something to re-render it, and nothing does: the
|
|
155
|
+
* team row is drawn from `agent_update`, a child in backoff emits exactly ONE
|
|
156
|
+
* event for the whole wait, and the lead's 1 Hz `retryTick` is gated on the
|
|
157
|
+
* LEAD's own `state.retry`. A `7s` that never changed for thirty seconds is a
|
|
158
|
+
* worse readout than no number at all — it reads as a hung countdown, which is
|
|
159
|
+
* precisely the impression this row exists to remove. The counter alone is
|
|
160
|
+
* honest and is what AC-34 asserts.
|
|
161
|
+
*/
|
|
162
|
+
retry?: {
|
|
163
|
+
attempt: number;
|
|
164
|
+
maxRetries: number;
|
|
165
|
+
};
|
|
107
166
|
}
|
|
108
167
|
/** One message on the team bus. */
|
|
109
168
|
export interface TeamMessage {
|
|
@@ -124,8 +183,27 @@ export interface DispatchOutcome {
|
|
|
124
183
|
aborted: boolean;
|
|
125
184
|
/** Messages addressed to `lead`; surfaced in their own report section. */
|
|
126
185
|
leadMail: TeamMessage[];
|
|
127
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Summed across MAIN-TIER children. Real spend; it must reach the status bar
|
|
188
|
+
* (§3.9).
|
|
189
|
+
*
|
|
190
|
+
* THE MEANING NARROWED WHEN THE FAST TIER LANDED, and the split is not a
|
|
191
|
+
* rounding concern: a Haiku child under a Sonnet lead priced at the lead's
|
|
192
|
+
* table over-reports by roughly an order of magnitude, and the entire
|
|
193
|
+
* justification for delegating is a number in this report (R-7).
|
|
194
|
+
*/
|
|
128
195
|
usage: TokenUsage;
|
|
196
|
+
/** Summed across FAST-TIER children. Absent when none ran, which is what
|
|
197
|
+
* keeps a pre-feature dispatch's report byte-identical. */
|
|
198
|
+
fastUsage?: TokenUsage;
|
|
199
|
+
/**
|
|
200
|
+
* Specs that asked for `model:"fast"` and ran on `main` anyway (§3.4 / R-6).
|
|
201
|
+
*
|
|
202
|
+
* REPORTED, NEVER SILENT. A silent downgrade means the model asked for a cheap
|
|
203
|
+
* child, got an expensive one, and has no way to learn that its cost model is
|
|
204
|
+
* wrong.
|
|
205
|
+
*/
|
|
206
|
+
downgraded?: number;
|
|
129
207
|
}
|
|
130
208
|
/**
|
|
131
209
|
* What the UI renders: a cheap immutable projection, rebuilt per event.
|
|
@@ -160,6 +238,7 @@ export type TeamEvent = {
|
|
|
160
238
|
dispatchId: string;
|
|
161
239
|
label: string;
|
|
162
240
|
usage: TokenUsage;
|
|
241
|
+
tier: FastTierName;
|
|
163
242
|
} | {
|
|
164
243
|
type: 'message';
|
|
165
244
|
dispatchId: string;
|
package/dist/tools/bash-tool.js
CHANGED
|
@@ -57,7 +57,9 @@ export function makeBash(deps) {
|
|
|
57
57
|
},
|
|
58
58
|
required: ['command'],
|
|
59
59
|
},
|
|
60
|
-
|
|
60
|
+
// `id` rather than `_id`: the tool-call id is what keys the live-output side
|
|
61
|
+
// channel (agent-activity-presentation-live §3.1.2).
|
|
62
|
+
async execute(id, rawParams, ctx) {
|
|
61
63
|
const params = rawParams;
|
|
62
64
|
const command = params.command;
|
|
63
65
|
const cwd = params.cwd ? params.cwd : deps.getCwd();
|
|
@@ -106,11 +108,25 @@ export function makeBash(deps) {
|
|
|
106
108
|
clearTimeout(timer);
|
|
107
109
|
ctx.signal?.removeEventListener('abort', onAbort);
|
|
108
110
|
}
|
|
111
|
+
// `output` IS UNTOUCHED BY THE RECORDER, and that is what makes AC-30
|
|
112
|
+
// checkable by comparison rather than by inspection: the string the
|
|
113
|
+
// model receives, its truncation and the `[exit code N]` footer are all
|
|
114
|
+
// byte-identical to the pre-round build. The recorder is a SECOND
|
|
115
|
+
// consumer of a chunk this tool already had (R-7).
|
|
116
|
+
//
|
|
117
|
+
// No throttle here, deliberately: `append` is O(chunk), and a per-chunk
|
|
118
|
+
// throttle in the tool would drop the LAST chunk of a burst -- the one
|
|
119
|
+
// that matters. What must be throttled is the view update, and `App`
|
|
120
|
+
// does that where the machinery for it already lives (§3.1.2 / D-34).
|
|
109
121
|
child.stdout?.on('data', (d) => {
|
|
110
|
-
|
|
122
|
+
const s = d.toString();
|
|
123
|
+
output += s;
|
|
124
|
+
deps.recordOutput?.(id, s);
|
|
111
125
|
});
|
|
112
126
|
child.stderr?.on('data', (d) => {
|
|
113
|
-
|
|
127
|
+
const s = d.toString();
|
|
128
|
+
output += s;
|
|
129
|
+
deps.recordOutput?.(id, s);
|
|
114
130
|
});
|
|
115
131
|
child.on('error', (err) => {
|
|
116
132
|
finish(() => errorResult(`Failed to run command: ${err.message}`));
|
package/dist/tools/diff.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The MODEL-FACING unified diff for `edit_file` (spec §4), re-expressed on top
|
|
3
|
+
* of `buildPatch` (agent-activity-presentation §3.3.4).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The signature, the call site (`fs-tools.ts`), the `---` / `+++` headers and the
|
|
6
|
+
* ` ` / `- ` / `+ ` row prefixes are all unchanged; what changed is that the
|
|
7
|
+
* differing middle is now a real hunk list instead of one removed block followed
|
|
8
|
+
* by one added block. For a `replace_all` touching line 40 and line 900 that used
|
|
9
|
+
* to be 860 removed rows and 860 near-identical added rows.
|
|
10
|
+
*
|
|
11
|
+
* TWO WIDTHS, ONE ALGORITHM (D-20). This renderer keeps `CONTEXT_LINES = 2`; the
|
|
12
|
+
* UI passes `PATCH_LIMITS.context` (3). Collapsing them to one constant is the
|
|
13
|
+
* tempting simplification and it is wrong: `fs-tools.ts` passes no override, so a
|
|
14
|
+
* shared 3 would add two context rows to EVERY model-facing diff — the direction
|
|
15
|
+
* AC-17 forbids, in the most common case, with nothing watching.
|
|
16
|
+
*
|
|
17
|
+
* The shape of the change to the model's input is therefore exact: a
|
|
18
|
+
* single-region edit is today's rows plus ONE `@@` header, a multi-region edit is
|
|
19
|
+
* strictly smaller, and a trailing `\r` no longer reaches the prompt.
|
|
8
20
|
*/
|
|
21
|
+
import { type FilePatch } from './patch.js';
|
|
9
22
|
export interface DiffOptions {
|
|
10
23
|
/** File path shown in the `---`/`+++` header. */
|
|
11
24
|
path?: string;
|
|
@@ -13,3 +26,10 @@ export interface DiffOptions {
|
|
|
13
26
|
context?: number;
|
|
14
27
|
}
|
|
15
28
|
export declare function renderUnifiedDiff(oldStr: string, newStr: string, options?: DiffOptions): string;
|
|
29
|
+
/**
|
|
30
|
+
* A `FilePatch` as unified-diff text.
|
|
31
|
+
*
|
|
32
|
+
* Exported so the patch the user reads and the diff the model reads are provably
|
|
33
|
+
* the same change (D-10), and so `patch.test.ts` can assert the round trip.
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderPatchText(patch: FilePatch, path?: string): string;
|
package/dist/tools/diff.js
CHANGED
|
@@ -1,50 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The MODEL-FACING unified diff for `edit_file` (spec §4), re-expressed on top
|
|
3
|
+
* of `buildPatch` (agent-activity-presentation §3.3.4).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The signature, the call site (`fs-tools.ts`), the `---` / `+++` headers and the
|
|
6
|
+
* ` ` / `- ` / `+ ` row prefixes are all unchanged; what changed is that the
|
|
7
|
+
* differing middle is now a real hunk list instead of one removed block followed
|
|
8
|
+
* by one added block. For a `replace_all` touching line 40 and line 900 that used
|
|
9
|
+
* to be 860 removed rows and 860 near-identical added rows.
|
|
10
|
+
*
|
|
11
|
+
* TWO WIDTHS, ONE ALGORITHM (D-20). This renderer keeps `CONTEXT_LINES = 2`; the
|
|
12
|
+
* UI passes `PATCH_LIMITS.context` (3). Collapsing them to one constant is the
|
|
13
|
+
* tempting simplification and it is wrong: `fs-tools.ts` passes no override, so a
|
|
14
|
+
* shared 3 would add two context rows to EVERY model-facing diff — the direction
|
|
15
|
+
* AC-17 forbids, in the most common case, with nothing watching.
|
|
16
|
+
*
|
|
17
|
+
* The shape of the change to the model's input is therefore exact: a
|
|
18
|
+
* single-region edit is today's rows plus ONE `@@` header, a multi-region edit is
|
|
19
|
+
* strictly smaller, and a trailing `\r` no longer reaches the prompt.
|
|
8
20
|
*/
|
|
21
|
+
import { PATCH_LIMITS, buildPatch } from './patch.js';
|
|
9
22
|
const CONTEXT_LINES = 2;
|
|
23
|
+
/** Appended when the rendered text hits `PATCH_LIMITS.modelDiffMaxChars`. */
|
|
24
|
+
const DIFF_TRUNCATED_NOTE = '... (diff truncated)';
|
|
10
25
|
export function renderUnifiedDiff(oldStr, newStr, options = {}) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
const header = options.path
|
|
29
|
-
? [`--- ${options.path}`, `+++ ${options.path}`]
|
|
30
|
-
: [];
|
|
31
|
-
const lines = [];
|
|
32
|
-
const ctxStart = Math.max(0, start - context);
|
|
33
|
-
for (let i = ctxStart; i < start; i += 1) {
|
|
34
|
-
lines.push(` ${oldLines[i]}`);
|
|
35
|
-
}
|
|
36
|
-
for (let i = start; i <= endOld; i += 1) {
|
|
37
|
-
lines.push(`- ${oldLines[i]}`);
|
|
38
|
-
}
|
|
39
|
-
for (let i = start; i <= endNew; i += 1) {
|
|
40
|
-
lines.push(`+ ${newLines[i]}`);
|
|
26
|
+
const patch = buildPatch(oldStr, newStr, {
|
|
27
|
+
path: options.path ?? '',
|
|
28
|
+
context: options.context ?? CONTEXT_LINES,
|
|
29
|
+
});
|
|
30
|
+
return renderPatchText(patch, options.path);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A `FilePatch` as unified-diff text.
|
|
34
|
+
*
|
|
35
|
+
* Exported so the patch the user reads and the diff the model reads are provably
|
|
36
|
+
* the same change (D-10), and so `patch.test.ts` can assert the round trip.
|
|
37
|
+
*/
|
|
38
|
+
export function renderPatchText(patch, path) {
|
|
39
|
+
const header = path ? [`--- ${path}`, `+++ ${path}`] : [];
|
|
40
|
+
if (patch.hunks.length === 0) {
|
|
41
|
+
return `${header.join('\n')}${header.length ? '\n' : ''}(no textual change)`;
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
for (
|
|
44
|
-
lines.push(
|
|
43
|
+
const lines = [...header];
|
|
44
|
+
for (const hunk of patch.hunks) {
|
|
45
|
+
lines.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`);
|
|
46
|
+
for (const line of hunk.lines)
|
|
47
|
+
lines.push(`${signOf(line)}${line.text}`);
|
|
45
48
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
49
|
+
if (patch.truncated)
|
|
50
|
+
lines.push(DIFF_TRUNCATED_NOTE);
|
|
51
|
+
return clampToBudget(lines);
|
|
52
|
+
}
|
|
53
|
+
function signOf(line) {
|
|
54
|
+
if (line.kind === 'add')
|
|
55
|
+
return '+ ';
|
|
56
|
+
if (line.kind === 'del')
|
|
57
|
+
return '- ';
|
|
58
|
+
return ' ';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Cap the rendered text at `modelDiffMaxChars`.
|
|
62
|
+
*
|
|
63
|
+
* Whole rows, not a mid-row slice: half a diff line reaching the model reads as
|
|
64
|
+
* a real line that happens to be wrong, which is worse than an honestly short
|
|
65
|
+
* diff. `edit_file`'s result had no bound at all before this round beyond the
|
|
66
|
+
* executor's 100 KB backstop.
|
|
67
|
+
*/
|
|
68
|
+
function clampToBudget(lines) {
|
|
69
|
+
const kept = [];
|
|
70
|
+
let size = 0;
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
const next = size + line.length + 1;
|
|
73
|
+
if (kept.length > 0 && next > PATCH_LIMITS.modelDiffMaxChars) {
|
|
74
|
+
kept.push(DIFF_TRUNCATED_NOTE);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
kept.push(line);
|
|
78
|
+
size = next;
|
|
48
79
|
}
|
|
49
|
-
return
|
|
80
|
+
return kept.join('\n');
|
|
50
81
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI-local side channel a `FilePatch` travels on (agent-activity-presentation
|
|
3
|
+
* §3.3.5 / D-8).
|
|
4
|
+
*
|
|
5
|
+
* The patch does NOT ride in the tool result: `ToolResult` has no metadata
|
|
6
|
+
* channel and `packages/core`'s surface is frozen by `public-api.test.ts`, and
|
|
7
|
+
* more importantly a richer display must not cost the model more tokens. So the
|
|
8
|
+
* tool records the patch here, keyed by its own tool-call id, and `reduceEvent`
|
|
9
|
+
* takes it out again when `tool_execution_end` arrives.
|
|
10
|
+
*
|
|
11
|
+
* Three properties, each load-bearing:
|
|
12
|
+
*
|
|
13
|
+
* - **Owner-scoped.** The key is `${owner}:${toolCallId}`. Only `'lead'` is ever
|
|
14
|
+
* written this round (`team/**` is out of scope, so `buildSubagentTools`
|
|
15
|
+
* passes no recorder), but subagents build their tools from the same factory
|
|
16
|
+
* — so the day one gets a recorder, a child's tool-call id must not be able
|
|
17
|
+
* to land on a lead's card. One string concatenation, spent now (D-13).
|
|
18
|
+
* - **Take-once.** `tool_execution_end` fires exactly once per call, and a patch
|
|
19
|
+
* not consumed there never will be. Removal on read is what keeps a long
|
|
20
|
+
* session's store at constant size however many files are edited.
|
|
21
|
+
* - **Bounded FIFO.** At `cap` entries the oldest key is evicted. The cap is a
|
|
22
|
+
* RING, not a leak detector: nothing is retained longer than `cap` writes, so
|
|
23
|
+
* a patch nobody consumes cannot accumulate.
|
|
24
|
+
*/
|
|
25
|
+
import type { FilePatch } from './patch.js';
|
|
26
|
+
export interface FileChangeStore {
|
|
27
|
+
record(owner: string, toolCallId: string, patch: FilePatch): void;
|
|
28
|
+
/** Returns and REMOVES the patch. A patch is consumed exactly once. */
|
|
29
|
+
take(owner: string, toolCallId: string): FilePatch | undefined;
|
|
30
|
+
size(): number;
|
|
31
|
+
}
|
|
32
|
+
/** Entries retained before the oldest is evicted. */
|
|
33
|
+
export declare const FILE_CHANGE_STORE_CAP = 64;
|
|
34
|
+
export declare function createFileChangeStore(cap?: number): FileChangeStore;
|