@aragon-agent/cli 0.6.21 → 0.6.23

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.
@@ -135,6 +135,16 @@ export interface SubagentHooks {
135
135
  * so the phase read at fire time is current.
136
136
  */
137
137
  onActivity?: () => void;
138
+ /**
139
+ * THE TURN CAP AS AN INSPECTION TRIGGER (F-8 A'2). Called ONCE, at the turn
140
+ * that crossed `maxTurnsPerSubagent`, ONLY when the dispatch supervisor is
141
+ * live - the runtime wires this hook under the same gate as
142
+ * `overseerActive`, so its presence IS the supervised/legacy branch. The
143
+ * child is NOT stopped: the supervisor decides wait / nudge / replace /
144
+ * abandon through the same `inspectAndApply` path every other trigger
145
+ * uses, and a child it lets run carries no `truncated` flag.
146
+ */
147
+ onTurnCapReached?: (label: string) => void;
138
148
  }
139
149
  export interface SubagentHandle {
140
150
  spec: SubagentSpec;
@@ -359,12 +359,26 @@ export function createSubagent(spec, deps, hooks) {
359
359
  // not runaway - it is working.
360
360
  const turnCap = deps.config.team.maxTurnsPerSubagent;
361
361
  if (turnCap > 0 && run.turns >= turnCap) {
362
- run.truncated = true;
363
- // THE FLAG FIRST, exactly like `handle.abort()`: the child's
364
- // reviewer reads `abortRequested` as its guard-1 fact, and a
365
- // turn-cap stop must be visible to it in the same tick.
366
- abortRequested = true;
367
- agent.abort();
362
+ if (hooks.onTurnCapReached !== undefined) {
363
+ // A'2, D-4 for the turn cap: SUPERVISED means the ceiling is
364
+ // an inspection trigger, not a death sentence. ONE look at the
365
+ // crossing turn (`===`), never a per-turn storm; the child
366
+ // keeps working unless the supervisor decides replace or
367
+ // abandon, and a child that keeps running carries NO
368
+ // `truncated` flag - the report must not call it partial.
369
+ if (run.turns === turnCap)
370
+ hooks.onTurnCapReached(spec.label);
371
+ }
372
+ else {
373
+ // LEGACY regime (no supervisor for this dispatch): the hard
374
+ // stop, byte for byte what it always was.
375
+ run.truncated = true;
376
+ // THE FLAG FIRST, exactly like `handle.abort()`: the child's
377
+ // reviewer reads `abortRequested` as its guard-1 fact, and a
378
+ // turn-cap stop must be visible to it in the same tick.
379
+ abortRequested = true;
380
+ agent.abort();
381
+ }
368
382
  }
369
383
  break;
370
384
  }
@@ -39,6 +39,13 @@ export interface TaskToolDeps {
39
39
  activeProvider: () => string;
40
40
  /** The lead's cost table, so the report header states real spend (R-5). */
41
41
  modelCost: () => ModelCost | undefined;
42
+ /**
43
+ * The FAST tier's cost table, so the report's supervisor spend line states
44
+ * real spend (subagent-overseer-v2 D-7 / R-P1-2). OPTIONAL and returning
45
+ * `undefined`: an unpriced fast model renders `pricing unknown`, never a
46
+ * fabricated `$0.00` - the C-11 / RV-4 rule.
47
+ */
48
+ fastModelCost?: () => ModelCost | undefined;
42
49
  /**
43
50
  * Run the dispatch with the LEAD's idle watchdog paused, `try/finally` inside.
44
51
  *
@@ -123,8 +123,10 @@ export function createTaskTool(deps) {
123
123
  // shape, and D-14 one level up). Every child ran the lead's own model,
124
124
  // so the lead's cost table is the one honest price for the total.
125
125
  const cost = deps.modelCost();
126
+ const fastCost = deps.fastModelCost?.();
126
127
  return textResult(buildDispatchReport(outcome, {
127
128
  ...(cost ? { cost } : {}),
129
+ ...(fastCost ? { fastCost } : {}),
128
130
  }));
129
131
  },
130
132
  };
@@ -159,6 +159,22 @@ export interface SubagentRun {
159
159
  * report reads "this row is the Nth body of the same brief".
160
160
  */
161
161
  replacements?: number;
162
+ /**
163
+ * The most recent supervisor decision applied to this child
164
+ * (subagent-overseer-v2 section 6). TRANSIENT, never persisted - the
165
+ * same rule as `activity` / `activityArgs`; a resumed session has no
166
+ * live dispatch.
167
+ *
168
+ * `at` is ABSOLUTE epoch ms, the same units as `startedAt` / `endedAt`
169
+ * (R-P2-3), so the panel renders `nudged 2m` with one subtraction and
170
+ * no second clock convention. `reasonHead` <= 60 chars, for the UI
171
+ * badge line only.
172
+ */
173
+ lastIntervention?: {
174
+ action: OverseerAction;
175
+ at: number;
176
+ reasonHead: string;
177
+ };
162
178
  }
163
179
  /** What the supervisor may do with one inspected child. */
164
180
  export type OverseerAction = 'wait' | 'nudge' | 'replace' | 'abandon';
@@ -184,8 +200,15 @@ export interface OverseerIntervention {
184
200
  at: number;
185
201
  action: OverseerAction;
186
202
  reason: string;
187
- /** What the trigger was: event stall, wall-clock check, or post-mortem. */
188
- trigger: 'silence' | 'clock' | 'postmortem';
203
+ /**
204
+ * What the trigger was: event stall, wall-clock check, the child's turn
205
+ * cap (F-8 A'2 - an inspection the supervisor then ruled on), post-mortem,
206
+ * or the supervisor going QUIET (subagent-overseer-v2 D-10) - the look
207
+ * budget ran out and supervision stopped waking this child. A VALUE OF
208
+ * THE UNION, never a reason prefix: a human-readable sentence is not a
209
+ * machine contract (the `run.error` rule).
210
+ */
211
+ trigger: 'silence' | 'clock' | 'turncap' | 'postmortem' | 'quiet';
189
212
  }
190
213
  /**
191
214
  * A terminal supervisor decision handed to the WORKER LOOP, which is the only
@@ -231,6 +254,25 @@ export interface DispatchOutcome {
231
254
  * total priced at the lead's table is the honest accounting.
232
255
  */
233
256
  usage: TokenUsage;
257
+ /**
258
+ * The supervisor's own fast-tier spend for this dispatch
259
+ * (subagent-overseer-v2 D-7). Absent when nothing supervised the
260
+ * dispatch; read straight off the provider at dispatch end so there is
261
+ * exactly one accounting of it.
262
+ */
263
+ overseerUsage?: TokenUsage;
264
+ /** Inspection calls made, including ones that failed softly. */
265
+ overseerCalls?: number;
266
+ /**
267
+ * EDGE-LATCHED, once per dispatch (D-10): the fast tier was missing at
268
+ * least one cadence tick and the children waited UNASSISTED - announced,
269
+ * never killed. The per-child wait events of a degraded tick
270
+ * deliberately do NOT enter `interventions`; this flag plus the tick
271
+ * count below are the report's aggregate note.
272
+ */
273
+ overseerDegraded?: boolean;
274
+ /** How many cadence ticks ran unassisted (the aggregate note's number). */
275
+ overseerDegradedTicks?: number;
234
276
  }
