@aragon-agent/cli 0.6.28 → 0.6.31
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 +2123 -1975
- package/README.md +81 -14
- package/dist/agent/project-guidance-prompt.js +20 -20
- package/dist/agent/reducer.js +47 -2
- package/dist/cli.js +169 -56
- package/dist/commands/builtins.js +4 -0
- package/dist/commands/perf.d.ts +25 -1
- package/dist/commands/perf.js +26 -2
- package/dist/commands/registry.d.ts +6 -0
- package/dist/config/env.js +4 -0
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +12 -1
- package/dist/config/schema.d.ts +27 -0
- package/dist/config/schema.js +9 -0
- package/dist/config/ui-state.d.ts +9 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/input/limits.d.ts +20 -0
- package/dist/input/limits.js +20 -0
- package/dist/input/stdin-filter.js +68 -5
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +155 -67
- package/dist/ui/AutocompletePopup.js +1 -1
- package/dist/ui/Header.js +7 -2
- package/dist/ui/Markdown.js +30 -3
- package/dist/ui/PromptInput.js +41 -3
- package/dist/ui/ScrollbackShell.d.ts +28 -0
- package/dist/ui/ScrollbackShell.js +10 -0
- package/dist/ui/Transcript.js +7 -0
- package/dist/ui/frame-differ.d.ts +12 -0
- package/dist/ui/layout/Gutter.js +1 -1
- package/dist/ui/layout/OverlayFrame.d.ts +15 -0
- package/dist/ui/layout/OverlayFrame.js +18 -0
- package/dist/ui/layout/ScrollIndicator.js +10 -3
- package/dist/ui/layout/budget.d.ts +15 -0
- package/dist/ui/layout/budget.js +15 -0
- package/dist/ui/layout/header-hints.d.ts +9 -0
- package/dist/ui/layout/header-hints.js +14 -1
- package/dist/ui/layout/status-layout.js +63 -2
- package/dist/ui/markdown-blocks.d.ts +26 -5
- package/dist/ui/markdown-blocks.js +110 -33
- package/dist/ui/overlays/HelpOverlay.d.ts +1 -1
- package/dist/ui/overlays/HelpOverlay.js +21 -3
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +32 -0
- package/dist/ui/scrollback/budget.d.ts +43 -0
- package/dist/ui/scrollback/budget.js +44 -0
- package/dist/ui/scrollback/drain.d.ts +32 -0
- package/dist/ui/scrollback/drain.js +64 -0
- package/dist/ui/scrollback/exit-summary.d.ts +23 -0
- package/dist/ui/scrollback/exit-summary.js +25 -0
- package/dist/ui/scrollback/limits.d.ts +25 -0
- package/dist/ui/scrollback/limits.js +25 -0
- package/dist/ui/scrollback/live-preview.d.ts +26 -0
- package/dist/ui/scrollback/live-preview.js +106 -0
- package/dist/ui/scrollback/renderer.d.ts +38 -0
- package/dist/ui/scrollback/renderer.js +158 -0
- package/dist/ui/scrollback/scrollback-writer.d.ts +74 -0
- package/dist/ui/scrollback/scrollback-writer.js +263 -0
- package/dist/ui/scrollback/settled-boundary.d.ts +42 -0
- package/dist/ui/scrollback/settled-boundary.js +106 -0
- package/dist/ui/scrollback/use-scrollback-view.d.ts +92 -0
- package/dist/ui/scrollback/use-scrollback-view.js +263 -0
- package/dist/ui/scrollback/writer-shapes.d.ts +61 -0
- package/dist/ui/scrollback/writer-shapes.js +114 -0
- package/dist/ui/stdout-frame-writer.js +12 -2
- package/dist/ui/terminal-plan.d.ts +52 -0
- package/dist/ui/terminal-plan.js +49 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +21 -7
- package/dist/ui/use-startup-notices.d.ts +12 -1
- package/dist/ui/use-startup-notices.js +28 -2
- package/dist/ui/use-wheel-routing.d.ts +10 -7
- package/dist/ui/use-wheel-routing.js +11 -6
- package/package.json +2 -2
- package/skills/project-indexer/SKILL.md +163 -163
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -46
- package/skills/project-indexer/templates/cleancode-template.md +94 -94
- package/skills/project-indexer/templates/config-template.md +69 -69
- package/skills/project-indexer/templates/index-template.md +129 -129
- package/skills/skill-creator/SKILL.md +96 -96
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS,
|
|
|
29
29
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
30
30
|
import { getLogger } from './logging/logger.js';
|
|
31
31
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
32
|
-
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampContextWindow, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, isAutoToken, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
32
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampContextWindow, clampMaxRenderInterval, clampScrollMode, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, isAutoToken, parseMaxTokensInput, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SCROLL_MODE, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
33
33
|
import { makeGetApiKey } from './config/load.js';
|
|
34
34
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
35
35
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
@@ -49,13 +49,17 @@ import { createBranchLauncher } from './session/branch-launch.js';
|
|
|
49
49
|
import { EMPTY_USAGE_TOTAL } from './agent/reducer.js';
|
|
50
50
|
import { createFrameDiffer } from './ui/frame-differ.js';
|
|
51
51
|
import { wrapStdoutForFrames } from './ui/stdout-frame-writer.js';
|
|
52
|
-
import { setFrameStatsProvider } from './commands/perf.js';
|
|
52
|
+
import { setFrameStatsProvider, setScrollbackStatsProvider } from './commands/perf.js';
|
|
53
53
|
import { tryCreateStdinFilter } from './input/stdin-filter.js';
|
|
54
54
|
import { enableKeyboardEnhancement, NOOP_KEYBOARD_ENHANCEMENT, } from './input/keyboard-enhancement.js';
|
|
55
55
|
import { createScrollbarBridge } from './ui/scrollbar-controller.js';
|
|
56
56
|
import { createPointerRouter } from './input/pointer-router.js';
|
|
57
57
|
import { createFrameObserver } from './ui/frame-observer.js';
|
|
58
|
-
import { enterAltScreen, writeExitTranscript } from './ui/screen.js';
|
|
58
|
+
import { enterAltScreen, enterNativeScreen, writeExitTranscript, } from './ui/screen.js';
|
|
59
|
+
import { resolveTerminalPlan } from './ui/terminal-plan.js';
|
|
60
|
+
import { createScrollbackDrain } from './ui/scrollback/drain.js';
|
|
61
|
+
import { createScrollbackWriter } from './ui/scrollback/scrollback-writer.js';
|
|
62
|
+
import { createScrollbackBridge, } from './ui/scrollback/use-scrollback-view.js';
|
|
59
63
|
import { supportsWindowsVtInput } from './ui/win-vt-input.js';
|
|
60
64
|
import { forceWindowsVtInput } from './ui/win-vt-force.js';
|
|
61
65
|
import { readExitSnapshot } from './ui/exit-snapshot.js';
|
|
@@ -88,6 +92,19 @@ function readVersion() {
|
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
const VERSION = readVersion();
|
|
95
|
+
/**
|
|
96
|
+
* `--scroll-mode` is the one flag validated at parse time (tui-native-terminal-scroll
|
|
97
|
+
* §6.1): a typo must be an exit-2 sentence, not a silent fallback to the default.
|
|
98
|
+
*/
|
|
99
|
+
function parseScrollModeFlag(raw) {
|
|
100
|
+
if (raw === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
const mode = clampScrollMode(raw, DEFAULT_SCROLL_MODE);
|
|
103
|
+
if (mode === raw.trim().toLowerCase())
|
|
104
|
+
return mode;
|
|
105
|
+
process.stderr.write(`Invalid --scroll-mode "${raw}" (expected native or app).\n`);
|
|
106
|
+
process.exit(2);
|
|
107
|
+
}
|
|
91
108
|
function toFlags(opts) {
|
|
92
109
|
return {
|
|
93
110
|
provider: opts.provider,
|
|
@@ -186,6 +203,8 @@ function toFlags(opts) {
|
|
|
186
203
|
// user whose terminal renders the diffed frame wrongly, and a user who has
|
|
187
204
|
// been told to pass it and sees no change has no next move.
|
|
188
205
|
diffRender: opts.diffRender,
|
|
206
|
+
// Third site for `--scroll-mode`, the same trap the sentence above records.
|
|
207
|
+
scrollMode: parseScrollModeFlag(opts.scrollMode),
|
|
189
208
|
syncOutput: opts.syncOutput,
|
|
190
209
|
// Site three of three for `--update` / `--no-update` — SEVEN features, one
|
|
191
210
|
// trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, retry, fast, now
|
|
@@ -431,8 +450,10 @@ function runInteractive(flags, extras = {}) {
|
|
|
431
450
|
const config = controller.getConfig();
|
|
432
451
|
const caps = resolveTuiCapabilities({}, config.color);
|
|
433
452
|
const logger = getLogger();
|
|
453
|
+
const scrollMode = config.scrollMode ?? DEFAULT_SCROLL_MODE;
|
|
434
454
|
logger.info('cli', 'run_interactive', {
|
|
435
|
-
mode: '
|
|
455
|
+
mode: scrollMode === 'native' ? 'native' : 'fullscreen',
|
|
456
|
+
provider: config.provider, model: config.model,
|
|
436
457
|
unicode: caps.unicode, colorLevel: caps.colorLevel,
|
|
437
458
|
});
|
|
438
459
|
attachAgentEvents(logger, controller);
|
|
@@ -513,6 +534,37 @@ function runInteractive(flags, extras = {}) {
|
|
|
513
534
|
};
|
|
514
535
|
let screen = null;
|
|
515
536
|
let replayed = false;
|
|
537
|
+
/**
|
|
538
|
+
* Native-scroll exit, shared by EVERY restore path (normal exit, `process.on
|
|
539
|
+
* ('exit')`, the signal terminator, the crash handler, `failInteractive`).
|
|
540
|
+
* Idempotent: the writer's `finalize` returns `''` the second time.
|
|
541
|
+
*
|
|
542
|
+
* Order (RV-9): publish the last unsettled entries to the feed, take the summary
|
|
543
|
+
* (lazily, and a failure only costs the summary), then write the finalize bytes
|
|
544
|
+
* through the REAL stdout. After `finalize` the writer swallows every Ink write,
|
|
545
|
+
* so it does not matter whether Ink has unmounted yet; callers must not add an
|
|
546
|
+
* asynchronous wait for it.
|
|
547
|
+
*/
|
|
548
|
+
const finalizeNativeScreen = () => {
|
|
549
|
+
const bridge = scrollbackBridge;
|
|
550
|
+
if (!bridge)
|
|
551
|
+
return;
|
|
552
|
+
try {
|
|
553
|
+
bridge.settleAllNow();
|
|
554
|
+
}
|
|
555
|
+
catch { /* the feed keeps whatever was queued */ }
|
|
556
|
+
let summary = [];
|
|
557
|
+
try {
|
|
558
|
+
summary = bridge.getSummaryRows();
|
|
559
|
+
}
|
|
560
|
+
catch { /* degrade to no summary */ }
|
|
561
|
+
try {
|
|
562
|
+
const bytes = bridge.writer.finalize(summary);
|
|
563
|
+
if (bytes.length > 0)
|
|
564
|
+
process.stdout.write(bytes);
|
|
565
|
+
}
|
|
566
|
+
catch { /* a closed stdout must never turn into a crash on the exit path */ }
|
|
567
|
+
};
|
|
516
568
|
// --- Mouse wheel region routing (mouse-wheel-region-routing §4.2). -------
|
|
517
569
|
//
|
|
518
570
|
// BUILD THE FILTER FIRST, THEN ENABLE REPORTING. The order is invariant I-8
|
|
@@ -553,10 +605,23 @@ function runInteractive(flags, extras = {}) {
|
|
|
553
605
|
// a console this process just repaired.
|
|
554
606
|
const vtInputByNode = supportsWindowsVtInput(process.platform, process.versions.node);
|
|
555
607
|
const vtInputSupported = vtInputByNode || forceVtInputForThisConsole();
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
608
|
+
// THE ONE DECISION POINT for screen mode, mouse, paste, key enhancement and the
|
|
609
|
+
// frame writer (tui-native-terminal-scroll D10). In `app` mode every field below
|
|
610
|
+
// is the pre-feature expression term for term (`terminal-plan.test.ts` pins it).
|
|
611
|
+
const plan = resolveTerminalPlan({
|
|
612
|
+
scrollMode,
|
|
613
|
+
configMouse: config.mouse,
|
|
614
|
+
configMouseSelect: config.mouseSelect,
|
|
615
|
+
configPaste: config.paste,
|
|
616
|
+
configKeyboardEnhancement: config.keyboardEnhancement,
|
|
617
|
+
configDiffRender: config.diffRender,
|
|
618
|
+
stdinIsTTY: !!process.stdin.isTTY,
|
|
619
|
+
stdoutIsTTY: !!process.stdout.isTTY,
|
|
620
|
+
platform: process.platform,
|
|
621
|
+
vtInputSupported,
|
|
622
|
+
});
|
|
623
|
+
const native = plan.scrollMode === 'native';
|
|
624
|
+
const wantMouse = plan.mouseReporting;
|
|
560
625
|
// --- Paste handling (tui-paste-handling section 5.1.1 / D-13 / D-17). ----
|
|
561
626
|
//
|
|
562
627
|
// THE GATE IS WIDER THAN THE MOUSE'S, AND `paste` DEFAULTS TO TRUE. Paste
|
|
@@ -571,7 +636,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
571
636
|
// would flip true: `?1000h`/`?1006h`/`?1002h` written, terminal selection taken
|
|
572
637
|
// away (the ONLY reason anyone passes `--no-mouse`), and a startup notice
|
|
573
638
|
// advising the user about a mode they just disabled.
|
|
574
|
-
const wantPaste =
|
|
639
|
+
const wantPaste = plan.bracketedPaste;
|
|
575
640
|
const pasteBridge = { notify: null };
|
|
576
641
|
// --- Keyboard enhancement (Shift+Enter newline). --------------------------
|
|
577
642
|
//
|
|
@@ -584,9 +649,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
584
649
|
// Elsewhere the push is kitty disambiguate + modifyOtherKeys, which a
|
|
585
650
|
// terminal that implements neither ignores (verified byte-identical on
|
|
586
651
|
// conhost), so no capability gate is possible or needed there.
|
|
587
|
-
const wantEnhancedKeys =
|
|
588
|
-
!!process.stdin.isTTY &&
|
|
589
|
-
(process.platform !== 'win32' || vtInputSupported);
|
|
652
|
+
const wantEnhancedKeys = plan.enhancedKeys;
|
|
590
653
|
const filter = process.stdin.isTTY
|
|
591
654
|
? tryCreateStdinFilter(process.stdin, { mouse: wantMouse, paste: wantPaste, enhancedKeys: wantEnhancedKeys }, (reason) => logger.warn('cli', 'stdin_filter_failed', { reason }), pasteBridge)
|
|
592
655
|
: null;
|
|
@@ -607,6 +670,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
607
670
|
//
|
|
608
671
|
// Selection owns its controller, decoration and notice. Button motion follows
|
|
609
672
|
// mouseOn independently so --no-mouse-select still permits scrollbar dragging.
|
|
673
|
+
// `mouseOn` is already false in native mode, so this needs no `plan.dragSelect` term.
|
|
610
674
|
const wantSelect = mouseOn && config.mouseSelect;
|
|
611
675
|
let mouseCaptured = mouseOn;
|
|
612
676
|
const scrollbar = createScrollbarBridge(() => mouseCaptured);
|
|
@@ -618,59 +682,94 @@ function runInteractive(flags, extras = {}) {
|
|
|
618
682
|
onCopied: null,
|
|
619
683
|
requestRedraw: null,
|
|
620
684
|
};
|
|
621
|
-
|
|
622
|
-
|
|
685
|
+
// Native scroll: a drain + writer pair replaces the full-screen differ. The bridge
|
|
686
|
+
// is the only object shared with the React tree (tui-native-terminal-scroll §6.2).
|
|
687
|
+
let scrollbackBridge;
|
|
688
|
+
if (plan.differ === 'scrollback') {
|
|
689
|
+
const drain = createScrollbackDrain();
|
|
690
|
+
const writer = createScrollbackWriter({
|
|
623
691
|
sync: config.syncOutput && process.env.TERM !== 'dumb',
|
|
624
692
|
rows: () => process.stdout.rows,
|
|
625
|
-
// Rows that fill the line skip `CSI K` so the scrollbar's last column
|
|
626
|
-
// is not erased on pending-wrap terminals (frame-differ I-4).
|
|
627
693
|
cols: () => process.stdout.columns,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// user-visible channel with the TUI mounted — a direct write would
|
|
631
|
-
// itself be the foreign write being reported. Latched on the 0->1
|
|
632
|
-
// edge inside the differ: a notice per fallback would be a repaint
|
|
633
|
-
// storm triggered by a repaint problem (§5.6).
|
|
694
|
+
fullEveryFrame: plan.fullEveryFrame,
|
|
695
|
+
drain,
|
|
634
696
|
onFirstFallback: () => console.warn(FRAME_FALLBACK_NOTICE),
|
|
635
|
-
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
697
|
+
onGuardTrip: () => scrollbackBridge?.onGuardTrip?.(),
|
|
698
|
+
// Deferred: static rows can be queued from inside a React commit, and the
|
|
699
|
+
// repaint writes to the stream Ink is in the middle of writing to.
|
|
700
|
+
requestRepaint: () => { setImmediate(() => frameWriter?.repaint()); },
|
|
701
|
+
});
|
|
702
|
+
scrollbackBridge = createScrollbackBridge({
|
|
703
|
+
writer, drain, requestFlush: () => { setImmediate(() => frameWriter?.repaint()); },
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const frameWriter = scrollbackBridge
|
|
707
|
+
? wrapStdoutForFrames(process.stdout, scrollbackBridge.writer)
|
|
708
|
+
: plan.differ === 'fullscreen'
|
|
709
|
+
? wrapStdoutForFrames(process.stdout, createFrameDiffer({
|
|
710
|
+
sync: config.syncOutput && process.env.TERM !== 'dumb',
|
|
711
|
+
rows: () => process.stdout.rows,
|
|
712
|
+
// Rows that fill the line skip `CSI K` so the scrollbar's last column
|
|
713
|
+
// is not erased on pending-wrap terminals (frame-differ I-4).
|
|
714
|
+
cols: () => process.stdout.columns,
|
|
715
|
+
// The console bridge (`App.tsx`, full-screen only) turns this into
|
|
716
|
+
// `dispatch({type:'notice', level:'warn'})`, which is the only legal
|
|
717
|
+
// user-visible channel with the TUI mounted — a direct write would
|
|
718
|
+
// itself be the foreign write being reported. Latched on the 0->1
|
|
719
|
+
// edge inside the differ: a notice per fallback would be a repaint
|
|
720
|
+
// storm triggered by a repaint problem (§5.6).
|
|
721
|
+
onFirstFallback: () => console.warn(FRAME_FALLBACK_NOTICE),
|
|
722
|
+
// ABSENT ENTIRELY without drag-select, not a no-op function: the
|
|
723
|
+
// differ's identity path then allocates nothing and copies nothing,
|
|
724
|
+
// which is what makes `mouseSelect: false` a statement about the code
|
|
725
|
+
// rather than about how cheap an empty function is.
|
|
726
|
+
...(wantSelect
|
|
727
|
+
? {
|
|
728
|
+
decorate: (lines) => selectionBridge.controller?.decorate(lines) ?? lines,
|
|
729
|
+
// P1-7: a pass-through frame writes the RAW Ink frame verbatim,
|
|
730
|
+
// so the highlight is wiped off the terminal while the
|
|
731
|
+
// controller still believes it is painted. Deferred out of the
|
|
732
|
+
// current stack because this fires from inside `transform`,
|
|
733
|
+
// which is inside Ink's own `stdout.write`.
|
|
734
|
+
onInvalidate: () => {
|
|
735
|
+
const controller = selectionBridge.controller;
|
|
736
|
+
if (controller)
|
|
737
|
+
setImmediate(() => controller.clear());
|
|
738
|
+
},
|
|
739
|
+
}
|
|
740
|
+
: {}),
|
|
741
|
+
}))
|
|
742
|
+
: null;
|
|
656
743
|
// `null` when no writer was built, so `/perf` can say "diff render off" rather
|
|
657
744
|
// than printing zeroes that read like a writer doing nothing (§5.4).
|
|
658
745
|
if (frameWriter)
|
|
659
746
|
setFrameStatsProvider(frameWriter.stats);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
747
|
+
if (scrollbackBridge) {
|
|
748
|
+
const { writer } = scrollbackBridge;
|
|
749
|
+
setScrollbackStatsProvider(() => ({
|
|
750
|
+
...writer.scrollbackStats(), regionRows: writer.regionRows(),
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
// The observer only serves the full-screen scrollbar geometry; native has none.
|
|
754
|
+
const frameObserver = native
|
|
755
|
+
? null
|
|
756
|
+
: createFrameObserver({
|
|
757
|
+
stdout: frameWriter?.stdout ?? process.stdout, terminal: process.stdout, scrollbar,
|
|
758
|
+
});
|
|
663
759
|
const disposeFrameWriter = () => {
|
|
664
|
-
frameObserver
|
|
760
|
+
frameObserver?.dispose();
|
|
665
761
|
pointerRouter?.dispose();
|
|
666
762
|
scrollbar.controller?.dispose();
|
|
667
763
|
if (!frameWriter)
|
|
668
764
|
return;
|
|
669
765
|
setFrameStatsProvider(null);
|
|
766
|
+
setScrollbackStatsProvider(null);
|
|
670
767
|
frameWriter.dispose();
|
|
671
768
|
};
|
|
672
769
|
const replayTranscript = () => {
|
|
673
|
-
|
|
770
|
+
// Native mode never replays: the conversation is already in the scrollback, and
|
|
771
|
+
// a replay would print it twice (D9).
|
|
772
|
+
if (replayed || !config.exitTranscript || native)
|
|
674
773
|
return;
|
|
675
774
|
replayed = true;
|
|
676
775
|
const snapshot = readExitSnapshot();
|
|
@@ -710,17 +809,21 @@ function runInteractive(flags, extras = {}) {
|
|
|
710
809
|
// paste at its default `true` still builds a filter, and passing the handle
|
|
711
810
|
// here would write `?1000h` at a user who disabled the mouse precisely to
|
|
712
811
|
// keep their terminal's own selection.
|
|
713
|
-
screen =
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
812
|
+
screen = plan.altScreen
|
|
813
|
+
? enterAltScreen(process.stdout, {
|
|
814
|
+
mouse: mouseOn,
|
|
815
|
+
motion: mouseOn,
|
|
816
|
+
bracketedPaste: pasteOn,
|
|
817
|
+
})
|
|
818
|
+
// Native scroll: no alternate screen, no mouse (D6). Only DEC 2004.
|
|
819
|
+
: enterNativeScreen(process.stdout, { bracketedPaste: pasteOn });
|
|
718
820
|
// Input-encoding modes unwind FIRST, for the same reason the mouse
|
|
719
821
|
// reporting does inside `screen.restore()`: the terminal must stop
|
|
720
822
|
// sending enhanced encodings before anything else tears the reader
|
|
721
823
|
// down, or a keystroke taken in that instant survives into the shell.
|
|
722
824
|
const restore = () => {
|
|
723
825
|
keyboardEnhancement.restore();
|
|
826
|
+
finalizeNativeScreen();
|
|
724
827
|
screen?.restore();
|
|
725
828
|
};
|
|
726
829
|
// Four idempotent restore paths (§4.4). The signal hook is NOT redundant:
|
|
@@ -838,7 +941,8 @@ function runInteractive(flags, extras = {}) {
|
|
|
838
941
|
interactiveClosed = true;
|
|
839
942
|
logger.error('cli', 'render_failed', { error: String(error) });
|
|
840
943
|
for (const cleanup of [
|
|
841
|
-
() => screen?.restore(), disposeSelection, disposeStdinFilter,
|
|
944
|
+
finalizeNativeScreen, () => screen?.restore(), disposeSelection, disposeStdinFilter,
|
|
945
|
+
disposeFrameWriter,
|
|
842
946
|
() => controller.abort(), () => controller.dispose(), () => updateService?.dispose(),
|
|
843
947
|
() => { process.stdin.pause(); process.stdin.unref?.(); },
|
|
844
948
|
]) {
|
|
@@ -865,7 +969,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
865
969
|
vtInputWarning: vtInputSupported ? undefined : { nodeVersion: process.versions.node }, updateBridge: updateEligible ? updateBridge : undefined,
|
|
866
970
|
// Absent when nothing can refuse a paste, so a `--no-paste` tree is
|
|
867
971
|
// byte-identical to a build without this feature.
|
|
868
|
-
pasteBridge: pasteOn ? pasteBridge : undefined, terminal: terminalBridge, session: identity, sessionBridge: sessionBridge, ...(startup.initialEntries.length > 0 ? { initialEntries: startup.initialEntries } : {}), ...(startup.notice ? { sessionNotice: startup.notice } : {}) }),
|
|
972
|
+
pasteBridge: pasteOn ? pasteBridge : undefined, terminal: terminalBridge, ...(scrollbackBridge ? { scrollMode: plan.scrollMode, scrollback: scrollbackBridge } : {}), session: identity, sessionBridge: sessionBridge, ...(startup.initialEntries.length > 0 ? { initialEntries: startup.initialEntries } : {}), ...(startup.notice ? { sessionNotice: startup.notice } : {}) }),
|
|
869
973
|
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
870
974
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
871
975
|
{
|
|
@@ -874,7 +978,7 @@ function runInteractive(flags, extras = {}) {
|
|
|
874
978
|
// I-5: with mouse support off the REAL stdin is handed over unwrapped, so
|
|
875
979
|
// every existing path is provably unchanged.
|
|
876
980
|
stdin: filter?.stdin ?? process.stdin,
|
|
877
|
-
stdout: frameObserver.stdout,
|
|
981
|
+
stdout: frameObserver?.stdout ?? frameWriter?.stdout ?? process.stdout,
|
|
878
982
|
}, caps);
|
|
879
983
|
}
|
|
880
984
|
catch (error) {
|
|
@@ -955,6 +1059,9 @@ function runInteractive(flags, extras = {}) {
|
|
|
955
1059
|
// Also cover a successful immediate exit when launched via dist/cli.js,
|
|
956
1060
|
// which bypasses the launcher's code-zero boot-guard backstop.
|
|
957
1061
|
markBootHealthy(VERSION);
|
|
1062
|
+
// Native scroll: finalize BEFORE the screen handle (the writer erases the live
|
|
1063
|
+
// region and prints the summary; the handle then shows the cursor again).
|
|
1064
|
+
finalizeNativeScreen();
|
|
958
1065
|
screen?.restore();
|
|
959
1066
|
// BEFORE `disposeStdinFilter()`, which clears the listener set it subscribed to,
|
|
960
1067
|
// and after `restore()` for the reason that call records: the terminal must
|
|
@@ -1159,6 +1266,8 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1159
1266
|
// tui-input-flicker-fix §5.1. Listed here AND cased in the switch below, for
|
|
1160
1267
|
// the reason `transcriptRetain` records three lines up.
|
|
1161
1268
|
'diffRender',
|
|
1269
|
+
// tui-native-terminal-scroll. Listed here AND cased in the switch below.
|
|
1270
|
+
'scrollMode',
|
|
1162
1271
|
'syncOutput',
|
|
1163
1272
|
// Without these two, `density` and `hints` would be settable only as one-shot
|
|
1164
1273
|
// flags and never persist -- inconsistent with every other config field.
|
|
@@ -1401,6 +1510,9 @@ function runConfigSetValue(key, value) {
|
|
|
1401
1510
|
case 'diffRender':
|
|
1402
1511
|
patch.diffRender = value === 'true' || value === '1';
|
|
1403
1512
|
break;
|
|
1513
|
+
case 'scrollMode':
|
|
1514
|
+
patch.scrollMode = clampScrollMode(value, DEFAULT_SCROLL_MODE);
|
|
1515
|
+
break;
|
|
1404
1516
|
case 'syncOutput':
|
|
1405
1517
|
patch.syncOutput = value === 'true' || value === '1';
|
|
1406
1518
|
break;
|
|
@@ -1790,6 +1902,7 @@ function buildProgram() {
|
|
|
1790
1902
|
// one documented escape hatch for a mis-rendering terminal into a no-op.
|
|
1791
1903
|
.option('--diff-render', 'Repaint only changed rows (default, full-screen only)')
|
|
1792
1904
|
.option('--no-diff-render', 'Repaint the whole frame every time (pre-fix behavior)')
|
|
1905
|
+
.option('--scroll-mode <mode>', 'Scrolling: native (terminal scrollback, default) or app (full-screen)')
|
|
1793
1906
|
.option('--sync-output', 'Ask the terminal to present each repaint atomically (the default)')
|
|
1794
1907
|
.option('--no-sync-output', 'Do not emit DEC 2026 begin/end synchronized update')
|
|
1795
1908
|
.option('--no-color', 'Disable ANSI color')
|
|
@@ -1122,6 +1122,10 @@ const COMMANDS = [
|
|
|
1122
1122
|
name: 'mouse',
|
|
1123
1123
|
description: 'Release the mouse to the terminal, or take it back',
|
|
1124
1124
|
run: (ctx) => {
|
|
1125
|
+
if (ctx.nativeScroll) {
|
|
1126
|
+
ctx.notify('info', 'Mouse capture is not used in native scroll mode; your terminal handles scrolling and selection.');
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1125
1129
|
const port = ctx.mouse;
|
|
1126
1130
|
if (!port) {
|
|
1127
1131
|
// Inline mode, `--no-mouse`, a non-TTY, or a console that cannot deliver
|
package/dist/commands/perf.d.ts
CHANGED
|
@@ -34,7 +34,8 @@ export interface PerfSnapshot {
|
|
|
34
34
|
heightsEstimated: number;
|
|
35
35
|
cols: number;
|
|
36
36
|
/** Geometry. */
|
|
37
|
-
mode: 'fullscreen';
|
|
37
|
+
mode: 'fullscreen' | 'native';
|
|
38
|
+
/** Full-screen: the transcript viewport. Native: the live region height. */
|
|
38
39
|
viewportRows: number;
|
|
39
40
|
offset: number;
|
|
40
41
|
/** Right-edge scrollbar diagnostics; read lazily so it reflects the live bridge. */
|
|
@@ -74,6 +75,29 @@ export declare function setPerfResetHook(fn: (() => void) | null): void;
|
|
|
74
75
|
/** Called by `cli.tsx` after building the writer. Passing `null` detaches. */
|
|
75
76
|
export declare function setFrameStatsProvider(fn: (() => FrameWriterStats) | null): void;
|
|
76
77
|
export declare function readFrameStats(): FrameWriterStats | null;
|
|
78
|
+
/**
|
|
79
|
+
* Native-scroll writer counters (tui-native-terminal-scroll §5.1). A third
|
|
80
|
+
* module-level slot for the reason the one above records: the writer lives outside
|
|
81
|
+
* React. `guardHitsOutsideResize > 0` on a session with no resize is the defect
|
|
82
|
+
* signal (a live region reached the terminal height); `clippedRows > 0` means Ink
|
|
83
|
+
* and the terminal disagreed about a character's width (I-S13).
|
|
84
|
+
*/
|
|
85
|
+
export interface ScrollbackPerf {
|
|
86
|
+
regionRows: number;
|
|
87
|
+
framesSeed: number;
|
|
88
|
+
framesDiff: number;
|
|
89
|
+
framesFull: number;
|
|
90
|
+
staticRowsWritten: number;
|
|
91
|
+
elidedEntries: number;
|
|
92
|
+
guardHits: number;
|
|
93
|
+
guardHitsOutsideResize: number;
|
|
94
|
+
clippedRows: number;
|
|
95
|
+
fallbacks: number;
|
|
96
|
+
resizes: number;
|
|
97
|
+
repaints: number;
|
|
98
|
+
}
|
|
99
|
+
/** Called by `cli.tsx` in native mode. Passing `null` detaches. */
|
|
100
|
+
export declare function setScrollbackStatsProvider(fn: (() => ScrollbackPerf) | null): void;
|
|
77
101
|
/** Format a snapshot as the six-line block documented in §5.4 (plus the scrollbar line). */
|
|
78
102
|
export declare function formatPerfReport(snap: PerfSnapshot): string;
|
|
79
103
|
export declare const perfCommand: SlashCommand;
|
package/dist/commands/perf.js
CHANGED
|
@@ -68,6 +68,24 @@ export function setFrameStatsProvider(fn) {
|
|
|
68
68
|
export function readFrameStats() {
|
|
69
69
|
return frameStats ? frameStats() : null;
|
|
70
70
|
}
|
|
71
|
+
let scrollbackStats = null;
|
|
72
|
+
/** Called by `cli.tsx` in native mode. Passing `null` detaches. */
|
|
73
|
+
export function setScrollbackStatsProvider(fn) {
|
|
74
|
+
scrollbackStats = fn;
|
|
75
|
+
}
|
|
76
|
+
function formatScrollbackLine() {
|
|
77
|
+
const stats = scrollbackStats?.();
|
|
78
|
+
if (!stats)
|
|
79
|
+
return [];
|
|
80
|
+
return [
|
|
81
|
+
`scrollback region ${stats.regionRows} rows - batches seed ${stats.framesSeed}, ` +
|
|
82
|
+
`diff ${stats.framesDiff}, full ${stats.framesFull}, repaint ${stats.repaints} - ` +
|
|
83
|
+
`static rows ${stats.staticRowsWritten} - elided ${stats.elidedEntries}`,
|
|
84
|
+
` guardHits ${stats.guardHits} (outside resize ${stats.guardHitsOutsideResize}) - ` +
|
|
85
|
+
`clippedRows ${stats.clippedRows} - fallbacks ${stats.fallbacks} - ` +
|
|
86
|
+
`resizes ${stats.resizes}`,
|
|
87
|
+
];
|
|
88
|
+
}
|
|
71
89
|
function mb(bytes) {
|
|
72
90
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
73
91
|
}
|
|
@@ -107,7 +125,10 @@ export function formatPerfReport(snap) {
|
|
|
107
125
|
const caches = renderCacheStats();
|
|
108
126
|
const eco = snap.rung > 0 ? ' - eco' : '';
|
|
109
127
|
const governor = snap.governorEnabled ? '' : ' - governor off';
|
|
110
|
-
const
|
|
128
|
+
const native = snap.mode === 'native';
|
|
129
|
+
const scrollbar = native
|
|
130
|
+
? ['scrollbar n/a (native scroll)']
|
|
131
|
+
: snap.scrollbar ? [formatScrollbarLine(snap.scrollbar)] : [];
|
|
111
132
|
return [
|
|
112
133
|
`render rung ${snap.rung} - interval ${snap.intervalMs}ms - last commit ` +
|
|
113
134
|
`${Math.round(snap.lastCommitMs)}ms${eco}${governor}`,
|
|
@@ -118,8 +139,11 @@ export function formatPerfReport(snap) {
|
|
|
118
139
|
`caches md ${caches.markdownEntries} (${mb(caches.markdownBytes)}) - ` +
|
|
119
140
|
`hl ${caches.highlightEntries} (${mb(caches.highlightBytes)}) - ` +
|
|
120
141
|
`lines ${caches.lineEntries}`,
|
|
121
|
-
|
|
142
|
+
native
|
|
143
|
+
? `mode native - live region ${snap.viewportRows} rows`
|
|
144
|
+
: `mode ${snap.mode} - viewport ${snap.viewportRows} rows - offset ${snap.offset}`,
|
|
122
145
|
formatWriterLine(),
|
|
146
|
+
...formatScrollbackLine(),
|
|
123
147
|
...scrollbar,
|
|
124
148
|
].join('\n');
|
|
125
149
|
}
|
|
@@ -100,6 +100,12 @@ export interface CommandContext {
|
|
|
100
100
|
* missing, which is the same reason `update` above it is optional.
|
|
101
101
|
*/
|
|
102
102
|
mouse?: MouseCommandPort;
|
|
103
|
+
/**
|
|
104
|
+
* True in native-scroll mode (tui-native-terminal-scroll): the terminal owns
|
|
105
|
+
* scrolling and selection, so there is no capture to toggle. Lets `/mouse` say so
|
|
106
|
+
* instead of the generic "not captured" answer.
|
|
107
|
+
*/
|
|
108
|
+
nativeScroll?: boolean;
|
|
103
109
|
/**
|
|
104
110
|
* The foreign-write door, for `/copy`'s OSC 52 half (§4.4.5).
|
|
105
111
|
*
|
package/dist/config/env.js
CHANGED
|
@@ -392,6 +392,10 @@ export function readEnvConfig() {
|
|
|
392
392
|
partial.diffRender =
|
|
393
393
|
diffRender === '1' || diffRender === 'true' || diffRender === 'on' || diffRender === 'yes';
|
|
394
394
|
}
|
|
395
|
+
// Invalid values are ignored, like every other ARAGON_* key.
|
|
396
|
+
const scrollMode = process.env.ARAGON_SCROLL_MODE?.trim().toLowerCase();
|
|
397
|
+
if (scrollMode === 'native' || scrollMode === 'app')
|
|
398
|
+
partial.scrollMode = scrollMode;
|
|
395
399
|
const syncOutput = process.env.ARAGON_SYNC_OUTPUT?.trim().toLowerCase();
|
|
396
400
|
if (syncOutput !== undefined && syncOutput.length > 0) {
|
|
397
401
|
partial.syncOutput =
|
package/dist/config/load.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* layers, `idleTimeout` is re-derived to be strictly ≥ `toolTimeout` so a
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
|
-
import { type CliConfig } from './schema.js';
|
|
8
|
+
import { type CliConfig, type ScrollMode } from './schema.js';
|
|
9
9
|
import type { ModelRole } from './model-profiles.js';
|
|
10
10
|
export interface CliFlags {
|
|
11
11
|
provider?: string;
|
|
@@ -149,6 +149,8 @@ export interface CliFlags {
|
|
|
149
149
|
* all — i.e. the documented escape hatch would quietly un-set itself.
|
|
150
150
|
*/
|
|
151
151
|
diffRender?: boolean;
|
|
152
|
+
/** `--scroll-mode <native|app>`; already validated by `cli.tsx` (exit 2 on junk). */
|
|
153
|
+
scrollMode?: ScrollMode;
|
|
152
154
|
/** `--sync-output` ⇒ true, `--no-sync-output` ⇒ false, absent ⇒ undefined (§5.2). */
|
|
153
155
|
syncOutput?: boolean;
|
|
154
156
|
/**
|
package/dist/config/load.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import process from 'node:process';
|
|
9
|
-
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, legacyTeamCeilingPatch, parseThresholdInput, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, DEFAULT_SCROLL_MODE, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollMode, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, legacyTeamCeilingPatch, parseThresholdInput, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile, updatePersistedConfig } from './store.js';
|
|
12
12
|
import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey, } from './model-profile-resolution.js';
|
|
@@ -334,6 +334,16 @@ function resolveDiffRender(flags, env, file) {
|
|
|
334
334
|
return file.diffRender;
|
|
335
335
|
return DEFAULT_CONFIG.diffRender;
|
|
336
336
|
}
|
|
337
|
+
/** flags > env > file > default, like `resolveDiffRender` (tui-native-terminal-scroll §6.1). */
|
|
338
|
+
function resolveScrollMode(flags, env, file) {
|
|
339
|
+
if (flags.scrollMode !== undefined)
|
|
340
|
+
return flags.scrollMode;
|
|
341
|
+
if (env.scrollMode !== undefined)
|
|
342
|
+
return env.scrollMode;
|
|
343
|
+
if (file.scrollMode !== undefined)
|
|
344
|
+
return clampScrollMode(file.scrollMode, DEFAULT_SCROLL_MODE);
|
|
345
|
+
return DEFAULT_SCROLL_MODE;
|
|
346
|
+
}
|
|
337
347
|
/** Same ladder for the DEC 2026 envelope (§5.1). */
|
|
338
348
|
function resolveSyncOutput(flags, env, file) {
|
|
339
349
|
if (flags.syncOutput !== undefined)
|
|
@@ -638,6 +648,7 @@ export function loadConfig(flags = {}, options = {}) {
|
|
|
638
648
|
renderGovernor: resolveRenderGovernor(flags, env.partial, file),
|
|
639
649
|
maxRenderIntervalMs: clampMaxRenderInterval(pick(flags.maxRenderInterval, env.partial.maxRenderIntervalMs, file.maxRenderIntervalMs), DEFAULT_CONFIG.maxRenderIntervalMs),
|
|
640
650
|
diffRender: resolveDiffRender(flags, env.partial, file),
|
|
651
|
+
scrollMode: resolveScrollMode(flags, env.partial, file),
|
|
641
652
|
syncOutput: resolveSyncOutput(flags, env.partial, file),
|
|
642
653
|
confirmTools,
|
|
643
654
|
toolTimeoutMs,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -39,6 +39,17 @@ export declare function isThemeName(v: unknown): v is ThemeName;
|
|
|
39
39
|
* and would not match `clampThinkingLevel` / `clampTranscriptWindow`.
|
|
40
40
|
*/
|
|
41
41
|
export declare function clampTheme(v: unknown, fallback: ThemeName): ThemeName;
|
|
42
|
+
export declare const SCROLL_MODES: readonly ["native", "app"];
|
|
43
|
+
/**
|
|
44
|
+
* How the interactive TUI scrolls (tui-native-terminal-scroll D1).
|
|
45
|
+
*
|
|
46
|
+
* `native` prints settled conversation into the host terminal's own scrollback and
|
|
47
|
+
* keeps only a bounded live region on screen; `app` is the original full-screen
|
|
48
|
+
* alternate-screen layout with application-owned scrolling.
|
|
49
|
+
*/
|
|
50
|
+
export type ScrollMode = (typeof SCROLL_MODES)[number];
|
|
51
|
+
export declare const DEFAULT_SCROLL_MODE: ScrollMode;
|
|
52
|
+
export declare function clampScrollMode(v: unknown, fallback: ScrollMode): ScrollMode;
|
|
42
53
|
export declare const DENSITY_MODES: readonly ["comfortable", "compact"];
|
|
43
54
|
export type DensityMode = (typeof DENSITY_MODES)[number];
|
|
44
55
|
export declare function clampDensity(v: unknown, fallback: DensityMode): DensityMode;
|
|
@@ -864,6 +875,8 @@ export interface PersistedConfig {
|
|
|
864
875
|
maxRenderIntervalMs: number;
|
|
865
876
|
/** Repaint only the frame rows that changed (tui-input-flicker-fix §5.1). */
|
|
866
877
|
diffRender: boolean;
|
|
878
|
+
/** Native terminal scrolling vs the full-screen app layout (tui-native-terminal-scroll D1). */
|
|
879
|
+
scrollMode: ScrollMode;
|
|
867
880
|
/** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
|
|
868
881
|
syncOutput: boolean;
|
|
869
882
|
confirmTools: boolean;
|
|
@@ -1068,6 +1081,20 @@ export interface CliConfig {
|
|
|
1068
1081
|
* with no compile error and no runtime error.
|
|
1069
1082
|
*/
|
|
1070
1083
|
diffRender: boolean;
|
|
1084
|
+
/**
|
|
1085
|
+
* Native terminal scrolling vs the full-screen app layout
|
|
1086
|
+
* (tui-native-terminal-scroll D1).
|
|
1087
|
+
*
|
|
1088
|
+
* IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
|
|
1089
|
+
* `diffRender` above records: `runInteractive` reads `config.scrollMode`, where
|
|
1090
|
+
* `config` is a `CliConfig`, so a key added to the persisted shape alone would
|
|
1091
|
+
* round-trip through `config set` and never reach the screen plan.
|
|
1092
|
+
*
|
|
1093
|
+
* OPTIONAL ON THIS SHAPE ONLY so the many hand-built `CliConfig` fixtures in the
|
|
1094
|
+
* test suite stay valid without edits (I-S9); `loadConfig` always sets it and
|
|
1095
|
+
* the one reader (`runInteractive`) falls back to `DEFAULT_SCROLL_MODE`.
|
|
1096
|
+
*/
|
|
1097
|
+
scrollMode?: ScrollMode;
|
|
1071
1098
|
/** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
|
|
1072
1099
|
syncOutput: boolean;
|
|
1073
1100
|
confirmTools: boolean;
|
package/dist/config/schema.js
CHANGED
|
@@ -69,6 +69,14 @@ export function clampTheme(v, fallback) {
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
70
|
// Density / hints (v0.4.0)
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
|
+
export const SCROLL_MODES = ['native', 'app'];
|
|
73
|
+
export const DEFAULT_SCROLL_MODE = 'native';
|
|
74
|
+
export function clampScrollMode(v, fallback) {
|
|
75
|
+
if (typeof v !== 'string')
|
|
76
|
+
return fallback;
|
|
77
|
+
const normalized = v.trim().toLowerCase();
|
|
78
|
+
return normalized === 'native' || normalized === 'app' ? normalized : fallback;
|
|
79
|
+
}
|
|
72
80
|
export const DENSITY_MODES = ['comfortable', 'compact'];
|
|
73
81
|
export function clampDensity(v, fallback) {
|
|
74
82
|
return v === 'comfortable' || v === 'compact' ? v : fallback;
|
|
@@ -970,6 +978,7 @@ export const DEFAULT_CONFIG = {
|
|
|
970
978
|
renderGovernor: DEFAULT_RENDER_GOVERNOR,
|
|
971
979
|
maxRenderIntervalMs: DEFAULT_MAX_RENDER_INTERVAL_MS,
|
|
972
980
|
diffRender: DEFAULT_DIFF_RENDER,
|
|
981
|
+
scrollMode: DEFAULT_SCROLL_MODE,
|
|
973
982
|
syncOutput: DEFAULT_SYNC_OUTPUT,
|
|
974
983
|
confirmTools: false,
|
|
975
984
|
toolTimeoutMs: DEFAULT_TOOL_TIMEOUT_MS,
|