@oh-my-pi/pi-tui 18.11.0 → 18.11.1

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 (53) hide show
  1. package/CHANGELOG.md +26 -37
  2. package/dist/types/autocomplete.d.ts +2 -0
  3. package/dist/types/components/image.d.ts +2 -0
  4. package/dist/types/components/markdown.d.ts +37 -0
  5. package/dist/types/native/blobs.d.ts +11 -11
  6. package/dist/types/overlays/job-tree.d.ts +50 -0
  7. package/dist/types/overlays/jobs-panel.d.ts +24 -11
  8. package/dist/types/prompt/image-references.d.ts +22 -0
  9. package/dist/types/render/hyperlink.d.ts +2 -0
  10. package/dist/types/render/utils.d.ts +1 -1
  11. package/dist/types/status-line/context-usage.d.ts +3 -0
  12. package/dist/types/status-line/startup.d.ts +6 -6
  13. package/dist/types/stdin-buffer.d.ts +7 -0
  14. package/dist/types/terminal.d.ts +7 -3
  15. package/dist/types/tools/eval.d.ts +5 -0
  16. package/dist/types/tools/grouped-file-output.d.ts +2 -0
  17. package/dist/types/tools/read.d.ts +5 -4
  18. package/dist/types/tui.d.ts +1 -0
  19. package/package.json +9 -9
  20. package/src/autocomplete.ts +14 -19
  21. package/src/bracketed-paste.ts +9 -2
  22. package/src/chat/assistant-message.ts +34 -9
  23. package/src/chat/bash-execution.ts +7 -2
  24. package/src/chat/image-loading.ts +4 -6
  25. package/src/chat/read-tool-group.ts +1 -0
  26. package/src/chat/tool-execution.ts +11 -4
  27. package/src/chat/user-message.ts +22 -15
  28. package/src/components/editor.ts +12 -12
  29. package/src/components/image.ts +23 -6
  30. package/src/components/markdown.ts +375 -6
  31. package/src/native/blobs.ts +22 -6
  32. package/src/native/reconcile.ts +19 -15
  33. package/src/overlays/job-tree.ts +177 -0
  34. package/src/overlays/jobs-panel.ts +107 -49
  35. package/src/prompt/composer.ts +22 -12
  36. package/src/prompt/image-references.ts +79 -2
  37. package/src/render/code-cell.ts +8 -1
  38. package/src/render/hyperlink.ts +5 -0
  39. package/src/render/utils.ts +1 -1
  40. package/src/render/width-aware-text.ts +6 -5
  41. package/src/status-line/component.ts +4 -0
  42. package/src/status-line/context-usage.ts +22 -16
  43. package/src/status-line/startup.ts +6 -6
  44. package/src/stdin-buffer.ts +11 -1
  45. package/src/terminal.ts +39 -18
  46. package/src/tools/ast-grep.ts +2 -2
  47. package/src/tools/edit.ts +16 -1
  48. package/src/tools/eval.ts +11 -4
  49. package/src/tools/grouped-file-output.ts +6 -1
  50. package/src/tools/read.ts +6 -5
  51. package/src/tools/write.ts +1 -1
  52. package/src/tui.ts +33 -23
  53. package/src/utils.ts +5 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.11.1] - 2026-10-15
