@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
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* (`~/.zeta/agent/cache/composer.db`).
|
|
4
4
|
*
|
|
5
5
|
* Each row is one JSON payload keyed by project (the resolved cwd) and kind.
|
|
6
|
-
* Settings-derived kinds (theme/composer preferences,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* rows stay per project.
|
|
6
|
+
* Settings-derived kinds (theme/composer preferences, status-bar inputs) are
|
|
7
|
+
* also written under the empty project, so a folder that never ran zetacode still
|
|
8
|
+
* paints with the user's theme and status bar: those are rarely
|
|
9
|
+
* project-specific, and path/branch render live.
|
|
11
10
|
*
|
|
12
11
|
* ```text
|
|
13
12
|
* entries (project TEXT, kind TEXT, value TEXT JSON, PRIMARY KEY (project, kind))
|
|
@@ -25,7 +24,6 @@ import * as logger from "@linxiraos/pi-utils/logger";
|
|
|
25
24
|
import * as postmortem from "@linxiraos/pi-utils/postmortem";
|
|
26
25
|
import { openSqliteDatabaseSync } from "@linxiraos/pi-utils/sqlite";
|
|
27
26
|
import { isRecord } from "@linxiraos/pi-utils/type-guards";
|
|
28
|
-
import type { LspServerInfo, RecentSession } from "./welcome";
|
|
29
27
|
import type { ComposerPreferences, ComposerStatusCache } from "./composer";
|
|
30
28
|
import { readStatusLineStartupData } from "../status-line/startup";
|
|
31
29
|
import type { SymbolPreset } from "../theme/theme";
|
package/src/prompt/composer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { ProcessTerminal, type Terminal } from "../terminal";
|
|
|
8
8
|
import {
|
|
9
9
|
type Component,
|
|
10
10
|
Container,
|
|
11
|
+
type RenderTimer,
|
|
11
12
|
type ResizeScrollbackMode,
|
|
12
13
|
type TerminalFramePlan,
|
|
13
14
|
type TerminalFrameProvider,
|
|
@@ -22,10 +23,12 @@ import { postmortem } from "@linxiraos/pi-utils";
|
|
|
22
23
|
import { CustomEditor } from "./custom-editor";
|
|
23
24
|
import type { WordCompletionMethod } from "./word-completion";
|
|
24
25
|
import { type AnimationFrame, TranscriptContainer } from "../chrome/transcript-container";
|
|
25
|
-
import { WelcomeComponent
|
|
26
|
+
import { WelcomeComponent } from "./welcome";
|
|
26
27
|
import { ensureThemeSync, getEditorTheme, theme } from "../theme/theme";
|
|
27
28
|
|
|
28
29
|
const DOUBLE_INTERRUPT_MS = 500;
|
|
30
|
+
// How long idle chrome must hold its height after a turn before it is treated as permanent.
|
|
31
|
+
const IDLE_CHROME_SETTLE_MS = 250;
|
|
29
32
|
|
|
30
33
|
/** Live settings that affect the composer before and after session adoption. */
|
|
31
34
|
export interface ComposerPreferences {
|
|
@@ -58,11 +61,6 @@ export const COMPOSER_DEFAULTS: ComposerPreferences = {
|
|
|
58
61
|
/** Welcome data that can be supplied initially or patched as startup resolves it. */
|
|
59
62
|
export interface ComposerWelcomeUpdate {
|
|
60
63
|
readonly version?: string;
|
|
61
|
-
readonly modelName?: string;
|
|
62
|
-
readonly providerName?: string;
|
|
63
|
-
readonly recentSessions?: readonly RecentSession[];
|
|
64
|
-
/** Detected project servers; `null` means LSP is disabled and hides the welcome section. */
|
|
65
|
-
readonly lspServers?: readonly LspServerInfo[] | null;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
/**
|
|
@@ -91,18 +89,8 @@ export interface ComposerOptions {
|
|
|
91
89
|
readonly now?: () => number;
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
/** How {@link Composer.setRuntimeChildren}
|
|
92
|
+
/** How {@link Composer.setRuntimeChildren} lays out the roots below the transcript. */
|
|
95
93
|
export interface RuntimeChildrenOptions {
|
|
96
|
-
/**
|
|
97
|
-
* Below-transcript roots whose growth is momentary (inline dialogs, a tall
|
|
98
|
-
* multi-line editor). They are billed at their smallest height so expansion
|
|
99
|
-
* clips the live tail instead of retiring rows a later shrink could not
|
|
100
|
-
* reclaim (#11007). Every other root is billed at its current height, so
|
|
101
|
-
* settled rows it displaces retire to native scrollback. Decision panels
|
|
102
|
-
* declaring `retireDisplacedTranscript` opt out of this floor even when
|
|
103
|
-
* mounted as a child of a transient editor container.
|
|
104
|
-
*/
|
|
105
|
-
readonly transient?: readonly Component[];
|
|
106
94
|
/**
|
|
107
95
|
* Dock order on a TSP terminal (no status strip; the composer carries its
|
|
108
96
|
* facts): the native dock stacks HUD pills, the working row and queued messages over
|
|
@@ -110,6 +98,20 @@ export interface RuntimeChildrenOptions {
|
|
|
110
98
|
* to the roots below the transcript in render order.
|
|
111
99
|
*/
|
|
112
100
|
readonly nativeDock?: readonly Component[];
|
|
101
|
+
/**
|
|
102
|
+
* Below-transcript roots the user opens and closes (the editor surface that
|
|
103
|
+
* drafts and dialogs swap into, docked reports). Their rows never become
|
|
104
|
+
* part of the post-run chrome floor, so collapsing them refills the screen.
|
|
105
|
+
*/
|
|
106
|
+
readonly transientChrome?: readonly Component[];
|
|
107
|
+
/**
|
|
108
|
+
* Transient roots that clear themselves (todo and subagent HUDs, progress).
|
|
109
|
+
* Their first paint can be their tallest, e.g. a restored todo list on a
|
|
110
|
+
* resumed session, so unlike {@link transientChrome} it is never treated as
|
|
111
|
+
* their resting height: all of their rows stay out of the floor. Roots listed
|
|
112
|
+
* here are transient and need not also appear in `transientChrome`.
|
|
113
|
+
*/
|
|
114
|
+
readonly selfClearingChrome?: readonly Component[];
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
/** Controls the first terminal paint for a composer that does not already own the terminal. */
|
|
@@ -215,10 +217,6 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
215
217
|
#preferences: ComposerPreferences;
|
|
216
218
|
#welcome: WelcomeComponent | undefined;
|
|
217
219
|
#version = "";
|
|
218
|
-
#modelName = "";
|
|
219
|
-
#providerName = "";
|
|
220
|
-
#recentSessions: RecentSession[] = [];
|
|
221
|
-
#lspServers: LspServerInfo[] | null = [];
|
|
222
220
|
#headerBefore: readonly Component[] = [];
|
|
223
221
|
#headerAfter: readonly Component[] = [];
|
|
224
222
|
#runtimeChildren: readonly Component[] = [];
|
|
@@ -266,19 +264,42 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
266
264
|
#retiredHeaderStart = 0;
|
|
267
265
|
#resizeRetiredHeaderStart: number | undefined;
|
|
268
266
|
#lastNormalRows = 0;
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
// The floor is terminal-height independent —
|
|
277
|
-
//
|
|
278
|
-
//
|
|
267
|
+
// Retirement bills below-transcript chrome at this floor, not its current
|
|
268
|
+
// height. It follows contractions immediately and promotes stable eligible
|
|
269
|
+
// growth only while idle. Transient roots count at their minimum painted
|
|
270
|
+
// height: a growing dialog or draft clips the live tail without committing
|
|
271
|
+
// displaced rows, so those rows refill the screen when it shrinks (#11007).
|
|
272
|
+
// Displaced rows reach history when newer output pushes them past the floor
|
|
273
|
+
// or persistent idle chrome settles and raises it.
|
|
274
|
+
// The floor is terminal-height independent — chrome heights do not scale
|
|
275
|
+
// with rows — so it is retained across resizes rather than rediscovered
|
|
276
|
+
// from whatever chrome is expanded when the height changes.
|
|
277
|
+
#chromeFloor: number | undefined;
|
|
278
|
+
// Settled idle chrome has retired rows; contraction must preserve native history.
|
|
279
|
+
#chromeFloorPromoted = false;
|
|
280
|
+
#runActive = false;
|
|
279
281
|
#transientChrome: ReadonlySet<Component> = new Set();
|
|
280
|
-
#
|
|
281
|
-
|
|
282
|
+
#selfClearingChrome: ReadonlySet<Component> = new Set();
|
|
283
|
+
// Fewest rows the user-driven transient roots have painted since mount; only rows above it are excluded from the floor.
|
|
284
|
+
#transientFloor: number | undefined;
|
|
285
|
+
// Editor and shape the transient floor was measured under, plus the last frame's transient rows.
|
|
286
|
+
// A shape swap (band → pi) changes the editor's resting height; a minimum carried over from the
|
|
287
|
+
// shorter shape would exclude the taller shape's fixed rows forever and clip that many transcript rows.
|
|
288
|
+
#transientEditor: Component | undefined;
|
|
289
|
+
#transientEditorStyle: string | undefined;
|
|
290
|
+
#lastTransientRows = 0;
|
|
291
|
+
// Fewest rows the bare editor painted before the runtime mounted, and the shape it was painted in.
|
|
292
|
+
// A draft replayed from the startup keystroke buffer can already be present on the first runtime
|
|
293
|
+
// frame, so that frame is not the editor's resting height; this is, because the bootstrap frame
|
|
294
|
+
// paints before replay. Only valid while the shape is unchanged.
|
|
295
|
+
#bootstrapEditorRows: number | undefined;
|
|
296
|
+
#bootstrapEditorStyle: string | undefined;
|
|
297
|
+
// Idle chrome height awaiting the settle window, and whether it has elapsed.
|
|
298
|
+
// Turn-scoped HUDs can close a few frames after the last block finalizes;
|
|
299
|
+
// billing them first would commit rows they cover and strand a band.
|
|
300
|
+
#idleChrome: number | undefined;
|
|
301
|
+
#idleChromeSettled = false;
|
|
302
|
+
#idleChromeTimer: RenderTimer | undefined;
|
|
282
303
|
/** Rows the chrome below each below-transcript root took in the last frame (see {@link rowsBelow}). */
|
|
283
304
|
#rowsBelow = new Map<Component, number>();
|
|
284
305
|
#lastInterruptAt = 0;
|
|
@@ -348,6 +369,9 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
348
369
|
this.ui.addChild(this.editor);
|
|
349
370
|
this.ui.addChild(this.#statusHost);
|
|
350
371
|
this.ui.setFocus(this.editor);
|
|
372
|
+
// A settle timer that fires while the TUI is stopped (external editor, suspend) is dropped;
|
|
373
|
+
// forget it on restart so the next frame re-arms instead of waiting on it forever.
|
|
374
|
+
this.ui.addStartListener(() => this.#resetIdleChrome());
|
|
351
375
|
}
|
|
352
376
|
/**
|
|
353
377
|
* Rows the below-transcript chrome under `root` (editor, status line, …)
|
|
@@ -358,18 +382,6 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
358
382
|
return this.#rowsBelow.get(root);
|
|
359
383
|
}
|
|
360
384
|
|
|
361
|
-
/**
|
|
362
|
-
* Keep the input on the bottom row while the live rows cannot fill the
|
|
363
|
-
* screen, as after an inline decision panel closes. A tall block that just
|
|
364
|
-
* left the chrome above the editor (a command report) may have scrolled
|
|
365
|
-
* rows into native history that cannot be pulled back; without the pin the
|
|
366
|
-
* editor would jump up to where the shorter frame now ends. The pin lifts
|
|
367
|
-
* once live rows fill the screen again.
|
|
368
|
-
*/
|
|
369
|
-
pinInputToBottom(): void {
|
|
370
|
-
this.#anchorAfterInlineRetirement = true;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
385
|
/** Compose the bounded mutable viewport and the next ordered history append. */
|
|
374
386
|
renderFrame(viewport: ViewportSize): TerminalFramePlan {
|
|
375
387
|
if (!this.#started || this.#stopped) return { viewport: [] };
|
|
@@ -386,62 +398,56 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
386
398
|
const transcriptIndex = roots.findIndex(root => root instanceof TranscriptContainer);
|
|
387
399
|
if (transcriptIndex < 0) {
|
|
388
400
|
this.#lastClickSpans = [];
|
|
389
|
-
|
|
401
|
+
const viewport: string[] = [];
|
|
402
|
+
for (const root of roots) {
|
|
403
|
+
const rendered = root.render(width);
|
|
404
|
+
if (!this.#runtimeMounted && root === this.editor) this.#recordBootstrapEditorRows(rendered.length);
|
|
405
|
+
viewport.push(...rendered);
|
|
406
|
+
}
|
|
407
|
+
return { viewport: viewport.slice(-rows) };
|
|
390
408
|
}
|
|
391
409
|
const transcript = roots[transcriptIndex] as TranscriptContainer;
|
|
392
410
|
const preRoots = this.#renderRoots(roots.slice(0, transcriptIndex), width);
|
|
393
411
|
const afterRoots = roots.slice(transcriptIndex + 1);
|
|
394
412
|
const after: string[] = [];
|
|
395
413
|
const afterSpans: ViewportClickSpan[] = [];
|
|
396
|
-
let transientRows = 0;
|
|
397
|
-
let displacingRows = 0;
|
|
398
|
-
let decisionPanelOpen = false;
|
|
399
414
|
const ends: { root: Component; end: number }[] = [];
|
|
415
|
+
let transientRows = 0;
|
|
416
|
+
let selfClearingRows = 0;
|
|
400
417
|
for (const root of afterRoots) {
|
|
401
|
-
const chrome: Component = root;
|
|
402
418
|
const start = after.length;
|
|
403
419
|
this.#renderBelowRoot(root, width, after, afterSpans);
|
|
420
|
+
if (this.#selfClearingChrome.has(root)) selfClearingRows += after.length - start;
|
|
421
|
+
else if (this.#transientChrome.has(root)) transientRows += after.length - start;
|
|
404
422
|
ends.push({ root, end: after.length });
|
|
405
|
-
if (this.#transientChrome.has(root)) transientRows += after.length - start;
|
|
406
|
-
if (
|
|
407
|
-
chrome.retireDisplacedTranscript ||
|
|
408
|
-
(root instanceof Container && root.children.some(child => child.retireDisplacedTranscript))
|
|
409
|
-
) {
|
|
410
|
-
if (this.#transientChrome.has(root)) displacingRows += after.length - start;
|
|
411
|
-
decisionPanelOpen = true;
|
|
412
|
-
}
|
|
413
423
|
}
|
|
414
424
|
this.#rowsBelow = new Map(ends.map(({ root, end }) => [root, after.length - end]));
|
|
425
|
+
const transientFloor = this.#syncTransientFloor(transientRows);
|
|
426
|
+
// Eligible chrome excludes user-driven transient rows above their resting height and every
|
|
427
|
+
// self-clearing row; contraction, floor and rebaseline all use it.
|
|
428
|
+
const eligibleChrome = after.length - (transientRows - transientFloor) - selfClearingRows;
|
|
429
|
+
const promotedChromeContracted = this.#chromeFloorPromoted && eligibleChrome < (this.#chromeFloor ?? 0);
|
|
430
|
+
this.#chromeFloor = Math.min(this.#chromeFloor ?? eligibleChrome, eligibleChrome);
|
|
431
|
+
this.#rebaselineChromeFloor(this.#runActive || transcript.hasActiveBlock(), eligibleChrome);
|
|
432
|
+
const belowFloor = this.#chromeFloor;
|
|
415
433
|
// Offer history under capacity pressure only: blocks stay live (and keep
|
|
416
434
|
// reflowing to the current width) while the screen has room. A batch
|
|
417
435
|
// leaves the mutable viewport in the same frame it is appended, so its
|
|
418
436
|
// rows are never painted twice.
|
|
419
|
-
//
|
|
420
|
-
// Retirement bills ordinary transient roots at their floor, not their
|
|
421
|
-
// peak: a confirmation dialog or a tall multi-line editor clips the live tail for
|
|
422
|
-
// its lifetime, but must not permanently commit transcript rows to native
|
|
423
|
-
// history — otherwise a later shrink cannot refill the freed rows and the
|
|
424
|
-
// editor drifts up above a band of blank rows (#11007).
|
|
425
|
-
this.#transientChromeFloor = Math.min(this.#transientChromeFloor ?? transientRows, transientRows);
|
|
426
|
-
// An ask panel remains open while the user reads the preceding response.
|
|
427
|
-
// Its displaced settled rows must be reachable in scrollback now, rather
|
|
428
|
-
// than clipped until the panel closes. Ordinary drafts retain their floor.
|
|
429
|
-
const belowFloor = after.length - transientRows + Math.max(this.#transientChromeFloor, displacingRows);
|
|
430
437
|
const now = performance.now();
|
|
431
438
|
const frame: AnimationFrame = { now, tick: Math.floor(now / 80) };
|
|
432
439
|
// Retirement measures the same live blocks the viewport lays out below;
|
|
433
440
|
// one open frame renders each of them once for both.
|
|
434
441
|
transcript.beginFrame(frame);
|
|
435
442
|
const history = this.#offerHistory(transcript, width, rows, preRoots.length + belowFloor);
|
|
436
|
-
if (decisionPanelOpen && history !== undefined) this.#anchorAfterInlineRetirement = true;
|
|
437
443
|
const headerVisible = !this.#headerRetired && this.#offeredHistory?.source !== "header";
|
|
438
444
|
const headerRows = headerVisible ? this.#header.render(width) : [];
|
|
439
445
|
const before = [...headerRows, ...preRoots];
|
|
440
446
|
// The live tail is laid out against the same baseline retirement is
|
|
441
447
|
// billed against, so its compaction allocator (one row per block, no
|
|
442
448
|
// inter-block blanks) engages only when a block genuinely cannot retire.
|
|
443
|
-
// Rows the
|
|
444
|
-
// the `drop` slice below
|
|
449
|
+
// Rows the chrome above its floor displaces are clipped from the top by
|
|
450
|
+
// the `drop` slice below; they stay live until the chrome shrinks.
|
|
445
451
|
const active = transcript.renderViewport(width, Math.max(0, rows - before.length - belowFloor), frame);
|
|
446
452
|
const activeSpans: ViewportClickSpan[] = [];
|
|
447
453
|
for (const span of transcript.getLastViewportSpans()) {
|
|
@@ -451,13 +457,39 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
451
457
|
}
|
|
452
458
|
const drop = Math.max(0, before.length + active.length + after.length - rows);
|
|
453
459
|
const mutable = [...before, ...active, ...after].slice(drop);
|
|
454
|
-
// Once
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
460
|
+
// Once a paint reaches the bottom row over committed history, rows above
|
|
461
|
+
// it may already be in native scrollback, which no later frame can pull
|
|
462
|
+
// back down. Keep the input on the bottom row whenever the frame shrinks
|
|
463
|
+
// — a closing dialog, report or HUD, or a live block collapsing. The
|
|
464
|
+
// slack that leaves has to sit somewhere: rows above the live area hold
|
|
465
|
+
// committed history still on screen, so the live rows cannot move up into
|
|
466
|
+
// them, and replaying those rows would duplicate them while clearing
|
|
467
|
+
// history would lose them. Replays position themselves. Without native
|
|
468
|
+
// history the live viewport is the whole document and shrinks in place.
|
|
469
|
+
const filledHistoryRows =
|
|
470
|
+
history?.kind === "replay" || !(history !== undefined || this.ui.hasProviderHistory())
|
|
471
|
+
? undefined
|
|
472
|
+
: this.ui.getFilledHistoryRows();
|
|
473
|
+
// Once settled chrome has retired rows, shrinking it cannot pull those
|
|
474
|
+
// rows back out of native scrollback. Let the editor move up instead of
|
|
475
|
+
// inserting a seam band; subsequent frames retain that natural anchor
|
|
476
|
+
// until new output fills the screen again. Shell history stays untouched.
|
|
477
|
+
const slack =
|
|
478
|
+
filledHistoryRows === undefined || promotedChromeContracted
|
|
479
|
+
? 0
|
|
480
|
+
: Math.max(0, rows - Math.min(rows, filledHistoryRows + (history?.rows.length ?? 0)) - mutable.length);
|
|
481
|
+
// Park the slack below the live rows rather than above them. Above, it
|
|
482
|
+
// splits the on-screen history from the live tail and reads as a hole
|
|
483
|
+
// punched into whatever was streaming — a table's rows included — and
|
|
484
|
+
// each slack row is written with no cells, so no repaint, mark or
|
|
485
|
+
// selection can cover it until something scrolls it away. Below, the
|
|
486
|
+
// transcript stays contiguous with the history, the slack reads as the
|
|
487
|
+
// empty tail of a short document, and growth consumes it before any row
|
|
488
|
+
// can scroll.
|
|
489
|
+
if (slack > 0) {
|
|
490
|
+
const at = Math.max(0, Math.min(mutable.length, before.length + active.length - drop));
|
|
491
|
+
mutable.splice(at, 0, ...Array<string>(slack).fill(""));
|
|
492
|
+
}
|
|
461
493
|
const viewportLength = mutable.length;
|
|
462
494
|
const spans: ViewportClickSpan[] = [];
|
|
463
495
|
const shift = (span: ViewportClickSpan, base: number): void => {
|
|
@@ -471,8 +503,8 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
471
503
|
spans.push({ start: clamped, end, candidates: (local: number) => span.candidates(local + skew) });
|
|
472
504
|
}
|
|
473
505
|
};
|
|
474
|
-
for (const span of activeSpans) shift(span,
|
|
475
|
-
for (const span of afterSpans) shift(span,
|
|
506
|
+
for (const span of activeSpans) shift(span, before.length - drop);
|
|
507
|
+
for (const span of afterSpans) shift(span, slack + before.length + active.length - drop);
|
|
476
508
|
this.#lastClickSpans = spans;
|
|
477
509
|
if (history !== undefined && this.#offeredHistory?.source === "header") {
|
|
478
510
|
const visibleHeaderRows = Math.max(0, rows - (mutable.length + drop));
|
|
@@ -681,6 +713,71 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
681
713
|
}
|
|
682
714
|
}
|
|
683
715
|
|
|
716
|
+
/** Raise the floor to eligible idle chrome that survives the settle window, even if it appeared after the run. */
|
|
717
|
+
#rebaselineChromeFloor(busy: boolean, chrome: number): void {
|
|
718
|
+
if (busy || chrome <= (this.#chromeFloor ?? chrome)) {
|
|
719
|
+
this.#resetIdleChrome();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (this.#idleChromeSettled && this.#idleChrome === chrome) {
|
|
723
|
+
this.#chromeFloorPromoted = true;
|
|
724
|
+
this.#chromeFloor = chrome;
|
|
725
|
+
this.#resetIdleChrome();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (this.#idleChrome === chrome && this.#idleChromeTimer !== undefined) return;
|
|
729
|
+
this.#resetIdleChrome();
|
|
730
|
+
this.#idleChrome = chrome;
|
|
731
|
+
this.#idleChromeTimer = this.ui.scheduleAfter(IDLE_CHROME_SETTLE_MS, () => {
|
|
732
|
+
this.#idleChromeTimer = undefined;
|
|
733
|
+
this.#idleChromeSettled = true;
|
|
734
|
+
this.ui.requestRender();
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
#resetIdleChrome(): void {
|
|
739
|
+
this.#idleChromeTimer?.cancel();
|
|
740
|
+
this.#idleChromeTimer = undefined;
|
|
741
|
+
this.#idleChrome = undefined;
|
|
742
|
+
this.#idleChromeSettled = false;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Track the empty editor's pre-mount height per shape; a shape change restarts the measurement. */
|
|
746
|
+
#recordBootstrapEditorRows(rows: number): void {
|
|
747
|
+
const style = this.editor.getBorderStyle();
|
|
748
|
+
this.#bootstrapEditorRows =
|
|
749
|
+
style === this.#bootstrapEditorStyle ? Math.min(this.#bootstrapEditorRows ?? rows, rows) : rows;
|
|
750
|
+
this.#bootstrapEditorStyle = style;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Follow the transient roots' resting height. Normally a running minimum; when the editor or its
|
|
755
|
+
* shape changes, the floor shifts by that frame's height change instead, so draft growth stays
|
|
756
|
+
* excluded while the new shape's fixed rows are billed.
|
|
757
|
+
*/
|
|
758
|
+
#syncTransientFloor(transientRows: number): number {
|
|
759
|
+
const editor = this.editor;
|
|
760
|
+
const style = editor.getBorderStyle();
|
|
761
|
+
let floor: number;
|
|
762
|
+
if (this.#transientFloor === undefined) {
|
|
763
|
+
// The first runtime frame may already carry a replayed startup draft; seed from the empty
|
|
764
|
+
// editor's pre-mount height when it was painted in the same shape.
|
|
765
|
+
const bootstrap = this.#bootstrapEditorStyle === style ? this.#bootstrapEditorRows : undefined;
|
|
766
|
+
floor = bootstrap === undefined ? transientRows : Math.min(transientRows, bootstrap);
|
|
767
|
+
} else if (editor !== this.#transientEditor || style !== this.#transientEditorStyle) {
|
|
768
|
+
floor = Math.max(0, Math.min(transientRows, this.#transientFloor + transientRows - this.#lastTransientRows));
|
|
769
|
+
} else {
|
|
770
|
+
floor = Math.min(this.#transientFloor, transientRows);
|
|
771
|
+
}
|
|
772
|
+
this.#transientFloor = floor;
|
|
773
|
+
this.#bootstrapEditorRows = undefined;
|
|
774
|
+
this.#bootstrapEditorStyle = undefined;
|
|
775
|
+
this.#transientEditor = editor;
|
|
776
|
+
this.#transientEditorStyle = style;
|
|
777
|
+
this.#lastTransientRows = transientRows;
|
|
778
|
+
return floor;
|
|
779
|
+
}
|
|
780
|
+
|
|
684
781
|
/** Header retires first; replay coalesces it with the complete transcript ledger. */
|
|
685
782
|
#offerHistory(
|
|
686
783
|
transcript: TranscriptContainer,
|
|
@@ -894,7 +991,17 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
894
991
|
const welcome = this.#welcome;
|
|
895
992
|
if (!welcome) return;
|
|
896
993
|
if (update.version !== undefined) welcome.setVersion(this.#version);
|
|
897
|
-
|
|
994
|
+
this.ui.requestRender();
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Mark whether a host agent run is in flight. Mid-run gaps with no streaming
|
|
999
|
+
* block (tool arguments not yet parseable, between tool calls) then do not
|
|
1000
|
+
* count as the run ending when the post-run chrome floor is decided.
|
|
1001
|
+
*/
|
|
1002
|
+
setRunActive(active: boolean): void {
|
|
1003
|
+
if (this.#runActive === active) return;
|
|
1004
|
+
this.#runActive = active;
|
|
898
1005
|
this.ui.requestRender();
|
|
899
1006
|
}
|
|
900
1007
|
|
|
@@ -934,10 +1041,13 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
934
1041
|
/** Mount or replace session-aware root children while preserving the header and status hosts. */
|
|
935
1042
|
setRuntimeChildren(children: readonly Component[], options: RuntimeChildrenOptions = {}): void {
|
|
936
1043
|
if (this.#stopped) return;
|
|
937
|
-
this.#
|
|
938
|
-
this.#
|
|
939
|
-
this.#
|
|
1044
|
+
this.#chromeFloor = undefined;
|
|
1045
|
+
this.#chromeFloorPromoted = false;
|
|
1046
|
+
this.#resetIdleChrome();
|
|
940
1047
|
this.#nativeDock = options.nativeDock;
|
|
1048
|
+
this.#transientChrome = new Set(options.transientChrome);
|
|
1049
|
+
this.#selfClearingChrome = new Set(options.selfClearingChrome);
|
|
1050
|
+
this.#transientFloor = undefined;
|
|
941
1051
|
this.ui.removeChild(this.#statusHost);
|
|
942
1052
|
if (this.#runtimeMounted) {
|
|
943
1053
|
for (const child of this.#runtimeChildren) this.ui.removeChild(child);
|
|
@@ -976,20 +1086,10 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
976
1086
|
|
|
977
1087
|
#applyWelcomeUpdate(update: ComposerWelcomeUpdate): void {
|
|
978
1088
|
if (update.version !== undefined) this.#version = update.version;
|
|
979
|
-
if (update.modelName !== undefined || update.providerName !== undefined) {
|
|
980
|
-
this.#modelName = update.modelName ?? this.#modelName;
|
|
981
|
-
this.#providerName = update.providerName ?? this.#providerName;
|
|
982
|
-
}
|
|
983
1089
|
}
|
|
984
1090
|
|
|
985
1091
|
#ensureWelcome(): void {
|
|
986
|
-
this.#welcome ??= new WelcomeComponent(
|
|
987
|
-
this.#version,
|
|
988
|
-
this.#modelName,
|
|
989
|
-
this.#providerName,
|
|
990
|
-
this.#recentSessions,
|
|
991
|
-
this.#lspServers,
|
|
992
|
-
);
|
|
1092
|
+
this.#welcome ??= new WelcomeComponent(this.#version);
|
|
993
1093
|
}
|
|
994
1094
|
|
|
995
1095
|
#rebuildHeader(): void {
|