@oh-my-pi/pi-tui 18.10.3 → 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.
Files changed (50) hide show
  1. package/CHANGELOG.md +39 -142
  2. package/dist/types/app-keybindings.d.ts +5 -0
  3. package/dist/types/chat/figure-markdown.d.ts +7 -2
  4. package/dist/types/chat/read-tool-group.d.ts +2 -2
  5. package/dist/types/components/markdown.d.ts +8 -0
  6. package/dist/types/components/text.d.ts +2 -0
  7. package/dist/types/keybinding-matchers.d.ts +6 -0
  8. package/dist/types/multiplexers/tmux.d.ts +1 -0
  9. package/dist/types/multiplexers/types.d.ts +6 -0
  10. package/dist/types/overlays/approval-dialog.d.ts +69 -0
  11. package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
  12. package/dist/types/prompt/custom-editor.d.ts +18 -10
  13. package/dist/types/render/hanging-wrap.d.ts +5 -0
  14. package/dist/types/terminal-capabilities.d.ts +8 -0
  15. package/dist/types/terminal-multiplexer.d.ts +1 -0
  16. package/dist/types/tmux.d.ts +30 -4
  17. package/dist/types/tools/read.d.ts +7 -0
  18. package/package.json +9 -9
  19. package/src/app-keybindings.ts +7 -0
  20. package/src/apps/debug/raw-sse.ts +2 -2
  21. package/src/chat/assistant-message.ts +19 -4
  22. package/src/chat/figure-markdown.ts +10 -3
  23. package/src/chat/read-tool-group.ts +78 -32
  24. package/src/chat/svg-figure.ts +3 -1
  25. package/src/chat/svg-source.ts +6 -3
  26. package/src/components/markdown.ts +12 -0
  27. package/src/components/text.ts +11 -1
  28. package/src/hotkeys-markdown.ts +1 -0
  29. package/src/keybinding-matchers.ts +19 -0
  30. package/src/multiplexers/tmux.ts +3 -0
  31. package/src/multiplexers/types.ts +6 -0
  32. package/src/overlays/approval-dialog.ts +321 -0
  33. package/src/overlays/btw-history-panel.ts +2 -2
  34. package/src/overlays/model-browser.ts +9 -6
  35. package/src/overlays/plan-review-overlay.ts +16 -0
  36. package/src/prompt/custom-editor.ts +149 -64
  37. package/src/render/code-cell.ts +10 -4
  38. package/src/render/hanging-wrap.ts +31 -0
  39. package/src/terminal-capabilities.ts +25 -7
  40. package/src/terminal.ts +14 -3
  41. package/src/tmux.ts +74 -16
  42. package/src/tools/ask.ts +20 -9
  43. package/src/tools/edit.ts +4 -1
  44. package/src/tools/lsp.ts +9 -2
  45. package/src/tools/read.ts +21 -14
  46. package/src/tools/todo.ts +3 -2
  47. package/src/tools/write.ts +14 -4
  48. package/src/tui.ts +16 -2
  49. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
  50. package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
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
+
26
+ ## [18.10.4] - 2026-10-13
27
+
28
+ ### Added
29
+
30
+ - Added `ApprovalDialogComponent` and the `app.approval.approve` keybinding (default `Ctrl+Enter`, `Ctrl+Q`) ([#15548](https://github.com/can1357/oh-my-pi/pull/15548) by [@H4vC](https://github.com/H4vC))
31
+ - Plan review overlay can take a remote answer: it shows the remotely chosen execution tier and locks further input like a terminal approval ([#15552](https://github.com/can1357/oh-my-pi/pull/15552) by [@Shadorain](https://github.com/Shadorain))
32
+
33
+ ### Fixed
34
+
35
+ - The model browser shows the price a model is billed at now, not the one current when its row was loaded: DeepSeek V4 Pro shows its $0.30 / $1.20 card in effect since 2026-09-14 instead of $1.32 / $3.96, and Gemini 4 Argon switches to $4 / $20 on 2026-12-01 ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
36
+ - Clicking Copy answer in the `/btw` history (and Copy raw in the raw SSE viewer) on native terminals copies the answer instead of the button's label ([#15547](https://github.com/can1357/oh-my-pi/pull/15547) by [@H4vC](https://github.com/H4vC))
37
+ - Fixed image previews inside tmux showing as `SIXEL IMAGE (WxH)` placeholder text when the outer terminal lacks SIXEL; SIXEL is now enabled from tmux's DA1 reply only when tmux's terminal description for the attached client has `Sxl` ([#15513](https://github.com/can1357/oh-my-pi/issues/15513))
38
+
39
+ ### Removed
40
+
41
+ - Removed `tools/eval-format/code-mode-declarations` (`generateCodeModeDeclarations`), which only served Codex Code Mode.
42
+
5
43
  ## [18.10.3] - 2026-10-12
6
44
 
7
45
  ### Changed
@@ -2641,145 +2679,4 @@
2641
2679
 
2642
2680
  - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
2643
2681
 
2644
- ## [11.3.0] - 2026-02-06
2645
-
2646
- ### Breaking Changes
2647
-
2648
- - Removed `getCursorPosition()` method from Component interface and implementations, eliminating hardware cursor positioning support
2649
-
2650
- ### Added
2651
-
2652
- - Added sticky column behavior for vertical cursor movement, preserving target column when navigating through lines of varying lengths
2653
- - Added `drainInput()` method to Terminal interface to prevent Kitty key release events from leaking to parent shell over slow SSH connections
2654
- - Added `setClearOnShrink()` method to control whether full re-render occurs when content shrinks below working area
2655
- - Added support for hidden paths (e.g., `.pi`, `.github`) in autocomplete while excluding `.git` directories
2656
-
2657
- ### Changed
2658
-
2659
- - Changed default value of `PI_HARDWARE_CURSOR` environment variable from implicit true to explicit `"1"` for clarity
2660
- - Changed default value of `PI_CLEAR_ON_SHRINK` environment variable from implicit false to explicit `"0"` for clarity
2661
- - Changed TUI to clear screen on startup to prevent shell prompts and status messages from bleeding into the first rendered frame
2662
- - Refactored full-render logic into reusable helper function to reduce code duplication across multiple render paths
2663
- - Changed autocomplete to include hidden paths but filter out `.git` and its contents
2664
- - Changed Input component to properly handle surrogate pairs in Unicode text, preventing cursor display corruption with emoji and multi-byte characters
2665
- - Changed Editor to use `setCursorCol()` for all cursor column updates, enabling sticky column tracking
2666
- - Changed Editor's vertical navigation to implement sticky column logic via `moveToVisualLine()` and `computeVerticalMoveColumn()`
2667
- - Changed Editor's Enter key handling to extract submit logic into `submitValue()` method for better code organization
2668
- - Changed SettingsList to truncate long lines to viewport width, preventing text overflow
2669
- - Changed Terminal's `stop()` method to drain stdin before restoring raw mode, fixing race condition where Ctrl+D could close parent shell over SSH
2670
- - Changed TUI rendering to add `clearOnShrink` option (controlled by `PI_CLEAR_ON_SHRINK` env var) for reducing redraws on slower terminals
2671
- - Changed TUI rendering to detect when extra lines exceed viewport height and trigger full re-render instead of incremental updates
2672
-
2673
- ### Fixed
2674
-
2675
- - Fixed rendering of extra blank lines when content shrinks by improving cursor positioning logic during line deletion
2676
- - Fixed cursor display position in Input component when scrolling horizontally through long text
2677
- - Fixed Kitty keyboard protocol disable sequence to use safe write method, preventing potential output buffering issues
2678
- - 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
2679
- - Fixed scrollback clearing behavior to only clear screen instead of scrollback when resizing or shrinking content, preventing loss of terminal history
2680
- - Fixed `.git` directory appearing in autocomplete suggestions when filtering by prefix
2681
- - Fixed cursor position corruption in Input component when displaying text with emoji and combining characters
2682
- - Fixed `.git` directory appearing in autocomplete suggestions
2683
- - Fixed race condition where Kitty key release events could leak to parent shell after TUI exit over slow SSH connections
2684
- - Fixed Editor's word movement (Ctrl+Left/Right) to properly reset sticky column for subsequent vertical navigation
2685
- - Fixed Editor's undo operation to reset sticky column state when restoring cursor position
2686
- - Fixed Editor's right arrow key at end of last line to set sticky column for subsequent up/down navigation
2687
- - Fixed TUI rendering to correctly detect viewport changes and avoid false full-redraws after content shrinks
2688
- - Fixed Kitty protocol key parsing to prefer codepoint over base layout for Latin letters and symbols, fixing keyboard layout issues (e.g., Dvorak)
2689
-
2690
- ## [11.0.0] - 2026-02-05
2691
-
2692
- ### Added
2693
-
2694
- - Introduced `terminal-capabilities.ts` module consolidating terminal detection and image protocol support
2695
- - Added `TerminalInfo` class with methods for detecting image lines and formatting notifications
2696
- - Added `NotifyProtocol` enum supporting Bell, OSC 99, and OSC 9 notification protocols
2697
- - Added `isNotificationSuppressed()` function to check `OMP_NOTIFICATIONS` environment variable
2698
- - Added `TERMINAL` constant providing detected terminal capabilities at runtime
2699
-
2700
- ### Changed
2701
-
2702
- - Changed notification suppression environment variable from `OMP_NOTIFICATIONS` to `PI_NOTIFICATIONS`
2703
- - Changed TUI write log environment variable from `OMP_TUI_WRITE_LOG` to `PI_TUI_WRITE_LOG`
2704
- - Changed hardware cursor environment variable from `OMP_HARDWARE_CURSOR` to `PI_HARDWARE_CURSOR`
2705
- - Updated environment variable access to use `getEnv()` utility function from `@oh-my-pi/pi-utils` for consistent handling
2706
- - Renamed `TERMINAL_INFO` export to `TERMINAL` for clearer API semantics
2707
- - Reorganized terminal image exports from `terminal-image` to `terminal-capabilities` module
2708
- - Updated all internal references to use `TERMINAL` instead of `TERMINAL_INFO`
2709
-
2710
- ### Removed
2711
-
2712
- - Removed `terminal-image` module exports from public API (functionality migrated to `terminal-capabilities`)
2713
-
2714
- ## [10.5.0] - 2026-02-04
2715
-
2716
- ### Fixed
2717
-
2718
- - Treated inline image lines with cursor-move prefixes as image sequences to prevent width overflow crashes
2719
-
2720
- ## [9.8.0] - 2026-02-01
2721
-
2722
- ### Changed
2723
-
2724
- - Moved `wrapTextWithAnsi` export to `@oh-my-pi/pi-natives` package
2725
-
2726
- ### Fixed
2727
-
2728
- - Improved Kitty terminal key sequence parsing to correctly handle text field codepoints in CSI-u sequences
2729
- - Fixed handling of private use Unicode codepoints (U+E000 to U+F8FF) in Kitty key decoding to prevent invalid character interpretation
2730
-
2731
- ## [9.7.0] - 2026-02-01
2732
-
2733
- ### Breaking Changes
2734
-
2735
- - Removed `Key` helper object from public API; use string literals like `"ctrl+c"` instead of `Key.ctrl("c")`
2736
- - Removed `KeyEventType` export from public API
2737
-
2738
- ### Changed
2739
-
2740
- - Migrated key parsing and matching logic to native implementation for improved performance
2741
- - Simplified `isKeyRelease()` and `isKeyRepeat()` to use regex pattern matching instead of string inclusion checks
2742
-
2743
- ## [9.6.2] - 2026-02-01
2744
-
2745
- ### Changed
2746
-
2747
- - Renamed `EllipsisKind` enum to `Ellipsis` for clearer API naming
2748
- - Changed hardcoded ellipsis character from theme-configurable to literal "…" in editor truncation
2749
- - Refactored `visibleWidth` function to use caching wrapper around new `visibleWidthRaw` implementation for improved performance
2750
-
2751
- ### Removed
2752
-
2753
- - Removed `truncateToWidth`, `sliceWithWidth`, and `extractSegments` functions from public API (now re-exported directly from @oh-my-pi/pi-natives)
2754
- - Removed `ellipsis` property from `SymbolTheme` interface
2755
- - Removed `extractAnsiCode` function from public API
2756
-
2757
- ## [9.6.1] - 2026-02-01
2758
-
2759
- ### Changed
2760
-
2761
- - Improved performance of key ID parsing with optimized cache lookup strategy
2762
- - Simplified `visibleWidth` calculation to use consistent Bun.stringWidth approach for all string lengths
2763
-
2764
- ### Removed
2765
-
2766
- - Removed `visibleWidth` benchmark file in favor of Kitty sequence benchmarking
2767
-
2768
- ## [9.5.0] - 2026-02-01
2769
-
2770
- ### Changed
2771
-
2772
- - Improved fuzzy file search performance by using native implementation instead of spawning external process
2773
- - Replaced external `fd` binary with native fuzzy path search for `@`-prefixed autocomplete
2774
-
2775
- ## [9.4.0] - 2026-01-31
2776
-
2777
- ### Added
2778
-
2779
- - Exported `padding` utility function for creating space-padded strings efficiently
2780
-
2781
- ### Changed
2782
-
2783
- - Optimized padding operations across all components to use pre-allocated space buffer for better performance
2784
-
2785
- Older entries are archived in [packages/tui/CHANGELOG.md@e19bb3cb2344](https://github.com/can1357/oh-my-pi/blob/e19bb3cb2344a8953dc04aa398b810230f0e24c3/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).
@@ -18,6 +18,7 @@ interface AppKeybindings {
18
18
  "app.tools.expand": true;
19
19
  "app.tools.toggleVisibility": true;
20
20
  "app.ask.hide": true;
21
+ "app.approval.approve": true;
21
22
  "app.editor.external": true;
22
23
  "app.message.followUp": true;
23
24
  "app.retry": true;
@@ -243,6 +244,10 @@ export declare const KEYBINDINGS: {
243
244
  readonly defaultKeys: "alt+h";
244
245
  readonly description: "Hide or show a pending ask dialog";
245
246
  };
247
+ readonly "app.approval.approve": {
248
+ readonly defaultKeys: ["ctrl+enter", "ctrl+q"];
249
+ readonly description: "Approve a pending tool call";
250
+ };
246
251
  readonly "app.editor.external": {
247
252
  readonly defaultKeys: "ctrl+g";
248
253
  readonly description: "Open external editor";
@@ -1,5 +1,5 @@
1
1
  import type { ImageBudget } from "../components/image.js";
2
- import type { Markdown } from "../components/markdown.js";
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. */
@@ -74,8 +74,8 @@ export declare class ReadToolGroupComponent extends Container implements ToolExe
74
74
  * The read run as one inline `tool` on terminals that list the kind, else
75
75
  * the fallback: a bare `card` (role `omp.tool.read`) with `Read <path>` or
76
76
  * `Read (N)` in the head, a `list` of path items (status tone, link,
77
- * correction and conflict detail, nested usage) and, with content previews
78
- * on, a `code` block per read clamped by the card's preview while collapsed.
77
+ * correction and conflict detail, nested usage) and a clamped preview per
78
+ * read when content previews are on.
79
79
  */
80
80
  describe(cx?: DescribeContext): NativeNode;
81
81
  }
@@ -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. */
@@ -36,3 +38,7 @@ export declare function matchesAppExternalEditor(data: string): boolean;
36
38
  * effective follow-up binding.
37
39
  */
38
40
  export declare function matchesAppFollowUp(data: string): boolean;
41
+ /** Effective tool-approval keys: the `app.approval.approve` bindings, or Ctrl+Enter/Ctrl+Q when unbound. */
42
+ export declare function appApproveKeys(): readonly KeyId[];
43
+ /** Match the tool-approval key, recognizing modifier-tagged LF as Ctrl+Enter like {@link matchesAppFollowUp}. */
44
+ export declare function matchesAppApprove(data: string): boolean;
@@ -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
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Tool-approval prompt in the composer's place, presented like the ask
3
+ * dialog: the approval title and its details over an action bar. Approving
4
+ * takes a deliberate chord (`app.approval.approve`, Ctrl+Enter by default),
5
+ * so an Enter typed for the composer as the prompt appears never approves a
6
+ * call, and the chord itself only arms {@link APPROVE_ARM_MS} after mount: it
7
+ * is also the follow-up key, pressed mid-run exactly when prompts appear.
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.
13
+ */
14
+ import { type Component, type TUI } from "../index.js";
15
+ import type { NativeNode, NativeUiEvent } from "../native/node.js";
16
+ /** The approve key is ignored this long after mount, so a follow-up sent as the prompt appears cannot approve it. */
17
+ export declare const APPROVE_ARM_MS = 500;
18
+ interface ApprovalDialogCallbacks {
19
+ onApprove(): void;
20
+ onDeny(): void;
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
+ }
28
+ interface ApprovalDialogOptions {
29
+ tui?: TUI;
30
+ /** The prompt with every detail in full, which the expand key swaps in (wrapped) for the summarized one. */
31
+ fullPrompt?: string;
32
+ /** Deny once this many ms pass without input (any key restarts the countdown). */
33
+ timeout?: number;
34
+ /** Called when {@link timeout} expires, before the dialog denies. */
35
+ onTimeout?: () => void;
36
+ /** Called when the countdown starts, as the dialog mounts. */
37
+ onTimeoutStart?: () => void;
38
+ /** Called each time a key restarts the countdown. */
39
+ onTimeoutReset?: () => void;
40
+ /** Route typing to the composer instead of dropping it. */
41
+ draft?: ApprovalDialogDraft;
42
+ }
43
+ export declare class ApprovalDialogComponent implements Component {
44
+ #private;
45
+ private readonly callbacks;
46
+ private readonly options;
47
+ /** `prompt`: the approval title on its first line, the details after it. */
48
+ constructor(prompt: string, callbacks: ApprovalDialogCallbacks, options?: ApprovalDialogOptions);
49
+ invalidate(): void;
50
+ dispose(): void;
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;
58
+ render(width: number): readonly string[];
59
+ /**
60
+ * In the dock, in the composer's place and framed as the composer is (an
61
+ * `omp.editor.*` root role), like the ask dialog, rather than a card over
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.
64
+ */
65
+ describe(): NativeNode;
66
+ /** Deny, Approve, and Details run exactly what Esc, the approve key, and the expand key run. */
67
+ handleNativeEvent(event: NativeUiEvent): void;
68
+ }
69
+ export {};
@@ -86,6 +86,10 @@ export declare class PlanReviewOverlay implements Component {
86
86
  * reset scroll/focus so the operator starts at the top. Does not emit
87
87
  * `onPlanEdited` (the editor round-trip already persisted the file). */
88
88
  setPlanContent(planContent: string): void;
89
+ /** Synchronize an external tier pick with the displayed slider before committing. */
90
+ setSliderIndex(index: number): void;
91
+ /** Commit an external answer through the same input-locking path as Enter; false when nothing was committed. */
92
+ selectOption(label: string): boolean;
89
93
  handleInput(keyData: string): void;
90
94
  /** The native sheet: large glass, titled by the plan's own title heading (the body never repeats it). */
91
95
  get nativeOverlay(): NonNullable<Component["nativeOverlay"]>;
@@ -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`, `submit`,
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
- * Clicks on the composer's controls take the same paths as their keys: ⇧⇥,
248
- * ⏎ and Esc; the viewing header's links (`focus:<id>`) go to the host,
249
- * the status facts' clicks (`status.*`) to their source. Selection edits
250
- * go to the buffer; `send` submits its own prompt after saving the old draft
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, and the clear action reach the buffer.
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;
@@ -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,10 +20,28 @@ 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 name recorded for this tmux client.
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}`. The local IPC
19
- * query runs once per process and degrades to `null` when unavailable.
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.
29
+ */
30
+ export declare function resolveTmuxClientTerminalNames(env?: NodeJS.ProcessEnv): readonly string[];
31
+ /**
32
+ * Whether tmux draws SIXEL to the client attached to this session: its
33
+ * effective terminal description for that client carries `Sxl`.
34
+ *
35
+ * A tmux built with SIXEL answers every pane's DA1 with attribute 4 and draws a
36
+ * `SIXEL IMAGE (WxH)` text placeholder for clients without SIXEL, so the pane's
37
+ * DA1 says nothing about the outer terminal. tmux itself renders SIXEL only
38
+ * when the client's terminal has `Sxl`, whether from terminfo,
39
+ * `terminal-overrides`, or the `sixel` feature (DA1 or `terminal-features`),
40
+ * and `show-messages -T` reports that merged description. The TUI consults
41
+ * this before trusting the pane's DA1 attribute. Queried once per tmux
42
+ * session; `false` outside tmux, with no attached client, or when the query
43
+ * fails. Nested tmux usually reports no `Sxl`: the inner client's terminal is
44
+ * the outer tmux, whose DA1 starts with level 1, which tmux does not parse
45
+ * for features.
20
46
  */
21
- export declare function resolveTmuxClientTerminalName(env?: NodeJS.ProcessEnv): string | null;
47
+ export declare function tmuxClientRendersSixel(env?: NodeJS.ProcessEnv): boolean;
@@ -114,6 +114,13 @@ export declare function pendingFileLinkPath(inputPath: string): string;
114
114
  * image reads only record the path in `meta.source`). URL/internal sources are
115
115
  * not fs paths, so only `type: "path"` qualifies. */
116
116
  export declare function readSourceFsPath(details: ReadToolDetails | undefined): string | undefined;
117
+ /**
118
+ * Whether a read result renders as a Markdown preview rather than numbered source:
119
+ * the read tool tagged it `text/markdown` (`read.renderMarkdown`, or a protocol
120
+ * resource) and the call did not ask for `:raw` source. Shared by the standalone
121
+ * read card and the read group's content previews so both honor the setting.
122
+ */
123
+ export declare function readResultIsMarkdown(details: ReadToolDetails | undefined, args: ReadRenderArgs | undefined): boolean;
117
124
  /**
118
125
  * Read content as numbered `code` (no path header: the head names the file).
119
126
  * Numbers come from `lineNumbers` (`displayContent`'s per-line numbers,
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.10.3",
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.10.3",
45
- "@oh-my-pi/pi-agent-core": "18.10.3",
46
- "@oh-my-pi/pi-ai": "18.10.3",
47
- "@oh-my-pi/pi-catalog": "18.10.3",
48
- "@oh-my-pi/pi-natives": "18.10.3",
49
- "@oh-my-pi/pi-utils": "18.10.3",
50
- "@oh-my-pi/pi-wire": "18.10.3",
51
- "@oh-my-pi/snapcompact": "18.10.3"
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"
@@ -38,6 +38,7 @@ interface AppKeybindings {
38
38
  "app.tools.expand": true;
39
39
  "app.tools.toggleVisibility": true;
40
40
  "app.ask.hide": true;
41
+ "app.approval.approve": true;
41
42
  "app.editor.external": true;
42
43
  "app.message.followUp": true;
43
44
  "app.retry": true;
@@ -143,6 +144,12 @@ export const KEYBINDINGS = {
143
144
  defaultKeys: "alt+h",
144
145
  description: "Hide or show a pending ask dialog",
145
146
  },
147
+ "app.approval.approve": {
148
+ // A chord, not Enter: an Enter typed for the composer as the prompt appears must not approve.
149
+ // Ctrl+Q backs up Ctrl+Enter, which Windows Terminal cannot deliver (#1903).
150
+ defaultKeys: ["ctrl+enter", "ctrl+q"],
151
+ description: "Approve a pending tool call",
152
+ },
146
153
  "app.editor.external": {
147
154
  defaultKeys: "ctrl+g",
148
155
  description: "Open external editor",
@@ -245,7 +245,7 @@ export class RawSseViewerComponent implements Component {
245
245
  /** The action bar's buttons run the same code as their keys. */
246
246
  handleNativeEvent(event: NativeUiEvent): void {
247
247
  if (event.type !== "action") return;
248
- if (event.act === "copy") this.#copyAll();
248
+ if (event.act === "copy-raw") this.#copyAll();
249
249
  else if (event.act === "close") this.#close();
250
250
  }
251
251
 
@@ -305,7 +305,7 @@ export class RawSseViewerComponent implements Component {
305
305
  "status",
306
306
  ),
307
307
  actionBar([
308
- actionButton("Copy raw", "copy", { keys: "ctrl+c", tone: "accent" }),
308
+ actionButton("Copy raw", "copy-raw", { keys: "ctrl+c", tone: "accent" }),
309
309
  null,
310
310
  actionButton("Close", "close", { keys: "escape" }),
311
311
  ]),