@aragon-agent/cli 0.5.6 → 0.5.9

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +1429 -1105
  3. package/dist/agent/coalesce.d.ts +13 -0
  4. package/dist/agent/coalesce.js +50 -11
  5. package/dist/agent/controller.d.ts +141 -2
  6. package/dist/agent/controller.js +396 -4
  7. package/dist/agent/entry-limits.d.ts +65 -0
  8. package/dist/agent/entry-limits.js +133 -0
  9. package/dist/agent/headless.d.ts +8 -0
  10. package/dist/agent/headless.js +40 -0
  11. package/dist/agent/reducer.d.ts +253 -3
  12. package/dist/agent/reducer.js +553 -28
  13. package/dist/agent/retry-view.d.ts +68 -0
  14. package/dist/agent/retry-view.js +95 -0
  15. package/dist/agent/system-prompt.d.ts +11 -0
  16. package/dist/agent/system-prompt.js +2 -0
  17. package/dist/cli.js +122 -4
  18. package/dist/commands/builtins.d.ts +1 -1
  19. package/dist/commands/builtins.js +313 -2
  20. package/dist/commands/perf.d.ts +45 -0
  21. package/dist/commands/perf.js +78 -0
  22. package/dist/config/cli-commands.d.ts +53 -2
  23. package/dist/config/cli-commands.js +136 -3
  24. package/dist/config/env.d.ts +6 -1
  25. package/dist/config/env.js +131 -1
  26. package/dist/config/load.d.ts +63 -0
  27. package/dist/config/load.js +125 -2
  28. package/dist/config/schema.d.ts +273 -10
  29. package/dist/config/schema.js +250 -7
  30. package/dist/config/store.js +20 -1
  31. package/dist/fast/critique.d.ts +43 -0
  32. package/dist/fast/critique.js +62 -0
  33. package/dist/fast/digest.d.ts +42 -0
  34. package/dist/fast/digest.js +85 -0
  35. package/dist/fast/frames.d.ts +99 -0
  36. package/dist/fast/frames.js +180 -0
  37. package/dist/fast/limits.d.ts +98 -0
  38. package/dist/fast/limits.js +98 -0
  39. package/dist/fast/prompt.d.ts +72 -0
  40. package/dist/fast/prompt.js +94 -0
  41. package/dist/fast/resolve.d.ts +43 -0
  42. package/dist/fast/resolve.js +105 -0
  43. package/dist/fast/review-call.d.ts +99 -0
  44. package/dist/fast/review-call.js +130 -0
  45. package/dist/fast/reviewer.d.ts +248 -0
  46. package/dist/fast/reviewer.js +664 -0
  47. package/dist/fast/types.d.ts +128 -0
  48. package/dist/fast/types.js +14 -0
  49. package/dist/fast/wiring.d.ts +170 -0
  50. package/dist/fast/wiring.js +316 -0
  51. package/dist/logging/install.d.ts +15 -1
  52. package/dist/logging/install.js +37 -4
  53. package/dist/logging/logger.d.ts +1 -1
  54. package/dist/session/persist.js +57 -1
  55. package/dist/team/normalize.d.ts +26 -1
  56. package/dist/team/normalize.js +23 -3
  57. package/dist/team/prompt.d.ts +11 -0
  58. package/dist/team/prompt.js +10 -0
  59. package/dist/team/report.d.ts +18 -0
  60. package/dist/team/report.js +27 -1
  61. package/dist/team/runtime.d.ts +13 -2
  62. package/dist/team/runtime.js +33 -6
  63. package/dist/team/subagent.d.ts +19 -1
  64. package/dist/team/subagent.js +72 -10
  65. package/dist/team/task-tool.d.ts +26 -0
  66. package/dist/team/task-tool.js +48 -4
  67. package/dist/team/types.d.ts +80 -1
  68. package/dist/tools/bash-tool.js +19 -3
  69. package/dist/tools/diff.d.ts +25 -5
  70. package/dist/tools/diff.js +72 -41
  71. package/dist/tools/file-change-store.d.ts +34 -0
  72. package/dist/tools/file-change-store.js +57 -0
  73. package/dist/tools/fs-tools.d.ts +26 -0
  74. package/dist/tools/fs-tools.js +62 -5
  75. package/dist/tools/index.d.ts +31 -0
  76. package/dist/tools/index.js +7 -1
  77. package/dist/tools/patch.d.ts +115 -0
  78. package/dist/tools/patch.js +453 -0
  79. package/dist/tools/terminal-output.d.ts +107 -0
  80. package/dist/tools/terminal-output.js +198 -0
  81. package/dist/tools/tool-output-store.d.ts +68 -0
  82. package/dist/tools/tool-output-store.js +102 -0
  83. package/dist/ui/ActivityLine.d.ts +48 -0
  84. package/dist/ui/ActivityLine.js +22 -0
  85. package/dist/ui/App.d.ts +1 -4
  86. package/dist/ui/App.js +357 -44
  87. package/dist/ui/BottomStatusRow.d.ts +43 -0
  88. package/dist/ui/BottomStatusRow.js +14 -0
  89. package/dist/ui/Composer.d.ts +1 -6
  90. package/dist/ui/Composer.js +2 -2
  91. package/dist/ui/Markdown.d.ts +7 -1
  92. package/dist/ui/Markdown.js +15 -48
  93. package/dist/ui/PromptInput.d.ts +1 -15
  94. package/dist/ui/PromptInput.js +1 -38
  95. package/dist/ui/StatusBar.d.ts +50 -0
  96. package/dist/ui/StatusBar.js +7 -3
  97. package/dist/ui/TeamPanel.d.ts +5 -4
  98. package/dist/ui/TeamPanel.js +1 -1
  99. package/dist/ui/TodoPanel.d.ts +5 -4
  100. package/dist/ui/Transcript.d.ts +80 -5
  101. package/dist/ui/Transcript.js +179 -11
  102. package/dist/ui/activity-phrases.d.ts +33 -0
  103. package/dist/ui/activity-phrases.js +50 -0
  104. package/dist/ui/entries/AssistantEntry.d.ts +38 -1
  105. package/dist/ui/entries/AssistantEntry.js +34 -3
  106. package/dist/ui/entries/DiffView.d.ts +67 -0
  107. package/dist/ui/entries/DiffView.js +103 -0
  108. package/dist/ui/entries/FastCard.d.ts +53 -0
  109. package/dist/ui/entries/FastCard.js +87 -0
  110. package/dist/ui/entries/RetryCard.d.ts +56 -0
  111. package/dist/ui/entries/RetryCard.js +60 -0
  112. package/dist/ui/entries/TeamCard.d.ts +9 -1
  113. package/dist/ui/entries/TeamCard.js +28 -2
  114. package/dist/ui/entries/TodoCard.d.ts +9 -1
  115. package/dist/ui/entries/TodoCard.js +23 -1
  116. package/dist/ui/entries/ToolCard.d.ts +50 -1
  117. package/dist/ui/entries/ToolCard.js +104 -7
  118. package/dist/ui/entries/ToolPreview.d.ts +2 -2
  119. package/dist/ui/entries/UserEntry.d.ts +9 -1
  120. package/dist/ui/entries/UserEntry.js +17 -1
  121. package/dist/ui/exit-snapshot.d.ts +7 -0
  122. package/dist/ui/glyphs.d.ts +9 -0
  123. package/dist/ui/glyphs.js +2 -0
  124. package/dist/ui/layout/AppShell.d.ts +13 -22
  125. package/dist/ui/layout/AppShell.js +3 -38
  126. package/dist/ui/layout/MeasuredEntry.d.ts +21 -0
  127. package/dist/ui/layout/MeasuredEntry.js +30 -0
  128. package/dist/ui/layout/ScrollViewport.js +8 -2
  129. package/dist/ui/layout/viewport-geometry.d.ts +28 -0
  130. package/dist/ui/layout/viewport-geometry.js +27 -0
  131. package/dist/ui/layout/virtual-window.d.ts +100 -0
  132. package/dist/ui/layout/virtual-window.js +308 -0
  133. package/dist/ui/live-clamp.d.ts +42 -0
  134. package/dist/ui/live-clamp.js +74 -0
  135. package/dist/ui/markdown-blocks.d.ts +25 -1
  136. package/dist/ui/markdown-blocks.js +43 -0
  137. package/dist/ui/overlays/HelpOverlay.js +16 -2
  138. package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
  139. package/dist/ui/overlays/SettingsScreen.js +147 -1
  140. package/dist/ui/render-cache.d.ts +81 -0
  141. package/dist/ui/render-cache.js +195 -0
  142. package/dist/ui/render-governor.d.ts +54 -0
  143. package/dist/ui/render-governor.js +78 -0
  144. package/dist/ui/transcript-text.d.ts +11 -0
  145. package/dist/ui/transcript-text.js +32 -0
  146. package/dist/ui/use-height-store.d.ts +31 -0
  147. package/dist/ui/use-height-store.js +76 -0
  148. package/dist/ui/use-render-governor.d.ts +39 -0
  149. package/dist/ui/use-render-governor.js +57 -0
  150. package/dist/ui/use-startup-notices.d.ts +9 -1
  151. package/dist/ui/use-startup-notices.js +35 -1
  152. package/dist/ui/use-wheel-routing.d.ts +16 -32
  153. package/dist/ui/use-wheel-routing.js +21 -38
  154. package/package.json +2 -2
  155. package/dist/ui/layout/regions.d.ts +0 -55
  156. package/dist/ui/layout/regions.js +0 -52
