@oh-my-pi/pi-tui 18.10.4 → 18.11.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 +22 -100
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/components/markdown.d.ts +8 -0
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +2 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/overlays/approval-dialog.d.ts +24 -6
- package/dist/types/prompt/custom-editor.d.ts +18 -10
- package/dist/types/render/hanging-wrap.d.ts +5 -0
- package/dist/types/terminal-capabilities.d.ts +8 -0
- package/dist/types/terminal-multiplexer.d.ts +1 -0
- package/dist/types/tmux.d.ts +13 -4
- package/package.json +9 -9
- package/src/chat/assistant-message.ts +19 -4
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/read-tool-group.ts +5 -1
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/components/markdown.ts +12 -0
- package/src/components/text.ts +11 -1
- package/src/keybinding-matchers.ts +5 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/overlays/approval-dialog.ts +115 -24
- package/src/prompt/custom-editor.ts +149 -64
- package/src/render/code-cell.ts +10 -4
- package/src/render/hanging-wrap.ts +31 -0
- package/src/terminal-capabilities.ts +25 -7
- package/src/terminal.ts +14 -3
- package/src/tmux.ts +42 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/edit.ts +4 -1
- package/src/tools/lsp.ts +9 -2
- package/src/tools/todo.ts +3 -2
- package/src/tools/write.ts +14 -4
- package/src/tui.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.11.0] - 2026-10-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added native composer microphone controls with visible starting, recording and transcription states; a start still loading its model can be cancelled, and Enter and Send wait while dictation is recording or transcribing into the draft ([#15759](https://github.com/can1357/oh-my-pi/pull/15759) by [@daaximus](https://github.com/daaximus)).
|
|
10
|
+
- `ApprovalDialogComponent` takes a `draft` option that routes typing to the composer beneath it, so a message can be written while a tool call waits ([#15642](https://github.com/can1357/oh-my-pi/pull/15642) by [@H4vC](https://github.com/H4vC))
|
|
11
|
+
- `ApprovalDialogComponent` takes a `fullPrompt` option that the expand key (`app.tools.expand`) or a Details button swaps in for the summarized prompt; it replaces `wrapDetails` ([#15642](https://github.com/can1357/oh-my-pi/pull/15642) by [@H4vC](https://github.com/H4vC))
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- SVG fences in CRLF or CR-only assistant Markdown now render as figures instead of source code ([#15663](https://github.com/can1357/oh-my-pi/issues/15663)).
|
|
16
|
+
- Fixed clipboard-image and image-path pastes being dropped while the composer draft is edited beneath an ask or approval dialog ([#15642](https://github.com/can1357/oh-my-pi/pull/15642) by [@H4vC](https://github.com/H4vC))
|
|
17
|
+
- Fixed inline images inside tmux assuming Kitty graphics from the pane's `TERM`; the attached client is identified from tmux's terminal-type reply or its `TERM` (e.g. `xterm-kitty`), and unrecognized clients go to the SIXEL probe ([#15524](https://github.com/can1357/oh-my-pi/issues/15524)).
|
|
18
|
+
- Fixed ```svg fences rendering as their source code instead of a drawing when no theme was installed yet, e.g. in an extension rendering a message that opens with the fence ([#15660](https://github.com/can1357/oh-my-pi/issues/15660))
|
|
19
|
+
- Fixed pastes inside tmux 3.5a and older submitting line by line after another program turned bracketed paste off on the pane; omp now re-enables it on the next render or keypress ([#15596](https://github.com/can1357/oh-my-pi/issues/15596))
|
|
20
|
+
- Fixed assistant replies containing a table (on image-capable terminals with table charts on) publishing still-streaming rows to native scrollback, which froze the block's append-only retirement and could leave overwritten reply rows in scrollback ([#15693](https://github.com/can1357/oh-my-pi/issues/15693))
|
|
21
|
+
- Fixed streamed thinking publishing a line to native scrollback just before a code fence or `<!-- -->` sentinel split across deltas rewrote it with the prose-fold ellipsis ([#15693](https://github.com/can1357/oh-my-pi/issues/15693))
|
|
22
|
+
- Fixed streamed assistant text or thinking containing a tab stopping all later rows of the reply from retiring into native scrollback until the turn ended ([#15693](https://github.com/can1357/oh-my-pi/issues/15693))
|
|
23
|
+
- Fixed wrapped lines in Write, Read, Edit, Todo, Ask and LSP tool cards losing their alignment under line numbers, tree connectors, and option or code rails ([#15677](https://github.com/can1357/oh-my-pi/issues/15677))
|
|
24
|
+
- Fixed SIXEL images leaving their previous raster on screen, drawn again over itself, when the transcript moves them within the viewport ([#15657](https://github.com/can1357/oh-my-pi/issues/15657))
|
|
25
|
+
|
|
5
26
|
## [18.10.4] - 2026-10-13
|
|
6
27
|
|
|
7
28
|
### Added
|
|
@@ -2658,103 +2679,4 @@
|
|
|
2658
2679
|
|
|
2659
2680
|
- Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
|
|
2660
2681
|
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
### Breaking Changes
|
|
2664
|
-
|
|
2665
|
-
- Removed `getCursorPosition()` method from Component interface and implementations, eliminating hardware cursor positioning support
|
|
2666
|
-
|
|
2667
|
-
### Added
|
|
2668
|
-
|
|
2669
|
-
- Added sticky column behavior for vertical cursor movement, preserving target column when navigating through lines of varying lengths
|
|
2670
|
-
- Added `drainInput()` method to Terminal interface to prevent Kitty key release events from leaking to parent shell over slow SSH connections
|
|
2671
|
-
- Added `setClearOnShrink()` method to control whether full re-render occurs when content shrinks below working area
|
|
2672
|
-
- Added support for hidden paths (e.g., `.pi`, `.github`) in autocomplete while excluding `.git` directories
|
|
2673
|
-
|
|
2674
|
-
### Changed
|
|
2675
|
-
|
|
2676
|
-
- Changed default value of `PI_HARDWARE_CURSOR` environment variable from implicit true to explicit `"1"` for clarity
|
|
2677
|
-
- Changed default value of `PI_CLEAR_ON_SHRINK` environment variable from implicit false to explicit `"0"` for clarity
|
|
2678
|
-
- Changed TUI to clear screen on startup to prevent shell prompts and status messages from bleeding into the first rendered frame
|
|
2679
|
-
- Refactored full-render logic into reusable helper function to reduce code duplication across multiple render paths
|
|
2680
|
-
- Changed autocomplete to include hidden paths but filter out `.git` and its contents
|
|
2681
|
-
- Changed Input component to properly handle surrogate pairs in Unicode text, preventing cursor display corruption with emoji and multi-byte characters
|
|
2682
|
-
- Changed Editor to use `setCursorCol()` for all cursor column updates, enabling sticky column tracking
|
|
2683
|
-
- Changed Editor's vertical navigation to implement sticky column logic via `moveToVisualLine()` and `computeVerticalMoveColumn()`
|
|
2684
|
-
- Changed Editor's Enter key handling to extract submit logic into `submitValue()` method for better code organization
|
|
2685
|
-
- Changed SettingsList to truncate long lines to viewport width, preventing text overflow
|
|
2686
|
-
- Changed Terminal's `stop()` method to drain stdin before restoring raw mode, fixing race condition where Ctrl+D could close parent shell over SSH
|
|
2687
|
-
- Changed TUI rendering to add `clearOnShrink` option (controlled by `PI_CLEAR_ON_SHRINK` env var) for reducing redraws on slower terminals
|
|
2688
|
-
- Changed TUI rendering to detect when extra lines exceed viewport height and trigger full re-render instead of incremental updates
|
|
2689
|
-
|
|
2690
|
-
### Fixed
|
|
2691
|
-
|
|
2692
|
-
- Fixed rendering of extra blank lines when content shrinks by improving cursor positioning logic during line deletion
|
|
2693
|
-
- Fixed cursor display position in Input component when scrolling horizontally through long text
|
|
2694
|
-
- Fixed Kitty keyboard protocol disable sequence to use safe write method, preventing potential output buffering issues
|
|
2695
|
-
- Fixed unnecessary full-screen redraws when changes occur in out-of-view components (e.g., spinners), reducing terminal scroll events and improving performance on slower connections
|
|
2696
|
-
- Fixed scrollback clearing behavior to only clear screen instead of scrollback when resizing or shrinking content, preventing loss of terminal history
|
|
2697
|
-
- Fixed `.git` directory appearing in autocomplete suggestions when filtering by prefix
|
|
2698
|
-
- Fixed cursor position corruption in Input component when displaying text with emoji and combining characters
|
|
2699
|
-
- Fixed `.git` directory appearing in autocomplete suggestions
|
|
2700
|
-
- Fixed race condition where Kitty key release events could leak to parent shell after TUI exit over slow SSH connections
|
|
2701
|
-
- Fixed Editor's word movement (Ctrl+Left/Right) to properly reset sticky column for subsequent vertical navigation
|
|
2702
|
-
- Fixed Editor's undo operation to reset sticky column state when restoring cursor position
|
|
2703
|
-
- Fixed Editor's right arrow key at end of last line to set sticky column for subsequent up/down navigation
|
|
2704
|
-
- Fixed TUI rendering to correctly detect viewport changes and avoid false full-redraws after content shrinks
|
|
2705
|
-
- Fixed Kitty protocol key parsing to prefer codepoint over base layout for Latin letters and symbols, fixing keyboard layout issues (e.g., Dvorak)
|
|
2706
|
-
|
|
2707
|
-
## [11.0.0] - 2026-02-05
|
|
2708
|
-
|
|
2709
|
-
### Added
|
|
2710
|
-
|
|
2711
|
-
- Introduced `terminal-capabilities.ts` module consolidating terminal detection and image protocol support
|
|
2712
|
-
- Added `TerminalInfo` class with methods for detecting image lines and formatting notifications
|
|
2713
|
-
- Added `NotifyProtocol` enum supporting Bell, OSC 99, and OSC 9 notification protocols
|
|
2714
|
-
- Added `isNotificationSuppressed()` function to check `OMP_NOTIFICATIONS` environment variable
|
|
2715
|
-
- Added `TERMINAL` constant providing detected terminal capabilities at runtime
|
|
2716
|
-
|
|
2717
|
-
### Changed
|
|
2718
|
-
|
|
2719
|
-
- Changed notification suppression environment variable from `OMP_NOTIFICATIONS` to `PI_NOTIFICATIONS`
|
|
2720
|
-
- Changed TUI write log environment variable from `OMP_TUI_WRITE_LOG` to `PI_TUI_WRITE_LOG`
|
|
2721
|
-
- Changed hardware cursor environment variable from `OMP_HARDWARE_CURSOR` to `PI_HARDWARE_CURSOR`
|
|
2722
|
-
- Updated environment variable access to use `getEnv()` utility function from `@oh-my-pi/pi-utils` for consistent handling
|
|
2723
|
-
- Renamed `TERMINAL_INFO` export to `TERMINAL` for clearer API semantics
|
|
2724
|
-
- Reorganized terminal image exports from `terminal-image` to `terminal-capabilities` module
|
|
2725
|
-
- Updated all internal references to use `TERMINAL` instead of `TERMINAL_INFO`
|
|
2726
|
-
|
|
2727
|
-
### Removed
|
|
2728
|
-
|
|
2729
|
-
- Removed `terminal-image` module exports from public API (functionality migrated to `terminal-capabilities`)
|
|
2730
|
-
|
|
2731
|
-
## [10.5.0] - 2026-02-04
|
|
2732
|
-
|
|
2733
|
-
### Fixed
|
|
2734
|
-
|
|
2735
|
-
- Treated inline image lines with cursor-move prefixes as image sequences to prevent width overflow crashes
|
|
2736
|
-
|
|
2737
|
-
## [9.8.0] - 2026-02-01
|
|
2738
|
-
|
|
2739
|
-
### Changed
|
|
2740
|
-
|
|
2741
|
-
- Moved `wrapTextWithAnsi` export to `@oh-my-pi/pi-natives` package
|
|
2742
|
-
|
|
2743
|
-
### Fixed
|
|
2744
|
-
|
|
2745
|
-
- Improved Kitty terminal key sequence parsing to correctly handle text field codepoints in CSI-u sequences
|
|
2746
|
-
- Fixed handling of private use Unicode codepoints (U+E000 to U+F8FF) in Kitty key decoding to prevent invalid character interpretation
|
|
2747
|
-
|
|
2748
|
-
## [9.7.0] - 2026-02-01
|
|
2749
|
-
|
|
2750
|
-
### Breaking Changes
|
|
2751
|
-
|
|
2752
|
-
- Removed `Key` helper object from public API; use string literals like `"ctrl+c"` instead of `Key.ctrl("c")`
|
|
2753
|
-
- Removed `KeyEventType` export from public API
|
|
2754
|
-
|
|
2755
|
-
### Changed
|
|
2756
|
-
|
|
2757
|
-
- Migrated key parsing and matching logic to native implementation for improved performance
|
|
2758
|
-
- Simplified `isKeyRelease()` and `isKeyRepeat()` to use regex pattern matching instead of string inclusion checks
|
|
2759
|
-
|
|
2760
|
-
Older entries are archived in [packages/tui/CHANGELOG.md@e6b8074bbe4b](https://github.com/can1357/oh-my-pi/blob/e6b8074bbe4b0d459aa812948822c2a634c8e62d/packages/tui/CHANGELOG.md).
|
|
2682
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@a66558815743](https://github.com/can1357/oh-my-pi/blob/a66558815743a61164ceebceb2bb528989fb09b3/packages/tui/CHANGELOG.md).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ImageBudget } from "../components/image.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type Markdown } from "../components/markdown.js";
|
|
3
3
|
import { Container } from "../tui.js";
|
|
4
4
|
export interface FigureMarkdownOptions {
|
|
5
5
|
/** Markdown for one prose run (and a failed figure's code fallback); FigureMarkdown drives its transience. */
|
|
@@ -32,7 +32,12 @@ export declare class FigureMarkdown extends Container {
|
|
|
32
32
|
/**
|
|
33
33
|
* The trimmed prose ahead of the first figure, or "" when the block opens
|
|
34
34
|
* with one. These are the only rows plain Markdown reproduces byte for
|
|
35
|
-
* byte, so they are all a transcript may publish ahead of the figure.
|
|
35
|
+
* byte, so they are all a transcript may publish ahead of the figure. While
|
|
36
|
+
* that prose is still the streaming tail (no figure follows it yet), only
|
|
37
|
+
* its settled prefix ({@link Markdown.getLastRenderStableText}) qualifies:
|
|
38
|
+
* the rest is still being written. Both forms are in
|
|
39
|
+
* {@link streamingMarkdownSource} form, so the settled prefix stays a byte
|
|
40
|
+
* prefix of the whole run once a figure splits it off.
|
|
36
41
|
*/
|
|
37
42
|
get leadingProse(): string;
|
|
38
43
|
/** Whether a figure's final source still waits for its raster, or a chart for its pick or raster. */
|
|
@@ -2,6 +2,14 @@ import { type TokensList } from "@oh-my-pi/pi-utils/marked";
|
|
|
2
2
|
import type { DescribeContext, NativeNode } from "../native/node.js";
|
|
3
3
|
import type { SymbolTheme } from "../symbols.js";
|
|
4
4
|
import type { Component } from "../tui.js";
|
|
5
|
+
/**
|
|
6
|
+
* `text` as the streaming renderer lexes it: OSC 8 terminators unified and
|
|
7
|
+
* tabs expanded. {@link Markdown.getLastRenderStableText} is a prefix of this
|
|
8
|
+
* form, not of the raw source, so append-only publishers that mix settled
|
|
9
|
+
* prefixes with whole blocks must compare both in this form. Rendering it is
|
|
10
|
+
* byte-identical to rendering `text`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function streamingMarkdownSource(text: string): string;
|
|
5
13
|
/** @internal exported for tests — the grown-line block-kind gate. */
|
|
6
14
|
export declare function fastLineStartHazard(grownLine: string): boolean;
|
|
7
15
|
/**
|
|
@@ -12,6 +12,8 @@ import type { Component } from "../tui.js";
|
|
|
12
12
|
export declare class Text implements Component {
|
|
13
13
|
#private;
|
|
14
14
|
setIgnoreTight(ignore: boolean): this;
|
|
15
|
+
/** Override row wrapping when the source carries structural prefixes. */
|
|
16
|
+
setWrapLine(wrapLine: (line: string, width: number) => string[]): this;
|
|
15
17
|
constructor(text?: string, paddingX?: number, paddingY?: number, customBgFn?: (text: string) => string);
|
|
16
18
|
/** Return bounded text and layout state for debug inspection. */
|
|
17
19
|
debugState(): Record<string, unknown>;
|
|
@@ -9,6 +9,8 @@ import type { KeyId } from "./app-keybindings.js";
|
|
|
9
9
|
export declare function matchesAppInterrupt(data: string): boolean;
|
|
10
10
|
/** Match `app.tools.expand`, falling back to Ctrl+O when the action is unbound. */
|
|
11
11
|
export declare function matchesAppToolsExpand(data: string): boolean;
|
|
12
|
+
/** Effective tools-expand key: the first bound key, or Ctrl+O when unbound. */
|
|
13
|
+
export declare function appToolsExpandKey(): KeyId;
|
|
12
14
|
/** Match the generic selector cancel keybinding. */
|
|
13
15
|
export declare function matchesSelectCancel(data: string): boolean;
|
|
14
16
|
/** Match the generic selector up-navigation keybinding. */
|
|
@@ -22,6 +22,7 @@ export declare const tmuxMultiplexer: {
|
|
|
22
22
|
readonly passthrough: typeof wrapTmuxPassthrough;
|
|
23
23
|
readonly settleMs: 100;
|
|
24
24
|
};
|
|
25
|
+
readonly gatesBracketedPaste: true;
|
|
25
26
|
readonly expandsRepPadding: true;
|
|
26
27
|
readonly expiresSynchronizedOutput: true;
|
|
27
28
|
readonly altRestoreEndsSynchronizedOutput: true;
|
|
@@ -73,6 +73,12 @@ export interface TerminalMultiplexerModule<Id extends string = string> {
|
|
|
73
73
|
* session, and an unrecognized report does not turn it off.
|
|
74
74
|
*/
|
|
75
75
|
readonly honorsSynchronizedOutput?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Tracks DEC 2004 per pane and forwards paste markers only while it is set,
|
|
78
|
+
* yet may leave DECRQM 2004 unanswered, so bracketed paste is reasserted
|
|
79
|
+
* without waiting for a DECRPM confirmation.
|
|
80
|
+
*/
|
|
81
|
+
readonly gatesBracketedPaste?: boolean;
|
|
76
82
|
/**
|
|
77
83
|
* The session does not reveal whether its client renders graphics while
|
|
78
84
|
* outer-terminal identity variables leak in, so images and Kitty
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
* so an Enter typed for the composer as the prompt appears never approves a
|
|
6
6
|
* call, and the chord itself only arms {@link APPROVE_ARM_MS} after mount: it
|
|
7
7
|
* is also the follow-up key, pressed mid-run exactly when prompts appear.
|
|
8
|
-
* Esc denies.
|
|
8
|
+
* Esc denies. With an {@link ApprovalDialogDraft}, every other key edits the
|
|
9
|
+
* composer mounted beneath the dialog, so a message can steer the turn while
|
|
10
|
+
* the call waits; an empty composer ignores Enter and arrows scroll the details.
|
|
11
|
+
* The prompt stays a few rows so the composer keeps its room; with a full
|
|
12
|
+
* prompt, the expand key (`app.tools.expand`) swaps it in, wrapped.
|
|
9
13
|
*/
|
|
10
14
|
import { type Component, type TUI } from "../index.js";
|
|
11
15
|
import type { NativeNode, NativeUiEvent } from "../native/node.js";
|
|
@@ -15,10 +19,16 @@ interface ApprovalDialogCallbacks {
|
|
|
15
19
|
onApprove(): void;
|
|
16
20
|
onDeny(): void;
|
|
17
21
|
}
|
|
22
|
+
/** The composer mounted beneath the dialog: it receives every key the dialog does not own. */
|
|
23
|
+
export interface ApprovalDialogDraft {
|
|
24
|
+
/** True while the composer holds a draft — text, attachments, or a paste still landing: keys then edit and send it. */
|
|
25
|
+
hasDraft(): boolean;
|
|
26
|
+
handleInput(keyData: string): void;
|
|
27
|
+
}
|
|
18
28
|
interface ApprovalDialogOptions {
|
|
19
29
|
tui?: TUI;
|
|
20
|
-
/**
|
|
21
|
-
|
|
30
|
+
/** The prompt with every detail in full, which the expand key swaps in (wrapped) for the summarized one. */
|
|
31
|
+
fullPrompt?: string;
|
|
22
32
|
/** Deny once this many ms pass without input (any key restarts the countdown). */
|
|
23
33
|
timeout?: number;
|
|
24
34
|
/** Called when {@link timeout} expires, before the dialog denies. */
|
|
@@ -27,6 +37,8 @@ interface ApprovalDialogOptions {
|
|
|
27
37
|
onTimeoutStart?: () => void;
|
|
28
38
|
/** Called each time a key restarts the countdown. */
|
|
29
39
|
onTimeoutReset?: () => void;
|
|
40
|
+
/** Route typing to the composer instead of dropping it. */
|
|
41
|
+
draft?: ApprovalDialogDraft;
|
|
30
42
|
}
|
|
31
43
|
export declare class ApprovalDialogComponent implements Component {
|
|
32
44
|
#private;
|
|
@@ -37,15 +49,21 @@ export declare class ApprovalDialogComponent implements Component {
|
|
|
37
49
|
invalidate(): void;
|
|
38
50
|
dispose(): void;
|
|
39
51
|
handleInput(keyData: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Swap the summarized prompt for every detail, wrapped, or back, as the expand key does here;
|
|
54
|
+
* for hosts that intercept that key first. Restarts the countdown like any key. False (nothing
|
|
55
|
+
* changes) without a full prompt, so the host's own expand handling (tool previews) can run.
|
|
56
|
+
*/
|
|
57
|
+
toggleDetails(): boolean;
|
|
40
58
|
render(width: number): readonly string[];
|
|
41
59
|
/**
|
|
42
60
|
* In the dock, in the composer's place and framed as the composer is (an
|
|
43
61
|
* `omp.editor.*` root role), like the ask dialog, rather than a card over
|
|
44
|
-
* the transcript: the title, the details, then
|
|
45
|
-
* Approve, whose keys show in their tooltips.
|
|
62
|
+
* the transcript: the title, the details, then Details (with a full prompt),
|
|
63
|
+
* Deny, and the primary Approve, whose keys show in their tooltips.
|
|
46
64
|
*/
|
|
47
65
|
describe(): NativeNode;
|
|
48
|
-
/** Deny and
|
|
66
|
+
/** Deny, Approve, and Details run exactly what Esc, the approve key, and the expand key run. */
|
|
49
67
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
50
68
|
}
|
|
51
69
|
export {};
|
|
@@ -24,6 +24,8 @@ export interface ComposerNativeState {
|
|
|
24
24
|
readonly rate?: number;
|
|
25
25
|
/** A turn is running: the send keycap becomes a Stop button. */
|
|
26
26
|
readonly running: boolean;
|
|
27
|
+
/** Native dictation control; undefined hides it for hosts without speech-to-text. */
|
|
28
|
+
readonly dictation?: "idle" | "starting" | "recording" | "transcribing";
|
|
27
29
|
/** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
|
|
28
30
|
readonly viewing?: readonly string[];
|
|
29
31
|
/** The session's title, quoted in italics as the TSP placeholder; undefined until it has one. */
|
|
@@ -220,7 +222,8 @@ export declare class CustomEditor extends Editor {
|
|
|
220
222
|
* once after construction (and again after `setEditorComponent` swaps the
|
|
221
223
|
* editor). Passing `undefined` clears any pending frame. */
|
|
222
224
|
setShimmerRepaintHandler(handler: (() => void) | undefined): void;
|
|
223
|
-
/** Editing is available during bootstrap; atomic sends wait until submission is wired and enabled
|
|
225
|
+
/** Editing is available during bootstrap; atomic sends wait until submission is wired and enabled
|
|
226
|
+
* and no dictation is still writing into the draft. */
|
|
224
227
|
protected get nativeSendable(): boolean;
|
|
225
228
|
/** Viewing a subagent, the draft goes to it: the placeholder names it; else the session's title, quoted in italics. */
|
|
226
229
|
describePlaceholder: () => TspText;
|
|
@@ -238,17 +241,17 @@ export declare class CustomEditor extends Editor {
|
|
|
238
241
|
* subagent is focused, the attachment chips, a `line` row of the
|
|
239
242
|
* shell-mode chip and the input, and the `bar`: model chip, effort chip
|
|
240
243
|
* (or the effort glyph as the model chip's icon), the generation rate,
|
|
241
|
-
* the other status facts, usage, then send (Stop while a turn runs).
|
|
242
|
-
* Clicks come back as `status.model`, `thinking.cycle`, `
|
|
243
|
-
* `interrupt` and `focus:<id>` actions.
|
|
244
|
+
* the other status facts, usage, dictation, then send (Stop while a turn runs).
|
|
245
|
+
* Clicks come back as `status.model`, `thinking.cycle`, `dictation.toggle`,
|
|
246
|
+
* `submit`, `interrupt` and `focus:<id>` actions.
|
|
244
247
|
*/
|
|
245
248
|
describeLayout: (input: NativeNode, cx: DescribeContext) => NativeEditorLayout;
|
|
246
249
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* the status facts' clicks
|
|
250
|
-
* go to the buffer; `send` submits its own prompt after
|
|
251
|
-
* for recall and waiting for in-flight clipboard work.
|
|
250
|
+
* Dictation clicks go directly to the host, independent of keybindings.
|
|
251
|
+
* Other controls take the same paths as their keys; the viewing header's
|
|
252
|
+
* links (`focus:<id>`) go to the host, the status facts' clicks to their source.
|
|
253
|
+
* Selection edits go to the buffer; `send` submits its own prompt after
|
|
254
|
+
* saving the old draft for recall and waiting for in-flight clipboard work.
|
|
252
255
|
*/
|
|
253
256
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
254
257
|
onEscape?: () => void;
|
|
@@ -275,6 +278,8 @@ export declare class CustomEditor extends Editor {
|
|
|
275
278
|
onRetry?: () => void;
|
|
276
279
|
/** Called when Caps Lock is pressed. */
|
|
277
280
|
onCapsLock?: () => void;
|
|
281
|
+
/** Called directly by the native microphone/stop control, without synthesizing a keypress. */
|
|
282
|
+
onToggleDictation?: () => void;
|
|
278
283
|
/** Called when left-arrow is pressed while the editor is empty (cursor necessarily at start). */
|
|
279
284
|
onLeftAtStart?: () => void;
|
|
280
285
|
/** Called when the viewing header asks to view agent `id` ({@link MAIN_AGENT_ID}: the main session). */
|
|
@@ -296,6 +301,8 @@ export declare class CustomEditor extends Editor {
|
|
|
296
301
|
* Clear all custom key handlers.
|
|
297
302
|
*/
|
|
298
303
|
clearCustomKeyHandlers(): void;
|
|
304
|
+
/** True while a clipboard-image or image-path paste is still landing; later keys queue behind it. */
|
|
305
|
+
get pasteInFlight(): boolean;
|
|
299
306
|
capturesInput(data: string): boolean;
|
|
300
307
|
handleInput(data: string): void;
|
|
301
308
|
/**
|
|
@@ -305,7 +312,8 @@ export declare class CustomEditor extends Editor {
|
|
|
305
312
|
* an Ask dialog opened over a non-empty prompt — so finishing or submitting
|
|
306
313
|
* the draft cannot fire an editor-slot shortcut that clears
|
|
307
314
|
* `editorContainer` and orphans the overlay. Only text editing, cursor
|
|
308
|
-
* movement, submission,
|
|
315
|
+
* movement, submission, the clear action, and pastes (clipboard images and
|
|
316
|
+
* image paths attach as on the composer) reach the buffer.
|
|
309
317
|
*/
|
|
310
318
|
handleDraftEdit(data: string): void;
|
|
311
319
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Theme } from "../theme/theme.js";
|
|
2
|
+
/** Wrap content under a distinct first-row prefix, preserving its column on continuation rows. */
|
|
3
|
+
export declare function wrapPrefixedLine(prefix: string, continuationPrefix: string, content: string, width: number): string[];
|
|
4
|
+
/** Preserve a tree connector's ancestor guides while hanging a long node beneath its label. */
|
|
5
|
+
export declare function wrapTreeLine(line: string, width: number, theme: Theme): string[] | undefined;
|
|
@@ -327,6 +327,14 @@ export interface ParsedKittyPlacementLine {
|
|
|
327
327
|
* callers fall back to writing the line verbatim.
|
|
328
328
|
*/
|
|
329
329
|
export declare function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacementLine | null;
|
|
330
|
+
/**
|
|
331
|
+
* Rows above its own line that an {@link Image} direct-placement line paints
|
|
332
|
+
* over: the `CUU(rows-1)` of its lead-in, or 0 for any other line. The renderer
|
|
333
|
+
* erases those rows before a SIXEL/iTerm2 block is written again, because
|
|
334
|
+
* those protocols have no delete command and the block's reserved rows are
|
|
335
|
+
* byte-identical between frames, so the diff never rewrites them.
|
|
336
|
+
*/
|
|
337
|
+
export declare function directPlacementLift(line: string): number;
|
|
330
338
|
/**
|
|
331
339
|
* Rebuild an {@link Image} direct-placement line for the viewport row it is
|
|
332
340
|
* written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
|
|
@@ -38,6 +38,7 @@ declare const registry: readonly [{
|
|
|
38
38
|
readonly passthrough: typeof import("./tmux.js").wrapTmuxPassthrough;
|
|
39
39
|
readonly settleMs: 100;
|
|
40
40
|
};
|
|
41
|
+
readonly gatesBracketedPaste: true;
|
|
41
42
|
readonly expandsRepPadding: true;
|
|
42
43
|
readonly expiresSynchronizedOutput: true;
|
|
43
44
|
readonly altRestoreEndsSynchronizedOutput: true;
|
package/dist/types/tmux.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { wrapTmuxPassthrough } from "./multiplexers/tmux.js";
|
|
2
2
|
/** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
|
|
3
3
|
export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* Emulator name candidates from a `#{client_termtype}<TAB>#{client_termname}`
|
|
6
|
+
* line, best first: the terminal-type reply (tmux 3.4+, XTVERSION), then the
|
|
7
|
+
* emulator the client's `TERM` names when it ships its own terminfo entry. Both
|
|
8
|
+
* are kept because the reply can name an unrecognized program (a nested tmux
|
|
9
|
+
* answers `tmux 3.4`) while `TERM` still identifies the emulator.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tmuxClientProgramsFromReport(report: string): string[];
|
|
4
12
|
/**
|
|
5
13
|
* Whether the terminal hosting this tmux session draws TSP programs natively:
|
|
6
14
|
* it sets the session user option `@tern-tsp` while it watches the session.
|
|
@@ -12,13 +20,14 @@ export declare function tmuxSessionSpeaksTsp(env?: NodeJS.ProcessEnv): boolean;
|
|
|
12
20
|
*/
|
|
13
21
|
export declare function setTmuxPaneApcTitles(on: boolean, env?: NodeJS.ProcessEnv): void;
|
|
14
22
|
/**
|
|
15
|
-
* Resolve the terminal emulator
|
|
23
|
+
* Resolve the terminal emulator names recorded for this tmux client, best first.
|
|
16
24
|
*
|
|
17
25
|
* tmux overwrites pane identity variables with its own values, but retains the
|
|
18
|
-
* attached client's terminal-type reply in `#{client_termtype}
|
|
19
|
-
*
|
|
26
|
+
* attached client's terminal-type reply in `#{client_termtype}` and its `TERM`
|
|
27
|
+
* in `#{client_termname}` (see {@link tmuxClientProgramsFromReport}). The local
|
|
28
|
+
* IPC query runs once per process and degrades to `[]` when unavailable.
|
|
20
29
|
*/
|
|
21
|
-
export declare function
|
|
30
|
+
export declare function resolveTmuxClientTerminalNames(env?: NodeJS.ProcessEnv): readonly string[];
|
|
22
31
|
/**
|
|
23
32
|
* Whether tmux draws SIXEL to the client attached to this session: its
|
|
24
33
|
* effective terminal description for that client carries `Sxl`.
|
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.
|
|
4
|
+
"version": "18.11.0",
|
|
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,14 +41,14 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.
|
|
44
|
+
"@oh-my-pi/omptype": "18.11.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.11.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.11.0",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.11.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.11.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.11.0",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.11.0",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.11.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Markdown,
|
|
8
8
|
type MarkdownTheme,
|
|
9
9
|
rewriteMarkdownLinkDestinations,
|
|
10
|
+
streamingMarkdownSource,
|
|
10
11
|
} from "../components/markdown";
|
|
11
12
|
import { Spacer } from "../components/spacer";
|
|
12
13
|
import { Text } from "../components/text";
|
|
@@ -111,6 +112,14 @@ interface StableRenderInputs {
|
|
|
111
112
|
readonly color: ((text: string) => string) | undefined;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* A partial last thinking line the prose fold may still drop: the start of a
|
|
117
|
+
* fence marker (`` ` ``, `~`) or of an empty `<!-- -->` sentinel. Once it turns
|
|
118
|
+
* into either, the fold's ellipsis lands back on the line above it
|
|
119
|
+
* (`formatThinkingForDisplay`), so that line is not yet settled.
|
|
120
|
+
*/
|
|
121
|
+
const UNSETTLED_THINKING_TAIL = /(?:^|\n)[^\S\n]*(?:`{1,2}|~{1,2}|<(?:!(?:-(?:-[^\S\n]*-{0,2})?)?)?)[^\S\n]*$/;
|
|
122
|
+
|
|
114
123
|
function isSnapshotExtension(previous: readonly StablePart[], current: readonly StablePart[]): boolean {
|
|
115
124
|
if (previous.length > current.length) return false;
|
|
116
125
|
for (let index = 0; index < previous.length; index++) {
|
|
@@ -1240,17 +1249,23 @@ export class AssistantMessageComponent extends Container {
|
|
|
1240
1249
|
// Streaming child: publish Markdown's frozen prefix, and only
|
|
1241
1250
|
// once non-blank content exists past it — the block's last
|
|
1242
1251
|
// non-blank line is still being written (thinking's prose fold
|
|
1243
|
-
// may rewrite it) and must stay out of published bytes.
|
|
1252
|
+
// may rewrite it) and must stay out of published bytes. A
|
|
1253
|
+
// partial thinking line the fold may still drop does not count.
|
|
1254
|
+
// The frozen prefix is in streamingMarkdownSource form, so
|
|
1255
|
+
// offsets into the block use that form too.
|
|
1244
1256
|
const raw = md.getLastRenderStableText();
|
|
1245
1257
|
const frozen = raw.trim();
|
|
1246
|
-
|
|
1258
|
+
let past = streamingMarkdownSource(item.lastText).slice(raw.length);
|
|
1259
|
+
if (item.blockType === "thinking") past = past.replace(UNSETTLED_THINKING_TAIL, "");
|
|
1260
|
+
if (frozen.length > 0 && /\S/.test(past)) {
|
|
1247
1261
|
parts.push({ kind: item.blockType, text: frozen });
|
|
1248
1262
|
}
|
|
1249
1263
|
break;
|
|
1250
1264
|
}
|
|
1251
1265
|
// Closed child: only the streaming tail may mutate in place, so
|
|
1252
|
-
// everything before it is final.
|
|
1253
|
-
|
|
1266
|
+
// everything before it is final. Same form as the frozen prefix it
|
|
1267
|
+
// published while streaming, so the snapshot still extends.
|
|
1268
|
+
parts.push({ kind: item.blockType, text: streamingMarkdownSource(item.lastText) });
|
|
1254
1269
|
itemIndex++;
|
|
1255
1270
|
continue;
|
|
1256
1271
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Tokens } from "@oh-my-pi/pi-utils/marked";
|
|
2
2
|
import type { ImageBudget } from "../components/image";
|
|
3
|
-
import type
|
|
3
|
+
import { type Markdown, streamingMarkdownSource } from "../components/markdown";
|
|
4
4
|
import { Spacer } from "../components/spacer";
|
|
5
5
|
import { Container } from "../tui";
|
|
6
6
|
import { SvgFigure, svgFigureRendering } from "./svg-figure";
|
|
@@ -68,11 +68,18 @@ export class FigureMarkdown extends Container {
|
|
|
68
68
|
/**
|
|
69
69
|
* The trimmed prose ahead of the first figure, or "" when the block opens
|
|
70
70
|
* with one. These are the only rows plain Markdown reproduces byte for
|
|
71
|
-
* byte, so they are all a transcript may publish ahead of the figure.
|
|
71
|
+
* byte, so they are all a transcript may publish ahead of the figure. While
|
|
72
|
+
* that prose is still the streaming tail (no figure follows it yet), only
|
|
73
|
+
* its settled prefix ({@link Markdown.getLastRenderStableText}) qualifies:
|
|
74
|
+
* the rest is still being written. Both forms are in
|
|
75
|
+
* {@link streamingMarkdownSource} form, so the settled prefix stays a byte
|
|
76
|
+
* prefix of the whole run once a figure splits it off.
|
|
72
77
|
*/
|
|
73
78
|
get leadingProse(): string {
|
|
74
79
|
const first = this.#parts[0];
|
|
75
|
-
|
|
80
|
+
if (first?.kind !== "markdown") return "";
|
|
81
|
+
if (this.#transient && this.#parts.length === 1) return first.md.getLastRenderStableText().trim();
|
|
82
|
+
return streamingMarkdownSource(first.text);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
/** Whether a figure's final source still waits for its raster, or a chart for its pick or raster. */
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from "../tools/read";
|
|
17
17
|
import { PREVIEW_LIMITS, shortenPath } from "../render/render-utils";
|
|
18
18
|
import { fileHyperlink, renderCodeCell, renderMarkdownCell } from "../render";
|
|
19
|
+
import { wrapTreeLine } from "../render/hanging-wrap";
|
|
20
|
+
import { wrapTextWithAnsi } from "../utils";
|
|
19
21
|
import { canonicalizeMessage } from "./thinking-display";
|
|
20
22
|
import { internalUrlSchemeSpec, splitUrlScheme } from "../tools/url-scheme-host";
|
|
21
23
|
import type { ToolExecutionHandle } from "./tool-execution";
|
|
@@ -891,7 +893,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
891
893
|
|
|
892
894
|
// Clear previous children and rebuild the summary and preview blocks.
|
|
893
895
|
this.clear();
|
|
894
|
-
this.#text = new Text("", 0, 0)
|
|
896
|
+
this.#text = new Text("", 0, 0).setWrapLine(
|
|
897
|
+
(line, width) => wrapTreeLine(line, width, theme) ?? wrapTextWithAnsi(line, width),
|
|
898
|
+
);
|
|
895
899
|
|
|
896
900
|
if (displayRows.length === 0) {
|
|
897
901
|
this.#text.setText(` ${theme.format.bullet} ${theme.fg("toolTitle", theme.bold("Read"))}`);
|
package/src/chat/svg-figure.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { hexToOklch, hueDistance, logger, oklchCusp, oklchToHex } from "@oh-my-p
|
|
|
17
17
|
import { Image, type ImageBudget, imageMaxColumns } from "../components/image";
|
|
18
18
|
import { fencedCode, type Markdown } from "../components/markdown";
|
|
19
19
|
import { type CellDimensions, getCellDimensions, getImageDimensions } from "../terminal-capabilities";
|
|
20
|
-
import { getThemeEpoch, type ThemeColor, theme } from "../theme";
|
|
20
|
+
import { ensureThemeSync, getThemeEpoch, type ThemeColor, theme } from "../theme";
|
|
21
21
|
import type { Component } from "../tui";
|
|
22
22
|
import { closePartialSvg, prepareSvg } from "./svg-source";
|
|
23
23
|
|
|
@@ -59,6 +59,7 @@ const SERIES_CHROMA = 0.19;
|
|
|
59
59
|
|
|
60
60
|
/** The active theme's color per figure token, for resolving a figure with `prepareSvg`. */
|
|
61
61
|
export function svgFigurePalette(): Record<string, string> {
|
|
62
|
+
ensureThemeSync();
|
|
62
63
|
const palette: Record<string, string> = { surface: theme.getBgHex("selectedBg") };
|
|
63
64
|
for (const token in PALETTE) palette[token] = theme.getColorHex(PALETTE[token]!);
|
|
64
65
|
seriesColors(palette.accent!, theme.isLight).forEach((color, at) => {
|
|
@@ -181,6 +182,7 @@ export class SvgFigure implements Component {
|
|
|
181
182
|
#placeholder: readonly string[] | undefined;
|
|
182
183
|
|
|
183
184
|
constructor(options: SvgFigureOptions) {
|
|
185
|
+
ensureThemeSync();
|
|
184
186
|
this.#options = options;
|
|
185
187
|
}
|
|
186
188
|
|
package/src/chat/svg-source.ts
CHANGED
|
@@ -35,9 +35,12 @@ export function splitSvgFences(markdown: string): FigureSegment[] {
|
|
|
35
35
|
let proseStart = 0;
|
|
36
36
|
let lineStart = 0;
|
|
37
37
|
while (lineStart <= markdown.length) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const next =
|
|
38
|
+
let lineEnd = lineStart;
|
|
39
|
+
while (lineEnd < markdown.length && markdown[lineEnd] !== "\n" && markdown[lineEnd] !== "\r") lineEnd++;
|
|
40
|
+
const next =
|
|
41
|
+
lineEnd === markdown.length
|
|
42
|
+
? markdown.length + 1
|
|
43
|
+
: lineEnd + (markdown[lineEnd] === "\r" && markdown[lineEnd + 1] === "\n" ? 2 : 1);
|
|
41
44
|
const line = markdown.slice(lineStart, lineEnd);
|
|
42
45
|
if (fence) {
|
|
43
46
|
if (closesFence(line, fence.marker)) {
|
|
@@ -52,6 +52,18 @@ function normalizeOsc8Terminators(text: string): string {
|
|
|
52
52
|
return text.replace(OSC8_ST_PREFIX_REGEX, "$1\x07");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* `text` as the streaming renderer lexes it: OSC 8 terminators unified and
|
|
57
|
+
* tabs expanded. {@link Markdown.getLastRenderStableText} is a prefix of this
|
|
58
|
+
* form, not of the raw source, so append-only publishers that mix settled
|
|
59
|
+
* prefixes with whole blocks must compare both in this form. Rendering it is
|
|
60
|
+
* byte-identical to rendering `text`.
|
|
61
|
+
*/
|
|
62
|
+
export function streamingMarkdownSource(text: string): string {
|
|
63
|
+
if (!text.includes("\t") && !text.includes("\x1b")) return text;
|
|
64
|
+
return replaceTabs(normalizeOsc8Terminators(text));
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
/** The longest suffix of `text` a future append could still complete into a
|
|
56
68
|
* full `\x1b]8;[^\x07\x1b]*\x1b\\` match: the last `\x1b]8;` plus clean
|
|
57
69
|
* body (or that plus the pending ST-ESC `\x1b`), or a strict prefix of the
|