@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
|
@@ -9,10 +9,13 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
9
9
|
import process from 'node:process';
|
|
10
10
|
import { resolveOutputTokens } from '@aragon-agent/core';
|
|
11
11
|
import { clampThinkingLevel, clampTheme, isThemeName, parseMaxTokensInput, DEFAULT_MAX_TOKENS, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
|
|
12
|
+
import { TODO_FOLLOW_LIMITS } from '../todo/limits.js';
|
|
13
|
+
import { buildContinuationMessage } from '../todo/follow-through.js';
|
|
12
14
|
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
13
15
|
import { loadConfig } from '../config/load.js';
|
|
14
16
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
15
17
|
import { getLogger } from '../logging/logger.js';
|
|
18
|
+
import { perfCommand } from './perf.js';
|
|
16
19
|
const COMMANDS = [
|
|
17
20
|
{
|
|
18
21
|
name: 'help',
|
|
@@ -220,6 +223,160 @@ const COMMANDS = [
|
|
|
220
223
|
ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>].`);
|
|
221
224
|
},
|
|
222
225
|
},
|
|
226
|
+
{
|
|
227
|
+
name: 'todo',
|
|
228
|
+
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | clear | continue',
|
|
229
|
+
/**
|
|
230
|
+
* `/todo on` CANNOT turn planning on in a session that started with
|
|
231
|
+
* `--no-todo`, for the reason `/team on` above records at length: the tool
|
|
232
|
+
* array is built ONCE and rebuilding it is forbidden (C-1), while
|
|
233
|
+
* `rebuildSystemPrompt()` IS a live path — so the naive reading produces a
|
|
234
|
+
* session where the model is told about a `todo_write` that was never
|
|
235
|
+
* registered.
|
|
236
|
+
*
|
|
237
|
+
* TWO CALLS, IN THIS ORDER, FOR BOTH `on|off` AND `panel on|off`, and
|
|
238
|
+
* neither pair is optional:
|
|
239
|
+
*
|
|
240
|
+
* `setTodoEnabled` re-splices `<todo_planning>` (P1-1). Without it the
|
|
241
|
+
* block survives `/todo off` and every call comes back
|
|
242
|
+
* refused while the instructions to keep calling are
|
|
243
|
+
* still in the model's context.
|
|
244
|
+
* `setTodoConfig` updates the object `App` reads at RENDER time (P1-2).
|
|
245
|
+
* `persistConfig` only writes the file, so without it
|
|
246
|
+
* `/todo panel off` reports success and changes nothing
|
|
247
|
+
* until the next launch.
|
|
248
|
+
* `persistConfig` is what survives the session, in both cases.
|
|
249
|
+
*/
|
|
250
|
+
run: (ctx) => {
|
|
251
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
252
|
+
const [verb, value] = arg.split(/\s+/, 2);
|
|
253
|
+
const controller = ctx.controller;
|
|
254
|
+
if (!verb || verb === 'status') {
|
|
255
|
+
const snapshot = controller.getTodoSnapshot();
|
|
256
|
+
const state = !controller.isTodoRegistered()
|
|
257
|
+
? 'off for this session (started with --no-todo)'
|
|
258
|
+
: controller.isTodoEnabled()
|
|
259
|
+
? 'on'
|
|
260
|
+
: 'off';
|
|
261
|
+
const todoCfg = controller.getTodoConfig();
|
|
262
|
+
const panel = todoCfg.panel ? 'on' : 'off';
|
|
263
|
+
// THE COUNTERS APPEAR ONLY IN `auto` MODE (§4.3): printing `0/25` under
|
|
264
|
+
// `notify` would advertise a budget nothing is spending.
|
|
265
|
+
const follow = todoCfg.followThrough === 'auto'
|
|
266
|
+
? `auto (${ctx.followBudget?.used ?? 0}/` +
|
|
267
|
+
`${TODO_FOLLOW_LIMITS.maxAutoContinuesPerList} used)`
|
|
268
|
+
: todoCfg.followThrough;
|
|
269
|
+
if (!snapshot) {
|
|
270
|
+
ctx.notify('info', `No todo list. Todo planning: ${state}. Panel: ${panel}. ` +
|
|
271
|
+
`Follow-through: ${follow}.`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const active = snapshot.activeIndex >= 0
|
|
275
|
+
? ` In progress: "${snapshot.items[snapshot.activeIndex]?.content ?? ''}".`
|
|
276
|
+
: '';
|
|
277
|
+
ctx.notify('info', `Todos: ${snapshot.doneCount}/${snapshot.total} done.${active} Panel: ${panel}. ` +
|
|
278
|
+
`Follow-through: ${follow}.`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (verb === 'on' || verb === 'off') {
|
|
282
|
+
const enabled = verb === 'on';
|
|
283
|
+
// A PARTIAL `todo` section, safe only because `updatePersistedConfig`
|
|
284
|
+
// deep-merges it — the contract §4.2 spells out in full.
|
|
285
|
+
ctx.persistConfig({ todo: { enabled } });
|
|
286
|
+
if (!controller.isTodoRegistered()) {
|
|
287
|
+
ctx.notify('info', enabled
|
|
288
|
+
? 'Todo planning is off for this session (started with --no-todo). Saved for next launch.'
|
|
289
|
+
: 'Todo planning is already off for this session. Saved for next launch.');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
controller.setTodoEnabled(enabled);
|
|
293
|
+
ctx.toast('success', `Todo planning ${enabled ? 'on' : 'off'}.`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (verb === 'panel') {
|
|
297
|
+
if (value !== 'on' && value !== 'off') {
|
|
298
|
+
ctx.notify('warn', 'Usage: /todo panel <on|off>');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const panel = value === 'on';
|
|
302
|
+
controller.setTodoConfig({ panel });
|
|
303
|
+
ctx.persistConfig({ todo: { panel } });
|
|
304
|
+
ctx.toast('success', `Todo panel ${panel ? 'on' : 'off'}.`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (verb === 'follow') {
|
|
308
|
+
if (value !== 'notify' && value !== 'auto' && value !== 'off') {
|
|
309
|
+
ctx.notify('warn', 'Usage: /todo follow <notify|auto|off>');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const followThrough = value;
|
|
313
|
+
// THE SAME TWO-CALL RULE AS `panel` ABOVE, minus `setTodoEnabled`:
|
|
314
|
+
// nothing in `<todo_planning>` mentions follow-through (D-3), so
|
|
315
|
+
// `todoEnabled` has no bearing here.
|
|
316
|
+
//
|
|
317
|
+
// `setTodoConfig` REBUILDS THE SYSTEM PROMPT UNCONDITIONALLY
|
|
318
|
+
// (`controller.ts`), and that is fine and must be left alone: the
|
|
319
|
+
// rebuild is idempotent for this key (the block does not mention
|
|
320
|
+
// follow-through, so the output is byte-identical), it costs one string
|
|
321
|
+
// composition PER COMMAND INVOCATION rather than per turn, and
|
|
322
|
+
// `setTodoConfig` is the ONLY method that updates the object `App` reads
|
|
323
|
+
// at decision time. A branch that bypassed it to avoid the rebuild would
|
|
324
|
+
// trade a free string concatenation for the exact "reports success,
|
|
325
|
+
// changes nothing" bug the method exists to prevent (P1-1 / P2-10).
|
|
326
|
+
controller.setTodoConfig({ followThrough });
|
|
327
|
+
ctx.persistConfig({ todo: { followThrough } });
|
|
328
|
+
ctx.toast('success', followThrough === 'auto'
|
|
329
|
+
? `Follow-through: auto (up to ${TODO_FOLLOW_LIMITS.maxAutoContinuesPerList} ` +
|
|
330
|
+
'continuations per plan).'
|
|
331
|
+
: `Follow-through: ${followThrough}.`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (verb === 'clear') {
|
|
335
|
+
// REFUSED MID-RUN, which bounds the model/panel disagreement window to
|
|
336
|
+
// zero (§1.2): between turns the model's next `todo_write` is a full
|
|
337
|
+
// replacement anyway.
|
|
338
|
+
if (controller.isRunning()) {
|
|
339
|
+
ctx.notify('warn', 'Cannot clear the todo list while a run is in progress.');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!controller.getTodoSnapshot()) {
|
|
343
|
+
ctx.notify('info', 'No todo list.');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
controller.clearTodos();
|
|
347
|
+
ctx.toast('info', 'Todo list cleared.');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (verb === 'continue') {
|
|
351
|
+
// REFUSED WHILE RUNNING TOO (P2-7). `ctx.submit` is the App's
|
|
352
|
+
// `submitMessage`, which routes to `controller.steer()` when the status
|
|
353
|
+
// is `running` — so the un-refused form would inject "continue the
|
|
354
|
+
// remaining items" into the run that is already doing exactly that.
|
|
355
|
+
if (controller.isRunning()) {
|
|
356
|
+
ctx.notify('warn', 'Already running.');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const snapshot = controller.getTodoSnapshot();
|
|
360
|
+
if (!snapshot || snapshot.doneCount >= snapshot.total) {
|
|
361
|
+
ctx.notify('info', 'Nothing left to continue.');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// THE SAME FUNCTION THE AUTO PATH USES, which is what keeps the two from
|
|
365
|
+
// drifting (D-11 / D-12). Round 1's fixed string "Continue with the
|
|
366
|
+
// remaining todo items." was a REFERENCE to a `todo_write` call that a
|
|
367
|
+
// long session may have scrolled out of view.
|
|
368
|
+
//
|
|
369
|
+
// `{ userInitiated: false }`: the user typed `/todo continue`, which the
|
|
370
|
+
// slash history already has. Putting this canned paragraph under the
|
|
371
|
+
// composer's up-arrow as well would be the same event recorded twice, in
|
|
372
|
+
// the more verbose of the two forms (AC-17).
|
|
373
|
+
ctx.submit(buildContinuationMessage(snapshot), { userInitiated: false });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use ` +
|
|
377
|
+
'/todo [status|on|off|panel on|off|follow <mode>|clear|continue].');
|
|
378
|
+
},
|
|
379
|
+
},
|
|
223
380
|
{
|
|
224
381
|
name: 'expand',
|
|
225
382
|
description: 'Expand / collapse a recent tool card (default: last)',
|
|
@@ -296,6 +453,9 @@ const COMMANDS = [
|
|
|
296
453
|
},
|
|
297
454
|
messages: ctx.controller.getMessages(),
|
|
298
455
|
entries: ctx.state.entries,
|
|
456
|
+
// `/resume` restores `messages`, so the belief that justifies the
|
|
457
|
+
// panel comes back — and the panel has to come back with it (§3.13).
|
|
458
|
+
todos: ctx.controller.getTodoSnapshot()?.items ?? [],
|
|
299
459
|
});
|
|
300
460
|
ctx.notify('info', `Saved session to ${path}`);
|
|
301
461
|
}
|
|
@@ -312,6 +472,13 @@ const COMMANDS = [
|
|
|
312
472
|
const path = resolveSessionPath(ctx.args, ctx.controller.getCwd());
|
|
313
473
|
const session = loadSession(path);
|
|
314
474
|
ctx.controller.replaceMessages(session.messages);
|
|
475
|
+
// IN THE SAME `try`, IMMEDIATELY AFTER `replaceMessages`, so a malformed
|
|
476
|
+
// file fails the whole resume rather than half of it — and AN ABSENT
|
|
477
|
+
// `todos` CLEARS RATHER THAN SKIPPING (P0-2 / D-22). `replaceMessages`
|
|
478
|
+
// has just discarded the conversation the current list belonged to, so
|
|
479
|
+
// leaving the rail up produces a panel that disagrees with the model,
|
|
480
|
+
// which §1.2 ranks below having no panel at all.
|
|
481
|
+
ctx.controller.restoreTodos(session.todos ?? []);
|
|
315
482
|
ctx.dispatch({ type: 'restoreEntries', entries: session.entries });
|
|
316
483
|
if (session.model?.providerId && session.model?.modelId) {
|
|
317
484
|
ctx.controller.setModel(session.model.providerId, session.model.modelId, session.model.baseUrl);
|
|
@@ -369,6 +536,10 @@ const COMMANDS = [
|
|
|
369
536
|
ctx.toast('success', `Reloaded config: ${config.provider}:${config.model}.`);
|
|
370
537
|
},
|
|
371
538
|
},
|
|
539
|
+
// Lives in its own module because it is the ONLY built-in that reads the
|
|
540
|
+
// render layer, and threading `ui/render-cache.js` into this file would make
|
|
541
|
+
// every other command's import graph pull the renderer in with it.
|
|
542
|
+
perfCommand,
|
|
372
543
|
{
|
|
373
544
|
name: 'exit',
|
|
374
545
|
aliases: ['quit'],
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/perf` — what the renderer is actually doing (tui-render-performance §5.4).
|
|
3
|
+
*
|
|
4
|
+
* SILENT DEGRADATION IS THE ONE THING A "robust" CLAIM CANNOT AFFORD. The
|
|
5
|
+
* governor makes the stream chunkier under load, the retain ring drops old
|
|
6
|
+
* entries, and virtualisation mounts a fraction of the transcript. Every one of
|
|
7
|
+
* those is invisible by design, so there has to be one place that names them.
|
|
8
|
+
*
|
|
9
|
+
* The snapshot crosses the React boundary through a module-level single slot,
|
|
10
|
+
* the shape `ui/exit-snapshot.ts` already uses and for the same reason: a
|
|
11
|
+
* command runs outside the render pass and has no other way to reach `App`'s
|
|
12
|
+
* refs.
|
|
13
|
+
*
|
|
14
|
+
* READ-ONLY AND ALLOCATION-LIGHT. It renders as a NOTICE ENTRY rather than a
|
|
15
|
+
* live panel, because a performance readout that costs a repaint per second to
|
|
16
|
+
* display would be self-defeating.
|
|
17
|
+
*/
|
|
18
|
+
import type { SlashCommand } from './registry.js';
|
|
19
|
+
export interface PerfSnapshot {
|
|
20
|
+
/** Governor rung, its resolved interval, and the last measured commit cost. */
|
|
21
|
+
rung: number;
|
|
22
|
+
intervalMs: number;
|
|
23
|
+
lastCommitMs: number;
|
|
24
|
+
governorEnabled: boolean;
|
|
25
|
+
/** Transcript accounting. */
|
|
26
|
+
totalEntries: number;
|
|
27
|
+
retain: number;
|
|
28
|
+
droppedEntries: number;
|
|
29
|
+
mountedEntries: number;
|
|
30
|
+
/** Height cache. */
|
|
31
|
+
heightsMeasured: number;
|
|
32
|
+
heightsEstimated: number;
|
|
33
|
+
cols: number;
|
|
34
|
+
/** Geometry. */
|
|
35
|
+
mode: 'fullscreen' | 'inline';
|
|
36
|
+
viewportRows: number;
|
|
37
|
+
offset: number;
|
|
38
|
+
}
|
|
39
|
+
/** Called by `App`. Passing `null` detaches. */
|
|
40
|
+
export declare function setPerfSnapshotProvider(fn: (() => PerfSnapshot | null) | null): void;
|
|
41
|
+
export declare function readPerfSnapshot(): PerfSnapshot | null;
|
|
42
|
+
export declare function setPerfResetHook(fn: (() => void) | null): void;
|
|
43
|
+
/** Format a snapshot as the five-line block documented in §5.4. */
|
|
44
|
+
export declare function formatPerfReport(snap: PerfSnapshot): string;
|
|
45
|
+
export declare const perfCommand: SlashCommand;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/perf` — what the renderer is actually doing (tui-render-performance §5.4).
|
|
3
|
+
*
|
|
4
|
+
* SILENT DEGRADATION IS THE ONE THING A "robust" CLAIM CANNOT AFFORD. The
|
|
5
|
+
* governor makes the stream chunkier under load, the retain ring drops old
|
|
6
|
+
* entries, and virtualisation mounts a fraction of the transcript. Every one of
|
|
7
|
+
* those is invisible by design, so there has to be one place that names them.
|
|
8
|
+
*
|
|
9
|
+
* The snapshot crosses the React boundary through a module-level single slot,
|
|
10
|
+
* the shape `ui/exit-snapshot.ts` already uses and for the same reason: a
|
|
11
|
+
* command runs outside the render pass and has no other way to reach `App`'s
|
|
12
|
+
* refs.
|
|
13
|
+
*
|
|
14
|
+
* READ-ONLY AND ALLOCATION-LIGHT. It renders as a NOTICE ENTRY rather than a
|
|
15
|
+
* live panel, because a performance readout that costs a repaint per second to
|
|
16
|
+
* display would be self-defeating.
|
|
17
|
+
*/
|
|
18
|
+
import { clearRenderCaches, renderCacheStats } from '../ui/render-cache.js';
|
|
19
|
+
/** Cleared on unmount, so `/perf` in a torn-down tree says so instead of lying. */
|
|
20
|
+
let provider = null;
|
|
21
|
+
/** Called by `App`. Passing `null` detaches. */
|
|
22
|
+
export function setPerfSnapshotProvider(fn) {
|
|
23
|
+
provider = fn;
|
|
24
|
+
}
|
|
25
|
+
export function readPerfSnapshot() {
|
|
26
|
+
return provider ? provider() : null;
|
|
27
|
+
}
|
|
28
|
+
/** Reset hooks the App registers alongside the snapshot provider. */
|
|
29
|
+
let resetGovernor = null;
|
|
30
|
+
export function setPerfResetHook(fn) {
|
|
31
|
+
resetGovernor = fn;
|
|
32
|
+
}
|
|
33
|
+
function mb(bytes) {
|
|
34
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
35
|
+
}
|
|
36
|
+
/** Format a snapshot as the five-line block documented in §5.4. */
|
|
37
|
+
export function formatPerfReport(snap) {
|
|
38
|
+
const caches = renderCacheStats();
|
|
39
|
+
const eco = snap.rung > 0 ? ' - eco' : '';
|
|
40
|
+
const governor = snap.governorEnabled ? '' : ' - governor off';
|
|
41
|
+
return [
|
|
42
|
+
`render rung ${snap.rung} - interval ${snap.intervalMs}ms - last commit ` +
|
|
43
|
+
`${Math.round(snap.lastCommitMs)}ms${eco}${governor}`,
|
|
44
|
+
`transcript ${snap.totalEntries} entries - ${snap.retain} retained - ` +
|
|
45
|
+
`${snap.droppedEntries} dropped - ${snap.mountedEntries} mounted`,
|
|
46
|
+
`heights ${snap.heightsMeasured} cached - ${snap.heightsEstimated} estimated - ` +
|
|
47
|
+
`cols ${snap.cols}`,
|
|
48
|
+
`caches md ${caches.markdownEntries} (${mb(caches.markdownBytes)}) - ` +
|
|
49
|
+
`hl ${caches.highlightEntries} (${mb(caches.highlightBytes)}) - ` +
|
|
50
|
+
`lines ${caches.lineEntries}`,
|
|
51
|
+
`mode ${snap.mode} - viewport ${snap.viewportRows} rows - offset ${snap.offset}`,
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
export const perfCommand = {
|
|
55
|
+
name: 'perf',
|
|
56
|
+
description: 'Render statistics (/perf reset clears the caches and the governor)',
|
|
57
|
+
run: (ctx) => {
|
|
58
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
59
|
+
if (arg === 'reset') {
|
|
60
|
+
clearRenderCaches();
|
|
61
|
+
resetGovernor?.();
|
|
62
|
+
ctx.toast('success', 'Render caches cleared, governor reset.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (arg.length > 0) {
|
|
66
|
+
ctx.notify('warn', `Unknown argument "${arg}" - use /perf [reset].`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const snap = readPerfSnapshot();
|
|
70
|
+
if (!snap) {
|
|
71
|
+
// Reachable from a headless / detached tree. Saying so beats printing a
|
|
72
|
+
// block of zeroes that reads like a renderer doing nothing.
|
|
73
|
+
ctx.notify('info', 'No render statistics: the TUI is not mounted.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
ctx.notify('info', formatPerfReport(snap));
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -6,6 +6,7 @@ import type { AgentController } from '../agent/controller.js';
|
|
|
6
6
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
7
7
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
8
8
|
import type { PersistedConfig } from '../config/schema.js';
|
|
9
|
+
import type { FollowThroughBudget } from '../todo/follow-through.js';
|
|
9
10
|
export interface CommandContext {
|
|
10
11
|
/** Raw argument string after the command word (may be empty). */
|
|
11
12
|
args: string;
|
|
@@ -24,8 +25,33 @@ export interface CommandContext {
|
|
|
24
25
|
* idle). Dynamic skill commands need this: they turn `/pdf-forms a.pdf` into a
|
|
25
26
|
* real user message. It is a separate entry point from `handleSubmit` on
|
|
26
27
|
* purpose — routing back through that would re-enter slash-command parsing.
|
|
28
|
+
*
|
|
29
|
+
* `userInitiated: false` suppresses `recordPrompt` (todo-plan-followthrough
|
|
30
|
+
* C-12 / AC-17), which skips TWO things and both are wanted: the prompt-history
|
|
31
|
+
* append, and the `submitCount` bump that drives the composer's hint fade. A
|
|
32
|
+
* canned string the user did not type belongs in neither. The parameter is
|
|
33
|
+
* named for the CAUSE rather than for one of its effects, because a flag
|
|
34
|
+
* called `recordHistory` would have made the second one look like a bug.
|
|
35
|
+
*
|
|
36
|
+
* WIDENING THIS SIGNATURE IS THE WHOLE REASON THIS FILE IS IN THE CHANGE PLAN
|
|
37
|
+
* (P1-5): `builtins.ts` reaches `submitMessage` only through here, so
|
|
38
|
+
* `/todo continue` cannot opt out of prompt history without it.
|
|
39
|
+
*/
|
|
40
|
+
submit: (text: string, opts?: {
|
|
41
|
+
userInitiated?: boolean;
|
|
42
|
+
}) => void;
|
|
43
|
+
/**
|
|
44
|
+
* The live auto-continue budget, for `/todo status`'s `(2/25 used)` clause
|
|
45
|
+
* (todo-plan-followthrough §4.3).
|
|
46
|
+
*
|
|
47
|
+
* READ-ONLY AND OPTIONAL. It lives in an `App` ref rather than on the
|
|
48
|
+
* controller or in `ViewState` (§4.4 / §5.2) because it counts a SESSION'S
|
|
49
|
+
* INTERACTION WITH THE USER, not the model's plan — so this is the only way a
|
|
50
|
+
* command can see it, and `makeCtx` is the only place that supplies it.
|
|
51
|
+
* Optional so the many test contexts that build this object by literal need no
|
|
52
|
+
* change; absent means the clause degrades to naming the ceiling alone.
|
|
27
53
|
*/
|
|
28
|
-
|
|
54
|
+
followBudget?: FollowThroughBudget;
|
|
29
55
|
/** Rebuild the system prompt and the dynamic command list after a skill change. */
|
|
30
56
|
refreshSkills: () => void;
|
|
31
57
|
/**
|
package/dist/config/env.d.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
9
9
|
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN, ARAGON_TEAM,
|
|
10
|
-
* ARAGON_TEAM_MAX, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR
|
|
10
|
+
* ARAGON_TEAM_MAX, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR,
|
|
11
|
+
* ARAGON_TRANSCRIPT_RETAIN, ARAGON_RENDER_GOVERNOR,
|
|
12
|
+
* ARAGON_MAX_RENDER_INTERVAL_MS.
|
|
11
13
|
*
|
|
12
14
|
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
13
15
|
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
package/dist/config/env.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
9
9
|
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN, ARAGON_TEAM,
|
|
10
|
-
* ARAGON_TEAM_MAX, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR
|
|
10
|
+
* ARAGON_TEAM_MAX, ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR,
|
|
11
|
+
* ARAGON_TRANSCRIPT_RETAIN, ARAGON_RENDER_GOVERNOR,
|
|
12
|
+
* ARAGON_MAX_RENDER_INTERVAL_MS.
|
|
11
13
|
*
|
|
12
14
|
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
13
15
|
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
|
@@ -134,6 +136,77 @@ export function readEnvConfig() {
|
|
|
134
136
|
if (Object.keys(section).length > 0)
|
|
135
137
|
partial.team = section;
|
|
136
138
|
}
|
|
139
|
+
// ARAGON_TODO=0 turns todo planning off entirely (no tool, no rail). Parsed by
|
|
140
|
+
// the POSITIVE list, exactly as ARAGON_FULLSCREEN / ARAGON_MOUSE / ARAGON_PLAN
|
|
141
|
+
// / ARAGON_TEAM are — NOT by `envBool` below, which uses a negative list. The
|
|
142
|
+
// two readers disagree on values like `ARAGON_TODO=disable`, and this file
|
|
143
|
+
// already records that once for team mode.
|
|
144
|
+
//
|
|
145
|
+
// There is no `ARAGON_TODO_PANEL`: the panel is a per-machine display
|
|
146
|
+
// preference, which is what `config.json` is for, and every env var here
|
|
147
|
+
// exists because a flag and a config file are BOTH unreachable in the channel
|
|
148
|
+
// it serves (containers, SSH, being spawned by another tool).
|
|
149
|
+
//
|
|
150
|
+
// ARAGON_TODO_FOLLOW=notify|auto|off PASSES THAT SAME TEST (D-18), which is
|
|
151
|
+
// why it exists where `ARAGON_TODO_PANEL` does not: follow-through is exactly
|
|
152
|
+
// the knob an unattended `-p` run in a container with a fixed entrypoint needs
|
|
153
|
+
// and can reach no other way.
|
|
154
|
+
//
|
|
155
|
+
// ONE ACCUMULATED SECTION, ASSIGNED ONCE — the shape the `team` branch above
|
|
156
|
+
// already uses, and NOT two `if` blocks each assigning `partial.todo` (P1-4).
|
|
157
|
+
// THE CAST IS WHAT WOULD MAKE THAT MISTAKE SILENT: a second assignment
|
|
158
|
+
// compiles clean and drops `enabled`, so `ARAGON_TODO=0 ARAGON_TODO_FOLLOW=auto`
|
|
159
|
+
// would quietly re-enable the tool the user turned off.
|
|
160
|
+
const todo = process.env.ARAGON_TODO?.trim().toLowerCase();
|
|
161
|
+
const todoFollow = process.env.ARAGON_TODO_FOLLOW?.trim().toLowerCase();
|
|
162
|
+
if ((todo !== undefined && todo.length > 0) ||
|
|
163
|
+
(todoFollow !== undefined && todoFollow.length > 0)) {
|
|
164
|
+
// Keyed by `TodoConfig` but VALUED `unknown`, which is what lets the mode be
|
|
165
|
+
// written through unvalidated (below) while still failing to compile if a
|
|
166
|
+
// future key is misspelled.
|
|
167
|
+
const section = {};
|
|
168
|
+
if (todo !== undefined && todo.length > 0) {
|
|
169
|
+
section.enabled = todo === '1' || todo === 'true' || todo === 'on' || todo === 'yes';
|
|
170
|
+
}
|
|
171
|
+
// Written through unvalidated: `clampTodoConfig` clamps an unrecognized mode
|
|
172
|
+
// to the default rather than rejecting it, so validating here would be a
|
|
173
|
+
// second gate that can disagree with the first.
|
|
174
|
+
if (todoFollow !== undefined && todoFollow.length > 0) {
|
|
175
|
+
section.followThrough = todoFollow;
|
|
176
|
+
}
|
|
177
|
+
// A PARTIAL section, safe only because every consumer deep-merges it — the
|
|
178
|
+
// same contract `partial.skills`, `partial.log` and `partial.team` rely on.
|
|
179
|
+
if (Object.keys(section).length > 0)
|
|
180
|
+
partial.todo = section;
|
|
181
|
+
}
|
|
182
|
+
// Render bounds (tui-render-performance §5.3). Same channel story as
|
|
183
|
+
// ARAGON_FULLSCREEN: a container or a wrapper that owns argv can reach neither
|
|
184
|
+
// a flag nor a config file, and a session that freezes is exactly the case
|
|
185
|
+
// where the user needs a knob they can turn from the outside.
|
|
186
|
+
//
|
|
187
|
+
// ARAGON_TRANSCRIPT_RETAIN / ARAGON_MAX_RENDER_INTERVAL_MS are left ABSENT
|
|
188
|
+
// when unparseable rather than written as a default, so the config file can
|
|
189
|
+
// still win — the rule `ARAGON_TEAM_MAX` above records.
|
|
190
|
+
const retain = process.env.ARAGON_TRANSCRIPT_RETAIN?.trim();
|
|
191
|
+
if (retain !== undefined && retain.length > 0) {
|
|
192
|
+
const parsed = Number.parseInt(retain, 10);
|
|
193
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
194
|
+
partial.transcriptRetain = parsed;
|
|
195
|
+
}
|
|
196
|
+
// Parsed by the POSITIVE list, exactly as ARAGON_FULLSCREEN / ARAGON_MOUSE /
|
|
197
|
+
// ARAGON_PLAN / ARAGON_TEAM / ARAGON_TODO are — NOT by `envBool` below, which
|
|
198
|
+
// uses a negative list. The two readers disagree on `ARAGON_RENDER_GOVERNOR=disable`.
|
|
199
|
+
const governor = process.env.ARAGON_RENDER_GOVERNOR?.trim().toLowerCase();
|
|
200
|
+
if (governor !== undefined && governor.length > 0) {
|
|
201
|
+
partial.renderGovernor =
|
|
202
|
+
governor === '1' || governor === 'true' || governor === 'on' || governor === 'yes';
|
|
203
|
+
}
|
|
204
|
+
const renderInterval = process.env.ARAGON_MAX_RENDER_INTERVAL_MS?.trim();
|
|
205
|
+
if (renderInterval !== undefined && renderInterval.length > 0) {
|
|
206
|
+
const parsed = Number.parseInt(renderInterval, 10);
|
|
207
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
208
|
+
partial.maxRenderIntervalMs = parsed;
|
|
209
|
+
}
|
|
137
210
|
const log = readEnvLogConfig();
|
|
138
211
|
if (log)
|
|
139
212
|
partial.log = log;
|
package/dist/config/load.d.ts
CHANGED
|
@@ -52,12 +52,53 @@ export interface CliFlags {
|
|
|
52
52
|
team?: boolean;
|
|
53
53
|
/** `--team-max <n>` — override `team.maxSubagents` for this run (still clamped). */
|
|
54
54
|
teamMax?: string | number;
|
|
55
|
+
/**
|
|
56
|
+
* `--todo` ⇒ true, `--no-todo` ⇒ false, absent ⇒ undefined
|
|
57
|
+
* (todo-plan-execution §4.3).
|
|
58
|
+
*/
|
|
59
|
+
todo?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* `--todo-panel` ⇒ true, `--no-todo-panel` ⇒ false, absent ⇒ undefined.
|
|
62
|
+
*
|
|
63
|
+
* DECLARED AS A PAIR, like `--todo` above, and unlike `--no-skills` (P1-8).
|
|
64
|
+
* `panel` is a PERSISTED setting, so "not passed" has to stay distinguishable
|
|
65
|
+
* from `true` — otherwise commander's lone-`--no-x` default silently
|
|
66
|
+
* overwrites a config-file `panel: false` on every run that passes no flag at
|
|
67
|
+
* all. `--no-skills` escapes the rule only because nothing needs to
|
|
68
|
+
* distinguish `--skills` from silence.
|
|
69
|
+
*/
|
|
70
|
+
todoPanel?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* `--todo-follow <mode>` — `notify` | `auto` | `off`
|
|
73
|
+
* (todo-plan-followthrough §4.2).
|
|
74
|
+
*
|
|
75
|
+
* A VALUE FLAG, NOT A PAIR, so commander materializes it only when passed and
|
|
76
|
+
* the `!== undefined` rule above applies to its presence rather than to a
|
|
77
|
+
* synthesized `true`. Typed `string` and not `FollowThroughMode`: an
|
|
78
|
+
* unrecognized value is CLAMPED to the default by `clampTodoConfig`, never
|
|
79
|
+
* rejected, which is what every other key in this file does.
|
|
80
|
+
*/
|
|
81
|
+
todoFollow?: string;
|
|
55
82
|
/** `--log-level <lv>` — this run only, never persisted. */
|
|
56
83
|
logLevel?: string;
|
|
57
84
|
/** `--log-file` ⇒ true, `--no-log-file` ⇒ false, absent ⇒ undefined. */
|
|
58
85
|
logFile?: boolean;
|
|
59
86
|
/** `--log-dir <dir>` — this run only. */
|
|
60
87
|
logDir?: string;
|
|
88
|
+
/** `--transcript-retain <n>` — the `ViewState` ring cap for this run (L1). */
|
|
89
|
+
transcriptRetain?: string | number;
|
|
90
|
+
/**
|
|
91
|
+
* `--render-governor` ⇒ true, `--no-render-governor` ⇒ false, absent ⇒
|
|
92
|
+
* undefined (L4).
|
|
93
|
+
*
|
|
94
|
+
* DECLARED AS A PAIR for the reason `--todo-panel` records: `renderGovernor`
|
|
95
|
+
* is PERSISTED, so a lone `--no-render-governor` would make commander default
|
|
96
|
+
* `opts.renderGovernor` to `true` and silently overwrite a stored `false` on
|
|
97
|
+
* every run that passed no flag at all.
|
|
98
|
+
*/
|
|
99
|
+
renderGovernor?: boolean;
|
|
100
|
+
/** `--max-render-interval <ms>` — the governor ceiling for this run (L4). */
|
|
101
|
+
maxRenderInterval?: string | number;
|
|
61
102
|
}
|
|
62
103
|
/**
|
|
63
104
|
* Resolve the effective config by merging all layers, then derive the runtime
|
package/dist/config/load.js
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import process from 'node:process';
|
|
9
|
-
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampSkillsConfig, clampTeamConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampTheme, clampThinkingLevel, clampMaxTokens, clampMaxRenderInterval, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
12
|
import { getConfigPath } from './app-paths.js';
|
|
13
13
|
import { setHistoryEnabled } from './prompt-history.js';
|
|
14
|
+
import { setEntryRetain } from '../agent/entry-limits.js';
|
|
14
15
|
import { readSubmitCount } from './ui-state.js';
|
|
15
16
|
import { getLogger } from '../logging/logger.js';
|
|
16
17
|
import { registerSecret, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
@@ -97,6 +98,48 @@ function resolveTeamConfig(flags, env, file) {
|
|
|
97
98
|
: {}),
|
|
98
99
|
});
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Merge the `todo` section across all four layers (defaults › file › env ›
|
|
103
|
+
* flags), todo-plan-execution §4.3.
|
|
104
|
+
*
|
|
105
|
+
* `!== undefined` ON BOTH FLAGS, and that is the whole point (P1-8). `commander`
|
|
106
|
+
* materializes a lone `--no-x` as `opts.x = true` when the flag is absent, so a
|
|
107
|
+
* truthiness check cannot tell `--no-todo-panel` from "not passed" — and both
|
|
108
|
+
* keys here are persisted, so both need the distinction. This is the same rule
|
|
109
|
+
* `resolveTeamConfig` above states, one feature later.
|
|
110
|
+
*/
|
|
111
|
+
function resolveTodoConfig(flags, env, file) {
|
|
112
|
+
return clampTodoConfig({
|
|
113
|
+
...DEFAULT_CONFIG.todo,
|
|
114
|
+
...(file.todo ?? {}),
|
|
115
|
+
...(env.todo ?? {}),
|
|
116
|
+
...(flags.todo !== undefined ? { enabled: flags.todo } : {}),
|
|
117
|
+
...(flags.todoPanel !== undefined ? { panel: flags.todoPanel } : {}),
|
|
118
|
+
// `!== undefined` for the third time, though for a value flag the risk is
|
|
119
|
+
// milder: commander leaves `opts.todoFollow` absent unless it was passed.
|
|
120
|
+
// Written this way anyway so the three lines read as one rule.
|
|
121
|
+
...(flags.todoFollow !== undefined ? { followThrough: flags.todoFollow } : {}),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the adaptive render governor: flag › env › file › default `true`
|
|
126
|
+
* (tui-render-performance L4).
|
|
127
|
+
*
|
|
128
|
+
* Shaped like `resolveMouse` rather than like `resolveFullscreen`: there are no
|
|
129
|
+
* heuristics for an explicit `true` to override, so a persisted `true` is just a
|
|
130
|
+
* value and not an opinion that has to be told apart from silence. `!==
|
|
131
|
+
* undefined` on the flag is what makes `--no-render-governor` beat a config file
|
|
132
|
+
* that turned it on (P1-4's rule, one feature later).
|
|
133
|
+
*/
|
|
134
|
+
function resolveRenderGovernor(flags, env, file) {
|
|
135
|
+
if (flags.renderGovernor !== undefined)
|
|
136
|
+
return flags.renderGovernor;
|
|
137
|
+
if (env.renderGovernor !== undefined)
|
|
138
|
+
return env.renderGovernor;
|
|
139
|
+
if (file.renderGovernor !== undefined)
|
|
140
|
+
return file.renderGovernor;
|
|
141
|
+
return DEFAULT_CONFIG.renderGovernor;
|
|
142
|
+
}
|
|
100
143
|
/** Merge the `log` section across all four layers (defaults › file › env › flags). */
|
|
101
144
|
function resolveLogConfig(flags, env, file) {
|
|
102
145
|
const merged = clampLogConfig({
|
|
@@ -245,6 +288,23 @@ export function loadConfig(flags = {}) {
|
|
|
245
288
|
// The prompt-history store is a module-level singleton, so the resolved value
|
|
246
289
|
// has to be pushed into it — this is the ONLY place that happens.
|
|
247
290
|
setHistoryEnabled(historyEnabled);
|
|
291
|
+
// Transcript bounds (tui-render-performance L1 / L3).
|
|
292
|
+
//
|
|
293
|
+
// `transcriptRetain` is RAISED TO `transcriptWindow` when the two conflict:
|
|
294
|
+
// retaining fewer entries than the window can scroll to would silently make
|
|
295
|
+
// part of the window unreachable, and the user would see a horizon they can
|
|
296
|
+
// never actually reach.
|
|
297
|
+
//
|
|
298
|
+
// The raise is REPORTED, not just performed (§5.1). Overriding a value the
|
|
299
|
+
// user typed and saying nothing is the one thing this feature is not allowed
|
|
300
|
+
// to add — every other adjustment it makes is announced somewhere, and a
|
|
301
|
+
// config key is the last place to start making exceptions.
|
|
302
|
+
const transcriptWindow = clampTranscriptWindow(pick(env.partial.transcriptWindow, file.transcriptWindow), DEFAULT_CONFIG.transcriptWindow);
|
|
303
|
+
const requestedRetain = clampTranscriptRetain(pick(flags.transcriptRetain, env.partial.transcriptRetain, file.transcriptRetain), DEFAULT_CONFIG.transcriptRetain);
|
|
304
|
+
const transcriptRetain = Math.max(transcriptWindow, requestedRetain);
|
|
305
|
+
// `viewReducer` is pure and takes no config, so the resolved ring cap reaches
|
|
306
|
+
// it through the same module-singleton channel `setHistoryEnabled` uses.
|
|
307
|
+
setEntryRetain(transcriptRetain);
|
|
248
308
|
const config = {
|
|
249
309
|
provider,
|
|
250
310
|
model,
|
|
@@ -257,7 +317,11 @@ export function loadConfig(flags = {}) {
|
|
|
257
317
|
exitTranscript: flags.exitTranscript !== undefined
|
|
258
318
|
? flags.exitTranscript
|
|
259
319
|
: file.exitTranscript ?? DEFAULT_CONFIG.exitTranscript,
|
|
260
|
-
transcriptWindow
|
|
320
|
+
transcriptWindow,
|
|
321
|
+
transcriptRetain,
|
|
322
|
+
...(requestedRetain < transcriptRetain ? { transcriptRetainRequested: requestedRetain } : {}),
|
|
323
|
+
renderGovernor: resolveRenderGovernor(flags, env.partial, file),
|
|
324
|
+
maxRenderIntervalMs: clampMaxRenderInterval(pick(flags.maxRenderInterval, env.partial.maxRenderIntervalMs, file.maxRenderIntervalMs), DEFAULT_CONFIG.maxRenderIntervalMs),
|
|
261
325
|
confirmTools,
|
|
262
326
|
toolTimeoutMs,
|
|
263
327
|
idleTimeoutMs,
|
|
@@ -285,6 +349,7 @@ export function loadConfig(flags = {}) {
|
|
|
285
349
|
},
|
|
286
350
|
log: resolveLogConfig(flags, env.partial, file),
|
|
287
351
|
team: resolveTeamConfig(flags, env.partial, file),
|
|
352
|
+
todo: resolveTodoConfig(flags, env.partial, file),
|
|
288
353
|
cwd,
|
|
289
354
|
color,
|
|
290
355
|
colorLevel,
|