@@ -5,7 +5,7 @@
5
5
  * See spec §3.5 (resolution pipeline), §6.1 (config file), and §3.8 (timeout
6
6
  * invariant).
7
7
  */
8
- import { type ThinkingLevel } from '@aragon-agent/core';
8
+ import { type RetryPolicy, type ThinkingLevel } from '@aragon-agent/core';
9
9
  import { type LogLevelName } from '../logging/levels.js';
10
10
  import type { FollowThroughMode } from '../todo/types.js';
11
11
  /**
@@ -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
- * How many trailing entries the full-screen viewport renders (invariant I-3).
68
- * Yoga lays out every child even when `overflow: hidden` clips it, so the cost
69
- * of a long session is linear in the entry count regardless of visibility. The
70
- * reducer and `/save` keep the full history — this is a render-layer bound only.
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 = 300;
77
+ export declare const DEFAULT_TRANSCRIPT_WINDOW = 1000;
73
78
  export declare const MIN_TRANSCRIPT_WINDOW = 50;
74
- export declare const MAX_TRANSCRIPT_WINDOW = 2000;
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
  /**
@@ -274,12 +299,171 @@ export declare const DEFAULT_TODO_CONFIG: TodoConfig;
274
299
  * setting won't stick".
275
300
  */
276
301
  export declare function clampTodoConfig(raw: unknown): TodoConfig;