235
277
  /**
236
278
  * What the UI renders: a cheap immutable projection, rebuilt per event.
package/dist/ui/App.js CHANGED
@@ -664,6 +664,20 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
664
664
  const snap = controller.getTeamSnapshot();
665
665
  if (snap)
666
666
  dispatch({ type: 'teamUpdate', snapshot: snap });
667
+ // G-4 / AC-5 (subagent-overseer-v2): a TERMINAL verdict is the
668
+ // one decision the user must not discover from the final report
669
+ // - it removes work they watched running. wait and nudge stay
670
+ // panel-only so the transcript gains no per-tick noise.
671
+ if (event.decision.action === 'replace' || event.decision.action === 'abandon') {
672
+ const verb = event.decision.action === 'replace' ? 'replaced' : 'abandoned';
673
+ const level = event.decision.action === 'replace' ? 'info' : 'warn';
674
+ const reason = event.decision.reason.slice(0, 120);
675
+ dispatch({
676
+ type: 'notice',
677
+ level,
678
+ text: `team supervisor ${verb} ${event.label} - ${reason}`,
679
+ });
680
+ }
667
681
  break;
668
682
  }
669
683
  case 'usage': {
@@ -57,4 +57,26 @@ export declare function activityBudget(cols: number): number;
57
57
  * columns and break at 80.
58
58
  */
59
59
  export declare function activityLine(run: SubagentRun, cols: number): string;
60
+ /**
61
+ * The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
62
+ * last did to THIS child and how long ago, e.g. `nudged 2m03s`.
63
+ *
64
+ * `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
65
+ * `startedAt`, so this is one subtraction - no second clock convention.
66
+ */
67
+ export declare function overseerBadge(run: SubagentRun, at: number): string;
68
+ /**
69
+ * The activity column WITH the badge composed in (AC-5 / risk table): the
70
+ * badge rides the SAME `activityBudget(cols)` allocation the prose uses, so
71
+ * a row can never grow past its terminal; on a narrow terminal the badge
72
+ * degrades to the action word alone - the who-acted fact outranks the
73
+ * what-they-are-doing tail exactly when there is room for only one.
74
+ */
75
+ export declare function activityWithBadge(run: SubagentRun, cols: number, at: number): string;
76
+ /**
77
+ * The panel's one supervisor status line (AC-5): the most recent intervention
78
+ * across the roster, human-phrased. Empty when the supervisor has not acted
79
+ * yet - furniture that says nothing is worse than no line.
80
+ */
81
+ export declare function supervisorStatusLine(runs: SubagentRun[], at: number): string;
60
82
  export declare function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }: TeamPanelProps): React.ReactElement | null;
@@ -101,6 +101,69 @@ export function activityLine(run, cols) {
101
101
  return 'aborted';
102
102
  }
103
103
  }
