@aragon-agent/cli 0.5.9 → 0.5.11
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 +1157 -1026
- package/README.md +123 -0
- package/dist/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +376 -2
- package/dist/commands/builtins.js +118 -0
- package/dist/commands/perf.d.ts +5 -1
- package/dist/commands/perf.js +43 -1
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +27 -1
- package/dist/config/cli-commands.js +51 -2
- package/dist/config/env.d.ts +11 -1
- package/dist/config/env.js +58 -1
- package/dist/config/load.d.ts +30 -0
- package/dist/config/load.js +56 -1
- package/dist/config/schema.d.ts +91 -0
- package/dist/config/schema.js +107 -0
- package/dist/config/store.js +13 -1
- package/dist/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +88 -2
- package/dist/ui/BottomStatusRow.d.ts +25 -3
- package/dist/ui/BottomStatusRow.js +10 -7
- package/dist/ui/PromptInput.d.ts +10 -0
- package/dist/ui/PromptInput.js +41 -48
- package/dist/ui/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- package/dist/ui/editor-reducer.d.ts +62 -0
- package/dist/ui/editor-reducer.js +90 -0
- package/dist/ui/frame-differ.d.ts +95 -0
- package/dist/ui/frame-differ.js +230 -0
- package/dist/ui/stdout-frame-writer.d.ts +41 -0
- package/dist/ui/stdout-frame-writer.js +102 -0
- package/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ import { formatMigrationNotice, migrateLegacyState } from './config/migrate-lega
|
|
|
23
23
|
import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
|
|
24
24
|
import { migrateStateOutOfConfig } from './config/migrate-state-out-of-config.js';
|
|
25
25
|
import { runHistoryCommand } from './config/history-commands.js';
|
|
26
|
-
import { FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
26
|
+
import { FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
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';
|
|
@@ -34,6 +34,9 @@ import { runSkillsCommand } from './skills/cli-commands.js';
|
|
|
34
34
|
import { runHeadless } from './agent/headless.js';
|
|
35
35
|
import { App } from './ui/App.js';
|
|
36
36
|
import { decideRenderMode } from './ui/layout/frame.js';
|
|
37
|
+
import { createFrameDiffer } from './ui/frame-differ.js';
|
|
38
|
+
import { wrapStdoutForFrames } from './ui/stdout-frame-writer.js';
|
|
39
|
+
import { setFrameStatsProvider } from './commands/perf.js';
|
|
37
40
|
import { tryCreateMouseFilter } from './input/stdin-mouse-filter.js';
|
|
38
41
|
import { enterAltScreen, writeExitTranscript } from './ui/screen.js';
|
|
39
42
|
import { readExitSnapshot } from './ui/exit-snapshot.js';
|
|
@@ -41,6 +44,15 @@ import { renderTranscriptText } from './ui/transcript-text.js';
|
|
|
41
44
|
import { detectCapabilities } from './ui/capabilities.js';
|
|
42
45
|
import { pickGlyphs } from './ui/glyphs.js';
|
|
43
46
|
import { DENY_ALL_HUMAN_INPUT } from './tools/human-input.js';
|
|
47
|
+
// A VALUE import, and the one deliberate exception to the paragraph above
|
|
48
|
+
// (cli-auto-update-hardening P2-7). `boot/guard.js` statically pulls
|
|
49
|
+
// `update/state.js`, so `aragon -p` now EVALUATES that module - it is pure at
|
|
50
|
+
// module scope, calls nothing, and `update-wiring.test.ts` walks the transitive
|
|
51
|
+
// graph against an allow-list so it cannot quietly grow. The alternative, a
|
|
52
|
+
// dynamic import for a two-line disarm on the hottest path in the file, buys
|
|
53
|
+
// nothing: the guard has already loaded and read the same module milliseconds
|
|
54
|
+
// earlier, in the launcher.
|
|
55
|
+
import { markBootHealthy } from './boot/guard.js';
|
|
44
56
|
// ---------------------------------------------------------------------------
|
|
45
57
|
// Version (read from the shipped package.json next to dist/)
|
|
46
58
|
// ---------------------------------------------------------------------------
|
|
@@ -131,6 +143,25 @@ function toFlags(opts) {
|
|
|
131
143
|
transcriptRetain: opts.transcriptRetain,
|
|
132
144
|
renderGovernor: opts.renderGovernor,
|
|
133
145
|
maxRenderInterval: opts.maxRenderInterval,
|
|
146
|
+
// Site three of three for `--diff-render` / `--sync-output` — EIGHT
|
|
147
|
+
// features, one trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4,
|
|
148
|
+
// retry, fast, update, now frame diffing). A flag needs the `.option()`
|
|
149
|
+
// declaration below, a field on `CliFlags`, AND a line here; miss this one
|
|
150
|
+
// and the flag is inert with no compile error and no runtime error. It bites
|
|
151
|
+
// hardest for `--no-diff-render`: that is the documented escape hatch for a
|
|
152
|
+
// user whose terminal renders the diffed frame wrongly, and a user who has
|
|
153
|
+
// been told to pass it and sees no change has no next move.
|
|
154
|
+
diffRender: opts.diffRender,
|
|
155
|
+
syncOutput: opts.syncOutput,
|
|
156
|
+
// Site three of three for `--update` / `--no-update` — SEVEN features, one
|
|
157
|
+
// trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, retry, fast, now
|
|
158
|
+
// update). A flag needs the `.option()` declaration below, a field on
|
|
159
|
+
// `CliFlags`, AND a line here; miss this one and the flag is inert with no
|
|
160
|
+
// compile error and no runtime error. It bites hardest here of all: this is
|
|
161
|
+
// the KILL SWITCH for a feature that installs software, so a missing line
|
|
162
|
+
// means `--no-update` reports nothing, does nothing, and leaves the updater
|
|
163
|
+
// running.
|
|
164
|
+
update: opts.update,
|
|
134
165
|
};
|
|
135
166
|
}
|
|
136
167
|
// ---------------------------------------------------------------------------
|
|
@@ -206,6 +237,20 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
206
237
|
});
|
|
207
238
|
return { controller, confirmBridge, humanInputBridge };
|
|
208
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Raised once per session, on the 0->1 edge of the writer's `fallbacks` counter
|
|
242
|
+
* (tui-input-flicker-fix §5.6 / AC-15).
|
|
243
|
+
*
|
|
244
|
+
* The characteristic failure of frame diffing is SILENT — absolute addressing
|
|
245
|
+
* loses its origin and the user sees a subtly wrong screen — so leaving its only
|
|
246
|
+
* signal behind a slash command the affected user has no reason to run is not an
|
|
247
|
+
* acceptable end state for a fix whose whole premise is "the user should not have
|
|
248
|
+
* to notice the renderer". `warn`, not `error`: a single fallback is self-healing
|
|
249
|
+
* by construction (I-6), and it is worth reporting only because the PATTERN is
|
|
250
|
+
* not.
|
|
251
|
+
*/
|
|
252
|
+
const FRAME_FALLBACK_NOTICE = 'Frame diffing lost sync with the terminal and fell back to a full repaint. ' +
|
|
253
|
+
'If the display looks wrong, restart with `--no-diff-render`.';
|
|
209
254
|
/**
|
|
210
255
|
* The interactive TUI — and the ONLY place that may touch the screen.
|
|
211
256
|
*
|
|
@@ -254,6 +299,40 @@ function runInteractive(flags, extras = {}) {
|
|
|
254
299
|
? tryCreateMouseFilter(process.stdin, (reason) => logger.warn('cli', 'mouse_filter_failed', { reason }))
|
|
255
300
|
: null;
|
|
256
301
|
const disposeMouse = () => mouseFilter?.dispose();
|
|
302
|
+
// --- Frame differ (tui-input-flicker-fix §3.2 / §4.2). -------------------
|
|
303
|
+
//
|
|
304
|
+
// FULL-SCREEN ONLY, and that is invariant I-3 rather than caution: inline mode
|
|
305
|
+
// prints settled entries through Ink's `<Static>`, starts wherever the shell's
|
|
306
|
+
// cursor happened to be and scrolls, so row k of a frame is not absolute row k
|
|
307
|
+
// there and never will be. `--no-diff-render` skips construction entirely,
|
|
308
|
+
// which is what makes rung 2 of the fail-safe ladder byte-identical to a
|
|
309
|
+
// pre-fix build rather than merely equivalent.
|
|
310
|
+
//
|
|
311
|
+
// `rows` is a THUNK, not a snapshot: it is the only input to the I-9 geometry
|
|
312
|
+
// guard and it changes under the differ's feet on every resize.
|
|
313
|
+
const frameWriter = mode === 'fullscreen' && config.diffRender
|
|
314
|
+
? wrapStdoutForFrames(process.stdout, createFrameDiffer({
|
|
315
|
+
sync: config.syncOutput && process.env.TERM !== 'dumb',
|
|
316
|
+
rows: () => process.stdout.rows,
|
|
317
|
+
// The console bridge (`App.tsx`, full-screen only) turns this into
|
|
318
|
+
// `dispatch({type:'notice', level:'warn'})`, which is the only legal
|
|
319
|
+
// user-visible channel with the TUI mounted — a direct write would
|
|
320
|
+
// itself be the foreign write being reported. Latched on the 0->1
|
|
321
|
+
// edge inside the differ: a notice per fallback would be a repaint
|
|
322
|
+
// storm triggered by a repaint problem (§5.6).
|
|
323
|
+
onFirstFallback: () => console.warn(FRAME_FALLBACK_NOTICE),
|
|
324
|
+
}))
|
|
325
|
+
: null;
|
|
326
|
+
// `null` when no writer was built, so `/perf` can say "diff render off" rather
|
|
327
|
+
// than printing zeroes that read like a writer doing nothing (§5.4).
|
|
328
|
+
if (frameWriter)
|
|
329
|
+
setFrameStatsProvider(frameWriter.stats);
|
|
330
|
+
const disposeFrameWriter = () => {
|
|
331
|
+
if (!frameWriter)
|
|
332
|
+
return;
|
|
333
|
+
setFrameStatsProvider(null);
|
|
334
|
+
frameWriter.dispose();
|
|
335
|
+
};
|
|
257
336
|
const replayTranscript = () => {
|
|
258
337
|
if (replayed || mode !== 'fullscreen' || !config.exitTranscript)
|
|
259
338
|
return;
|
|
@@ -302,7 +381,26 @@ function runInteractive(flags, extras = {}) {
|
|
|
302
381
|
process.exit(128 + signo);
|
|
303
382
|
});
|
|
304
383
|
}
|
|
305
|
-
|
|
384
|
+
// --- Auto-update (cli-auto-update section 3.8). --------------------------
|
|
385
|
+
//
|
|
386
|
+
// THE ONLY CONSTRUCTION SITE. `runOneShot` / `runHeadless` never reach this
|
|
387
|
+
// function, so `aragon -p` executes zero update code and its stdout is
|
|
388
|
+
// byte-identical to a pre-feature build (AC-1); `aragon config <subcommand>`,
|
|
389
|
+
// `aragon logs`, `aragon skills` and `aragon --version` likewise. Bare
|
|
390
|
+
// `aragon config` DOES construct it, because it is a full interactive session
|
|
391
|
+
// with a settings overlay on top - which is correct, and is why AC-1's claim
|
|
392
|
+
// names the SUBCOMMAND form.
|
|
393
|
+
//
|
|
394
|
+
// `CI` is excluded on top of the two TTY tests because a CI runner with a pty
|
|
395
|
+
// allocated is otherwise a machine that silently mutates its own global
|
|
396
|
+
// toolchain mid-pipeline (D-11).
|
|
397
|
+
const updateBridge = { service: null, onAttach: null };
|
|
398
|
+
let updateService = null;
|
|
399
|
+
const updateEligible = !!process.stdout.isTTY &&
|
|
400
|
+
!!process.stdin.isTTY &&
|
|
401
|
+
!process.env.CI &&
|
|
402
|
+
config.update.mode !== 'off';
|
|
403
|
+
const instance = render(_jsx(App, { controller: controller, version: VERSION, mode: mode, confirmBridge: confirmBridge, humanInputBridge: humanInputBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay, mouseSource: mouseFilter?.source, updateBridge: updateEligible ? updateBridge : undefined }),
|
|
306
404
|
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
307
405
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
308
406
|
{
|
|
@@ -311,7 +409,50 @@ function runInteractive(flags, extras = {}) {
|
|
|
311
409
|
// I-5: with mouse support off the REAL stdin is handed over unwrapped, so
|
|
312
410
|
// every existing path is provably unchanged.
|
|
313
411
|
stdin: mouseFilter?.stdin ?? process.stdin,
|
|
412
|
+
// SPREAD, not `frameWriter?.stdout ?? process.stdout`. This call passed no
|
|
413
|
+
// `stdout` at all before, and Ink defaults it to `process.stdout`; omitting
|
|
414
|
+
// the key entirely when the writer is off keeps the options object the same
|
|
415
|
+
// SHAPE it has always had, which is what makes AC-5 / AC-6 ("byte-identical
|
|
416
|
+
// with --no-diff-render, inline mode and `aragon -p`") a statement about
|
|
417
|
+
// the code rather than about Ink's defaulting.
|
|
418
|
+
...(frameWriter ? { stdout: frameWriter.stdout } : {}),
|
|
314
419
|
});
|
|
420
|
+
// H1's primary disarm, and this is the earliest moment at which "this build
|
|
421
|
+
// STARTS" is proven: the module graph loaded, config resolved, Ink mounted
|
|
422
|
+
// (cli-auto-update-hardening section 5.1.4). It is what makes a long session
|
|
423
|
+
// that is later `SIGKILL`ed - terminal window closed, machine slept badly, OOM
|
|
424
|
+
// killer - still count as healthy.
|
|
425
|
+
//
|
|
426
|
+
// UNGATED BY `updateEligible` ON PURPOSE. The guard is armed only by an
|
|
427
|
+
// auto-install, which `mode: 'off'` already prevents, so on a machine that
|
|
428
|
+
// never armed it this is one read and one string compare. Gating it would mean
|
|
429
|
+
// a user who switches `mode` to `off` between the install and the next launch
|
|
430
|
+
// leaves the guard armed with nothing able to disarm it.
|
|
431
|
+
markBootHealthy(VERSION);
|
|
432
|
+
if (updateEligible) {
|
|
433
|
+
// DYNAMIC, and fire-and-forget AFTER `render()` (D-25). Dynamic because a
|
|
434
|
+
// static import would make AC-1 false for `aragon -p`, which shares this
|
|
435
|
+
// module and has no bundler to shake the subsystem out; after `render()`
|
|
436
|
+
// because the service must never be able to delay or fail the first frame.
|
|
437
|
+
// The `.catch` is not decoration: a subsystem that cannot even be imported
|
|
438
|
+
// must degrade to "no updater", never to a crashed TUI.
|
|
439
|
+
void import('./update/service.js')
|
|
440
|
+
.then(({ UpdateService }) => {
|
|
441
|
+
const service = new UpdateService({ config: config.update, currentVersion: VERSION });
|
|
442
|
+
updateService = service;
|
|
443
|
+
updateBridge.service = service;
|
|
444
|
+
// `App` may have mounted first and parked its listener here; it may also
|
|
445
|
+
// not have mounted yet, in which case its own effect reads
|
|
446
|
+
// `bridge.service` directly. Both orders happen (IF-4).
|
|
447
|
+
updateBridge.onAttach?.(service);
|
|
448
|
+
service.start();
|
|
449
|
+
})
|
|
450
|
+
.catch((err) => {
|
|
451
|
+
logger.warn('update', 'update_service_unavailable', {
|
|
452
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
}
|
|
315
456
|
// The return value MUST be captured — `waitUntilExit()` is the normal-exit
|
|
316
457
|
// restore path and it cannot be registered otherwise. `dispose()` runs on
|
|
317
458
|
// BOTH branches (I-7): a live `'data'` listener on stdin keeps the event loop
|
|
@@ -329,16 +470,33 @@ function runInteractive(flags, extras = {}) {
|
|
|
329
470
|
.then(() => {
|
|
330
471
|
screen?.restore();
|
|
331
472
|
disposeMouse();
|
|
473
|
+
// BOTH BRANCHES, the same discipline `controller.dispose()` and
|
|
474
|
+
// `updateService?.dispose()` already follow. It drops the `'resize'`
|
|
475
|
+
// listener of I-8 and detaches the `/perf` provider so a later `/perf` in a
|
|
476
|
+
// torn-down tree reports honestly instead of reading a dead closure.
|
|
477
|
+
disposeFrameWriter();
|
|
332
478
|
// Orphaned subagents survive `Ctrl+C` otherwise, and a live child holds
|
|
333
479
|
// the event loop open so `aragon` never returns to the shell (R-15). The
|
|
334
480
|
// App's unmount cleanup also calls this; `dispose()` is idempotent.
|
|
335
481
|
controller.dispose();
|
|
482
|
+
// BOTH BRANCHES, exactly as `controller.dispose()` is (section 3.6 / P2-6),
|
|
483
|
+
// and idempotent for the same reason. It clears the timers and drops the
|
|
484
|
+
// listeners; it NEVER signals the installer child (U-3), because killing a
|
|
485
|
+
// running `npm install -g` is the one action here that can leave the
|
|
486
|
+
// user's global installation broken - and it would be triggered by the
|
|
487
|
+
// most ordinary event there is, the user pressing Ctrl+C. The signal path
|
|
488
|
+
// above calls `process.exit()` and reaches neither branch, which is
|
|
489
|
+
// correct and is precisely WHY U-3 matters: the detached child survives
|
|
490
|
+
// that exit and completes.
|
|
491
|
+
updateService?.dispose();
|
|
336
492
|
replayTranscript();
|
|
337
493
|
})
|
|
338
494
|
.catch(() => {
|
|
339
495
|
screen?.restore();
|
|
340
496
|
disposeMouse();
|
|
497
|
+
disposeFrameWriter();
|
|
341
498
|
controller.dispose();
|
|
499
|
+
updateService?.dispose();
|
|
342
500
|
});
|
|
343
501
|
}
|
|
344
502
|
async function runOneShot(flags, prompt, quiet) {
|
|
@@ -432,6 +590,10 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
432
590
|
'transcriptRetain',
|
|
433
591
|
'renderGovernor',
|
|
434
592
|
'maxRenderIntervalMs',
|
|
593
|
+
// tui-input-flicker-fix §5.1. Listed here AND cased in the switch below, for
|
|
594
|
+
// the reason `transcriptRetain` records three lines up.
|
|
595
|
+
'diffRender',
|
|
596
|
+
'syncOutput',
|
|
435
597
|
// Without these two, `density` and `hints` would be settable only as one-shot
|
|
436
598
|
// flags and never persist -- inconsistent with every other config field.
|
|
437
599
|
'density',
|
|
@@ -471,6 +633,8 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
471
633
|
...RETRY_CONFIG_SET_KEYS,
|
|
472
634
|
// `fast.*` keys are handled by `applyFastConfigSet`, same reasoning again.
|
|
473
635
|
...FAST_CONFIG_SET_KEYS,
|
|
636
|
+
// `update.*` keys are handled by `applyUpdateConfigSet`, same reasoning again.
|
|
637
|
+
...UPDATE_CONFIG_SET_KEYS,
|
|
474
638
|
]);
|
|
475
639
|
/**
|
|
476
640
|
* `aragon config set maxTokens <n|auto|default>`.
|
|
@@ -546,6 +710,17 @@ function runConfigSet(key, value) {
|
|
|
546
710
|
process.stdout.write(`Set ${key} = ${String(merged.fast[field])}\n`);
|
|
547
711
|
return;
|
|
548
712
|
}
|
|
713
|
+
const updatePatch = applyUpdateConfigSet(key, value);
|
|
714
|
+
if (updatePatch) {
|
|
715
|
+
// Echoed from the STORED value, so `config set update.checkIntervalMs 5`
|
|
716
|
+
// prints the clamped 900000 rather than reporting a number that was never
|
|
717
|
+
// written (AC-20) — the rule `team.*`, `retry.*` and `fast.*` each state
|
|
718
|
+
// above.
|
|
719
|
+
const merged = updatePersistedConfig(updatePatch);
|
|
720
|
+
const field = key.slice('update.'.length);
|
|
721
|
+
process.stdout.write(`Set ${key} = ${String(merged.update[field])}\n`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
549
724
|
const patch = {};
|
|
550
725
|
switch (key) {
|
|
551
726
|
case 'provider':
|
|
@@ -590,6 +765,12 @@ function runConfigSet(key, value) {
|
|
|
590
765
|
case 'maxRenderIntervalMs':
|
|
591
766
|
patch.maxRenderIntervalMs = clampMaxRenderInterval(value, DEFAULT_MAX_RENDER_INTERVAL_MS);
|
|
592
767
|
break;
|
|
768
|
+
case 'diffRender':
|
|
769
|
+
patch.diffRender = value === 'true' || value === '1';
|
|
770
|
+
break;
|
|
771
|
+
case 'syncOutput':
|
|
772
|
+
patch.syncOutput = value === 'true' || value === '1';
|
|
773
|
+
break;
|
|
593
774
|
case 'density':
|
|
594
775
|
patch.density = clampDensity(value, 'comfortable');
|
|
595
776
|
break;
|
|
@@ -662,6 +843,166 @@ function runConfigSet(key, value) {
|
|
|
662
843
|
updatePersistedConfig(patch);
|
|
663
844
|
process.stdout.write(`Set ${key} = ${value}\n`);
|
|
664
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Exit codes (cli-auto-update section 4.3): `0` = up to date, or installed;
|
|
848
|
+
* `1` = the check or the install failed; `2` = an install source we will not
|
|
849
|
+
* install for (the advice line is printed); `4` = `--rollback` had nothing to
|
|
850
|
+
* roll back to (cli-auto-update-hardening section 6.2 / P2-8).
|
|
851
|
+
*
|
|
852
|
+
* A SOURCE WE DECLINE IS `2` AND NOT `1`, and the difference matters to the only
|
|
853
|
+
* consumer that reads an exit code - a script. `1` says "try again later"; `2`
|
|
854
|
+
* says "this machine will never auto-update, run the command we printed".
|
|
855
|
+
*/
|
|
856
|
+
function updateExitCode(snapshot) {
|
|
857
|
+
// REASON BEFORE PHASE, and the order is the whole change (P1-1). The three
|
|
858
|
+
// classified failures all arrive with `phase: 'failed'` - they come from
|
|
859
|
+
// `recordFailure` - so leaving the phase test first, as it was, means these
|
|
860
|
+
// two lines can never be reached and the mapping is a silent no-op.
|
|
861
|
+
//
|
|
862
|
+
// THIS IS A BEHAVIOUR CHANGE FOR SCRIPTS AND IS CALLED ONE (R-23). A
|
|
863
|
+
// `blocked-by-os` install used to exit `1` ("try again later") and now exits
|
|
864
|
+
// `2` ("this machine will never auto-update, run the command we printed").
|
|
865
|
+
// That is the honest answer - on Windows the same `npm i -g` from the same
|
|
866
|
+
// shell fails the same way until the user closes something - and it is
|
|
867
|
+
// recorded in the CHANGELOG under the version that ships it.
|
|
868
|
+
if (snapshot.reason === 'blocked-by-os' || snapshot.reason === 'no-space')
|
|
869
|
+
return 2;
|
|
870
|
+
if (snapshot.reason === 'source-ineligible' || snapshot.reason === 'not-writable')
|
|
871
|
+
return 2;
|
|
872
|
+
if (snapshot.phase === 'failed')
|
|
873
|
+
return 1;
|
|
874
|
+
if (snapshot.reason === 'install-ineffective')
|
|
875
|
+
return 1;
|
|
876
|
+
return 0;
|
|
877
|
+
}
|
|
878
|
+
/** Human output: three lines at most (section 4.3). */
|
|
879
|
+
function formatUpdateResult(snapshot) {
|
|
880
|
+
const lines = [];
|
|
881
|
+
switch (snapshot.phase) {
|
|
882
|
+
case 'ready':
|
|
883
|
+
lines.push(`Installed ${snapshot.latestVersion ?? ''}. Restart aragon to apply.`);
|
|
884
|
+
break;
|
|
885
|
+
case 'available':
|
|
886
|
+
lines.push(snapshot.reason === 'install-ineffective'
|
|
887
|
+
? `${snapshot.latestVersion ?? 'The new version'} installed somewhere else - ` +
|
|
888
|
+
'check your npm prefix.'
|
|
889
|
+
: `${snapshot.latestVersion ?? 'A new version'} is available ` +
|
|
890
|
+
`(running ${snapshot.currentVersion}).`);
|
|
891
|
+
if (snapshot.advice)
|
|
892
|
+
lines.push(` ${snapshot.advice}`);
|
|
893
|
+
break;
|
|
894
|
+
case 'failed':
|
|
895
|
+
lines.push('Update failed. See the log for details.');
|
|
896
|
+
if (snapshot.advice)
|
|
897
|
+
lines.push(` ${snapshot.advice}`);
|
|
898
|
+
break;
|
|
899
|
+
default:
|
|
900
|
+
lines.push(`aragon ${snapshot.currentVersion} is up to date.`);
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
if (snapshot.reason && snapshot.phase !== 'ready')
|
|
904
|
+
lines.push(` (${snapshot.reason})`);
|
|
905
|
+
return lines.slice(0, 3).join('\n');
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* `aragon update --rollback` - the MANUAL form of the boot guard's rollback
|
|
909
|
+
* (cli-auto-update-hardening section 6.2).
|
|
910
|
+
*
|
|
911
|
+
* It exists because a release can be BAD WITHOUT FAILING TO START, which is the
|
|
912
|
+
* case non-goal 2 declines to detect automatically precisely because a human
|
|
913
|
+
* detects it in one second. It shares `performRollback` verbatim, so the latch,
|
|
914
|
+
* the re-classification, the lock and the post-install verification are the same
|
|
915
|
+
* code the automatic path uses.
|
|
916
|
+
*
|
|
917
|
+
* Exit `4` for "nothing to roll back to" is a fourth code on a documented
|
|
918
|
+
* contract, so the contract's own comment above moves with it (P2-8). A rollback
|
|
919
|
+
* that RAN and failed is an ordinary failed install and keeps exit `1`.
|
|
920
|
+
*/
|
|
921
|
+
async function runRollbackCommand() {
|
|
922
|
+
const { readUpdateState, updateUpdateState } = await import('./update/state.js');
|
|
923
|
+
const good = readUpdateState().lastGoodVersion;
|
|
924
|
+
if (!good) {
|
|
925
|
+
process.stderr.write('Nothing to roll back to: no previous version was recorded by an auto-update.\n');
|
|
926
|
+
return 4;
|
|
927
|
+
}
|
|
928
|
+
if (good === VERSION) {
|
|
929
|
+
process.stdout.write(`aragon ${VERSION} is already the last known-good version.\n`);
|
|
930
|
+
return 0;
|
|
931
|
+
}
|
|
932
|
+
const { performRollback } = await import('./boot/rollback.js');
|
|
933
|
+
const result = await performRollback(VERSION, good);
|
|
934
|
+
if (!result.ok) {
|
|
935
|
+
process.stderr.write(`Rollback to ${good} did not complete (${result.failure}).\n`);
|
|
936
|
+
return 1;
|
|
937
|
+
}
|
|
938
|
+
// THE NOTICE IS FOR A DOWNGRADE THE USER DID NOT ASK FOR, AND THIS ONE WAS
|
|
939
|
+
// TYPED. `performRollback` sets `rolledBackFrom` because it cannot tell which
|
|
940
|
+
// caller it has, and the field has exactly one rendering: `rolled back to <to>
|
|
941
|
+
// after <bad> FAILED TO START` (section 8). On this path that sentence is
|
|
942
|
+
// false - the version started fine, the user simply did not want it - and a
|
|
943
|
+
// bottom row asserting a crash that never happened is worse than no row.
|
|
944
|
+
//
|
|
945
|
+
// Clearing it here rather than teaching `performRollback` a mode keeps the
|
|
946
|
+
// "verbatim shared mechanism" of section 6.2 intact and adds no fifth state
|
|
947
|
+
// field under C-17. D-38's purpose survives: the notice exists so a SILENT
|
|
948
|
+
// downgrade cannot go unnoticed, and the line below is this one's notice.
|
|
949
|
+
// `/update status` still shows `lastgood`, and `update_rolled_back` is in the
|
|
950
|
+
// log either way.
|
|
951
|
+
updateUpdateState({ rolledBackFrom: '' });
|
|
952
|
+
process.stdout.write(`Rolled back to ${good}. Restart aragon to apply.\n`);
|
|
953
|
+
return 0;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* `aragon update` - a deliberate, foreground check.
|
|
957
|
+
*
|
|
958
|
+
* PRINTS TO STDOUT, unlike every other update surface in this package: here the
|
|
959
|
+
* update IS the command's output, so C-10's "the TUI is the only legal visible
|
|
960
|
+
* channel" does not apply - there is no TUI.
|
|
961
|
+
*
|
|
962
|
+
* Reached through the SAME dynamic `import()` the interactive gate uses, so
|
|
963
|
+
* `dist/cli.js` still carries no static specifier under `update/` (AC-1).
|
|
964
|
+
*/
|
|
965
|
+
async function runUpdateCommand(flags, opts) {
|
|
966
|
+
const config = loadConfig(flags);
|
|
967
|
+
getLogger().reconfigure(config.log);
|
|
968
|
+
const { UpdateService } = await import('./update/service.js');
|
|
969
|
+
const service = new UpdateService({
|
|
970
|
+
// `--check` is exactly `mode: 'notify'` FOR THIS RUN, and expressing it that
|
|
971
|
+
// way rather than with a second flag through the service means there is one
|
|
972
|
+
// code path deciding whether to install, not two.
|
|
973
|
+
config: opts.check ? { ...config.update, mode: 'notify' } : config.update,
|
|
974
|
+
currentVersion: VERSION,
|
|
975
|
+
});
|
|
976
|
+
try {
|
|
977
|
+
if (opts.rollback)
|
|
978
|
+
return await runRollbackCommand();
|
|
979
|
+
const target = opts.to?.trim();
|
|
980
|
+
if (target) {
|
|
981
|
+
// REPORTED, not silently ignored. `installNow` also refuses a malformed
|
|
982
|
+
// version (it is the one entry point whose target comes straight from
|
|
983
|
+
// argv), but there the refusal is a no-op snapshot that would exit 0 and
|
|
984
|
+
// tell the user they are up to date — which is a lie about the command
|
|
985
|
+
// they actually typed.
|
|
986
|
+
const { STRICT_VERSION_RE } = await import('./update/semver.js');
|
|
987
|
+
if (!STRICT_VERSION_RE.test(target)) {
|
|
988
|
+
process.stderr.write(`Not a version: "${target}". Use e.g. --to 0.6.0\n`);
|
|
989
|
+
return 1;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const snapshot = target
|
|
993
|
+
? await service.installNow(target)
|
|
994
|
+
: await service.checkNow({ force: true });
|
|
995
|
+
process.stdout.write(opts.json ? `${JSON.stringify(snapshot)}\n` : `${formatUpdateResult(snapshot)}\n`);
|
|
996
|
+
return updateExitCode(snapshot);
|
|
997
|
+
}
|
|
998
|
+
catch (err) {
|
|
999
|
+
process.stderr.write(`update failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1000
|
+
return 1;
|
|
1001
|
+
}
|
|
1002
|
+
finally {
|
|
1003
|
+
service.dispose();
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
665
1006
|
// ---------------------------------------------------------------------------
|
|
666
1007
|
// CLI definition
|
|
667
1008
|
// ---------------------------------------------------------------------------
|
|
@@ -751,6 +1092,14 @@ function buildProgram() {
|
|
|
751
1092
|
.option('--fast-model <id>', 'The fast model id (implies nothing about --fast)')
|
|
752
1093
|
.option('--fast-provider <id>', 'Provider for the fast model (default: the main provider)')
|
|
753
1094
|
.option('--fast-review <n|off>', 'Turns between automatic fast reviews, or "off"')
|
|
1095
|
+
// BOTH FORMS, positive first, for the reason recorded seven times above —
|
|
1096
|
+
// and this is the pair whose omission would be worst. `update.mode` is
|
|
1097
|
+
// PERSISTED and defaults to `'auto'`, so a lone `--no-update` would make
|
|
1098
|
+
// commander default `opts.update` to `true` and silently overwrite a stored
|
|
1099
|
+
// `off` on EVERY run that passed no flag at all: the kill switch for a
|
|
1100
|
+
// feature that installs software would quietly un-set itself (C-11).
|
|
1101
|
+
.option('--update', 'Check for and install updates in the background (the default)')
|
|
1102
|
+
.option('--no-update', 'Disable auto-update for this session')
|
|
754
1103
|
.option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
|
|
755
1104
|
.option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
|
|
756
1105
|
.option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
|
|
@@ -772,6 +1121,15 @@ function buildProgram() {
|
|
|
772
1121
|
.option('--render-governor', 'Adapt the frame interval under load (the default)')
|
|
773
1122
|
.option('--no-render-governor', 'Pin the frame interval at 33ms however expensive a frame is')
|
|
774
1123
|
.option('--max-render-interval <ms>', 'Governor ceiling in ms (33-1000, default 320)')
|
|
1124
|
+
// Composer flicker (tui-input-flicker-fix §5.2). Positive form FIRST for the
|
|
1125
|
+
// tri-state reason recorded five times above: both keys are persisted and
|
|
1126
|
+
// default to `true`, so a negative-only declaration would let commander
|
|
1127
|
+
// synthesize `true` and overwrite a stored `false` on every run — turning the
|
|
1128
|
+
// one documented escape hatch for a mis-rendering terminal into a no-op.
|
|
1129
|
+
.option('--diff-render', 'Repaint only changed rows (default, full-screen only)')
|
|
1130
|
+
.option('--no-diff-render', 'Repaint the whole frame every time (pre-fix behavior)')
|
|
1131
|
+
.option('--sync-output', 'Ask the terminal to present each repaint atomically (the default)')
|
|
1132
|
+
.option('--no-sync-output', 'Do not emit DEC 2026 begin/end synchronized update')
|
|
775
1133
|
.option('--no-color', 'Disable ANSI color')
|
|
776
1134
|
.option('--quiet', '(print mode) suppress tool/usage lines on stderr')
|
|
777
1135
|
.action(async (prompt, opts) => {
|
|
@@ -881,6 +1239,22 @@ function buildProgram() {
|
|
|
881
1239
|
const code = await runSkillsCommand(subcommand ?? 'list', argument, opts, toFlags(program.opts()), VERSION);
|
|
882
1240
|
process.exitCode = code;
|
|
883
1241
|
});
|
|
1242
|
+
// aragon update [--check] [--to <version>] [--json]
|
|
1243
|
+
//
|
|
1244
|
+
// THE GUARANTEED REPORTING SURFACE (C-12 / D-18), and the only update path
|
|
1245
|
+
// available to a headless user at all - `runInteractive` is the sole place the
|
|
1246
|
+
// background service is constructed, so without this command `aragon -p` users
|
|
1247
|
+
// could never update deliberately.
|
|
1248
|
+
program
|
|
1249
|
+
.command('update')
|
|
1250
|
+
.description('Check for a new release and install it')
|
|
1251
|
+
.option('--check', 'Report only; never install')
|
|
1252
|
+
.option('--to <version>', 'Install an exact version (an explicit escape hatch)')
|
|
1253
|
+
.option('--rollback', 'Reinstall the version that ran before the last auto-update')
|
|
1254
|
+
.option('--json', 'Machine-readable result on stdout')
|
|
1255
|
+
.action(async (opts) => {
|
|
1256
|
+
process.exitCode = await runUpdateCommand(toFlags(program.opts()), opts);
|
|
1257
|
+
});
|
|
884
1258
|
// aragon models [--provider p]
|
|
885
1259
|
program
|
|
886
1260
|
.command('models')
|
|
@@ -31,6 +31,62 @@ import { perfCommand } from './perf.js';
|
|
|
31
31
|
* feature that is spending money is the same class of lie as pricing a Haiku
|
|
32
32
|
* child at Sonnet rates.
|
|
33
33
|
*/
|
|
34
|
+
/**
|
|
35
|
+
* `/update status` - the human-readable readout (cli-auto-update §4.4).
|
|
36
|
+
*
|
|
37
|
+
* ASCII ONLY and NO GLYPHS: this string goes into a transcript `notice`, which
|
|
38
|
+
* `Transcript.tsx` renders with its own marker; the glyph tier belongs to the
|
|
39
|
+
* bottom-row line, not here. Everything a user could want to act on is named:
|
|
40
|
+
* what is running, what is available, how it got installed, and when we look
|
|
41
|
+
* again.
|
|
42
|
+
*/
|
|
43
|
+
function formatUpdateStatus(port, override) {
|
|
44
|
+
const snapshot = override ?? port.snapshot();
|
|
45
|
+
const lines = [
|
|
46
|
+
`Auto-update: ${snapshot.phase}`,
|
|
47
|
+
` running ${snapshot.currentVersion}`,
|
|
48
|
+
` latest ${snapshot.latestVersion ?? 'unknown'}`,
|
|
49
|
+
` source ${snapshot.source}`,
|
|
50
|
+
];
|
|
51
|
+
if (snapshot.reason)
|
|
52
|
+
lines.push(` reason ${snapshot.reason}`);
|
|
53
|
+
if (snapshot.advice)
|
|
54
|
+
lines.push(` install ${snapshot.advice}`);
|
|
55
|
+
// The three hardening lines, each only when it has something to say
|
|
56
|
+
// (cli-auto-update-hardening section 6.3).
|
|
57
|
+
//
|
|
58
|
+
// THE KEY COLUMN IS TEN WIDE, WHICH IS WHY THE FIRST KEY IS `rollback` AND NOT
|
|
59
|
+
// `rolledback`. Section 6.3 asks for "a padded key, matching the six lines
|
|
60
|
+
// `formatUpdateStatus` already emits", and every one of those pads the key to
|
|
61
|
+
// ten so the values start in the same column - the entire reason a readout is
|
|
62
|
+
// padded at all. `rolledback` is ten characters on its own, so it cannot take
|
|
63
|
+
// a separating space without pushing its value one column past the other six,
|
|
64
|
+
// which is the misalignment the padding exists to prevent. The spec's sample
|
|
65
|
+
// block is internally consistent (all three new lines at eleven) and
|
|
66
|
+
// inconsistent with the six it says it matches; the prose is the requirement.
|
|
67
|
+
//
|
|
68
|
+
// THE ROLLBACK LINE CARRIES NO FAILURE COUNT on purpose: `bootFailures` is
|
|
69
|
+
// zeroed by the rollback itself, so any number printed here would be `0`. The
|
|
70
|
+
// count that mattered is in the `update_rolled_back` log record.
|
|
71
|
+
if (snapshot.rolledBackFrom) {
|
|
72
|
+
const to = snapshot.rolledBackTo ?? snapshot.currentVersion;
|
|
73
|
+
lines.push(` rollback ${snapshot.rolledBackFrom} -> ${to}`);
|
|
74
|
+
}
|
|
75
|
+
// What `aragon update --rollback` would reinstall. This is the only place a
|
|
76
|
+
// user can find that out BEFORE running the command.
|
|
77
|
+
if (snapshot.lastGoodVersion)
|
|
78
|
+
lines.push(` lastgood ${snapshot.lastGoodVersion}`);
|
|
79
|
+
// The only place a user can learn their machine updates through the fallback.
|
|
80
|
+
if (snapshot.probe) {
|
|
81
|
+
lines.push(` probe ${snapshot.probe === 'npm' ? 'npm (proxy fallback)' : 'http'}`);
|
|
82
|
+
}
|
|
83
|
+
if (snapshot.consecutiveFailures > 0) {
|
|
84
|
+
lines.push(` failures ${snapshot.consecutiveFailures} in a row`);
|
|
85
|
+
}
|
|
86
|
+
const next = override ? port.nextCheckAt() : snapshot.nextCheckAt;
|
|
87
|
+
lines.push(` next ${next ? new Date(next).toLocaleTimeString() : 'not scheduled'}`);
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
34
90
|
function formatFastStatus(controller) {
|
|
35
91
|
const status = controller.getFastStatus();
|
|
36
92
|
const cfg = controller.getFastConfig();
|
|
@@ -458,6 +514,68 @@ const COMMANDS = [
|
|
|
458
514
|
'delegate on|off].');
|
|
459
515
|
},
|
|
460
516
|
},
|
|
517
|
+
{
|
|
518
|
+
name: 'update',
|
|
519
|
+
description: 'Auto-update: status | now | skip | off',
|
|
520
|
+
/**
|
|
521
|
+
* THE GUARANTEED REPORTING SURFACE for the updater (C-12 / D-18).
|
|
522
|
+
*
|
|
523
|
+
* The bottom row can be preempted by a toast or by the activity line, and it
|
|
524
|
+
* is deliberately last in that precedence (D-2), so a user who is actually
|
|
525
|
+
* working may never see it. This command always answers "is it on, what did
|
|
526
|
+
* it find, and when does it look again".
|
|
527
|
+
*
|
|
528
|
+
* REACHES THE SERVICE THROUGH `ctx.update`, NOT THROUGH `ctx.controller`
|
|
529
|
+
* (C-14 / P0-2). `/fast`, `/team` and `/todo` ride on the controller because
|
|
530
|
+
* their subsystems live there; `update/` may not import `agent/` at all, so
|
|
531
|
+
* it carries its own optional port. `undefined` is a legitimate state - `off`,
|
|
532
|
+
* a non-TTY, or CI - and says so rather than pretending to have failed.
|
|
533
|
+
*/
|
|
534
|
+
run: (ctx) => {
|
|
535
|
+
const arg = ctx.args.trim();
|
|
536
|
+
const [rawVerb] = arg.split(/\s+/, 1);
|
|
537
|
+
const verb = (rawVerb ?? '').toLowerCase();
|
|
538
|
+
const port = ctx.update;
|
|
539
|
+
// `/update off` is the one branch that works WITHOUT a service: turning
|
|
540
|
+
// something off must not require it to be running, or a user who launched
|
|
541
|
+
// with `--no-update` can never make that stick.
|
|
542
|
+
if (verb === 'off') {
|
|
543
|
+
ctx.persistConfig({ update: { mode: 'off' } });
|
|
544
|
+
ctx.toast('success', 'Auto-update off. Takes effect on the next launch.');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (!port) {
|
|
548
|
+
ctx.notify('info', 'Updates are disabled for this session (update.mode: off, a non-TTY, or CI). ' +
|
|
549
|
+
'Enable with: aragon config set update.mode auto');
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (!verb || verb === 'status') {
|
|
553
|
+
ctx.notify('info', formatUpdateStatus(port));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (verb === 'now') {
|
|
557
|
+
// `force: true` bypasses the machine-wide throttle - a human typing this
|
|
558
|
+
// is the one caller entitled to ignore `lastCheckAt`.
|
|
559
|
+
void port
|
|
560
|
+
.checkNow({ force: true })
|
|
561
|
+
.then((snapshot) => ctx.notify('info', formatUpdateStatus(port, snapshot)))
|
|
562
|
+
.catch(() => ctx.notify('warn', 'Update check failed. See the log for details.'));
|
|
563
|
+
ctx.toast('info', 'Checking for updates...');
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (verb === 'skip') {
|
|
567
|
+
const version = port.snapshot().latestVersion;
|
|
568
|
+
if (!version) {
|
|
569
|
+
ctx.notify('info', 'No update is being offered, so there is nothing to skip.');
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
port.skip(version);
|
|
573
|
+
ctx.toast('success', `Skipped ${version}. You will hear about the next release.`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
ctx.notify('warn', `Unknown argument "${verb}" - use /update [status|now|skip|off].`);
|
|
577
|
+
},
|
|
578
|
+
},
|
|
461
579
|
{
|
|
462
580
|
name: 'todo',
|
|
463
581
|
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | clear | continue',
|
package/dist/commands/perf.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* live panel, because a performance readout that costs a repaint per second to
|
|
16
16
|
* display would be self-defeating.
|
|
17
17
|
*/
|
|
18
|
+
import type { FrameWriterStats } from '../ui/frame-differ.js';
|
|
18
19
|
import type { SlashCommand } from './registry.js';
|
|
19
20
|
export interface PerfSnapshot {
|
|
20
21
|
/** Governor rung, its resolved interval, and the last measured commit cost. */
|
|
@@ -40,6 +41,9 @@ export interface PerfSnapshot {
|
|
|
40
41
|
export declare function setPerfSnapshotProvider(fn: (() => PerfSnapshot | null) | null): void;
|
|
41
42
|
export declare function readPerfSnapshot(): PerfSnapshot | null;
|
|
42
43
|
export declare function setPerfResetHook(fn: (() => void) | null): void;
|
|
43
|
-
/**
|
|
44
|
+
/** Called by `cli.tsx` after building the writer. Passing `null` detaches. */
|
|
45
|
+
export declare function setFrameStatsProvider(fn: (() => FrameWriterStats) | null): void;
|
|
46
|
+
export declare function readFrameStats(): FrameWriterStats | null;
|
|
47
|
+
/** Format a snapshot as the six-line block documented in §5.4. */
|
|
44
48
|
export declare function formatPerfReport(snap: PerfSnapshot): string;
|
|
45
49
|
export declare const perfCommand: SlashCommand;
|