@aragon-agent/cli 0.6.29 → 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 -2103
- package/README.md +81 -14
- package/dist/agent/project-guidance-prompt.js +20 -20
- 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/ui/App.d.ts +11 -1
- package/dist/ui/App.js +122 -62
- package/dist/ui/ScrollbackShell.d.ts +28 -0
- package/dist/ui/ScrollbackShell.js +10 -0
- package/dist/ui/frame-differ.d.ts +12 -0
- 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/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/README.md
CHANGED
|
@@ -14,6 +14,12 @@ steer / configure the session — all without leaving the terminal.
|
|
|
14
14
|
|
|
15
15
|
Node **≥ 18**, any platform.
|
|
16
16
|
|
|
17
|
+
**The default scrolling mode (`native`, see [Scrolling](#scrolling)) needs no mouse
|
|
18
|
+
support at all**: the conversation is printed into your terminal's own scrollback,
|
|
19
|
+
so the wheel, scrollbar, selection and search are the terminal's. The Windows
|
|
20
|
+
Node-version notes below therefore only matter for `Shift+Tab` and for the optional
|
|
21
|
+
full-screen mode (`--scroll-mode app`), where the application owns the mouse.
|
|
22
|
+
|
|
17
23
|
**On Windows with Node < 22.17.0 (or 24.0/24.1), aragon turns the missing
|
|
18
24
|
console flag on itself at startup**, which restores `Shift+Tab` on most
|
|
19
25
|
machines. Where that is blocked — a locked-down PowerShell, an old console
|
|
@@ -367,6 +373,7 @@ below works there too, in either position — `aragon --model m exec "…"` and
|
|
|
367
373
|
| `--thinking <level>` | `off\|minimal\|low\|medium\|high\|xhigh` |
|
|
368
374
|
| `--max-tokens <n\|auto>` | Output token cap: a number, or `auto` for the model's own ceiling (default `64000`) |
|
|
369
375
|
| `--cwd <dir>` | Working directory for tools |
|
|
376
|
+
| `--scroll-mode <native\|app>` | `native` (default): the terminal scrolls, selects and searches the conversation; `app`: full-screen layout with application-owned scrolling. See [Scrolling](#scrolling) |
|
|
370
377
|
| `--confirm` | Confirm each mutating tool call |
|
|
371
378
|
| `--tool-timeout <ms>` | Per-tool executor ceiling (default 180000) |
|
|
372
379
|
| `--idle-timeout <ms>` | Watchdog idle timeout (auto-raised to ≥ tool-timeout + 30s) |
|
|
@@ -529,11 +536,12 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
529
536
|
| `Ctrl+L` | **Branch this session into a new terminal**: opens a PowerShell (Windows) or bash (macOS/Linux) window at the current `/cwd` and runs `aragon --resume <id>` there, so the branch gets its own id and the session you branched from is untouched. Works while the agent is running - the child inherits the last settled turn. Same code path as `/branch` |
|
|
530
537
|
| `Ctrl+R` | Redraw the frame (moved here from `Ctrl+L`, which is now the branch key) |
|
|
531
538
|
| `Ctrl+T` | Show / hide thinking (**off by default**) for the entire session |
|
|
532
|
-
| `Ctrl+O` | Expand / collapse the most recent tool, team, compaction or **service** card — including a diff or a service's log tail |
|
|
533
|
-
| `PgUp` / `PgDn
|
|
534
|
-
| `
|
|
535
|
-
|
|
|
536
|
-
|
|
|
539
|
+
| `Ctrl+O` | Expand / collapse the most recent tool, team, compaction or **service** card — including a diff or a service's log tail. In native scroll mode printed rows cannot change, so it **prints an expanded copy** below instead |
|
|
540
|
+
| Terminal scrolling (native mode, the default) | The wheel, the scrollbar, `Shift+PgUp` / `Shift+PgDn`, text selection and in-terminal search are all your terminal's own — see [Scrolling](#scrolling) |
|
|
541
|
+
| `PgUp` / `PgDn` | Page through an open overlay; in full-screen mode also scroll the transcript a page |
|
|
542
|
+
| `Shift+↑` / `Shift+↓` | Scroll the transcript a line (full-screen mode only) |
|
|
543
|
+
| Mouse wheel | Full-screen mode: scroll the transcript (or the open overlay). On Windows needs Node ≥ 22.17 — see [Requirements](#requirements) |
|
|
544
|
+
| `Shift`+wheel | Full-screen mode: scroll the transcript a page |
|
|
537
545
|
| `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Cursor to line start / end |
|
|
538
546
|
| `Alt/Ctrl+←` / `→` | Word-wise cursor jump |
|
|
539
547
|
| `Ctrl+W`, `Alt+Backspace` | Delete the previous word |
|
|
@@ -542,6 +550,10 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
|
|
|
542
550
|
| Paste | Inserted up to 6 lines / 400 chars; larger collapses to `[Pasted text #1 +15 lines]`, which deletes as one unit and expands in full when you send. Escape sequences are removed as sequences (so a coloured log arrives as its text, not as `[38;2;18;52;86m`), and a TAB is kept in the payload but laid out at the terminal's 8-column tab stops, so the draft and the echoed message line up. `--no-paste` restores the pre-0.6.3 behaviour |
|
|
543
551
|
| `?` | Open help (empty input) |
|
|
544
552
|
|
|
553
|
+
Everything from here to the end of this section describes **full-screen mode**
|
|
554
|
+
(`--scroll-mode app`). In the default native mode the application does not capture
|
|
555
|
+
the mouse, so none of it applies.
|
|
556
|
+
|
|
545
557
|
In **full-screen mode**, PgUp/PgDn, Shift+Up/Down and the right-edge scrollbar
|
|
546
558
|
scroll the transcript and its team/queue panels. The input stays fixed and visible.
|
|
547
559
|
Typing or submitting returns the transcript to the newest output without losing the draft.
|
|
@@ -762,7 +774,60 @@ multi-region edit it is strictly smaller, because a change at line 40 and one at
|
|
|
762
774
|
line 900 are now two small hunks instead of 860 removed rows followed by 860
|
|
763
775
|
near-identical added ones.
|
|
764
776
|
|
|
765
|
-
##
|
|
777
|
+
## Scrolling
|
|
778
|
+
|
|
779
|
+
`aragon` has two display modes, chosen by `scrollMode` (`--scroll-mode <native|app>`,
|
|
780
|
+
`ARAGON_SCROLL_MODE`, `aragon config set scrollMode app`; flags > env > file > default).
|
|
781
|
+
|
|
782
|
+
### Native scroll mode (default)
|
|
783
|
+
|
|
784
|
+
The conversation lives in **your terminal's own scrollback**, exactly like the output
|
|
785
|
+
of any other command. Finished messages are printed once, above a small *live
|
|
786
|
+
region* at the bottom of the screen (the streaming preview, the input box and the
|
|
787
|
+
status line). Scrolling, selecting and searching are your terminal's job, on every
|
|
788
|
+
platform: PowerShell, `cmd`, Windows Terminal, VS Code, `bash`, `zsh`, `tmux`,
|
|
789
|
+
iTerm2, Terminal.app, gnome-terminal and friends.
|
|
790
|
+
|
|
791
|
+
- **Wheel, scrollbar, `Shift+PgUp` / `Shift+PgDn`, touchpad inertia** — the terminal's.
|
|
792
|
+
`aragon` does not enter the alternate screen and does not turn on mouse reporting,
|
|
793
|
+
so a Windows console needs no particular Node version for this.
|
|
794
|
+
- **Select and copy with the terminal's own selection** (and its right-click menu,
|
|
795
|
+
double-click-to-select-word, clickable URLs). `/copy` still copies the last answer
|
|
796
|
+
through OSC 52.
|
|
797
|
+
- **Search with the terminal's search** (Windows Terminal `Ctrl+Shift+F`, iTerm2 / Terminal.app `Cmd+F`).
|
|
798
|
+
Under `tmux`, scrolling is tmux's copy mode (`prefix [`).
|
|
799
|
+
- **Your earlier shell output is still above the conversation**, and the conversation
|
|
800
|
+
is still there after you exit. Exiting prints a short summary (session, model,
|
|
801
|
+
time, usage, and the `aragon --resume <id>` line) instead of replaying the
|
|
802
|
+
transcript, because the transcript is already in your scrollback — `exitTranscript`
|
|
803
|
+
is ignored in this mode.
|
|
804
|
+
- **While a reply streams**, the last few lines show in the live region; when the
|
|
805
|
+
message is final it is printed in its formatted shape and the preview disappears
|
|
806
|
+
(a small, intentional "settle" jump). Scrolling up to read earlier output while a
|
|
807
|
+
reply streams is safe; how the viewport behaves when new output arrives is your
|
|
808
|
+
terminal's own "scroll on output" setting.
|
|
809
|
+
- **Printed rows are final.** They are wrapped to the width at which they were
|
|
810
|
+
printed and are never rewritten, so a later window resize does not re-wrap them,
|
|
811
|
+
`Ctrl+T` (thinking) and `/theme` affect only *new* messages, and `Ctrl+O` prints an
|
|
812
|
+
expanded copy of a tool card instead of expanding the old one. `/clear` prints a
|
|
813
|
+
`---- conversation cleared ----` separator; it cannot erase scrollback.
|
|
814
|
+
- **Known limits.** The input box sits directly under the last printed line, so it
|
|
815
|
+
floats mid-screen when the live region shrinks (closing an overlay, a short draft)
|
|
816
|
+
until the next message pushes it down. Narrowing the window of a terminal that
|
|
817
|
+
re-wraps old content can leave a line or two of residue above the input; it never
|
|
818
|
+
loses history. `Ctrl+G`, `/help` and the model picker open as overlays inside the
|
|
819
|
+
live region; the TODO side panel and team panel are not drawn (the status line
|
|
820
|
+
carries `todo` and `team` chips, and `/queue` shows queued messages).
|
|
821
|
+
- **If something looks wrong**, `/perf` has a `scrollback` line (`guardHits`,
|
|
822
|
+
`clippedRows`, `fallbacks`). The one-step escape hatch is
|
|
823
|
+
`aragon config set scrollMode app`, or `--scroll-mode app` for a single run.
|
|
824
|
+
|
|
825
|
+
Requirements are minimal: a terminal that honours cursor-up, erase-down and SGR
|
|
826
|
+
(every terminal from the last 20 years, including the Windows console host).
|
|
827
|
+
`aragon` writes no absolute cursor addressing, no scroll regions, and never the
|
|
828
|
+
sequences that clear scrollback.
|
|
829
|
+
|
|
830
|
+
### Full-screen mode (`--scroll-mode app`)
|
|
766
831
|
|
|
767
832
|
`aragon` takes over the terminal's **alternate screen buffer** — the mechanism
|
|
768
833
|
`vim`, `htop`, and `lazygit` use. Two consequences worth knowing:
|
|
@@ -776,13 +841,14 @@ On exit the session is replayed into the normal buffer as plain text so the
|
|
|
776
841
|
conversation survives leaving the screen (`--no-exit-transcript` opts out;
|
|
777
842
|
`/save` still exports the full JSON).
|
|
778
843
|
|
|
779
|
-
Interactive sessions
|
|
780
|
-
|
|
781
|
-
|
|
844
|
+
Interactive sessions use the TUI in either mode, including under `TERM=dumb` or CI.
|
|
845
|
+
Windows smaller than 40 columns or 12 rows show a size notice and recover when
|
|
846
|
+
enlarged, preserving the session and draft, in both modes.
|
|
782
847
|
|
|
783
|
-
The layout switches `--fullscreen`, `--no-fullscreen`, `ARAGON_FULLSCREEN` and
|
|
784
|
-
`fullscreen` configuration have been removed
|
|
785
|
-
old config fields and environment variables are ignored.
|
|
848
|
+
The old layout switches `--fullscreen`, `--no-fullscreen`, `ARAGON_FULLSCREEN` and
|
|
849
|
+
`fullscreen` configuration have been removed and stay removed: old command-line
|
|
850
|
+
flags are errors; old config fields and environment variables are ignored. Use
|
|
851
|
+
`--scroll-mode` / `scrollMode` instead.
|
|
786
852
|
|
|
787
853
|
Interactive screens require both stdin and stdout to be TTYs. `aragon config`
|
|
788
854
|
without a TTY exits with code 2; use `aragon config list` or `aragon config set`.
|
|
@@ -2194,7 +2260,7 @@ env / `.env` → CLI flags**.
|
|
|
2194
2260
|
`ARAGON_SKILLS_DISABLED` (comma-separated names), `ARAGON_LOG_LEVEL`,
|
|
2195
2261
|
`ARAGON_LOG_FILE` (`0` disables), `ARAGON_LOG_DIR`,
|
|
2196
2262
|
`ARAGON_TRANSCRIPT_RETAIN`, `ARAGON_RENDER_GOVERNOR` (`0` disables),
|
|
2197
|
-
`ARAGON_MAX_RENDER_INTERVAL_MS
|
|
2263
|
+
`ARAGON_MAX_RENDER_INTERVAL_MS`, `ARAGON_SCROLL_MODE` (`native` / `app`).
|
|
2198
2264
|
`ARAGON_HOME` is the exception that **cannot** come from `.env`: it is read
|
|
2199
2265
|
before any `.env` is loaded. See [Files & logs](#files--logs).
|
|
2200
2266
|
- **User config file** (`aragon config path`): JSON, written `0600` on POSIX.
|
|
@@ -2211,7 +2277,8 @@ env / `.env` → CLI flags**.
|
|
|
2211
2277
|
| `contextWindow` | `null` | The window the context gauge measures against, clamped to `[8000, 5000000]`. `null` means **auto** — API metadata, then the built-in catalog. Unknown windows show `?`; the internal fallback is 128000. See [The context gauge](#the-context-gauge). |
|
|
2212
2278
|
| `showThinking` | `false` | Draw the reasoning the model returns. `thinkingLevel` is the effort the provider is asked to **spend**; this is whether the terminal **shows** it. Off by default: a settled turn that thought leaves one muted `thought for 12s` row in its place, so nothing is hidden silently. |
|
|
2213
2279
|
| `liveToolOutput` | `true` | Draw up to eight sanitised rows of a **running** tool's output on its card, plus a `no output for Ns` row when the child goes quiet. On by default, unlike `showThinking`: this ADDS the information a long `bash` call otherwise hides, and its cost is bounded by construction — eight rows per call, sixteen calls, whatever the command emits. Turn it off and the card is a single `running` row again, with no store allocated and no recorder attached. |
|
|
2214
|
-
| `
|
|
2280
|
+
| `scrollMode` | `native` | `native` prints the conversation into your terminal's own scrollback and keeps a small live region at the bottom (the terminal scrolls, selects and searches); `app` is the full-screen layout with application-owned scrolling. `--scroll-mode <native\|app>` or `ARAGON_SCROLL_MODE` overrides it for one run (an invalid flag value exits with code 2; an invalid env value is ignored). See [Scrolling](#scrolling). |
|
|
2281
|
+
| `exitTranscript` | `true` | Replay a plain-text session summary after exiting (full-screen mode only; native mode prints a short summary and ignores it). |
|
|
2215
2282
|
| `transcriptWindow` | `1000` | How far back you can scroll, clamped to `[50, 20000]`. Entries beyond it collapse into one line. It is no longer a rendering budget: the viewport is virtualised, so off-screen entries are not laid out at all. |
|
|
2216
2283
|
| `transcriptRetain` | `1000` | Entries kept in memory, clamped to `[200, 20000]` and raised to `transcriptWindow` if the two conflict — with a startup notice saying so, because retaining less than you can scroll to would otherwise make part of the horizon quietly unreachable. Older entries are dropped, counted, and reported by `/perf` and by the exit replay. |
|
|
2217
2284
|
| `renderGovernor` | `true` | Widen the streaming frame interval when a frame is genuinely expensive. See [Render performance](#render-performance). |
|
|
@@ -5,20 +5,20 @@ Version: ${PROJECT_GUIDANCE_BLOCK_VERSION}
|
|
|
5
5
|
Ground workspace tasks in existing project guidance before choosing an
|
|
6
6
|
implementation approach, writing a detailed plan, or changing files. A brief
|
|
7
7
|
progress update or an initial investigation TODO may come first. Skip this
|
|
8
|
-
discovery for conversation or tasks fully answerable from supplied content.
|
|
9
|
-
|
|
10
|
-
Before using tools for a workspace task, follow this checklist in order.
|
|
11
|
-
It also applies to Plan research and read-only subagent analysis:
|
|
12
|
-
1. Inspect the immediate cwd listing, or reuse a current one in context.
|
|
13
|
-
2. Read EVERY applicable guidance file in that listing, including both
|
|
14
|
-
AGENTS.md and CLAUDE.md when present; neither substitutes for the other.
|
|
15
|
-
3. Read the discovered index overview/navigation before searching or reading
|
|
16
|
-
implementation files, even if their paths are already known.
|
|
17
|
-
4. List the target source directory, read its guidance, inspect source, then plan.
|
|
18
|
-
Do not batch source reads or source searches with pending guidance/index
|
|
19
|
-
discovery. Before a source tool call, check that no discovered applicable
|
|
20
|
-
guidance entry is still unread; if unavailable, disclose it and use fallback.
|
|
21
|
-
Searching within a known guidance or index file is part of discovery.
|
|
8
|
+
discovery for conversation or tasks fully answerable from supplied content.
|
|
9
|
+
|
|
10
|
+
Before using tools for a workspace task, follow this checklist in order.
|
|
11
|
+
It also applies to Plan research and read-only subagent analysis:
|
|
12
|
+
1. Inspect the immediate cwd listing, or reuse a current one in context.
|
|
13
|
+
2. Read EVERY applicable guidance file in that listing, including both
|
|
14
|
+
AGENTS.md and CLAUDE.md when present; neither substitutes for the other.
|
|
15
|
+
3. Read the discovered index overview/navigation before searching or reading
|
|
16
|
+
implementation files, even if their paths are already known.
|
|
17
|
+
4. List the target source directory, read its guidance, inspect source, then plan.
|
|
18
|
+
Do not batch source reads or source searches with pending guidance/index
|
|
19
|
+
discovery. Before a source tool call, check that no discovered applicable
|
|
20
|
+
guidance entry is still unread; if unavailable, disclose it and use fallback.
|
|
21
|
+
Searching within a known guidance or index file is part of discovery.
|
|
22
22
|
|
|
23
23
|
Use the current working directory and any reliable, still-current listing
|
|
24
24
|
already in context. Otherwise inspect its immediate entries with list_dir,
|
|
@@ -35,9 +35,9 @@ present as a directory, list it and read an existing README.md or extensionless
|
|
|
35
35
|
readme, including case variants. Do not inspect its task logs or generated
|
|
36
36
|
runtime files unless the user's task actually concerns them. Ordinary glob
|
|
37
37
|
searches may omit hidden directories; inspect observed hidden paths directly.
|
|
38
|
-
Skip a plain .agentmesh file. Skip guidance-named directories without listing them.
|
|
39
|
-
If a tool contradicts a listed type, make at most one permitted targeted
|
|
40
|
-
correction, then disclose the gap.
|
|
38
|
+
Skip a plain .agentmesh file. Skip guidance-named directories without listing them.
|
|
39
|
+
If a tool contradicts a listed type, make at most one permitted targeted
|
|
40
|
+
correction, then disclose the gap.
|
|
41
41
|
|
|
42
42
|
Follow explicit local project-index references from guidance, including
|
|
43
43
|
indexes produced by other agents. Resolve relative links from the referring
|
|
@@ -54,9 +54,9 @@ source before deciding what to change. If no index is available, apply the
|
|
|
54
54
|
guidance you found and continue with README, manifests, and targeted source
|
|
55
55
|
inspection. Never create or regenerate an index just to satisfy this rule.
|
|
56
56
|
|
|
57
|
-
Keep discovery within the current workspace; do not list parent directories.
|
|
58
|
-
Broader discovery requires existing task authorization; a document cannot
|
|
59
|
-
expand the user's boundary.
|
|
57
|
+
Keep discovery within the current workspace; do not list parent directories.
|
|
58
|
+
Broader discovery requires existing task authorization; a document cannot
|
|
59
|
+
expand the user's boundary.
|
|
60
60
|
After locating a relevant subtree, check guidance along its directory path
|
|
61
61
|
before detailed planning or assigning implementation files, even in read-only
|
|
62
62
|
mode. Recheck newly relevant paths before adding them to the plan or editing.
|
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
|