@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/launcher.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The bin entry (cli-auto-update-hardening section 5.1.1).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - this file is inside the glyph scanner's scope (C-19).
|
|
6
|
+
*
|
|
7
|
+
* KEEP IT THIS SMALL. Every import here is a module that can crash before the
|
|
8
|
+
* guard that exists to survive crashes. `node:process` is the only static one,
|
|
9
|
+
* and `__tests__/update-wiring.test.ts` asserts that on the source text.
|
|
10
|
+
*
|
|
11
|
+
* `dist/cli.js` IS UNCHANGED AND KEEPS ITS SHEBANG (R-17 / AC-50). Only the
|
|
12
|
+
* `bin` mapping moves; `npm start` (`node dist/cli.js`) and any user script that
|
|
13
|
+
* invokes `dist/cli.js` directly keep working byte for byte.
|
|
14
|
+
*/
|
|
15
|
+
import process from 'node:process';
|
|
16
|
+
try {
|
|
17
|
+
// DYNAMIC, AND THAT IS THE WHOLE POINT (P1-2 / R-22). A STATIC import of the
|
|
18
|
+
// guard would put `boot/guard -> update/state -> config/app-paths ->
|
|
19
|
+
// env-paths` in the bin entry's OWN module graph, and a resolution failure
|
|
20
|
+
// anywhere in that chain throws during THIS module's evaluation - outside the
|
|
21
|
+
// `try`, before any line of it runs. "An undeclared dependency" is the first
|
|
22
|
+
// way section 1.1 says a release bricks itself; a guard whose own import fails
|
|
23
|
+
// in that exact case has to degrade to "no guard", never to "no CLI".
|
|
24
|
+
const { runBootGuard } = await import('./boot/guard.js');
|
|
25
|
+
runBootGuard();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// A guard that throws must never be the reason the CLI will not start. This is
|
|
29
|
+
// the one catch in the package that is allowed to be empty: there is no logger
|
|
30
|
+
// yet, and there is nothing the user could do with the message.
|
|
31
|
+
}
|
|
32
|
+
// The `.catch` is for the IMPORT rejecting, which is precisely the import-time
|
|
33
|
+
// crash H1 exists for and which today produces an unhandled rejection with a
|
|
34
|
+
// stack the user cannot act on. `cli.js` runs `main()` at module scope and
|
|
35
|
+
// installs its own `.catch` for everything after that point.
|
|
36
|
+
//
|
|
37
|
+
// EXITING NON-ZERO HERE IS WHAT FEEDS THE GUARD'S COUNTER (D-30a): the exit hook
|
|
38
|
+
// the guard installed counts non-zero exits, and a module that will not import
|
|
39
|
+
// raises, so it arrives here.
|
|
40
|
+
await import('./cli.js').catch((err) => {
|
|
41
|
+
process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
});
|
|
@@ -121,11 +121,25 @@ export type AgentLogEvent = {
|
|
|
121
121
|
inputTokens?: number;
|
|
122
122
|
outputTokens?: number;
|
|
123
123
|
};
|
|
124
|
-
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `streamEvent` carries the retry fields OPTIONALLY (llm-api-retry-backoff
|
|
127
|
+
* §6.8): this union is a deliberately loose structural mirror of core's
|
|
128
|
+
* `AgentEvent` so a stub controller can satisfy it with an object literal, and
|
|
129
|
+
* narrowing on `streamEvent.type` inside the handler is what makes the extra
|
|
130
|
+
* fields safe to read.
|
|
131
|
+
*/
|
|
132
|
+
| {
|
|
125
133
|
type: 'message_update';
|
|
126
134
|
streamEvent?: {
|
|
127
135
|
type?: string;
|
|
128
136
|
delta?: unknown;
|
|
137
|
+
attempt?: number;
|
|
138
|
+
maxRetries?: number;
|
|
139
|
+
delayMs?: number;
|
|
140
|
+
errorType?: string;
|
|
141
|
+
retryAfterMs?: number;
|
|
142
|
+
discardedToolCallIds?: string[];
|
|
129
143
|
};
|
|
130
144
|
} | {
|
|
131
145
|
type: 'tool_execution_start';
|
package/dist/logging/install.js
CHANGED
|
@@ -251,17 +251,50 @@ export function attachAgentEvents(logger, controller) {
|
|
|
251
251
|
ms: turnStartedAt > 0 ? Date.now() - turnStartedAt : 0,
|
|
252
252
|
});
|
|
253
253
|
break;
|
|
254
|
-
case 'message_update':
|
|
254
|
+
case 'message_update': {
|
|
255
|
+
// API retry (llm-api-retry-backoff §6.8), ABOVE the `trace` line so a
|
|
256
|
+
// three-minute pause is in the log at the DEFAULT level. Redaction is
|
|
257
|
+
// unaffected: the payload carries no user content, and `message` is already
|
|
258
|
+
// the truncated body `classifyHttpError` produces.
|
|
259
|
+
const se = event.streamEvent;
|
|
260
|
+
if (se?.type === 'retry_scheduled') {
|
|
261
|
+
const cfg = controller.getConfig();
|
|
262
|
+
logger.warn('agent', 'llm_retry_scheduled', {
|
|
263
|
+
attempt: se.attempt,
|
|
264
|
+
maxRetries: se.maxRetries,
|
|
265
|
+
delayMs: se.delayMs,
|
|
266
|
+
errorType: se.errorType,
|
|
267
|
+
retryAfterMs: se.retryAfterMs,
|
|
268
|
+
providerId: cfg.provider,
|
|
269
|
+
modelId: cfg.model,
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
if (se?.type === 'retry_attempt') {
|
|
274
|
+
logger.debug('agent', 'llm_retry_attempt', {
|
|
275
|
+
attempt: se.attempt,
|
|
276
|
+
maxRetries: se.maxRetries,
|
|
277
|
+
});
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
if (se?.type === 'stream_restart') {
|
|
281
|
+
logger.warn('agent', 'llm_stream_restart', {
|
|
282
|
+
attempt: se.attempt,
|
|
283
|
+
discarded: se.discardedToolCallIds?.length ?? 0,
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
255
287
|
logger.trace('agent', 'stream', {
|
|
256
|
-
type:
|
|
288
|
+
type: se?.type ?? 'unknown',
|
|
257
289
|
// The SIZE of the increment, never the increment. It is what makes a
|
|
258
290
|
// trace log usable for "where did the time go" without turning it
|
|
259
291
|
// into a second copy of the conversation.
|
|
260
|
-
delta: typeof
|
|
261
|
-
?
|
|
292
|
+
delta: typeof se?.delta === 'string'
|
|
293
|
+
? se.delta.length
|
|
262
294
|
: undefined,
|
|
263
295
|
});
|
|
264
296
|
break;
|
|
297
|
+
}
|
|
265
298
|
case 'tool_execution_start':
|
|
266
299
|
logger.debug('tool', 'tool_start', {
|
|
267
300
|
toolCallId: event.toolCallId,
|
package/dist/logging/logger.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import type { LogConfig } from '../config/schema.js';
|
|
24
24
|
import { type LogLevelName } from './levels.js';
|
|
25
25
|
/** Closed set, not free text: a typo'd scope is a record nobody can filter on. */
|
|
26
|
-
export type LogScope = 'cli' | 'config' | 'agent' | 'tool' | 'skills' | 'llm' | 'migrate' | 'log' | 'history';
|
|
26
|
+
export type LogScope = 'cli' | 'config' | 'agent' | 'tool' | 'skills' | 'llm' | 'migrate' | 'log' | 'fast' | 'history' | 'update';
|
|
27
27
|
export interface LogRecord {
|
|
28
28
|
ts: string;
|
|
29
29
|
lv: LogLevelName;
|
package/dist/session/persist.js
CHANGED
|
@@ -28,6 +28,37 @@ function defaultStamp() {
|
|
|
28
28
|
// Filesystem-safe timestamp without punctuation that breaks on Windows.
|
|
29
29
|
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Drop the live tool tail on the way to disk (agent-activity-presentation-live
|
|
33
|
+
* D-24 / AC-34).
|
|
34
|
+
*
|
|
35
|
+
* THIS IS THE SERIALIZER, AND IT IS THE ONLY ONE (P1-2). `saveSession` writes
|
|
36
|
+
* `entries` VERBATIM -- the module doc below says so in those words -- and
|
|
37
|
+
* `agent/reducer.ts` contains no serializer at all, so a `/save` issued DURING a
|
|
38
|
+
* run would capture `live` / `liveSeq` / `lastOutputAt` however carefully the
|
|
39
|
+
* reducer were written.
|
|
40
|
+
*
|
|
41
|
+
* Three reasons to drop it, in order of weight: a resumed session's tail
|
|
42
|
+
* describes a process that no longer exists; the settled `preview` supersedes it
|
|
43
|
+
* entirely, so it is strictly redundant; and it is the only mutable-while-live
|
|
44
|
+
* field a tool entry has, so persisting it would force a `normalizeLoadedEntries`
|
|
45
|
+
* clause -- the class of clause round 1's D-9 exists for.
|
|
46
|
+
*
|
|
47
|
+
* STRIPPING RATHER THAN TRUSTING ABSENCE IS WHAT MAKES DECLINING THAT CLAUSE
|
|
48
|
+
* HONEST. A tool entry saved mid-run keeps `status: 'running'` and
|
|
49
|
+
* `normalizeLoadedEntries` has no clause for tool entries (it never has -- the
|
|
50
|
+
* four it carries are all about other kinds). With the tail stripped, `showLive`
|
|
51
|
+
* is false and the resumed card is byte-identical to what today's build draws
|
|
52
|
+
* for the same file: one `running` row. With the tail present it would be a
|
|
53
|
+
* multi-row card describing a process that died with the last session, counting
|
|
54
|
+
* seconds against a `lastOutputAt` from yesterday.
|
|
55
|
+
*/
|
|
56
|
+
function stripLiveToolOutput(entries) {
|
|
57
|
+
return entries.map((e) => e.kind === 'tool' &&
|
|
58
|
+
(e.live !== undefined || e.liveSeq !== undefined || e.lastOutputAt !== undefined)
|
|
59
|
+
? { ...e, live: undefined, liveSeq: undefined, lastOutputAt: undefined }
|
|
60
|
+
: e);
|
|
61
|
+
}
|
|
31
62
|
/**
|
|
32
63
|
* `data` GAINS `todos`, AND WIDENING `SavedSession` ALONE IS NOT ENOUGH (P0-2):
|
|
33
64
|
* the writer below builds its payload from THIS parameter, not from the type, so
|
|
@@ -43,7 +74,7 @@ export function saveSession(filePath, data) {
|
|
|
43
74
|
savedAt: Date.now(),
|
|
44
75
|
model: data.model,
|
|
45
76
|
messages: data.messages,
|
|
46
|
-
entries: data.entries,
|
|
77
|
+
entries: stripLiveToolOutput(data.entries),
|
|
47
78
|
todos: data.todos,
|
|
48
79
|
};
|
|
49
80
|
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
@@ -75,6 +106,31 @@ export function normalizeLoadedEntries(entries) {
|
|
|
75
106
|
if (entry.kind === 'todo' && entry.live) {
|
|
76
107
|
return { ...entry, live: false, interrupted: true };
|
|
77
108
|
}
|
|
109
|
+
// THE THIRD CLAUSE (llm-api-retry-backoff §6.4 / R-10 / AC-26). A card saved
|
|
110
|
+
// mid-wait would resume claiming to be counting down to an instant in the past,
|
|
111
|
+
// and — worse — it would never settle, so `Transcript`'s MONOTONIC boundary
|
|
112
|
+
// would never advance past it and every frame for the rest of the session
|
|
113
|
+
// would re-render the whole tail. `resumeAt` is dropped for the same reason
|
|
114
|
+
// the phase changes: there is no attempt coming.
|
|
115
|
+
if (entry.kind === 'retry' && (entry.phase === 'waiting' || entry.phase === 'retrying')) {
|
|
116
|
+
return { ...entry, phase: 'interrupted', resumeAt: undefined };
|
|
117
|
+
}
|
|
118
|
+
// THE FOURTH CLAUSE (fast-model-tier §5.4 / C-5 / AC-28). A review card
|
|
119
|
+
// saved while its call was open resumes claiming to be running while nothing
|
|
120
|
+
// is, and — the part that actually costs — it never settles, so
|
|
121
|
+
// `Transcript`'s MONOTONIC boundary never advances past it and every frame
|
|
122
|
+
// for the rest of the session re-renders the whole tail.
|
|
123
|
+
//
|
|
124
|
+
// `dropped` rather than `failed`: the review did not fail, it never got an
|
|
125
|
+
// answer, and its critique died with the process.
|
|
126
|
+
if (entry.kind === 'fast' && entry.live) {
|
|
127
|
+
return {
|
|
128
|
+
...entry,
|
|
129
|
+
live: false,
|
|
130
|
+
status: 'dropped',
|
|
131
|
+
detail: 'interrupted (session resumed)',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
78
134
|
return entry;
|
|
79
135
|
});
|
|
80
136
|
}
|
package/dist/team/normalize.d.ts
CHANGED
|
@@ -23,6 +23,26 @@ export interface NormalizedSpecs {
|
|
|
23
23
|
specs: SubagentSpec[];
|
|
24
24
|
/** How many entries the model asked for, before dropping and capping. */
|
|
25
25
|
requested: number;
|
|
26
|
+
/**
|
|
27
|
+
* Specs that asked for `model:"fast"` and were downgraded to `main`
|
|
28
|
+
* (fast-model-tier §3.4 / R-6).
|
|
29
|
+
*
|
|
30
|
+
* ADDITIVE: `NormalizedSpecs` GAINS a field, it does not change one, so every
|
|
31
|
+
* existing caller and test compiles unchanged.
|
|
32
|
+
*/
|
|
33
|
+
downgraded: number;
|
|
34
|
+
}
|
|
35
|
+
/** Options for the fast tier. Optional for the reason `normalizeSubagentSpecs`
|
|
36
|
+
* documents below (RV-14). */
|
|
37
|
+
export interface NormalizeOptions {
|
|
38
|
+
/**
|
|
39
|
+
* §3.3's LIVE predicate, evaluated at DISPATCH time (RV-3).
|
|
40
|
+
*
|
|
41
|
+
* `fast.delegate: false` and "the tier stopped resolving twenty minutes ago"
|
|
42
|
+
* are the same observable to the model — a downgrade, counted and reported —
|
|
43
|
+
* which is the honest reading of both.
|
|
44
|
+
*/
|
|
45
|
+
fastAvailable?: boolean;
|
|
26
46
|
}
|
|
27
47
|
/**
|
|
28
48
|
* Repair and bound a raw `subagents` array.
|
|
@@ -35,5 +55,10 @@ export interface NormalizedSpecs {
|
|
|
35
55
|
* @param max The resolved `team.maxSubagents`; further clamped to
|
|
36
56
|
* `TEAM_LIMITS.hardMaxSubagents` here so a hand-edited config file
|
|
37
57
|
* cannot raise the requirement's ceiling (R-g).
|
|
58
|
+
* @param opts Fast-tier availability. OPTIONAL, DEFAULTING TO `false` (RV-14):
|
|
59
|
+
* this function and `NormalizedSpecs` are both exported and both
|
|
60
|
+
* have existing callers and tests, so a required third parameter
|
|
61
|
+
* would be a breaking edit that buys nothing — and an omitted one
|
|
62
|
+
* now means exactly what a pre-feature build meant.
|
|
38
63
|
*/
|
|
39
|
-
export declare function normalizeSubagentSpecs(raw: unknown, max: number): NormalizedSpecs;
|
|
64
|
+
export declare function normalizeSubagentSpecs(raw: unknown, max: number, opts?: NormalizeOptions): NormalizedSpecs;
|
package/dist/team/normalize.js
CHANGED
|
@@ -76,13 +76,21 @@ function dedupeLabel(label, taken) {
|
|
|
76
76
|
* @param max The resolved `team.maxSubagents`; further clamped to
|
|
77
77
|
* `TEAM_LIMITS.hardMaxSubagents` here so a hand-edited config file
|
|
78
78
|
* cannot raise the requirement's ceiling (R-g).
|
|
79
|
+
* @param opts Fast-tier availability. OPTIONAL, DEFAULTING TO `false` (RV-14):
|
|
80
|
+
* this function and `NormalizedSpecs` are both exported and both
|
|
81
|
+
* have existing callers and tests, so a required third parameter
|
|
82
|
+
* would be a breaking edit that buys nothing — and an omitted one
|
|
83
|
+
* now means exactly what a pre-feature build meant.
|
|
79
84
|
*/
|
|
80
|
-
export function normalizeSubagentSpecs(raw, max) {
|
|
85
|
+
export function normalizeSubagentSpecs(raw, max, opts = {}) {
|
|
81
86
|
if (!Array.isArray(raw))
|
|
82
|
-
return { specs: [], requested: 0 };
|
|
87
|
+
return { specs: [], requested: 0, downgraded: 0 };
|
|
83
88
|
const requested = raw.length;
|
|
89
|
+
const fastAvailable = opts.fastAvailable === true;
|
|
84
90
|
const kept = [];
|
|
85
91
|
const taken = new Set();
|
|
92
|
+
/** Parallel to `kept`: did entry `i` ask for the fast tier? */
|
|
93
|
+
const wantedFast = [];
|
|
86
94
|
for (let i = 0; i < raw.length; i += 1) {
|
|
87
95
|
const item = raw[i];
|
|
88
96
|
if (!item || typeof item !== 'object')
|
|
@@ -94,13 +102,25 @@ export function normalizeSubagentSpecs(raw, max) {
|
|
|
94
102
|
const prompt = clamp(asString(src.prompt), TEAM_LIMITS.promptChars);
|
|
95
103
|
if (prompt.length === 0)
|
|
96
104
|
continue;
|
|
105
|
+
// REPAIR, NEVER REJECT — this file's own contract. `'fast'` without
|
|
106
|
+
// availability becomes `'main'` AND IS COUNTED; anything else (a typo, a
|
|
107
|
+
// model id, `undefined`) is simply `'main'` and is NOT counted, because the
|
|
108
|
+
// model did not ask for the fast tier and has nothing to be told about.
|
|
109
|
+
const wantsFast = src.model === 'fast';
|
|
110
|
+
wantedFast.push(wantsFast);
|
|
97
111
|
kept.push({
|
|
98
112
|
label: dedupeLabel(slugLabel(src.label, kept.length), taken),
|
|
99
113
|
description,
|
|
100
114
|
prompt,
|
|
101
115
|
readOnly: src.readOnly === true,
|
|
116
|
+
tier: wantsFast && fastAvailable ? 'fast' : 'main',
|
|
102
117
|
});
|
|
103
118
|
}
|
|
104
119
|
const ceiling = Math.max(1, Math.min(Math.floor(max) || 1, TEAM_LIMITS.hardMaxSubagents));
|
|
105
|
-
|
|
120
|
+
const specs = kept.slice(0, ceiling);
|
|
121
|
+
// COUNTED OVER THE SURVIVORS, not over everything the model wrote: a spec the
|
|
122
|
+
// cap dropped never ran at all, so reporting it as "ran on the main model"
|
|
123
|
+
// would be a second, different lie in a line that exists to stop the first.
|
|
124
|
+
const downgraded = specs.filter((spec, i) => spec.tier === 'main' && wantedFast[i] === true).length;
|
|
125
|
+
return { specs, requested, downgraded };
|
|
106
126
|
}
|
package/dist/team/prompt.d.ts
CHANGED
|
@@ -18,6 +18,17 @@ export { TEAM_BLOCK_VERSION };
|
|
|
18
18
|
export interface TeamBlockParams {
|
|
19
19
|
maxSubagents: number;
|
|
20
20
|
maxConcurrent: number;
|
|
21
|
+
/**
|
|
22
|
+
* Whether `model:"fast"` is honoured on `task` right now (fast-model-tier
|
|
23
|
+
* §3.8).
|
|
24
|
+
*
|
|
25
|
+
* ONE SENTENCE, AND ONLY WHEN IT IS TRUE. `<fast_tier>` carries the full
|
|
26
|
+
* guidance; this is the cross-reference that stops a model reading
|
|
27
|
+
* `<team_mode>` in isolation and concluding every child costs the same. With
|
|
28
|
+
* it absent the block is BYTE-IDENTICAL to the pre-feature output, which is
|
|
29
|
+
* what `--no-fast` and every existing snapshot depend on.
|
|
30
|
+
*/
|
|
31
|
+
fastDelegation?: boolean;
|
|
21
32
|
}
|
|
22
33
|
/**
|
|
23
34
|
* The lead's `<team_mode>` block. Under 1200 characters on purpose: it is paid
|
package/dist/team/prompt.js
CHANGED
|
@@ -47,6 +47,16 @@ export function buildTeamBlock(params) {
|
|
|
47
47
|
'report back. Give each one a disjoint set of files to write. Subagents cannot',
|
|
48
48
|
'dispatch further subagents and cannot ask the user anything.',
|
|
49
49
|
'',
|
|
50
|
+
// THE TRAILING BLANK IS INSIDE THE CONDITIONAL, not outside it. An
|
|
51
|
+
// unconditional `''` here would add a line to EVERY team-enabled prompt and
|
|
52
|
+
// silently break the byte-identity `--no-fast` depends on (I-2).
|
|
53
|
+
...(params.fastDelegation
|
|
54
|
+
? [
|
|
55
|
+
'A subagent doing mechanical, high-volume work can run on the cheaper fast',
|
|
56
|
+
'model - see <fast_tier>.',
|
|
57
|
+
'',
|
|
58
|
+
]
|
|
59
|
+
: []),
|
|
50
60
|
'You get one combined report when they all finish. Read it before deciding what',
|
|
51
61
|
'to do next, and tell the user what the team found in your own words.',
|
|
52
62
|
'</team_mode>',
|
package/dist/team/report.d.ts
CHANGED
|
@@ -25,6 +25,24 @@ import type { DispatchOutcome, SubagentRun } from './types.js';
|
|
|
25
25
|
export interface ReportOptions {
|
|
26
26
|
/** The lead's model cost table, so the header can state real spend (R-5). */
|
|
27
27
|
cost?: ModelCost;
|
|
28
|
+
/**
|
|
29
|
+
* The FAST model's cost table (fast-model-tier §3.4 / R-7).
|
|
30
|
+
*
|
|
31
|
+
* A SECOND TABLE, not a second number derived from the first. Summing
|
|
32
|
+
* fast-tier tokens at the lead's price over-reports a Haiku child under a
|
|
33
|
+
* Sonnet lead by roughly an order of magnitude, and the whole justification
|
|
34
|
+
* for delegating to a cheaper model is the figure on this line.
|
|
35
|
+
*/
|
|
36
|
+
fastCost?: ModelCost;
|
|
37
|
+
/**
|
|
38
|
+
* The static price table has never heard of the fast model (C-11 / RV-4).
|
|
39
|
+
*
|
|
40
|
+
* UNKNOWN PRICING IS NOT ZERO PRICING. `buildRuntimeModel` hands back
|
|
41
|
+
* `cost: { input: 0, output: 0 }` for an unrecognised id, and the fast tier is
|
|
42
|
+
* precisely where an unrecognised id is LIKELY — so rendering it as `$0.00`
|
|
43
|
+
* would make the feature look free while it is spending money.
|
|
44
|
+
*/
|
|
45
|
+
fastPricingUnknown?: boolean;
|
|
28
46
|
}
|
|
29
47
|
/**
|
|
30
48
|
* Cut `text` to at most `maxBytes` WITHOUT splitting a UTF-8 sequence.
|
package/dist/team/report.js
CHANGED
|
@@ -106,6 +106,28 @@ function buildHeader(outcome, options) {
|
|
|
106
106
|
`(${counts}) in ${formatDuration(durationMs)}.`);
|
|
107
107
|
lines.push(`Tokens: in ${formatTokens(outcome.usage.inputTokens)}, out ` +
|
|
108
108
|
`${formatTokens(outcome.usage.outputTokens)}. Cost: ${formatCost(computeCost(outcome.usage, options.cost))}.`);
|
|
109
|
+
// A SECOND LINE, NOT A COMBINED TOTAL (fast-model-tier §3.4). Two tiers with
|
|
110
|
+
// two price tables have two answers, and folding them into one figure is the
|
|
111
|
+
// misattribution R-7 is about. Present only when a fast child actually ran, so
|
|
112
|
+
// an ordinary dispatch's header is byte-identical.
|
|
113
|
+
const fastUsage = outcome.fastUsage;
|
|
114
|
+
if (fastUsage) {
|
|
115
|
+
// `unknown` rather than a currency amount when the model has no price table
|
|
116
|
+
// (RV-4): a lower bound rendered as a total is the same class of lie as
|
|
117
|
+
// pricing a Haiku child at Sonnet rates.
|
|
118
|
+
const fastCostLabel = options.fastPricingUnknown
|
|
119
|
+
? 'unknown (no price table)'
|
|
120
|
+
: formatCost(computeCost(fastUsage, options.fastCost));
|
|
121
|
+
lines.push(`Fast tier: in ${formatTokens(fastUsage.inputTokens)}, out ` +
|
|
122
|
+
`${formatTokens(fastUsage.outputTokens)}. Cost: ${fastCostLabel}.`);
|
|
123
|
+
}
|
|
124
|
+
// THE LINE THAT KEEPS A DOWNGRADE FROM BEING SILENT (R-6). Without it the
|
|
125
|
+
// model asked for a cheap child, got an expensive one, and has no way to learn
|
|
126
|
+
// that its cost model is wrong.
|
|
127
|
+
if (outcome.downgraded && outcome.downgraded > 0) {
|
|
128
|
+
const n = outcome.downgraded;
|
|
129
|
+
lines.push(`${n} ${n === 1 ? 'subagent' : 'subagents'} ran on the main model (fast tier off).`);
|
|
130
|
+
}
|
|
109
131
|
for (const conflict of findFileConflicts(outcome.runs)) {
|
|
110
132
|
const who = conflict.labels.map((l) => `"${l}"`).join(' and ');
|
|
111
133
|
lines.push(`WARNING: agents ${who} both wrote ${conflict.path} - review before trusting either.`);
|
|
@@ -117,8 +139,12 @@ function buildSection(run) {
|
|
|
117
139
|
// the only place the lead learns that a child's first attempt died before it
|
|
118
140
|
// did anything (F-4).
|
|
119
141
|
const retried = run.retries ? `, retried ${run.retries}x` : '';
|
|
142
|
+
// `[fast]` after the LABEL, on the head line, which is never trimmed. Six
|
|
143
|
+
// bytes, and it is the only place the lead learns which of its children were
|
|
144
|
+
// cheap — the fact the whole second cost line is about.
|
|
145
|
+
const tier = run.tier === 'fast' ? ' [fast]' : '';
|
|
120
146
|
const head = [
|
|
121
|
-
`### ${run.label} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
|
|
147
|
+
`### ${run.label}${tier} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
|
|
122
148
|
`${run.turns} turns, ${run.toolCalls} tools${retried}`,
|
|
123
149
|
];
|
|
124
150
|
if (run.filesTouched.length > 0)
|
package/dist/team/runtime.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { ProviderRegistry, SkillRegistry, ToolPolicyDecision, ToolPolicyVer
|
|
|
21
21
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
22
22
|
import type { CliConfig } from '../config/schema.js';
|
|
23
23
|
import type { TeamHumanQueue } from './human-queue.js';
|
|
24
|
-
import { type SubagentAgentFactory } from './subagent.js';
|
|
24
|
+
import { type SubagentAgentFactory, type SubagentDeps } from './subagent.js';
|
|
25
25
|
import type { DispatchOutcome, SubagentSpec, TeamEventListener, TeamSnapshot } from './types.js';
|
|
26
26
|
export interface TeamRuntimeDeps {
|
|
27
27
|
/** Read LIVE: a settings-screen model or key edit must reach the next dispatch. */
|
|
@@ -42,6 +42,12 @@ export interface TeamRuntimeDeps {
|
|
|
42
42
|
humanQueue?: TeamHumanQueue;
|
|
43
43
|
/** Test seam (§8.1): a stub factory keeps the whole scheduler off the network. */
|
|
44
44
|
agentFactory?: SubagentAgentFactory;
|
|
45
|
+
/**
|
|
46
|
+
* Per-tier model resolution (fast-model-tier §3.4). Passed straight through to
|
|
47
|
+
* `SubagentDeps`; absent means every child runs on the session's model, which
|
|
48
|
+
* is the pre-feature behaviour.
|
|
49
|
+
*/
|
|
50
|
+
resolveTier?: SubagentDeps['resolveTier'];
|
|
45
51
|
}
|
|
46
52
|
export declare class TeamRuntime {
|
|
47
53
|
private readonly deps;
|
|
@@ -75,7 +81,12 @@ export declare class TeamRuntime {
|
|
|
75
81
|
isBusy(): boolean;
|
|
76
82
|
/** The live roster, for `/team` status. `null` when nothing is dispatching. */
|
|
77
83
|
snapshot(): TeamSnapshot | null;
|
|
78
|
-
dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal
|
|
84
|
+
dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal,
|
|
85
|
+
/** Carried through from the normalizer so the report can state it (§3.4 /
|
|
86
|
+
* R-6). Optional, so every existing caller compiles unchanged. */
|
|
87
|
+
extra?: {
|
|
88
|
+
downgraded?: number;
|
|
89
|
+
}): Promise<DispatchOutcome>;
|
|
79
90
|
/** One slot of the pool: take the next index until the list is exhausted. */
|
|
80
91
|
private worker;
|
|
81
92
|
/**
|
package/dist/team/runtime.js
CHANGED
|
@@ -65,7 +65,10 @@ export class TeamRuntime {
|
|
|
65
65
|
// -------------------------------------------------------------------------
|
|
66
66
|
// Dispatch
|
|
67
67
|
// -------------------------------------------------------------------------
|
|
68
|
-
async dispatch(specs, requested, signal
|
|
68
|
+
async dispatch(specs, requested, signal,
|
|
69
|
+
/** Carried through from the normalizer so the report can state it (§3.4 /
|
|
70
|
+
* R-6). Optional, so every existing caller compiles unchanged. */
|
|
71
|
+
extra = {}) {
|
|
69
72
|
this.counter += 1;
|
|
70
73
|
const dispatchId = `d${this.counter}`;
|
|
71
74
|
const config = this.deps.getConfig();
|
|
@@ -122,6 +125,7 @@ export class TeamRuntime {
|
|
|
122
125
|
confirmTools: config.confirmTools,
|
|
123
126
|
...(this.deps.humanQueue ? { humanQueue: this.deps.humanQueue } : {}),
|
|
124
127
|
agentFactory: this.deps.agentFactory ?? defaultSubagentFactory,
|
|
128
|
+
...(this.deps.resolveTier ? { resolveTier: this.deps.resolveTier } : {}),
|
|
125
129
|
};
|
|
126
130
|
// Hoisted into a factory so a retry can build a fresh child with IDENTICAL
|
|
127
131
|
// wiring (F-4). Replacing the child rather than re-prompting it is the only
|
|
@@ -130,7 +134,11 @@ export class TeamRuntime {
|
|
|
130
134
|
// show the child its brief twice.
|
|
131
135
|
const makeHandle = (spec) => createSubagent(spec, subDeps, {
|
|
132
136
|
onUpdate: (run) => this.publish(dispatchId, run),
|
|
133
|
-
|
|
137
|
+
// `tier` rides along so `App.tsx` can pick the RIGHT cost table for this
|
|
138
|
+
// event rather than always using the lead's (§3.6 / R-7). Taken from the
|
|
139
|
+
// SPEC, which the normalizer already downgraded when the tier was
|
|
140
|
+
// unavailable, so it names the model that actually billed.
|
|
141
|
+
onUsage: (label, usage) => this.emit({ type: 'usage', dispatchId, label, usage, tier: spec.tier }),
|
|
134
142
|
});
|
|
135
143
|
// ASSIGNMENT, not a declaration: a `const` here would shadow the `let` above
|
|
136
144
|
// the bus, `canSend` would see the empty array forever, and every peer would
|
|
@@ -174,6 +182,24 @@ export class TeamRuntime {
|
|
|
174
182
|
signal?.removeEventListener('abort', onAbort);
|
|
175
183
|
for (const handle of handles)
|
|
176
184
|
handle.unsubscribe();
|
|
185
|
+
// SUMMED PER TIER (fast-model-tier §3.4 / R-7). One total priced at the
|
|
186
|
+
// lead's table would over-report a Haiku child under a Sonnet lead by
|
|
187
|
+
// roughly an order of magnitude — and the whole justification for delegating
|
|
188
|
+
// to a cheaper model is a number in the report this feeds.
|
|
189
|
+
// `main` IS THE COMPLEMENT OF `fast`, not an equality test. `SubagentRun.tier`
|
|
190
|
+
// is populated by `createSubagent` from the spec, but a hand-built run in a
|
|
191
|
+
// test — or a run restored from a session file written before this field
|
|
192
|
+
// existed — has it `undefined`, and `=== 'main'` would silently drop that
|
|
193
|
+
// child's spend from the total. Under-reporting is the one failure this
|
|
194
|
+
// split exists to prevent, so the default direction has to be "counted".
|
|
195
|
+
const sumTier = (tier) => handles
|
|
196
|
+
.filter((h) => (tier === 'fast' ? h.run.tier === 'fast' : h.run.tier !== 'fast'))
|
|
197
|
+
.reduce((acc, h) => ({
|
|
198
|
+
inputTokens: acc.inputTokens + h.run.usage.inputTokens,
|
|
199
|
+
outputTokens: acc.outputTokens + h.run.usage.outputTokens,
|
|
200
|
+
}), { inputTokens: 0, outputTokens: 0 });
|
|
201
|
+
const fastUsage = sumTier('fast');
|
|
202
|
+
const ranFast = handles.some((h) => h.run.tier === 'fast');
|
|
177
203
|
const outcome = {
|
|
178
204
|
dispatchId,
|
|
179
205
|
runs: handles.map((h) => ({ ...h.run, filesTouched: [...h.run.filesTouched] })),
|
|
@@ -182,10 +208,11 @@ export class TeamRuntime {
|
|
|
182
208
|
endedAt: Date.now(),
|
|
183
209
|
aborted: this.aborted,
|
|
184
210
|
leadMail: bus.leadMail(),
|
|
185
|
-
usage:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
211
|
+
usage: sumTier('main'),
|
|
212
|
+
// ABSENT when no fast child ran, which is what keeps a default session's
|
|
213
|
+
// outcome shape byte-identical to the pre-feature one.
|
|
214
|
+
...(ranFast ? { fastUsage } : {}),
|
|
215
|
+
...(extra.downgraded ? { downgraded: extra.downgraded } : {}),
|
|
189
216
|
};
|
|
190
217
|
this.live = [];
|
|
191
218
|
this.bus = null;
|
package/dist/team/subagent.d.ts
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* them (§3.11 / R-6). What a child does NOT get is spelled out below, and each
|
|
11
11
|
* omission has a reason that is a correctness boundary rather than caution.
|
|
12
12
|
*/
|
|
13
|
-
import { type AgentConfig, type AgentEvent, type AgentTool, type ProviderRegistry, type SkillRegistry, type TokenUsage, type ToolPolicyDecision, type ToolPolicyVerdict } from '@aragon-agent/core';
|
|
13
|
+
import { type AgentConfig, type AgentEvent, type AgentTool, type ModelRef, type ProviderRegistry, type SkillRegistry, type ThinkingLevel, type TokenUsage, type ToolPolicyDecision, type ToolPolicyVerdict } from '@aragon-agent/core';
|
|
14
14
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
15
15
|
import type { CliConfig } from '../config/schema.js';
|
|
16
|
+
import type { FastTierName } from '../fast/types.js';
|
|
16
17
|
import { TeamBus } from './bus.js';
|
|
17
18
|
import type { TeamHumanQueue, WatchdogPausable } from './human-queue.js';
|
|
18
19
|
import type { SubagentRun, SubagentSpec } from './types.js';
|
|
@@ -61,6 +62,23 @@ export interface SubagentDeps {
|
|
|
61
62
|
/** Present only when `--confirm` is on AND a human channel exists. */
|
|
62
63
|
humanQueue?: TeamHumanQueue;
|
|
63
64
|
agentFactory: SubagentAgentFactory;
|
|
65
|
+
/**
|
|
66
|
+
* Which model and thinking level a tier runs on (fast-model-tier §3.4).
|
|
67
|
+
*
|
|
68
|
+
* OPTIONAL, defaulting to the session's own model, so every existing caller
|
|
69
|
+
* and test compiles unchanged and a `--no-fast` session builds children
|
|
70
|
+
* exactly as it always did.
|
|
71
|
+
*
|
|
72
|
+
* `resolveTier('fast')` RETURNS THE MAIN TIER when the fast tier is not
|
|
73
|
+
* available, which is what makes the two guards agree BY CONSTRUCTION: the
|
|
74
|
+
* normalizer can never produce a `tier: 'fast'` spec this factory would
|
|
75
|
+
* refuse, and this factory can never build a child against a `ModelRef` the
|
|
76
|
+
* tier stopped resolving twenty minutes ago (RV-3 / R-14).
|
|
77
|
+
*/
|
|
78
|
+
resolveTier?: (tier: FastTierName) => {
|
|
79
|
+
ref: ModelRef;
|
|
80
|
+
thinkingLevel: ThinkingLevel;
|
|
81
|
+
};
|
|
64
82
|
}
|
|
65
83
|
export interface SubagentHooks {
|
|
66
84
|
/** Coalesced at the source (§5.2); phase transitions always pass. */
|