104
+ /** The past-tense word the supervisor badge and status line render. */
105
+ function interventionWord(action) {
106
+ switch (action) {
107
+ case 'nudge':
108
+ return 'nudged';
109
+ case 'replace':
110
+ return 'replaced';
111
+ case 'abandon':
112
+ return 'abandoned';
113
+ default:
114
+ return 'waited';
115
+ }
116
+ }
117
+ /**
118
+ * The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
119
+ * last did to THIS child and how long ago, e.g. `nudged 2m03s`.
120
+ *
121
+ * `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
122
+ * `startedAt`, so this is one subtraction - no second clock convention.
123
+ */
124
+ export function overseerBadge(run, at) {
125
+ const last = run.lastIntervention;
126
+ if (last === undefined)
127
+ return '';
128
+ return `${interventionWord(last.action)} ${formatDuration(Math.max(0, at - last.at))}`;
129
+ }
130
+ /**
131
+ * The activity column WITH the badge composed in (AC-5 / risk table): the
132
+ * badge rides the SAME `activityBudget(cols)` allocation the prose uses, so
133
+ * a row can never grow past its terminal; on a narrow terminal the badge
134
+ * degrades to the action word alone - the who-acted fact outranks the
135
+ * what-they-are-doing tail exactly when there is room for only one.
136
+ */
137
+ export function activityWithBadge(run, cols, at) {
138
+ const last = run.lastIntervention;
139
+ if (last === undefined)
140
+ return activityLine(run, cols);
141
+ const badge = overseerBadge(run, at);
142
+ if (cols < TEAM_LIMITS.activityWideCols)
143
+ return interventionWord(last.action);
144
+ return sanitizeActivity(`${badge}; ${activityLine(run, cols)}`, activityBudget(cols));
145
+ }
146
+ /**
147
+ * The panel's one supervisor status line (AC-5): the most recent intervention
148
+ * across the roster, human-phrased. Empty when the supervisor has not acted
149
+ * yet - furniture that says nothing is worse than no line.
150
+ */
151
+ export function supervisorStatusLine(runs, at) {
152
+ let label = '';
153
+ let latest;
154
+ for (const run of runs) {
155
+ const last = run.lastIntervention;
156
+ if (last !== undefined && (latest === undefined || last.at > latest.at)) {
157
+ latest = last;
158
+ label = run.label;
159
+ }
160
+ }
161
+ if (latest === undefined)
162
+ return '';
163
+ const ago = formatDuration(Math.max(0, at - latest.at));
164
+ const head = latest.reasonHead.length > 0 ? ` (${latest.reasonHead})` : '';
165
+ return `supervisor: ${interventionWord(latest.action)} ${label} ${ago} ago${head}`;
166
+ }
104
167
  export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }) {
105
168
  const projection = layout ?? buildTeamPanelLayout({
106
169
  snapshot, terminalRows: rows, availableRows: Infinity,
@@ -130,8 +193,14 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
130
193
  // `reducedMotion` and a non-Unicode terminal fall back to the same
131
194
  // static marker, exactly as `AssistantEntry` and `ToolCard` already do.
132
195
  const animate = isRunning(run) && !reducedMotion && caps.unicode;
133
- return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: run.label.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
134
- }), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
196
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: run.label.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityWithBadge(run, cols, at)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
197
+ }), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), (() => {
198
+ // The panel's supervisor line (AC-5): one row, only when the
199
+ // supervisor has actually acted - the per-row badges above already
200
+ // carry the per-child facts.
201
+ const line = supervisorStatusLine(snapshot.runs, at);
202
+ return line ? (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', line] })) : null;
203
+ })(), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
135
204
  }
136
205
  function marker(run, glyphs) {
137
206
  if (run.phase === 'done')
@@ -12,7 +12,10 @@ export function buildTeamPanelLayout(input) {
12
12
  }
13
13
  const selection = selectPanelRows(input.snapshot.runs, TEAM_LIMITS.panelMaxRows);
14
14
  const natural = 1 + selection.visible.length + Number(selection.hiddenTotal > 0)
15
- + Number(Boolean(input.snapshot.lastMessage));
15
+ + Number(Boolean(input.snapshot.lastMessage))
16
+ // The supervisor status line (subagent-overseer-v2 AC-5) occupies one
17
+ // row whenever any run carries an intervention badge.
18
+ + Number(input.snapshot.runs.some((run) => run.lastIntervention !== undefined));
16
19
  const collapsed = nonNegativeInt(input.terminalRows) < TEAM_LIMITS.panelCollapseRows
17
20
  || available - natural < TODO_LIMITS.panelMinRows;
18
21
  return { ...selection, collapsed, rowCount: collapsed ? 1 : natural };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/cli",
3
- "version": "0.6.21",
3
+ "version": "0.6.23",
4
4
  "description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",
@@ -56,7 +56,7 @@
56
56
  "prepublishOnly": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
- "@aragon-agent/core": "^0.2.33",
59
+ "@aragon-agent/core": "^0.2.35",
60
60
  "chalk": "^5.6.2",
61
61
  "cli-highlight": "^2.0.0",
62
62
  "commander": "^12.1.0",