@aragon-agent/cli 0.6.16 → 0.6.17
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/README.md +44 -47
- package/dist/agent/controller.js +39 -47
- package/dist/agent/reducer.js +0 -4
- package/dist/cli.js +0 -13
- package/dist/commands/builtins.js +3 -17
- package/dist/compaction/child.d.ts +10 -8
- package/dist/compaction/child.js +23 -25
- package/dist/compaction/limits.d.ts +0 -2
- package/dist/compaction/limits.js +0 -2
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +16 -15
- package/dist/config/load.d.ts +0 -17
- package/dist/config/load.js +0 -6
- package/dist/config/schema.d.ts +17 -5
- package/dist/config/schema.js +14 -11
- package/dist/exec/events.d.ts +12 -8
- package/dist/exec/events.js +2 -2
- package/dist/exec/runner.js +4 -1
- package/dist/fast/limits.d.ts +2 -2
- package/dist/fast/limits.js +2 -2
- package/dist/fast/prompt.d.ts +10 -11
- package/dist/fast/prompt.js +19 -20
- package/dist/fast/reviewer.d.ts +1 -1
- package/dist/fast/reviewer.js +1 -1
- package/dist/fast/types.d.ts +0 -2
- package/dist/fast/wiring.d.ts +18 -34
- package/dist/fast/wiring.js +20 -51
- package/dist/session/validate-session.js +3 -1
- package/dist/team/child-reviewer.d.ts +105 -0
- package/dist/team/child-reviewer.js +117 -0
- package/dist/team/normalize.d.ts +7 -26
- package/dist/team/normalize.js +9 -22
- package/dist/team/prompt.d.ts +0 -11
- package/dist/team/prompt.js +3 -12
- package/dist/team/report.d.ts +4 -19
- package/dist/team/report.js +1 -27
- package/dist/team/runtime.d.ts +5 -10
- package/dist/team/runtime.js +35 -39
- package/dist/team/subagent.d.ts +18 -18
- package/dist/team/subagent.js +80 -30
- package/dist/team/task-tool.d.ts +6 -26
- package/dist/team/task-tool.js +13 -47
- package/dist/team/types.d.ts +3 -42
- package/dist/ui/App.js +3 -4
- package/dist/ui/BottomStatusRow.js +2 -1
- package/dist/ui/StatusBar.js +2 -1
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/entries/TeamCard.js +1 -6
- package/dist/ui/layout/status-detail-layout.js +12 -2
- package/dist/ui/layout/status-layout.d.ts +4 -2
- package/dist/ui/layout/status-layout.js +24 -1
- package/dist/ui/overlays/HelpOverlay.js +1 -2
- package/package.json +2 -2
- package/dist/ui/ModeChip.d.ts +0 -18
- package/dist/ui/ModeChip.js +0 -24
package/dist/fast/wiring.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* THIS CLASS EXISTS FOR A BUDGET REASON, NOT AN AESTHETIC ONE (C-12 / RV-5).
|
|
8
8
|
* `CLAUDE.md` caps a source file at 1000 lines and `controller.ts` was at 960
|
|
9
9
|
* before this feature, which handed it nine new responsibilities. `FastWiring`
|
|
10
|
-
* owns the flag pair, the tier cache, the reviewer's lifetime and the
|
|
11
|
-
*
|
|
10
|
+
* owns the flag pair, the tier cache, the reviewer's lifetime and the shared
|
|
11
|
+
* fail-fast review transport; `AgentController` keeps one field and four thin
|
|
12
12
|
* forwarders. It is still CLI-local and still holds no `ui/` import.
|
|
13
13
|
*
|
|
14
14
|
* TWO FLAGS AND ONE LIVE PREDICATE (§3.3), which is the shape every optional
|
|
@@ -64,7 +64,6 @@ export function offFastStatus() {
|
|
|
64
64
|
// `budgetReached` is CARRIED and callers must never re-derive it.
|
|
65
65
|
reviewBudget: 0,
|
|
66
66
|
budgetReached: false,
|
|
67
|
-
delegated: 0,
|
|
68
67
|
usage: { inputTokens: 0, outputTokens: 0 },
|
|
69
68
|
pricingUnknown: false,
|
|
70
69
|
inFlight: false,
|
|
@@ -86,7 +85,6 @@ export class FastWiring {
|
|
|
86
85
|
tier;
|
|
87
86
|
/** Warned once per TRANSITION, so a broken tier does not shout every turn. */
|
|
88
87
|
lastReportedReason = null;
|
|
89
|
-
delegated = 0;
|
|
90
88
|
constructor(deps) {
|
|
91
89
|
this.deps = deps;
|
|
92
90
|
this.tier = resolveFastTier(deps.getConfig(), deps.hasKey);
|
|
@@ -168,26 +166,15 @@ export class FastWiring {
|
|
|
168
166
|
* §3.3's live predicate. THE THIRD TERM IS THE POINT - see the header.
|
|
169
167
|
*
|
|
170
168
|
* `FastReviewer.isSelfDisabled()` is DELIBERATELY NOT a fourth term. Folding
|
|
171
|
-
* it in would look tidier and would break something else:
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
169
|
+
* it in would look tidier and would break something else: per-child reviewers
|
|
170
|
+
* gate on `available()` too, so three consecutive REVIEW failures would
|
|
171
|
+
* silently stop every child's reviews as well - turning a diagnostic signal
|
|
172
|
+
* into a behaviour change for every dispatch. The two facts are reported
|
|
173
|
+
* separately on `FastSnapshot` instead (`live` / `selfDisabled`).
|
|
176
174
|
*/
|
|
177
175
|
available() {
|
|
178
176
|
return this.registered && this.enabled && this.tier.ok;
|
|
179
177
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Whether `model: "fast"` on `task` is honoured right now.
|
|
182
|
-
*
|
|
183
|
-
* Evaluated AT DISPATCH TIME, never snapshot (§3.4 / RV-3). `fast.delegate:
|
|
184
|
-
* false` and "the tier stopped resolving twenty minutes ago" are the same
|
|
185
|
-
* observable to the model - a downgrade, counted and reported - which is the
|
|
186
|
-
* honest reading of both.
|
|
187
|
-
*/
|
|
188
|
-
delegationAvailable() {
|
|
189
|
-
return this.available() && this.deps.getConfig().fast.delegate === true;
|
|
190
|
-
}
|
|
191
178
|
setEnabled(enabled) {
|
|
192
179
|
this.enabled = enabled;
|
|
193
180
|
this.deps.onPromptChanged();
|
|
@@ -233,40 +220,23 @@ export class FastWiring {
|
|
|
233
220
|
return describeFastTierProblem(this.tier, fastProviderOf(this.deps.getConfig()));
|
|
234
221
|
}
|
|
235
222
|
// -----------------------------------------------------------------------
|
|
236
|
-
//
|
|
223
|
+
// The tier's transport, shared with per-child reviewers (§3.4)
|
|
237
224
|
// -----------------------------------------------------------------------
|
|
238
|
-
/**
|
|
239
|
-
* The `ModelRef` and thinking level one subagent tier runs on.
|
|
240
|
-
*
|
|
241
|
-
* `'fast'` FALLS BACK TO MAIN when delegation is not available, so the two
|
|
242
|
-
* guards agree BY CONSTRUCTION: the normalizer can never produce a
|
|
243
|
-
* `tier: 'fast'` spec this factory would refuse, and this factory can never
|
|
244
|
-
* build a child against a `ModelRef` the tier no longer resolves (RV-3).
|
|
245
|
-
*/
|
|
246
|
-
resolveTier(tier) {
|
|
247
|
-
const config = this.deps.getConfig();
|
|
248
|
-
if (tier === 'fast' && this.delegationAvailable() && this.tier.ok) {
|
|
249
|
-
return { ref: this.tier.ref, thinkingLevel: this.tier.thinkingLevel, role: 'fast' };
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
ref: {
|
|
253
|
-
providerId: config.provider,
|
|
254
|
-
modelId: config.model,
|
|
255
|
-
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
256
|
-
},
|
|
257
|
-
thinkingLevel: config.thinkingLevel,
|
|
258
|
-
role: 'main',
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
/** The resolved fast `ModelRef`, or `null` - for per-tier cost lookup (§3.6). */
|
|
225
|
+
/** The resolved fast `ModelRef`, or `null` - for cost lookups (§3.6). */
|
|
262
226
|
fastRef() {
|
|
263
227
|
return this.tier.ok ? this.tier.ref : null;
|
|
264
228
|
}
|
|
265
|
-
/**
|
|
266
|
-
*
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
229
|
+
/**
|
|
230
|
+
* The fail-fast review transport, exposed so PER-CHILD reviewers share ONE
|
|
231
|
+
* registry instead of building one each - the same adapter-state reuse
|
|
232
|
+
* argument `TeamRuntime` makes about the lead's `ProviderRegistry`.
|
|
233
|
+
*
|
|
234
|
+
* Lazy: a session that never reviews allocates nothing (I-2). The retry
|
|
235
|
+
* policy is `FAST_RETRY_POLICY`, deliberately not the user's `retry` config
|
|
236
|
+
* - see `getFastRegistry()` for the reason.
|
|
237
|
+
*/
|
|
238
|
+
completeViaFastRegistry(providerId, request) {
|
|
239
|
+
return this.getFastRegistry().complete(providerId, request);
|
|
270
240
|
}
|
|
271
241
|
// -----------------------------------------------------------------------
|
|
272
242
|
// Reviewer lifetime
|
|
@@ -302,7 +272,6 @@ export class FastWiring {
|
|
|
302
272
|
reviews: this.reviewer.reviewCount(),
|
|
303
273
|
reviewBudget: this.reviewer.budgetLimit(),
|
|
304
274
|
budgetReached: this.reviewer.isBudgetReached(),
|
|
305
|
-
delegated: this.delegated,
|
|
306
275
|
usage: this.reviewer.sessionUsage(),
|
|
307
276
|
// UNKNOWN PRICING IS NOT ZERO PRICING (C-11 / RV-4). The fast tier is
|
|
308
277
|
// precisely where an unrecognised model id is likely, and rendering it as
|
|
@@ -128,7 +128,9 @@ const todo = shape({
|
|
|
128
128
|
});
|
|
129
129
|
const subagent = shape({
|
|
130
130
|
label: string, description: string,
|
|
131
|
-
//
|
|
131
|
+
// OPTIONAL IN BOTH DIRECTIONS: sessions written since main-agent parity
|
|
132
|
+
// have no tier at all, and older archives may carry `main` or `fast` from
|
|
133
|
+
// the removed per-child fast dispatch. Renderers treat absence as main.
|
|
132
134
|
tier: optional(enumeration('main', 'fast')),
|
|
133
135
|
phase: enumeration('queued', 'starting', 'thinking', 'tool', 'waiting', 'done', 'failed', 'aborted'),
|
|
134
136
|
startedAt: optional(number), endedAt: optional(number), turns: number, toolCalls: number,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-child fast review (main-agent parity).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope
|
|
5
|
+
* (`glyphs.test.ts`), so no literal in this tree may hold a non-ASCII byte.
|
|
6
|
+
*
|
|
7
|
+
* THE LEAD HAS A FAST REVIEWER; A CHILD NOW HAS ONE TOO. The requirement is
|
|
8
|
+
* that "when the main agent pulls up subagents, all subagents are as capable as
|
|
9
|
+
* the main agent", and the fast/slow cooperation is part of what the main agent
|
|
10
|
+
* has: a cheap second model periodically critiques the run and steers a
|
|
11
|
+
* `<fast_review>` block into the conversation. A child runs the lead's own
|
|
12
|
+
* model, and this module gives that run the same second opinion.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS SHARED, AND WHAT IS PER CHILD:
|
|
15
|
+
* - SHARED: the tier resolution (`resolveFastTier` on the LIVE config), the
|
|
16
|
+
* fail-fast review transport (`FastWiring.completeViaFastRegistry` - one
|
|
17
|
+
* lazy registry for the whole session, the same adapter-state reuse
|
|
18
|
+
* argument `TeamRuntime` makes about the lead's `ProviderRegistry`), and
|
|
19
|
+
* the `fast.*` policy keys. A `/fast off` mid-dispatch therefore stops
|
|
20
|
+
* child reviews at the same instant it stops the lead's.
|
|
21
|
+
* - PER CHILD: one `FastReviewer` instance, its own turn-frame ring, its own
|
|
22
|
+
* session budget, its own goal (the spec's description).
|
|
23
|
+
*
|
|
24
|
+
* THE REVIEWER'S PROTOCOL IS UNCHANGED, AND THAT IS THE POINT. `FastReviewer`
|
|
25
|
+
* steers from exactly one place (the `tool_execution_end` that brings the batch
|
|
26
|
+
* to zero), never steers into a requested abort, and proves delivery at the
|
|
27
|
+
* next `turn_start`. The two guards need host-side facts, and a child supplies
|
|
28
|
+
* them differently than the lead:
|
|
29
|
+
*
|
|
30
|
+
* - `userSteerCount` is ALWAYS ZERO: no human can steer a child. That makes
|
|
31
|
+
* the reviewer's `clearAllQueues` provably safe by construction (D-21).
|
|
32
|
+
* - `isAbortRequested` comes from the HANDLE-level abort flag that
|
|
33
|
+
* `TeamRuntime` sets BEFORE calling `agent.abort()`. Reading the agent
|
|
34
|
+
* alone is not enough: an Esc lands as `abortAll()`, and without the flag
|
|
35
|
+
* the reviewer would race the abort it was told about.
|
|
36
|
+
* - `emit` and `notify` LOG INSTEAD OF RENDERING. A child has no transcript
|
|
37
|
+
* card and no status chip; the team panel row and the dispatch report are
|
|
38
|
+
* where a child's internals belong, and review spend is bounded by the
|
|
39
|
+
* same `fast.reviewMaxPerSession` budget the lead obeys.
|
|
40
|
+
*/
|
|
41
|
+
import type { AgentEvent, AssistantMessage, LLMRequest } from '@aragon-agent/core';
|
|
42
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
43
|
+
import type { CliConfig } from '../config/schema.js';
|
|
44
|
+
/**
|
|
45
|
+
* The slice of a child `Agent` the reviewer needs beyond what
|
|
46
|
+
* `SubagentAgentLike` already carries. Core's `Agent` satisfies this with its
|
|
47
|
+
* own `steer` / `clearAllQueues`; a test stub satisfies it the same way.
|
|
48
|
+
*/
|
|
49
|
+
export interface ChildReviewerAgent {
|
|
50
|
+
subscribe(listener: (event: AgentEvent) => void): () => void;
|
|
51
|
+
steer(text: string): void;
|
|
52
|
+
clearAllQueues(): void;
|
|
53
|
+
}
|
|
54
|
+
/** Everything one child reviewer needs to know about its child. */
|
|
55
|
+
export interface ChildReviewerRequest {
|
|
56
|
+
label: string;
|
|
57
|
+
/** The spec's description: the goal the critique judges the run against. */
|
|
58
|
+
goal: string;
|
|
59
|
+
agent: ChildReviewerAgent;
|
|
60
|
+
/** A live phase, not a terminal one - the lead's `isRunning` equivalent. */
|
|
61
|
+
isRunning: () => boolean;
|
|
62
|
+
/** Set by the HANDLE-level abort, synchronously before `agent.abort()`. */
|
|
63
|
+
isAbortRequested: () => boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Everything the reviewer needs from the session, all of it shared. */
|
|
66
|
+
export interface ChildReviewerDeps {
|
|
67
|
+
/** Read LIVE on every call - a mid-dispatch `/fast off` must reach children. */
|
|
68
|
+
getConfig: () => CliConfig;
|
|
69
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
70
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
71
|
+
/** `FastWiring.completeViaFastRegistry` - the shared fail-fast transport. */
|
|
72
|
+
complete: (providerId: string, request: LLMRequest) => Promise<AssistantMessage>;
|
|
73
|
+
/** `FastWiring.available()` - registered AND enabled AND resolving. */
|
|
74
|
+
available: () => boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface ChildReviewerHandle {
|
|
77
|
+
dispose(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What the controller hands `subagent.ts`, so a child's reviewer is built from
|
|
81
|
+
* the lead's own wiring rather than a second tier resolver.
|
|
82
|
+
*
|
|
83
|
+
* `active` AND `create` ARE SEPARATE ON PURPOSE: the child's system prompt
|
|
84
|
+
* must be composed BEFORE the agent exists, and `create` needs the agent. The
|
|
85
|
+
* two must answer the same question at the same instant, so both derive from
|
|
86
|
+
* `FastWiring.available() && config.fast.review` and the caller reads `active`
|
|
87
|
+
* once per child.
|
|
88
|
+
*/
|
|
89
|
+
export interface ChildReviewProvider {
|
|
90
|
+
/** Whether per-child review is live RIGHT NOW (tier live AND `fast.review`). */
|
|
91
|
+
active: () => boolean;
|
|
92
|
+
/** The resolved fast model id, for the child's `<fast_tier>` prompt block. */
|
|
93
|
+
modelId: () => string;
|
|
94
|
+
/** Build one reviewer for a child; `undefined` when not active. */
|
|
95
|
+
create: (req: ChildReviewerRequest) => ChildReviewerHandle | undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build one reviewer for one child, or `undefined` when the session has no
|
|
99
|
+
* live review to give it.
|
|
100
|
+
*
|
|
101
|
+
* `undefined` RATHER THAN A NO-OP REVIEWER, for the same reason
|
|
102
|
+
* `createChildContextManager` declines: the caller then wires nothing at all,
|
|
103
|
+
* and a session with the fast tier off builds the child it always built.
|
|
104
|
+
*/
|
|
105
|
+
export declare function createChildReviewer(req: ChildReviewerRequest, deps: ChildReviewerDeps): ChildReviewerHandle | undefined;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-child fast review (main-agent parity).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope
|
|
5
|
+
* (`glyphs.test.ts`), so no literal in this tree may hold a non-ASCII byte.
|
|
6
|
+
*
|
|
7
|
+
* THE LEAD HAS A FAST REVIEWER; A CHILD NOW HAS ONE TOO. The requirement is
|
|
8
|
+
* that "when the main agent pulls up subagents, all subagents are as capable as
|
|
9
|
+
* the main agent", and the fast/slow cooperation is part of what the main agent
|
|
10
|
+
* has: a cheap second model periodically critiques the run and steers a
|
|
11
|
+
* `<fast_review>` block into the conversation. A child runs the lead's own
|
|
12
|
+
* model, and this module gives that run the same second opinion.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IS SHARED, AND WHAT IS PER CHILD:
|
|
15
|
+
* - SHARED: the tier resolution (`resolveFastTier` on the LIVE config), the
|
|
16
|
+
* fail-fast review transport (`FastWiring.completeViaFastRegistry` - one
|
|
17
|
+
* lazy registry for the whole session, the same adapter-state reuse
|
|
18
|
+
* argument `TeamRuntime` makes about the lead's `ProviderRegistry`), and
|
|
19
|
+
* the `fast.*` policy keys. A `/fast off` mid-dispatch therefore stops
|
|
20
|
+
* child reviews at the same instant it stops the lead's.
|
|
21
|
+
* - PER CHILD: one `FastReviewer` instance, its own turn-frame ring, its own
|
|
22
|
+
* session budget, its own goal (the spec's description).
|
|
23
|
+
*
|
|
24
|
+
* THE REVIEWER'S PROTOCOL IS UNCHANGED, AND THAT IS THE POINT. `FastReviewer`
|
|
25
|
+
* steers from exactly one place (the `tool_execution_end` that brings the batch
|
|
26
|
+
* to zero), never steers into a requested abort, and proves delivery at the
|
|
27
|
+
* next `turn_start`. The two guards need host-side facts, and a child supplies
|
|
28
|
+
* them differently than the lead:
|
|
29
|
+
*
|
|
30
|
+
* - `userSteerCount` is ALWAYS ZERO: no human can steer a child. That makes
|
|
31
|
+
* the reviewer's `clearAllQueues` provably safe by construction (D-21).
|
|
32
|
+
* - `isAbortRequested` comes from the HANDLE-level abort flag that
|
|
33
|
+
* `TeamRuntime` sets BEFORE calling `agent.abort()`. Reading the agent
|
|
34
|
+
* alone is not enough: an Esc lands as `abortAll()`, and without the flag
|
|
35
|
+
* the reviewer would race the abort it was told about.
|
|
36
|
+
* - `emit` and `notify` LOG INSTEAD OF RENDERING. A child has no transcript
|
|
37
|
+
* card and no status chip; the team panel row and the dispatch report are
|
|
38
|
+
* where a child's internals belong, and review spend is bounded by the
|
|
39
|
+
* same `fast.reviewMaxPerSession` budget the lead obeys.
|
|
40
|
+
*/
|
|
41
|
+
import { getLogger } from '../logging/logger.js';
|
|
42
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
43
|
+
import { FastReviewer } from '../fast/reviewer.js';
|
|
44
|
+
/**
|
|
45
|
+
* A snapshot for the reviewer's self-disable announcement. The lead's
|
|
46
|
+
* `FastWiring.snapshot()` builds the same shape from its own reviewer; a child
|
|
47
|
+
* has no wiring, so this builds it from the reviewer the module itself holds.
|
|
48
|
+
*
|
|
49
|
+
* `pricingUnknown` is FALSE because the child reviewer never prices anything:
|
|
50
|
+
* it logs counts, not currency, and an unpriced model id therefore cannot lie
|
|
51
|
+
* here the way it can on the lead's status line (C-11).
|
|
52
|
+
*/
|
|
53
|
+
function buildSnapshot(reviewer, tier) {
|
|
54
|
+
return {
|
|
55
|
+
live: tier.ok,
|
|
56
|
+
selfDisabled: reviewer?.isSelfDisabled() ?? false,
|
|
57
|
+
model: tier.ok ? tier.ref.modelId : '',
|
|
58
|
+
sameAsMain: tier.ok ? tier.sameAsMain : false,
|
|
59
|
+
reviews: reviewer?.reviewCount() ?? 0,
|
|
60
|
+
reviewBudget: reviewer?.budgetLimit() ?? 0,
|
|
61
|
+
budgetReached: reviewer?.isBudgetReached() ?? false,
|
|
62
|
+
usage: reviewer?.sessionUsage() ?? { inputTokens: 0, outputTokens: 0 },
|
|
63
|
+
pricingUnknown: false,
|
|
64
|
+
inFlight: reviewer?.isReviewInFlight() ?? false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build one reviewer for one child, or `undefined` when the session has no
|
|
69
|
+
* live review to give it.
|
|
70
|
+
*
|
|
71
|
+
* `undefined` RATHER THAN A NO-OP REVIEWER, for the same reason
|
|
72
|
+
* `createChildContextManager` declines: the caller then wires nothing at all,
|
|
73
|
+
* and a session with the fast tier off builds the child it always built.
|
|
74
|
+
*/
|
|
75
|
+
export function createChildReviewer(req, deps) {
|
|
76
|
+
// GATED ON THE SAME THREE FACTS THE LEAD'S REVIEWER IS: the wiring is live
|
|
77
|
+
// (`available()`), the policy allows reviews (`fast.review`), and the tier
|
|
78
|
+
// resolves against the LIVE config. Re-checked, never snapshotted, so a
|
|
79
|
+
// mid-dispatch `/fast off` or a settings-screen model edit reaches the next
|
|
80
|
+
// child even when it cannot stop one already running.
|
|
81
|
+
const tier = resolveFastTier(deps.getConfig(), deps.hasKey);
|
|
82
|
+
if (!deps.available() || !deps.getConfig().fast.review || !tier.ok)
|
|
83
|
+
return undefined;
|
|
84
|
+
const log = getLogger().child('fast');
|
|
85
|
+
let reviewer = null;
|
|
86
|
+
const built = new FastReviewer({
|
|
87
|
+
subscribe: (listener) => req.agent.subscribe(listener),
|
|
88
|
+
complete: deps.complete,
|
|
89
|
+
steer: (text) => req.agent.steer(text),
|
|
90
|
+
isRunning: req.isRunning,
|
|
91
|
+
isAbortRequested: req.isAbortRequested,
|
|
92
|
+
userSteerCount: () => 0,
|
|
93
|
+
clearAllQueues: () => req.agent.clearAllQueues(),
|
|
94
|
+
getTier: () => resolveFastTier(deps.getConfig(), deps.hasKey),
|
|
95
|
+
getConfig: () => deps.getConfig().fast,
|
|
96
|
+
available: () => deps.available() && resolveFastTier(deps.getConfig(), deps.hasKey).ok,
|
|
97
|
+
getApiKey: (providerId) => deps.getApiKey(providerId, 'fast'),
|
|
98
|
+
emit: (event) => {
|
|
99
|
+
log.info('child_review', { label: req.label, type: event.type });
|
|
100
|
+
},
|
|
101
|
+
snapshot: () => buildSnapshot(reviewer, tier),
|
|
102
|
+
// A CHILD HAS NO TRANSCRIPT. The lead's warnings ride `notify` into a card
|
|
103
|
+
// the user reads; the same text from a background worker would claim the
|
|
104
|
+
// user's attention for an agent they cannot see. It is logged instead.
|
|
105
|
+
notify: (level, text) => {
|
|
106
|
+
log.warn('child_review_notice', { label: req.label, level, text });
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
reviewer = built;
|
|
110
|
+
built.setGoal(req.goal);
|
|
111
|
+
log.info('child_review_attached', {
|
|
112
|
+
label: req.label,
|
|
113
|
+
model: tier.ref.modelId,
|
|
114
|
+
reviewEveryTurns: deps.getConfig().fast.reviewEveryTurns,
|
|
115
|
+
});
|
|
116
|
+
return { dispose: () => built.dispose() };
|
|
117
|
+
}
|
package/dist/team/normalize.d.ts
CHANGED
|
@@ -17,32 +17,18 @@
|
|
|
17
17
|
* whether an optional install step succeeded.
|
|
18
18
|
*
|
|
19
19
|
* NEVER THROWS. Zero survivors is the caller's single hard failure.
|
|
20
|
+
*
|
|
21
|
+
* EVERY CHILD RUNS THE LEAD'S MODEL (main-agent parity): the former
|
|
22
|
+
* `model: "fast"` per-child tier was removed, so a spec that still carries a
|
|
23
|
+
* `model` key simply ignores it - there is no tier to downgrade TO, and the
|
|
24
|
+
* honest reading of a stale key is silence, not a report line about a
|
|
25
|
+
* capability that no longer exists.
|
|
20
26
|
*/
|
|
21
27
|
import type { SubagentSpec } from './types.js';
|
|
22
28
|
export interface NormalizedSpecs {
|
|
23
29
|
specs: SubagentSpec[];
|
|
24
30
|
/** How many entries the model asked for, before dropping and capping. */
|
|
25
31
|
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;
|
|
46
32
|
}
|
|
47
33
|
/**
|
|
48
34
|
* Repair and bound a raw `subagents` array.
|
|
@@ -55,10 +41,5 @@ export interface NormalizeOptions {
|
|
|
55
41
|
* @param max The resolved `team.maxSubagents`; further clamped to
|
|
56
42
|
* `TEAM_LIMITS.hardMaxSubagents` here so a hand-edited config file
|
|
57
43
|
* 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.
|
|
63
44
|
*/
|
|
64
|
-
export declare function normalizeSubagentSpecs(raw: unknown, max: number
|
|
45
|
+
export declare function normalizeSubagentSpecs(raw: unknown, max: number): NormalizedSpecs;
|
package/dist/team/normalize.js
CHANGED
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
* whether an optional install step succeeded.
|
|
18
18
|
*
|
|
19
19
|
* NEVER THROWS. Zero survivors is the caller's single hard failure.
|
|
20
|
+
*
|
|
21
|
+
* EVERY CHILD RUNS THE LEAD'S MODEL (main-agent parity): the former
|
|
22
|
+
* `model: "fast"` per-child tier was removed, so a spec that still carries a
|
|
23
|
+
* `model` key simply ignores it - there is no tier to downgrade TO, and the
|
|
24
|
+
* honest reading of a stale key is silence, not a report line about a
|
|
25
|
+
* capability that no longer exists.
|
|
20
26
|
*/
|
|
21
27
|
import { TEAM_LIMITS } from './limits.js';
|
|
22
28
|
function asString(value) {
|
|
@@ -76,21 +82,13 @@ function dedupeLabel(label, taken) {
|
|
|
76
82
|
* @param max The resolved `team.maxSubagents`; further clamped to
|
|
77
83
|
* `TEAM_LIMITS.hardMaxSubagents` here so a hand-edited config file
|
|
78
84
|
* 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.
|
|
84
85
|
*/
|
|
85
|
-
export function normalizeSubagentSpecs(raw, max
|
|
86
|
+
export function normalizeSubagentSpecs(raw, max) {
|
|
86
87
|
if (!Array.isArray(raw))
|
|
87
|
-
return { specs: [], requested: 0
|
|
88
|
+
return { specs: [], requested: 0 };
|
|
88
89
|
const requested = raw.length;
|
|
89
|
-
const fastAvailable = opts.fastAvailable === true;
|
|
90
90
|
const kept = [];
|
|
91
91
|
const taken = new Set();
|
|
92
|
-
/** Parallel to `kept`: did entry `i` ask for the fast tier? */
|
|
93
|
-
const wantedFast = [];
|
|
94
92
|
for (let i = 0; i < raw.length; i += 1) {
|
|
95
93
|
const item = raw[i];
|
|
96
94
|
if (!item || typeof item !== 'object')
|
|
@@ -102,25 +100,14 @@ export function normalizeSubagentSpecs(raw, max, opts = {}) {
|
|
|
102
100
|
const prompt = clamp(asString(src.prompt), TEAM_LIMITS.promptChars);
|
|
103
101
|
if (prompt.length === 0)
|
|
104
102
|
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);
|
|
111
103
|
kept.push({
|
|
112
104
|
label: dedupeLabel(slugLabel(src.label, kept.length), taken),
|
|
113
105
|
description,
|
|
114
106
|
prompt,
|
|
115
107
|
readOnly: src.readOnly === true,
|
|
116
|
-
tier: wantsFast && fastAvailable ? 'fast' : 'main',
|
|
117
108
|
});
|
|
118
109
|
}
|
|
119
110
|
const ceiling = Math.max(1, Math.min(Math.floor(max) || 1, TEAM_LIMITS.hardMaxSubagents));
|
|
120
111
|
const specs = kept.slice(0, ceiling);
|
|
121
|
-
|
|
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 };
|
|
112
|
+
return { specs, requested };
|
|
126
113
|
}
|
package/dist/team/prompt.d.ts
CHANGED
|
@@ -18,17 +18,6 @@ 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;
|
|
32
21
|
}
|
|
33
22
|
/**
|
|
34
23
|
* The lead's `<team_mode>` block. Under 1200 characters on purpose: it is paid
|
package/dist/team/prompt.js
CHANGED
|
@@ -44,19 +44,10 @@ export function buildTeamBlock(params) {
|
|
|
44
44
|
'',
|
|
45
45
|
'Each subagent starts with no memory of this conversation, so its prompt must',
|
|
46
46
|
'carry everything it needs: the goal, the files or areas it owns, and what to',
|
|
47
|
-
'report back. Give each one a disjoint set of files to write.
|
|
48
|
-
'
|
|
47
|
+
'report back. Give each one a disjoint set of files to write. Every subagent',
|
|
48
|
+
'runs on your own model. Subagents cannot dispatch further subagents and',
|
|
49
|
+
'cannot ask the user anything.',
|
|
49
50
|
'',
|
|
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
|
-
: []),
|
|
60
51
|
'You get one combined report when they all finish. Read it before deciding what',
|
|
61
52
|
'to do next, and tell the user what the team found in your own words.',
|
|
62
53
|
'</team_mode>',
|
package/dist/team/report.d.ts
CHANGED
|
@@ -23,26 +23,11 @@
|
|
|
23
23
|
import type { ModelCost } from '@aragon-agent/core';
|
|
24
24
|
import type { DispatchOutcome, SubagentRun } from './types.js';
|
|
25
25
|
export interface ReportOptions {
|
|
26
|
-
/** The lead's model cost table, so the header can state real spend (R-5).
|
|
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).
|
|
26
|
+
/** The lead's model cost table, so the header can state real spend (R-5).
|
|
39
27
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
* would make the feature look free while it is spending money.
|
|
44
|
-
*/
|
|
45
|
-
fastPricingUnknown?: boolean;
|
|
28
|
+
* THE ONE TABLE, because every child runs the lead's own model
|
|
29
|
+
* (main-agent parity): one tier, one price, one honest total. */
|
|
30
|
+
cost?: ModelCost;
|
|
46
31
|
}
|
|
47
32
|
/**
|
|
48
33
|
* Cut `text` to at most `maxBytes` WITHOUT splitting a UTF-8 sequence.
|
package/dist/team/report.js
CHANGED
|
@@ -106,28 +106,6 @@ 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
|
-
}
|
|
131
109
|
for (const conflict of findFileConflicts(outcome.runs)) {
|
|
132
110
|
const who = conflict.labels.map((l) => `"${l}"`).join(' and ');
|
|
133
111
|
lines.push(`WARNING: agents ${who} both wrote ${conflict.path} - review before trusting either.`);
|
|
@@ -139,10 +117,6 @@ function buildSection(run) {
|
|
|
139
117
|
// the only place the lead learns that a child's first attempt died before it
|
|
140
118
|
// did anything (F-4).
|
|
141
119
|
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]' : '';
|
|
146
120
|
// `compacted N` in the same position `retried Nx` uses, and for the same
|
|
147
121
|
// reason (context-auto-compaction-hardening §3.4.4 / W3): this is the ONLY
|
|
148
122
|
// place the lead learns that a child's own history was summarized mid-task, so
|
|
@@ -150,7 +124,7 @@ function buildSection(run) {
|
|
|
150
124
|
// rather than an unexplained one. Absent when it never happened.
|
|
151
125
|
const compacted = run.compactions ? `, compacted ${run.compactions}` : '';
|
|
152
126
|
const head = [
|
|
153
|
-
`### ${run.label}
|
|
127
|
+
`### ${run.label} "${run.description}" ${statusBadge(run)} ${elapsed(run)}, ` +
|
|
154
128
|
`${run.turns} turns, ${run.toolCalls} tools${retried}${compacted}`,
|
|
155
129
|
];
|
|
156
130
|
if (run.filesTouched.length > 0)
|
package/dist/team/runtime.d.ts
CHANGED
|
@@ -56,11 +56,11 @@ export interface TeamRuntimeDeps {
|
|
|
56
56
|
/** Test seam (§8.1): a stub factory keeps the whole scheduler off the network. */
|
|
57
57
|
agentFactory?: SubagentAgentFactory;
|
|
58
58
|
/**
|
|
59
|
-
* Per-
|
|
60
|
-
* `SubagentDeps
|
|
61
|
-
*
|
|
59
|
+
* Per-child fast review (main-agent parity), passed straight through to
|
|
60
|
+
* `SubagentDeps`. ABSENT means the session has no fast wiring and children
|
|
61
|
+
* are built exactly as they were before the reviewers existed.
|
|
62
62
|
*/
|
|
63
|
-
|
|
63
|
+
review?: SubagentDeps['review'];
|
|
64
64
|
/**
|
|
65
65
|
* Per-child context compaction (context-auto-compaction-hardening §3.4 / W3).
|
|
66
66
|
*
|
|
@@ -104,12 +104,7 @@ export declare class TeamRuntime {
|
|
|
104
104
|
isBusy(): boolean;
|
|
105
105
|
/** The live roster, for `/team` status. `null` when nothing is dispatching. */
|
|
106
106
|
snapshot(): TeamSnapshot | null;
|
|
107
|
-
dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal
|
|
108
|
-
/** Carried through from the normalizer so the report can state it (§3.4 /
|
|
109
|
-
* R-6). Optional, so every existing caller compiles unchanged. */
|
|
110
|
-
extra?: {
|
|
111
|
-
downgraded?: number;
|
|
112
|
-
}): Promise<DispatchOutcome>;
|
|
107
|
+
dispatch(specs: SubagentSpec[], requested: number, signal?: AbortSignal): Promise<DispatchOutcome>;
|
|
113
108
|
/** One slot of the pool: take the next index until the list is exhausted. */
|
|
114
109
|
private worker;
|
|
115
110
|
/**
|