@aragon-agent/cli 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -71
- package/dist/agent/controller.d.ts +10 -0
- package/dist/agent/controller.js +52 -47
- package/dist/agent/reducer.js +0 -4
- package/dist/cli.js +49 -15
- package/dist/commands/builtins.js +27 -43
- package/dist/compaction/child.d.ts +10 -8
- package/dist/compaction/child.js +23 -25
- package/dist/compaction/context-command.js +14 -3
- package/dist/compaction/limits.d.ts +0 -2
- package/dist/compaction/limits.js +0 -2
- package/dist/compaction/types.d.ts +6 -1
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +16 -15
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +13 -1
- package/dist/config/load.d.ts +9 -17
- package/dist/config/load.js +14 -6
- package/dist/config/model-windows.d.ts +91 -0
- package/dist/config/model-windows.js +181 -0
- package/dist/config/schema.d.ts +34 -5
- package/dist/config/schema.js +15 -11
- package/dist/exec/events.d.ts +12 -8
- package/dist/exec/events.js +2 -2
- package/dist/exec/runner.js +4 -1
- package/dist/fast/limits.d.ts +2 -2
- package/dist/fast/limits.js +2 -2
- package/dist/fast/prompt.d.ts +10 -11
- package/dist/fast/prompt.js +19 -20
- package/dist/fast/reviewer.d.ts +1 -1
- package/dist/fast/reviewer.js +1 -1
- package/dist/fast/types.d.ts +0 -2
- package/dist/fast/wiring.d.ts +18 -34
- package/dist/fast/wiring.js +20 -51
- package/dist/input/csiu-keys.d.ts +66 -0
- package/dist/input/csiu-keys.js +170 -0
- package/dist/input/keyboard-enhancement.d.ts +42 -0
- package/dist/input/keyboard-enhancement.js +65 -0
- package/dist/input/stdin-filter.d.ts +9 -0
- package/dist/input/stdin-filter.js +45 -9
- package/dist/input/win32-input-mode.d.ts +61 -0
- package/dist/input/win32-input-mode.js +251 -0
- package/dist/session/validate-session.js +3 -1
- package/dist/team/child-reviewer.d.ts +105 -0
- package/dist/team/child-reviewer.js +117 -0
- package/dist/team/normalize.d.ts +7 -26
- package/dist/team/normalize.js +9 -22
- package/dist/team/prompt.d.ts +0 -11
- package/dist/team/prompt.js +3 -12
- package/dist/team/report.d.ts +4 -19
- package/dist/team/report.js +1 -27
- package/dist/team/runtime.d.ts +5 -10
- package/dist/team/runtime.js +35 -39
- package/dist/team/subagent.d.ts +18 -18
- package/dist/team/subagent.js +80 -30
- package/dist/team/task-tool.d.ts +6 -26
- package/dist/team/task-tool.js +13 -47
- package/dist/team/types.d.ts +3 -42
- package/dist/ui/App.js +3 -4
- package/dist/ui/BottomStatusRow.js +2 -1
- package/dist/ui/StatusBar.js +2 -1
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/entries/TeamCard.js +1 -6
- package/dist/ui/layout/status-detail-layout.js +12 -2
- package/dist/ui/layout/status-layout.d.ts +4 -2
- package/dist/ui/layout/status-layout.js +24 -1
- package/dist/ui/overlays/HelpOverlay.js +1 -2
- package/package.json +2 -2
- package/dist/ui/ModeChip.d.ts +0 -18
- package/dist/ui/ModeChip.js +0 -24
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ import { createFrameDiffer } from './ui/frame-differ.js';
|
|
|
46
46
|
import { wrapStdoutForFrames } from './ui/stdout-frame-writer.js';
|
|
47
47
|
import { setFrameStatsProvider } from './commands/perf.js';
|
|
48
48
|
import { tryCreateStdinFilter } from './input/stdin-filter.js';
|
|
49
|
+
import { enableKeyboardEnhancement, NOOP_KEYBOARD_ENHANCEMENT, } from './input/keyboard-enhancement.js';
|
|
49
50
|
import { createScrollbarBridge } from './ui/scrollbar-controller.js';
|
|
50
51
|
import { createPointerRouter } from './input/pointer-router.js';
|
|
51
52
|
import { createFrameObserver } from './ui/frame-observer.js';
|
|
@@ -120,6 +121,10 @@ function toFlags(opts) {
|
|
|
120
121
|
// `CliFlags`, AND a line here; `config.test.ts` is the regression pin that
|
|
121
122
|
// exists because `--no-mouse` shipped inert once already.
|
|
122
123
|
paste: opts.paste,
|
|
124
|
+
// Site three of three for `--keyboard-enhancement` /
|
|
125
|
+
// `--no-keyboard-enhancement`, for the reason `paste` one line up
|
|
126
|
+
// records: the key is persisted and defaults to true.
|
|
127
|
+
keyboardEnhancement: opts.keyboardEnhancement,
|
|
123
128
|
skills: opts.skills,
|
|
124
129
|
skill: opts.skill,
|
|
125
130
|
skillsYes: opts.skillsYes,
|
|
@@ -155,11 +160,6 @@ function toFlags(opts) {
|
|
|
155
160
|
fastModel: opts.fastModel,
|
|
156
161
|
fastProvider: opts.fastProvider,
|
|
157
162
|
fastReview: opts.fastReview,
|
|
158
|
-
// …and again for `--fast-delegate` / `--no-fast-delegate`, one feature
|
|
159
|
-
// later — NINE features, one trap. It is `fast.delegate`'s only channel:
|
|
160
|
-
// there is no `ARAGON_FAST_DELEGATE`, so missing this line leaves a wrapper
|
|
161
|
-
// with no way at all to say "do not delegate on this run".
|
|
162
|
-
fastDelegate: opts.fastDelegate,
|
|
163
163
|
// `--verbose` is just a shorthand for `--log-level debug`; an explicit
|
|
164
164
|
// `--log-level` still wins, so the two can be combined without surprise.
|
|
165
165
|
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
@@ -465,8 +465,22 @@ function runInteractive(flags, extras = {}) {
|
|
|
465
465
|
// advising the user about a mode they just disabled.
|
|
466
466
|
const wantPaste = config.paste && !!process.stdin.isTTY;
|
|
467
467
|
const pasteBridge = { notify: null };
|
|
468
|
+
// --- Keyboard enhancement (Shift+Enter newline). --------------------------
|
|
469
|
+
//
|
|
470
|
+
// THE THIRD STREAM GATE, AND THE SAME DISCIPLINE AS THE OTHER TWO. On
|
|
471
|
+
// win32 the enhancement rides the SAME `vtInputSupported` measurement
|
|
472
|
+
// the mouse does: Node < 22.17 / < 24.2 never turns on
|
|
473
|
+
// `ENABLE_VIRTUAL_TERMINAL_INPUT`, the win32-input-mode records cannot
|
|
474
|
+
// reach the process, and pushing `?9001h` there would be a mode nobody
|
|
475
|
+
// can observe -- the exact gap the mouse gate closes for `?1000h`.
|
|
476
|
+
// Elsewhere the push is kitty disambiguate + modifyOtherKeys, which a
|
|
477
|
+
// terminal that implements neither ignores (verified byte-identical on
|
|
478
|
+
// conhost), so no capability gate is possible or needed there.
|
|
479
|
+
const wantEnhancedKeys = config.keyboardEnhancement &&
|
|
480
|
+
!!process.stdin.isTTY &&
|
|
481
|
+
(process.platform !== 'win32' || vtInputSupported);
|
|
468
482
|
const filter = process.stdin.isTTY
|
|
469
|
-
? tryCreateStdinFilter(process.stdin, { mouse: wantMouse, paste: wantPaste }, (reason) => logger.warn('cli', 'stdin_filter_failed', { reason }), pasteBridge)
|
|
483
|
+
? tryCreateStdinFilter(process.stdin, { mouse: wantMouse, paste: wantPaste, enhancedKeys: wantEnhancedKeys }, (reason) => logger.warn('cli', 'stdin_filter_failed', { reason }), pasteBridge)
|
|
470
484
|
: null;
|
|
471
485
|
/**
|
|
472
486
|
* THE ONE EXPRESSION EVERY MOUSE SITE READS (I-11 / D-17).
|
|
@@ -478,6 +492,8 @@ function runInteractive(flags, extras = {}) {
|
|
|
478
492
|
const mouseOn = wantMouse && filter !== null;
|
|
479
493
|
/** Same discipline for DEC 2004: already decided here, never re-derived. */
|
|
480
494
|
const pasteOn = wantPaste && filter !== null;
|
|
495
|
+
/** Same discipline: the translation runs only when a filter fronts Ink. */
|
|
496
|
+
const enhancedKeysOn = wantEnhancedKeys && filter !== null;
|
|
481
497
|
const disposeStdinFilter = () => filter?.dispose();
|
|
482
498
|
// --- Drag-select (tui-selection-and-scroll-follow §4.4). ------------------
|
|
483
499
|
//
|
|
@@ -565,6 +581,16 @@ function runInteractive(flags, extras = {}) {
|
|
|
565
581
|
...(snapshot.droppedEntries ? { droppedEntries: snapshot.droppedEntries } : {}),
|
|
566
582
|
}));
|
|
567
583
|
};
|
|
584
|
+
// AFTER the filter, BEFORE the render: the enhanced encodings this mode
|
|
585
|
+
// turns on must never reach Ink un-translated, and the ordering invariant
|
|
586
|
+
// `stdin-filter.ts` states for `?1000h` / `?2004h` covers `?9001h`
|
|
587
|
+
// and the CSI-u pushes identically.
|
|
588
|
+
const keyboardEnhancement = enhancedKeysOn
|
|
589
|
+
? enableKeyboardEnhancement(process.stdout, {
|
|
590
|
+
platform: process.platform,
|
|
591
|
+
vtInputSupported,
|
|
592
|
+
})
|
|
593
|
+
: NOOP_KEYBOARD_ENHANCEMENT;
|
|
568
594
|
{
|
|
569
595
|
// `mouse` here means "a mouse-parsing filter is installed", NOT "the user
|
|
570
596
|
// wants mouse support" — see `AltScreenOptions`. `screen.ts` must never
|
|
@@ -581,7 +607,14 @@ function runInteractive(flags, extras = {}) {
|
|
|
581
607
|
motion: mouseOn,
|
|
582
608
|
bracketedPaste: pasteOn,
|
|
583
609
|
});
|
|
584
|
-
|
|
610
|
+
// Input-encoding modes unwind FIRST, for the same reason the mouse
|
|
611
|
+
// reporting does inside `screen.restore()`: the terminal must stop
|
|
612
|
+
// sending enhanced encodings before anything else tears the reader
|
|
613
|
+
// down, or a keystroke taken in that instant survives into the shell.
|
|
614
|
+
const restore = () => {
|
|
615
|
+
keyboardEnhancement.restore();
|
|
616
|
+
screen?.restore();
|
|
617
|
+
};
|
|
585
618
|
// Four idempotent restore paths (§4.4). The signal hook is NOT redundant:
|
|
586
619
|
// - `waitUntilExit().then()` is a microtask and may never be reached when
|
|
587
620
|
// the process is killed;
|
|
@@ -948,6 +981,9 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
948
981
|
// `mouse` records: a key present here but absent there falls through every
|
|
949
982
|
// case, writes nothing, and still prints `Set paste = false`.
|
|
950
983
|
'paste',
|
|
984
|
+
// Listed here AND cased in the switch below, for the reason the note above
|
|
985
|
+
// `paste` records.
|
|
986
|
+
'keyboardEnhancement',
|
|
951
987
|
'scrollResumeMs',
|
|
952
988
|
'historyEnabled',
|
|
953
989
|
'planModeDefault',
|
|
@@ -1187,6 +1223,9 @@ function runConfigSetValue(key, value) {
|
|
|
1187
1223
|
case 'paste':
|
|
1188
1224
|
patch.paste = value === 'true' || value === '1';
|
|
1189
1225
|
break;
|
|
1226
|
+
case 'keyboardEnhancement':
|
|
1227
|
+
patch.keyboardEnhancement = value === 'true' || value === '1';
|
|
1228
|
+
break;
|
|
1190
1229
|
// Clamped rather than rejected, for the same reason `planModeDefault` below
|
|
1191
1230
|
// records: hardening only the READ path leaves a bad value on disk that
|
|
1192
1231
|
// reverts to the default on every launch, which presents as "my setting
|
|
@@ -1451,6 +1490,9 @@ function buildProgram() {
|
|
|
1451
1490
|
// silence, and silently overriding a stored `false` on every run.
|
|
1452
1491
|
.option('--paste', 'Collapse large pastes into a placeholder (the default)')
|
|
1453
1492
|
.option('--no-paste', 'Treat pasted bytes as keystrokes (pre-0.6.3 behavior)')
|
|
1493
|
+
// Same persisted-defaults-true trap as `--paste` above.
|
|
1494
|
+
.option('--keyboard-enhancement', 'Make Shift+Enter insert a newline (the default where the terminal can)')
|
|
1495
|
+
.option('--no-keyboard-enhancement', 'Never push keyboard-enhancement modes (Shift+Enter then needs a terminal binding)')
|
|
1454
1496
|
.option('--no-exit-transcript', 'Do not replay the session summary after exiting')
|
|
1455
1497
|
.option('--plan', 'Start the session in PLAN mode (read-only research + review)')
|
|
1456
1498
|
.option('--no-plan', 'Start the session in BUILD mode (overrides planModeDefault)')
|
|
@@ -1490,14 +1532,6 @@ function buildProgram() {
|
|
|
1490
1532
|
.option('--fast-model <id>', 'The fast model id (implies nothing about --fast)')
|
|
1491
1533
|
.option('--fast-provider <id>', 'Provider for the fast model (default: the main provider)')
|
|
1492
1534
|
.option('--fast-review <n|off>', 'Turns between automatic fast reviews, or "off"')
|
|
1493
|
-
// BOTH FORMS, POSITIVE FIRST, and here it matters more than anywhere above:
|
|
1494
|
-
// `fast.delegate` is PERSISTED and defaults to TRUE, so declaring only
|
|
1495
|
-
// `--no-fast-delegate` would make commander default `opts.fastDelegate` to
|
|
1496
|
-
// `true` and force `delegate: true` on EVERY run that passed no flag at all
|
|
1497
|
-
// — silently overwriting a user's stored `false`. Same polarity, same trap
|
|
1498
|
-
// and same fix as `--update` / `--compaction`.
|
|
1499
|
-
.option('--fast-delegate', 'Let the fast tier take mechanical sub-steps (the default)')
|
|
1500
|
-
.option('--no-fast-delegate', 'Run every sub-step on the main model')
|
|
1501
1535
|
// BOTH FORMS, positive first, for the reason recorded seven times above —
|
|
1502
1536
|
// and this is the pair whose omission would be worst. `update.mode` is
|
|
1503
1537
|
// PERSISTED and defaults to `'auto'`, so a lone `--no-update` would make
|
|
@@ -106,8 +106,7 @@ function formatFastStatus(controller) {
|
|
|
106
106
|
else {
|
|
107
107
|
lines.push(`Fast tier: ${state}. Model: ${cfg.model || '(not set)'}.`);
|
|
108
108
|
}
|
|
109
|
-
lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}
|
|
110
|
-
`Delegation: ${cfg.delegate ? 'on' : 'off'}.`);
|
|
109
|
+
lines.push(`Review: ${cfg.review ? `every ${cfg.reviewEveryTurns} turns` : 'off'}.`);
|
|
111
110
|
const snapshot = status.snapshot;
|
|
112
111
|
const cost = snapshot.pricingUnknown
|
|
113
112
|
? `unknown (no price table for ${snapshot.model})`
|
|
@@ -124,7 +123,6 @@ function formatFastStatus(controller) {
|
|
|
124
123
|
// numbers are `0` placeholders and `reviews >= reviewBudget` would be true.
|
|
125
124
|
const budget = snapshot.budgetReached ? ' (budget reached - raise with /fast budget <n>)' : '';
|
|
126
125
|
lines.push(`This session: ${snapshot.reviews}/${snapshot.reviewBudget} reviews${budget}, ` +
|
|
127
|
-
`${snapshot.delegated} delegated subagents, ` +
|
|
128
126
|
`in ${formatTokens(snapshot.usage.inputTokens)} / out ` +
|
|
129
127
|
`${formatTokens(snapshot.usage.outputTokens)}, cost ${cost}.`);
|
|
130
128
|
return lines.join('\n');
|
|
@@ -339,7 +337,7 @@ const COMMANDS = [
|
|
|
339
337
|
{
|
|
340
338
|
name: 'fast',
|
|
341
339
|
description: 'Fast model tier: status | on | off | model <id> | provider <id> | same | ' +
|
|
342
|
-
'review <n|off> | budget <n>
|
|
340
|
+
'review <n|off> | budget <n>',
|
|
343
341
|
/**
|
|
344
342
|
* `/fast on` CANNOT turn the tier on in a session that started without it,
|
|
345
343
|
* for the reason `/team on` above records at length: the tool array is built
|
|
@@ -516,20 +514,8 @@ const COMMANDS = [
|
|
|
516
514
|
`${snapshot.reviews} used.`);
|
|
517
515
|
return;
|
|
518
516
|
}
|
|
519
|
-
if (verb === 'delegate') {
|
|
520
|
-
const raw = (value ?? '').trim().toLowerCase();
|
|
521
|
-
if (raw !== 'on' && raw !== 'off') {
|
|
522
|
-
ctx.notify('warn', 'Usage: /fast delegate <on|off>');
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
if (!persist({ delegate: raw === 'on' }))
|
|
526
|
-
return;
|
|
527
|
-
ctx.toast('success', `Fast delegation ${raw}.`);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
517
|
ctx.notify('warn', `Unknown argument "${verb}" - use /fast ` +
|
|
531
|
-
'[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n
|
|
532
|
-
'delegate on|off].');
|
|
518
|
+
'[status|on|off|model <id>|provider <id>|same|review <n|off>|budget <n>].');
|
|
533
519
|
},
|
|
534
520
|
},
|
|
535
521
|
{
|
|
@@ -1054,55 +1040,53 @@ const COMMANDS = [
|
|
|
1054
1040
|
},
|
|
1055
1041
|
{
|
|
1056
1042
|
name: 'terminal-setup',
|
|
1057
|
-
description: 'How
|
|
1043
|
+
description: 'How Shift+Enter sends a newline in your terminal',
|
|
1058
1044
|
run: (ctx) => {
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1066
|
-
// either. The lines below are for the terminals that send a plain
|
|
1067
|
-
// CR for Shift+Enter and CAN be configured.
|
|
1045
|
+
// THE APP NOW PUSHES THE MODES ITSELF (shift-enter-newline fix):
|
|
1046
|
+
// win32-input-mode on Windows, kitty disambiguate + modifyOtherKeys=2
|
|
1047
|
+
// elsewhere, each translated back to legacy bytes before Ink parses
|
|
1048
|
+
// them. The lines below cover the terminals where pushing is not
|
|
1049
|
+
// enough: macOS Terminal.app and conhost under an old Node publish no
|
|
1050
|
+
// keyboard-enhancement protocol at all, and a terminal that ignores
|
|
1051
|
+
// the push still sends a plain CR for Shift+Enter.
|
|
1068
1052
|
const lines = [
|
|
1069
1053
|
'Shift+Enter newline - terminal setup',
|
|
1070
1054
|
'',
|
|
1071
|
-
'
|
|
1072
|
-
'
|
|
1073
|
-
'
|
|
1055
|
+
'This app asks the terminal to report modified keys itself:',
|
|
1056
|
+
' Windows: win32-input-mode (conhost and Windows Terminal)',
|
|
1057
|
+
' macOS/Linux: kitty disambiguate + modifyOtherKeys (iTerm2,',
|
|
1058
|
+
' kitty, ghostty, WezTerm, foot, Alacritty)',
|
|
1059
|
+
'A terminal that implements none of these sends a plain CR for',
|
|
1060
|
+
'Shift+Enter, indistinguishable from Enter. There Ctrl+J inserts a',
|
|
1061
|
+
'newline with no setup, and Alt+Enter may also work.',
|
|
1062
|
+
'',
|
|
1063
|
+
'If your terminal can be configured, make Shift+Enter send the',
|
|
1064
|
+
'CSI-u sequence \\u001b[13;2u and it works even without the push:',
|
|
1074
1065
|
'',
|
|
1075
1066
|
' Windows Terminal settings.json, merge root-level arrays:',
|
|
1076
1067
|
' "copyOnSelect": false,',
|
|
1077
1068
|
' "actions": [{ "id": "User.AragonNewline", "command":',
|
|
1078
1069
|
' { "action": "sendInput", "input": "\\u001b[13;2u" } }],',
|
|
1079
1070
|
' "keybindings": [{ "keys": "shift+enter", "id": "User.AragonNewline" }]',
|
|
1080
|
-
' PowerShell runs inside the terminal; configure this binding in',
|
|
1081
|
-
' Windows Terminal, not in the PowerShell profile. If both keys',
|
|
1082
|
-
' send CR, the application cannot distinguish Shift+Enter from Enter.',
|
|
1083
1071
|
' VS Code terminal keybindings.json:',
|
|
1084
1072
|
' { "key": "shift+enter",',
|
|
1085
1073
|
' "command": "workbench.action.terminal.sendSequence",',
|
|
1086
1074
|
' "when": "terminalFocus",',
|
|
1087
1075
|
' "args": { "text": "\\u001b[13;2u" } }',
|
|
1088
|
-
' User settings: "terminal.integrated.copyOnSelection": false',
|
|
1089
1076
|
' iTerm2 Settings > Profiles > Keys > Key Mappings:',
|
|
1090
1077
|
' Shift+Enter -> Send Escape Sequence: [13;2u',
|
|
1091
1078
|
' kitty/WezTerm/foot native once keyboard enhancement is on',
|
|
1092
1079
|
' (kitty: map shift+enter send_text all \\u001b[13;2u)',
|
|
1093
1080
|
' Alacritty alacritty.toml, [keyboard] bindings:',
|
|
1094
1081
|
' { key = "Enter", mods = "Shift", chars = "\\u001b[13;2u" }',
|
|
1095
|
-
' macOS Terminal.app / conhost cannot rebind Enter; use Ctrl+J',
|
|
1096
|
-
' or Alt+Enter',
|
|
1082
|
+
' macOS Terminal.app / old conhost cannot rebind Enter; use Ctrl+J',
|
|
1097
1083
|
'',
|
|
1084
|
+
'Windows needs Node >= 22.17 (or >= 24.2): older versions never',
|
|
1085
|
+
'turn on virtual-terminal input, so no report can arrive.',
|
|
1086
|
+
'Opt out of the push entirely:',
|
|
1087
|
+
' aragon config set keyboardEnhancement false',
|
|
1088
|
+
' --no-keyboard-enhancement / ARAGON_KEYBOARD_ENHANCEMENT=0',
|
|
1098
1089
|
'This command only prints advice; it changes no terminal modes.',
|
|
1099
|
-
'Host selections (Shift+drag or /mouse off) belong to the terminal.',
|
|
1100
|
-
'App selections wait for Ctrl+C; only a native tool exit confirms copying.',
|
|
1101
|
-
'SSH uses OSC 52: the terminal may deny it; sent does not mean confirmed.',
|
|
1102
|
-
'Without the stdin filter, Delete falls back to Backspace.',
|
|
1103
|
-
'Without paste support, multiline paste cannot be guaranteed safe.',
|
|
1104
|
-
'IME confirmation has no composition signal in PTY bytes. If it emits CR,',
|
|
1105
|
-
'edit externally and use bracketed paste; this host combination needs validation.',
|
|
1106
1090
|
];
|
|
1107
1091
|
ctx.notify('info', lines.join('\n'));
|
|
1108
1092
|
},
|
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
* turn the child already ran, and receives one partial sentence for all of it.
|
|
14
14
|
* Two fast-model summarizations is the cheap side of that trade.
|
|
15
15
|
*
|
|
16
|
-
* A CHILD MANAGER
|
|
17
|
-
* overlay
|
|
16
|
+
* SINCE MAIN-AGENT PARITY, A CHILD MANAGER RUNS THE LEAD'S OWN POLICY. The only
|
|
17
|
+
* overlay left is the CONNECTION (provider / model / base URL / manual window),
|
|
18
|
+
* which must follow the model the child actually runs on. `keepRecentTurns`,
|
|
19
|
+
* `onFailure`, `useFastTier`, `archive` and the per-run compaction bound are all
|
|
20
|
+
* read from the SAME live config the lead reads, so a user who tunes compaction
|
|
21
|
+
* tunes every child identically - the requirement "same mode and configuration
|
|
22
|
+
* as the main agent", taken literally.
|
|
18
23
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* path by design (`limits.ts` header). Without it the child silently
|
|
23
|
-
* inherits the lead's 5 - two and a half times the bound the reversal above
|
|
24
|
-
* rests on.
|
|
24
|
+
* THE INSTANCE BOUND stays (mechanism B of RV-2) because `maxPerRun` is
|
|
25
|
+
* STRUCTURAL with no config path by design (`limits.ts` header); it is now the
|
|
26
|
+
* SAME `COMPACTION_LIMITS.maxPerRun` the lead's own manager runs under.
|
|
25
27
|
*
|
|
26
28
|
* NO UI. A child's compaction produces no transcript card (DH-7): the lead's
|
|
27
29
|
* transcript describes the LEAD's context, and a card there would claim the
|
package/dist/compaction/child.js
CHANGED
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
* turn the child already ran, and receives one partial sentence for all of it.
|
|
14
14
|
* Two fast-model summarizations is the cheap side of that trade.
|
|
15
15
|
*
|
|
16
|
-
* A CHILD MANAGER
|
|
17
|
-
* overlay
|
|
16
|
+
* SINCE MAIN-AGENT PARITY, A CHILD MANAGER RUNS THE LEAD'S OWN POLICY. The only
|
|
17
|
+
* overlay left is the CONNECTION (provider / model / base URL / manual window),
|
|
18
|
+
* which must follow the model the child actually runs on. `keepRecentTurns`,
|
|
19
|
+
* `onFailure`, `useFastTier`, `archive` and the per-run compaction bound are all
|
|
20
|
+
* read from the SAME live config the lead reads, so a user who tunes compaction
|
|
21
|
+
* tunes every child identically - the requirement "same mode and configuration
|
|
22
|
+
* as the main agent", taken literally.
|
|
18
23
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* path by design (`limits.ts` header). Without it the child silently
|
|
23
|
-
* inherits the lead's 5 - two and a half times the bound the reversal above
|
|
24
|
-
* rests on.
|
|
24
|
+
* THE INSTANCE BOUND stays (mechanism B of RV-2) because `maxPerRun` is
|
|
25
|
+
* STRUCTURAL with no config path by design (`limits.ts` header); it is now the
|
|
26
|
+
* SAME `COMPACTION_LIMITS.maxPerRun` the lead's own manager runs under.
|
|
25
27
|
*
|
|
26
28
|
* NO UI. A child's compaction produces no transcript card (DH-7): the lead's
|
|
27
29
|
* transcript describes the LEAD's context, and a card there would claim the
|
|
@@ -45,11 +47,11 @@ export function createChildContextManager(req, deps) {
|
|
|
45
47
|
* which is the same reason `subagent.ts` reuses the lead's `getApiKey` closure
|
|
46
48
|
* rather than the resolved key.
|
|
47
49
|
*
|
|
48
|
-
* `useFastTier
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* `useFastTier` is read LIVE from the base config (main-agent
|
|
51
|
+
* parity - no overlay): it expresses a PREFERENCE, not an outcome.
|
|
52
|
+
* `resolveFastTier` still decides, and when nothing resolves the child
|
|
53
|
+
* falls back exactly as the lead does - to its OWN model, because
|
|
54
|
+
* `provider` / `model` are overlaid here too.
|
|
53
55
|
* Failed summaries preserve the child's task just as they preserve the lead's.
|
|
54
56
|
*/
|
|
55
57
|
const childConfig = () => {
|
|
@@ -63,16 +65,11 @@ export function createChildContextManager(req, deps) {
|
|
|
63
65
|
baseUrl: req.model.baseUrl,
|
|
64
66
|
// The lead's manual limit describes its connection, not every worker.
|
|
65
67
|
contextWindow: sameConnection ? base.contextWindow : null,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// ONE ARCHIVE PER LEAD COMPACTION IS AUDITABLE; TWENTY PER DISPATCH IS
|
|
72
|
-
// NOISE. The child's dropped slice is therefore never retained either -
|
|
73
|
-
// `runCompaction` reads this key before it stashes anything.
|
|
74
|
-
archive: false,
|
|
75
|
-
},
|
|
68
|
+
// NO POLICY OVERLAY (main-agent parity): `keepRecentTurns`, `onFailure`,
|
|
69
|
+
// `useFastTier` and `archive` are read LIVE from the base config, so the
|
|
70
|
+
// child compacts under exactly the policy the lead compacts under. A
|
|
71
|
+
// settings-screen edit mid-dispatch reaches a running child because this
|
|
72
|
+
// view is re-read on every call.
|
|
76
73
|
};
|
|
77
74
|
};
|
|
78
75
|
const meter = new ContextMeter({
|
|
@@ -121,8 +118,9 @@ export function createChildContextManager(req, deps) {
|
|
|
121
118
|
// user's transcript with no card to explain which agent it is about.
|
|
122
119
|
notify: () => { },
|
|
123
120
|
},
|
|
124
|
-
// MECHANISM B - the instance bound (RV-2 / DH-16).
|
|
125
|
-
|
|
121
|
+
// MECHANISM B - the instance bound (RV-2 / DH-16). The SAME bound the
|
|
122
|
+
// lead's own manager runs under (main-agent parity).
|
|
123
|
+
{ maxPerRun: COMPACTION_LIMITS.maxPerRun });
|
|
126
124
|
return {
|
|
127
125
|
// THERE IS NO `/compact` FOR A CHILD, so nothing is ever queued and the
|
|
128
126
|
// manual branch of `shouldCompact` is unreachable by construction.
|
|
@@ -74,7 +74,9 @@ function occupancyLine(usage) {
|
|
|
74
74
|
return `${pct} ${formatTokens(usage.occupied)} of ${formatTokens(usage.window)} ${breakdown}`;
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
* Where the denominator came from.
|
|
77
|
+
* Where the denominator came from. FOUR SOURCES, ALL NAMED - the fourth
|
|
78
|
+
* being the user's own model-windows.json, which exists precisely because
|
|
79
|
+
* the other three cannot know a gateway alias released last week.
|
|
78
80
|
*
|
|
79
81
|
* THE PLACEHOLDER BRANCH IS THE ONE THAT MATTERS. `buildRuntimeModel` returns a
|
|
80
82
|
* flat 128000 for any model the static table has never seen - a custom
|
|
@@ -85,11 +87,20 @@ function occupancyLine(usage) {
|
|
|
85
87
|
*/
|
|
86
88
|
function windowLine(usage) {
|
|
87
89
|
if (usage.windowOverridden) {
|
|
88
|
-
return `${usage.window} from contextWindow configuration (overrides API and model
|
|
90
|
+
return `${usage.window} from contextWindow configuration (overrides API, model table, and model-windows.json)`;
|
|
89
91
|
}
|
|
90
92
|
if (!usage.windowKnown) {
|
|
91
93
|
return (`UNKNOWN - no verified model limit; internal fallback budget ${usage.window}; ` +
|
|
92
|
-
'set contextWindow to correct it');
|
|
94
|
+
'set contextWindow or list the model in model-windows.json to correct it');
|
|
95
|
+
}
|
|
96
|
+
if (usage.windowSource === 'user') {
|
|
97
|
+
return `${usage.window} from model-windows.json (your per-model table)`;
|
|
98
|
+
}
|
|
99
|
+
if (usage.windowSource === undefined) {
|
|
100
|
+
// A child agent can be windowKnown through the pricing-table heuristic
|
|
101
|
+
// without naming a source; claiming 'the model table' there would be an
|
|
102
|
+
// evidence-free assertion in the one report that exists to be checked.
|
|
103
|
+
return `${usage.window} source unverified`;
|
|
93
104
|
}
|
|
94
105
|
return `${usage.window} from ${usage.windowSource === 'api' ? 'the model API' : 'the model table'}`;
|
|
95
106
|
}
|
|
@@ -28,8 +28,6 @@ export declare const COMPACTION_LIMITS: {
|
|
|
28
28
|
readonly archiveMaxFiles: 20;
|
|
29
29
|
readonly archiveMaxAgeMs: 604800000;
|
|
30
30
|
readonly archiveMaxBytes: 8000000;
|
|
31
|
-
readonly childKeepRecentTurns: 2;
|
|
32
|
-
readonly childMaxPerRun: 2;
|
|
33
31
|
readonly cardTextRows: 6;
|
|
34
32
|
readonly statusCompactCols: 100;
|
|
35
33
|
};
|
|
@@ -65,7 +65,12 @@ export interface CompactionDecision {
|
|
|
65
65
|
deltaTokens: number;
|
|
66
66
|
}
|
|
67
67
|
export interface Pressure {
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* `user` means the CLI's model-windows.json supplied the denominator - a
|
|
70
|
+
* hand-typed window for a model no table knows. It outranks `api` and
|
|
71
|
+
* `catalog` in `getModelInfoFor` precisely because the user asserted it.
|
|
72
|
+
*/
|
|
73
|
+
windowSource?: 'api' | 'catalog' | 'fallback' | 'user';
|
|
69
74
|
occupied: number;
|
|
70
75
|
contextWindow: number;
|
|
71
76
|
/** `occupied / contextWindow`, clamped to [0, 1]. */
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.aragon-agent/
|
|
13
13
|
* ├── config.json persisted config (0600 on POSIX)
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
|
+
* ├── model-windows.json per-model context windows, user-edited
|
|
15
16
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
17
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
18
|
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
@@ -72,6 +73,15 @@ export declare function getHomeRoot(): string;
|
|
|
72
73
|
export declare function getConfigPath(): string;
|
|
73
74
|
/** Written by `aragon config edit` before spawning the editor. */
|
|
74
75
|
export declare function getConfigBackupPath(): string;
|
|
76
|
+
/**
|
|
77
|
+
* `<home>/model-windows.json` — per-model context windows the user declares by
|
|
78
|
+
* hand for ids no table knows (`kimi-k3`, a gateway alias, a self-hosted model).
|
|
79
|
+
*
|
|
80
|
+
* READ-ONLY from the application's side: nothing ever writes or creates it, so
|
|
81
|
+
* a user's file cannot be clobbered by an upgrade and its absence is not an
|
|
82
|
+
* error. `config/model-windows.ts` owns the format and the lookup.
|
|
83
|
+
*/
|
|
84
|
+
export declare function getModelWindowsPath(): string;
|
|
75
85
|
/**
|
|
76
86
|
* `<home>/prompt-history.jsonl` — the prompts the user submitted, one JSON
|
|
77
87
|
* object per line. Deliberately NOT under `logs/`: that directory is governed
|
package/dist/config/app-paths.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.aragon-agent/
|
|
13
13
|
* ├── config.json persisted config (0600 on POSIX)
|
|
14
14
|
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
|
+
* ├── model-windows.json per-model context windows, user-edited
|
|
15
16
|
* ├── prompt-history.jsonl submitted prompts, append-only (0600 on POSIX)
|
|
16
17
|
* ├── state.json UI bookkeeping scalars (0600 on POSIX)
|
|
17
18
|
* ├── update-state.json auto-update coordination (0600 on POSIX)
|
|
@@ -129,6 +130,17 @@ export function getConfigPath() {
|
|
|
129
130
|
export function getConfigBackupPath() {
|
|
130
131
|
return join(resolution.root, 'config.json.bak');
|
|
131
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* `<home>/model-windows.json` — per-model context windows the user declares by
|
|
135
|
+
* hand for ids no table knows (`kimi-k3`, a gateway alias, a self-hosted model).
|
|
136
|
+
*
|
|
137
|
+
* READ-ONLY from the application's side: nothing ever writes or creates it, so
|
|
138
|
+
* a user's file cannot be clobbered by an upgrade and its absence is not an
|
|
139
|
+
* error. `config/model-windows.ts` owns the format and the lookup.
|
|
140
|
+
*/
|
|
141
|
+
export function getModelWindowsPath() {
|
|
142
|
+
return join(resolution.root, 'model-windows.json');
|
|
143
|
+
}
|
|
132
144
|
/**
|
|
133
145
|
* `<home>/prompt-history.jsonl` — the prompts the user submitted, one JSON
|
|
134
146
|
* object per line. Deliberately NOT under `logs/`: that directory is governed
|
|
@@ -42,7 +42,7 @@ export declare const RETRY_CONFIG_SET_KEYS: readonly ["retry.enabled", "retry.ma
|
|
|
42
42
|
* would silently never take effect, and `config set` would still print
|
|
43
43
|
* `Set fast.model = haiku`.
|
|
44
44
|
*/
|
|
45
|
-
export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provider", "fast.model", "fast.baseUrl", "fast.thinkingLevel", "fast.
|
|
45
|
+
export declare const FAST_CONFIG_SET_KEYS: readonly ["fast.enabled", "fast.provider", "fast.model", "fast.baseUrl", "fast.thinkingLevel", "fast.review", "fast.reviewEveryTurns", "fast.reviewContextTurns", "fast.reviewMaxChars", "fast.reviewMaxPerSession"];
|
|
46
46
|
/**
|
|
47
47
|
* Config keys under `update.` that `aragon config set` accepts (cli-auto-update
|
|
48
48
|
* §4.1).
|
|
@@ -14,7 +14,7 @@ import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, getPromptH
|
|
|
14
14
|
import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
|
|
15
15
|
import { loadPersistedConfig, updatePersistedConfig } from './store.js';
|
|
16
16
|
import { normalizeModelProfiles, validateModelProfiles } from './model-profiles.js';
|
|
17
|
-
import { clampFastConfig, clampRetryConfig, clampCompactionConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, } from './schema.js';
|
|
17
|
+
import { clampFastConfig, clampRetryConfig, clampCompactionConfig, clampTeamConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, } from './schema.js';
|
|
18
18
|
import { clampLogLevel } from '../logging/levels.js';
|
|
19
19
|
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
20
20
|
export const LOG_CONFIG_SET_KEYS = [
|
|
@@ -81,7 +81,6 @@ export const FAST_CONFIG_SET_KEYS = [
|
|
|
81
81
|
'fast.model',
|
|
82
82
|
'fast.baseUrl',
|
|
83
83
|
'fast.thinkingLevel',
|
|
84
|
-
'fast.delegate',
|
|
85
84
|
'fast.review',
|
|
86
85
|
'fast.reviewEveryTurns',
|
|
87
86
|
'fast.reviewContextTurns',
|
|
@@ -177,25 +176,29 @@ export function applyLogConfigSet(key, value) {
|
|
|
177
176
|
*/
|
|
178
177
|
export function applyTeamConfigSet(key, value) {
|
|
179
178
|
const patch = (team) => ({ team });
|
|
179
|
+
// EVERY NUMERIC KEY ROUTES THROUGH `clampTeamConfig`, not through
|
|
180
|
+
// `coercePositiveInt`, for the reason `applyRetryConfigSet` records one block
|
|
181
|
+
// below: `coercePositiveInt` returns its fallback for `n <= 0`, so
|
|
182
|
+
// `config set team.subagentTimeoutMs 0` would write 300000 (the old default)
|
|
183
|
+
// instead of the documented NO-LIMIT value, silently. The caller echoes the
|
|
184
|
+
// STORED value, which is what makes an out-of-range input visible.
|
|
185
|
+
const clampOne = (field) => {
|
|
186
|
+
const merged = clampTeamConfig({ ...DEFAULT_TEAM_CONFIG, [field]: value });
|
|
187
|
+
return { [field]: merged[field] };
|
|
188
|
+
};
|
|
180
189
|
switch (key) {
|
|
181
190
|
case 'team.enabled':
|
|
182
191
|
return patch({ enabled: isTrue(value) });
|
|
183
192
|
case 'team.maxSubagents':
|
|
184
|
-
return patch(
|
|
193
|
+
return patch(clampOne('maxSubagents'));
|
|
185
194
|
case 'team.maxConcurrent':
|
|
186
|
-
return patch(
|
|
195
|
+
return patch(clampOne('maxConcurrent'));
|
|
187
196
|
case 'team.subagentTimeoutMs':
|
|
188
|
-
return patch(
|
|
189
|
-
subagentTimeoutMs: coercePositiveInt(value, DEFAULT_TEAM_CONFIG.subagentTimeoutMs),
|
|
190
|
-
});
|
|
197
|
+
return patch(clampOne('subagentTimeoutMs'));
|
|
191
198
|
case 'team.dispatchTimeoutMs':
|
|
192
|
-
return patch(
|
|
193
|
-
dispatchTimeoutMs: coercePositiveInt(value, DEFAULT_TEAM_CONFIG.dispatchTimeoutMs),
|
|
194
|
-
});
|
|
199
|
+
return patch(clampOne('dispatchTimeoutMs'));
|
|
195
200
|
case 'team.maxTurnsPerSubagent':
|
|
196
|
-
return patch(
|
|
197
|
-
maxTurnsPerSubagent: coercePositiveInt(value, DEFAULT_TEAM_CONFIG.maxTurnsPerSubagent),
|
|
198
|
-
});
|
|
201
|
+
return patch(clampOne('maxTurnsPerSubagent'));
|
|
199
202
|
default:
|
|
200
203
|
return null;
|
|
201
204
|
}
|
|
@@ -273,8 +276,6 @@ export function applyFastConfigSet(key, value) {
|
|
|
273
276
|
return patch(clampOne('baseUrl'));
|
|
274
277
|
case 'fast.thinkingLevel':
|
|
275
278
|
return patch(clampOne('thinkingLevel'));
|
|
276
|
-
case 'fast.delegate':
|
|
277
|
-
return patch({ delegate: isTrue(value) });
|
|
278
279
|
case 'fast.review':
|
|
279
280
|
return patch({ review: isTrue(value) });
|
|
280
281
|
case 'fast.reviewEveryTurns':
|
package/dist/config/env.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_KEYBOARD_ENHANCEMENT,
|
|
11
|
+
* ARAGON_PLAN, ARAGON_TEAM,
|
|
11
12
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
13
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
14
|
* ARAGON_LOG_LEVEL,
|
package/dist/config/env.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
8
|
* ARAGON_SHOW_THINKING,
|
|
9
9
|
* ARAGON_MAX_TOKENS (a number, or `auto` / `0` for the per-model ceiling),
|
|
10
|
-
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE,
|
|
10
|
+
* ARAGON_THEME, ARAGON_MOUSE, ARAGON_PASTE, ARAGON_KEYBOARD_ENHANCEMENT,
|
|
11
|
+
* ARAGON_PLAN, ARAGON_TEAM,
|
|
11
12
|
* ARAGON_TEAM_MAX, ARAGON_RETRY, ARAGON_RETRY_MAX, ARAGON_FAST,
|
|
12
13
|
* ARAGON_FAST_PROVIDER, ARAGON_FAST_MODEL, ARAGON_FAST_BASE_URL,
|
|
13
14
|
* ARAGON_LOG_LEVEL,
|
|
@@ -151,6 +152,17 @@ export function readEnvConfig() {
|
|
|
151
152
|
if (paste !== undefined && paste.length > 0) {
|
|
152
153
|
partial.paste = paste === '1' || paste === 'true' || paste === 'on' || paste === 'yes';
|
|
153
154
|
}
|
|
155
|
+
// ARAGON_KEYBOARD_ENHANCEMENT=0 stops the Shift+Enter mode push and its
|
|
156
|
+
// translation -- the one-env revert to pre-enhancement input handling.
|
|
157
|
+
// Parsed by the POSITIVE list, exactly as its three neighbours above are.
|
|
158
|
+
const keyboardEnhancement = process.env.ARAGON_KEYBOARD_ENHANCEMENT?.trim().toLowerCase();
|
|
159
|
+
if (keyboardEnhancement !== undefined && keyboardEnhancement.length > 0) {
|
|
160
|
+
partial.keyboardEnhancement =
|
|
161
|
+
keyboardEnhancement === '1' ||
|
|
162
|
+
keyboardEnhancement === 'true' ||
|
|
163
|
+
keyboardEnhancement === 'on' ||
|
|
164
|
+
keyboardEnhancement === 'yes';
|
|
165
|
+
}
|
|
154
166
|
// ARAGON_SCROLL_RESUME_MS=<n> — idle delay before a paused viewport returns to
|
|
155
167
|
// the newest line; `0` disables auto-resume. THE ONLY CHANNEL BESIDES THE
|
|
156
168
|
// CONFIG FILE, because §6.1 gives this key no CLI flag on purpose. `load.ts`
|