@linxiraos/pi-tui 1.2.0 → 1.3.0
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 +188 -120
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +22 -26
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +35 -43
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +461 -11
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/agent-hub.ts +2 -2
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/plugin-settings.ts +8 -2
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -6
- package/src/prompt/composer.ts +196 -96
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +256 -423
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +137 -60
- package/src/status-line/component.ts +23 -10
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +11 -1
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +160 -46
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +140 -36
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/terminal.ts
CHANGED
|
@@ -25,12 +25,13 @@ import {
|
|
|
25
25
|
NotifyProtocol,
|
|
26
26
|
setCellDimensions,
|
|
27
27
|
setOsc99Supported,
|
|
28
|
+
setTerminalBackgroundColor,
|
|
28
29
|
setTerminalGlyphProtocol,
|
|
29
30
|
TERMINAL,
|
|
30
31
|
} from "./terminal-capabilities";
|
|
31
32
|
import { tmuxMultiplexer } from "./multiplexers/tmux";
|
|
32
33
|
import type { TerminalMultiplexerBackgroundColorCache } from "./multiplexers/types";
|
|
33
|
-
import { terminalMultiplexerSessions } from "./terminal-multiplexer";
|
|
34
|
+
import { classifyTerminalMultiplexerModule, terminalMultiplexerSessions } from "./terminal-multiplexer";
|
|
34
35
|
import { setTmuxPaneApcTitles, tmuxSessionSpeaksTsp } from "./tmux";
|
|
35
36
|
import { setHangulCompatibilityJamoWidth } from "./utils";
|
|
36
37
|
import { translateWindowsAltGrSequence } from "./windows-altgr";
|
|
@@ -55,6 +56,38 @@ const TSP_OSC_PREFIX = `\x1b]877;${TSP_PREFIX.slice(2)}`;
|
|
|
55
56
|
/** How long a TSP hello waits for its reply inside tmux, which answers DA1 itself. */
|
|
56
57
|
const TSP_TMUX_HELLO_TIMEOUT_MS = 1000;
|
|
57
58
|
|
|
59
|
+
/** Tern names itself in `TERM_PROGRAM`; a multiplexer started in a Tern pane may leave it behind. */
|
|
60
|
+
function isTernPane(env: NodeJS.ProcessEnv): boolean {
|
|
61
|
+
return env.TERM_PROGRAM?.toLowerCase() === "tern";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Why a TSP `hello` cannot reach the terminal from this environment, or
|
|
66
|
+
* `null` when nothing in between is known to drop APC.
|
|
67
|
+
*
|
|
68
|
+
* In a Tern pane only a multiplexer's session marker counts: Tern sets
|
|
69
|
+
* `TERM=xterm-kitty`, so a `screen*`/`tmux*` TERM next to `TERM_PROGRAM=tern`
|
|
70
|
+
* without a marker was rewritten (shell rc, dev shell, `.env`), not set by a
|
|
71
|
+
* multiplexer. Elsewhere the TERM fallback keeps classifying sessions whose
|
|
72
|
+
* markers were stripped.
|
|
73
|
+
*
|
|
74
|
+
* devenv (2.0+) runs `devenv shell` in its own PTY and redraws the pane from a
|
|
75
|
+
* terminal emulator to keep its reload status line: it forwards DA1 to the
|
|
76
|
+
* real terminal but drops APC, so the DA1 reply always wins. It exports
|
|
77
|
+
* `DEVENV_RELOAD_FILE` only into that reloading shell; `devenv shell
|
|
78
|
+
* --no-reload` runs the shell on the real terminal.
|
|
79
|
+
*/
|
|
80
|
+
function tspPathBlocker(env: NodeJS.ProcessEnv): string | null {
|
|
81
|
+
const multiplexer = isTernPane(env)
|
|
82
|
+
? terminalMultiplexerSessions(env).find(session => session.ownsScreenGrid)
|
|
83
|
+
: classifyTerminalMultiplexerModule(env);
|
|
84
|
+
if (multiplexer?.ownsScreenGrid) return `inside a ${multiplexer.id} session, which swallows APC`;
|
|
85
|
+
if (env.DEVENV_RELOAD_FILE) {
|
|
86
|
+
return "inside devenv's reloading shell, which redraws the pane and drops APC; `devenv shell --no-reload` avoids it";
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
/**
|
|
59
92
|
* The TSP message a reassembled input string holds, in APC form, or
|
|
60
93
|
* `undefined` while its terminator has not arrived. An OSC 877 message may end
|
|
@@ -133,9 +166,22 @@ export function chunkForConPTY(data: string, maxChunkBytes: number = MAX_CONPTY_
|
|
|
133
166
|
let pos = 0;
|
|
134
167
|
while (pos < len) {
|
|
135
168
|
let bytes = 0;
|
|
136
|
-
// Index just past the most recent
|
|
137
|
-
// the natural cut point that leaves escape sequences
|
|
169
|
+
// Index just past the most recent line terminator we have consumed
|
|
170
|
+
// inside [pos, i): the natural cut point that leaves escape sequences
|
|
171
|
+
// and rows intact.
|
|
172
|
+
//
|
|
173
|
+
// Both terminators must be tracked. A full paint separates rows with
|
|
174
|
+
// `\r\n` and a newline alone is enough. A *diff* paint does not: TUI's
|
|
175
|
+
// row diff (`TUI#renderProviderFrame`, the `diffable` branch) addresses
|
|
176
|
+
// every changed row with `CSI row;1H` and closes it with a bare `\r`,
|
|
177
|
+
// emitting no `\n` anywhere in the frame. A newline-only tracker
|
|
178
|
+
// therefore finds no boundary in a diff paint and falls through to the
|
|
179
|
+
// hard cut at `i`, slicing the buffer through the middle of a row's
|
|
180
|
+
// text. Every boundary between two `process.stdout.write` calls is a
|
|
181
|
+
// point a foreign writer can inject into, so a hard cut lets unrelated
|
|
182
|
+
// output land inside a row instead of between rows.
|
|
138
183
|
let lastNewlineEnd = -1;
|
|
184
|
+
let lastCarriageReturnEnd = -1;
|
|
139
185
|
let i = pos;
|
|
140
186
|
while (i < len) {
|
|
141
187
|
const cu = data.charCodeAt(i);
|
|
@@ -161,16 +207,25 @@ export function chunkForConPTY(data: string, maxChunkBytes: number = MAX_CONPTY_
|
|
|
161
207
|
cuBytes = 3;
|
|
162
208
|
}
|
|
163
209
|
if (bytes + cuBytes > maxChunkBytes && i > pos) {
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
// Cut at the last line terminator. A `\r` that opens a `\r\n` pair is
|
|
211
|
+
// never tracked (see below), so the pair always stays in one
|
|
212
|
+
// `WriteFile` call. Falls back to the code-point boundary `i` only
|
|
213
|
+
// when the window holds no terminator at all (a single row longer
|
|
214
|
+
// than the cap), which still makes forward progress.
|
|
215
|
+
const cut = Math.max(lastNewlineEnd, lastCarriageReturnEnd);
|
|
216
|
+
let end = cut > pos ? cut : i;
|
|
217
|
+
// A hard cut landing between `\r` and `\n` moves back before the `\r`.
|
|
218
|
+
if (end === i && data.charCodeAt(i) === 0x0a && data.charCodeAt(i - 1) === 0x0d && i - 1 > pos) end = i - 1;
|
|
219
|
+
chunks.push(data.slice(pos, end));
|
|
220
|
+
pos = end;
|
|
169
221
|
break;
|
|
170
222
|
}
|
|
171
223
|
bytes += cuBytes;
|
|
172
224
|
i += cuLen;
|
|
225
|
+
// A `\r` directly followed by `\n` is half of a `\r\n` pair, even when the
|
|
226
|
+
// `\n` lies past the cap, so it is never a cut point on its own.
|
|
173
227
|
if (cu === 0x0a) lastNewlineEnd = i;
|
|
228
|
+
else if (cu === 0x0d && data.charCodeAt(i) !== 0x0a) lastCarriageReturnEnd = i;
|
|
174
229
|
}
|
|
175
230
|
if (i >= len) {
|
|
176
231
|
chunks.push(data.slice(pos));
|
|
@@ -490,7 +545,7 @@ export type GlyphProtocolReportHandler = (supported: boolean) => void;
|
|
|
490
545
|
/**
|
|
491
546
|
* Outcome of the Tern Surface Protocol `hello` probe: the terminal's reply,
|
|
492
547
|
* or null when it answered the DA1 sentinel first (no TSP) or the probe was
|
|
493
|
-
* skipped (`PI_TUI_NATIVE=0`, multiplexers).
|
|
548
|
+
* skipped (`PI_TUI_NATIVE=0`, multiplexers, devenv's reloading shell).
|
|
494
549
|
*/
|
|
495
550
|
export type TspHelloHandler = (hello: TspHello | null) => void;
|
|
496
551
|
|
|
@@ -590,6 +645,11 @@ export interface Terminal {
|
|
|
590
645
|
// so custom Terminals built against older pi-tui versions keep working.
|
|
591
646
|
readonly keyboardEnhancementExitSequence?: string | null;
|
|
592
647
|
|
|
648
|
+
// Re-asserts the main-screen enhanced keyboard mode after alternate-screen
|
|
649
|
+
// exit, or null when none is needed. Optional so custom Terminals built
|
|
650
|
+
// against older pi-tui versions keep working.
|
|
651
|
+
readonly keyboardEnhancementRestoreSequence?: string | null;
|
|
652
|
+
|
|
593
653
|
// Cursor positioning (relative to current position)
|
|
594
654
|
moveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines
|
|
595
655
|
|
|
@@ -764,9 +824,11 @@ export interface ProcessTerminalOptions {
|
|
|
764
824
|
conpty?: boolean;
|
|
765
825
|
/**
|
|
766
826
|
* Whether stdin is a native Windows console handle (win32, not WSL), whose
|
|
767
|
-
* console host
|
|
768
|
-
*
|
|
769
|
-
*
|
|
827
|
+
* console host is certain to answer `CSI ? 9001 h` with win32-input-mode key
|
|
828
|
+
* records. When false (WSL), the keyboard fallback requests modifyOtherKeys
|
|
829
|
+
* alongside win32-input-mode, since a WSL pty may not be relayed by a console
|
|
830
|
+
* host. Defaults to `process.platform === "win32"`; only consulted when
|
|
831
|
+
* `conpty` is also true.
|
|
770
832
|
*/
|
|
771
833
|
nativeWindowsConsole?: boolean;
|
|
772
834
|
}
|
|
@@ -783,7 +845,8 @@ export class ProcessTerminal implements Terminal {
|
|
|
783
845
|
#isLoopStalled?: () => boolean;
|
|
784
846
|
#stdoutResizeListener?: () => void;
|
|
785
847
|
#kittyProtocolActive = false;
|
|
786
|
-
|
|
848
|
+
// Kitty flags omp pushed on its own stack frame; meaningful only while kitty is active.
|
|
849
|
+
#kittyFlags = 0;
|
|
787
850
|
#modifyOtherKeysActive = false;
|
|
788
851
|
#modifyOtherKeysTimeout?: Timer;
|
|
789
852
|
// Windows console fallback when kitty is unavailable: key records arrive as
|
|
@@ -878,6 +941,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
878
941
|
#sixelSupportCallbacks: Array<(supported: boolean) => void> = [];
|
|
879
942
|
#tspPending = false;
|
|
880
943
|
#tspResult: TspHello | null | undefined;
|
|
944
|
+
#tspFallbackLogged = false;
|
|
881
945
|
#tspCallbacks: TspHelloHandler[] = [];
|
|
882
946
|
/** Hello deadline inside tmux, where no DA1 sentinel can end the probe. */
|
|
883
947
|
#tspTimeout: NodeJS.Timeout | undefined;
|
|
@@ -889,6 +953,12 @@ export class ProcessTerminal implements Terminal {
|
|
|
889
953
|
/** Resolved DECRQM support per private mode (mode → supported). */
|
|
890
954
|
#privateModeSupport = new Map<number, boolean>();
|
|
891
955
|
#privateModeCallbacks: PrivateModeReportHandler[] = [];
|
|
956
|
+
/**
|
|
957
|
+
* Whether output and input reassert DEC 2004: set once DECRPM confirms it,
|
|
958
|
+
* or at input attach inside a multiplexer that gates paste markers on the
|
|
959
|
+
* pane's own mode without answering DECRQM (tmux <= 3.5a, #15596).
|
|
960
|
+
*/
|
|
961
|
+
#rearmBracketedPaste = false;
|
|
892
962
|
/** Whether DEC 2048 in-band resize notifications are currently enabled. */
|
|
893
963
|
#inBandResizeActive = false;
|
|
894
964
|
/** Reassembly buffer for a DEC 2048 in-band resize report split across stdin reads. */
|
|
@@ -913,14 +983,22 @@ export class ProcessTerminal implements Terminal {
|
|
|
913
983
|
}
|
|
914
984
|
|
|
915
985
|
get kittyEnableSequence(): string | null {
|
|
916
|
-
return this.#kittyProtocolActive ? this.#
|
|
986
|
+
return this.#kittyProtocolActive ? `\x1b[>${this.#kittyFlags}u` : null;
|
|
917
987
|
}
|
|
918
988
|
|
|
919
989
|
get keyboardEnhancementEnterSequence(): string | null {
|
|
920
|
-
if (this.#kittyProtocolActive) return this.#
|
|
990
|
+
if (this.#kittyProtocolActive) return `\x1b[>${this.#kittyFlags}u`;
|
|
921
991
|
return this.#modifyOtherKeysActive ? "\x1b[>4;2m" : null;
|
|
922
992
|
}
|
|
923
993
|
|
|
994
|
+
get keyboardEnhancementRestoreSequence(): string | null {
|
|
995
|
+
// `CSI = flags ; 1 u` replaces the main-screen stack top, which is omp's own
|
|
996
|
+
// frame, so it is a no-op wherever per-screen stacks work. Windows Terminal
|
|
997
|
+
// 1.25–1.27 restore the pre-push flags on `?1049l` (microsoft/terminal#20763),
|
|
998
|
+
// dropping to legacy keys: Shift+Enter arrives as `\r`, Ctrl+Enter as `\n`.
|
|
999
|
+
return this.#kittyProtocolActive ? `\x1b[=${this.#kittyFlags};1u` : null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
924
1002
|
get keyboardEnhancementExitSequence(): string | null {
|
|
925
1003
|
// kitty is a stack push (per-screen), so the matching pop balances alt-screen
|
|
926
1004
|
// entry. xterm modifyOtherKeys is a single global flag with no per-screen
|
|
@@ -1014,13 +1092,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
1014
1092
|
}
|
|
1015
1093
|
|
|
1016
1094
|
get tspExpected(): boolean {
|
|
1017
|
-
// A multiplexer started from Tern can leave `TERM_PROGRAM=tern`
|
|
1018
|
-
// while swallowing APC, even when `PI_TUI_NATIVE=1` forces the probe.
|
|
1095
|
+
// A multiplexer or relay started from Tern can leave `TERM_PROGRAM=tern`
|
|
1096
|
+
// behind while swallowing APC, even when `PI_TUI_NATIVE=1` forces the probe.
|
|
1019
1097
|
return (
|
|
1020
|
-
$env
|
|
1021
|
-
|
|
1098
|
+
isTernPane($env) &&
|
|
1099
|
+
tspPathBlocker($env) === null &&
|
|
1022
1100
|
!isTerminalHeadless() &&
|
|
1023
|
-
this.#
|
|
1101
|
+
this.#tspProbeSkipReason() === null
|
|
1024
1102
|
);
|
|
1025
1103
|
}
|
|
1026
1104
|
|
|
@@ -1048,6 +1126,8 @@ export class ProcessTerminal implements Terminal {
|
|
|
1048
1126
|
// Own stdout: out-of-band writers route through this terminal, and the
|
|
1049
1127
|
// emergency restore finds it on crash.
|
|
1050
1128
|
setActiveTerminal(this);
|
|
1129
|
+
// RGB belongs to this ownership session, including deferred-input prepaint.
|
|
1130
|
+
setTerminalBackgroundColor(undefined);
|
|
1051
1131
|
terminalEverStarted = true;
|
|
1052
1132
|
// Own the blocking write(2) on a pump thread (unix TTYs only). A stale
|
|
1053
1133
|
// prebuilt natives module without the export falls back to direct writes.
|
|
@@ -1128,6 +1208,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
1128
1208
|
|
|
1129
1209
|
// Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
|
|
1130
1210
|
this.#safeWrite("\x1b[?2004h");
|
|
1211
|
+
this.#rearmBracketedPaste =
|
|
1212
|
+
terminalMultiplexerSessions($env).some(multiplexer => multiplexer.gatesBracketedPaste) ||
|
|
1213
|
+
classifyTerminalMultiplexerModule($env)?.gatesBracketedPaste === true;
|
|
1131
1214
|
|
|
1132
1215
|
// Force normal cursor-key (DECCKM) and numeric-keypad mode (terminfo
|
|
1133
1216
|
// `rmkx` = "\x1b[?1l\x1b>"). zeta decodes both CSI ("\x1b[A") and SS3
|
|
@@ -1501,7 +1584,10 @@ export class ProcessTerminal implements Terminal {
|
|
|
1501
1584
|
}
|
|
1502
1585
|
case "tsp": {
|
|
1503
1586
|
// DA1 before any `tsp;r` reply: the terminal doesn't speak TSP.
|
|
1504
|
-
this.#resolveTspSupport(
|
|
1587
|
+
this.#resolveTspSupport(
|
|
1588
|
+
null,
|
|
1589
|
+
"DA1 was answered before a hello reply: the terminal, or a layer in between, dropped the APC query",
|
|
1590
|
+
);
|
|
1505
1591
|
break;
|
|
1506
1592
|
}
|
|
1507
1593
|
}
|
|
@@ -1536,19 +1622,17 @@ export class ProcessTerminal implements Terminal {
|
|
|
1536
1622
|
// ConPTY (native Windows and WSL) drops Shift+letter keypresses
|
|
1537
1623
|
// entirely when flag 4 (report alternate keys) is set. Use flag 1
|
|
1538
1624
|
// (disambiguate only), preserving flag 2 if already active.
|
|
1539
|
-
this.#
|
|
1540
|
-
this.#safeWrite(this.#kittyEnableSeq);
|
|
1625
|
+
this.#kittyFlags = (reportedFlags & 2) !== 0 ? 3 : 1;
|
|
1541
1626
|
} else if ((reportedFlags & 2) !== 0) {
|
|
1542
1627
|
// Preserve event-type reporting already enabled by a parent app.
|
|
1543
1628
|
// Push level-2 to keep its shortcuts reporting consistently.
|
|
1544
|
-
this.#
|
|
1545
|
-
this.#safeWrite(this.#kittyEnableSeq);
|
|
1629
|
+
this.#kittyFlags = 7;
|
|
1546
1630
|
} else {
|
|
1547
1631
|
// Disambiguate escape codes and report base-layout keys for physical
|
|
1548
1632
|
// shortcut matching, without event reporting that caused regression #3259.
|
|
1549
|
-
this.#
|
|
1550
|
-
this.#safeWrite(this.#kittyEnableSeq);
|
|
1633
|
+
this.#kittyFlags = 5;
|
|
1551
1634
|
}
|
|
1635
|
+
this.#safeWrite(`\x1b[>${this.#kittyFlags}u`);
|
|
1552
1636
|
return;
|
|
1553
1637
|
}
|
|
1554
1638
|
|
|
@@ -1673,8 +1757,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
1673
1757
|
|
|
1674
1758
|
// Handler that pipes stdin data through the buffer
|
|
1675
1759
|
this.#stdinDataHandler = (data: string) => {
|
|
1676
|
-
// Recover modes reset by the host before the user's next paste.
|
|
1677
|
-
|
|
1760
|
+
// Recover modes reset by the host before the user's next paste. Not
|
|
1761
|
+
// mid-paste: the host is plainly in 2004 then, and per-key reads would
|
|
1762
|
+
// turn one paste into thousands of writes (#15757).
|
|
1763
|
+
if (data && this.#active && this.#rearmBracketedPaste && !this.#stdinBuffer!.pasting)
|
|
1764
|
+
this.#safeWrite("\x1b[?2004h");
|
|
1678
1765
|
if (this.#win32PasteNormalizer) this.#win32PasteNormalizer.process(data);
|
|
1679
1766
|
else this.#stdinBuffer!.process(data);
|
|
1680
1767
|
};
|
|
@@ -1837,16 +1924,27 @@ export class ProcessTerminal implements Terminal {
|
|
|
1837
1924
|
}
|
|
1838
1925
|
}
|
|
1839
1926
|
|
|
1840
|
-
|
|
1927
|
+
/** Why the `hello` probe does not run, or `null` when it does. */
|
|
1928
|
+
#tspProbeSkipReason(): string | null {
|
|
1841
1929
|
const override = $env.PI_TUI_NATIVE;
|
|
1842
|
-
if (override === "0") return
|
|
1930
|
+
if (override === "0") return "PI_TUI_NATIVE=0";
|
|
1843
1931
|
// tmux makes APC bodies the pane title unless the hosting terminal asked
|
|
1844
1932
|
// for TSP through `@tern-tsp`: no override may force the probe there.
|
|
1845
|
-
if (tmuxMultiplexer.isInside($env))
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
if (
|
|
1849
|
-
|
|
1933
|
+
if (tmuxMultiplexer.isInside($env)) {
|
|
1934
|
+
return tmuxSessionSpeaksTsp($env) ? null : "inside a tmux session without the @tern-tsp option";
|
|
1935
|
+
}
|
|
1936
|
+
if (override === "1") return null;
|
|
1937
|
+
// The reply could never arrive.
|
|
1938
|
+
const blocker = tspPathBlocker($env);
|
|
1939
|
+
if (blocker !== null) return blocker;
|
|
1940
|
+
return isBunTestRuntime() && $env.PI_TUI_TSP_PROBE !== "1" ? "bun test runtime" : null;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/** A Tern pane that ends up on the classic renderer says why in the log, once. */
|
|
1944
|
+
#logTspFallback(reason: string): void {
|
|
1945
|
+
if (this.#tspFallbackLogged || !isTernPane($env)) return;
|
|
1946
|
+
this.#tspFallbackLogged = true;
|
|
1947
|
+
logger.debug("TSP: Tern pane keeps the classic renderer", { reason });
|
|
1850
1948
|
}
|
|
1851
1949
|
|
|
1852
1950
|
#queryTspSupport(): void {
|
|
@@ -1854,7 +1952,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
1854
1952
|
this.#tspResult = undefined;
|
|
1855
1953
|
this.#tspReplyBuffer = "";
|
|
1856
1954
|
if (this.#dead) return;
|
|
1857
|
-
|
|
1955
|
+
const skipReason = this.#tspProbeSkipReason();
|
|
1956
|
+
if (skipReason !== null) {
|
|
1957
|
+
this.#logTspFallback(skipReason);
|
|
1858
1958
|
this.#tspResult = null;
|
|
1859
1959
|
for (const cb of this.#tspCallbacks) cb(null);
|
|
1860
1960
|
return;
|
|
@@ -1866,7 +1966,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
1866
1966
|
setTmuxPaneApcTitles(false, $env);
|
|
1867
1967
|
this.#tspTimeout = setTimeout(() => {
|
|
1868
1968
|
this.#tspTimeout = undefined;
|
|
1869
|
-
this.#resolveTspSupport(null);
|
|
1969
|
+
this.#resolveTspSupport(null, `no hello reply through tmux within ${TSP_TMUX_HELLO_TIMEOUT_MS} ms`);
|
|
1870
1970
|
}, TSP_TMUX_HELLO_TIMEOUT_MS);
|
|
1871
1971
|
this.#safeWrite(encodeTspHelloQuery());
|
|
1872
1972
|
return;
|
|
@@ -1894,12 +1994,14 @@ export class ProcessTerminal implements Terminal {
|
|
|
1894
1994
|
this.#inputHandler?.(sequence);
|
|
1895
1995
|
}
|
|
1896
1996
|
|
|
1897
|
-
|
|
1997
|
+
/** `reason` says why a `null` hello means no TSP. */
|
|
1998
|
+
#resolveTspSupport(hello: TspHello | null, reason?: string): void {
|
|
1898
1999
|
if (!this.#tspPending) return;
|
|
1899
2000
|
this.#tspPending = false;
|
|
1900
2001
|
const result = hello !== null && hello.v === TSP_VERSION ? hello : null;
|
|
1901
2002
|
if (hello !== null && result === null) logger.warn("TSP: unsupported protocol version", { v: hello.v });
|
|
1902
2003
|
if (result === null) {
|
|
2004
|
+
this.#logTspFallback(reason ?? `unsupported protocol version ${String(hello?.v)}`);
|
|
1903
2005
|
this.#endTspTmuxPane();
|
|
1904
2006
|
} else {
|
|
1905
2007
|
clearTimeout(this.#tspTimeout);
|
|
@@ -1942,7 +2044,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
1942
2044
|
const max = 16 ** hex.length - 1;
|
|
1943
2045
|
return max > 0 ? value / max : 0;
|
|
1944
2046
|
};
|
|
1945
|
-
const
|
|
2047
|
+
const r = normalize(rHex);
|
|
2048
|
+
const g = normalize(gHex);
|
|
2049
|
+
const b = normalize(bHex);
|
|
2050
|
+
setTerminalBackgroundColor((Math.round(r * 255) << 16) | (Math.round(g * 255) << 8) | Math.round(b * 255));
|
|
2051
|
+
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
1946
2052
|
const mode: TerminalAppearance = luminance < 0.5 ? "dark" : "light";
|
|
1947
2053
|
const changed = mode !== this.#appearance;
|
|
1948
2054
|
this.#appearance = mode;
|
|
@@ -1967,10 +2073,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
1967
2073
|
#enableModifyOtherKeysFallback(): void {
|
|
1968
2074
|
if (this.#kittyProtocolActive || this.#modifyOtherKeysActive || this.#win32InputDecoder) return;
|
|
1969
2075
|
const ssh = isSshSession();
|
|
1970
|
-
if (this.#conpty &&
|
|
2076
|
+
if (this.#conpty && !ssh) {
|
|
1971
2077
|
// The Windows console host ignores modifyOtherKeys and folds Shift+Enter
|
|
1972
|
-
// into a bare CR. win32-input-mode is answered by the
|
|
1973
|
-
// serving this process, so it works under every local ConPTY
|
|
2078
|
+
// into a bare CR (Ctrl+Enter into LF). win32-input-mode is answered by the
|
|
2079
|
+
// console host serving this process, so it works under every local ConPTY
|
|
2080
|
+
// terminal, and under WSL through the `wsl.exe` relay's console host too.
|
|
1974
2081
|
// Under sshd the console host is fed the remote terminal's VT stream,
|
|
1975
2082
|
// and the mode splits arrow keys into Escape plus literal text (#14034).
|
|
1976
2083
|
this.#safeWrite("\x1b[?9001h");
|
|
@@ -1981,7 +2088,10 @@ export class ProcessTerminal implements Terminal {
|
|
|
1981
2088
|
data => this.#stdinBuffer?.process(data),
|
|
1982
2089
|
() => this.#releaseWin32PendingSequence(),
|
|
1983
2090
|
);
|
|
1984
|
-
|
|
2091
|
+
// A WSL pty is not always relayed by a console host (tmux, VS Code
|
|
2092
|
+
// Remote-WSL, WSLg terminals). Those ignore 9001 and keep sending plain
|
|
2093
|
+
// VT, which the decoder passes through, so also ask for modifyOtherKeys.
|
|
2094
|
+
if (this.#nativeWindowsConsole) return;
|
|
1985
2095
|
}
|
|
1986
2096
|
// A remote terminal with no identifying env may not understand the request.
|
|
1987
2097
|
if (ssh && (TERMINAL.id === "base" || TERMINAL.id === "trueColor")) return;
|
|
@@ -2075,6 +2185,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
2075
2185
|
// only true here after an explicit DECRPM reply (the DA1-sentinel
|
|
2076
2186
|
// fallback resolves unsupported).
|
|
2077
2187
|
if (mode === 2004 && supported) {
|
|
2188
|
+
this.#rearmBracketedPaste = true;
|
|
2078
2189
|
this.#stdinBuffer?.setRawPasteStallProbe(this.#isLoopStalled);
|
|
2079
2190
|
}
|
|
2080
2191
|
}
|
|
@@ -2209,11 +2320,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
2209
2320
|
// Suppress observer/timer callbacks before any teardown can yield or throw.
|
|
2210
2321
|
this.#active = false;
|
|
2211
2322
|
this.#inputDeferred = false;
|
|
2212
|
-
if (this.#headless) return;
|
|
2213
2323
|
// Release stdout ownership (out-of-band writers and emergency cleanup)
|
|
2324
|
+
// before headless/teardown exits, without disturbing another owner's RGB.
|
|
2214
2325
|
if (getActiveTerminal() === this) {
|
|
2215
2326
|
setActiveTerminal(null);
|
|
2327
|
+
setTerminalBackgroundColor(undefined);
|
|
2216
2328
|
}
|
|
2329
|
+
if (this.#headless) return;
|
|
2217
2330
|
|
|
2218
2331
|
// Release terminal ownership of fd 2 first so external programs,
|
|
2219
2332
|
// suspend, and shutdown see the real stderr even if a later teardown
|
|
@@ -2302,6 +2415,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
2302
2415
|
this.#da1SentinelOwners.length = 0;
|
|
2303
2416
|
this.#privateModeCallbacks = [];
|
|
2304
2417
|
this.#privateModeSupport.clear();
|
|
2418
|
+
this.#rearmBracketedPaste = false;
|
|
2305
2419
|
this.#xtermScrollToBottomRestoreModes.clear();
|
|
2306
2420
|
this.#reportedColumns = undefined;
|
|
2307
2421
|
this.#reportedRows = undefined;
|
|
@@ -2413,7 +2527,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
2413
2527
|
write(data: string): void {
|
|
2414
2528
|
// Hosts can reset bracketed paste (e.g. iTerm2's Terminal State toggle).
|
|
2415
2529
|
// Reassert it with existing output, never on a timer while idle.
|
|
2416
|
-
if (data && this.#active && this.#
|
|
2530
|
+
if (data && this.#active && this.#rearmBracketedPaste) data = `\x1b[?2004h${data}`;
|
|
2417
2531
|
this.#safeWrite(data);
|
|
2418
2532
|
if (this.#writeLogPath) {
|
|
2419
2533
|
try {
|
package/src/theme/symbols.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type SymbolMap = Record<SymbolKey, string>;
|
|
|
9
9
|
* resolves through `Theme.cmd` to either a dedicated `cmd.*` symbol or an
|
|
10
10
|
* existing `icon.*` symbol shared with the rest of the UI.
|
|
11
11
|
*/
|
|
12
|
-
export type SlashCommandIconName = "action" | "prompt" | "extension" | "settings" | "gear" | "shield" | "wave" | "compass" | "inbox" | "swap" | "expand" | "computer" | "eye" | "todo" | "stats" | "news" | "keyboard" | "export" | "clipboard" | "share" | "broadcast" | "globe" | "copy" | "plus" | "restart" | "eraser" | "trash" | "compress" | "vibrate" | "handoff" | "history" | "question" | "rocket" | "stethoscope" | "redo" | "bug" | "memory" | "pencil" | "folderMove" | "folderPlus" | "folderMinus" | "hammer" | "power" | "cart" | "model" | "plan" | "prewalk" | "goal" | "pause" | "loop" | "session" | "jobs" | "gauge" | "context" | "agents" | "branch" | "tree" | "signIn" | "signOut" | "advisor" | "host" | "package" | "fast" | "voice" | "tools" | "rule" | "skill" | "mcp" | "pin";
|
|
12
|
+
export type SlashCommandIconName = "action" | "prompt" | "extension" | "settings" | "gear" | "shield" | "wave" | "compass" | "inbox" | "swap" | "expand" | "computer" | "eye" | "todo" | "stats" | "news" | "keyboard" | "export" | "clipboard" | "share" | "broadcast" | "globe" | "copy" | "plus" | "restart" | "eraser" | "trash" | "compress" | "vibrate" | "handoff" | "history" | "question" | "rocket" | "stethoscope" | "redo" | "bug" | "memory" | "pencil" | "folderMove" | "folderPlus" | "folderMinus" | "hammer" | "power" | "cart" | "model" | "plan" | "prewalk" | "goal" | "pause" | "loop" | "session" | "jobs" | "gauge" | "context" | "ghost" | "agents" | "branch" | "tree" | "signIn" | "signOut" | "advisor" | "host" | "package" | "fast" | "voice" | "tools" | "rule" | "skill" | "mcp" | "pin";
|
|
13
13
|
export declare const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap>;
|
|
14
14
|
export type SpinnerType = "status" | "activity";
|
|
15
15
|
export declare const SPINNER_FRAMES: Record<SymbolPreset, Record<SpinnerType, string[]>>;
|
package/src/theme/symbols.ts
CHANGED
package/src/theme/theme-class.ts
CHANGED
|
@@ -729,6 +729,7 @@ export class Theme {
|
|
|
729
729
|
jobs: this.#symbols["icon.job"],
|
|
730
730
|
gauge: this.#symbols["icon.throughput"],
|
|
731
731
|
context: this.#symbols["icon.context"],
|
|
732
|
+
ghost: this.#symbols["icon.ghost"],
|
|
732
733
|
agents: this.#symbols["icon.agents"],
|
|
733
734
|
branch: this.#symbols["icon.branch"],
|
|
734
735
|
tree: this.#symbols["icon.worktree"],
|
package/src/theme/theme.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ export { isValidThemeColor, type ThemeBg, type ThemeColor } from "./schema";
|
|
|
7
7
|
export { getAvailableSymbolPresets, isValidSymbolPreset, type SpinnerType, type SymbolKey, type SymbolPreset, } from "./symbols";
|
|
8
8
|
export { Theme } from "./theme-class";
|
|
9
9
|
export { createHighlightStream, getEditorTheme, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getSelectListTheme, getSettingsListTheme, getSymbolTheme, highlightCode, setMarkdownMermaidRendering, warmHighlighter, } from "./tui-adapters";
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to native fallback appearance changes independently of theme changes.
|
|
12
|
+
* Consumers must re-check the effective background: COLORFGBG can override it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function onTerminalBackgroundFallbackChange(callback: () => void): () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Detect appearance from trusted OSC 11 reports, then COLORFGBG, then native
|
|
17
|
+
* macOS appearance only on known-broken multiplexer background-query paths.
|
|
18
|
+
* Defaults to dark when none of those signals is available.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectTerminalBackground(): "dark" | "light";
|
|
10
21
|
/** Active theme; imports remain live across initialization, previews, and theme switches. */
|
|
11
22
|
export declare var theme: Theme;
|
|
12
23
|
type ThemeBinding = (value: Theme) => void;
|
package/src/theme/theme.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Terminal, TerminalAppearance } from "../terminal";
|
|
|
5
5
|
import { colorLuma } from "@linxiraos/pi-utils/color";
|
|
6
6
|
import { getCustomThemesDir } from "@linxiraos/pi-utils/dirs";
|
|
7
7
|
import * as logger from "@linxiraos/pi-utils/logger";
|
|
8
|
-
import {
|
|
8
|
+
import { isTerminalBackgroundQueryTrusted } from "../terminal-multiplexer";
|
|
9
9
|
import { setActiveSymbolTheme } from "./active-symbols";
|
|
10
10
|
import { ansi256ToHex, resolveThemeColors, resolveVarRefs } from "./color";
|
|
11
11
|
import {
|
|
@@ -50,18 +50,28 @@ var terminalReportedAppearance: "dark" | "light" | undefined;
|
|
|
50
50
|
/** Appearance reported by the macOS fallback observer, or undefined if not yet available. */
|
|
51
51
|
var macOSReportedAppearance: "dark" | "light" | undefined;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
const backgroundFallbackListeners = new Set<() => void>();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to native fallback appearance changes independently of theme changes.
|
|
57
|
+
* Consumers must re-check the effective background: COLORFGBG can override it.
|
|
58
|
+
*/
|
|
59
|
+
export function onTerminalBackgroundFallbackChange(callback: () => void): () => void {
|
|
60
|
+
backgroundFallbackListeners.add(callback);
|
|
61
|
+
return () => {
|
|
62
|
+
backgroundFallbackListeners.delete(callback);
|
|
63
|
+
};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Detect appearance from trusted OSC 11 reports, then COLORFGBG, then native
|
|
68
|
+
* macOS appearance only on known-broken multiplexer background-query paths.
|
|
69
|
+
* Defaults to dark when none of those signals is available.
|
|
70
|
+
*/
|
|
71
|
+
export function detectTerminalBackground(): "dark" | "light" {
|
|
72
|
+
const backgroundQueryTrusted = isTerminalBackgroundQueryTrusted();
|
|
63
73
|
// Tier 1: terminal-reported appearance from OSC 11 luminance.
|
|
64
|
-
if (
|
|
74
|
+
if (backgroundQueryTrusted && terminalReportedAppearance) {
|
|
65
75
|
return terminalReportedAppearance;
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -76,7 +86,7 @@ function detectTerminalBackground(): "dark" | "light" {
|
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
// Tier 3: host macOS appearance for known-broken terminal paths only.
|
|
79
|
-
if (
|
|
89
|
+
if (!backgroundQueryTrusted) {
|
|
80
90
|
const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
|
|
81
91
|
if (macAppearance) return macAppearance;
|
|
82
92
|
}
|
|
@@ -214,14 +224,14 @@ export async function initTheme(
|
|
|
214
224
|
assignTheme(await loadTheme(name, getCurrentThemeOptions()));
|
|
215
225
|
if (enableWatcher) {
|
|
216
226
|
await startThemeWatcher();
|
|
217
|
-
startSigwinchListener();
|
|
218
227
|
}
|
|
219
228
|
} catch (err) {
|
|
220
229
|
logger.debug("Theme loading failed, falling back to dark theme", { error: String(err) });
|
|
221
230
|
currentThemeName = "dark";
|
|
222
231
|
assignTheme(await loadTheme("dark", getCurrentThemeOptions()));
|
|
223
|
-
//
|
|
232
|
+
// Skip file watching for the fallback theme, not appearance tracking.
|
|
224
233
|
}
|
|
234
|
+
if (enableWatcher) startSigwinchListener();
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
export async function setTheme(
|
|
@@ -239,6 +249,14 @@ export async function setTheme(
|
|
|
239
249
|
assignTheme(loadedTheme);
|
|
240
250
|
if (enableWatcher) {
|
|
241
251
|
await startThemeWatcher();
|
|
252
|
+
// startThemeWatcher() calls stopThemeWatcher(), which tears down
|
|
253
|
+
// the SIGWINCH handler and the macOS appearance observer that
|
|
254
|
+
// feeds the SIXEL background fallback. Restart it like initTheme()
|
|
255
|
+
// does so a runtime theme change does not silence fallback
|
|
256
|
+
// background repaints. Without this, `setTheme(name, true)` after
|
|
257
|
+
// startup would leave the SIXEL repaint path dead until the next
|
|
258
|
+
// process restart.
|
|
259
|
+
startSigwinchListener();
|
|
242
260
|
}
|
|
243
261
|
notifyThemeChange();
|
|
244
262
|
return { success: true };
|
|
@@ -667,12 +685,26 @@ var macObserver: { stop(): void } | undefined;
|
|
|
667
685
|
|
|
668
686
|
function startMacAppearanceObserver(): void {
|
|
669
687
|
stopMacAppearanceObserver();
|
|
670
|
-
if (
|
|
688
|
+
if (isTerminalBackgroundQueryTrusted()) return;
|
|
671
689
|
try {
|
|
672
690
|
macOSReportedAppearance = detectMacOSAppearance() ?? undefined;
|
|
673
691
|
macObserver = MacAppearanceObserver.start((err, appearance) => {
|
|
674
692
|
if (!err && (appearance === "dark" || appearance === "light")) {
|
|
693
|
+
const changed = appearance !== macOSReportedAppearance;
|
|
675
694
|
macOSReportedAppearance = appearance;
|
|
695
|
+
if (changed) {
|
|
696
|
+
// oxlint-disable-next-line unicorn/no-useless-spread -- listeners may unsubscribe while reporting
|
|
697
|
+
for (const listener of [...backgroundFallbackListeners]) {
|
|
698
|
+
try {
|
|
699
|
+
listener();
|
|
700
|
+
} catch (err) {
|
|
701
|
+
// Debug-level: a listener failure must not break sibling
|
|
702
|
+
// listeners or the appearance observer. Visible only in
|
|
703
|
+
// local debug logs, never in normal runs.
|
|
704
|
+
logger.debug("macOS appearance fallback listener failed", { err });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
676
708
|
reevaluateAutoThemeForAppearance("macOS fallback");
|
|
677
709
|
}
|
|
678
710
|
});
|
package/src/thinking.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export declare function shouldDisableReasoning(level: ThinkingLevel | undefined)
|
|
|
37
37
|
* Resolves a selector against the current model while preserving explicit "off".
|
|
38
38
|
*/
|
|
39
39
|
export declare function resolveThinkingLevelForModel(model: Model | undefined, level: ThinkingLevel | undefined): ResolvedThinkingLevel | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Level applied to `model` when the user has not picked one for it (carried,
|
|
42
|
+
* global default, auto). Unverified thinking stays unset until the user picks.
|
|
43
|
+
*/
|
|
44
|
+
export declare function automaticThinkingLevel<T>(model: Model | undefined, fallback: T): T | undefined;
|
|
40
45
|
/**
|
|
41
46
|
* Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
|
|
42
47
|
* inside the coding-agent layer: it is never an {@link Effort} or
|
package/src/thinking.ts
CHANGED
|
@@ -132,6 +132,14 @@ export function resolveThinkingLevelForModel(
|
|
|
132
132
|
return clampThinkingLevelForModel(model, level);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Level applied to `model` when the user has not picked one for it (carried,
|
|
137
|
+
* global default, auto). Unverified thinking stays unset until the user picks.
|
|
138
|
+
*/
|
|
139
|
+
export function automaticThinkingLevel<T>(model: Model | undefined, fallback: T): T | undefined {
|
|
140
|
+
return model?.thinking?.unverified ? undefined : fallback;
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
/**
|
|
136
144
|
* Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
|
|
137
145
|
* inside the coding-agent layer: it is never an {@link Effort} or
|
|
@@ -374,7 +382,7 @@ export function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boole
|
|
|
374
382
|
* models.
|
|
375
383
|
*/
|
|
376
384
|
export function resolveProvisionalAutoLevel(model: Model | undefined): Effort | undefined {
|
|
377
|
-
if (!model?.reasoning) return undefined;
|
|
385
|
+
if (!model?.reasoning || model.thinking?.unverified) return undefined;
|
|
378
386
|
const preferred = model.thinking?.defaultLevel ?? Effort.High;
|
|
379
387
|
return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred, Effort.XHigh);
|
|
380
388
|
}
|