@aragon-agent/cli 0.6.8 → 0.6.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.
- package/CHANGELOG.md +27 -0
- package/README.md +38 -37
- package/dist/agent/controller.d.ts +1 -1
- package/dist/agent/reducer.d.ts +7 -113
- package/dist/agent/reducer.js +0 -62
- package/dist/cli.js +48 -149
- package/dist/commands/perf.d.ts +32 -2
- package/dist/commands/perf.js +32 -2
- package/dist/commands/registry.d.ts +2 -2
- package/dist/config/env.js +0 -130
- package/dist/config/load.d.ts +0 -2
- package/dist/config/load.js +2 -31
- package/dist/config/schema.d.ts +1 -14
- package/dist/config/schema.js +0 -1
- package/dist/config/store.js +2 -0
- package/dist/logging/install.d.ts +2 -2
- package/dist/logging/install.js +2 -2
- package/dist/todo/follow-through.d.ts +0 -4
- package/dist/todo/follow-through.js +0 -4
- package/dist/todo/limits.d.ts +0 -10
- package/dist/todo/limits.js +0 -10
- package/dist/todo/prompt.d.ts +0 -15
- package/dist/ui/ActivityLine.d.ts +26 -34
- package/dist/ui/ActivityLine.js +30 -19
- package/dist/ui/App.d.ts +1 -41
- package/dist/ui/App.js +66 -128
- package/dist/ui/BottomStatusRow.d.ts +1 -3
- package/dist/ui/BottomStatusRow.js +4 -8
- package/dist/ui/Composer.d.ts +28 -1
- package/dist/ui/Composer.js +43 -17
- package/dist/ui/Header.d.ts +2 -16
- package/dist/ui/Header.js +3 -18
- package/dist/ui/Logo.d.ts +1 -17
- package/dist/ui/Logo.js +0 -16
- package/dist/ui/ModeChip.d.ts +7 -18
- package/dist/ui/ModeChip.js +9 -0
- package/dist/ui/PromptInput.d.ts +2 -28
- package/dist/ui/PromptInput.js +4 -7
- package/dist/ui/StatusBar.d.ts +0 -11
- package/dist/ui/ToastStack.d.ts +2 -18
- package/dist/ui/ToastStack.js +4 -11
- package/dist/ui/Transcript.d.ts +5 -71
- package/dist/ui/Transcript.js +9 -199
- package/dist/ui/entries/AssistantEntry.d.ts +0 -23
- package/dist/ui/entries/AssistantEntry.js +3 -4
- package/dist/ui/entries/DiffView.d.ts +1 -7
- package/dist/ui/entries/DiffView.js +2 -2
- package/dist/ui/entries/ToolCard.d.ts +0 -6
- package/dist/ui/entries/ToolCard.js +4 -13
- package/dist/ui/frame-differ.d.ts +13 -0
- package/dist/ui/frame-differ.js +36 -6
- package/dist/ui/ink-runtime.d.ts +1 -0
- package/dist/ui/ink-runtime.js +21 -0
- package/dist/ui/layout/AppShell.d.ts +1 -10
- package/dist/ui/layout/AppShell.js +2 -5
- package/dist/ui/layout/OverlayFrame.d.ts +1 -25
- package/dist/ui/layout/OverlayFrame.js +0 -24
- package/dist/ui/layout/ScrollViewport.d.ts +3 -0
- package/dist/ui/layout/ScrollViewport.js +17 -5
- package/dist/ui/layout/frame.d.ts +0 -22
- package/dist/ui/layout/frame.js +0 -36
- package/dist/ui/layout/todo-layout.d.ts +1 -3
- package/dist/ui/layout/todo-layout.js +7 -8
- package/dist/ui/layout/viewport-geometry.d.ts +1 -1
- package/dist/ui/layout/viewport-geometry.js +1 -1
- package/dist/ui/live-clamp.d.ts +1 -29
- package/dist/ui/live-clamp.js +1 -29
- package/dist/ui/overlays/HelpOverlay.js +1 -0
- package/dist/ui/run-status-row.d.ts +61 -0
- package/dist/ui/run-status-row.js +70 -0
- package/dist/ui/transcript-text.d.ts +0 -8
- package/dist/ui/transcript-text.js +0 -8
- package/dist/ui/use-startup-notices.d.ts +1 -57
- package/dist/ui/use-startup-notices.js +3 -54
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/ui/TodoStrip.d.ts +0 -36
- package/dist/ui/TodoStrip.js +0 -32
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,8 @@ import { dirname, join } from 'node:path';
|
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import process from 'node:process';
|
|
17
17
|
import '../runtime/insecure-tls-warning.cjs';
|
|
18
|
-
|
|
18
|
+
// Must precede every other dependency that imports Ink: CI detection is cached.
|
|
19
|
+
import { render } from './ui/ink-runtime.js';
|
|
19
20
|
import { Command } from 'commander';
|
|
20
21
|
import { loadConfig } from './config/load.js';
|
|
21
22
|
import { getConfigPath, readConfigFile, updatePersistedConfig } from './config/store.js';
|
|
@@ -40,7 +41,6 @@ import { registerExecCommand } from './exec/cli-commands.js';
|
|
|
40
41
|
import { registerSessionsCommand } from './session/cli-commands.js';
|
|
41
42
|
import { registerDoctorCommand, registerInfoCommand } from './diagnostics/cli-commands.js';
|
|
42
43
|
import { App } from './ui/App.js';
|
|
43
|
-
import { decideRenderMode } from './ui/layout/frame.js';
|
|
44
44
|
import { createFrameDiffer } from './ui/frame-differ.js';
|
|
45
45
|
import { wrapStdoutForFrames } from './ui/stdout-frame-writer.js';
|
|
46
46
|
import { setFrameStatsProvider } from './commands/perf.js';
|
|
@@ -96,7 +96,6 @@ function toFlags(opts) {
|
|
|
96
96
|
confirm: opts.confirm,
|
|
97
97
|
toolTimeout: opts.toolTimeout,
|
|
98
98
|
idleTimeout: opts.idleTimeout,
|
|
99
|
-
fullscreen: opts.fullscreen,
|
|
100
99
|
exitTranscript: opts.exitTranscript,
|
|
101
100
|
compact: opts.compact,
|
|
102
101
|
hints: opts.hints,
|
|
@@ -230,14 +229,8 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
230
229
|
// resolved a bootstrap level — it ran before commander and before `.env` were
|
|
231
230
|
// readable — and without this hand-off the fully resolved one never applies.
|
|
232
231
|
getLogger().reconfigure(config.log);
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
// is pure and `runInteractive` calls it again with the same inputs, so
|
|
236
|
-
// computing it here rather than threading it back costs nothing and keeps the
|
|
237
|
-
// controller's construction self-contained.
|
|
238
|
-
const todoPanelCapable = opts.interactive &&
|
|
239
|
-
decideRenderMode({ fullscreen: config.fullscreen }, process.env, process.stdout) ===
|
|
240
|
-
'fullscreen';
|
|
232
|
+
// Headless controllers never expose the task panel.
|
|
233
|
+
const todoPanelCapable = opts.interactive;
|
|
241
234
|
const confirmBridge = { handler: null };
|
|
242
235
|
// `cancelPending` is replaced by the App's own implementation on mount; this
|
|
243
236
|
// no-op keeps the object total so nothing has to null-check it.
|
|
@@ -389,11 +382,15 @@ function forceVtInputForThisConsole() {
|
|
|
389
382
|
* renders Ink, `runOneShot()` goes to `runHeadless()`, which never does.
|
|
390
383
|
*/
|
|
391
384
|
function runInteractive(flags, extras = {}) {
|
|
385
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
386
|
+
process.stderr.write('Interactive UI requires a TTY. Use -p "<prompt>", exec, or config list / config set.\n');
|
|
387
|
+
process.exitCode = 2;
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
392
390
|
const { controller, confirmBridge, humanInputBridge } = makeController(flags);
|
|
393
391
|
const config = controller.getConfig();
|
|
394
|
-
const mode = decideRenderMode({ fullscreen: config.fullscreen }, process.env, process.stdout);
|
|
395
392
|
const logger = getLogger();
|
|
396
|
-
logger.info('cli', 'run_interactive', { mode, provider: config.provider, model: config.model });
|
|
393
|
+
logger.info('cli', 'run_interactive', { mode: 'fullscreen', provider: config.provider, model: config.model });
|
|
397
394
|
attachAgentEvents(logger, controller);
|
|
398
395
|
// The team stream is CLI-local (D-10), so `attachAgentEvents` cannot see it.
|
|
399
396
|
// Without this a dispatch leaves NO trace in the log file at all — the wrong
|
|
@@ -442,8 +439,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
442
439
|
// a console this process just repaired.
|
|
443
440
|
const vtInputByNode = supportsWindowsVtInput(process.platform, process.versions.node);
|
|
444
441
|
const vtInputSupported = vtInputByNode || forceVtInputForThisConsole();
|
|
445
|
-
const wantMouse =
|
|
446
|
-
config.mouse &&
|
|
442
|
+
const wantMouse = config.mouse &&
|
|
447
443
|
!!process.stdout.isTTY &&
|
|
448
444
|
!!process.stdin.isTTY &&
|
|
449
445
|
vtInputSupported;
|
|
@@ -492,21 +488,13 @@ function runInteractive(flags, extras = {}) {
|
|
|
492
488
|
onCopied: null,
|
|
493
489
|
requestRedraw: null,
|
|
494
490
|
};
|
|
495
|
-
|
|
496
|
-
//
|
|
497
|
-
// FULL-SCREEN ONLY, and that is invariant I-3 rather than caution: inline mode
|
|
498
|
-
// prints settled entries through Ink's `<Static>`, starts wherever the shell's
|
|
499
|
-
// cursor happened to be and scrolls, so row k of a frame is not absolute row k
|
|
500
|
-
// there and never will be. `--no-diff-render` skips construction entirely,
|
|
501
|
-
// which is what makes rung 2 of the fail-safe ladder byte-identical to a
|
|
502
|
-
// pre-fix build rather than merely equivalent.
|
|
503
|
-
//
|
|
504
|
-
// `rows` is a THUNK, not a snapshot: it is the only input to the I-9 geometry
|
|
505
|
-
// guard and it changes under the differ's feet on every resize.
|
|
506
|
-
const frameWriter = mode === 'fullscreen' && config.diffRender
|
|
491
|
+
const frameWriter = config.diffRender
|
|
507
492
|
? wrapStdoutForFrames(process.stdout, createFrameDiffer({
|
|
508
493
|
sync: config.syncOutput && process.env.TERM !== 'dumb',
|
|
509
494
|
rows: () => process.stdout.rows,
|
|
495
|
+
// Rows that fill the line skip `CSI K` so the scrollbar's last column
|
|
496
|
+
// is not erased on pending-wrap terminals (frame-differ I-4).
|
|
497
|
+
cols: () => process.stdout.columns,
|
|
510
498
|
// The console bridge (`App.tsx`, full-screen only) turns this into
|
|
511
499
|
// `dispatch({type:'notice', level:'warn'})`, which is the only legal
|
|
512
500
|
// user-visible channel with the TUI mounted — a direct write would
|
|
@@ -539,11 +527,11 @@ function runInteractive(flags, extras = {}) {
|
|
|
539
527
|
// than printing zeroes that read like a writer doing nothing (§5.4).
|
|
540
528
|
if (frameWriter)
|
|
541
529
|
setFrameStatsProvider(frameWriter.stats);
|
|
542
|
-
const frameObserver =
|
|
530
|
+
const frameObserver = createFrameObserver({
|
|
543
531
|
stdout: frameWriter?.stdout ?? process.stdout, terminal: process.stdout, scrollbar,
|
|
544
|
-
})
|
|
532
|
+
});
|
|
545
533
|
const disposeFrameWriter = () => {
|
|
546
|
-
frameObserver
|
|
534
|
+
frameObserver.dispose();
|
|
547
535
|
pointerRouter?.dispose();
|
|
548
536
|
scrollbar.controller?.dispose();
|
|
549
537
|
if (!frameWriter)
|
|
@@ -552,7 +540,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
552
540
|
frameWriter.dispose();
|
|
553
541
|
};
|
|
554
542
|
const replayTranscript = () => {
|
|
555
|
-
if (replayed ||
|
|
543
|
+
if (replayed || !config.exitTranscript)
|
|
556
544
|
return;
|
|
557
545
|
replayed = true;
|
|
558
546
|
const snapshot = readExitSnapshot();
|
|
@@ -571,7 +559,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
571
559
|
...(snapshot.droppedEntries ? { droppedEntries: snapshot.droppedEntries } : {}),
|
|
572
560
|
}));
|
|
573
561
|
};
|
|
574
|
-
|
|
562
|
+
{
|
|
575
563
|
// `mouse` here means "a mouse-parsing filter is installed", NOT "the user
|
|
576
564
|
// wants mouse support" — see `AltScreenOptions`. `screen.ts` must never
|
|
577
565
|
// derive it for itself, or a later refactor re-opens the gap I-8 closes.
|
|
@@ -585,7 +573,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
585
573
|
screen = enterAltScreen(process.stdout, {
|
|
586
574
|
mouse: mouseOn,
|
|
587
575
|
motion: mouseOn,
|
|
588
|
-
bracketedPaste: pasteOn
|
|
576
|
+
bracketedPaste: pasteOn,
|
|
589
577
|
});
|
|
590
578
|
const restore = () => screen?.restore();
|
|
591
579
|
// Four idempotent restore paths (§4.4). The signal hook is NOT redundant:
|
|
@@ -610,42 +598,6 @@ function runInteractive(flags, extras = {}) {
|
|
|
610
598
|
process.exit(128 + signo);
|
|
611
599
|
});
|
|
612
600
|
}
|
|
613
|
-
// --- Auto-update (cli-auto-update section 3.8). --------------------------
|
|
614
|
-
//
|
|
615
|
-
// THE ONLY CONSTRUCTION SITE. `runOneShot` / `runHeadless` never reach this
|
|
616
|
-
// function, so `aragon -p` executes zero update code and its stdout is
|
|
617
|
-
// byte-identical to a pre-feature build (AC-1); `aragon config <subcommand>`,
|
|
618
|
-
// `aragon logs`, `aragon skills` and `aragon --version` likewise. Bare
|
|
619
|
-
// `aragon config` DOES construct it, because it is a full interactive session
|
|
620
|
-
// with a settings overlay on top - which is correct, and is why AC-1's claim
|
|
621
|
-
// names the SUBCOMMAND form.
|
|
622
|
-
//
|
|
623
|
-
// `CI` is excluded on top of the two TTY tests because a CI runner with a pty
|
|
624
|
-
// allocated is otherwise a machine that silently mutates its own global
|
|
625
|
-
// toolchain mid-pipeline (D-11).
|
|
626
|
-
// --- The foreign-write door (§4.4.5). ------------------------------------
|
|
627
|
-
//
|
|
628
|
-
// Where bytes that are OURS but are NOT a frame go — today, the OSC 52
|
|
629
|
-
// clipboard sequence. THREE SHAPES, ONE DOOR, and the third one is the reason
|
|
630
|
-
// this is a resolved value rather than an inline arrow:
|
|
631
|
-
//
|
|
632
|
-
// * with the differ in front of stdout it MUST be `frameWriter.writeForeign`,
|
|
633
|
-
// which invalidates the cache and skips the fallback accounting — through
|
|
634
|
-
// the proxy the same bytes are an unrecognised chunk, which is
|
|
635
|
-
// `passThrough(true)`, which prints `FRAME_FALLBACK_NOTICE` at a user who
|
|
636
|
-
// did nothing but copy something (P1-6);
|
|
637
|
-
// * without one — `--no-diff-render`, and inline mode, which never builds a
|
|
638
|
-
// differ at all — stdout IS the real stream, so a direct write is both safe
|
|
639
|
-
// and what §4.4.5 prescribes. OSC 52 emits no newline, so Ink's own line
|
|
640
|
-
// accounting is untouched and there is nothing to invalidate;
|
|
641
|
-
// * on a non-TTY there is no terminal to accept it, so the door is ABSENT.
|
|
642
|
-
//
|
|
643
|
-
// It must never be a function that accepts a chunk and writes NOWHERE.
|
|
644
|
-
// `copyText` reports `'osc52'` whenever its door took the text, so a silent
|
|
645
|
-
// no-op door — which `frameWriter?.writeForeign(chunk)` is under
|
|
646
|
-
// `--no-diff-render` — makes the toast name a mechanism that was never
|
|
647
|
-
// attempted. Naming the mechanism honestly is the entire reason that return
|
|
648
|
-
// value exists (R-7), and a door that lies is worse than no door.
|
|
649
601
|
const writeForeign = frameWriter
|
|
650
602
|
? (text) => { frameObserver?.invalidate(); frameWriter.writeForeign(text); }
|
|
651
603
|
: process.stdout.isTTY
|
|
@@ -695,45 +647,30 @@ function runInteractive(flags, extras = {}) {
|
|
|
695
647
|
selectionBridge.controller?.dispose();
|
|
696
648
|
selectionBridge.controller = null;
|
|
697
649
|
};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
// A released mouse cannot be dragging, and a selection left
|
|
723
|
-
// painted over a screen the terminal is now selecting on its
|
|
724
|
-
// own would be two highlights claiming the same rows.
|
|
725
|
-
if (!on)
|
|
726
|
-
selectionBridge.controller?.clear();
|
|
727
|
-
},
|
|
728
|
-
isMouseCaptured: () => mouseCaptured,
|
|
729
|
-
}
|
|
730
|
-
: {}),
|
|
731
|
-
// The same door the controller copies through, so `/copy` and a
|
|
732
|
-
// drag-release can never disagree about which stream OSC 52 goes to.
|
|
733
|
-
...(writeForeign ? { writeForeign } : {}),
|
|
734
|
-
...(selectionBridge.controller ? { selection: selectionBridge } : {}),
|
|
735
|
-
}
|
|
736
|
-
: undefined;
|
|
650
|
+
const terminalBridge = {
|
|
651
|
+
mouseSelect: wantSelect,
|
|
652
|
+
scrollbar,
|
|
653
|
+
...(mouseOn
|
|
654
|
+
? {
|
|
655
|
+
setMouseCapture: (on) => {
|
|
656
|
+
screen?.setMouseCapture(on);
|
|
657
|
+
mouseCaptured = on;
|
|
658
|
+
if (!on)
|
|
659
|
+
scrollbar.controller?.cancel();
|
|
660
|
+
// A released mouse cannot be dragging, and a selection left
|
|
661
|
+
// painted over a screen the terminal is now selecting on its
|
|
662
|
+
// own would be two highlights claiming the same rows.
|
|
663
|
+
if (!on)
|
|
664
|
+
selectionBridge.controller?.clear();
|
|
665
|
+
},
|
|
666
|
+
isMouseCaptured: () => mouseCaptured,
|
|
667
|
+
}
|
|
668
|
+
: {}),
|
|
669
|
+
// The same door the controller copies through, so `/copy` and a
|
|
670
|
+
// drag-release can never disagree about which stream OSC 52 goes to.
|
|
671
|
+
...(writeForeign ? { writeForeign } : {}),
|
|
672
|
+
...(selectionBridge.controller ? { selection: selectionBridge } : {}),
|
|
673
|
+
};
|
|
737
674
|
const updateBridge = { service: null, onAttach: null };
|
|
738
675
|
let updateService = null;
|
|
739
676
|
let interactiveClosed = false;
|
|
@@ -761,12 +698,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
761
698
|
config.update.mode !== 'off';
|
|
762
699
|
let instance;
|
|
763
700
|
try {
|
|
764
|
-
instance = render(_jsx(App, { controller: controller, version: VERSION,
|
|
765
|
-
// `mouseOn`, NOT the handle (I-11). `App.tsx:186-188` documents this
|
|
766
|
-
// prop's contract as "absent under `--no-mouse`, inline, or non-TTY", and
|
|
767
|
-
// it feeds `enabled: !!mouseSource` on a wheel subscription that would
|
|
768
|
-
// otherwise be a channel no event can ever arrive on.
|
|
769
|
-
mouseSource: mouseOn && filter ? filter.source : undefined,
|
|
701
|
+
instance = render(_jsx(App, { controller: controller, version: VERSION, confirmBridge: confirmBridge, humanInputBridge: humanInputBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay, mouseSource: mouseOn && filter ? filter.source : undefined,
|
|
770
702
|
// Present ONLY when this console swallows `CSI Z` and mouse reports.
|
|
771
703
|
// Passed in rather than probed inside `App` so that every test renders
|
|
772
704
|
// the same tree on every machine — reading `process.versions.node` from
|
|
@@ -780,17 +712,11 @@ function runInteractive(flags, extras = {}) {
|
|
|
780
712
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
781
713
|
{
|
|
782
714
|
exitOnCtrlC: false,
|
|
783
|
-
patchConsole:
|
|
715
|
+
patchConsole: false,
|
|
784
716
|
// I-5: with mouse support off the REAL stdin is handed over unwrapped, so
|
|
785
717
|
// every existing path is provably unchanged.
|
|
786
718
|
stdin: filter?.stdin ?? process.stdin,
|
|
787
|
-
|
|
788
|
-
// `stdout` at all before, and Ink defaults it to `process.stdout`; omitting
|
|
789
|
-
// the key entirely when the writer is off keeps the options object the same
|
|
790
|
-
// SHAPE it has always had, which is what makes AC-5 / AC-6 ("byte-identical
|
|
791
|
-
// with --no-diff-render, inline mode and `aragon -p`") a statement about
|
|
792
|
-
// the code rather than about Ink's defaulting.
|
|
793
|
-
...(frameObserver ? { stdout: frameObserver.stdout } : {}),
|
|
719
|
+
stdout: frameObserver.stdout,
|
|
794
720
|
});
|
|
795
721
|
}
|
|
796
722
|
catch (error) {
|
|
@@ -968,7 +894,6 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
968
894
|
'confirmTools',
|
|
969
895
|
'toolTimeoutMs',
|
|
970
896
|
'idleTimeoutMs',
|
|
971
|
-
'fullscreen',
|
|
972
897
|
'exitTranscript',
|
|
973
898
|
'transcriptWindow',
|
|
974
899
|
// tui-render-performance L1 / L4. Listed here AND cased in the switch
|
|
@@ -1189,9 +1114,6 @@ function runConfigSet(key, value) {
|
|
|
1189
1114
|
case 'idleTimeoutMs':
|
|
1190
1115
|
patch.idleTimeoutMs = coercePositiveInt(value, 210_000);
|
|
1191
1116
|
break;
|
|
1192
|
-
case 'fullscreen':
|
|
1193
|
-
patch.fullscreen = value === 'true' || value === '1';
|
|
1194
|
-
break;
|
|
1195
1117
|
case 'exitTranscript':
|
|
1196
1118
|
patch.exitTranscript = value === 'true' || value === '1';
|
|
1197
1119
|
break;
|
|
@@ -1480,30 +1402,16 @@ function buildProgram() {
|
|
|
1480
1402
|
.option('--tool-timeout <ms>', 'Per-tool executor ceiling (default 180000)')
|
|
1481
1403
|
.option('--idle-timeout <ms>', 'Watchdog idle timeout (auto-raised to >= tool-timeout+30s)')
|
|
1482
1404
|
.option('--theme <name>', 'auto|warm|cool|light ("dark" is an alias for "cool")')
|
|
1483
|
-
// Same tri-state shape as `--fullscreen`: declaring the positive form first
|
|
1484
|
-
// keeps the default `undefined`, so "no opinion" stays distinguishable from
|
|
1485
|
-
// an explicit choice and the config layer below can still win.
|
|
1486
1405
|
.option('--compact', 'Compact transcript density (no blank rows between turns)')
|
|
1487
1406
|
.option('--no-compact', 'Comfortable transcript density')
|
|
1488
1407
|
.option('--hints', 'Always show the composer hint row')
|
|
1489
1408
|
.option('--no-hints', 'Hide the composer hint row')
|
|
1490
|
-
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
1491
1409
|
.option('--show-thinking', 'Show the reasoning blocks the model returns')
|
|
1492
1410
|
.option('--no-show-thinking', 'Hide reasoning blocks (the default)')
|
|
1493
|
-
// Positive form first, same tri-state reason as `--fullscreen` above. The
|
|
1494
|
-
// pair matters more here than for its neighbour because this key defaults to
|
|
1495
|
-
// ON: a lone negative form would let commander synthesize `true` and
|
|
1496
|
-
// overwrite a stored `false` on every run that passed no flag at all.
|
|
1497
1411
|
.option('--live-tool-output', 'Show a running command output tail (the default)')
|
|
1498
1412
|
.option('--no-live-tool-output', 'Keep a running tool card to one line')
|
|
1499
|
-
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
1500
1413
|
.option('--mouse', 'Wheel scrolls the transcript (full-screen mode)')
|
|
1501
1414
|
.option('--no-mouse', 'Leave the mouse to the terminal (the wheel does nothing)')
|
|
1502
|
-
// Positive form FIRST, for the tri-state reason `--fullscreen` records
|
|
1503
|
-
// below: `mouseSelect` is persisted and defaults to `true`, so a lone
|
|
1504
|
-
// `--no-mouse-select` would make commander default `opts.mouseSelect` to
|
|
1505
|
-
// `true` — indistinguishable from silence, and silently overriding a stored
|
|
1506
|
-
// `false` on every run that passes no flag at all.
|
|
1507
1415
|
.option('--mouse-select', 'Drag with the mouse to select text; releasing copies it')
|
|
1508
1416
|
.option('--no-mouse-select', 'Keep wheel scrolling, but leave drag-select off')
|
|
1509
1417
|
// Positive form FIRST, same tri-state reason as `--mouse-select` above:
|
|
@@ -1512,16 +1420,7 @@ function buildProgram() {
|
|
|
1512
1420
|
// silence, and silently overriding a stored `false` on every run.
|
|
1513
1421
|
.option('--paste', 'Collapse large pastes into a placeholder (the default)')
|
|
1514
1422
|
.option('--no-paste', 'Treat pasted bytes as keystrokes (pre-0.6.3 behavior)')
|
|
1515
|
-
// Declaring `--fullscreen` BEFORE `--no-fullscreen` keeps the default
|
|
1516
|
-
// `undefined` instead of `true`, which is what makes the tri-state work:
|
|
1517
|
-
// "no opinion" must stay distinguishable from "force it".
|
|
1518
|
-
.option('--fullscreen', 'Force the full-screen TUI (overrides auto-downgrade, except non-TTY)')
|
|
1519
|
-
.option('--no-fullscreen', 'Force the inline renderer (v0.2.0 behavior)')
|
|
1520
1423
|
.option('--no-exit-transcript', 'Do not replay the session summary after exiting')
|
|
1521
|
-
// Same tri-state shape as `--fullscreen`: the positive form is declared
|
|
1522
|
-
// FIRST so the default stays `undefined` and "no opinion" remains
|
|
1523
|
-
// distinguishable from an explicit `--no-plan`, which is what lets the
|
|
1524
|
-
// config layer win when neither flag was given.
|
|
1525
1424
|
.option('--plan', 'Start the session in PLAN mode (read-only research + review)')
|
|
1526
1425
|
.option('--no-plan', 'Start the session in BUILD mode (overrides planModeDefault)')
|
|
1527
1426
|
// BOTH FORMS, positive first, and the pair is mandatory (team-subagents
|
package/dist/commands/perf.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* display would be self-defeating.
|
|
17
17
|
*/
|
|
18
18
|
import type { FrameWriterStats } from '../ui/frame-differ.js';
|
|
19
|
+
import type { ScrollbarBridge } from '../ui/scrollbar-controller.js';
|
|
19
20
|
import type { SlashCommand } from './registry.js';
|
|
20
21
|
export interface PerfSnapshot {
|
|
21
22
|
/** Governor rung, its resolved interval, and the last measured commit cost. */
|
|
@@ -33,10 +34,39 @@ export interface PerfSnapshot {
|
|
|
33
34
|
heightsEstimated: number;
|
|
34
35
|
cols: number;
|
|
35
36
|
/** Geometry. */
|
|
36
|
-
mode: 'fullscreen'
|
|
37
|
+
mode: 'fullscreen';
|
|
37
38
|
viewportRows: number;
|
|
38
39
|
offset: number;
|
|
40
|
+
/** Right-edge scrollbar diagnostics; read lazily so it reflects the live bridge. */
|
|
41
|
+
scrollbar?: ScrollbarPerf;
|
|
39
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Why the right-edge scrollbar is or is not usable, in one record
|
|
45
|
+
* (tui-scrollbar-edge-and-run-row section 3.2). "Cannot see it" and "cannot drag
|
|
46
|
+
* it" had no self-check before; each field below separates one cause.
|
|
47
|
+
*/
|
|
48
|
+
export interface ScrollbarPerf {
|
|
49
|
+
/** 1-based terminal column of the track, or `null` before the first layout. */
|
|
50
|
+
trackCol: number | null;
|
|
51
|
+
trackRows: number | null;
|
|
52
|
+
/** Mouse capture is on. Off keeps the track visible but not draggable. */
|
|
53
|
+
mouseOn: boolean;
|
|
54
|
+
/** A full frame matching the geometry was observed, so pointer hits are trusted. */
|
|
55
|
+
frameReady: boolean;
|
|
56
|
+
thumb: {
|
|
57
|
+
start: number;
|
|
58
|
+
size: number;
|
|
59
|
+
} | null;
|
|
60
|
+
unicode: boolean;
|
|
61
|
+
/** `skipped`: the differ omits CSI K on full-width rows; `n/a`: diff render is off. */
|
|
62
|
+
edgeEl: 'skipped' | 'always' | 'n/a';
|
|
63
|
+
}
|
|
64
|
+
/** Build the diagnostics record from the live bridge (called at `/perf` time). */
|
|
65
|
+
export declare function readScrollbarPerf(opts: {
|
|
66
|
+
bridge: ScrollbarBridge | undefined;
|
|
67
|
+
diffRender: boolean;
|
|
68
|
+
unicode: boolean;
|
|
69
|
+
}): ScrollbarPerf | undefined;
|
|
40
70
|
/** Called by `App`. Passing `null` detaches. */
|
|
41
71
|
export declare function setPerfSnapshotProvider(fn: (() => PerfSnapshot | null) | null): void;
|
|
42
72
|
export declare function readPerfSnapshot(): PerfSnapshot | null;
|
|
@@ -44,6 +74,6 @@ export declare function setPerfResetHook(fn: (() => void) | null): void;
|
|
|
44
74
|
/** Called by `cli.tsx` after building the writer. Passing `null` detaches. */
|
|
45
75
|
export declare function setFrameStatsProvider(fn: (() => FrameWriterStats) | null): void;
|
|
46
76
|
export declare function readFrameStats(): FrameWriterStats | null;
|
|
47
|
-
/** Format a snapshot as the six-line block documented in §5.4. */
|
|
77
|
+
/** Format a snapshot as the six-line block documented in §5.4 (plus the scrollbar line). */
|
|
48
78
|
export declare function formatPerfReport(snap: PerfSnapshot): string;
|
|
49
79
|
export declare const perfCommand: SlashCommand;
|
package/dist/commands/perf.js
CHANGED
|
@@ -16,6 +16,23 @@
|
|
|
16
16
|
* display would be self-defeating.
|
|
17
17
|
*/
|
|
18
18
|
import { clearRenderCaches, renderCacheStats } from '../ui/render-cache.js';
|
|
19
|
+
/** Build the diagnostics record from the live bridge (called at `/perf` time). */
|
|
20
|
+
export function readScrollbarPerf(opts) {
|
|
21
|
+
const { bridge, diffRender, unicode } = opts;
|
|
22
|
+
if (!bridge)
|
|
23
|
+
return undefined;
|
|
24
|
+
const geometry = bridge.geometry;
|
|
25
|
+
return {
|
|
26
|
+
trackCol: geometry?.trackCol ?? null,
|
|
27
|
+
trackRows: geometry?.trackRows ?? null,
|
|
28
|
+
mouseOn: bridge.isEnabled(),
|
|
29
|
+
frameReady: bridge.frameReady,
|
|
30
|
+
thumb: geometry?.thumb ? { start: geometry.thumb.start, size: geometry.thumb.size } : null,
|
|
31
|
+
unicode,
|
|
32
|
+
// `cli.tsx` always wires `cols` when it builds the differ.
|
|
33
|
+
edgeEl: diffRender ? 'skipped' : 'n/a',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
19
36
|
/** Cleared on unmount, so `/perf` in a torn-down tree says so instead of lying. */
|
|
20
37
|
let provider = null;
|
|
21
38
|
/** Called by `App`. Passing `null` detaches. */
|
|
@@ -39,7 +56,7 @@ export function setPerfResetHook(fn) {
|
|
|
39
56
|
* render — so routing them through a 1900-line component's props would buy
|
|
40
57
|
* nothing and cost `App.tsx` plus `app.test.tsx` churn (P1-2).
|
|
41
58
|
*
|
|
42
|
-
* `null` means the writer was never built — `--no-diff-render
|
|
59
|
+
* `null` means the writer was never built — `--no-diff-render` or
|
|
43
60
|
* a non-interactive path. `/perf` says so in words, so "the flag is off" and
|
|
44
61
|
* "the writer crashed" never look alike.
|
|
45
62
|
*/
|
|
@@ -74,11 +91,23 @@ function formatWriterLine() {
|
|
|
74
91
|
`lines/frame ${ratio(stats.linesWritten, stats.framesTotal)} - ` +
|
|
75
92
|
`bytes/frame ${bytesPerFrame} - fallbacks ${stats.fallbacks}`);
|
|
76
93
|
}
|
|
77
|
-
|
|
94
|
+
function formatScrollbarLine(bar) {
|
|
95
|
+
const thumb = bar.thumb ? `${bar.thumb.start}+${bar.thumb.size}` : 'none';
|
|
96
|
+
const line = `scrollbar col=${bar.trackCol ?? '-'} rows=${bar.trackRows ?? '-'} ` +
|
|
97
|
+
`mouse=${bar.mouseOn ? 'on' : 'off'} frameReady=${bar.frameReady ? 'yes' : 'no'} ` +
|
|
98
|
+
`thumb=${thumb} glyphs=${bar.unicode ? 'unicode' : 'ascii'} edge-el=${bar.edgeEl}`;
|
|
99
|
+
if (!bar.mouseOn || bar.frameReady)
|
|
100
|
+
return line;
|
|
101
|
+
const size = bar.trackCol !== null && bar.trackRows !== null
|
|
102
|
+
? `${bar.trackCol}x${bar.trackRows + 4}` : 'the terminal size';
|
|
103
|
+
return `${line}\n waiting for a full frame matching ${size}`;
|
|
104
|
+
}
|
|
105
|
+
/** Format a snapshot as the six-line block documented in §5.4 (plus the scrollbar line). */
|
|
78
106
|
export function formatPerfReport(snap) {
|
|
79
107
|
const caches = renderCacheStats();
|
|
80
108
|
const eco = snap.rung > 0 ? ' - eco' : '';
|
|
81
109
|
const governor = snap.governorEnabled ? '' : ' - governor off';
|
|
110
|
+
const scrollbar = snap.scrollbar ? [formatScrollbarLine(snap.scrollbar)] : [];
|
|
82
111
|
return [
|
|
83
112
|
`render rung ${snap.rung} - interval ${snap.intervalMs}ms - last commit ` +
|
|
84
113
|
`${Math.round(snap.lastCommitMs)}ms${eco}${governor}`,
|
|
@@ -91,6 +120,7 @@ export function formatPerfReport(snap) {
|
|
|
91
120
|
`lines ${caches.lineEntries}`,
|
|
92
121
|
`mode ${snap.mode} - viewport ${snap.viewportRows} rows - offset ${snap.offset}`,
|
|
93
122
|
formatWriterLine(),
|
|
123
|
+
...scrollbar,
|
|
94
124
|
].join('\n');
|
|
95
125
|
}
|
|
96
126
|
export const perfCommand = {
|
|
@@ -89,7 +89,7 @@ export interface CommandContext {
|
|
|
89
89
|
/**
|
|
90
90
|
* Mouse capture, for `/mouse` (tui-selection-and-scroll-follow §6.2 / G3).
|
|
91
91
|
*
|
|
92
|
-
* OPTIONAL, and legitimately absent:
|
|
92
|
+
* OPTIONAL, and legitimately absent: `--no-mouse`, a non-TTY, a
|
|
93
93
|
* Windows console that cannot deliver reports, and every test that builds this
|
|
94
94
|
* context by literal. `/mouse` has to have something honest to say when it is
|
|
95
95
|
* missing, which is the same reason `update` above it is optional.
|
|
@@ -101,7 +101,7 @@ export interface CommandContext {
|
|
|
101
101
|
* NOT `process.stdout.write`. In full-screen mode stdout is a `Proxy` in front
|
|
102
102
|
* of the frame differ, and an unrecognised chunk there is counted as a foreign
|
|
103
103
|
* write and prints a diagnostic at the user the first time they copy anything
|
|
104
|
-
* (P1-6). Absent
|
|
104
|
+
* (P1-6). Absent with `--no-diff-render`, where the real
|
|
105
105
|
* stream is handed over directly.
|
|
106
106
|
*/
|
|
107
107
|
writeForeign?: (text: string) => void;
|