@oh-my-pi/pi-tui 18.13.2 → 18.13.3
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 +24 -14
- package/dist/types/apps/session-picker.d.ts +4 -0
- package/dist/types/chat/assistant-message.d.ts +1 -1
- package/dist/types/chat/bash-execution.d.ts +2 -0
- package/dist/types/chat/display-preferences.d.ts +2 -0
- package/dist/types/chat/tool-execution.d.ts +1 -1
- package/dist/types/components/editor.d.ts +2 -0
- package/dist/types/overlays/annotation-overlay.d.ts +7 -1
- package/dist/types/overlays/annotation-types.d.ts +28 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +13 -2
- package/dist/types/theme/theme.d.ts +0 -23
- package/dist/types/tools/bash-interactive.d.ts +6 -0
- package/dist/types/tui.d.ts +4 -1
- package/package.json +10 -10
- package/src/apps/session-picker.ts +5 -0
- package/src/chat/advisor-message.ts +2 -0
- package/src/chat/assistant-message.ts +3 -1
- package/src/chat/bash-execution.ts +6 -0
- package/src/chat/display-preferences.ts +3 -0
- package/src/chat/tool-execution.ts +7 -3
- package/src/components/editor.ts +3 -1
- package/src/native/backend.ts +2 -64
- package/src/overlays/annotation-overlay.ts +125 -37
- package/src/overlays/annotation-types.ts +24 -0
- package/src/overlays/hook-selector.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +13 -2
- package/src/overlays/session-selector.ts +195 -26
- package/src/overlays/usage-dashboard.ts +5 -12
- package/src/prompt/custom-editor.ts +6 -1
- package/src/terminal.ts +24 -2
- package/src/theme/theme.ts +0 -65
- package/src/tools/bash-interactive.ts +6 -1
- package/src/tools/job-widget.ts +13 -1
- package/src/tools/json-tree.ts +1 -1
- package/src/tui.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.13.3] - 2026-10-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- The resume picker renames the selected session in place: F2 opens a dialog prefilled with the current title, Enter commits, Esc cancels ([#17262](https://github.com/can1357/oh-my-pi/pull/17262) by [@Twilight0](https://github.com/Twilight0))
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Updated Bun engine requirement to ≥1.4.4
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Restored WebP and JPEG images stay visible in Kitty scrollback after resuming a session ([#17220](https://github.com/can1357/oh-my-pi/issues/17220)).
|
|
18
|
+
- Escape in an annotation note no longer discards it: the overlay closes and `getDraft()`/`restoreDraft()` let the host keep and reopen the note; **Discard notes and close** (confirmed by a second press) clears them instead, and code review's actions start on Paste so a stray Enter never sends the LLM review ([#17271](https://github.com/can1357/oh-my-pi/pull/17271) by [@H4vC](https://github.com/H4vC), based on [#17230](https://github.com/can1357/oh-my-pi/pull/17230) by [@Shadorain](https://github.com/Shadorain)).
|
|
19
|
+
- Fixed terminal replies such as Tern's `tsp;e;{"ev":"ack",…}` showing up in the shell prompt after quitting omp; the exit drain now waits for the terminal to answer a final DA1 sentinel
|
|
20
|
+
- Fixed the plan review sheet in Tern squeezing the "continue with" model tiers under a long plan, so the model name overlapped the tier buttons
|
|
21
|
+
- `/usage` in Tern shows when each quota resets as a countdown (`resets in 2h30m`) instead of a clock time; hovering shows the date and time
|
|
22
|
+
- Fixed extension `ui.select` dialogs treating unconfigured `j`/`k` keys as navigation, which could turn a default No into Yes when typing `ok` then Enter ([#17255](https://github.com/can1357/oh-my-pi/pull/17255) by [@SAKANA-MOONLIGHT](https://github.com/SAKANA-MOONLIGHT)).
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- Removed support for the TSP `t` (theme) verb, as surface theme synchronization is no longer required
|
|
27
|
+
|
|
5
28
|
## [18.13.2] - 2026-10-27
|
|
6
29
|
|
|
7
30
|
### Added
|
|
@@ -2475,17 +2498,4 @@
|
|
|
2475
2498
|
- Fixed TUI shutdown placing the parent shell prompt one row below short rendered content instead of directly on the next line ([#1620](https://github.com/can1357/oh-my-pi/issues/1620)).
|
|
2476
2499
|
- Stopped painting inline color swatches for 4-digit hex runs in Markdown rendering. The `#RGBA` CSS form collides with hashline `#TAG` snapshot tags (4 hex digits, e.g. `#6C5E`), which were sprouting spurious RGB swatches in prose and codespans. Only `#RGB`, `#RRGGBB`, and `#RRGGBBAA` qualify now.
|
|
2477
2500
|
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
### Fixed
|
|
2481
|
-
|
|
2482
|
-
- Fixed native Windows + Windows Terminal freezing the editor on the wrap keystroke, on `/plan`/`/resume`/model-switch/status-line toggles, and on any other offscreen structural mutation until the next prompt submit. The `15.7.5` `#1635` fix routed every viewport-saturating pure-append and structural mutation through `deferredMutation` (a literal no-op) whenever `isNativeViewportAtBottom()` returned `undefined` — which it always does under `WT_SESSION` because the kernel32 probe can't see WT host scrollback. The deferral was only ever meant for the *confirmed-scrolled* case; an unknown viewport now falls back to a non-destructive `viewportRepaint` instead, so the live UI keeps updating without emitting `\x1b[3J` and without yanking a possibly-scrolled reader. Confirmed-scrolled frames (probe returns `false`) still defer.
|
|
2483
|
-
- Removed the hard-coded 20-result cap on `@`-prefixed fuzzy file completion in `CombinedAutocompleteProvider.#getFuzzyFileSuggestions`. The dropdown now honors the existing `maxResults: 100` ceiling already configured for `fuzzyFind`, so projects with many files sharing a common stem (e.g. `@controller`, `@test`) surface all relevant matches instead of being silently truncated. ([#1652](https://github.com/can1357/oh-my-pi/issues/1652))
|
|
2484
|
-
|
|
2485
|
-
## [15.7.5] - 2026-06-01
|
|
2486
|
-
|
|
2487
|
-
### Fixed
|
|
2488
|
-
|
|
2489
|
-
- Fixed native Windows + Windows Terminal scrollback being yanked to the top when a streaming response triggered a TUI full redraw. Under ConPTY the `kernel32` `GetConsoleScreenBufferInfo` probe answers about the pseudo-console (always at the buffer tail) and not about WT's host scrollback, so `isNativeViewportAtBottom()` falsely returned `true` while the user was scrolled up and the shrink-across-viewport branch issued a destructive `historyRebuild` (`\x1b[2J\x1b[H\x1b[3J`). The probe now short-circuits to `undefined` whenever `WT_SESSION` is set, letting the existing deferred-rebuild path keep streaming-time mutations non-destructive and reconcile native history at the next prompt-submit checkpoint. ([#1635](https://github.com/can1357/oh-my-pi/issues/1635))
|
|
2490
|
-
|
|
2491
|
-
Older entries are archived in [packages/tui/CHANGELOG.md@3d45d8557cd7](https://github.com/can1357/oh-my-pi/blob/3d45d8557cd7493221b69c7ed3c6b6a2307f46e7/packages/tui/CHANGELOG.md).
|
|
2501
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@3deb6751027b](https://github.com/can1357/oh-my-pi/blob/3deb6751027b11b10ac14b2a5c07303817276505/packages/tui/CHANGELOG.md).
|
|
@@ -4,6 +4,8 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
|
|
|
4
4
|
loadPinnedIds?(): Promise<ReadonlySet<string>>;
|
|
5
5
|
loadHistoryMatcher?(): SessionHistoryMatcher;
|
|
6
6
|
deleteSession?(session: T): Promise<boolean>;
|
|
7
|
+
/** Rename the session and resolve with the title as stored, or null when it did not happen. */
|
|
8
|
+
renameSession?(session: T, title: string): Promise<string | null>;
|
|
7
9
|
loadAllSessions?(): Promise<T[]>;
|
|
8
10
|
}
|
|
9
11
|
/** Presentation and capability controls for the standalone session picker. */
|
|
@@ -13,6 +15,8 @@ export interface SessionPickerOptions<T extends SessionSelectorEntry = SessionSe
|
|
|
13
15
|
scopeLabel?: string | false;
|
|
14
16
|
showCwd?: boolean;
|
|
15
17
|
allowDelete?: boolean;
|
|
18
|
+
/** Rename the selected session; resolve with the title as stored, or null when it did not happen. */
|
|
19
|
+
onRename?: (session: T, title: string) => Promise<string | null>;
|
|
16
20
|
allowGlobalScope?: boolean;
|
|
17
21
|
historySearch?: boolean;
|
|
18
22
|
pinnedIds?: ReadonlySet<string>;
|
|
@@ -87,7 +87,7 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
87
87
|
*/
|
|
88
88
|
setExpanded(expanded: boolean): void;
|
|
89
89
|
isTranscriptBlockFinalized(): boolean;
|
|
90
|
-
/**
|
|
90
|
+
/** Hold image and figure rendering until the completed block can enter immutable scrollback. */
|
|
91
91
|
isTranscriptBlockPending(): boolean;
|
|
92
92
|
render(width: number): readonly string[];
|
|
93
93
|
/**
|
|
@@ -37,6 +37,8 @@ export declare class BashExecutionComponent extends Container {
|
|
|
37
37
|
appendOutput(chunk: string): void;
|
|
38
38
|
/** Switch to PTY rendering and feed raw terminal bytes through the vterm replay. */
|
|
39
39
|
appendPtyChunk(chunk: string): void;
|
|
40
|
+
/** Bytes a terminal would send for a key event, honoring the command's cursor-key mode; "" for key releases. */
|
|
41
|
+
encodePtyInput(data: string): string;
|
|
40
42
|
setComplete(exitCode: number | undefined, cancelled: boolean, options?: {
|
|
41
43
|
output?: string;
|
|
42
44
|
truncation?: TruncationMeta;
|
|
@@ -6,6 +6,8 @@ export interface ChatTranscriptDisplayPreferences {
|
|
|
6
6
|
cacheMissMarker: boolean;
|
|
7
7
|
showTokenUsage: boolean;
|
|
8
8
|
showTurnTime: boolean;
|
|
9
|
+
/** Max rows a collapsed advisor card shows below its header; 0 = no cap. */
|
|
10
|
+
advisorCollapsedLines: number;
|
|
9
11
|
}
|
|
10
12
|
/** Current transcript display preferences. */
|
|
11
13
|
export declare const chatTranscriptDisplayPreferences: ChatTranscriptDisplayPreferences;
|
|
@@ -102,7 +102,7 @@ export declare class ToolExecutionComponent extends Container {
|
|
|
102
102
|
* component-scoped so the TUI reuses every other root subtree (issue #4377).
|
|
103
103
|
*/
|
|
104
104
|
tickSpinner(frame: number): void;
|
|
105
|
-
/**
|
|
105
|
+
/** Hold visible results until their figures and Kitty images are ready for immutable scrollback. */
|
|
106
106
|
isTranscriptBlockPending(): boolean;
|
|
107
107
|
/**
|
|
108
108
|
* Whether this block is ready to retire as immutable history. Partial
|
|
@@ -98,6 +98,8 @@ export declare class Editor implements Component, Focusable {
|
|
|
98
98
|
describeDecorations: ((lines: readonly string[]) => readonly TspEditorDecoration[]) | undefined;
|
|
99
99
|
/** TSP code language of the buffer (`python`, `bash`): the terminal highlights it in the mono face. */
|
|
100
100
|
describeLanguage: (() => string | undefined) | undefined;
|
|
101
|
+
/** Label the TSP field to disable host-side editing while another input owner handles raw keys. */
|
|
102
|
+
nativeKeyPassthrough: boolean;
|
|
101
103
|
/**
|
|
102
104
|
* TSP chrome around the `editor` node (the prompt composer's chips, mode
|
|
103
105
|
* chip, send/stop): the root role, the children with `input` placed where
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Focusable, type TUI } from "../index.js";
|
|
2
2
|
import { type KeybindingsManager } from "../app-keybindings.js";
|
|
3
3
|
import { type Theme } from "../theme/theme.js";
|
|
4
|
-
import type { CodeReviewAnnotation, CodeReviewOverlayResult, ReviewDiffFile, TextReviewAnnotation, TextReviewOverlayResult, TextReviewSource } from "./annotation-types.js";
|
|
4
|
+
import type { AnnotationOverlayDraft, CodeReviewAnnotation, CodeReviewOverlayResult, ReviewDiffFile, TextReviewAnnotation, TextReviewOverlayResult, TextReviewSource } from "./annotation-types.js";
|
|
5
5
|
import type { TspSpan } from "@oh-my-pi/pi-wire";
|
|
6
6
|
import { type NativeNode, type NativeUiEvent } from "../native/node.js";
|
|
7
7
|
export declare const CONTINUE_CODE_REVIEW_ACTION = "Continue with LLM review";
|
|
@@ -20,6 +20,8 @@ export interface CodeReviewOverlayCallbacks extends AnnotationOverlayCallbacks {
|
|
|
20
20
|
export interface TextReviewOverlayCallbacks extends AnnotationOverlayCallbacks {
|
|
21
21
|
onComplete(result: TextReviewOverlayResult | undefined): void;
|
|
22
22
|
}
|
|
23
|
+
/** Clears every note and closes; the host then has nothing to keep. Needs a second confirm. */
|
|
24
|
+
export declare const DISCARD_ANNOTATIONS_ACTION = "Discard notes and close";
|
|
23
25
|
/** Splits a text source into the overlay's 1-based annotatable lines. */
|
|
24
26
|
export declare function splitTextLines(text: string): string[];
|
|
25
27
|
/** The native sheet: full-pane glass, titled by the review kind with what is under review after it. */
|
|
@@ -38,6 +40,10 @@ export declare class AnnotationOverlay implements Focusable {
|
|
|
38
40
|
setUseTerminalCursor(useTerminalCursor: boolean): void;
|
|
39
41
|
invalidate(): void;
|
|
40
42
|
dispose(): void;
|
|
43
|
+
/** Notes, the unfinished note, and any source edit, in the form {@link restoreDraft} replays. */
|
|
44
|
+
getDraft(): AnnotationOverlayDraft;
|
|
45
|
+
/** Replay a draft taken from this same source; anything anchored outside it is dropped. */
|
|
46
|
+
restoreDraft(draft: AnnotationOverlayDraft): void;
|
|
41
47
|
getAnnotations(): CodeReviewAnnotation[];
|
|
42
48
|
getTextAnnotations(): TextReviewAnnotation[];
|
|
43
49
|
/** Repo-relative path of the diff file under the cursor, if this is a code review. */
|
|
@@ -84,3 +84,31 @@ export interface TextReviewOverlayResult {
|
|
|
84
84
|
/** Source text after an external-editor edit; absent when the source was not edited. */
|
|
85
85
|
editedText?: string;
|
|
86
86
|
}
|
|
87
|
+
export type AnnotationOverlayDraft = {
|
|
88
|
+
kind: "diff";
|
|
89
|
+
annotations: {
|
|
90
|
+
fileIndex: number;
|
|
91
|
+
sourceIndex: number;
|
|
92
|
+
annotation: CodeReviewAnnotation;
|
|
93
|
+
}[];
|
|
94
|
+
editor?: {
|
|
95
|
+
fileIndex: number;
|
|
96
|
+
sourceIndex: number;
|
|
97
|
+
scope: "file" | "line";
|
|
98
|
+
editingAnnotationIndex?: number;
|
|
99
|
+
text: string;
|
|
100
|
+
};
|
|
101
|
+
} | {
|
|
102
|
+
kind: "text";
|
|
103
|
+
annotations: {
|
|
104
|
+
sourceIndex: number;
|
|
105
|
+
annotation: TextReviewAnnotation;
|
|
106
|
+
}[];
|
|
107
|
+
editedText?: string;
|
|
108
|
+
editor?: {
|
|
109
|
+
sourceIndex: number;
|
|
110
|
+
scope: "text" | "line";
|
|
111
|
+
editingAnnotationIndex?: number;
|
|
112
|
+
text: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
@@ -74,6 +74,8 @@ export interface PlanReviewOverlayOptions {
|
|
|
74
74
|
slider?: HookSelectorSlider;
|
|
75
75
|
/** Display label for the external-editor key, surfaced in the footer help. */
|
|
76
76
|
externalEditorLabel?: string;
|
|
77
|
+
/** The external editor's name ("Notepad", "nvim") for the native Edit button; `$EDITOR` when unset. */
|
|
78
|
+
externalEditorName?: string;
|
|
77
79
|
/** Serializable annotations restored into this overlay instance. */
|
|
78
80
|
annotationState?: PlanReviewAnnotationState;
|
|
79
81
|
}
|
|
@@ -71,6 +71,7 @@ declare class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
71
71
|
onExit: () => void;
|
|
72
72
|
onToggleScope?: () => void;
|
|
73
73
|
onDeleteRequest?: (session: T) => void;
|
|
74
|
+
onRenameRequest?: (session: T) => void;
|
|
74
75
|
/** Re-render hook for async list updates (fuzzy scan completion, history merge). */
|
|
75
76
|
onRequestRender?: () => void;
|
|
76
77
|
constructor(sessions: T[], showCwd?: boolean, historyMatcher?: SessionHistoryMatcher, getTerminalRows?: () => number, pinnedIds?: ReadonlySet<string>, currentSessionPath?: string | (() => string | undefined));
|
|
@@ -79,6 +80,8 @@ declare class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
79
80
|
/** Cancel pending async search work; idempotent, called on every picker exit path. */
|
|
80
81
|
dispose(): void;
|
|
81
82
|
removeSession(sessionPath: string): void;
|
|
83
|
+
/** Update a session's title in place and refresh the visible list, keeping the selection. */
|
|
84
|
+
renameSessionTitle(sessionPath: string, title: string): void;
|
|
82
85
|
/** Resolve a list-local rendered-line index to a filtered-session index. */
|
|
83
86
|
hitTestSession(line: number): number | undefined;
|
|
84
87
|
/** Wheel notch: move the selection one step (clamped, no wrap). */
|
|
@@ -93,6 +96,8 @@ declare class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
93
96
|
resumeSelected(): void;
|
|
94
97
|
/** Delete / Backspace on an empty query: ask the parent to confirm deleting the selected session. */
|
|
95
98
|
requestDelete(): void;
|
|
99
|
+
/** F2: ask the parent to rename the selected session. */
|
|
100
|
+
requestRename(): void;
|
|
96
101
|
/** Empty the search query (the empty state's "Clear search"). */
|
|
97
102
|
clearSearch(): void;
|
|
98
103
|
get selectedSession(): T | undefined;
|
|
@@ -120,6 +125,12 @@ declare class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
120
125
|
}
|
|
121
126
|
export interface SessionSelectorOptions<T extends SessionSelectorEntry = SessionSelectorEntry> {
|
|
122
127
|
onDelete?: (session: T) => Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* Rename the selected session to a user-supplied title; resolve with the
|
|
130
|
+
* title as stored (the session canonicalizes it) or null when the rename
|
|
131
|
+
* did not happen.
|
|
132
|
+
*/
|
|
133
|
+
onRename?: (session: T, title: string) => Promise<string | null>;
|
|
123
134
|
historyMatcher?: SessionHistoryMatcher;
|
|
124
135
|
/** Loads sessions across all projects for the all-projects scope toggle (Tab). */
|
|
125
136
|
loadAllSessions?: () => Promise<T[]>;
|
|
@@ -188,8 +199,8 @@ export declare class SessionSelectorComponent<T extends SessionSelectorEntry = S
|
|
|
188
199
|
/**
|
|
189
200
|
* With the `picker` kind: the data-first session picker (§4.7). Otherwise
|
|
190
201
|
* scope `tabs`, loading/error message, then the search `input` and session
|
|
191
|
-
* `list` — or the delete-confirmation dialog in their place, as on
|
|
192
|
-
* path — and the key-hint footer.
|
|
202
|
+
* `list` — or the delete-confirmation / rename dialog in their place, as on
|
|
203
|
+
* the ANSI path — and the key-hint footer.
|
|
193
204
|
*/
|
|
194
205
|
describe(cx: DescribeContext): NativeNode;
|
|
195
206
|
/** Picker events (root keypath); fallback: scope tab → toggle scope, click on a session → resume it, exactly like a mouse click. */
|
|
@@ -114,29 +114,6 @@ export declare function stopThemeWatcher(): void;
|
|
|
114
114
|
* Used by HTML export to generate CSS custom properties.
|
|
115
115
|
*/
|
|
116
116
|
export declare function getResolvedThemeColors(themeName?: string): Promise<Record<string, string>>;
|
|
117
|
-
/** One appearance variant of {@link NativeThemePalette}: token name → `#rrggbb`. */
|
|
118
|
-
export type NativeThemeVariant = Record<string, string>;
|
|
119
|
-
/** omp's resolved theme for a Tern Surface Protocol terminal (the `t` verb body minus `sf`). */
|
|
120
|
-
export interface NativeThemePalette {
|
|
121
|
-
dark?: NativeThemeVariant;
|
|
122
|
-
light?: NativeThemeVariant;
|
|
123
|
-
name: {
|
|
124
|
-
dark?: string;
|
|
125
|
-
light?: string;
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Cheap identity of everything {@link getNativeThemePalette} depends on;
|
|
130
|
-
* recompute the palette only when it changes.
|
|
131
|
-
*/
|
|
132
|
-
export declare function getNativeThemePaletteKey(): string;
|
|
133
|
-
/**
|
|
134
|
-
* omp's theme for a native surface: with auto theme, the configured dark and
|
|
135
|
-
* light themes; otherwise (or while the theme selector previews one) the
|
|
136
|
-
* active theme under its own appearance only. Themes without a loadable
|
|
137
|
-
* definition (in-memory instances) contribute nothing.
|
|
138
|
-
*/
|
|
139
|
-
export declare function getNativeThemePalette(): NativeThemePalette;
|
|
140
117
|
/**
|
|
141
118
|
* Check if a theme is a "light" theme by analyzing its status-line background
|
|
142
119
|
* luminance. Loads theme JSON synchronously (built-in or custom file on disk)
|
|
@@ -6,6 +6,12 @@ import type { Theme } from "../theme/theme.js";
|
|
|
6
6
|
export interface BashInteractiveTerminalBackend {
|
|
7
7
|
resize(columns: number, rows: number): void;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Translates a TUI key event into the bytes a terminal would send to a PTY
|
|
11
|
+
* child; key releases encode to "". Shared by the agent `pty` console and
|
|
12
|
+
* `!` commands (`BashExecutionComponent.encodePtyInput`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeInputForPty(data: string, applicationCursorKeysMode: boolean): string;
|
|
9
15
|
/** Interactive terminal overlay driven by an external PTY controller. */
|
|
10
16
|
export declare class BashInteractiveOverlayComponent implements Component {
|
|
11
17
|
#private;
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -507,7 +507,10 @@ export declare class TUI extends Container {
|
|
|
507
507
|
* never come.
|
|
508
508
|
*/
|
|
509
509
|
addStartListener(listener: StartListener): () => void;
|
|
510
|
-
|
|
510
|
+
/** Registers a key listener; `prepend` gives a foreground input owner first refusal over global shortcuts. */
|
|
511
|
+
addInputListener(listener: InputListener, options?: {
|
|
512
|
+
prepend?: boolean;
|
|
513
|
+
}): () => void;
|
|
511
514
|
removeInputListener(listener: InputListener): void;
|
|
512
515
|
stop(): void;
|
|
513
516
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.13.
|
|
4
|
+
"version": "18.13.3",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,20 +41,20 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.13.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.13.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.13.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.13.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.13.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.13.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.13.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.13.
|
|
44
|
+
"@oh-my-pi/omptype": "18.13.3",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.13.3",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.13.3",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.13.3",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.13.3",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.13.3",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.13.3",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.13.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
|
-
"bun": ">=1.
|
|
57
|
+
"bun": ">=1.4.4"
|
|
58
58
|
},
|
|
59
59
|
"files": [
|
|
60
60
|
"src",
|
|
@@ -11,6 +11,8 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
|
|
|
11
11
|
loadPinnedIds?(): Promise<ReadonlySet<string>>;
|
|
12
12
|
loadHistoryMatcher?(): SessionHistoryMatcher;
|
|
13
13
|
deleteSession?(session: T): Promise<boolean>;
|
|
14
|
+
/** Rename the session and resolve with the title as stored, or null when it did not happen. */
|
|
15
|
+
renameSession?(session: T, title: string): Promise<string | null>;
|
|
14
16
|
loadAllSessions?(): Promise<T[]>;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -21,6 +23,8 @@ export interface SessionPickerOptions<T extends SessionSelectorEntry = SessionSe
|
|
|
21
23
|
scopeLabel?: string | false;
|
|
22
24
|
showCwd?: boolean;
|
|
23
25
|
allowDelete?: boolean;
|
|
26
|
+
/** Rename the selected session; resolve with the title as stored, or null when it did not happen. */
|
|
27
|
+
onRename?: (session: T, title: string) => Promise<string | null>;
|
|
24
28
|
allowGlobalScope?: boolean;
|
|
25
29
|
historySearch?: boolean;
|
|
26
30
|
pinnedIds?: ReadonlySet<string>;
|
|
@@ -63,6 +67,7 @@ export async function selectSession<T extends SessionSelectorEntry>(
|
|
|
63
67
|
},
|
|
64
68
|
{
|
|
65
69
|
onDelete: options.allowDelete === false ? undefined : host.deleteSession,
|
|
70
|
+
onRename: options.onRename ?? host.renameSession,
|
|
66
71
|
historyMatcher,
|
|
67
72
|
loadAllSessions: options.allowGlobalScope === false ? undefined : host.loadAllSessions,
|
|
68
73
|
allSessions: options.allSessions,
|
|
@@ -2,6 +2,7 @@ import { type Component } from "../tui";
|
|
|
2
2
|
import { Disclosure } from "../components/disclosure";
|
|
3
3
|
import { visibleWidth } from "../utils";
|
|
4
4
|
import type { AdvisorMessageDetails, AdvisorNote, AdvisorSeverity } from "./messages";
|
|
5
|
+
import { chatTranscriptDisplayPreferences } from "./display-preferences";
|
|
5
6
|
import { formatBadge, replaceTabs, type ToolUIColor, wrapTextWithAnsi } from "../render/render-utils";
|
|
6
7
|
import { Ellipsis, truncateToWidth } from "../render";
|
|
7
8
|
import type { Theme } from "../theme";
|
|
@@ -274,6 +275,7 @@ export function createAdvisorMessageCard(
|
|
|
274
275
|
body: () => new AdvisorNotes(notes, 0, uiTheme),
|
|
275
276
|
expanded: getExpanded(),
|
|
276
277
|
paddingX: 1,
|
|
278
|
+
maxCollapsedRows: chatTranscriptDisplayPreferences.advisorCollapsedLines || undefined,
|
|
277
279
|
});
|
|
278
280
|
// The tool-output toggle owns expansion state; synchronize the controlled
|
|
279
281
|
// disclosure from the callback on every render.
|
|
@@ -792,8 +792,9 @@ export class AssistantMessageComponent extends Container {
|
|
|
792
792
|
return this.#transcriptBlockFinalized;
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
-
/**
|
|
795
|
+
/** Hold image and figure rendering until the completed block can enter immutable scrollback. */
|
|
796
796
|
isTranscriptBlockPending(): boolean {
|
|
797
|
+
if (this.#showImages && this.#kittyConversionsAwaited.size > 0) return true;
|
|
797
798
|
for (const block of this.#figureBlocks.values()) if (block.pending) return true;
|
|
798
799
|
return false;
|
|
799
800
|
}
|
|
@@ -1615,6 +1616,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
1615
1616
|
})
|
|
1616
1617
|
.catch(() => {
|
|
1617
1618
|
this.#kittyConversionsAwaited.delete(key);
|
|
1619
|
+
this.#onImageUpdate?.();
|
|
1618
1620
|
});
|
|
1619
1621
|
}
|
|
1620
1622
|
}
|
|
@@ -16,6 +16,7 @@ import type { TruncationMeta } from "../tools/output-meta";
|
|
|
16
16
|
import { resolveImageOptions } from "../render/render-utils";
|
|
17
17
|
import { OutputPane } from "../render/output-pane";
|
|
18
18
|
import { loadXtermTerminal, readTerminalRows, styleTerminalRow } from "../tools/terminal-output";
|
|
19
|
+
import { normalizeInputForPty } from "../tools/bash-interactive";
|
|
19
20
|
import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../render/sixel";
|
|
20
21
|
import {
|
|
21
22
|
buildExecutionFrame,
|
|
@@ -286,6 +287,11 @@ export class BashExecutionComponent extends Container {
|
|
|
286
287
|
this.#drainPtyQueue();
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
/** Bytes a terminal would send for a key event, honoring the command's cursor-key mode; "" for key releases. */
|
|
291
|
+
encodePtyInput(data: string): string {
|
|
292
|
+
return normalizeInputForPty(data, this.#ptyTerminal?.modes.applicationCursorKeysMode ?? false);
|
|
293
|
+
}
|
|
294
|
+
|
|
289
295
|
#drainPtyQueue(): void {
|
|
290
296
|
const terminal = this.#ptyTerminal;
|
|
291
297
|
if (!terminal || this.#ptyWriting) return;
|
|
@@ -6,6 +6,8 @@ export interface ChatTranscriptDisplayPreferences {
|
|
|
6
6
|
cacheMissMarker: boolean;
|
|
7
7
|
showTokenUsage: boolean;
|
|
8
8
|
showTurnTime: boolean;
|
|
9
|
+
/** Max rows a collapsed advisor card shows below its header; 0 = no cap. */
|
|
10
|
+
advisorCollapsedLines: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/** Current transcript display preferences. */
|
|
@@ -16,6 +18,7 @@ export const chatTranscriptDisplayPreferences: ChatTranscriptDisplayPreferences
|
|
|
16
18
|
cacheMissMarker: false,
|
|
17
19
|
showTokenUsage: false,
|
|
18
20
|
showTurnTime: false,
|
|
21
|
+
advisorCollapsedLines: 0,
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
/** Apply host display preferences without pulling settings into the renderer. */
|
|
@@ -711,14 +711,15 @@ export class ToolExecutionComponent extends Container {
|
|
|
711
711
|
// Convert async - catch errors from processing
|
|
712
712
|
convertImageToPngShared(image)
|
|
713
713
|
.then(converted => {
|
|
714
|
+
this.#kittyConversionsAwaited.delete(key);
|
|
714
715
|
this.#kittyConverted.set(key, converted);
|
|
715
716
|
this.#displayInputVersion++;
|
|
716
717
|
this.#updateDisplay();
|
|
717
718
|
this.#ui.requestRender();
|
|
718
719
|
})
|
|
719
720
|
.catch(() => {
|
|
720
|
-
// Ignore conversion failures - display will use original image format
|
|
721
721
|
this.#kittyConversionsAwaited.delete(key);
|
|
722
|
+
this.#ui.requestRender();
|
|
722
723
|
});
|
|
723
724
|
}
|
|
724
725
|
}
|
|
@@ -859,9 +860,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
859
860
|
}
|
|
860
861
|
}
|
|
861
862
|
|
|
862
|
-
/**
|
|
863
|
+
/** Hold visible results until their figures and Kitty images are ready for immutable scrollback. */
|
|
863
864
|
isTranscriptBlockPending(): boolean {
|
|
864
|
-
return
|
|
865
|
+
return (
|
|
866
|
+
this.#toolActivityVisible &&
|
|
867
|
+
((this.#showImages && this.#kittyConversionsAwaited.size > 0) || this.#figure?.pending === true)
|
|
868
|
+
);
|
|
865
869
|
}
|
|
866
870
|
|
|
867
871
|
/**
|
package/src/components/editor.ts
CHANGED
|
@@ -776,6 +776,8 @@ export class Editor implements Component, Focusable {
|
|
|
776
776
|
describeDecorations: ((lines: readonly string[]) => readonly TspEditorDecoration[]) | undefined;
|
|
777
777
|
/** TSP code language of the buffer (`python`, `bash`): the terminal highlights it in the mono face. */
|
|
778
778
|
describeLanguage: (() => string | undefined) | undefined;
|
|
779
|
+
/** Label the TSP field to disable host-side editing while another input owner handles raw keys. */
|
|
780
|
+
nativeKeyPassthrough = false;
|
|
779
781
|
/**
|
|
780
782
|
* TSP chrome around the `editor` node (the prompt composer's chips, mode
|
|
781
783
|
* chip, send/stop): the root role, the children with `input` placed where
|
|
@@ -1894,7 +1896,7 @@ export class Editor implements Component, Focusable {
|
|
|
1894
1896
|
decor: decor.decor,
|
|
1895
1897
|
ghost: ghost ? plainText(ghost) : undefined,
|
|
1896
1898
|
placeholder: nativePlaceholder || undefined,
|
|
1897
|
-
mode: vim ? VIM_MODE_LABELS[vim.mode] : undefined,
|
|
1899
|
+
mode: this.nativeKeyPassthrough ? "PTY" : vim ? VIM_MODE_LABELS[vim.mode] : undefined,
|
|
1898
1900
|
lang: this.describeLanguage?.(),
|
|
1899
1901
|
maxLines: this.#maxHeight,
|
|
1900
1902
|
};
|
package/src/native/backend.ts
CHANGED
|
@@ -39,15 +39,7 @@ import {
|
|
|
39
39
|
type TspOp,
|
|
40
40
|
} from "@oh-my-pi/pi-wire";
|
|
41
41
|
import type { Terminal } from "../terminal";
|
|
42
|
-
import {
|
|
43
|
-
bindTheme,
|
|
44
|
-
getCurrentThemeName,
|
|
45
|
-
getNativeThemePalette,
|
|
46
|
-
getNativeThemePaletteKey,
|
|
47
|
-
isLightTheme,
|
|
48
|
-
type NativeThemePalette,
|
|
49
|
-
setNativeSymbolPreset,
|
|
50
|
-
} from "../theme/theme";
|
|
42
|
+
import { getCurrentThemeName, isLightTheme, setNativeSymbolPreset } from "../theme/theme";
|
|
51
43
|
import type { Component, OverlayOptions, RenderScheduler, RenderTimer } from "../tui";
|
|
52
44
|
import { TspDocument } from "./apply";
|
|
53
45
|
import { getNativeBlob, type NativeBlob } from "./blobs";
|
|
@@ -313,11 +305,6 @@ export class NativeBackend {
|
|
|
313
305
|
#sawResize = false;
|
|
314
306
|
#live = false;
|
|
315
307
|
#overlayNodes = new Map<Component, { key: string; node: NativeNode }>();
|
|
316
|
-
#unbindTheme: (() => void) | undefined;
|
|
317
|
-
#palette: NativeThemePalette | undefined;
|
|
318
|
-
#paletteKey: string | undefined;
|
|
319
|
-
/** Serialized palette last sent, to skip resends of an unchanged theme. */
|
|
320
|
-
#paletteSent: string | undefined;
|
|
321
308
|
/** Pending delivery owns bytes until sent or confirmed held; settled descriptions may already be released. */
|
|
322
309
|
#blobs = new Map<string, NativeBlob | "sent">();
|
|
323
310
|
/** `blobs` queries sent, oldest first: the terminal answers each once, in order. */
|
|
@@ -366,54 +353,11 @@ export class NativeBackend {
|
|
|
366
353
|
if (this.#live) return;
|
|
367
354
|
this.#live = true;
|
|
368
355
|
this.#useNerdSymbols(true);
|
|
369
|
-
this.#watchTheme();
|
|
370
356
|
this.#open(this.#inline);
|
|
371
357
|
setNativeRendering(true);
|
|
372
358
|
this.render();
|
|
373
359
|
}
|
|
374
360
|
|
|
375
|
-
/**
|
|
376
|
-
* Any theme swap (switch, selector preview, auto light/dark, file reload)
|
|
377
|
-
* schedules a render; `render()` resends the palette when it changed, so
|
|
378
|
-
* bursts coalesce into one `t` and no frame is needed for it.
|
|
379
|
-
*/
|
|
380
|
-
#watchTheme(): void {
|
|
381
|
-
this.#unbindTheme?.();
|
|
382
|
-
let initial = true;
|
|
383
|
-
this.#unbindTheme = bindTheme(() => {
|
|
384
|
-
if (initial) return;
|
|
385
|
-
this.#host.requestRender();
|
|
386
|
-
});
|
|
387
|
-
initial = false;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** The current palette, recomputed only when its inputs changed. */
|
|
391
|
-
#currentPalette(): NativeThemePalette {
|
|
392
|
-
const key = getNativeThemePaletteKey();
|
|
393
|
-
if (this.#palette === undefined || key !== this.#paletteKey) {
|
|
394
|
-
this.#paletteKey = key;
|
|
395
|
-
this.#palette = getNativeThemePalette();
|
|
396
|
-
}
|
|
397
|
-
return this.#palette;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/** Send the palette to `surface` (right after its `o`, before any `f`). */
|
|
401
|
-
#sendPalette(surface: Surface): void {
|
|
402
|
-
const palette = this.#currentPalette();
|
|
403
|
-
if (!palette.dark && !palette.light) return;
|
|
404
|
-
this.#paletteSent = JSON.stringify(palette);
|
|
405
|
-
this.#write("t", { sf: surface.id, ...palette });
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/** Resend the palette to every open surface when the theme changed since it was sent. */
|
|
409
|
-
#refreshPalette(): void {
|
|
410
|
-
const key = this.#paletteKey;
|
|
411
|
-
const palette = this.#currentPalette();
|
|
412
|
-
if (key === this.#paletteKey || JSON.stringify(palette) === this.#paletteSent) return;
|
|
413
|
-
this.#sendPalette(this.#inline);
|
|
414
|
-
if (this.#screen) this.#sendPalette(this.#screen);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
361
|
/**
|
|
418
362
|
* Tern's font carries every Nerd Font glyph, and it never answers the Glyph
|
|
419
363
|
* Protocol, so icons come from the nerd preset (process-local, not the
|
|
@@ -435,8 +379,6 @@ export class NativeBackend {
|
|
|
435
379
|
this.#close(this.#inline, keep);
|
|
436
380
|
this.#inline.doc?.close();
|
|
437
381
|
setNativeRendering(false);
|
|
438
|
-
this.#unbindTheme?.();
|
|
439
|
-
this.#unbindTheme = undefined;
|
|
440
382
|
this.#useNerdSymbols(false);
|
|
441
383
|
this.#clearStallTimer();
|
|
442
384
|
// Replies no longer reach the backend: forget the queries outright.
|
|
@@ -455,7 +397,6 @@ export class NativeBackend {
|
|
|
455
397
|
this.#applyHello(hello);
|
|
456
398
|
this.#live = true;
|
|
457
399
|
this.#useNerdSymbols(true);
|
|
458
|
-
this.#watchTheme();
|
|
459
400
|
this.#resetBlobs();
|
|
460
401
|
const surface = this.#inline;
|
|
461
402
|
surface.reconciler.detachLive();
|
|
@@ -466,7 +407,6 @@ export class NativeBackend {
|
|
|
466
407
|
surface.focus = null;
|
|
467
408
|
surface.dirty = false;
|
|
468
409
|
this.#write("o", { id: surface.id, mode: "inline", title: "omp", role: SESSION_ROLE, adopt: true });
|
|
469
|
-
this.#sendPalette(surface);
|
|
470
410
|
// After the `o`, as in `start()`.
|
|
471
411
|
setNativeRendering(true);
|
|
472
412
|
this.render();
|
|
@@ -508,7 +448,6 @@ export class NativeBackend {
|
|
|
508
448
|
/** Reconcile and send a frame, unless the target surface is out of credits. */
|
|
509
449
|
render(): void {
|
|
510
450
|
if (!this.#live) return;
|
|
511
|
-
this.#refreshPalette();
|
|
512
451
|
const overlays = this.#host.overlays();
|
|
513
452
|
let fullscreen = -1;
|
|
514
453
|
for (let i = overlays.length - 1; i >= 0; i--) {
|
|
@@ -608,7 +547,6 @@ export class NativeBackend {
|
|
|
608
547
|
|
|
609
548
|
#open(surface: Surface): void {
|
|
610
549
|
this.#write("o", { id: surface.id, mode: surface.mode, title: "omp", role: surface.role });
|
|
611
|
-
this.#sendPalette(surface);
|
|
612
550
|
}
|
|
613
551
|
|
|
614
552
|
#close(surface: Surface, keep: boolean): void {
|
|
@@ -776,7 +714,7 @@ export class NativeBackend {
|
|
|
776
714
|
}
|
|
777
715
|
}
|
|
778
716
|
|
|
779
|
-
#write(verb: "o" | "f" | "
|
|
717
|
+
#write(verb: "o" | "f" | "x" | "q", body: unknown): void {
|
|
780
718
|
this.#record("out", verb, undefined, body);
|
|
781
719
|
this.#host.terminal.write(encodeTspJson(verb, body, undefined, this.#limit));
|
|
782
720
|
}
|