6
+
7
+ ### Added
8
+
9
+ - Added a tree layout to the background jobs sheet: subagents' jobs nest under the task job that runs them, busiest branches first, and Cancel appears only on the viewing agent's own jobs ([#15854](https://github.com/can1357/oh-my-pi/pull/15854) by [@H4vC](https://github.com/H4vC))
10
+
11
+ ### Fixed
12
+
13
+ - Fixed transcript images not opening their file: image placeholders and native image nodes now link to the source file or a stored copy ([#15872](https://github.com/can1357/oh-my-pi/pull/15872) by [@H4vC](https://github.com/H4vC))
14
+ - A collapsing live block no longer leaves its slack as a band of blank rows between the retired history and the live rows: the slack falls below the transcript, so a streamed reply stays contiguous and the input keeps the bottom row ([#15499](https://github.com/can1357/oh-my-pi/pull/15499) by [@iacore](https://github.com/iacore))
15
+ - Fixed width-aware text on native (TSP) terminals truncating content two columns early ([#15806](https://github.com/can1357/oh-my-pi/pull/15806) by [@yuzu-octopus](https://github.com/yuzu-octopus))
16
+ - Fixed Tab/right-arrow completion silently dismissing a selected `@"quoted path"` file suggestion when the path contains spaces ([#15698](https://github.com/can1357/oh-my-pi/pull/15698) by [@drawde-nadroj](https://github.com/drawde-nadroj))
17
+ - Bracketed pastes are now capped by their UTF-8 size, so a large multibyte paste no longer buffers past the byte limit ([#15803](https://github.com/can1357/oh-my-pi/pull/15803) by [@yuzu-octopus](https://github.com/yuzu-octopus))
18
+ - Fixed OSC 66 text-sized spans bypassing truncation when their UTF-16 length was short enough to fit the fast-path guard ([#15802](https://github.com/can1357/oh-my-pi/pull/15802) by [@yuzu-octopus](https://github.com/yuzu-octopus))
19
+ - Updated the `ast_grep` result view's truncated-match note to suggest `skip` pagination instead of the schema's nonexistent `increase limit` flag ([#15532](https://github.com/can1357/oh-my-pi/issues/15532), [#15543](https://github.com/can1357/oh-my-pi/pull/15543) by [@peda-cos](https://github.com/peda-cos)).
20
+ - Fixed a streaming Edit preview with long wrapped lines growing past its row cap, because its tail was budgeted without the diff gutter its continuation rows hang under ([#15679](https://github.com/can1357/oh-my-pi/pull/15679))
21
+ - Fixed malformed base64 padding being accepted for chat image attachments before native image decoding ([#15765](https://github.com/can1357/oh-my-pi/pull/15765) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
22
+ - Fixed Shift+Enter submitting and Ctrl+Enter inserting a newline in Windows Terminal 1.25–1.27 after closing any fullscreen view such as `/settings`; the terminal dropped omp's keyboard mode on leaving the alternate screen ([microsoft/terminal#20763](https://github.com/microsoft/terminal/issues/20763)), so omp now re-applies it ([#15868](https://github.com/can1357/oh-my-pi/pull/15868) by [@H4vC](https://github.com/H4vC)).
23
+ - Long lines in native (Tern) `read`, `write`, and code-cell source now wrap instead of scrolling each block sideways ([#15823](https://github.com/can1357/oh-my-pi/issues/15823)).
24
+ - Fixed long text pastes taking seconds to land in terminals that deliver a paste one key at a time, such as JetBrains' terminal on Windows; omp no longer writes to the terminal for every pasted key ([#15757](https://github.com/can1357/oh-my-pi/issues/15757))
25
+ - Fixed Shift+Enter submitting the prompt and Ctrl+Enter inserting a newline in WSL under Windows Terminal 1.24 and earlier, conhost, and other ConPTY hosts without kitty keyboard support; Shift+Enter now inserts a newline and Ctrl+Enter sends a follow-up, as on native Windows.
26
+ - Fixed streamed Markdown tables jumping around as each row arrives: the terminal UI fills the row being written in under the existing column widths, and native (TSP) terminals receive each row once its line is complete ([#15856](https://github.com/can1357/oh-my-pi/pull/15856) by [@H4vC](https://github.com/H4vC)).
27
+ - Fixed streamed links and inline math showing raw `[text](url` or LaTeX and then rewrapping the paragraph when they close, in the terminal UI and native (TSP) terminals: they now render in their closed form while they stream ([#15864](https://github.com/can1357/oh-my-pi/pull/15864) by [@H4vC](https://github.com/H4vC)).
28
+ - Paint-only overlays no longer swallow typing intended for the focused input beneath them ([#15793](https://github.com/can1357/oh-my-pi/issues/15793)).
29
+
5
30
  ## [18.11.0] - 2026-10-14
6
31
 
7
32
  ### Added
@@ -2643,40 +2668,4 @@
2643
2668
  - Added `cursorOverride` and `cursorOverrideWidth` properties to customize the end-of-text cursor glyph with ANSI-styled strings
2644
2669
  - Added `getUseTerminalCursor()` method to query the terminal cursor mode setting
2645
2670
 
2646
- ## [11.10.0] - 2026-02-10
2647
-
2648
- ### Added
2649
-
2650
- - Added `hint` property to autocomplete items to display dim ghost text after cursor when item is selected
2651
- - Added `getInlineHint()` method to `SlashCommand` interface for providing inline hint text based on argument state
2652
- - Added `getInlineHint()` method to `AutocompleteProvider` interface for displaying dim ghost text after cursor
2653
- - Added `hintStyle` theme option to customize styling of inline hint/ghost text in editor
2654
-
2655
- ### Changed
2656
-
2657
- - Updated editor to render inline hint text as dim ghost text after cursor when autocomplete suggestions are active or provider supplies hints
2658
-
2659
- ## [11.8.0] - 2026-02-10
2660
-
2661
- ### Added
2662
-
2663
- - Added Alt+Y keybinding to cycle through kill ring entries (yank-pop)
2664
- - Added undo support to Input component with Ctrl+Z keybinding
2665
- - Added kill ring support to Input component for Emacs-style kill/yank operations
2666
- - Added yank (Ctrl+Y) and yank-pop (Alt+Y) support to Input component
2667
-
2668
- ### Changed
2669
-
2670
- - Changed Editor kill ring implementation to use dedicated KillRing class for better state management
2671
- - Changed Editor undo stack to use generic UndoStack class with automatic state cloning
2672
- - Changed kill/yank behavior to properly accumulate consecutive kill operations
2673
- - Changed Input component deletion methods to record killed text in kill ring
2674
- - Changed undo coalescing in Input component to group consecutive word typing into single undo units
2675
-
2676
- ## [11.4.1] - 2026-02-06
2677
-
2678
- ### Fixed
2679
-
2680
- - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
2681
-
2682
- Older entries are archived in [packages/tui/CHANGELOG.md@a66558815743](https://github.com/can1357/oh-my-pi/blob/a66558815743a61164ceebceb2bb528989fb09b3/packages/tui/CHANGELOG.md).
2671
+ Older entries are archived in [packages/tui/CHANGELOG.md@090e359aa972](https://github.com/can1357/oh-my-pi/blob/090e359aa97267de88bd365054685c8234843cd7/packages/tui/CHANGELOG.md).
@@ -6,6 +6,8 @@
6
6
  */
7
7
  export declare function findLeadingSlashCommandStart(text: string): number | null;
8
8
  export declare function findTrailingSlashCommandStart(text: string): number | null;
9
+ /** Extract the live whitespace-delimited or double-quoted `@` file token ending at the cursor. */
10
+ export declare function extractAtFilePrefix(text: string): string | null;
9
11
  /**
10
12
  * Whether an autocomplete value represents a directory: trailing slash or
11
13
  * backslash, optionally followed by a closing quote for quoted paths.
@@ -8,6 +8,8 @@ export interface ImageOptions {
8
8
  maxWidthCells?: number;
9
9
  maxHeightCells?: number;
10
10
  filename?: string;
11
+ /** File the image was read from; its link target. Images without one link to a blob copy of their bytes. */
12
+ sourcePath?: string;
11
13
  /** Shared budget that caps how many inline images render as live graphics. */
12
14
  budget?: ImageBudget;
13
15
  /**
@@ -10,6 +10,43 @@ import type { Component } from "../tui.js";
10
10
  * byte-identical to rendering `text`.
11
11
  */
12
12
  export declare function streamingMarkdownSource(text: string): string;
13
+ /**
14
+ * Streaming `md` source for a native terminal, minus a still-growing table row:
15
+ * the unterminated last line when a GFM table (a run of pipe lines holding a
16
+ * header and its delimiter row, outer pipes optional) sits right above it,
17
+ * outside any open code fence. GFM continues a table on any non-blank line, so
18
+ * that line is a row whatever it holds so far. The terminal sizes columns by
19
+ * every row it gets, so a half-streamed row (an unclosed link shows its whole
20
+ * URL) would rewrap the table each frame, and TSP cannot ask it to skip one.
21
+ * The result keeps the newline before the held line, so it is a prefix of
22
+ * `text` and of the result for every append to it: streaming stays
23
+ * append-only. Pass untrimmed source: trimming first drops the newline that
24
+ * marks the last row finished, so that row would be held too.
25
+ */
26
+ export declare function holdPartialTableRow(text: string): string;
27
+ /**
28
+ * `text`, one inline run whose end is still streaming, with a trailing open
29
+ * link or math span closed the way the finished text most likely closes it,
30
+ * or `undefined` when nothing at its end is open. A link gains its missing
31
+ * `)` (one per open parenthesis in its destination), so it renders as the
32
+ * link it becomes instead of literal brackets and URL. A math body is cut back
33
+ * to its last valid form (a half-typed command, dangling `^`/`_`, or the first
34
+ * `$` of a `$$` closer dropped, open braces closed) and closed, so the
35
+ * rendered math grows in place instead of raw LaTeX turning into symbols at
36
+ * the closer. A `$` body needs a LaTeX character, so a price (`$5 per`) is
37
+ * never speculated. Only prose is: escaped openers and anything inside a code
38
+ * span, closed or still being written, stay literal.
39
+ */
40
+ export declare function speculativeInlineClose(text: string): string | undefined;
41
+ /**
42
+ * Streaming `md` source for a native terminal with the open link or math span
43
+ * at its end speculatively closed ({@link speculativeInlineClose}), as the TUI
44
+ * renders it. Only the last block is read (a span cannot cross a blank line),
45
+ * and source inside an open code fence is left as it is. Each frame rewrites
46
+ * only the guessed closers at the end, which the reconciler sends as a small
47
+ * `splice`.
48
+ */
49
+ export declare function speculativeStreamingSource(text: string): string;
13
50
  /** @internal exported for tests — the grown-line block-kind gate. */
14
51
  export declare function fastLineStartHazard(grownLine: string): boolean;
15
52
  /**
@@ -1,12 +1,3 @@
1
- /**
2
- * Content-addressed binary blobs (images) for `image` nodes.
3
- *
4
- * `describe()` registers the bytes and puts the returned id in `image.p.blob`;
5
- * the native backend delivers each referenced blob once per terminal
6
- * connection (asking the terminal first which it holds, or through its blob
7
- * cache), else uploads it with verb `b` before the frame that first
8
- * references it.
9
- */
10
1
  import type { TspProps } from "@oh-my-pi/pi-wire";
11
2
  import type { NativeChild, NativeNode } from "./node.js";
12
3
  export interface NativeBlob {
@@ -26,10 +17,19 @@ export declare function nativeImageNode(bytes: Uint8Array, mime: string, p?: Omi
26
17
  * so callers cache the node per payload ({@link NativeImageCache}).
27
18
  */
28
19
  export declare function base64ImageNode(data: string, mimeType: string, p?: Omit<TspProps<"image">, "blob" | "builtin" | "w" | "h">, key?: string): NativeNode;
29
- /** `image` nodes for base64 payloads, one per slot key, rebuilt only when the slot's payload changes. */
20
+ /**
21
+ * Props that make an `image` node open the file at `filePath` (as a `file://` URL, the TSP
22
+ * open-target form): a click zooms the image in the terminal, and the file opens from its
23
+ * context menu. Empty without a file.
24
+ */
25
+ export declare function openableImageProps(filePath: string | undefined): Pick<TspProps<"image">, "href" | "actions">;
26
+ /**
27
+ * `image` nodes for base64 payloads, one per slot key, rebuilt only when the slot's payload
28
+ * or the link policy changes. Each opens its file (`sourcePath`, else a blob copy).
29
+ */
30
30
  export declare class NativeImageCache {
31
31
  #private;
32
- get(key: string, data: string, mimeType: string): NativeNode;
32
+ get(key: string, data: string, mimeType: string, sourcePath?: string): NativeNode;
33
33
  }
34
34
  /** The disk file a tool result's image was read from (`read` details `mediaPath`), when it has one. */
35
35
  export declare function readMediaPath(details: unknown): string | undefined;
@@ -0,0 +1,50 @@
1
+ import type { Theme } from "../theme/theme.js";
2
+ /** What the tree reads off a job. */
3
+ export interface JobTreeJob {
4
+ readonly id: string;
5
+ readonly status: "running" | "completed" | "failed" | "cancelled";
6
+ readonly startTime: number;
7
+ /** Registry id of the agent that owns the job; absent or the root id: a top-level job. */
8
+ readonly ownerId?: string;
9
+ /** Registry id of the subagent a task job runs: the job heads that agent's subtree. */
10
+ readonly agentId?: string;
11
+ }
12
+ /** A subagent in the tree, by registry id: an owner of listed jobs, or an ancestor linking one to the root. */
13
+ export interface JobTreeAgent {
14
+ readonly id: string;
15
+ readonly parentId?: string;
16
+ }
17
+ /** Where a row sits: its depth and the rails drawn left of it. */
18
+ export interface JobTreePlace {
19
+ /** 0 for the top level. */
20
+ readonly depth: number;
21
+ /** Per enclosing level below the top, whether that level's rail continues past this row. */
22
+ readonly rails: readonly boolean[];
23
+ /** Whether this row is the last of its siblings. */
24
+ readonly last: boolean;
25
+ /** Whether rows nest under this one. */
26
+ readonly parent: boolean;
27
+ }
28
+ /** One tree row: a job (a task job also heads its subagent), or a header for a subagent with no listed task job. */
29
+ export type JobTreeRow<J extends JobTreeJob> = (JobTreePlace & {
30
+ readonly kind: "job";
31
+ readonly job: J;
32
+ readonly agent?: JobTreeAgent;
33
+ }) | (JobTreePlace & {
34
+ readonly kind: "agent";
35
+ readonly agent: JobTreeAgent;
36
+ });
37
+ /**
38
+ * `jobs` as tree rows in display order. `rootId` is the viewing agent: its
39
+ * jobs, ownerless jobs and jobs of owners outside `agents` sit at the top
40
+ * level, as do agents whose parent is the root or unknown.
41
+ */
42
+ export declare function buildJobTree<J extends JobTreeJob>(jobs: readonly J[], agents: readonly JobTreeAgent[], rootId: string | undefined): JobTreeRow<J>[];
43
+ /** The connector drawn before a row's head: nothing at the top level, else rails then a branch. */
44
+ export declare function jobTreeGuide(row: JobTreePlace, theme: Theme): string;
45
+ /**
46
+ * The prefix of lines under a row's head (its label, a wrapped command): the
47
+ * rails that continue past it, then the rail down to its children or blank,
48
+ * two columns wide so a top-level leaf's lines indent by two.
49
+ */
50
+ export declare function jobTreeIndent(row: JobTreePlace, theme: Theme): string;
@@ -1,5 +1,6 @@
1
1
  import type { Component } from "../tui.js";
2
2
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
3
+ import { type JobTreeAgent } from "./job-tree.js";
3
4
  /** One async job as the session snapshots it. */
4
5
  export interface JobsPanelJob {
5
6
  readonly id: string;
@@ -8,13 +9,19 @@ export interface JobsPanelJob {
8
9
  readonly label: string;
9
10
  readonly startTime: number;
10
11
  readonly endTime?: number;
12
+ /** Registry id of the agent that owns the job. */
13
+ readonly ownerId?: string;
11
14
  /** Registry id of the subagent a task job runs. */
12
15
  readonly agentId?: string;
13
16
  }
14
- /** Running and recently settled jobs. */
17
+ /** Running and recently settled jobs of an agent and its subagents. */
15
18
  export interface JobsPanelSnapshot {
16
19
  readonly running: readonly JobsPanelJob[];
17
20
  readonly recent: readonly JobsPanelJob[];
21
+ /** The agent the sheet is for: its jobs sit at the top level, and only they can be cancelled. */
22
+ readonly rootId?: string;
23
+ /** Subagents owning listed jobs, with the ancestors linking them to the root. */
24
+ readonly agents?: readonly JobTreeAgent[];
18
25
  }
19
26
  /** What the sheet shows about the selected job beyond its row. */
20
27
  export interface JobsPanelDetail {
@@ -35,20 +42,26 @@ export interface JobsSheetSource {
35
42
  load(): JobsPanelSnapshot;
36
43
  /** The selected job's detail; runs once per frame, undefined once the job is gone. */
37
44
  inspect(id: string): JobsPanelDetail | undefined;
38
- /** Cancel a running job. */
39
- cancel(id: string): void;
45
+ /**
46
+ * Cancel a running job. Omitted by a read-only sheet (a focused subagent
47
+ * view, which only watches that agent): it lists the jobs and offers no way
48
+ * to stop them.
49
+ */
50
+ cancel?(id: string): void;
40
51
  /** Dismiss the sheet. */
41
52
  close(): void;
42
53
  }
43
54
  /**
44
55
  * The jobs pill's sheet: a centred `lg` glass sheet titled Background jobs.
45
- * A master–detail split (it stacks in a narrow pane): the selectable job list,
46
- * grouped Running / Recent, beside the selected job's detail — a status pill
47
- * with live elapsed and Cancel job, its command or task, facts (agent, cwd,
48
- * live pids, exit code, artifact) and a tail-following output pane. With no
49
- * jobs it shows an empty state. It re-reads the session every frame, so the
50
- * caller re-renders it on a timer while it is open. ↑/↓ select, X cancels the
51
- * selected running job, Esc or Close dismisses; nothing enters the transcript.
56
+ * A master–detail split (it stacks in a narrow pane): the selectable job tree
57
+ * beside the selected job's detail — a status pill with live elapsed and
58
+ * Cancel job, its command or task, facts (agent, cwd, live pids, exit code,
59
+ * artifact) and a tail-following output pane. With no jobs it shows an empty
60
+ * state. It re-reads the session every frame, so the caller re-renders it on
61
+ * a timer while it is open. ↑/↓ select, X cancels the selected running job,
62
+ * Esc or Close dismisses; nothing enters the transcript. A source without
63
+ * `cancel` renders read-only, and a subagent's job never offers Cancel: only
64
+ * the root's own jobs can be stopped here.
52
65
  */
53
66
  export declare class JobsSheet implements Component {
54
67
  #private;
@@ -60,7 +73,7 @@ export declare class JobsSheet implements Component {
60
73
  };
61
74
  constructor(source: JobsSheetSource);
62
75
  describe(_cx: DescribeContext): NativeNode;
63
- /** Rows for a terminal without the native surface: one per job, then the selected job's facts and output tail. */
76
+ /** Rows for a terminal without the native surface: the job tree, then the selected job's facts and output tail. */
64
77
  render(width: number): readonly string[];
65
78
  invalidate(): void;
66
79
  handleInput(data: string): void;
@@ -1,4 +1,5 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+ import type { TspSpan } from "@oh-my-pi/pi-wire";
2
3
  /** Materialized image destination returned by the host blob writer. */
3
4
  interface ImageBlobResult {
4
5
  displayPath: string;
@@ -23,4 +24,25 @@ type ImageBlobWriterSync = (data: Buffer, options?: {
23
24
  export declare function imageReferenceHyperlink(label: string, index: number, imageLinks: readonly (string | undefined)[] | undefined, renderLabel: (text: string) => string): string;
24
25
  export declare function materializeImageReferenceLinks(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriter): Promise<(string | undefined)[] | undefined>;
25
26
  export declare function materializeImageReferenceLinksSync(images: readonly ImageContent[] | undefined, putBlob: ImageBlobWriterSync): (string | undefined)[] | undefined;
27
+ /**
28
+ * Register the host's blob writer that backs links for images without a file of their
29
+ * own. Returns a disposer that unregisters it unless another writer replaced it since.
30
+ */
31
+ export declare function setImageBlobWriter(next: ImageBlobWriterSync): () => void;
32
+ /**
33
+ * Whether transcript images carry links under the current `tui.hyperlinks` policy: a
34
+ * native surface honours only an explicit `off`, text mode the resolved OSC 8 support.
35
+ * Renderers caching a linked image key their cache on it.
36
+ */
37
+ export declare function imageLinksEnabled(): boolean;
38
+ /**
39
+ * The file a transcript image opens: `sourcePath` when it was read from one, else a
40
+ * blob copy of its bytes written through the host. Undefined when links are disabled
41
+ * or no file exists, so a disabled policy never writes blobs.
42
+ */
43
+ export declare function transcriptImageLink(image: Pick<ImageContent, "data" | "mimeType">, sourcePath?: string): string | undefined;
44
+ /** `text` (an image's placeholder) as an OSC 8 link to the image's file; plain when hyperlinks are off or no file exists. */
45
+ export declare function linkedImageText(text: string, image: Pick<ImageContent, "data" | "mimeType">, sourcePath?: string): string;
46
+ /** Native counterpart of {@link linkedImageText}: a span linking to the image's file when one exists. */
47
+ export declare function linkedImageSpan(text: string, image: Pick<ImageContent, "data" | "mimeType">, sourcePath: string | undefined, s?: string): TspSpan;
26
48
  export {};
@@ -70,6 +70,8 @@ export declare function urlHyperlinkAlways(url: string, displayText: string): st
70
70
  * Non-HTTP(S) or unparsable URLs yield a plain span.
71
71
  */
72
72
  export declare function urlLinkSpan(target: string, displayText: string, s?: string): TspSpan;
73
+ /** Whether native link spans and nodes may carry an `href`: everything but an explicit `off` policy. */
74
+ export declare function isNativeLinkEnabled(): boolean;
73
75
  /**
74
76
  * A native link span to a filesystem path as a plain `file://` URI (the
75
77
  * terminal opens it through the system; the location stays in the visible
@@ -32,7 +32,7 @@ export interface RenderCache {
32
32
  lines: string[];
33
33
  }
34
34
  /** Build indentation and continuing branches for ancestor levels. */
35
- export declare function buildTreePrefix(ancestors: boolean[], theme: Theme): string;
35
+ export declare function buildTreePrefix(ancestors: readonly boolean[], theme: Theme): string;
36
36
  /** Return the branch glyph for a final or continuing tree item. */
37
37
  export declare function getTreeBranch(isLast: boolean, theme: Theme): string;
38
38
  /** Return the continuation prefix for subsequent lines of a tree item. */
@@ -98,6 +98,7 @@ export interface CompactionBoundaries {
98
98
  export declare function computeCompactionBoundaries(compactionSettings: CompactionSettings, contextWindow: number, speculationLeadTokens?: number): CompactionBoundaries | null;
99
99
  /** Stable inputs used to cache non-message token estimates. */
100
100
  export interface NonMessageTokenSource {
101
+ readonly model?: Model;
101
102
  readonly systemPrompt?: readonly string[];
102
103
  readonly agent?: {
103
104
  readonly state?: {
@@ -137,6 +138,8 @@ export declare function invalidateToolSchemaMetadata(tools: ToolSchemaSource): v
137
138
  * {@link invalidateToolSchemaMetadata} when those inputs change.
138
139
  */
139
140
  export declare function estimateToolSchemaTokens(tools: ToolSchemaSource, tokenizer: Tokenizer, sourceRevision?: number): number;
141
+ /** Count schemas carried on every request, excluding server-side deferred catalogs. */
142
+ export declare function estimateContextToolSchemaTokens(tools: ToolSchemaSource, tokenizer: Tokenizer, model: Model | undefined, sourceRevision?: number): number;
140
143
  export declare function computeNonMessageTokens(session: NonMessageTokenSource, tokenizer: Tokenizer, sourceRevision?: number): number;
141
144
  /**
142
145
  * Shared helper for the four non-message token totals used by
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Status line for the speculative first frame, before any session exists.
3
3
  *
4
- * InteractiveMode persists {@link StatusLineStartupData} (settings plus the last
5
- * session's model and thinking state); the next launch renders it through the
6
- * real {@link StatusLineComponent} as a fresh, unnamed session at the live
7
- * terminal width. Path and git branch are read live; context usage is reported
8
- * unknown so the gauge shows the window without a percent.
4
+ * InteractiveMode persists {@link StatusLineStartupData} (settings plus the
5
+ * resolved default role model and status inputs); the next launch renders it
6
+ * through the real {@link StatusLineComponent} as a fresh, unnamed session at
7
+ * the live terminal width. Path and git branch are read live; context usage is
8
+ * reported unknown so the gauge shows the window without a percent.
9
9
  *
10
10
  * @example
11
11
  * ```ts ignore
@@ -22,7 +22,7 @@ import type { StatusLineSettings } from "./types.js";
22
22
  export interface StatusLineStartupData {
23
23
  readonly settings: StatusLineSettings;
24
24
  readonly gitEnabled: boolean;
25
- /** Model of the last session; the next fresh session usually starts on it too. */
25
+ /** Model resolved for the next fresh session's default role. */
26
26
  readonly model?: Model;
27
27
  readonly thinkingLevel?: ThinkingLevel;
28
28
  readonly autoThinking: boolean;
@@ -61,6 +61,13 @@ export type StdinBufferEventMap = {
61
61
  export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
62
62
  #private;
63
63
  constructor(options?: StdinBufferOptions);
64
+ /**
65
+ * Whether a bracketed paste has started and its end marker has not arrived.
66
+ * Terminal skips its per-read DEC 2004 reassertion meanwhile: the host is
67
+ * evidently in bracketed-paste mode, and a paste delivered one key record
68
+ * per read would otherwise cost one terminal write per pasted key (#15757).
69
+ */
70
+ get pasting(): boolean;
64
71
  process(data: string | Buffer): void;
65
72
  /**
66
73
  * Gate unbracketed raw-paste classification on event-loop responsiveness.
@@ -181,6 +181,7 @@ export interface Terminal {
181
181
  get kittyEnableSequence(): string | null;
182
182
  readonly keyboardEnhancementEnterSequence?: string | null;
183
183
  readonly keyboardEnhancementExitSequence?: string | null;
184
+ readonly keyboardEnhancementRestoreSequence?: string | null;
184
185
  moveBy(lines: number): void;
185
186
  hideCursor(force?: boolean): void;
186
187
  showCursor(force?: boolean): void;
@@ -283,9 +284,11 @@ export interface ProcessTerminalOptions {
283
284
  conpty?: boolean;
284
285
  /**
285
286
  * Whether stdin is a native Windows console handle (win32, not WSL), whose
286
- * console host answers `CSI ? 9001 h` with win32-input-mode key records.
287
- * Defaults to `process.platform === "win32"`; only consulted when `conpty`
288
- * is also true.
287
+ * console host is certain to answer `CSI ? 9001 h` with win32-input-mode key
288
+ * records. When false (WSL), the keyboard fallback requests modifyOtherKeys
289
+ * alongside win32-input-mode, since a WSL pty may not be relayed by a console
290
+ * host. Defaults to `process.platform === "win32"`; only consulted when
291
+ * `conpty` is also true.
289
292
  */
290
293
  nativeWindowsConsole?: boolean;
291
294
  }
@@ -298,6 +301,7 @@ export declare class ProcessTerminal implements Terminal {
298
301
  get kittyProtocolActive(): boolean;
299
302
  get kittyEnableSequence(): string | null;
300
303
  get keyboardEnhancementEnterSequence(): string | null;
304
+ get keyboardEnhancementRestoreSequence(): string | null;
301
305
  get keyboardEnhancementExitSequence(): string | null;
302
306
  get appearance(): TerminalAppearance | undefined;
303
307
  onAppearanceChange(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): void;
@@ -20,6 +20,11 @@ export interface EvalCellResult {
20
20
  code: string;
21
21
  language?: EvalLanguage;
22
22
  output: string;
23
+ /**
24
+ * `output` without its `display[N]:` JSON sections, which renderers show
25
+ * from `jsonOutputs` instead. Absent when the cell displayed no JSON.
26
+ */
27
+ textOutput?: string;
23
28
  status: "pending" | "running" | "complete" | "error";
24
29
  durationMs?: number;
25
30
  exitCode?: number;
@@ -111,9 +111,11 @@ export interface NumberedLine {
111
111
  * of consecutive line numbers (`start` = the run's first line, match lines
112
112
  * `mark`ed), with a `…` row wherever the numbers jump or an elision marker
113
113
  * (`n: null`) sits. No path header: the row or head above names the file.
114
+ * `wrap` soft-wraps long lines instead of scrolling the block sideways.
114
115
  */
115
116
  export declare function numberedCode(lines: readonly NumberedLine[], opts?: {
116
117
  lang?: string;
118
+ wrap?: boolean;
117
119
  }): NativeNode[];
118
120
  /**
119
121
  * Describe grouped search output (grep / ast-grep / ast-edit display content)
@@ -122,10 +122,11 @@ export declare function readSourceFsPath(details: ReadToolDetails | undefined):
122
122
  */
123
123
  export declare function readResultIsMarkdown(details: ReadToolDetails | undefined, args: ReadRenderArgs | undefined): boolean;
124
124
  /**
125
- * Read content as numbered `code` (no path header: the head names the file).
126
- * Numbers come from `lineNumbers` (`displayContent`'s per-line numbers,
127
- * `null` for an elided block; runs split with `…` between) when they cover
128
- * every line, else count up from `start`.
125
+ * Read content as numbered, wrapped `code` (no path header: the head names
126
+ * the file), matching the ANSI preview's hanging wrap. Numbers come from
127
+ * `lineNumbers` (`displayContent`'s per-line numbers, `null` for an elided
128
+ * block; runs split with `…` between) when they cover every line, else count
129
+ * up from `start`.
129
130
  */
130
131
  export declare function readContentCode(content: string, start: number, numbers: readonly (number | null)[] | undefined, lang: string | undefined): NativeNode[];
131
132
  /** Render file, image, and URL reads in the transcript. */
@@ -348,6 +348,7 @@ export declare class TUI extends Container {
348
348
  overlayStack: {
349
349
  component: Component;
350
350
  options?: OverlayOptions;
351
+ /** Focus restored after closing; tracks live focus changes beneath an input-less key holder. */
351
352
  preFocus: Component | null;
352
353
  hidden: boolean;
353
354
  /**
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.11.0",
4
+ "version": "18.11.1",
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.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"
44
+ "@oh-my-pi/omptype": "18.11.1",
45
+ "@oh-my-pi/pi-agent-core": "18.11.1",
46
+ "@oh-my-pi/pi-ai": "18.11.1",
47
+ "@oh-my-pi/pi-catalog": "18.11.1",
48
+ "@oh-my-pi/pi-natives": "18.11.1",
49
+ "@oh-my-pi/pi-utils": "18.11.1",
50
+ "@oh-my-pi/pi-wire": "18.11.1",
51
+ "@oh-my-pi/snapcompact": "18.11.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -106,6 +106,18 @@ function extractQuotedPrefix(text: string): string | null {
106
106
  return text.slice(quoteStart);
107
107
  }
108
108
 
109
+ /** Extract the live whitespace-delimited or double-quoted `@` file token ending at the cursor. */
110
+ export function extractAtFilePrefix(text: string): string | null {
111
+ const quotedPrefix = extractQuotedPrefix(text);
112
+ if (quotedPrefix?.startsWith('@"')) {
113
+ return quotedPrefix;
114
+ }
115
+
116
+ const lastDelimiterIndex = findLastDelimiter(text);
117
+ const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
118
+ return text[tokenStart] === "@" ? text.slice(tokenStart) : null;
119
+ }
120
+
109
121
  function parsePathPrefix(prefix: string): { rawPrefix: string; isAtPrefix: boolean; isQuotedPrefix: boolean } {
110
122
  if (prefix.startsWith('@"')) {
111
123
  return { rawPrefix: prefix.slice(2), isAtPrefix: true, isQuotedPrefix: true };
@@ -719,7 +731,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
719
731
  }
720
732
 
721
733
  // Check for @ file reference (fuzzy search) - must be after a delimiter or at start
722
- const atPrefix = this.#extractAtPrefix(textBeforeCursor);
734
+ const atPrefix = extractAtFilePrefix(textBeforeCursor);
723
735
  if (atPrefix) {
724
736
  const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
725
737
  // Recursive fuzzy walks rooted outside the project (e.g. `@../`,
@@ -862,7 +874,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
862
874
 
863
875
  // Check if we're completing a file attachment (prefix starts with "@")
864
876
  if (prefix.startsWith("@")) {
865
- const liveAtPrefix = this.#extractAtPrefix(textBeforeCursor);
877
+ const liveAtPrefix = extractAtFilePrefix(textBeforeCursor);
866
878
  if (liveAtPrefix) {
867
879
  beforePrefix = currentLine.slice(0, cursorCol - liveAtPrefix.length);
868
880
  }
@@ -896,23 +908,6 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
896
908
  };
897
909
  }
898
910
 
899
- // Extract @ prefix for fuzzy file suggestions
900
- #extractAtPrefix(text: string): string | null {
901
- const quotedPrefix = extractQuotedPrefix(text);
902
- if (quotedPrefix?.startsWith('@"')) {
903
- return quotedPrefix;
904
- }
905
-
906
- const lastDelimiterIndex = findLastDelimiter(text);
907
- const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
908
-
909
- if (text[tokenStart] === "@") {
910
- return text.slice(tokenStart);
911
- }
912
-
913
- return null;
914
- }
915
-
916
911
  // Extract a path-like prefix from the text before cursor
917
912
  #extractPathPrefix(text: string, forceExtract: boolean = false): string | null {
918
913
  const quotedPrefix = extractQuotedPrefix(text);
@@ -68,6 +68,7 @@ const DEFAULT_BYTE_LIMIT = 64 * 1024 * 1024;
68
68
  */
69
69
  export class BracketedPasteHandler {
70
70
  #buffer = "";
71
+ #bufferBytes = 0;
71
72
  #active = false;
72
73
  readonly #byteLimit: number;
73
74
 
@@ -88,7 +89,10 @@ export class BracketedPasteHandler {
88
89
  const start = data.indexOf(PASTE_START);
89
90
  const end = data.indexOf(PASTE_END);
90
91
  if (start !== -1 && (end === -1 || start < end)) {
91
- if (!this.#active) this.#buffer = "";
92
+ if (!this.#active) {
93
+ this.#buffer = "";
94
+ this.#bufferBytes = 0;
95
+ }
92
96
  this.#active = true;
93
97
  // Only strip nested starts before this paste's first end. Later starts
94
98
  // belong to the next paste in `remaining`.
@@ -100,6 +104,7 @@ export class BracketedPasteHandler {
100
104
 
101
105
  if (!this.#active) return { handled: false };
102
106
 
107
+ this.#bufferBytes += Buffer.byteLength(data, "utf8");
103
108
  this.#buffer += data;
104
109
 
105
110
  const endIndex = this.#buffer.indexOf(PASTE_END);
@@ -108,6 +113,7 @@ export class BracketedPasteHandler {
108
113
  const remaining = this.#buffer.substring(endIndex + PASTE_END.length);
109
114
 
110
115
  this.#buffer = "";
116
+ this.#bufferBytes = 0;
111
117
  this.#active = false;
112
118
 
113
119
  return { handled: true, pasteContent, remaining };
@@ -118,9 +124,10 @@ export class BracketedPasteHandler {
118
124
  // neither lost nor held forever, and reset paste mode so subsequent
119
125
  // input recovers. See `StdinBuffer#abortPaste` for the sibling recovery
120
126
  // semantics inside `StdinBuffer`.
121
- if (this.#buffer.length > this.#byteLimit) {
127
+ if (this.#bufferBytes > this.#byteLimit) {
122
128
  const pasteContent = this.#buffer;
123
129
  this.#buffer = "";
130
+ this.#bufferBytes = 0;
124
131
  this.#active = false;
125
132
  return { handled: true, pasteContent, remaining: "" };
126
133
  }