302
+ export interface RetryConfig {
303
+ /**
304
+ * The user's switch. KEPT SEPARATE FROM `maxRetries: 0` on purpose: `enabled`
305
+ * survives round-tripping through the settings screen, while `maxRetries: 0` is
306
+ * what `toRetryPolicy` produces from it. Folding them would make
307
+ * `/retry off; /retry on` forget the user's chosen count.
308
+ */
309
+ enabled: boolean;
310
+ /** Retries AFTER the initial attempt. `0` is the kill switch, not a typo. */
311
+ maxRetries: number;
312
+ initialDelayMs: number;
313
+ maxDelayMs: number;
314
+ /** Growth factor. THE ONLY FLOAT KEY in this section. */
315
+ multiplier: number;
316
+ jitter: boolean;
317
+ respectRetryAfter: boolean;
318
+ /** Wall-clock budget from the first failure of one request. */
319
+ maxElapsedMs: number;
320
+ /** Allow a restart after content has already streamed to the transcript. */
321
+ onPartialStream: boolean;
322
+ }
323
+ export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
324
+ /**
325
+ * `hardMaxRetries` re-exported rather than re-spelled, for the reason
326
+ * `HARD_MAX_SUBAGENTS` records: the ceiling is enforced in TWO places — here on
327
+ * the config value, and again by `normalizePolicy` inside the engine — and two
328
+ * copies of the same 20 is exactly how a hand-edited `config.json` ends up able
329
+ * to raise a ceiling the mechanism fixed.
330
+ */
331
+ export declare const HARD_MAX_RETRIES: 20;
332
+ /**
333
+ * THE single gate for every retry-config read AND write, mirroring
334
+ * `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
335
+ * for the reason those record: hardening only the read path leaves a bad value on
336
+ * disk that reverts to the default on every launch, which presents to the user as
337
+ * "my setting won't stick".
338
+ *
339
+ * Three invariants live inside the gate:
340
+ *
341
+ * - `maxDelayMs = max(maxDelayMs, initialDelayMs)` — a ceiling under the floor
342
+ * makes the ladder a flat line and the countdown a lie.
343
+ * - `multiplier` goes through `clampNumber`, not `clampInt`: `2.5` is a
344
+ * legitimate factor and `coercePositiveInt` would floor it to 2.
345
+ * - `maxRetries` goes through `clampIntAllowingZero`. THIS IS THE ONE THAT SHIPS
346
+ * BROKEN WITHOUT IT (R-16): `clampInt` returns the fallback for `n <= 0`, so
347
+ * `clampRetryConfig({ maxRetries: 0 })` would yield 10 — the maximum instead
348
+ * of the minimum, silently, for the key that gates the whole feature.
349
+ *
350
+ * `maxElapsedMs` intentionally keeps plain `clampInt`: its floor is 10 000, so it
351
+ * has no `0` to express.
352
+ */
353
+ export declare function clampRetryConfig(raw: unknown): RetryConfig;
354
+ /**
355
+ * `RetryConfig` (user intent) -> `RetryPolicy` (engine policy), or `null` for
356
+ * "the user said no".
357
+ *
358
+ * `null` rather than a policy with `maxRetries: 0` because `ProviderRegistry`
359
+ * short-circuits on a null policy and returns the adapter's iterator DIRECTLY —
360
+ * an opted-out session is then allocation-identical to a pre-feature build.
361
+ *
362
+ * TOTAL, AND IT ACCEPTS A MISSING SECTION. `loadConfig` always supplies one, but
363
+ * a `CliConfig` built by hand — every controller test in this package does exactly
364
+ * that, and so would an embedder — legitimately omits it, and a `TypeError` from a
365
+ * pure translation function is the wrong way to find out. Routing through
366
+ * `clampRetryConfig` also means an out-of-range hand-built value can never reach
367
+ * the engine, which is the same "one gate, both directions" rule every other
368
+ * section in this file follows.
369
+ */
370
+ export declare function toRetryPolicy(cfg: RetryConfig | undefined): RetryPolicy | null;
371
+ export interface FastConfig {
372
+ /**
373
+ * Resolve the tier at all. Defaults to FALSE (R-d).
374
+ *
375
+ * OFF IS BYTE-IDENTICAL (I-2): with this false the system prompt, `task`'s
376
+ * schema, the tool array, the transcript, the status bar and the request
377
+ * payloads are exactly what a pre-feature build produces.
378
+ */
379
+ enabled: boolean;
380
+ /** `''` INHERITS the main provider — what makes `fast.model` alone a complete
381
+ * configuration for the common "same vendor, cheaper model" case. */
382
+ provider: string;
383
+ /** The fast model id. Empty means NOT CONFIGURED, never "same as main": that
384
+ * distinction is what keeps R-e (opt in to fast == main) from being
385
+ * indistinguishable from a typo (D-4). */
386
+ model: string;
387
+ /** `''` inherits only when the provider matches — a base URL belongs to one
388
+ * API, so a fast tier on a different vendor must not borrow it. */
389
+ baseUrl: string;
390
+ /** Applied to fast-tier children and to the review call. Defaults to `'off'`:
391
+ * a "fast" model asked to think for 32 768 tokens is not fast (D-13). */
392
+ thinkingLevel: ThinkingLevel;
393
+ /** Allow `model: "fast"` on `task`. */
394
+ delegate: boolean;
395
+ /** Run the periodic asynchronous review. */
396
+ review: boolean;
397
+ /** Completed turns between reviews. */
398
+ reviewEveryTurns: number;
399
+ /** Turn frames included in one digest. */
400
+ reviewContextTurns: number;
401
+ /** Ceiling on an injected critique, in characters. */
402
+ reviewMaxChars: number;
403
+ /**
404
+ * Reviews STARTED per session, across all runs (fast-model-tier-hardening
405
+ * §4.1 / W1).
406
+ *
407
+ * The one bound on spend the user never asked for. `reviewEveryTurns`
408
+ * controls the PACE; this controls the TOTAL — and the two are different
409
+ * questions, because a session is not one run. `reviewsThisRun` is reset by
410
+ * every user message, so thirty messages buys thirty runs' worth of reviews
411
+ * with nothing counting across them.
412
+ *
413
+ * DENOMINATED IN REVIEWS, NOT CURRENCY (D-H1). The fast tier is by
414
+ * construction the likeliest place in the product to name a model the static
415
+ * cost table has never seen, and `buildRuntimeModel` prices exactly those at
416
+ * zero (C-11) — so a dollar budget would silently never trigger for the
417
+ * configuration that most needs it. A count is exact for every model, and
418
+ * because per-review cost is already bounded on both sides (a 4 000-char
419
+ * digest in, 512 tokens out), `count x bound` is an honest ceiling.
420
+ */
421
+ reviewMaxPerSession: number;
422
+ }
423
+ export declare const DEFAULT_FAST_CONFIG: FastConfig;
424
+ /**
425
+ * THE single gate for every fast-config read AND write, mirroring
426
+ * `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
427
+ * for the reason those four record: hardening only the read path leaves a bad
428
+ * value on disk that reverts to the default on every launch, which presents to
429
+ * the user as "my setting won't stick".
430
+ *
431
+ * `provider` is CLAMPED TO `''` rather than rejected when it names something
432
+ * with no adapter. `''` means "inherit the main provider", which is the safe
433
+ * reading of a typo — and `resolveFastTier` reports `no_adapter` for the case a
434
+ * user really did configure a dead vendor as the MAIN one.
435
+ */
436
+ export declare function clampFastConfig(raw: unknown): FastConfig;
277
437
  export interface PersistedConfig {
278
438
  version: number;
279
439
  provider: string;
280
440
  model: string;
281
441
  baseUrl: string | null;
282
442
  thinkingLevel: ThinkingLevel;
443
+ /**
444
+ * Whether the terminal DRAWS the reasoning that came back
445
+ * (agent-activity-presentation §4.1).
446
+ *
447
+ * ADJACENT TO `thinkingLevel` AND DELIBERATELY NAMED DIFFERENTLY: that key is
448
+ * the effort the provider is asked to SPEND, this one is whether the transcript
449
+ * shows it. They are one word apart in the config file and must never be
450
+ * confused, which is why the flag is `--show-thinking` rather than the shorter
451
+ * `--thinking` the existing key already resembles.
452
+ */
453
+ showThinking: boolean;
454
+ /**
455
+ * Whether a RUNNING tool card draws a bounded tail of its live output
456
+ * (agent-activity-presentation-live §4.1).
457
+ *
458
+ * DEFAULT `true`, unlike `showThinking` next door (D-28). That key hides
459
+ * information by default because reasoning is noise most of the time; this one
460
+ * ADDS the information the requirement asks for, and its cost is bounded by
461
+ * construction -- eight rows per call, sixteen calls, whatever the command
462
+ * emits. It exists as a KILL SWITCH, not as an opt-in: "a build's output
463
+ * redraws the transcript" is exactly the kind of change a user on a slow SSH
464
+ * link may want off.
465
+ */
466
+ liveToolOutput: boolean;
283
467
  /**
284
468
  * Output token cap.
285
469
  *
@@ -300,8 +484,14 @@ export interface PersistedConfig {
300
484
  fullscreen: boolean;
301
485
  /** Replay a plain-text session summary after leaving the alternate screen. */
302
486
  exitTranscript: boolean;
303
- /** Trailing entries the full-screen viewport renders (I-3). */
487
+ /** Entries reachable by scrolling; older ones show the "collapsed" line (I-3). */
304
488
  transcriptWindow: number;
489
+ /** Entries kept in `ViewState`; older ones are dropped and counted (L1). */
490
+ transcriptRetain: number;
491
+ /** Adaptive coalescing under load (L4). */
492
+ renderGovernor: boolean;
493
+ /** Ceiling of the governor ladder in ms; `33` flattens it (L4). */
494
+ maxRenderIntervalMs: number;
305
495
  confirmTools: boolean;
306
496
  toolTimeoutMs: number;
307
497
  idleTimeoutMs: number;
@@ -322,7 +512,8 @@ export interface PersistedConfig {
322
512
  hints: boolean;
323
513
  /**
324
514
  * Enable SGR mouse reporting in full-screen mode so the wheel scrolls the
325
- * region under the pointer (mouse-wheel-region-routing §5.7).
515
+ * transcript, whatever row the pointer is on (wheel-scrolls-transcript-only
516
+ * §4.1).
326
517
  *
327
518
  * A PLAIN BOOLEAN, not a `fullscreen`-style tri-state: there are no
328
519
  * heuristics for an explicit `true` to override (D-10).
@@ -342,6 +533,10 @@ export interface PersistedConfig {
342
533
  team: TeamConfig;
343
534
  /** Todo planning. The FIFTH nested object; scalars only (see TodoConfig). */
344
535
  todo: TodoConfig;
536
+ /** API retry. The SIXTH nested object; scalars only (see RetryConfig). */
537
+ retry: RetryConfig;
538
+ /** Fast model tier. The SEVENTH nested object; scalars only (see FastConfig). */
539
+ fast: FastConfig;
345
540
  }
346
541
  export declare const DEFAULT_CONFIG: PersistedConfig;
347
542
  /**
@@ -373,6 +568,28 @@ export interface CliConfig {
373
568
  model: string;
374
569
  baseUrl?: string;
375
570
  thinkingLevel: ThinkingLevel;
571
+ /**
572
+ * Seeds `ViewState.thinkingVisible` on the first frame (§4.1 / P1-2).
573
+ *
574
+ * IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`. `App` reads `cfg.*`,
575
+ * where `cfg` is a `CliConfig`; a key added to the persisted shape alone would
576
+ * round-trip through `config set`, appear in `config list` — and never reach
577
+ * `initialViewState`. `--show-thinking` and `ARAGON_SHOW_THINKING` would parse
578
+ * and be discarded. Nothing would fail; the setting would simply do nothing.
579
+ */
580
+ showThinking: boolean;
581
+ /**
582
+ * Whether `AgentController` allocates the live-output store and binds
583
+ * `recordOutput` (agent-activity-presentation-live §4.1).
584
+ *
585
+ * IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason its
586
+ * neighbour records one comment up: the controller reads `config.*`, where
587
+ * `config` is a `CliConfig`, so a key added to the persisted shape alone would
588
+ * round-trip through `config set`, appear in `config list` -- and never reach
589
+ * the constructor. `--live-tool-output` and `ARAGON_LIVE_TOOL_OUTPUT` would
590
+ * parse and be discarded, with nothing failing.
591
+ */
592
+ liveToolOutput: boolean;
376
593
  /** The resolved cap, or `undefined` for AUTO (per-model ceiling, ≤ 64000). */
377
594
  maxTokens?: number;
378
595
  theme: ThemeName;
@@ -390,8 +607,30 @@ export interface CliConfig {
390
607
  fullscreen?: boolean;
391
608
  /** Replay a plain-text summary on exit (full-screen only — see §4.4). */
392
609
  exitTranscript: boolean;
393
- /** Trailing entries the full-screen viewport renders (I-3). */
610
+ /** Entries reachable by scrolling; older ones show the "collapsed" line (I-3). */
394
611
  transcriptWindow: number;
612
+ /**
613
+ * Entries kept in `ViewState` (L1).
614
+ *
615
+ * Resolved to be `>= transcriptWindow`: retaining fewer entries than the
616
+ * window can scroll to would silently make part of the window unreachable.
617
+ */
618
+ transcriptRetain: number;
619
+ /**
620
+ * What the user actually asked `transcriptRetain` to be, present ONLY when
621
+ * that value had to be raised (§5.1).
622
+ *
623
+ * A setting ignored without a word is the one failure mode this feature is
624
+ * not allowed to add: the eco chip, `/perf` and the dropped-entry line all
625
+ * exist so that nothing it does happens unannounced, and a config key
626
+ * overwritten in silence would be the exception. `undefined` means nothing
627
+ * was overridden, which is every default session.
628
+ */
629
+ transcriptRetainRequested?: number;
630
+ /** Adaptive coalescing under load (L4). */
631
+ renderGovernor: boolean;
632
+ /** Ceiling of the governor ladder in ms; `33` flattens it (L4). */
633
+ maxRenderIntervalMs: number;
395
634
  confirmTools: boolean;
396
635
  toolTimeoutMs: number;
397
636
  idleTimeoutMs: number;
@@ -446,6 +685,30 @@ export interface CliConfig {
446
685
  * must call `setTodoConfig` as well as persisting (P1-2).
447
686
  */
448
687
  todo: TodoConfig;
688
+ /**
689
+ * Resolved API-retry settings (llm-api-retry-backoff §6.2).
690
+ *
691
+ * Unlike `team.enabled` / `todo.enabled` this decides NOTHING at construction
692
+ * time: the policy lives on the `ProviderRegistry` and `setRetryConfig()`
693
+ * replaces it live, so `/retry off` takes effect in the running session. That is
694
+ * why the controller has no `retryRegistered` twin of the two flags above.
695
+ */
696
+ retry: RetryConfig;
697
+ /**
698
+ * Resolved fast-tier settings (fast-model-tier §4.2).
699
+ *
700
+ * `enabled` here decides `fastRegistered` — whether `task`'s schema carries a
701
+ * `model` property at all — which happens once at construction and can never
702
+ * be undone (C-2). The live on/off switch `/fast` flips is a separate flag on
703
+ * `FastWiring`.
704
+ *
705
+ * THE OTHER NINE KEYS ARE READ LIVE, and that is not the same statement.
706
+ * `provider` / `model` / `baseUrl` feed `resolveFastTier`, which is re-run on
707
+ * every config mutation (§3.2 rule 8 / RV-3) because `''` INHERITS from
708
+ * `config.provider` / `config.baseUrl` and those are rewritten by `/model`,
709
+ * `/provider`, `/reload` and the settings screen.
710
+ */
711
+ fast: FastConfig;
449
712
  /**
450
713
  * Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
451
714
  *
@@ -6,7 +6,7 @@
6
6
  * invariant).
7
7
  */
8
8
  import { isAbsolute } from 'node:path';
9
- import { ABSOLUTE_MAX_OUTPUT_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, MIN_MAX_OUTPUT_TOKENS, } from '@aragon-agent/core';
9
+ import { ABSOLUTE_MAX_OUTPUT_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_RETRY_POLICY, MIN_MAX_OUTPUT_TOKENS, RETRY_LIMITS, } from '@aragon-agent/core';
10
10
  import { clampLogLevel } from '../logging/levels.js';
11
11
  import { TEAM_LIMITS } from '../team/limits.js';
12
12
  // ---------------------------------------------------------------------------
@@ -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
- * How many trailing entries the full-screen viewport renders (invariant I-3).
102
- * Yoga lays out every child even when `overflow: hidden` clips it, so the cost
103
- * of a long session is linear in the entry count regardless of visibility. The
104
- * reducer and `/save` keep the full history — this is a render-layer bound only.
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 = 300;
111
+ export const DEFAULT_TRANSCRIPT_WINDOW = 1000;
107
112
  export const MIN_TRANSCRIPT_WINDOW = 50;
108
- export const MAX_TRANSCRIPT_WINDOW = 2000;
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
  //
@@ -179,6 +213,46 @@ function clampInt(v, fallback, range) {
179
213
  const n = coercePositiveInt(v, fallback);
180
214
  return Math.min(range.max, Math.max(range.min, n));
181
215
  }
216
+ /**
217
+ * `clampInt` for a key whose range legitimately includes ZERO.
218
+ *
219
+ * `clampInt` delegates to `coercePositiveInt`, which returns the FALLBACK for
220
+ * `n <= 0` — so a range whose floor is `0` cannot express `0` through it, and the
221
+ * value silently becomes the default. That trap is invisible at the call site and
222
+ * is what `retry.maxRetries` needs this helper for: `0` is that key's documented
223
+ * floor and its kill switch, and four documented controls send it
224
+ * (`/retry max 0`, `--retry-max 0`, `ARAGON_RETRY_MAX=0`,
225
+ * `aragon config set retry.maxRetries 0`). Through `clampInt` all four would set
226
+ * the MAXIMUM instead of the minimum, with no error anywhere.
227
+ *
228
+ * Own coercion: `Number.parseInt`, reject only non-finite and negative, then
229
+ * floor and clamp.
230
+ */
231
+ function clampIntAllowingZero(v, fallback, range) {
232
+ if (v === null || v === undefined || v === '')
233
+ return clampToRange(fallback, range);
234
+ const n = typeof v === 'number' ? v : Number.parseInt(String(v), 10);
235
+ if (!Number.isFinite(n) || n < 0)
236
+ return clampToRange(fallback, range);
237
+ return clampToRange(Math.floor(n), range);
238
+ }
239
+ /**
240
+ * `clampInt` for a key that is legitimately a FLOAT.
241
+ *
242
+ * `coercePositiveInt` floors, and `2.5` is a legitimate backoff factor — the same
243
+ * coercion trap `clampIntAllowingZero` above closes for `0`, one type over.
244
+ */
245
+ function clampNumber(v, fallback, range) {
246
+ if (v === null || v === undefined || v === '')
247
+ return clampToRange(fallback, range);
248
+ const n = typeof v === 'number' ? v : Number.parseFloat(String(v));
249
+ if (!Number.isFinite(n))
250
+ return clampToRange(fallback, range);
251
+ return clampToRange(n, range);
252
+ }
253
+ function clampToRange(n, range) {
254
+ return Math.min(range.max, Math.max(range.min, n));
255
+ }
182
256
  function stringArray(v, fallback) {
183
257
  if (!Array.isArray(v))
184
258
  return fallback;
@@ -346,12 +420,169 @@ export function clampTodoConfig(raw) {
346
420
  followThrough: followMode(src.followThrough, DEFAULT_TODO_CONFIG.followThrough),
347
421
  };
348
422
  }
423
+ export const DEFAULT_RETRY_CONFIG = {
424
+ enabled: true,
425
+ maxRetries: DEFAULT_RETRY_POLICY.maxRetries,
426
+ initialDelayMs: DEFAULT_RETRY_POLICY.initialDelayMs,
427
+ maxDelayMs: DEFAULT_RETRY_POLICY.maxDelayMs,
428
+ multiplier: DEFAULT_RETRY_POLICY.multiplier,
429
+ jitter: DEFAULT_RETRY_POLICY.jitter,
430
+ respectRetryAfter: DEFAULT_RETRY_POLICY.respectRetryAfter,
431
+ maxElapsedMs: DEFAULT_RETRY_POLICY.maxElapsedMs,
432
+ onPartialStream: DEFAULT_RETRY_POLICY.onPartialStream,
433
+ };
434
+ /**
435
+ * `hardMaxRetries` re-exported rather than re-spelled, for the reason
436
+ * `HARD_MAX_SUBAGENTS` records: the ceiling is enforced in TWO places — here on
437
+ * the config value, and again by `normalizePolicy` inside the engine — and two
438
+ * copies of the same 20 is exactly how a hand-edited `config.json` ends up able
439
+ * to raise a ceiling the mechanism fixed.
440
+ */
441
+ export const HARD_MAX_RETRIES = RETRY_LIMITS.hardMaxRetries;
442
+ /** FLOOR 0 — and `clampIntAllowingZero` is what makes that reachable. */
443
+ const RETRY_MAX_RANGE = { min: 0, max: HARD_MAX_RETRIES };
444
+ const RETRY_INITIAL_DELAY_RANGE = { min: 100, max: 30_000 };
445
+ const RETRY_MAX_DELAY_RANGE = { min: 1000, max: RETRY_LIMITS.absoluteMaxDelayMs };
446
+ const RETRY_MULTIPLIER_RANGE = { min: 1, max: 5 };
447
+ const RETRY_ELAPSED_RANGE = { min: 10_000, max: 1_800_000 };
448
+ /**
449
+ * THE single gate for every retry-config read AND write, mirroring
450
+ * `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
451
+ * for the reason those record: hardening only the read path leaves a bad value on
452
+ * disk that reverts to the default on every launch, which presents to the user as
453
+ * "my setting won't stick".
454
+ *
455
+ * Three invariants live inside the gate:
456
+ *
457
+ * - `maxDelayMs = max(maxDelayMs, initialDelayMs)` — a ceiling under the floor
458
+ * makes the ladder a flat line and the countdown a lie.
459
+ * - `multiplier` goes through `clampNumber`, not `clampInt`: `2.5` is a
460
+ * legitimate factor and `coercePositiveInt` would floor it to 2.
461
+ * - `maxRetries` goes through `clampIntAllowingZero`. THIS IS THE ONE THAT SHIPS
462
+ * BROKEN WITHOUT IT (R-16): `clampInt` returns the fallback for `n <= 0`, so
463
+ * `clampRetryConfig({ maxRetries: 0 })` would yield 10 — the maximum instead
464
+ * of the minimum, silently, for the key that gates the whole feature.
465
+ *
466
+ * `maxElapsedMs` intentionally keeps plain `clampInt`: its floor is 10 000, so it
467
+ * has no `0` to express.
468
+ */
469
+ export function clampRetryConfig(raw) {
470
+ const src = (raw && typeof raw === 'object' ? raw : {});
471
+ const initialDelayMs = clampInt(src.initialDelayMs, DEFAULT_RETRY_CONFIG.initialDelayMs, RETRY_INITIAL_DELAY_RANGE);
472
+ const maxDelayMs = Math.max(initialDelayMs, clampInt(src.maxDelayMs, DEFAULT_RETRY_CONFIG.maxDelayMs, RETRY_MAX_DELAY_RANGE));
473
+ return {
474
+ enabled: bool(src.enabled, DEFAULT_RETRY_CONFIG.enabled),
475
+ maxRetries: clampIntAllowingZero(src.maxRetries, DEFAULT_RETRY_CONFIG.maxRetries, RETRY_MAX_RANGE),
476
+ initialDelayMs,
477
+ maxDelayMs,
478
+ multiplier: clampNumber(src.multiplier, DEFAULT_RETRY_CONFIG.multiplier, RETRY_MULTIPLIER_RANGE),
479
+ jitter: bool(src.jitter, DEFAULT_RETRY_CONFIG.jitter),
480
+ respectRetryAfter: bool(src.respectRetryAfter, DEFAULT_RETRY_CONFIG.respectRetryAfter),
481
+ maxElapsedMs: clampInt(src.maxElapsedMs, DEFAULT_RETRY_CONFIG.maxElapsedMs, RETRY_ELAPSED_RANGE),
482
+ onPartialStream: bool(src.onPartialStream, DEFAULT_RETRY_CONFIG.onPartialStream),
483
+ };
484
+ }
485
+ /**
486
+ * `RetryConfig` (user intent) -> `RetryPolicy` (engine policy), or `null` for
487
+ * "the user said no".
488
+ *
489
+ * `null` rather than a policy with `maxRetries: 0` because `ProviderRegistry`
490
+ * short-circuits on a null policy and returns the adapter's iterator DIRECTLY —
491
+ * an opted-out session is then allocation-identical to a pre-feature build.
492
+ *
493
+ * TOTAL, AND IT ACCEPTS A MISSING SECTION. `loadConfig` always supplies one, but
494
+ * a `CliConfig` built by hand — every controller test in this package does exactly
495
+ * that, and so would an embedder — legitimately omits it, and a `TypeError` from a
496
+ * pure translation function is the wrong way to find out. Routing through
497
+ * `clampRetryConfig` also means an out-of-range hand-built value can never reach
498
+ * the engine, which is the same "one gate, both directions" rule every other
499
+ * section in this file follows.
500
+ */
501
+ export function toRetryPolicy(cfg) {
502
+ const safe = clampRetryConfig(cfg);
503
+ if (!safe.enabled || safe.maxRetries <= 0)
504
+ return null;
505
+ return {
506
+ maxRetries: safe.maxRetries,
507
+ initialDelayMs: safe.initialDelayMs,
508
+ maxDelayMs: safe.maxDelayMs,
509
+ multiplier: safe.multiplier,
510
+ jitter: safe.jitter,
511
+ respectRetryAfter: safe.respectRetryAfter,
512
+ maxElapsedMs: safe.maxElapsedMs,
513
+ onPartialStream: safe.onPartialStream,
514
+ };
515
+ }
516
+ export const DEFAULT_FAST_CONFIG = {
517
+ // R-d, and the one default in this file that is a REQUIREMENT rather than a
518
+ // judgement: a feature that silently spends money on a second provider is not
519
+ // a default (D-3).
520
+ enabled: false,
521
+ provider: '',
522
+ model: '',
523
+ baseUrl: '',
524
+ thinkingLevel: 'off',
525
+ delegate: true,
526
+ review: true,
527
+ reviewEveryTurns: 5,
528
+ reviewContextTurns: 3,
529
+ reviewMaxChars: 280,
530
+ // 40 is roughly 200 reviewed turns at the default cadence: comfortably past a
531
+ // full working session, short of a runaway (D-H2).
532
+ reviewMaxPerSession: 40,
533
+ };
534
+ const REVIEW_EVERY_RANGE = { min: 1, max: 50 };
535
+ const REVIEW_CONTEXT_RANGE = { min: 1, max: 10 };
536
+ const REVIEW_CHARS_RANGE = { min: 80, max: 600 };
537
+ /** No "unlimited" sentinel: a magic `0` reads as "disabled" to the next person
538
+ * who meets it, and 500 is already past any session a human sits through. */
539
+ const REVIEW_SESSION_RANGE = { min: 1, max: 500 };
540
+ /** Trim a string field, falling back when the value is not a string at all. */
541
+ function text(v, fallback) {
542
+ return typeof v === 'string' ? v.trim() : fallback;
543
+ }
544
+ /**
545
+ * THE single gate for every fast-config read AND write, mirroring
546
+ * `clampSkillsConfig` / `clampLogConfig` / `clampTeamConfig` / `clampTodoConfig`
547
+ * for the reason those four record: hardening only the read path leaves a bad
548
+ * value on disk that reverts to the default on every launch, which presents to
549
+ * the user as "my setting won't stick".
550
+ *
551
+ * `provider` is CLAMPED TO `''` rather than rejected when it names something
552
+ * with no adapter. `''` means "inherit the main provider", which is the safe
553
+ * reading of a typo — and `resolveFastTier` reports `no_adapter` for the case a
554
+ * user really did configure a dead vendor as the MAIN one.
555
+ */
556
+ export function clampFastConfig(raw) {
557
+ const src = (raw && typeof raw === 'object' ? raw : {});
558
+ const provider = text(src.provider, DEFAULT_FAST_CONFIG.provider);
559
+ return {
560
+ enabled: bool(src.enabled, DEFAULT_FAST_CONFIG.enabled),
561
+ provider: provider.length === 0 || isAdapterProvider(provider) ? provider : '',
562
+ model: text(src.model, DEFAULT_FAST_CONFIG.model),
563
+ baseUrl: text(src.baseUrl, DEFAULT_FAST_CONFIG.baseUrl),
564
+ thinkingLevel: clampThinkingLevel(src.thinkingLevel, DEFAULT_FAST_CONFIG.thinkingLevel),
565
+ delegate: bool(src.delegate, DEFAULT_FAST_CONFIG.delegate),
566
+ review: bool(src.review, DEFAULT_FAST_CONFIG.review),
567
+ reviewEveryTurns: clampInt(src.reviewEveryTurns, DEFAULT_FAST_CONFIG.reviewEveryTurns, REVIEW_EVERY_RANGE),
568
+ reviewContextTurns: clampInt(src.reviewContextTurns, DEFAULT_FAST_CONFIG.reviewContextTurns, REVIEW_CONTEXT_RANGE),
569
+ reviewMaxChars: clampInt(src.reviewMaxChars, DEFAULT_FAST_CONFIG.reviewMaxChars, REVIEW_CHARS_RANGE),
570
+ reviewMaxPerSession: clampInt(src.reviewMaxPerSession, DEFAULT_FAST_CONFIG.reviewMaxPerSession, REVIEW_SESSION_RANGE),
571
+ };
572
+ }
349
573
  export const DEFAULT_CONFIG = {
350
574
  version: CONFIG_VERSION,
351
575
  provider: 'anthropic',
352
576
  model: DEFAULT_MODEL,
353
577
  baseUrl: null,
354
578
  thinkingLevel: 'off',
579
+ // Off by default — the product default this whole round exists to establish
580
+ // (D-1). One config key restores the old behaviour permanently.
581
+ showThinking: false,
582
+ // ON by default (D-28), and the OPPOSITE of the key above it on purpose: this
583
+ // one shows work that is otherwise invisible for the whole duration of the
584
+ // most expensive thing the agent does.
585
+ liveToolOutput: true,
355
586
  // The engine owns this number; spelling it here again is how the CLI and the
356
587
  // provider adapters end up disagreeing about what "the default" is.
357
588
  maxTokens: DEFAULT_MAX_OUTPUT_TOKENS,
@@ -360,6 +591,9 @@ export const DEFAULT_CONFIG = {
360
591
  fullscreen: true,
361
592
  exitTranscript: true,
362
593
  transcriptWindow: DEFAULT_TRANSCRIPT_WINDOW,
594
+ transcriptRetain: DEFAULT_TRANSCRIPT_RETAIN,
595
+ renderGovernor: DEFAULT_RENDER_GOVERNOR,
596
+ maxRenderIntervalMs: DEFAULT_MAX_RENDER_INTERVAL_MS,
363
597
  confirmTools: false,
364
598
  toolTimeoutMs: DEFAULT_TOOL_TIMEOUT_MS,
365
599
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
@@ -378,6 +612,15 @@ export const DEFAULT_CONFIG = {
378
612
  // `todo` key at all, so this line is as required as the two merges in
379
613
  // `store.ts` (P0-1).
380
614
  todo: DEFAULT_TODO_CONFIG,
615
+ // Same requirement one section later: without this line a user with no `retry`
616
+ // key resolves `config.retry` to `undefined` and `toRetryPolicy` throws.
617
+ retry: DEFAULT_RETRY_CONFIG,
618
+ // And one section later again: the load path's first spread is what supplies
619
+ // `fast` to a user whose `config.json` predates this feature, which is every
620
+ // existing user. Without it `config.fast.enabled` reads `undefined`, which is
621
+ // falsy and so LOOKS correct — right up until `/fast on` writes a section that
622
+ // has no other keys in it (P0-1's shape, one feature later).
623
+ fast: DEFAULT_FAST_CONFIG,
381
624
  };
382
625
  /**
383
626
  * Runtime state that used to be stored in `config.json` by mistake