@aragon-agent/cli 0.5.4 → 0.5.8
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 +747 -577
- package/README.md +1163 -995
- package/dist/agent/controller.d.ts +71 -1
- package/dist/agent/controller.js +135 -1
- package/dist/agent/entry-limits.d.ts +65 -0
- package/dist/agent/entry-limits.js +133 -0
- package/dist/agent/headless.d.ts +29 -0
- package/dist/agent/headless.js +86 -1
- package/dist/agent/reducer.d.ts +53 -0
- package/dist/agent/reducer.js +145 -14
- package/dist/agent/system-prompt.d.ts +8 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +76 -3
- package/dist/commands/builtins.js +171 -0
- package/dist/commands/perf.d.ts +45 -0
- package/dist/commands/perf.js +78 -0
- package/dist/commands/registry.d.ts +27 -1
- package/dist/config/env.d.ts +3 -1
- package/dist/config/env.js +74 -1
- package/dist/config/load.d.ts +41 -0
- package/dist/config/load.js +67 -2
- package/dist/config/schema.d.ts +108 -8
- package/dist/config/schema.js +81 -6
- package/dist/config/store.js +10 -1
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +21 -2
- package/dist/team/subagent.js +11 -0
- package/dist/todo/follow-through.d.ts +117 -0
- package/dist/todo/follow-through.js +186 -0
- package/dist/todo/limits.d.ts +114 -0
- package/dist/todo/limits.js +114 -0
- package/dist/todo/normalize.d.ts +33 -0
- package/dist/todo/normalize.js +117 -0
- package/dist/todo/panel-rows.d.ts +53 -0
- package/dist/todo/panel-rows.js +103 -0
- package/dist/todo/prompt.d.ts +54 -0
- package/dist/todo/prompt.js +76 -0
- package/dist/todo/store.d.ts +86 -0
- package/dist/todo/store.js +183 -0
- package/dist/todo/todo-tool.d.ts +38 -0
- package/dist/todo/todo-tool.js +134 -0
- package/dist/todo/types.d.ts +62 -0
- package/dist/todo/types.js +12 -0
- package/dist/tools/index.d.ts +16 -2
- package/dist/tools/index.js +13 -0
- package/dist/ui/App.js +328 -14
- package/dist/ui/Markdown.d.ts +7 -1
- package/dist/ui/Markdown.js +15 -48
- package/dist/ui/StatusBar.d.ts +31 -0
- package/dist/ui/StatusBar.js +7 -2
- package/dist/ui/TodoPanel.d.ts +48 -0
- package/dist/ui/TodoPanel.js +74 -0
- package/dist/ui/TodoStrip.d.ts +36 -0
- package/dist/ui/TodoStrip.js +32 -0
- package/dist/ui/Transcript.d.ts +56 -4
- package/dist/ui/Transcript.js +123 -11
- package/dist/ui/entries/AssistantEntry.d.ts +18 -1
- package/dist/ui/entries/AssistantEntry.js +33 -3
- package/dist/ui/entries/TeamCard.d.ts +9 -1
- package/dist/ui/entries/TeamCard.js +22 -1
- package/dist/ui/entries/TodoCard.d.ts +39 -0
- package/dist/ui/entries/TodoCard.js +57 -0
- package/dist/ui/entries/ToolCard.d.ts +13 -1
- package/dist/ui/entries/ToolCard.js +39 -5
- package/dist/ui/entries/ToolPreview.d.ts +2 -2
- package/dist/ui/entries/UserEntry.d.ts +9 -1
- package/dist/ui/entries/UserEntry.js +17 -1
- package/dist/ui/exit-snapshot.d.ts +7 -0
- package/dist/ui/glyphs.d.ts +12 -0
- package/dist/ui/glyphs.js +8 -0
- package/dist/ui/layout/AppShell.d.ts +34 -1
- package/dist/ui/layout/AppShell.js +3 -3
- package/dist/ui/layout/MeasuredEntry.d.ts +21 -0
- package/dist/ui/layout/MeasuredEntry.js +30 -0
- package/dist/ui/layout/ScrollViewport.d.ts +13 -1
- package/dist/ui/layout/ScrollViewport.js +10 -4
- package/dist/ui/layout/rail.d.ts +56 -0
- package/dist/ui/layout/rail.js +68 -0
- package/dist/ui/layout/viewport-geometry.d.ts +28 -0
- package/dist/ui/layout/viewport-geometry.js +27 -0
- package/dist/ui/layout/virtual-window.d.ts +100 -0
- package/dist/ui/layout/virtual-window.js +204 -0
- package/dist/ui/live-clamp.d.ts +42 -0
- package/dist/ui/live-clamp.js +74 -0
- package/dist/ui/markdown-blocks.d.ts +25 -1
- package/dist/ui/markdown-blocks.js +43 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -0
- package/dist/ui/render-cache.d.ts +81 -0
- package/dist/ui/render-cache.js +195 -0
- package/dist/ui/render-governor.d.ts +54 -0
- package/dist/ui/render-governor.js +78 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +5 -0
- package/dist/ui/use-height-store.d.ts +31 -0
- package/dist/ui/use-height-store.js +76 -0
- package/dist/ui/use-render-governor.d.ts +39 -0
- package/dist/ui/use-render-governor.js +57 -0
- package/dist/ui/use-startup-notices.d.ts +9 -1
- package/dist/ui/use-startup-notices.js +35 -1
- package/package.json +3 -2
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { AgentEvent, ModelCost, ToolResult, TokenUsage } from '@aragon-agent/core';
|
|
14
14
|
import type { AgentMode } from './agent-mode.js';
|
|
15
15
|
import type { DispatchOutcome, SubagentRun, SubagentSpec, TeamSnapshot } from '../team/types.js';
|
|
16
|
+
import type { TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
16
17
|
export type NoticeLevel = 'info' | 'warn' | 'error';
|
|
17
18
|
export type ToastLevel = NoticeLevel | 'success';
|
|
18
19
|
export type Overlay = null | 'settings' | 'model' | 'help' | 'confirm' | 'question' | 'plan';
|
|
@@ -78,6 +79,34 @@ export type Entry = {
|
|
|
78
79
|
aborted: boolean;
|
|
79
80
|
durationMs?: number;
|
|
80
81
|
active: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The turn's todo checklist (todo-plan-execution §3.8). ONE per user turn,
|
|
85
|
+
* rewritten in place — a 7-item list updated twice per item would otherwise be
|
|
86
|
+
* 14 near-identical tool cards in the one surface the user reads most.
|
|
87
|
+
*
|
|
88
|
+
* JSON-SERIALIZABLE by construction, so `/save` needs no format change.
|
|
89
|
+
*
|
|
90
|
+
* `live: true` is normalized away ON LOAD, in `session/persist.ts`, for the
|
|
91
|
+
* same reason the team entry's `active` is (C-5): `Transcript`'s settled
|
|
92
|
+
* boundary is MONOTONIC, so an entry that never settles never reaches
|
|
93
|
+
* `<Static>` and is re-rendered on every frame for the rest of the session.
|
|
94
|
+
*/
|
|
95
|
+
| {
|
|
96
|
+
id: string;
|
|
97
|
+
kind: 'todo';
|
|
98
|
+
items: TodoItem[];
|
|
99
|
+
doneCount: number;
|
|
100
|
+
total: number;
|
|
101
|
+
/** The turn that owns this card is still running, so it may be rewritten. */
|
|
102
|
+
live: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Set by `normalizeLoadedEntries` on a card saved while its run was live
|
|
105
|
+
* (P2-6). The todo analogue of the team entry's `aborted`, and true for the
|
|
106
|
+
* same reason: the run behind it died with the process. Absent on every
|
|
107
|
+
* card this session produced.
|
|
108
|
+
*/
|
|
109
|
+
interrupted?: boolean;
|
|
81
110
|
};
|
|
82
111
|
export interface UsageTotal {
|
|
83
112
|
inputTokens: number;
|
|
@@ -112,14 +141,33 @@ export interface ViewState {
|
|
|
112
141
|
* live dispatch to show (§6.4).
|
|
113
142
|
*/
|
|
114
143
|
team: TeamSnapshot | null;
|
|
144
|
+
/**
|
|
145
|
+
* The live todo list, or `null` when there is none.
|
|
146
|
+
*
|
|
147
|
+
* NEVER PERSISTED HERE — `entries` is the history and `SavedSession.todos` is
|
|
148
|
+
* the list (§3.13). `null` is what leaves the rail unmounted entirely, which
|
|
149
|
+
* is why a session that never plans has byte-identical layout to a pre-feature
|
|
150
|
+
* build (AC-24).
|
|
151
|
+
*/
|
|
152
|
+
todos: TodoSnapshot | null;
|
|
115
153
|
seq: number;
|
|
116
154
|
/** The `kind: 'team'` entry the live dispatch is writing into. */
|
|
117
155
|
teamEntryId?: string;
|
|
156
|
+
/** The `kind: 'todo'` entry this turn is writing into. */
|
|
157
|
+
todoEntryId?: string;
|
|
118
158
|
streamingId?: string;
|
|
119
159
|
turnProduced: boolean;
|
|
120
160
|
errorNoticed: boolean;
|
|
121
161
|
/** The current run was aborted by the user (suppresses the failure guard). */
|
|
122
162
|
aborted: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Entries removed by `transcriptRetain` (tui-render-performance L1).
|
|
165
|
+
*
|
|
166
|
+
* Rendered by `transcript-text` and reported by `/perf`; NEVER PERSISTED —
|
|
167
|
+
* `SavedSession` keeps `{ model, messages, entries, todos }` exactly as it is,
|
|
168
|
+
* so `/save` and `/resume` need no format change and old files load unchanged.
|
|
169
|
+
*/
|
|
170
|
+
droppedEntries: number;
|
|
123
171
|
}
|
|
124
172
|
export declare function initialViewState(): ViewState;
|
|
125
173
|
export type ViewAction = {
|
|
@@ -213,6 +261,11 @@ export type ViewAction = {
|
|
|
213
261
|
} | {
|
|
214
262
|
type: 'teamEnd';
|
|
215
263
|
outcome: DispatchOutcome;
|
|
264
|
+
} | {
|
|
265
|
+
type: 'todoUpdate';
|
|
266
|
+
snapshot: TodoSnapshot;
|
|
267
|
+
} | {
|
|
268
|
+
type: 'todoCleared';
|
|
216
269
|
};
|
|
217
270
|
export declare const STORED_PREVIEW_CHARS = 8000;
|
|
218
271
|
export declare const STORED_PREVIEW_LINES = 200;
|
package/dist/agent/reducer.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* - `agent_end` after a turn that produced no `turn_end` and no error notice
|
|
11
11
|
* synthesizes a generic error notice (a swallowed throw is never a no-op).
|
|
12
12
|
*/
|
|
13
|
+
import process from 'node:process';
|
|
13
14
|
import { computeCost } from './usage.js';
|
|
15
|
+
import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
|
|
14
16
|
/** Default auto-dismiss window for a toast. */
|
|
15
17
|
export const DEFAULT_TOAST_TTL_MS = 2500;
|
|
16
18
|
export function initialViewState() {
|
|
@@ -28,12 +30,15 @@ export function initialViewState() {
|
|
|
28
30
|
agentMode: 'build',
|
|
29
31
|
pendingAgentMode: null,
|
|
30
32
|
team: null,
|
|
33
|
+
todos: null,
|
|
31
34
|
seq: 0,
|
|
32
35
|
teamEntryId: undefined,
|
|
36
|
+
todoEntryId: undefined,
|
|
33
37
|
streamingId: undefined,
|
|
34
38
|
turnProduced: false,
|
|
35
39
|
errorNoticed: false,
|
|
36
40
|
aborted: false,
|
|
41
|
+
droppedEntries: 0,
|
|
37
42
|
};
|
|
38
43
|
}
|
|
39
44
|
// ---------------------------------------------------------------------------
|
|
@@ -118,6 +123,20 @@ export function reduceEvent(event, cost) {
|
|
|
118
123
|
return [];
|
|
119
124
|
}
|
|
120
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Tools that render their own transcript entry, so the generic tool card would
|
|
128
|
+
* be a duplicate. EXACTLY ONE MEMBER; read todo-plan-execution §3.8 before
|
|
129
|
+
* adding a second.
|
|
130
|
+
*
|
|
131
|
+
* SUPPRESSING ONLY `tool_call_start` IS SUFFICIENT, AND THAT IS THE WHOLE REASON
|
|
132
|
+
* THIS IS SAFE (D-10). Every later handler — `toolCallDelta` / `toolCallEnd` /
|
|
133
|
+
* `toolExecStart` / `toolExecEnd` — resolves its target through
|
|
134
|
+
* `findToolEntryId`, which returns `undefined` when no entry was created, and
|
|
135
|
+
* every one of them already early-returns on that. There is no fifth handler. A
|
|
136
|
+
* second suppression rule would be four more places to get the id matching
|
|
137
|
+
* wrong, for nothing.
|
|
138
|
+
*/
|
|
139
|
+
const SELF_RENDERING_TOOLS = new Set(['todo_write']);
|
|
121
140
|
function reduceStreamEvent(se) {
|
|
122
141
|
switch (se.type) {
|
|
123
142
|
case 'thinking_start':
|
|
@@ -127,6 +146,8 @@ function reduceStreamEvent(se) {
|
|
|
127
146
|
case 'text_delta':
|
|
128
147
|
return [{ type: 'textDelta', delta: se.delta }];
|
|
129
148
|
case 'tool_call_start':
|
|
149
|
+
if (SELF_RENDERING_TOOLS.has(se.toolName))
|
|
150
|
+
return [];
|
|
130
151
|
return [
|
|
131
152
|
{ type: 'toolCallStart', toolCallId: se.toolCallId, toolName: se.toolName },
|
|
132
153
|
];
|
|
@@ -183,6 +204,35 @@ function nextId(state) {
|
|
|
183
204
|
function mapEntry(entries, id, fn) {
|
|
184
205
|
return entries.map((e) => (e.id === id ? fn(e) : e));
|
|
185
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Append one entry and ring-trim the result (tui-render-performance L1).
|
|
209
|
+
*
|
|
210
|
+
* TRIMMING HAPPENS ONLY AT APPEND POINTS, and that is the whole reason this is a
|
|
211
|
+
* helper rather than a line in `viewReducer`. A mid-turn trim triggered by an
|
|
212
|
+
* UPDATE action could drop the entry `streamingId` / `teamEntryId` /
|
|
213
|
+
* `todoEntryId` points at while it is still being written to; appending is the
|
|
214
|
+
* only moment at which the tail is by definition the newest thing.
|
|
215
|
+
*
|
|
216
|
+
* I-L1-1 — the trim must never drop a live target. With `retain >= 200` and a
|
|
217
|
+
* trim that only ever removes from the head this is structurally true, but a
|
|
218
|
+
* future clamp change would break it SILENTLY (the `mapEntry` calls would simply
|
|
219
|
+
* stop matching), so development builds assert it.
|
|
220
|
+
*/
|
|
221
|
+
function appendEntry(state, entries, entry) {
|
|
222
|
+
const trimmed = trimEntries([...entries, entry], entryRetain());
|
|
223
|
+
if (trimmed.dropped > 0 && process.env.NODE_ENV !== 'production') {
|
|
224
|
+
const live = [state.streamingId, state.teamEntryId, state.todoEntryId].filter((id) => typeof id === 'string');
|
|
225
|
+
for (const id of live) {
|
|
226
|
+
if (!trimmed.entries.some((e) => e.id === id)) {
|
|
227
|
+
throw new Error(`trimEntries dropped a live entry (${id}) - see I-L1-1`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
entries: trimmed.entries,
|
|
233
|
+
droppedEntries: state.droppedEntries + trimmed.dropped,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
186
236
|
function findToolEntryId(state, toolCallId) {
|
|
187
237
|
for (let i = state.entries.length - 1; i >= 0; i -= 1) {
|
|
188
238
|
const e = state.entries[i];
|
|
@@ -199,8 +249,12 @@ export function viewReducer(state, action) {
|
|
|
199
249
|
return {
|
|
200
250
|
...state,
|
|
201
251
|
seq,
|
|
202
|
-
|
|
252
|
+
...appendEntry(state, state.entries, entry),
|
|
203
253
|
streamingId: undefined,
|
|
254
|
+
// A new turn gets its own card, in that turn's position in the
|
|
255
|
+
// transcript. The PREVIOUS card stays exactly where it is — it is
|
|
256
|
+
// history, and history is not retracted.
|
|
257
|
+
todoEntryId: undefined,
|
|
204
258
|
turnProduced: false,
|
|
205
259
|
errorNoticed: false,
|
|
206
260
|
aborted: false,
|
|
@@ -224,7 +278,7 @@ export function viewReducer(state, action) {
|
|
|
224
278
|
thinkingOpen: false,
|
|
225
279
|
streaming: true,
|
|
226
280
|
};
|
|
227
|
-
return { ...state, seq,
|
|
281
|
+
return { ...state, seq, ...appendEntry(state, state.entries, entry), streamingId: id };
|
|
228
282
|
}
|
|
229
283
|
case 'thinkingStart': {
|
|
230
284
|
if (!state.streamingId)
|
|
@@ -240,7 +294,7 @@ export function viewReducer(state, action) {
|
|
|
240
294
|
return {
|
|
241
295
|
...state,
|
|
242
296
|
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
243
|
-
? { ...e, thinking: (e.thinking ?? ''
|
|
297
|
+
? { ...e, thinking: appendBounded(e.thinking ?? '', action.delta, ENTRY_LIMITS.thinking) }
|
|
244
298
|
: e),
|
|
245
299
|
};
|
|
246
300
|
}
|
|
@@ -249,7 +303,9 @@ export function viewReducer(state, action) {
|
|
|
249
303
|
return state;
|
|
250
304
|
return {
|
|
251
305
|
...state,
|
|
252
|
-
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
306
|
+
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
307
|
+
? { ...e, text: appendBounded(e.text, action.delta, ENTRY_LIMITS.text) }
|
|
308
|
+
: e),
|
|
253
309
|
};
|
|
254
310
|
}
|
|
255
311
|
case 'toolCallStart': {
|
|
@@ -263,7 +319,7 @@ export function viewReducer(state, action) {
|
|
|
263
319
|
argsRaw: '',
|
|
264
320
|
status: 'pending',
|
|
265
321
|
};
|
|
266
|
-
return { ...state, seq,
|
|
322
|
+
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
267
323
|
}
|
|
268
324
|
case 'toolCallDelta': {
|
|
269
325
|
const id = findToolEntryId(state, action.toolCallId);
|
|
@@ -271,7 +327,9 @@ export function viewReducer(state, action) {
|
|
|
271
327
|
return state;
|
|
272
328
|
return {
|
|
273
329
|
...state,
|
|
274
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
|
|
330
|
+
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
|
|
331
|
+
? { ...e, argsRaw: appendBounded(e.argsRaw, action.argsDelta, ENTRY_LIMITS.argsRaw) }
|
|
332
|
+
: e),
|
|
275
333
|
};
|
|
276
334
|
}
|
|
277
335
|
case 'toolCallEnd': {
|
|
@@ -336,17 +394,27 @@ export function viewReducer(state, action) {
|
|
|
336
394
|
? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false } : e)
|
|
337
395
|
: state.entries;
|
|
338
396
|
let seq = state.seq;
|
|
397
|
+
let droppedEntries = state.droppedEntries;
|
|
339
398
|
// Silent-failure guard (R2): a swallowed throw shows nothing otherwise.
|
|
340
399
|
// A user abort is an expected empty run, not a failure — skip the guard.
|
|
341
400
|
if (!state.turnProduced && !state.errorNoticed && !state.aborted) {
|
|
342
401
|
const next = nextId({ ...state, seq });
|
|
343
402
|
seq = next.seq;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
403
|
+
const appended = appendEntry(state, entries, {
|
|
404
|
+
id: next.id,
|
|
405
|
+
kind: 'notice',
|
|
406
|
+
level: 'error',
|
|
407
|
+
text: GENERIC_RUN_FAILURE,
|
|
408
|
+
});
|
|
409
|
+
entries = appended.entries;
|
|
410
|
+
droppedEntries = appended.droppedEntries;
|
|
348
411
|
}
|
|
349
|
-
|
|
412
|
+
// The card may now reach `<Static>` (C-5 / I-6): the turn that owns it is
|
|
413
|
+
// over, so nothing will rewrite it.
|
|
414
|
+
if (state.todoEntryId) {
|
|
415
|
+
entries = mapEntry(entries, state.todoEntryId, (e) => e.kind === 'todo' ? { ...e, live: false } : e);
|
|
416
|
+
}
|
|
417
|
+
return { ...state, entries, seq, droppedEntries, status: 'idle', streamingId: undefined };
|
|
350
418
|
}
|
|
351
419
|
case 'notice': {
|
|
352
420
|
const { id, seq } = nextId(state);
|
|
@@ -354,7 +422,7 @@ export function viewReducer(state, action) {
|
|
|
354
422
|
return {
|
|
355
423
|
...state,
|
|
356
424
|
seq,
|
|
357
|
-
|
|
425
|
+
...appendEntry(state, state.entries, entry),
|
|
358
426
|
errorNoticed: action.level === 'error' ? true : state.errorNoticed,
|
|
359
427
|
};
|
|
360
428
|
}
|
|
@@ -376,19 +444,35 @@ export function viewReducer(state, action) {
|
|
|
376
444
|
return {
|
|
377
445
|
...state,
|
|
378
446
|
entries: [],
|
|
447
|
+
// The count describes what the RETAIN RING removed from the visible
|
|
448
|
+
// transcript. `/clear` empties it by intent, so carrying the number
|
|
449
|
+
// over would make the exported transcript claim a loss that did not
|
|
450
|
+
// happen.
|
|
451
|
+
droppedEntries: 0,
|
|
379
452
|
streamingId: undefined,
|
|
380
453
|
expandedToolIds: {},
|
|
381
454
|
teamEntryId: undefined,
|
|
455
|
+
// The entry it pointed at is gone, so the id goes with it. `todos` is
|
|
456
|
+
// DELIBERATELY UNTOUCHED (I-2): `/clear` wipes the transcript and not
|
|
457
|
+
// the conversation, so the model still believes in the plan, and a panel
|
|
458
|
+
// that disagrees with the model is worse than no panel.
|
|
459
|
+
todoEntryId: undefined,
|
|
382
460
|
};
|
|
383
461
|
case 'resetConversation':
|
|
384
462
|
return {
|
|
385
463
|
...state,
|
|
386
464
|
entries: [],
|
|
465
|
+
droppedEntries: 0,
|
|
387
466
|
streamingId: undefined,
|
|
388
467
|
expandedToolIds: {},
|
|
389
468
|
contextTokens: 0,
|
|
390
469
|
usageTotal: { inputTokens: 0, outputTokens: 0, costUsd: 0 },
|
|
391
470
|
teamEntryId: undefined,
|
|
471
|
+
// `/reset` clears `messages`, so the belief is gone and the list goes
|
|
472
|
+
// with it — the other half of I-2. `controller.clearMessages()` clears
|
|
473
|
+
// the STORE; this clears the mirror.
|
|
474
|
+
todos: null,
|
|
475
|
+
todoEntryId: undefined,
|
|
392
476
|
};
|
|
393
477
|
case 'setOverlay':
|
|
394
478
|
return { ...state, overlay: action.overlay };
|
|
@@ -444,7 +528,7 @@ export function viewReducer(state, action) {
|
|
|
444
528
|
return {
|
|
445
529
|
...state,
|
|
446
530
|
seq,
|
|
447
|
-
|
|
531
|
+
...appendEntry(state, state.entries, entry),
|
|
448
532
|
teamEntryId: id,
|
|
449
533
|
team: {
|
|
450
534
|
dispatchId: action.dispatchId,
|
|
@@ -496,13 +580,60 @@ export function viewReducer(state, action) {
|
|
|
496
580
|
// "(dang you de shi hou)": it renders only while a dispatch is running.
|
|
497
581
|
return { ...state, entries, team: null, teamEntryId: undefined };
|
|
498
582
|
}
|
|
583
|
+
// --- Todo planning (todo-plan-execution §5.2) -------------------------
|
|
584
|
+
case 'todoUpdate': {
|
|
585
|
+
const entry = (id) => ({
|
|
586
|
+
id,
|
|
587
|
+
kind: 'todo',
|
|
588
|
+
items: action.snapshot.items,
|
|
589
|
+
doneCount: action.snapshot.doneCount,
|
|
590
|
+
total: action.snapshot.total,
|
|
591
|
+
live: true,
|
|
592
|
+
});
|
|
593
|
+
// Rewrite the turn's card when there is one; otherwise append and record
|
|
594
|
+
// its id. One `mapEntry` on the KNOWN id, never a scan.
|
|
595
|
+
if (state.todoEntryId) {
|
|
596
|
+
const known = state.todoEntryId;
|
|
597
|
+
return {
|
|
598
|
+
...state,
|
|
599
|
+
todos: action.snapshot,
|
|
600
|
+
entries: mapEntry(state.entries, known, (e) => e.kind === 'todo' ? { ...entry(known), live: e.live } : e),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
const { id, seq } = nextId(state);
|
|
604
|
+
return {
|
|
605
|
+
...state,
|
|
606
|
+
seq,
|
|
607
|
+
todos: action.snapshot,
|
|
608
|
+
...appendEntry(state, state.entries, entry(id)),
|
|
609
|
+
todoEntryId: id,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
// THE ENTRY STAYS. It is history, and history is not retracted — only the
|
|
613
|
+
// live projection goes away, which is what unmounts the rail.
|
|
614
|
+
case 'todoCleared':
|
|
615
|
+
return { ...state, todos: null, todoEntryId: undefined };
|
|
499
616
|
case 'restoreEntries': {
|
|
500
617
|
// Adopt a seq beyond any restored id to keep future ids unique.
|
|
501
618
|
const maxSeq = action.entries.reduce((acc, e) => {
|
|
502
619
|
const n = Number.parseInt(e.id.replace(/^e/, ''), 10);
|
|
503
620
|
return Number.isFinite(n) ? Math.max(acc, n) : acc;
|
|
504
621
|
}, state.seq);
|
|
505
|
-
|
|
622
|
+
// A session file can be longer than the retain ring; trimming it here is
|
|
623
|
+
// the one append-free place the bound still has to hold, and the count
|
|
624
|
+
// starts from this restore rather than inheriting the previous session's.
|
|
625
|
+
const restored = trimEntries([...action.entries], entryRetain());
|
|
626
|
+
return {
|
|
627
|
+
...state,
|
|
628
|
+
entries: restored.entries,
|
|
629
|
+
droppedEntries: restored.dropped,
|
|
630
|
+
seq: maxSeq,
|
|
631
|
+
teamEntryId: undefined,
|
|
632
|
+
// As for `team`: the restored cards are history and no turn owns them.
|
|
633
|
+
// The LIST itself arrives separately, through `controller.restoreTodos`
|
|
634
|
+
// and the store's own event (§3.13).
|
|
635
|
+
todoEntryId: undefined,
|
|
636
|
+
};
|
|
506
637
|
}
|
|
507
638
|
default:
|
|
508
639
|
return state;
|
|
@@ -53,6 +53,14 @@ export interface SystemPromptParams {
|
|
|
53
53
|
* mixing them a one-character mistake.
|
|
54
54
|
*/
|
|
55
55
|
subagentBlock?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The rendered `<todo_planning>` block, or `''` (todo-plan-execution §3.7).
|
|
58
|
+
*
|
|
59
|
+
* Spliced conditionally exactly as `skillsBlock`, the plan block and
|
|
60
|
+
* `teamBlock` are, which is what keeps C-10 / I-5 true: `todo.enabled: false`
|
|
61
|
+
* (and `--no-todo`) produce the pre-todo prompt BYTE FOR BYTE.
|
|
62
|
+
*/
|
|
63
|
+
todoBlock?: string;
|
|
56
64
|
}
|
|
57
65
|
/** Describe the active shell so the model does not emit incompatible syntax. */
|
|
58
66
|
export declare function describeShell(): {
|
|
@@ -39,6 +39,7 @@ export function buildSystemPrompt(params) {
|
|
|
39
39
|
: '';
|
|
40
40
|
const teamBlock = params.teamBlock ?? '';
|
|
41
41
|
const subagentBlock = params.subagentBlock ?? '';
|
|
42
|
+
const todoBlock = params.todoBlock ?? '';
|
|
42
43
|
return [
|
|
43
44
|
'You are AragonAgent, an autonomous coding assistant operating inside a terminal (TUI).',
|
|
44
45
|
'You help the user accomplish software-engineering tasks by reasoning step by step and using the provided tools.',
|
|
@@ -54,6 +55,7 @@ export function buildSystemPrompt(params) {
|
|
|
54
55
|
...(planBlock ? ['', planBlock] : []),
|
|
55
56
|
...(teamBlock ? ['', teamBlock] : []),
|
|
56
57
|
...(subagentBlock ? ['', subagentBlock] : []),
|
|
58
|
+
...(todoBlock ? ['', todoBlock] : []),
|
|
57
59
|
'',
|
|
58
60
|
'Operating guidance:',
|
|
59
61
|
'- Prefer reading files and inspecting the workspace before making changes.',
|
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { LOG_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, applyLogConfigSet, applyTeam
|
|
|
27
27
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
28
28
|
import { getLogger } from './logging/logger.js';
|
|
29
29
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
30
|
-
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, parseMaxTokensInput, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SKILLS_CONFIG, } from './config/schema.js';
|
|
30
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampMaxRenderInterval, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
31
31
|
import { makeGetApiKey } from './config/load.js';
|
|
32
32
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
33
33
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
@@ -86,11 +86,29 @@ function toFlags(opts) {
|
|
|
86
86
|
// reach `CliFlags` and are inert, with no compile error and no runtime error.
|
|
87
87
|
team: opts.team,
|
|
88
88
|
teamMax: opts.teamMax,
|
|
89
|
+
// Site three of three for `--todo` / `--todo-panel`, and the one this
|
|
90
|
+
// package has now paid for twice (`mouse` P1-2, `team` P1-4): a flag needs
|
|
91
|
+
// the `.option()` declaration below, a field on `CliFlags`, AND a line here.
|
|
92
|
+
// Miss this one and the flag is inert with no compile error and no runtime
|
|
93
|
+
// error.
|
|
94
|
+
todo: opts.todo,
|
|
95
|
+
todoPanel: opts.todoPanel,
|
|
96
|
+
// Site three of three for `--todo-follow` as well (todo-plan-followthrough
|
|
97
|
+
// C-9). A value flag is no less inert than a boolean one when this line is
|
|
98
|
+
// missing.
|
|
99
|
+
todoFollow: opts.todoFollow,
|
|
89
100
|
// `--verbose` is just a shorthand for `--log-level debug`; an explicit
|
|
90
101
|
// `--log-level` still wins, so the two can be combined without surprise.
|
|
91
102
|
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
92
103
|
logFile: opts.logFile,
|
|
93
104
|
logDir: opts.logDir,
|
|
105
|
+
// Site three of three for the render-performance flags. A flag needs the
|
|
106
|
+
// `.option()` declaration below, a field on `CliFlags`, AND a line here;
|
|
107
|
+
// miss this one and it is inert with no compile error and no runtime error
|
|
108
|
+
// (`mouse` P1-2, `team` P1-4, `todo` C-9 -- four features, same trap).
|
|
109
|
+
transcriptRetain: opts.transcriptRetain,
|
|
110
|
+
renderGovernor: opts.renderGovernor,
|
|
111
|
+
maxRenderInterval: opts.maxRenderInterval,
|
|
94
112
|
};
|
|
95
113
|
}
|
|
96
114
|
// ---------------------------------------------------------------------------
|
|
@@ -120,12 +138,21 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
120
138
|
// resolved a bootstrap level — it ran before commander and before `.env` were
|
|
121
139
|
// readable — and without this hand-off the fully resolved one never applies.
|
|
122
140
|
getLogger().reconfigure(config.log);
|
|
141
|
+
// Whether this session could EVER show the todo rail (todo-plan-execution
|
|
142
|
+
// §3.7 / P1-6). It varies ONE sentence of `<todo_planning>`; `decideRenderMode`
|
|
143
|
+
// is pure and `runInteractive` calls it again with the same inputs, so
|
|
144
|
+
// computing it here rather than threading it back costs nothing and keeps the
|
|
145
|
+
// controller's construction self-contained.
|
|
146
|
+
const todoPanelCapable = opts.interactive &&
|
|
147
|
+
decideRenderMode({ fullscreen: config.fullscreen }, process.env, process.stdout) ===
|
|
148
|
+
'fullscreen';
|
|
123
149
|
const confirmBridge = { handler: null };
|
|
124
150
|
// `cancelPending` is replaced by the App's own implementation on mount; this
|
|
125
151
|
// no-op keeps the object total so nothing has to null-check it.
|
|
126
152
|
const humanInputBridge = { handler: null, cancelPending: () => { } };
|
|
127
153
|
const controller = new AgentController(config, {
|
|
128
154
|
version: VERSION,
|
|
155
|
+
todoPanelCapable,
|
|
129
156
|
confirm: (req) => confirmBridge.handler ? confirmBridge.handler(req) : Promise.resolve(true),
|
|
130
157
|
approval: opts.interactive
|
|
131
158
|
? {
|
|
@@ -219,6 +246,10 @@ function runInteractive(flags, extras = {}) {
|
|
|
219
246
|
provider: snapshot.provider,
|
|
220
247
|
model: snapshot.model,
|
|
221
248
|
elapsedMs: Date.now() - snapshot.startedAt,
|
|
249
|
+
// Names what the retain ring removed, so a long session's replay never
|
|
250
|
+
// starts mid-conversation without saying why (tui-render-performance
|
|
251
|
+
// L1 / K-6).
|
|
252
|
+
...(snapshot.droppedEntries ? { droppedEntries: snapshot.droppedEntries } : {}),
|
|
222
253
|
}));
|
|
223
254
|
};
|
|
224
255
|
if (mode === 'fullscreen') {
|
|
@@ -308,7 +339,13 @@ async function runOneShot(flags, prompt, quiet) {
|
|
|
308
339
|
for (const dir of untrusted) {
|
|
309
340
|
process.stderr.write(`[skills] project skills in ${dir} skipped (untrusted). Run: aragon skills trust ${dir}\n`);
|
|
310
341
|
}
|
|
311
|
-
|
|
342
|
+
// `followThrough` is read off the REAL controller here rather than through
|
|
343
|
+
// `HeadlessController`, which is deliberately the minimal surface a test can
|
|
344
|
+
// satisfy with an object literal (todo-plan-followthrough §3.6).
|
|
345
|
+
const code = await runHeadless(controller, prompt, {
|
|
346
|
+
quiet,
|
|
347
|
+
followThrough: controller.getTodoConfig().followThrough,
|
|
348
|
+
});
|
|
312
349
|
// Flushed HERE and not inside `runHeadless`: that module is deliberately
|
|
313
350
|
// injectable (its own controller interface, its own stdout/stderr) and has an
|
|
314
351
|
// early `return 2`, so a singleton dependency in there would be both a new
|
|
@@ -367,6 +404,12 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
367
404
|
'fullscreen',
|
|
368
405
|
'exitTranscript',
|
|
369
406
|
'transcriptWindow',
|
|
407
|
+
// tui-render-performance L1 / L4. Listed here AND cased in the switch
|
|
408
|
+
// below: a key present here but absent there falls through, writes
|
|
409
|
+
// nothing, and still prints `Set <key> = <value>` (P1-2).
|
|
410
|
+
'transcriptRetain',
|
|
411
|
+
'renderGovernor',
|
|
412
|
+
'maxRenderIntervalMs',
|
|
370
413
|
// Without these two, `density` and `hints` would be settable only as one-shot
|
|
371
414
|
// flags and never persist -- inconsistent with every other config field.
|
|
372
415
|
'density',
|
|
@@ -483,7 +526,16 @@ function runConfigSet(key, value) {
|
|
|
483
526
|
patch.exitTranscript = value === 'true' || value === '1';
|
|
484
527
|
break;
|
|
485
528
|
case 'transcriptWindow':
|
|
486
|
-
patch.transcriptWindow = clampTranscriptWindow(value,
|
|
529
|
+
patch.transcriptWindow = clampTranscriptWindow(value, DEFAULT_TRANSCRIPT_WINDOW);
|
|
530
|
+
break;
|
|
531
|
+
case 'transcriptRetain':
|
|
532
|
+
patch.transcriptRetain = clampTranscriptRetain(value, DEFAULT_TRANSCRIPT_RETAIN);
|
|
533
|
+
break;
|
|
534
|
+
case 'renderGovernor':
|
|
535
|
+
patch.renderGovernor = value === 'true' || value === '1';
|
|
536
|
+
break;
|
|
537
|
+
case 'maxRenderIntervalMs':
|
|
538
|
+
patch.maxRenderIntervalMs = clampMaxRenderInterval(value, DEFAULT_MAX_RENDER_INTERVAL_MS);
|
|
487
539
|
break;
|
|
488
540
|
case 'density':
|
|
489
541
|
patch.density = clampDensity(value, 'comfortable');
|
|
@@ -602,6 +654,19 @@ function buildProgram() {
|
|
|
602
654
|
.option('--team', 'Enable team subagents for this session (the default)')
|
|
603
655
|
.option('--no-team', 'Disable team subagents (the task tool is not registered at all)')
|
|
604
656
|
.option('--team-max <n>', 'Max subagents per dispatch for this run (1-10)')
|
|
657
|
+
// BOTH PAIRS, positive first (todo-plan-execution §4.3 / P1-8). `--todo` is
|
|
658
|
+
// the `--team` story verbatim; `--todo-panel` needs the pair for a second
|
|
659
|
+
// reason on top of it — `panel` is PERSISTED, so a lone `--no-todo-panel`
|
|
660
|
+
// would make commander default `opts.todoPanel` to `true` and silently
|
|
661
|
+
// overwrite a stored `panel: false` on every run that passed no flag.
|
|
662
|
+
.option('--todo', 'Enable todo planning for this session (the default)')
|
|
663
|
+
.option('--no-todo', 'Disable todo planning (the todo_write tool is not registered at all)')
|
|
664
|
+
.option('--todo-panel', 'Show the todo plan in a right-hand rail (the default)')
|
|
665
|
+
.option('--no-todo-panel', 'Keep todo planning but do not render the rail')
|
|
666
|
+
// NOT A PAIR, and it does not need to be: a value flag is absent unless
|
|
667
|
+
// passed, so `!== undefined` in `resolveTodoConfig` already distinguishes it
|
|
668
|
+
// from silence. An unrecognized mode is clamped to `notify`, not rejected.
|
|
669
|
+
.option('--todo-follow <mode>', 'What to do when a run ends with unfinished steps: notify (default) | auto | off')
|
|
605
670
|
.option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
|
|
606
671
|
.option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
|
|
607
672
|
.option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
|
|
@@ -615,6 +680,14 @@ function buildProgram() {
|
|
|
615
680
|
// No short form: `-v` is already `--version`.
|
|
616
681
|
.option('--verbose', 'Shorthand for --log-level debug')
|
|
617
682
|
.option('--log-dir <dir>', 'Absolute log directory for this run')
|
|
683
|
+
// Render performance (tui-render-performance §5.2). `--render-governor` is
|
|
684
|
+
// declared BEFORE its negative form for the tri-state reason recorded four
|
|
685
|
+
// times above: `renderGovernor` is persisted, so negative-only would default
|
|
686
|
+
// `opts.renderGovernor` to `true` and silently overwrite a stored `false`.
|
|
687
|
+
.option('--transcript-retain <n>', 'Entries kept in memory (200-20000, default 1000)')
|
|
688
|
+
.option('--render-governor', 'Adapt the frame interval under load (the default)')
|
|
689
|
+
.option('--no-render-governor', 'Pin the frame interval at 33ms however expensive a frame is')
|
|
690
|
+
.option('--max-render-interval <ms>', 'Governor ceiling in ms (33-1000, default 320)')
|
|
618
691
|
.option('--no-color', 'Disable ANSI color')
|
|
619
692
|
.option('--quiet', '(print mode) suppress tool/usage lines on stderr')
|
|
620
693
|
.action(async (prompt, opts) => {
|