@oh-my-pi/pi-tui 18.10.1 → 18.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +29 -50
  2. package/THIRD-PARTY-NOTICES.txt +3 -3
  3. package/dist/types/app-keybindings.d.ts +8 -8
  4. package/dist/types/apps/git/diff-pane.d.ts +5 -0
  5. package/dist/types/apps/git/state.d.ts +11 -0
  6. package/dist/types/chat/assistant-message.d.ts +8 -3
  7. package/dist/types/chat/tool-execution.d.ts +32 -20
  8. package/dist/types/native/backend.d.ts +5 -4
  9. package/dist/types/native/blobs.d.ts +13 -1
  10. package/dist/types/overlays/model-browser.d.ts +6 -1
  11. package/dist/types/thinking.d.ts +5 -0
  12. package/dist/types/tools/bash.d.ts +6 -1
  13. package/dist/types/tools/eval.d.ts +2 -2
  14. package/dist/types/tools/native-view.d.ts +1 -1
  15. package/dist/types/tools/read.d.ts +7 -1
  16. package/dist/types/tools/renderer.d.ts +2 -0
  17. package/package.json +9 -9
  18. package/src/app-keybindings.ts +8 -8
  19. package/src/apps/git/diff-pane.ts +39 -35
  20. package/src/apps/git/git-tui.ts +53 -4
  21. package/src/apps/git/state.ts +12 -0
  22. package/src/chat/assistant-message.ts +27 -6
  23. package/src/chat/tool-execution.ts +73 -30
  24. package/src/native/backend.ts +13 -5
  25. package/src/native/blobs.ts +34 -1
  26. package/src/overlays/model-browser.ts +23 -1
  27. package/src/overlays/model-hub.ts +3 -9
  28. package/src/overlays/snapcompact-shape-preview.ts +13 -6
  29. package/src/overlays/tree-selector.ts +117 -2
  30. package/src/thinking.ts +9 -1
  31. package/src/tools/bash.ts +15 -9
  32. package/src/tools/eval.ts +2 -2
  33. package/src/tools/native-view.ts +2 -2
  34. package/src/tools/read.ts +12 -4
  35. package/src/tools/renderer.ts +2 -0
  36. package/src/tools/wait.ts +16 -30
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.10.3] - 2026-10-12
6
+
7
+ ### Changed
8
+
9
+ - Updated model picker to offer manual effort selection for unverified models instead of automatic mode
10
+
11
+ ### Fixed
12
+
13
+ - Fixed the `snapcompact.shape` settings preview labelling variants `5x8-sent` and repeating the first variant's sample when the frame edge differs from the variant table (Claude and GPT on Cursor; `auto` on high-resolution Claude) ([#15448](https://github.com/can1357/oh-my-pi/pull/15448) by [@will-bogusz](https://github.com/will-bogusz))
14
+
15
+ ## [18.10.2] - 2026-10-12
16
+
17
+ ### Added
18
+
19
+ - Added opt-in `/tree` branch navigation across visible branches and nested fork depths; bind `app.tree.previousBranch` / `app.tree.nextBranch` (e.g. to `Shift+Left` / `Shift+Right`) in `keybindings.yml` ([#12623](https://github.com/can1357/oh-my-pi/pull/12623) by [@azain47](https://github.com/azain47)).
20
+
21
+ ### Changed
22
+
23
+ - The `wait` tool's native job rows (`omp.wait.job`) are a single `row`: badge, id and label, progress and clock, without the output preview or capture notice beneath
24
+ - The native bash run foot reads In background with a live clock (`omp.run.time` without `stopped`) while a parked job's frames stream (`details.async.state === "running"`, partial), instead of Running
25
+ - Native model pickers (/model, the quick switch and the advisor's) mark providers with their logo (`mark.icon: "provider:<id>"`, initials where the terminal has none), and every model row leads with its provider's mark
26
+
27
+ ### Removed
28
+
29
+ - Removed the unused `app.tree.foldOrUp` / `app.tree.unfoldOrDown` keybinding ids, which no longer did anything.
30
+ - Added `parksAsBackground(toolName, details)`, `asyncJobState(details)`, `isSettledJobFrame(details)` and `settleWithJobFrame(component, frame, toolCallId)` (`chat/tool-execution`): whether a returned call's card stays live for its background job's frames (`task` always; `bash` and `eval` under native rendering), the job state a frame reports, and settling the card with the job's last frame (a cancelled job keeps its output and seals as cancelled)
31
+ - `BashToolDetails.async.state` and `EvalToolDetails.async.state` gain `cancelled` for a job killed before it finished
32
+
5
33
  ## [18.10.1] - 2026-10-12
6
34
 
7
35
  ### Added
@@ -2754,53 +2782,4 @@
2754
2782
 
2755
2783
  - Optimized padding operations across all components to use pre-allocated space buffer for better performance
2756
2784
 
2757
- ## [9.2.2] - 2026-01-31
2758
-
2759
- ### Added
2760
-
2761
- - Added setAutocompleteMaxVisible() configuration (3-20 items)
2762
- - Added image detection to terminal capabilities (containsImage method)
2763
- - Added stdin monitoring to detect stalled input events and log warnings
2764
-
2765
- ### Changed
2766
-
2767
- - Improved blockquote rendering with text wrapping in Markdown component
2768
- - Restructured terminal capabilities from interface-based to class-based model
2769
- - Improved table column width calculation with word-aware wrapping
2770
- - Refactored text utilities to use native WASM implementations for strings >256 chars with JS fast path
2771
-
2772
- ### Fixed
2773
-
2774
- - Simplified terminal write error handling to mark terminal as dead on any write failure
2775
- - Fixed multi-line strings in renderOutputBlock causing width overflow
2776
- - Fixed slash command autocomplete applying stale completion when typing quickly
2777
-
2778
- ### Removed
2779
-
2780
- - Removed TUI layout engine exports from public API (BoxNode, ColumnNode, LayoutNode, etc.)
2781
-
2782
- ## [8.12.7] - 2026-01-29
2783
-
2784
- ### Fixed
2785
-
2786
- - Fixed slash command autocomplete applying stale completion when typing quickly
2787
-
2788
- ## [8.4.1] - 2026-01-25
2789
-
2790
- ### Added
2791
-
2792
- - Added fuzzy match function for autocomplete suggestions
2793
-
2794
- ## [8.4.0] - 2026-01-25
2795
-
2796
- ### Changed
2797
-
2798
- - Added Ctrl+Backspace as a delete-word-backward keybinding and improved modified backspace matching
2799
-
2800
- ### Fixed
2801
-
2802
- - Terminal gracefully handles write failures by marking dead instead of exiting the process
2803
- - Reserved cursor space for zero padding and corrected end-of-line cursor rendering to prevent wrap glitches
2804
- - Corrected editor end-of-line cursor rendering assertion to use includes() instead of endsWith()
2805
-
2806
- Older entries are archived in [packages/tui/CHANGELOG.md@89ad0a77d575](https://github.com/can1357/oh-my-pi/blob/89ad0a77d575f01398d3c0a57bc3a22111bef36a/packages/tui/CHANGELOG.md).
2785
+ Older entries are archived in [packages/tui/CHANGELOG.md@e19bb3cb2344](https://github.com/can1357/oh-my-pi/blob/e19bb3cb2344a8953dc04aa398b810230f0e24c3/packages/tui/CHANGELOG.md).
@@ -79,9 +79,9 @@ uutils coreutils (https://github.com/uutils/coreutils), MIT
79
79
  --------------------------------------------------------------------------------
80
80
  Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
81
81
  b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
82
- cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
83
- printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee, touch, tr,
84
- truncate, uname, uniq, wc, whoami, yes.
82
+ cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nl, nproc,
83
+ paste, printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
84
+ touch, tr, truncate, uname, uniq, wc, whoami, yes.
85
85
 
86
86
  Copyright (c) uutils developers
87
87
 
@@ -37,8 +37,8 @@ interface AppKeybindings {
37
37
  "app.session.rename": true;
38
38
  "app.session.delete": true;
39
39
  "app.session.deleteNoninvasive": true;
40
- "app.tree.foldOrUp": true;
41
- "app.tree.unfoldOrDown": true;
40
+ "app.tree.previousBranch": true;
41
+ "app.tree.nextBranch": true;
42
42
  "app.plan.toggle": true;
43
43
  "app.history.search": true;
44
44
  "app.stt.pushToTalk": true;
@@ -319,13 +319,13 @@ export declare const KEYBINDINGS: {
319
319
  readonly defaultKeys: "ctrl+backspace";
320
320
  readonly description: "Delete session (non-invasive)";
321
321
  };
322
- readonly "app.tree.foldOrUp": {
323
- readonly defaultKeys: ["ctrl+left", "alt+left"];
324
- readonly description: "Fold or move up";
322
+ readonly "app.tree.previousBranch": {
323
+ readonly defaultKeys: [];
324
+ readonly description: "Jump to the previous branch in /tree";
325
325
  };
326
- readonly "app.tree.unfoldOrDown": {
327
- readonly defaultKeys: ["ctrl+right", "alt+right"];
328
- readonly description: "Unfold or move down";
326
+ readonly "app.tree.nextBranch": {
327
+ readonly defaultKeys: [];
328
+ readonly description: "Jump to the next branch in /tree";
329
329
  };
330
330
  readonly "app.plan.toggle": {
331
331
  readonly defaultKeys: "alt+shift+p";
@@ -118,6 +118,11 @@ export type PaneClick = {
118
118
  } | {
119
119
  type: "handled";
120
120
  } | null;
121
+ /**
122
+ * The heading of one side of a media/binary change (`Before · PNG · Git LFS`):
123
+ * `label` plus the side's kind, shared by the ANSI pane and the native compare.
124
+ */
125
+ export declare function assetTitle(label: string, side: FileAssetSide): string;
121
126
  /**
122
127
  * Scrollable diff viewport. The root component feeds it key/mouse input and
123
128
  * composes its rendered lines into the frame.
@@ -73,11 +73,22 @@ export interface TextFileContents {
73
73
  readonly newText: string;
74
74
  readonly streamResult: DiffStreamResult;
75
75
  }
76
+ /** Where one side of a change lives, so a host viewer (Tern's TSP `media` node) can load it by path. */
77
+ export interface MediaRef {
78
+ /** Absolute path of the file (repository root joined with its repository path). */
79
+ readonly path: string;
80
+ /** Git revision holding this side (`HEAD`, a sha, `:0` for the index); absent for the worktree file. */
81
+ readonly rev?: string;
82
+ }
76
83
  /** Non-text sides rendered as previews or explicit placeholders. */
77
84
  export interface AssetFileContents {
78
85
  readonly kind: "asset";
79
86
  readonly old: FileAssetSide;
80
87
  readonly new: FileAssetSide;
88
+ /** Source of {@link old}; absent when that side is empty. */
89
+ readonly oldRef?: MediaRef;
90
+ /** Source of {@link new}; absent when that side is empty. */
91
+ readonly newRef?: MediaRef;
81
92
  }
82
93
  /** Loaded file content selected for text diffing or asset preview. */
83
94
  export type FileContents = TextFileContents | AssetFileContents;
@@ -2,7 +2,7 @@ import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { type Component, Container } from "../tui.js";
3
3
  import { type ImageBudget } from "../components/image.js";
4
4
  import type { AssistantThinkingRenderer } from "./extension-types.js";
5
- import type { NativeNode, NativeUiEvent } from "../native/node.js";
5
+ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
6
6
  import { type CacheInvalidation } from "./cache-invalidation-marker.js";
7
7
  import { type ServedModelMismatch } from "./served-model-marker.js";
8
8
  import { type TranscriptStableRow } from "../chrome/transcript-container.js";
@@ -99,7 +99,7 @@ export declare class AssistantMessageComponent extends Container {
99
99
  * head's `title`. With `hideThinkingBlock` only the live head shows (collapsed)
100
100
  * and a finished thought emits nothing.
101
101
  */
102
- describe(): NativeNode;
102
+ describe(cx?: DescribeContext): NativeNode;
103
103
  /**
104
104
  * A TTSR rule rewound this partial answer: a native terminal dims it and
105
105
  * tags it `↺ rewound` (the ANSI render leaves it as it stopped).
@@ -130,7 +130,12 @@ export declare class AssistantMessageComponent extends Container {
130
130
  setTableChartsVisible(visible: boolean): void;
131
131
  /** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
132
132
  setToolResultImagesVisible(visible: boolean): void;
133
- setToolResultImages(toolCallId: string, images: ImageContent[]): void;
133
+ /**
134
+ * A grouped read's result images, and `mediaPath`: the file it read that Tern's media viewer
135
+ * shows (read details), which a native host supporting `media` shows by path instead of the
136
+ * images. A media-only result (a model, a PDF) passes no images.
137
+ */
138
+ setToolResultImages(toolCallId: string, images: ImageContent[], mediaPath?: string): void;
134
139
  updateContent(message: AssistantMessage, opts?: {
135
140
  transient?: boolean;
136
141
  }): void;
@@ -17,19 +17,21 @@ export interface ToolExecutionOptions {
17
17
  /** Allow the name-keyed renderer registry only when the active tool is the built-in implementation. */
18
18
  useBuiltInRenderer?: boolean;
19
19
  }
20
+ /** A tool result or progress frame as a card shows it. */
21
+ export interface ToolResultFrame {
22
+ content: Array<{
23
+ type: string;
24
+ text?: string;
25
+ data?: string;
26
+ mimeType?: string;
27
+ }>;
28
+ details?: unknown;
29
+ isError?: boolean;
30
+ }
20
31
  export interface ToolExecutionHandle extends Component {
21
32
  updateArgs(args: unknown, toolCallId?: string): void;
22
33
  updateStreamPreview?(update: unknown): void;
23
- updateResult(result: {
24
- content: Array<{
25
- type: string;
26
- text?: string;
27
- data?: string;
28
- mimeType?: string;
29
- }>;
30
- details?: unknown;
31
- isError?: boolean;
32
- }, isPartial?: boolean, toolCallId?: string): void;
34
+ updateResult(result: ToolResultFrame, isPartial?: boolean, toolCallId?: string): void;
33
35
  setArgsComplete(toolCallId?: string): void;
34
36
  setExecutionStarted(toolCallId?: string): void;
35
37
  setExpanded(expanded: boolean): void;
@@ -39,6 +41,25 @@ export interface ToolExecutionHandle extends Component {
39
41
  /** Seal the block as final history and stop its animations. */
40
42
  seal(): void;
41
43
  }
44
+ /**
45
+ * Whether a call that returned with `details.async.state === "running"` stays
46
+ * parked ({@link ToolExecutionHandle.parkAsBackground}) so its job's later
47
+ * frames keep filling the card until a `completed`/`failed` frame settles it.
48
+ * Detached `task` spawns always do; backgrounded `bash`/`eval` jobs only under
49
+ * native rendering, where the terminal transcript keeps them frozen instead.
50
+ * Shared by the live event controller and the transcript rebuild.
51
+ */
52
+ export declare function parksAsBackground(toolName: string, details: unknown): boolean;
53
+ /** The background job state a tool result reports (`details.async.state`: running, completed, failed, cancelled). */
54
+ export declare function asyncJobState(details: unknown): string | undefined;
55
+ /** Whether a frame is a background job's last one: it completed, failed or was cancelled. */
56
+ export declare function isSettledJobFrame(details: unknown): boolean;
57
+ /**
58
+ * Settle a parked card ({@link parksAsBackground}) with its job's last frame
59
+ * ({@link isSettledJobFrame}): a completed or failed job is the call's result;
60
+ * a cancelled one keeps the output it got to and seals the card as cancelled.
61
+ */
62
+ export declare function settleWithJobFrame(component: ToolExecutionHandle, frame: ToolResultFrame, toolCallId: string): void;
42
63
  /** Phase-locked spinner glyph index shared by every live tool block so parallel
43
64
  * spinners advance in lockstep instead of each tracking its own start time. */
44
65
  export declare function sharedSpinnerFrame(frameCount: number, now?: number): number;
@@ -74,16 +95,7 @@ export declare class ToolExecutionComponent extends Container {
74
95
  */
75
96
  whenPreviewSettled(timeoutMs?: number): Promise<void>;
76
97
  updateStreamPreview(update: unknown): void;
77
- updateResult(result: {
78
- content: Array<{
79
- type: string;
80
- text?: string;
81
- data?: string;
82
- mimeType?: string;
83
- }>;
84
- details?: unknown;
85
- isError?: boolean;
86
- }, isPartial?: boolean, _toolCallId?: string): void;
98
+ updateResult(result: ToolResultFrame, isPartial?: boolean, _toolCallId?: string): void;
87
99
  /**
88
100
  * Advance to the shared spinner glyph and repaint just this block. Driven by
89
101
  * the single shared spinner ticker (see `registerSpinnerBlock`); the tick is
@@ -47,10 +47,11 @@ export interface NativeBackendOptions {
47
47
  }
48
48
  /**
49
49
  * The reply a v1 terminal is assumed to give before its real `hello` arrives
50
- * (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary, the
51
- * default APC limit and credits, the terminal's width, the appearance omp
52
- * already detected, full motion, and `blobs`, so a resumed session's images
53
- * are asked about rather than resent before the reply comes.
50
+ * (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary except
51
+ * {@link UNASSUMED_KINDS}, the default APC limit and credits, the terminal's
52
+ * width, the appearance omp already detected, full motion, and `blobs`, so a
53
+ * resumed session's images are asked about rather than resent before the
54
+ * reply comes.
54
55
  */
55
56
  export declare function assumedTspHello(terminal: Terminal): TspHello;
56
57
  export declare class NativeBackend {
@@ -8,7 +8,7 @@
8
8
  * references it.
9
9
  */
10
10
  import type { TspProps } from "@oh-my-pi/pi-wire";
11
- import type { NativeNode } from "./node.js";
11
+ import type { NativeChild, NativeNode } from "./node.js";
12
12
  export interface NativeBlob {
13
13
  readonly id: string;
14
14
  readonly mime: string;
@@ -31,3 +31,15 @@ export declare class NativeImageCache {
31
31
  #private;
32
32
  get(key: string, data: string, mimeType: string): NativeNode;
33
33
  }
34
+ /** The disk file a tool result's image was read from (`read` details `mediaPath`), when it has one. */
35
+ export declare function readMediaPath(details: unknown): string | undefined;
36
+ /**
37
+ * A `media` node the host loads by path, capped at the inline image preview
38
+ * height. Callers check `cx.supports("media")` first.
39
+ */
40
+ export declare function nativeMediaNode(path: string, key: string): NativeNode;
41
+ /** Blob ids of the `image` nodes and paths of the `media` nodes under `children` (described nodes only). */
42
+ export declare function shownMedia(children: readonly NativeChild[] | undefined): {
43
+ blobs: Set<string>;
44
+ paths: Set<string>;
45
+ };
@@ -4,7 +4,7 @@ import type { Component } from "../tui.js";
4
4
  import type { SgrMouseEvent } from "../mouse.js";
5
5
  import { type ConfiguredThinkingLevel } from "../thinking.js";
6
6
  import { type ThemeColor } from "../theme/theme.js";
7
- import type { TspPickerColumn, TspPickerGroup, TspPickerItem } from "@oh-my-pi/pi-wire";
7
+ import type { TspPickerColumn, TspPickerGroup, TspPickerItem, TspPickerMark } from "@oh-my-pi/pi-wire";
8
8
  import type { NativeChild, NativeNode, NativeUiEvent } from "../native/node.js";
9
9
  /** Canonical display ordering of built-in model roles. */
10
10
  export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "commit" | "tiny" | "memory" | "task" | "advisor" | "image" | "web" | "speech" | "dictation" | "judge";
@@ -198,6 +198,11 @@ export declare function rankModelItems(query: string, items: ReadonlyArray<Model
198
198
  export declare function formatRoleChip(role: string, assignment: RoleAssignment, settings: ModelBrowserSource): string;
199
199
  /** Fact columns of a model picker (Stencil `NATIVE_REDESIGN.md` §4.7); the lowest priority hides first. */
200
200
  export declare const MODEL_PICKER_COLUMNS: readonly TspPickerColumn[];
201
+ /**
202
+ * A provider's picker mark: the terminal's logo for it, else its initials
203
+ * (`amazon-bedrock` → `AB`, `anthropic` → `An`) on its own gradient.
204
+ */
205
+ export declare function providerMark(providerId: string): TspPickerMark;
201
206
  /** The omp theme token of a thinking level's dot (`thinkingHigh`); none for inherit and auto. */
202
207
  export declare function thinkingDotToken(level: ConfiguredThinkingLevel): string | undefined;
203
208
  /**
@@ -37,6 +37,11 @@ export declare function shouldDisableReasoning(level: ThinkingLevel | undefined)
37
37
  * Resolves a selector against the current model while preserving explicit "off".
38
38
  */
39
39
  export declare function resolveThinkingLevelForModel(model: Model | undefined, level: ThinkingLevel | undefined): ResolvedThinkingLevel | undefined;
40
+ /**
41
+ * Level applied to `model` when the user has not picked one for it (carried,
42
+ * global default, auto). Unverified thinking stays unset until the user picks.
43
+ */
44
+ export declare function automaticThinkingLevel<T>(model: Model | undefined, fallback: T): T | undefined;
40
45
  /**
41
46
  * Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
42
47
  * inside the coding-agent layer: it is never an {@link Effort} or
@@ -18,6 +18,8 @@ export interface BashToolDetails {
18
18
  requestedTimeoutSeconds?: number;
19
19
  timeoutDisabled?: boolean;
20
20
  wallTimeMs?: number;
21
+ /** False when no wall-time footer was emitted; absent on older results. */
22
+ wallTimeNoticeEmitted?: boolean;
21
23
  /** Exit code of a command that ran to completion but failed (non-zero). */
22
24
  exitCode?: number;
23
25
  /** True when the command was killed by its timeout deadline (not a failure). */
@@ -31,12 +33,15 @@ export interface BashToolDetails {
31
33
  timedOut: boolean;
32
34
  pid?: number;
33
35
  };
36
+ /** Background job behind the call; `cancelled`: killed (`proc://<id>/kill`) before it finished. */
34
37
  async?: {
35
- state: "running" | "completed" | "failed";
38
+ state: "running" | "completed" | "failed" | "cancelled";
36
39
  jobId: string;
37
40
  type: "bash";
38
41
  };
39
42
  }
43
+ /** Minimum duration for a model-facing wall-time notice; shorter runs keep timing only in details. */
44
+ export declare const WALL_TIME_NOTICE_MIN_MS = 1000;
40
45
  /** Formats the model-facing command duration notice. */
41
46
  export declare function formatWallTimeNotice(wallTimeMs: number): string;
42
47
  /** Formats the model-facing command exit status notice. */
@@ -44,9 +44,9 @@ export interface EvalToolDetails {
44
44
  languages?: EvalLanguage[];
45
45
  /** Optional human-readable notice (e.g. fallback explanation). */
46
46
  notice?: string;
47
- /** Present when the cell was auto-backgrounded as an async job. */
47
+ /** Present when the cell was auto-backgrounded as an async job; `cancelled`: killed before it finished. */
48
48
  async?: {
49
- state: "running" | "completed" | "failed";
49
+ state: "running" | "completed" | "failed" | "cancelled";
50
50
  jobId: string;
51
51
  type: "eval";
52
52
  };
@@ -85,7 +85,7 @@ export interface RunFootInput {
85
85
  readonly state: RunState;
86
86
  /** Exit code of a `failed` run; a non-zero code names the state `exit N`. */
87
87
  readonly exitCode?: number;
88
- /** Milliseconds run so far (`running`) or in total; the time is omitted when undefined. */
88
+ /** Milliseconds run so far (`running`, a live `background` job) or in total; omitted when undefined. */
89
89
  readonly elapsedMs?: number;
90
90
  /** Quiet facts (job id, service state, artifact), muted and ` · `-joined. */
91
91
  readonly facts?: readonly string[];
@@ -20,6 +20,12 @@ export interface ReadToolDetails {
20
20
  truncation?: ReadTruncationStats;
21
21
  isDirectory?: boolean;
22
22
  resolvedPath?: string;
23
+ /**
24
+ * Absolute path of the disk file whose image this read returned. A native TSP host that
25
+ * supports `media` shows the file by path instead of the uploaded image bytes. Unset for
26
+ * screenshots, URL reads and other images not backed by a file. Display-only.
27
+ */
28
+ mediaPath?: string;
23
29
  suffixResolution?: {
24
30
  from: string;
25
31
  to: string;
@@ -140,7 +146,7 @@ export declare const readToolRenderer: {
140
146
  }>;
141
147
  details?: ReadToolDetails;
142
148
  isError?: boolean;
143
- }, _options: RenderResultOptions, args?: ReadRenderArgs): NativeToolView;
149
+ }, options: RenderResultOptions, args?: ReadRenderArgs): NativeToolView;
144
150
  mergeCallAndResult: true;
145
151
  };
146
152
  /** Inclusive line range describing one elided span in a structural summary. */
@@ -33,6 +33,8 @@ export interface RenderResultOptions {
33
33
  elapsedMs?: number;
34
34
  /** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
35
35
  cancelled?: boolean;
36
+ /** The native host shows files by path (`cx.supports("media")`). Native describe hooks only. */
37
+ media?: boolean;
36
38
  }
37
39
  /** Render options for a result, plus the tool-specific context the transcript threads through. */
38
40
  export type RenderResultContextOptions = RenderResultOptions & {
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.1",
4
+ "version": "18.10.3",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -41,14 +41,14 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.10.1",
45
- "@oh-my-pi/pi-agent-core": "18.10.1",
46
- "@oh-my-pi/pi-ai": "18.10.1",
47
- "@oh-my-pi/pi-catalog": "18.10.1",
48
- "@oh-my-pi/pi-natives": "18.10.1",
49
- "@oh-my-pi/pi-utils": "18.10.1",
50
- "@oh-my-pi/pi-wire": "18.10.1",
51
- "@oh-my-pi/snapcompact": "18.10.1"
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"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -57,8 +57,8 @@ interface AppKeybindings {
57
57
  "app.session.rename": true;
58
58
  "app.session.delete": true;
59
59
  "app.session.deleteNoninvasive": true;
60
- "app.tree.foldOrUp": true;
61
- "app.tree.unfoldOrDown": true;
60
+ "app.tree.previousBranch": true;
61
+ "app.tree.nextBranch": true;
62
62
  "app.plan.toggle": true;
63
63
  "app.history.search": true;
64
64
  "app.stt.pushToTalk": true;
@@ -227,13 +227,13 @@ export const KEYBINDINGS = {
227
227
  defaultKeys: "ctrl+backspace",
228
228
  description: "Delete session (non-invasive)",
229
229
  },
230
- "app.tree.foldOrUp": {
231
- defaultKeys: ["ctrl+left", "alt+left"],
232
- description: "Fold or move up",
230
+ "app.tree.previousBranch": {
231
+ defaultKeys: [],
232
+ description: "Jump to the previous branch in /tree",
233
233
  },
234
- "app.tree.unfoldOrDown": {
235
- defaultKeys: ["ctrl+right", "alt+right"],
236
- description: "Unfold or move down",
234
+ "app.tree.nextBranch": {
235
+ defaultKeys: [],
236
+ description: "Jump to the next branch in /tree",
237
237
  },
238
238
  "app.plan.toggle": {
239
239
  defaultKeys: "alt+shift+p",
@@ -570,6 +570,43 @@ interface AssetDocument {
570
570
  /** Result of a left click inside the pane. */
571
571
  export type PaneClick = { type: "hunk-action"; hunk: HunkBlock; action: HunkAction } | { type: "handled" } | null;
572
572
 
573
+ /**
574
+ * The heading of one side of a media/binary change (`Before · PNG · Git LFS`):
575
+ * `label` plus the side's kind, shared by the ANSI pane and the native compare.
576
+ */
577
+ export function assetTitle(label: string, side: FileAssetSide): string {
578
+ let kind: string;
579
+ let lfs = false;
580
+ switch (side.kind) {
581
+ case "empty":
582
+ return label;
583
+ case "image":
584
+ kind =
585
+ side.image.sourceMimeType === "image/svg+xml"
586
+ ? "SVG"
587
+ : side.image.sourceMimeType.replace(/^image\//, "").toUpperCase();
588
+ lfs = side.image.lfsOid !== undefined;
589
+ break;
590
+ case "text":
591
+ kind = "Text";
592
+ lfs = side.lfsOid !== undefined;
593
+ break;
594
+ case "binary":
595
+ kind = "Binary";
596
+ lfs = side.lfsOid !== undefined;
597
+ break;
598
+ case "tooLarge":
599
+ kind = "Too large";
600
+ lfs = side.lfsOid !== undefined;
601
+ break;
602
+ case "lfsMissing":
603
+ kind = "LFS missing";
604
+ lfs = true;
605
+ break;
606
+ }
607
+ return `${label} · ${kind}${lfs ? " · Git LFS" : ""}`;
608
+ }
609
+
573
610
  /**
574
611
  * Scrollable diff viewport. The root component feeds it key/mouse input and
575
612
  * composes its rendered lines into the frame.
@@ -1129,11 +1166,11 @@ export class DiffPane {
1129
1166
  for (let index = 0; index < height; index++) {
1130
1167
  const leftSource =
1131
1168
  index === 0
1132
- ? centerLine(theme.bold(this.#assetTitle("Before", asset.old)), leftWidth).trimEnd()
1169
+ ? centerLine(theme.bold(assetTitle("Before", asset.old)), leftWidth).trimEnd()
1133
1170
  : (oldLines[index - 1] ?? "");
1134
1171
  const rightSource =
1135
1172
  index === 0
1136
- ? centerLine(theme.bold(this.#assetTitle("After", asset.new)), rightWidth).trimEnd()
1173
+ ? centerLine(theme.bold(assetTitle("After", asset.new)), rightWidth).trimEnd()
1137
1174
  : (newLines[index - 1] ?? "");
1138
1175
  const left = truncateToWidth(leftSource, leftWidth);
1139
1176
  const right = truncateToWidth(rightSource, rightWidth);
@@ -1208,39 +1245,6 @@ export class DiffPane {
1208
1245
  return Array.from({ length: height }, (_, index) => content[index - top] ?? "");
1209
1246
  }
1210
1247
 
1211
- #assetTitle(label: string, side: FileAssetSide): string {
1212
- let kind: string;
1213
- let lfs = false;
1214
- switch (side.kind) {
1215
- case "empty":
1216
- return label;
1217
- case "image":
1218
- kind =
1219
- side.image.sourceMimeType === "image/svg+xml"
1220
- ? "SVG"
1221
- : side.image.sourceMimeType.replace(/^image\//, "").toUpperCase();
1222
- lfs = side.image.lfsOid !== undefined;
1223
- break;
1224
- case "text":
1225
- kind = "Text";
1226
- lfs = side.lfsOid !== undefined;
1227
- break;
1228
- case "binary":
1229
- kind = "Binary";
1230
- lfs = side.lfsOid !== undefined;
1231
- break;
1232
- case "tooLarge":
1233
- kind = "Too large";
1234
- lfs = side.lfsOid !== undefined;
1235
- break;
1236
- case "lfsMissing":
1237
- kind = "LFS missing";
1238
- lfs = true;
1239
- break;
1240
- }
1241
- return `${label} · ${kind}${lfs ? " · Git LFS" : ""}`;
1242
- }
1243
-
1244
1248
  #renderStreaming(width: number, height: number): string[] {
1245
1249
  const streaming = this.#streaming;
1246
1250
  if (!streaming) return [];