@linxiraos/pi-tui 1.2.1 → 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 -125
- 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 +21 -20
- 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 +26 -6
- 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/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/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 +1 -1
- package/src/prompt/composer.ts +194 -74
- 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 +97 -45
- 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 +22 -8
- 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/tui.ts
CHANGED
|
@@ -31,10 +31,12 @@ import type { DescribeContext, NativeNode, NativeScreen, NativeSurfaceProvider,
|
|
|
31
31
|
import { STDOUT_BACKLOG_CLEAR_BYTES, setAltScreenActive, type Terminal } from "./terminal";
|
|
32
32
|
import { classifyTerminalMultiplexerModule, terminalMultiplexerSessions } from "./terminal-multiplexer";
|
|
33
33
|
import {
|
|
34
|
+
directPlacementLift,
|
|
34
35
|
encodeKittyDeleteAllImages,
|
|
35
36
|
encodeKittyDeleteImage,
|
|
36
37
|
encodeKittyDeletePlacement,
|
|
37
38
|
encodeKittyPlacementLine,
|
|
39
|
+
getTerminalBackgroundColor,
|
|
38
40
|
ImageProtocol,
|
|
39
41
|
isImageProtocolForced,
|
|
40
42
|
isInsideTerminalMultiplexer,
|
|
@@ -45,7 +47,9 @@ import {
|
|
|
45
47
|
synchronizedOutputUserOverride,
|
|
46
48
|
TERMINAL,
|
|
47
49
|
} from "./terminal-capabilities";
|
|
50
|
+
import { onTerminalBackgroundFallbackChange } from "./theme/theme";
|
|
48
51
|
import { compositeLineAt } from "./render/composite";
|
|
52
|
+
import { tmuxMultiplexer } from "./multiplexers/tmux";
|
|
49
53
|
import {
|
|
50
54
|
Ellipsis,
|
|
51
55
|
getWidthConfigEpoch,
|
|
@@ -56,6 +60,7 @@ import {
|
|
|
56
60
|
truncateToWidth,
|
|
57
61
|
visibleWidth,
|
|
58
62
|
} from "./utils";
|
|
63
|
+
import { tmuxClientRendersSixel } from "./tmux";
|
|
59
64
|
|
|
60
65
|
/** Full-attribute reset terminating each rendered content row. */
|
|
61
66
|
export const SEGMENT_RESET = "\x1b[0m";
|
|
@@ -254,10 +259,6 @@ export interface Component {
|
|
|
254
259
|
*/
|
|
255
260
|
render(width: number): readonly string[];
|
|
256
261
|
|
|
257
|
-
/** Inline decision panels must keep displaced settled transcript rows in
|
|
258
|
-
* native scrollback even when mounted inside a transient editor container. */
|
|
259
|
-
readonly retireDisplacedTranscript?: boolean;
|
|
260
|
-
|
|
261
262
|
/**
|
|
262
263
|
* Describe the component semantically for a Tern Surface Protocol
|
|
263
264
|
* terminal (see `native/node.ts`). Called instead of `render()` when the
|
|
@@ -823,6 +824,10 @@ export class TUI extends Container {
|
|
|
823
824
|
// rows the paint prepended for a short viewport. Negative while prepended
|
|
824
825
|
// blanks outweigh replaced rows; zero on ordinary frames.
|
|
825
826
|
#providerViewportPadTop = 0;
|
|
827
|
+
// Whether native history holds rows the provider committed since the last
|
|
828
|
+
// destructive reset. Only paints write history and only a destructive reset
|
|
829
|
+
// erases it; an `append` resize replay keeps the rows it replays beneath.
|
|
830
|
+
#providerHistoryWritten = false;
|
|
826
831
|
// Viewport-relative row of the hardware cursor after the last normal paint
|
|
827
832
|
// (0 = parked at the viewport top). A resize reflows the normal buffer
|
|
828
833
|
// before the app hears about it; terminals keep the cursor attached to its
|
|
@@ -928,6 +933,8 @@ export class TUI extends Container {
|
|
|
928
933
|
#debugNextWindowTop = 0;
|
|
929
934
|
#inputListeners = new Set<InputListener>();
|
|
930
935
|
#startListeners = new Set<StartListener>();
|
|
936
|
+
/** True from the end of start() until stop(): the terminal modes start listeners enable are live. */
|
|
937
|
+
#started = false;
|
|
931
938
|
#paintListeners = new Set<PaintListener>();
|
|
932
939
|
|
|
933
940
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
@@ -1020,6 +1027,7 @@ export class TUI extends Container {
|
|
|
1020
1027
|
#forceViewportRepaintOnNextRender = false;
|
|
1021
1028
|
#hasEverRendered = false;
|
|
1022
1029
|
#stopped = false;
|
|
1030
|
+
#appearanceReportUnsubscribe: (() => void) | undefined;
|
|
1023
1031
|
#cancelPostmortemRestore?: () => void;
|
|
1024
1032
|
/** True between a `deferInput` start() and enableInput(). */
|
|
1025
1033
|
#inputDeferred = false;
|
|
@@ -1108,6 +1116,7 @@ export class TUI extends Container {
|
|
|
1108
1116
|
overlayStack: {
|
|
1109
1117
|
component: Component;
|
|
1110
1118
|
options?: OverlayOptions;
|
|
1119
|
+
/** Focus restored after closing; tracks live focus changes beneath an input-less key holder. */
|
|
1111
1120
|
preFocus: Component | null;
|
|
1112
1121
|
hidden: boolean;
|
|
1113
1122
|
/**
|
|
@@ -1315,6 +1324,9 @@ export class TUI extends Container {
|
|
|
1315
1324
|
setFocus(component: Component | null): void {
|
|
1316
1325
|
const holder = this.#getKeyHolderOverlay();
|
|
1317
1326
|
if (holder && !isOverlayFocusTarget(holder.component, component)) {
|
|
1327
|
+
if (!holder.component.handleInput && this.#focusedComponent === holder.component) {
|
|
1328
|
+
holder.preFocus = component;
|
|
1329
|
+
}
|
|
1318
1330
|
const currentFocus = this.#focusedComponent;
|
|
1319
1331
|
component = isOverlayFocusTarget(holder.component, currentFocus) ? currentFocus : holder.component;
|
|
1320
1332
|
}
|
|
@@ -1461,6 +1473,27 @@ export class TUI extends Container {
|
|
|
1461
1473
|
return { top: this.#providerViewportTop - this.#providerViewportPadTop, length: this.#providerWindow.length };
|
|
1462
1474
|
}
|
|
1463
1475
|
|
|
1476
|
+
/**
|
|
1477
|
+
* Rows of native history the last paint left on screen above a mutable
|
|
1478
|
+
* viewport that reached the bottom row, or `undefined` when that paint left
|
|
1479
|
+
* rows free below it (or none happened, or a destructive reset will repaint
|
|
1480
|
+
* from row zero). Rows above a bottom-reaching viewport may already sit in
|
|
1481
|
+
* native scrollback, which a later shrink cannot pull back down.
|
|
1482
|
+
*/
|
|
1483
|
+
getFilledHistoryRows(): number | undefined {
|
|
1484
|
+
if (!this.#hasEverRendered || this.#clearScrollbackOnNextRender) return undefined;
|
|
1485
|
+
const height = this.terminal.rows;
|
|
1486
|
+
if (this.#providerWindow.length === 0 || this.#providerViewportTop + this.#providerWindow.length < height) {
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
return Math.min(this.#providerViewportTop, height);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/** Whether native history holds rows the frame provider committed since the last destructive reset. */
|
|
1493
|
+
hasProviderHistory(): boolean {
|
|
1494
|
+
return this.#providerHistoryWritten && !this.#clearScrollbackOnNextRender;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1464
1497
|
/**
|
|
1465
1498
|
* Probe for opt-in normal-buffer click capture. The provider is read every
|
|
1466
1499
|
* frame; while it returns true (and no fullscreen overlay owns the
|
|
@@ -1544,6 +1577,34 @@ export class TUI extends Container {
|
|
|
1544
1577
|
|
|
1545
1578
|
start(options?: TUIStartOptions): void {
|
|
1546
1579
|
this.#stopped = false;
|
|
1580
|
+
this.#appearanceReportUnsubscribe?.();
|
|
1581
|
+
let backgroundRgb: number | undefined;
|
|
1582
|
+
let subscribed = true;
|
|
1583
|
+
const repaintForBackgroundChange = () => {
|
|
1584
|
+
if (!subscribed || this.#stopped) return;
|
|
1585
|
+
const nextBackgroundRgb = getTerminalBackgroundColor();
|
|
1586
|
+
// Track RGB before the SIXEL guard: the startup OSC 11 reply usually
|
|
1587
|
+
// arrives before DA1 enables SIXEL, so revisiting the same color once
|
|
1588
|
+
// SIXEL is live must not replay identical rows.
|
|
1589
|
+
if (nextBackgroundRgb === backgroundRgb) return;
|
|
1590
|
+
backgroundRgb = nextBackgroundRgb;
|
|
1591
|
+
if (TERMINAL.imageProtocol !== ImageProtocol.Sixel) return;
|
|
1592
|
+
// Track RGB even during optimistic native ownership: fallback paints
|
|
1593
|
+
// with the latest color, so a later return to the startup RGB must repaint.
|
|
1594
|
+
// Never write rows while the native surface still owns rendering.
|
|
1595
|
+
if (this.#nativeLive) return;
|
|
1596
|
+
this.invalidate();
|
|
1597
|
+
// Match theme changes: replay owned history on direct terminals, but
|
|
1598
|
+
// leave multiplexer scrollback intact and repaint only the viewport.
|
|
1599
|
+
this.requestRender(true, { clearScrollback: !isInsideTerminalMultiplexer() });
|
|
1600
|
+
};
|
|
1601
|
+
const unsubscribe = this.terminal.onAppearanceReport?.(repaintForBackgroundChange);
|
|
1602
|
+
const unsubscribeFallback = onTerminalBackgroundFallbackChange(repaintForBackgroundChange);
|
|
1603
|
+
this.#appearanceReportUnsubscribe = () => {
|
|
1604
|
+
subscribed = false;
|
|
1605
|
+
unsubscribe?.();
|
|
1606
|
+
unsubscribeFallback();
|
|
1607
|
+
};
|
|
1547
1608
|
this.#debugPaint = undefined;
|
|
1548
1609
|
this.#debugServer?.stop();
|
|
1549
1610
|
this.#debugServer = undefined;
|
|
@@ -1602,6 +1663,9 @@ export class TUI extends Container {
|
|
|
1602
1663
|
this.terminal.onSixelSupport?.(supported => {
|
|
1603
1664
|
if (!supported || this.#stopped || this.#nativeLive || this.terminal.tspExpected) return;
|
|
1604
1665
|
if (isImageProtocolForced()) return;
|
|
1666
|
+
// tmux answers DA1 for its own emulator; only its record of the
|
|
1667
|
+
// attached client says whether SIXEL reaches the outer terminal.
|
|
1668
|
+
if (tmuxMultiplexer.isInside() && !tmuxClientRendersSixel()) return;
|
|
1605
1669
|
// Keep consuming an already-requested XTSMGRAPHICS reply until it
|
|
1606
1670
|
// arrives or times out, even though DA1 has established support.
|
|
1607
1671
|
this.#enableSixelProtocol();
|
|
@@ -1668,16 +1732,14 @@ export class TUI extends Container {
|
|
|
1668
1732
|
() => this.stop(),
|
|
1669
1733
|
{ deferInput: this.#inputDeferred, isLoopStalled: () => this.#watchdog.isStalled() },
|
|
1670
1734
|
);
|
|
1735
|
+
// ProcessTerminal.start() resets the previous owner's RGB. Seed only
|
|
1736
|
+
// after that reset so an identical OSC 11 reply still repaints fallback rows.
|
|
1737
|
+
backgroundRgb = TERMINAL.imageProtocol === ImageProtocol.Sixel ? getTerminalBackgroundColor() : undefined;
|
|
1671
1738
|
if (this.#stopped) return;
|
|
1672
1739
|
this.#cancelPostmortemRestore?.();
|
|
1673
1740
|
this.#cancelPostmortemRestore = postmortem.register("tui-restore", () => this.stop());
|
|
1674
|
-
for (const listener of this.#startListeners)
|
|
1675
|
-
|
|
1676
|
-
listener();
|
|
1677
|
-
} catch {
|
|
1678
|
-
// Startup listeners are feature hooks; one broken hook must not prevent rendering.
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1741
|
+
for (const listener of this.#startListeners) this.#runStartListener(listener);
|
|
1742
|
+
this.#started = !this.#stopped;
|
|
1681
1743
|
this.terminal.hideCursor();
|
|
1682
1744
|
this.#recordHardwareCursorHidden();
|
|
1683
1745
|
if (!this.#inputDeferred) {
|
|
@@ -1819,7 +1881,7 @@ export class TUI extends Container {
|
|
|
1819
1881
|
this.#resizeSettleTimer?.cancel();
|
|
1820
1882
|
this.#resizeSettleTimer = undefined;
|
|
1821
1883
|
if (this.#altActive || this.#resizeAltActive) {
|
|
1822
|
-
this.terminal.write(`${this.#takePendingAltEnter()}${this.#
|
|
1884
|
+
this.terminal.write(`${this.#takePendingAltEnter()}${this.#altScreenExit()}`);
|
|
1823
1885
|
setAltScreenActive(false);
|
|
1824
1886
|
this.#altActive = false;
|
|
1825
1887
|
this.#resizeAltActive = false;
|
|
@@ -2133,7 +2195,7 @@ export class TUI extends Container {
|
|
|
2133
2195
|
this.#suppressResizeUntil = this.#renderScheduler.now() + 100;
|
|
2134
2196
|
this.#altPreviousLines = [];
|
|
2135
2197
|
this.#altPreparedRows = [];
|
|
2136
|
-
const exitSequence =
|
|
2198
|
+
const exitSequence = this.#altScreenExit();
|
|
2137
2199
|
// Only the provider plan frame emits a pending exit, and an exit already
|
|
2138
2200
|
// pending (a fused overlay close) owns that slot.
|
|
2139
2201
|
if (this.#frameProvider !== undefined && this.#pendingAltExit === "") {
|
|
@@ -2445,13 +2507,29 @@ export class TUI extends Container {
|
|
|
2445
2507
|
if (this.#heldInput !== undefined && this.#heldFocus === previous) this.#heldFocus = next;
|
|
2446
2508
|
}
|
|
2447
2509
|
|
|
2510
|
+
/**
|
|
2511
|
+
* Register a hook that re-enables terminal modes on every start(). A hook
|
|
2512
|
+
* added while the TUI is already running (a prepaint composer starts it
|
|
2513
|
+
* before the interactive mode wires its controllers) missed that start, so
|
|
2514
|
+
* it runs immediately instead of waiting for a stop/start cycle that may
|
|
2515
|
+
* never come.
|
|
2516
|
+
*/
|
|
2448
2517
|
addStartListener(listener: StartListener): () => void {
|
|
2449
2518
|
this.#startListeners.add(listener);
|
|
2519
|
+
if (this.#started) this.#runStartListener(listener);
|
|
2450
2520
|
return () => {
|
|
2451
2521
|
this.#startListeners.delete(listener);
|
|
2452
2522
|
};
|
|
2453
2523
|
}
|
|
2454
2524
|
|
|
2525
|
+
#runStartListener(listener: StartListener): void {
|
|
2526
|
+
try {
|
|
2527
|
+
listener();
|
|
2528
|
+
} catch {
|
|
2529
|
+
// Startup listeners are feature hooks; one broken hook must not prevent rendering.
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2455
2533
|
addInputListener(listener: InputListener): () => void {
|
|
2456
2534
|
this.#inputListeners.add(listener);
|
|
2457
2535
|
return () => {
|
|
@@ -2635,6 +2713,8 @@ export class TUI extends Container {
|
|
|
2635
2713
|
}
|
|
2636
2714
|
|
|
2637
2715
|
stop(): void {
|
|
2716
|
+
this.#appearanceReportUnsubscribe?.();
|
|
2717
|
+
this.#appearanceReportUnsubscribe = undefined;
|
|
2638
2718
|
this.#cancelPostmortemRestore?.();
|
|
2639
2719
|
this.#cancelPostmortemRestore = undefined;
|
|
2640
2720
|
this.#debugServer?.stop();
|
|
@@ -2662,7 +2742,7 @@ export class TUI extends Container {
|
|
|
2662
2742
|
this.#cancelResizeProbe();
|
|
2663
2743
|
if (this.#resizeAltActive) {
|
|
2664
2744
|
this.#resizeAltActive = false;
|
|
2665
|
-
this.terminal.write(`${this.#takePendingAltEnter()}${this.#
|
|
2745
|
+
this.terminal.write(`${this.#takePendingAltEnter()}${this.#altScreenExit()}`);
|
|
2666
2746
|
setAltScreenActive(false);
|
|
2667
2747
|
}
|
|
2668
2748
|
if (this.#altActive || this.#pendingAltExit) {
|
|
@@ -2674,7 +2754,7 @@ export class TUI extends Container {
|
|
|
2674
2754
|
const mouseExit = this.#mouseTracking !== "off" ? MOUSE_TRACKING_OFF : "";
|
|
2675
2755
|
const exitSequence = this.#pendingAltExit
|
|
2676
2756
|
? `${this.#takePendingAltEnter()}${this.#pendingAltExit}${mouseExit}`
|
|
2677
|
-
: `${mouseExit}${this.#
|
|
2757
|
+
: `${mouseExit}${this.#altScreenExit()}`;
|
|
2678
2758
|
this.terminal.write(exitSequence);
|
|
2679
2759
|
setAltScreenActive(false);
|
|
2680
2760
|
this.#altActive = false;
|
|
@@ -2704,6 +2784,7 @@ export class TUI extends Container {
|
|
|
2704
2784
|
// (and live-session ghosts are already bounded by the inline-image budget).
|
|
2705
2785
|
this.#clearSixelProbeState();
|
|
2706
2786
|
this.#stopped = true;
|
|
2787
|
+
this.#started = false;
|
|
2707
2788
|
this.#watchdog.stop();
|
|
2708
2789
|
if (this.#renderTimer) {
|
|
2709
2790
|
this.#renderTimer.cancel();
|
|
@@ -2771,6 +2852,13 @@ export class TUI extends Container {
|
|
|
2771
2852
|
this.#requestOrdinaryRender();
|
|
2772
2853
|
}
|
|
2773
2854
|
|
|
2855
|
+
/** Run `callback` after `delayMs` on the render scheduler's clock, unless the TUI has stopped. */
|
|
2856
|
+
scheduleAfter(delayMs: number, callback: () => void): RenderTimer {
|
|
2857
|
+
return this.#renderScheduler.scheduleRender(() => {
|
|
2858
|
+
if (!this.#stopped) callback();
|
|
2859
|
+
}, delayMs);
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2774
2862
|
/**
|
|
2775
2863
|
* Paint a forced frame synchronously when startup must hand off an already
|
|
2776
2864
|
* visible component tree before further async initialization. Same as
|
|
@@ -2986,9 +3074,9 @@ export class TUI extends Container {
|
|
|
2986
3074
|
this.#inputRenderGraceUntilMs = this.#renderScheduler.now() + TUI.#INPUT_RENDER_GRACE_MS;
|
|
2987
3075
|
}
|
|
2988
3076
|
|
|
2989
|
-
//
|
|
3077
|
+
// The effective input owner can reserve its gesture before TUI-wide listeners
|
|
2990
3078
|
// and debug shortcuts see the key, then handle the raw event exactly once.
|
|
2991
|
-
const focusedForCapture = this.#
|
|
3079
|
+
const focusedForCapture = this.#inputTarget();
|
|
2992
3080
|
if (
|
|
2993
3081
|
focusedForCapture?.handleInput &&
|
|
2994
3082
|
(!isKeyRelease(data) || focusedForCapture.wantsKeyRelease) &&
|
|
@@ -3027,22 +3115,28 @@ export class TUI extends Container {
|
|
|
3027
3115
|
return;
|
|
3028
3116
|
}
|
|
3029
3117
|
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
// Opted-in components only dirty their focused subtree. Unregistered
|
|
3033
|
-
// components retain the legacy full compose because their callbacks may
|
|
3034
|
-
// mutate siblings; focus changes also require the new surface to paint.
|
|
3035
|
-
const focused = this.#focusedComponent;
|
|
3036
|
-
if (focused?.handleInput) {
|
|
3118
|
+
const target = this.#inputTarget();
|
|
3119
|
+
if (target?.handleInput) {
|
|
3037
3120
|
// Filter out key release events unless component opts in
|
|
3038
|
-
if (isKeyRelease(data) && !
|
|
3121
|
+
if (isKeyRelease(data) && !target.wantsKeyRelease) {
|
|
3039
3122
|
return;
|
|
3040
3123
|
}
|
|
3041
|
-
|
|
3124
|
+
target.handleInput(data);
|
|
3042
3125
|
this.requestRender();
|
|
3043
3126
|
}
|
|
3044
3127
|
}
|
|
3045
3128
|
|
|
3129
|
+
/**
|
|
3130
|
+
* Component that receives keyboard input. An input-less key-holding overlay
|
|
3131
|
+
* leaves input with the focus it displaced; other focus owners keep their input.
|
|
3132
|
+
*/
|
|
3133
|
+
#inputTarget(): Component | null {
|
|
3134
|
+
const focused = this.#focusedComponent;
|
|
3135
|
+
if (focused?.handleInput) return focused;
|
|
3136
|
+
const holder = this.#getKeyHolderOverlay();
|
|
3137
|
+
return holder?.component === focused ? holder.preFocus : null;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3046
3140
|
/**
|
|
3047
3141
|
* Queue a keystroke typed before the app installed its key handlers; input
|
|
3048
3142
|
* listeners see it once, on replay. The cell-size reply is a terminal report,
|
|
@@ -3278,7 +3372,12 @@ export class TUI extends Container {
|
|
|
3278
3372
|
return output;
|
|
3279
3373
|
}
|
|
3280
3374
|
|
|
3281
|
-
/**
|
|
3375
|
+
/**
|
|
3376
|
+
* Clip a Kitty direct placement and record its live owner; history
|
|
3377
|
+
* placements have no mutable owner. Other direct placements (SIXEL,
|
|
3378
|
+
* iTerm2) have no delete command and composite over whatever pixels they
|
|
3379
|
+
* land on, so the rows they paint above `screenRow` are erased first.
|
|
3380
|
+
*/
|
|
3282
3381
|
#imageLineSequence(
|
|
3283
3382
|
line: string,
|
|
3284
3383
|
screenRow: number,
|
|
@@ -3287,7 +3386,10 @@ export class TUI extends Container {
|
|
|
3287
3386
|
): string {
|
|
3288
3387
|
if (screenRow < 0) return line;
|
|
3289
3388
|
const parsed = parseKittyDirectPlacementLine(line);
|
|
3290
|
-
if (!parsed)
|
|
3389
|
+
if (!parsed) {
|
|
3390
|
+
const lift = Math.min(directPlacementLift(line), screenRow);
|
|
3391
|
+
return lift > 0 ? `\x1b[${lift}A${`${ERASE_LINE}\x1b[B`.repeat(lift)}${line}` : line;
|
|
3392
|
+
}
|
|
3291
3393
|
const previous = placements?.get(placementRow);
|
|
3292
3394
|
const placementId = previous?.placementId ?? this.#nextImagePlacementId++;
|
|
3293
3395
|
const erase = previous ? encodeKittyDeletePlacement(previous.imageId, previous.placementId) : "";
|
|
@@ -3569,6 +3671,7 @@ export class TUI extends Container {
|
|
|
3569
3671
|
this.#providerViewportTop = 0;
|
|
3570
3672
|
this.#providerWindow = [];
|
|
3571
3673
|
this.#providerPreparedRows = [];
|
|
3674
|
+
this.#providerHistoryWritten = false;
|
|
3572
3675
|
}
|
|
3573
3676
|
// The viewport stays anchored directly below whatever history remains on
|
|
3574
3677
|
// screen. Appending K history rows moves the anchor down by K; the write
|
|
@@ -3799,6 +3902,7 @@ export class TUI extends Container {
|
|
|
3799
3902
|
this.#resizeBurstWidthChanged = false;
|
|
3800
3903
|
this.#resizeBurstResized = false;
|
|
3801
3904
|
this.#previousFrameLength = mutablePreparedLines.length;
|
|
3905
|
+
if (preparedHistory.lines.length > 0 || replayViewportRows > 0) this.#providerHistoryWritten = true;
|
|
3802
3906
|
this.#clearScrollbackOnNextRender = false;
|
|
3803
3907
|
this.#forceViewportRepaintOnNextRender = false;
|
|
3804
3908
|
this.#hasEverRendered = true;
|
|
@@ -3899,8 +4003,7 @@ export class TUI extends Container {
|
|
|
3899
4003
|
// restore it in the fused exit or later frames see matching states
|
|
3900
4004
|
// and inline click/hover stays dead until the setting toggles.
|
|
3901
4005
|
const mouseEnter = wantMouse !== "off" && this.#mouseTracking === "off" ? MOUSE_TRACKING_ON : "";
|
|
3902
|
-
const
|
|
3903
|
-
const exitSequence = `${mouseExit}${mouseEnter}${enhancementExit}\x1b[?1049l`;
|
|
4006
|
+
const exitSequence = `${mouseExit}${mouseEnter}${this.#altScreenExit()}`;
|
|
3904
4007
|
// Session replacement finishes while its fullscreen selector still
|
|
3905
4008
|
// covers the old normal buffer. Fuse the restore into the destructive
|
|
3906
4009
|
// repaint so no stale frame can become visible between writes.
|
|
@@ -4443,14 +4546,15 @@ export class TUI extends Container {
|
|
|
4443
4546
|
}
|
|
4444
4547
|
|
|
4445
4548
|
/**
|
|
4446
|
-
*
|
|
4447
|
-
*
|
|
4448
|
-
*
|
|
4549
|
+
* Leave the alternate screen: pop its keyboard-enhancement frame first, then
|
|
4550
|
+
* re-assert the main-screen mode for terminals that lose it on `?1049l`.
|
|
4551
|
+
* Falls back to popping kitty whenever a custom Terminal exposes its push
|
|
4552
|
+
* sequence but predates the `keyboardEnhancementExitSequence` property.
|
|
4449
4553
|
*/
|
|
4450
|
-
#
|
|
4554
|
+
#altScreenExit(): string {
|
|
4451
4555
|
const exit = this.terminal.keyboardEnhancementExitSequence;
|
|
4452
|
-
|
|
4453
|
-
return this.terminal.
|
|
4556
|
+
const keyboardExit = exit !== undefined ? (exit ?? "") : this.terminal.kittyEnableSequence ? "\x1b[<u" : "";
|
|
4557
|
+
return `${keyboardExit}\x1b[?1049l${this.terminal.keyboardEnhancementRestoreSequence ?? ""}`;
|
|
4454
4558
|
}
|
|
4455
4559
|
|
|
4456
4560
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -180,9 +180,11 @@ export function truncateToWidth(
|
|
|
180
180
|
pad?: boolean | null,
|
|
181
181
|
): string {
|
|
182
182
|
maxWidth = Math.max(0, maxWidth | 0);
|
|
183
|
-
// Fast path: every UTF-16 unit is at most 3 cells wide, so a string
|
|
184
|
-
// `length * 3` already fits within `
|
|
185
|
-
|
|
183
|
+
// Fast path: every UTF-16 unit is at most 3 cells wide, so a plain string
|
|
184
|
+
// whose `length * 3` already fits within `maxWidth` cannot need truncation.
|
|
185
|
+
// OSC 66 spans are excluded because their display width is metadata-driven,
|
|
186
|
+
// not proportional to their UTF-16 length.
|
|
187
|
+
if (!pad && !text.includes("\x1b]66") && text.length * 3 <= maxWidth) {
|
|
186
188
|
return text;
|
|
187
189
|
}
|
|
188
190
|
return nativeTruncateToWidth(
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate the TypeScript method signatures advertising eval-bridged tools
|
|
3
|
-
* under Codex Code Mode (spliced into the `eval-code-mode.md` prompt),
|
|
4
|
-
* mirroring codex-rs augment_tool_spec_for_code_mode.
|
|
5
|
-
*/
|
|
6
|
-
import { arkToWireSchema, isArkSchema } from "@linxiraos/pi-ai/utils/schema/wire";
|
|
7
|
-
|
|
8
|
-
interface JsonSchema {
|
|
9
|
-
type?: string;
|
|
10
|
-
properties?: Record<string, JsonSchema>;
|
|
11
|
-
required?: string[];
|
|
12
|
-
items?: JsonSchema;
|
|
13
|
-
enum?: unknown[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
17
|
-
|
|
18
|
-
function tsType(schema: JsonSchema | undefined, depth: number): string {
|
|
19
|
-
if (!schema || depth > 2) return "unknown";
|
|
20
|
-
if (Array.isArray(schema.enum) && schema.enum.every(value => typeof value === "string")) {
|
|
21
|
-
return schema.enum.map(value => JSON.stringify(value)).join(" | ");
|
|
22
|
-
}
|
|
23
|
-
switch (schema.type) {
|
|
24
|
-
case "string":
|
|
25
|
-
return "string";
|
|
26
|
-
case "number":
|
|
27
|
-
case "integer":
|
|
28
|
-
return "number";
|
|
29
|
-
case "boolean":
|
|
30
|
-
return "boolean";
|
|
31
|
-
case "array": {
|
|
32
|
-
const item = tsType(schema.items, depth + 1);
|
|
33
|
-
// `"a" | "b"[]` parses as `"a" | ("b"[])`, so a union item needs parens.
|
|
34
|
-
return /[|&]/.test(item) ? `(${item})[]` : `${item}[]`;
|
|
35
|
-
}
|
|
36
|
-
case "object": {
|
|
37
|
-
if (!schema.properties) return "Record<string, unknown>";
|
|
38
|
-
const required = new Set(schema.required ?? []);
|
|
39
|
-
const entries = Object.entries(schema.properties).map(([key, value]) => {
|
|
40
|
-
const printedKey = TS_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
41
|
-
return `${printedKey}${required.has(key) ? "" : "?"}: ${tsType(value, depth + 1)}`;
|
|
42
|
-
});
|
|
43
|
-
return `{ ${entries.join("; ")} }`;
|
|
44
|
-
}
|
|
45
|
-
default:
|
|
46
|
-
return "unknown";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Format tool schemas as TypeScript declarations for code-mode discovery. */
|
|
51
|
-
export function generateCodeModeDeclarations(tools: ReadonlyArray<{ name: string; parameters: unknown }>): string {
|
|
52
|
-
const lines = tools.map(tool => {
|
|
53
|
-
const printedName = TS_IDENTIFIER.test(tool.name) ? tool.name : JSON.stringify(tool.name);
|
|
54
|
-
const wire = isArkSchema(tool.parameters)
|
|
55
|
-
? (arkToWireSchema(tool.parameters) as JsonSchema)
|
|
56
|
-
: (tool.parameters as JsonSchema | undefined);
|
|
57
|
-
const args = wire?.type === "object" && wire.properties ? tsType(wire, 0) : "unknown";
|
|
58
|
-
return ` ${printedName}(args: ${args}): Promise<unknown>;`;
|
|
59
|
-
});
|
|
60
|
-
return lines.join("\n");
|
|
61
|
-
}
|