@aragon-agent/cli 0.5.6 → 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 +77 -0
- package/README.md +60 -2
- package/dist/agent/entry-limits.d.ts +65 -0
- package/dist/agent/entry-limits.js +133 -0
- package/dist/agent/reducer.d.ts +8 -0
- package/dist/agent/reducer.js +67 -15
- package/dist/cli.js +36 -2
- package/dist/commands/builtins.js +5 -0
- package/dist/commands/perf.d.ts +45 -0
- package/dist/commands/perf.js +78 -0
- package/dist/config/env.d.ts +3 -1
- package/dist/config/env.js +31 -1
- package/dist/config/load.d.ts +14 -0
- package/dist/config/load.js +43 -2
- package/dist/config/schema.d.ts +61 -8
- package/dist/config/schema.js +43 -6
- package/dist/ui/App.js +86 -9
- package/dist/ui/Markdown.d.ts +7 -1
- package/dist/ui/Markdown.js +15 -48
- package/dist/ui/StatusBar.d.ts +13 -0
- package/dist/ui/StatusBar.js +4 -2
- package/dist/ui/Transcript.d.ts +56 -4
- package/dist/ui/Transcript.js +112 -10
- 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 +9 -1
- package/dist/ui/entries/TodoCard.js +23 -1
- 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/layout/MeasuredEntry.d.ts +21 -0
- package/dist/ui/layout/MeasuredEntry.js +30 -0
- package/dist/ui/layout/ScrollViewport.js +8 -2
- 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/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 +2 -2
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';
|
|
@@ -102,6 +102,13 @@ function toFlags(opts) {
|
|
|
102
102
|
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
103
103
|
logFile: opts.logFile,
|
|
104
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,
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
114
|
// ---------------------------------------------------------------------------
|
|
@@ -239,6 +246,10 @@ function runInteractive(flags, extras = {}) {
|
|
|
239
246
|
provider: snapshot.provider,
|
|
240
247
|
model: snapshot.model,
|
|
241
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 } : {}),
|
|
242
253
|
}));
|
|
243
254
|
};
|
|
244
255
|
if (mode === 'fullscreen') {
|
|
@@ -393,6 +404,12 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
393
404
|
'fullscreen',
|
|
394
405
|
'exitTranscript',
|
|
395
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',
|
|
396
413
|
// Without these two, `density` and `hints` would be settable only as one-shot
|
|
397
414
|
// flags and never persist -- inconsistent with every other config field.
|
|
398
415
|
'density',
|
|
@@ -509,7 +526,16 @@ function runConfigSet(key, value) {
|
|
|
509
526
|
patch.exitTranscript = value === 'true' || value === '1';
|
|
510
527
|
break;
|
|
511
528
|
case 'transcriptWindow':
|
|
512
|
-
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);
|
|
513
539
|
break;
|
|
514
540
|
case 'density':
|
|
515
541
|
patch.density = clampDensity(value, 'comfortable');
|
|
@@ -654,6 +680,14 @@ function buildProgram() {
|
|
|
654
680
|
// No short form: `-v` is already `--version`.
|
|
655
681
|
.option('--verbose', 'Shorthand for --log-level debug')
|
|
656
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)')
|
|
657
691
|
.option('--no-color', 'Disable ANSI color')
|
|
658
692
|
.option('--quiet', '(print mode) suppress tool/usage lines on stderr')
|
|
659
693
|
.action(async (prompt, opts) => {
|
|
@@ -15,6 +15,7 @@ import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
|
15
15
|
import { loadConfig } from '../config/load.js';
|
|
16
16
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
17
17
|
import { getLogger } from '../logging/logger.js';
|
|
18
|
+
import { perfCommand } from './perf.js';
|
|
18
19
|
const COMMANDS = [
|
|
19
20
|
{
|
|
20
21
|
name: 'help',
|
|
@@ -535,6 +536,10 @@ const COMMANDS = [
|
|
|
535
536
|
ctx.toast('success', `Reloaded config: ${config.provider}:${config.model}.`);
|
|
536
537
|
},
|
|
537
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,
|
|
538
543
|
{
|
|
539
544
|
name: 'exit',
|
|
540
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
|
+
};
|
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
|
|
@@ -177,6 +179,34 @@ export function readEnvConfig() {
|
|
|
177
179
|
if (Object.keys(section).length > 0)
|
|
178
180
|
partial.todo = section;
|
|
179
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
|
+
}
|
|
180
210
|
const log = readEnvLogConfig();
|
|
181
211
|
if (log)
|
|
182
212
|
partial.log = log;
|
package/dist/config/load.d.ts
CHANGED
|
@@ -85,6 +85,20 @@ export interface CliFlags {
|
|
|
85
85
|
logFile?: boolean;
|
|
86
86
|
/** `--log-dir <dir>` — this run only. */
|
|
87
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;
|
|
88
102
|
}
|
|
89
103
|
/**
|
|
90
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, clampTodoConfig, 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';
|
|
@@ -120,6 +121,25 @@ function resolveTodoConfig(flags, env, file) {
|
|
|
120
121
|
...(flags.todoFollow !== undefined ? { followThrough: flags.todoFollow } : {}),
|
|
121
122
|
});
|
|
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
|
+
}
|
|
123
143
|
/** Merge the `log` section across all four layers (defaults › file › env › flags). */
|
|
124
144
|
function resolveLogConfig(flags, env, file) {
|
|
125
145
|
const merged = clampLogConfig({
|
|
@@ -268,6 +288,23 @@ export function loadConfig(flags = {}) {
|
|
|
268
288
|
// The prompt-history store is a module-level singleton, so the resolved value
|
|
269
289
|
// has to be pushed into it — this is the ONLY place that happens.
|
|
270
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);
|
|
271
308
|
const config = {
|
|
272
309
|
provider,
|
|
273
310
|
model,
|
|
@@ -280,7 +317,11 @@ export function loadConfig(flags = {}) {
|
|
|
280
317
|
exitTranscript: flags.exitTranscript !== undefined
|
|
281
318
|
? flags.exitTranscript
|
|
282
319
|
: file.exitTranscript ?? DEFAULT_CONFIG.exitTranscript,
|
|
283
|
-
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),
|
|
284
325
|
confirmTools,
|
|
285
326
|
toolTimeoutMs,
|
|
286
327
|
idleTimeoutMs,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -64,15 +64,40 @@ export declare const DEFAULT_IDLE_TIMEOUT_MS: number;
|
|
|
64
64
|
*/
|
|
65
65
|
export declare const PROMPT_HISTORY_CAP = 100;
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
67
|
+
* The SCROLL HORIZON: entries older than this are not reachable inside the app
|
|
68
|
+
* and render as the "N earlier entries collapsed" line instead (invariant I-3).
|
|
69
|
+
*
|
|
70
|
+
* It used to be a rendering budget as well, because yoga laid out every child
|
|
71
|
+
* even when `overflow: hidden` clipped it. The viewport is virtualised now
|
|
72
|
+
* (tui-render-performance L3) — off-screen entries are not mounted at all — so
|
|
73
|
+
* the horizon costs O(entries) of arithmetic rather than O(characters) of layout
|
|
74
|
+
* and the default rises 300 -> 1000 with a 20000 ceiling. An existing persisted
|
|
75
|
+
* `300` is still valid and still clamps; no config migration is required.
|
|
71
76
|
*/
|
|
72
|
-
export declare const DEFAULT_TRANSCRIPT_WINDOW =
|
|
77
|
+
export declare const DEFAULT_TRANSCRIPT_WINDOW = 1000;
|
|
73
78
|
export declare const MIN_TRANSCRIPT_WINDOW = 50;
|
|
74
|
-
export declare const MAX_TRANSCRIPT_WINDOW =
|
|
79
|
+
export declare const MAX_TRANSCRIPT_WINDOW = 20000;
|
|
75
80
|
export declare function clampTranscriptWindow(v: unknown, fallback: number): number;
|
|
81
|
+
/**
|
|
82
|
+
* Entries kept in `ViewState` at all (tui-render-performance L1 / R5).
|
|
83
|
+
*
|
|
84
|
+
* A DIFFERENT BOUND FROM `transcriptWindow`, and the two must not be merged:
|
|
85
|
+
* the window is what the user can scroll to, this is what the process keeps in
|
|
86
|
+
* memory and what `/save` can export. Dropped entries are COUNTED and reported
|
|
87
|
+
* (`/perf`, and a leading line in the exported transcript) — never silent.
|
|
88
|
+
*/
|
|
89
|
+
export declare const DEFAULT_TRANSCRIPT_RETAIN = 1000;
|
|
90
|
+
export declare const MIN_TRANSCRIPT_RETAIN = 200;
|
|
91
|
+
export declare const MAX_TRANSCRIPT_RETAIN = 20000;
|
|
92
|
+
export declare function clampTranscriptRetain(v: unknown, fallback: number): number;
|
|
93
|
+
/** Adaptive coalescing under load. On by default; `--no-render-governor` kills it. */
|
|
94
|
+
export declare const DEFAULT_RENDER_GOVERNOR = true;
|
|
95
|
+
/**
|
|
96
|
+
* Ceiling of the governor ladder, in ms. `33` flattens the ladder to a single
|
|
97
|
+
* rung, which is the pre-feature behaviour without needing the kill switch.
|
|
98
|
+
*/
|
|
99
|
+
export declare const DEFAULT_MAX_RENDER_INTERVAL_MS = 320;
|
|
100
|
+
export declare function clampMaxRenderInterval(v: unknown, fallback: number): number;
|
|
76
101
|
/** `ask_user` calls allowed per user turn. */
|
|
77
102
|
export declare const DEFAULT_PLAN_MAX_ASK_ROUNDS = 4;
|
|
78
103
|
/**
|
|
@@ -300,8 +325,14 @@ export interface PersistedConfig {
|
|
|
300
325
|
fullscreen: boolean;
|
|
301
326
|
/** Replay a plain-text session summary after leaving the alternate screen. */
|
|
302
327
|
exitTranscript: boolean;
|
|
303
|
-
/**
|
|
328
|
+
/** Entries reachable by scrolling; older ones show the "collapsed" line (I-3). */
|
|
304
329
|
transcriptWindow: number;
|
|
330
|
+
/** Entries kept in `ViewState`; older ones are dropped and counted (L1). */
|
|
331
|
+
transcriptRetain: number;
|
|
332
|
+
/** Adaptive coalescing under load (L4). */
|
|
333
|
+
renderGovernor: boolean;
|
|
334
|
+
/** Ceiling of the governor ladder in ms; `33` flattens it (L4). */
|
|
335
|
+
maxRenderIntervalMs: number;
|
|
305
336
|
confirmTools: boolean;
|
|
306
337
|
toolTimeoutMs: number;
|
|
307
338
|
idleTimeoutMs: number;
|
|
@@ -390,8 +421,30 @@ export interface CliConfig {
|
|
|
390
421
|
fullscreen?: boolean;
|
|
391
422
|
/** Replay a plain-text summary on exit (full-screen only — see §4.4). */
|
|
392
423
|
exitTranscript: boolean;
|
|
393
|
-
/**
|
|
424
|
+
/** Entries reachable by scrolling; older ones show the "collapsed" line (I-3). */
|
|
394
425
|
transcriptWindow: number;
|
|
426
|
+
/**
|
|
427
|
+
* Entries kept in `ViewState` (L1).
|
|
428
|
+
*
|
|
429
|
+
* Resolved to be `>= transcriptWindow`: retaining fewer entries than the
|
|
430
|
+
* window can scroll to would silently make part of the window unreachable.
|
|
431
|
+
*/
|
|
432
|
+
transcriptRetain: number;
|
|
433
|
+
/**
|
|
434
|
+
* What the user actually asked `transcriptRetain` to be, present ONLY when
|
|
435
|
+
* that value had to be raised (§5.1).
|
|
436
|
+
*
|
|
437
|
+
* A setting ignored without a word is the one failure mode this feature is
|
|
438
|
+
* not allowed to add: the eco chip, `/perf` and the dropped-entry line all
|
|
439
|
+
* exist so that nothing it does happens unannounced, and a config key
|
|
440
|
+
* overwritten in silence would be the exception. `undefined` means nothing
|
|
441
|
+
* was overridden, which is every default session.
|
|
442
|
+
*/
|
|
443
|
+
transcriptRetainRequested?: number;
|
|
444
|
+
/** Adaptive coalescing under load (L4). */
|
|
445
|
+
renderGovernor: boolean;
|
|
446
|
+
/** Ceiling of the governor ladder in ms; `33` flattens it (L4). */
|
|
447
|
+
maxRenderIntervalMs: number;
|
|
395
448
|
confirmTools: boolean;
|
|
396
449
|
toolTimeoutMs: number;
|
|
397
450
|
idleTimeoutMs: number;
|
package/dist/config/schema.js
CHANGED
|
@@ -98,18 +98,52 @@ export const DEFAULT_IDLE_TIMEOUT_MS = DEFAULT_TOOL_TIMEOUT_MS + IDLE_TIMEOUT_MA
|
|
|
98
98
|
*/
|
|
99
99
|
export const PROMPT_HISTORY_CAP = 100;
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
101
|
+
* The SCROLL HORIZON: entries older than this are not reachable inside the app
|
|
102
|
+
* and render as the "N earlier entries collapsed" line instead (invariant I-3).
|
|
103
|
+
*
|
|
104
|
+
* It used to be a rendering budget as well, because yoga laid out every child
|
|
105
|
+
* even when `overflow: hidden` clipped it. The viewport is virtualised now
|
|
106
|
+
* (tui-render-performance L3) — off-screen entries are not mounted at all — so
|
|
107
|
+
* the horizon costs O(entries) of arithmetic rather than O(characters) of layout
|
|
108
|
+
* and the default rises 300 -> 1000 with a 20000 ceiling. An existing persisted
|
|
109
|
+
* `300` is still valid and still clamps; no config migration is required.
|
|
105
110
|
*/
|
|
106
|
-
export const DEFAULT_TRANSCRIPT_WINDOW =
|
|
111
|
+
export const DEFAULT_TRANSCRIPT_WINDOW = 1000;
|
|
107
112
|
export const MIN_TRANSCRIPT_WINDOW = 50;
|
|
108
|
-
export const MAX_TRANSCRIPT_WINDOW =
|
|
113
|
+
export const MAX_TRANSCRIPT_WINDOW = 20_000;
|
|
109
114
|
export function clampTranscriptWindow(v, fallback) {
|
|
110
115
|
const n = coercePositiveInt(v, fallback);
|
|
111
116
|
return Math.min(MAX_TRANSCRIPT_WINDOW, Math.max(MIN_TRANSCRIPT_WINDOW, n));
|
|
112
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Entries kept in `ViewState` at all (tui-render-performance L1 / R5).
|
|
120
|
+
*
|
|
121
|
+
* A DIFFERENT BOUND FROM `transcriptWindow`, and the two must not be merged:
|
|
122
|
+
* the window is what the user can scroll to, this is what the process keeps in
|
|
123
|
+
* memory and what `/save` can export. Dropped entries are COUNTED and reported
|
|
124
|
+
* (`/perf`, and a leading line in the exported transcript) — never silent.
|
|
125
|
+
*/
|
|
126
|
+
export const DEFAULT_TRANSCRIPT_RETAIN = 1000;
|
|
127
|
+
export const MIN_TRANSCRIPT_RETAIN = 200;
|
|
128
|
+
export const MAX_TRANSCRIPT_RETAIN = 20_000;
|
|
129
|
+
export function clampTranscriptRetain(v, fallback) {
|
|
130
|
+
const n = coercePositiveInt(v, fallback);
|
|
131
|
+
return Math.min(MAX_TRANSCRIPT_RETAIN, Math.max(MIN_TRANSCRIPT_RETAIN, n));
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Adaptive render governor (tui-render-performance L4 / R7)
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
/** Adaptive coalescing under load. On by default; `--no-render-governor` kills it. */
|
|
137
|
+
export const DEFAULT_RENDER_GOVERNOR = true;
|
|
138
|
+
/**
|
|
139
|
+
* Ceiling of the governor ladder, in ms. `33` flattens the ladder to a single
|
|
140
|
+
* rung, which is the pre-feature behaviour without needing the kill switch.
|
|
141
|
+
*/
|
|
142
|
+
export const DEFAULT_MAX_RENDER_INTERVAL_MS = 320;
|
|
143
|
+
const RENDER_INTERVAL_RANGE = { min: 33, max: 1000 };
|
|
144
|
+
export function clampMaxRenderInterval(v, fallback) {
|
|
145
|
+
return clampInt(v, fallback, RENDER_INTERVAL_RANGE);
|
|
146
|
+
}
|
|
113
147
|
// ---------------------------------------------------------------------------
|
|
114
148
|
// Plan mode (plan-mode §4.4)
|
|
115
149
|
//
|
|
@@ -360,6 +394,9 @@ export const DEFAULT_CONFIG = {
|
|
|
360
394
|
fullscreen: true,
|
|
361
395
|
exitTranscript: true,
|
|
362
396
|
transcriptWindow: DEFAULT_TRANSCRIPT_WINDOW,
|
|
397
|
+
transcriptRetain: DEFAULT_TRANSCRIPT_RETAIN,
|
|
398
|
+
renderGovernor: DEFAULT_RENDER_GOVERNOR,
|
|
399
|
+
maxRenderIntervalMs: DEFAULT_MAX_RENDER_INTERVAL_MS,
|
|
363
400
|
confirmTools: false,
|
|
364
401
|
toolTimeoutMs: DEFAULT_TOOL_TIMEOUT_MS,
|
|
365
402
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|