@oh-my-pi/pi-tui 18.14.2 → 18.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -33
- package/README.md +1 -1
- package/dist/types/chat/transcript-render-helpers.d.ts +8 -0
- package/dist/types/chrome/transcript-status.d.ts +2 -2
- package/dist/types/overlays/hook-editor.d.ts +2 -0
- package/dist/types/overlays/jobs-panel.d.ts +4 -0
- package/dist/types/render/render-utils.d.ts +17 -0
- package/dist/types/terminal-capabilities.d.ts +1 -1
- package/dist/types/tools/grouped-file-output.d.ts +76 -8
- package/dist/types/tools/hashline-format.d.ts +5 -0
- package/dist/types/tools/native-view.d.ts +2 -0
- package/dist/types/tools/read.d.ts +10 -0
- package/dist/types/tools/wait.d.ts +2 -0
- package/package.json +9 -9
- package/src/chat/chat-transcript-builder.ts +4 -2
- package/src/chat/read-tool-group.ts +48 -21
- package/src/chat/transcript-render-helpers.ts +36 -2
- package/src/chrome/transcript-status.ts +5 -3
- package/src/overlays/hook-editor.ts +6 -0
- package/src/overlays/jobs-panel.ts +45 -19
- package/src/render/file-list.ts +4 -7
- package/src/render/render-utils.ts +31 -3
- package/src/terminal-capabilities.ts +5 -0
- package/src/tmux.ts +1 -0
- package/src/tools/ast-edit.ts +48 -93
- package/src/tools/ast-grep.ts +22 -66
- package/src/tools/edit.ts +11 -2
- package/src/tools/find.ts +6 -3
- package/src/tools/glob.ts +2 -1
- package/src/tools/grep.ts +43 -287
- package/src/tools/grouped-file-output.ts +250 -64
- package/src/tools/hashline-format.ts +5 -0
- package/src/tools/lsp.ts +15 -15
- package/src/tools/native-view.ts +11 -5
- package/src/tools/proc-render.ts +2 -2
- package/src/tools/read.ts +14 -0
- package/src/tools/wait.ts +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.15.0] - 2026-11-01
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- `GroupedLineContext.isUrl` is replaced by `url`, the internal URL a header names or a body row belongs to ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `FILE_NAME_COLOR`, `formatStyledPath` and `pathNameStart` to `render/render-utils` for paths in tool bodies and Tern file rows; `createGroupedRowStyler`, `groupedLinkTargets`, `fitGroupsToBudget`, `groupsByFile` and `isMatchRow` to `tools/grouped-file-output`; `splitReadQuery` to `tools/read`; and `HASHLINE_TAG_SUFFIX_RE` to `tools/hashline-format`. `classifyGroupedLines` reports each file header's full `displayPath` and `suffix`, and a `TranscriptStatusPart` without `color` keeps its already-styled text ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
14
|
+
- `JobsPanelJob.caption`: the jobs sheet's detail pane shows it, muted, between the job's purpose and its command (`omp.jobs.caption`) ([#17649](https://github.com/can1357/oh-my-pi/pull/17649) by [@wolfiesch](https://github.com/wolfiesch))
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- The Background jobs sheet names each job by what it is for when the model said so, shows that above the full command in the detail pane, and no longer repeats the command in an empty widget box for jobs that print nothing ([#17649](https://github.com/can1357/oh-my-pi/pull/17649) by [@wolfiesch](https://github.com/wolfiesch))
|
|
19
|
+
- File paths in tool output share one style: a dim directory and the file name in the custom-message label color (bronze in titanium), with line locations and selectors (`:1-175`, `:42:14`, `?q=`) muted. This covers Grep, AST Grep, AST Edit, Glob, Find, LSP, diagnostics, grouped Reads and file mentions; Glob's folder icon is muted like the file-type icons, and paths in tool headers keep their color ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
20
|
+
- Grep, AST Grep and AST Edit results label each file with its full path instead of the model-facing `# dir/` and `## file#tag` headers, and each file gets its own tree branch. Their collapsed previews fit the same row budget the same way, cutting the last file short rather than overflowing, and end with `… N more matches` (`… N more files` for AST Edit) ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
21
|
+
- AST Grep match rows link to their line, as Grep's do ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
22
|
+
- LSP diagnostics carry their severity color on the `[error]`/`[warning]` tag instead of the location, and collapsed diagnostic rows now show that tag ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Rex sessions now render Kitty graphics without forcing the protocol, including when only `TERM=xterm-rex` identifies the client ([#17651](https://github.com/can1357/oh-my-pi/issues/17651)).
|
|
27
|
+
- Fixed collapsed AST Grep showing no matches when its first file or folder was taller than the preview, and its `… N more` row counting hidden folders instead of hidden matches ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
28
|
+
- Fixed collapsed AST Edit overflowing its preview when the first file's diff was long ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
29
|
+
- Fixed search and diagnostic file paths in Tern dropping the leading `/` of absolute paths and a folder's own `(…)` ending, and AST Edit diffs in Tern losing highlighting to the file's `#tag`; a file name ending in lowercase or other-length `#…` text keeps it ([#17608](https://github.com/can1357/oh-my-pi/pull/17608) by [@JustasMasiulis](https://github.com/JustasMasiulis))
|
|
30
|
+
- Fixed a read of a URL drawing as dead text in the folded read row: its target is not a file path, so no link was built for it, and clicking the URL did nothing. The row (and each row of a grouped read) now links to the URL itself, gaining an `https://` scheme for a bare `www.` host ([#17598](https://github.com/can1357/oh-my-pi/pull/17598) by [@iacore](https://github.com/iacore))
|
|
31
|
+
- Fixed replayed transcripts ignoring a registered `async-result` message renderer, so a replayed conversation rendered differently from the live one ([#17372](https://github.com/can1357/oh-my-pi/pull/17372) by [@AlexBrd999](https://github.com/AlexBrd999), from [#14484](https://github.com/can1357/oh-my-pi/pull/14484) by [@F0Rextasy](https://github.com/F0Rextasy); [#14131](https://github.com/can1357/oh-my-pi/issues/14131))
|
|
32
|
+
|
|
5
33
|
## [18.14.2] - 2026-10-31
|
|
6
34
|
|
|
7
35
|
### Added
|
|
@@ -2478,36 +2506,4 @@
|
|
|
2478
2506
|
|
|
2479
2507
|
- Fixed the OSC 11 appearance poll re-querying every 2s forever on terminals that support Mode 2031 but never change theme, whose repeated OSC 11/DA1 writes cleared the user's active text selection (breaking copy every 2 seconds). The poll now stops as soon as DECRQM confirms Mode 2031 support, since push notifications make polling redundant.
|
|
2480
2508
|
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
### Added
|
|
2484
|
-
|
|
2485
|
-
- Added Kitty `CSI 22 J` screen-to-scrollback clears for non-destructive full paints, while keeping ED3 for destructive history/session rebuilds.
|
|
2486
|
-
- Added Kitty OSC 99 rich notification formatting and startup capability probing.
|
|
2487
|
-
- Added Kitty OSC 66 text-sized Markdown H1 headings (2x scale) plus native text-width support for OSC 66 spans. Off by default and gated to Kitty (the only terminal implementing OSC 66) via the `TERMINAL.textSizing` capability; hosts enable it through `setTextSizing`.
|
|
2488
|
-
- Added Kitty Unicode placeholder image rendering (`U=1` + U+10EEEE with explicit row/column diacritics): inline images are drawn as real text cells that carry the image id in their foreground color, so they survive horizontal slicing, reflow, and overlapping draws instead of relying on cursor-positioned `a=p` placements. Enabled by default on Kitty-family terminals; opt out with `PI_NO_KITTY_PLACEHOLDERS=1`, and falls back to direct placement when a grid exceeds the diacritic table's addressable range.
|
|
2489
|
-
- Added Kitty temp-file image transmission (`t=t`): on local sessions, decoded PNG bytes are written to a `tty-graphics-protocol` temp file and the path is sent instead of in-band base64, gated behind a startup `a=q,t=t` support probe. Controlled by `PI_KITTY_IMAGE_TRANSMISSION=direct|temp-file|auto`; disabled over SSH unless explicitly forced.
|
|
2490
|
-
- Added DECRQM capability detection for DEC private modes 2026 (synchronized output) and 2048 (in-band resize). Synchronized-output paint wrappers are dropped when the terminal reports 2026 unsupported (preserving the `PI_NO_SYNC_OUTPUT` override), and DEC 2048 in-band resize is enabled when supported — reported geometry and cell pixel size are updated from `CSI 48 ; rows ; cols ; yPx ; xPx t` reports, with SIGWINCH and `CSI 16 t` kept as fallbacks.
|
|
2491
|
-
- Added an injectable render scheduler for TUI tests, allowing deterministic render drains without patching global clocks or event-loop timing.
|
|
2492
|
-
- Added `ImageBudget`, an inline-image cap that keeps only the most recent N images as live terminal graphics and demotes older ones to their text fallback. Once a new image pushes the count past the cap, the renderer hides the oldest via a full redraw plus an explicit Kitty graphics purge (`a=d,d=I`) — text-clear escapes (`CSI 2 J`/`CSI 3 J`) do not remove Kitty images. Configure the cap via `TUI#setMaxInlineImages` (`0` disables it).
|
|
2493
|
-
- Changed Kitty inline images to a transmit-once + placement scheme: the base64 data is sent a single time (`a=t`) keyed by a stable image id, then every repaint emits only the tiny placement (`a=p,i=…,p=…`). Repaints — including full redraws — no longer re-send image data or stack duplicate placements, and the diff/line buffers and render caches hold short placement strings instead of multi-KB base64. The `ImageBudget` doubles as the transmit store (it tracks which ids are loaded and re-transmits after a purge frees the data). iTerm2/Sixel, which have no addressable image store, keep sending inline data as before.
|
|
2494
|
-
- Added a renderer-level DECCARA rectangular-SGR optimizer that paints solid background panels/rows (Box/Text/Markdown fills, status bars, any full-width `theme.bg` row) as a single coalesced rectangle escape (`CSI 2*x` / `CSI Pt;Pl;Pb;Pr;<sgr>$r` / `CSI *x`) instead of emitting a full-width run of background-styled spaces on every visible row. It operates at emit time on the final ANSI strings — components are unchanged — and strips only trailing padding it can prove sits under a single non-default background span, coalescing vertically adjacent identical fills into one rectangle and falling back to the original bytes whenever the rectangle would not save bytes. Enabled only on Kitty, which implements the SGR-background extension (`docs/deccara.rst`); **Ghostty is intentionally excluded** because its `CSI $r` is unimplemented (ghostty-org/ghostty#632) and would drop the background entirely. Scrollback-bound rows and the append/scroll paths always keep the padded representation so native history preserves colored cells, and the `PI_NO_DECCARA` kill switch (plus tmux/screen/zellij detection) forces the fallback.
|
|
2495
|
-
- Added `CMUX_SURFACE_ID` environment variable support to `getTerminalId()`, so cmux terminal surfaces get a stable identifier alongside kitty, tmux, macOS Terminal.app, and Windows Terminal — enabling per-surface session breadcrumbs for `omp -c` in cmux.
|
|
2496
|
-
|
|
2497
|
-
### Changed
|
|
2498
|
-
|
|
2499
|
-
- Changed TUI tests to use Ghostty's VT engine (`ghostty-web`) instead of `@xterm/headless`.
|
|
2500
|
-
- Changed the default inline-image live graphics budget from 3 to 8 images.
|
|
2501
|
-
|
|
2502
|
-
### Fixed
|
|
2503
|
-
|
|
2504
|
-
- Fixed the DECCARA background-fill optimizer rejecting or repainting the wrong cells when a trailing fill crossed from default-background spaces into colored spaces.
|
|
2505
|
-
- Fixed DEC private-mode reports with DECRPM status 3/4 being treated as unsupported, so permanent 2026/2048 reports stay recognized.
|
|
2506
|
-
- Fixed OSC 66 text-sizing width and slicing edge cases, including ZWJ emoji payloads and partial slices through scaled spans.
|
|
2507
|
-
- Fixed focused `Input` components following `TUI#setShowHardwareCursor`, so single-line prompts render either the terminal cursor or software cursor consistently with the editor.
|
|
2508
|
-
- Fixed the DECCARA background-fill optimizer painting fills on the wrong rows ("split into unaligned halves") in the differential repaint path. When a diff grew the transcript past the viewport, writing the rewritten rows scrolled the terminal, but the absolute DECCARA rectangle coordinates were derived from the pre-scroll viewport top, so every fill landed `scrollAmount` rows too low while the relatively-positioned text settled correctly; rows scrolled into history were also shortened, dropping their background padding from native scrollback. Rectangles now target the post-scroll rows and only rows remaining in the final viewport are optimized.
|
|
2509
|
-
- Fixed native scrollback desynchronization after terminal width or height changes reflowed overflowing content while the viewport was not at the bottom
|
|
2510
|
-
- Fixed a notification chip (or any injected block) rendering on top of an actively streaming tool render on ED3-risk terminals (Ghostty/kitty/Alacritty/iTerm2). While a foreground tool streams, its header's elapsed-time counter ticks every frame; once output scrolls the header above the viewport top, each tick is an offscreen edit that — because the eager scrollback-rebuild opt-in is gated off on these terminals — repaints the viewport in place and advances the rendered line count without committing the new overflow to native history. `#scrollbackHighWater` then lagged the logical viewport top, so a later content shrink whose changes landed in the visible region slipped past the shrink-across-boundary guard and reached the differential emitter, which is anchored to `#maxLinesRendered - height`: it rewrote only the suffix, dropped the newly exposed top row, and left a blank at the bottom, drifting every row below the edit one line up so it painted over the rows above. Such shrinks now re-anchor the bottom of the viewport with a non-destructive repaint, and the foreground-streaming shrink-across-boundary case repaints the live tail instead of padding and pinning the pre-shrink viewport.
|
|
2511
|
-
- Fixed a terminal resize during foreground-tool streaming on an unknown-viewport / ED3-risk host (Ghostty/kitty/Alacritty/iTerm2/WSL) leaving native scrollback permanently out of sync, so scrolling back after the turn showed missing rows. A pure geometry resize (no content change) takes the in-place viewport-repaint path, which — unlike a content-bearing resize that rebuilds via the geometry branch — never flagged native history. Because the prompt-submit checkpoint (`refreshNativeScrollbackIfDirty`) only rebuilds when scrollback is marked dirty on these hosts, the discrepancy was never reconciled. Overflowing geometry repaints whose viewport is not known to be at the bottom now mark scrollback dirty so the next checkpoint rebuilds an exact copy of the transcript.
|
|
2512
|
-
|
|
2513
|
-
Older entries are archived in [packages/tui/CHANGELOG.md@6693ddbfd58c](https://github.com/can1357/oh-my-pi/blob/6693ddbfd58ce9edcdc0dc3fe98a8882c9bae834/packages/tui/CHANGELOG.md).
|
|
2509
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@6b354a56a890](https://github.com/can1357/oh-my-pi/blob/6b354a56a890b69b6bc9503c16c510be33675b5a/packages/tui/CHANGELOG.md).
|
package/README.md
CHANGED
|
@@ -493,7 +493,7 @@ const spacer = new Spacer(2); // 2 empty lines (default: 1)
|
|
|
493
493
|
|
|
494
494
|
### Image
|
|
495
495
|
|
|
496
|
-
Renders images inline for terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm, and Warp on macOS/Linux), iTerm2 inline images or SIXEL. Falls back to a text placeholder on unsupported terminals.
|
|
496
|
+
Renders images inline for terminals that support the Kitty graphics protocol (including Kitty, Ghostty, WezTerm, Rex, and Warp on macOS/Linux), iTerm2 inline images or SIXEL. Falls back to a text placeholder on unsupported terminals.
|
|
497
497
|
|
|
498
498
|
```typescript
|
|
499
499
|
interface ImageTheme {
|
|
@@ -8,6 +8,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
8
8
|
import { type Component } from "../tui.js";
|
|
9
9
|
import { type FileMentionMessage } from "./messages.js";
|
|
10
10
|
import { ToolActivityContainer } from "../chrome/tool-activity.js";
|
|
11
|
+
import type { MessageRenderer } from "./extension-types.js";
|
|
11
12
|
import { type TranscriptBlock } from "../chrome/transcript-container.js";
|
|
12
13
|
type CustomOrHookMessage = Extract<AgentMessage, {
|
|
13
14
|
role: "custom" | "hookMessage";
|
|
@@ -21,6 +22,13 @@ type AssistantAgentMessage = Extract<AgentMessage, {
|
|
|
21
22
|
* row per job.
|
|
22
23
|
*/
|
|
23
24
|
export declare function buildAsyncResultBlock(message: CustomOrHookMessage): ToolActivityContainer;
|
|
25
|
+
/**
|
|
26
|
+
* The `async-result` card for both transcript surfaces: a registered renderer owns it (#14131);
|
|
27
|
+
* the built-in block covers no renderer, a declining renderer, and a throwing one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildAsyncResultComponent(message: CustomOrHookMessage, renderer: MessageRenderer | undefined): Component & {
|
|
30
|
+
setExpanded(expanded: boolean): void;
|
|
31
|
+
};
|
|
24
32
|
/**
|
|
25
33
|
* Render a `launch-completion` custom message (terminal supervised-process
|
|
26
34
|
* exits from the launch broker) as a transcript block of one compact
|
|
@@ -3,10 +3,10 @@ import type { NativeNode } from "../native/node.js";
|
|
|
3
3
|
import { type ThemeColor } from "../theme/index.js";
|
|
4
4
|
import { TranscriptBlock } from "./transcript-container.js";
|
|
5
5
|
import { type NoticeTone } from "./transcript-notice.js";
|
|
6
|
-
/** One
|
|
6
|
+
/** One run of a status row's terminal line, in `color` unless the text is already styled. */
|
|
7
7
|
export interface TranscriptStatusPart {
|
|
8
8
|
readonly text: string;
|
|
9
|
-
readonly color
|
|
9
|
+
readonly color?: ThemeColor;
|
|
10
10
|
}
|
|
11
11
|
/** A status row in a native terminal: its {@link noticeRow}'s tone, icon and message. */
|
|
12
12
|
export interface TranscriptStatusNative {
|
|
@@ -85,4 +85,6 @@ export declare class HookEditorComponent extends OverlayPanel implements Focusab
|
|
|
85
85
|
* enhanced-paste routing falls back to the main prompt editor hidden behind the
|
|
86
86
|
* dialog (#2127 routing contract). */
|
|
87
87
|
pasteText(text: string): void;
|
|
88
|
+
/** Replace the draft, as Ctrl+R history search does for the main composer. */
|
|
89
|
+
setText(text: string): void;
|
|
88
90
|
}
|
|
@@ -8,6 +8,10 @@ export interface JobsPanelJob {
|
|
|
8
8
|
readonly type: string;
|
|
9
9
|
readonly status: "running" | "completed" | "failed" | "cancelled";
|
|
10
10
|
readonly label: string;
|
|
11
|
+
/** What the job is for, in the model's words; rows and the detail lead with it over a command `label`. */
|
|
12
|
+
readonly intent?: string;
|
|
13
|
+
/** One line on what a command does and when it ends, from the cheap model; the detail shows it under the intent. */
|
|
14
|
+
readonly caption?: string;
|
|
11
15
|
readonly startTime: number;
|
|
12
16
|
readonly endTime?: number;
|
|
13
17
|
/** Registry id of the agent that owns the job. */
|
|
@@ -190,6 +190,23 @@ export declare function formatErrorMessage(message: string | undefined, theme: T
|
|
|
190
190
|
export declare function formatErrorDetail(message: string | undefined, theme: Theme): string;
|
|
191
191
|
/** Render a muted empty-state message with a warning icon. */
|
|
192
192
|
export declare function formatEmptyMessage(message: string, theme: Theme): string;
|
|
193
|
+
/**
|
|
194
|
+
* Color of a file's name, or a whole URL, in tool bodies: it keeps paths apart
|
|
195
|
+
* from the tool titles above them (bronze in titanium).
|
|
196
|
+
*/
|
|
197
|
+
export declare const FILE_NAME_COLOR = "customMessageLabel";
|
|
198
|
+
/**
|
|
199
|
+
* Where a display path's final segment (file or folder name) starts, past its
|
|
200
|
+
* last `/` or `\` separator; a trailing-slash directory keeps its slash on the
|
|
201
|
+
* name. Shared by the terminal ({@link formatStyledPath}) and Tern file rows.
|
|
202
|
+
*/
|
|
203
|
+
export declare function pathNameStart(filePath: string): number;
|
|
204
|
+
/**
|
|
205
|
+
* A file path in a tool body: the directory (with its trailing separator) dim,
|
|
206
|
+
* the final segment in {@link FILE_NAME_COLOR}. Tool headers keep paths in
|
|
207
|
+
* their plain subject color instead.
|
|
208
|
+
*/
|
|
209
|
+
export declare function formatStyledPath(filePath: string, theme: Theme): string;
|
|
193
210
|
/** Marker displayed in the gutter of a code frame. */
|
|
194
211
|
export type CodeFrameMarker = "" | " " | "*" | "+" | "-" | ">";
|
|
195
212
|
/** Format a code-frame row with a padded line-number gutter. */
|
|
@@ -18,7 +18,7 @@ export declare enum NotifyProtocol {
|
|
|
18
18
|
Osc99 = "\u001B]99;;",
|
|
19
19
|
Osc9 = "\u001B]9;"
|
|
20
20
|
}
|
|
21
|
-
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "monstar" | "base" | "trueColor";
|
|
21
|
+
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "monstar" | "rex" | "base" | "trueColor";
|
|
22
22
|
/** Terminal capability details used for rendering and protocol selection. */
|
|
23
23
|
export declare class TerminalInfo {
|
|
24
24
|
readonly id: TerminalId;
|
|
@@ -52,22 +52,35 @@ export interface FileMatchSection {
|
|
|
52
52
|
* Files with no model lines are omitted.
|
|
53
53
|
*/
|
|
54
54
|
export declare function formatFileMatches(files: string[], grouped: boolean, renderFile: (filePath: string) => FileMatchSection): GroupedFilesOutput;
|
|
55
|
-
/**
|
|
55
|
+
/** Whether a row of grouped output is a code-frame row the search matched (`*12│…`). */
|
|
56
|
+
export declare function isMatchRow(line: string): boolean;
|
|
57
|
+
/** Per-line classification of grouped output, used by renderers for file rows and hyperlinks. */
|
|
56
58
|
export interface GroupedLineContext {
|
|
57
59
|
/** Directory header, file header, or any non-header body/content line. */
|
|
58
60
|
kind: "dir" | "file" | "content";
|
|
59
61
|
/** Number of leading `#` for headers; 0 for content lines. */
|
|
60
62
|
depth: number;
|
|
61
|
-
/** Resolved absolute path of the
|
|
63
|
+
/** Resolved absolute path of the file a header points at (when resolvable). */
|
|
62
64
|
headerPath?: string;
|
|
65
|
+
/**
|
|
66
|
+
* For file headers, the full display path: the folded directory chain joined
|
|
67
|
+
* with the name, without `#` markers or the hashline tag.
|
|
68
|
+
*/
|
|
69
|
+
displayPath?: string;
|
|
70
|
+
/** For file headers, a trailing parenthesized note such as `(2 replacements)`. */
|
|
71
|
+
suffix?: string;
|
|
63
72
|
/** For content lines, the absolute path of the owning file (line hyperlinks). */
|
|
64
73
|
filePath?: string;
|
|
65
|
-
/**
|
|
66
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The internal or url-like target, as written, that a header names or a
|
|
76
|
+
* content line belongs to; the caller resolves it (no filesystem path).
|
|
77
|
+
*/
|
|
78
|
+
url?: string;
|
|
67
79
|
}
|
|
68
80
|
/**
|
|
69
81
|
* Walk grouped output lines, tracking a directory stack keyed by header depth, so
|
|
70
|
-
* each header and body line can be linked back to its absolute filesystem path
|
|
82
|
+
* each header and body line can be linked back to its absolute filesystem path
|
|
83
|
+
* and each file header to its full display path.
|
|
71
84
|
* Reconstruction is stack-based (not per-blank-group) so nested directory headers
|
|
72
85
|
* resolve correctly across the whole output.
|
|
73
86
|
*
|
|
@@ -76,9 +89,64 @@ export interface GroupedLineContext {
|
|
|
76
89
|
* formatted relative to cwd regardless of the (sub)directory the search was
|
|
77
90
|
* scoped to. `fileScope` is the initial owning file for body lines that appear
|
|
78
91
|
* before any header (single-file scopes have no `#` headers); it defaults to
|
|
79
|
-
* `headerBase` and should be passed the scoped file's absolute path.
|
|
92
|
+
* `headerBase` and should be passed the scoped file's absolute path. A URL base
|
|
93
|
+
* or scope (`local://…`) is no filesystem path: headerless lines belong to the
|
|
94
|
+
* URL, and headers under a URL base get no path.
|
|
80
95
|
*/
|
|
81
96
|
export declare function classifyGroupedLines(lines: readonly string[], headerBase: string | undefined, fileScope?: string | undefined): GroupedLineContext[];
|
|
97
|
+
/**
|
|
98
|
+
* Line groups with one file per group, for terminal tree lists: `# dir/`
|
|
99
|
+
* header rows are dropped (each file row carries its full path), and every
|
|
100
|
+
* file row starts a group of its own, so each file gets its own branch instead
|
|
101
|
+
* of hanging under the previous file of its directory. Rows before a group's
|
|
102
|
+
* first file row (notes, a single-file scope's matches) keep their group.
|
|
103
|
+
*/
|
|
104
|
+
export declare function groupsByFile(groups: readonly (readonly number[])[], contexts: readonly GroupedLineContext[]): number[][];
|
|
105
|
+
/** Details fields a grouped-output tool records for links: the display base paths and resolved URL targets. */
|
|
106
|
+
export interface GroupedLinkDetails {
|
|
107
|
+
cwd?: string;
|
|
108
|
+
searchPath?: string;
|
|
109
|
+
displayTargets?: Record<string, string>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Link targets for the rows of sanitized grouped output: a file row's file, a
|
|
113
|
+
* body row's owning file, and for an internal URL the file `displayTargets`
|
|
114
|
+
* resolves it to. Destinations keep the original path bytes: while the raw
|
|
115
|
+
* `displayContent` still aligns line-for-line with the sanitized `displayText`,
|
|
116
|
+
* a row takes its raw classification's target. Raw text never links a row the
|
|
117
|
+
* sanitized output leaves unlinked.
|
|
118
|
+
*/
|
|
119
|
+
export declare function groupedLinkTargets(contexts: readonly GroupedLineContext[], displayText: string, details: GroupedLinkDetails | undefined, rawDetails: unknown): (index: number) => string | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* A memoized styler for the rows of grouped output, so a collapsed preview
|
|
122
|
+
* styles only the rows it shows. A file row is its full path (dim directory,
|
|
123
|
+
* name in {@link FILE_NAME_COLOR}; a URL whole in that color) and any dim
|
|
124
|
+
* suffix, linked to `linkTarget`'s file or, for an unresolved URL, the URL.
|
|
125
|
+
* Body rows come from `styleBody`, tabs expanded; with `linkLines` a numbered
|
|
126
|
+
* code-frame row links to its line in the owning file.
|
|
127
|
+
*/
|
|
128
|
+
export declare function createGroupedRowStyler(lines: readonly string[], contexts: readonly GroupedLineContext[], theme: Theme, options: {
|
|
129
|
+
linkTarget: (index: number) => string | undefined;
|
|
130
|
+
styleBody: (index: number) => string;
|
|
131
|
+
linkLines?: boolean;
|
|
132
|
+
}): (index: number) => string;
|
|
133
|
+
/**
|
|
134
|
+
* Fit line groups (one file each) into `budget` rows, the `… N more` summary row
|
|
135
|
+
* included: whole groups first, then the next group cut short when it can still
|
|
136
|
+
* show a row under its file row (the first group always shows its head). The
|
|
137
|
+
* summary counts the `unit`s left out, the rows `isUnit` accepts, out of `total`
|
|
138
|
+
* when the caller knows it; with none left out it counts hidden lines. `gap`
|
|
139
|
+
* rows separate shown groups (framed lists put a blank line between files).
|
|
140
|
+
*/
|
|
141
|
+
export declare function fitGroupsToBudget(groups: readonly (readonly number[])[], budget: number, options: {
|
|
142
|
+
isUnit: (index: number) => boolean;
|
|
143
|
+
unit: string;
|
|
144
|
+
total?: number;
|
|
145
|
+
gap?: number;
|
|
146
|
+
}): {
|
|
147
|
+
shown: (readonly number[])[];
|
|
148
|
+
summary: string;
|
|
149
|
+
};
|
|
82
150
|
/**
|
|
83
151
|
* Split line indices into blank-line-separated groups, mirroring
|
|
84
152
|
* `splitGroupsByBlankLine`: when any blank line is present, break on runs of
|
|
@@ -108,8 +176,8 @@ export type GroupedOutputEvent = {
|
|
|
108
176
|
};
|
|
109
177
|
/**
|
|
110
178
|
* Walk grouped output (`# dir/`, `## file.ts (suffix)`, body lines), resolving
|
|
111
|
-
* each file header to its full display path (
|
|
112
|
-
*
|
|
179
|
+
* each file header to its full display path (see {@link classifyGroupedLines})
|
|
180
|
+
* and dropping blank separator lines.
|
|
113
181
|
*/
|
|
114
182
|
export declare function walkGroupedOutput(lines: readonly string[]): Generator<GroupedOutputEvent>;
|
|
115
183
|
/** One source line for {@link numberedCode}: its number (`null` for an elision marker, which adds no line), text and match mark. */
|
|
@@ -6,6 +6,11 @@ export declare const HL_FILE_SUFFIX = "]";
|
|
|
6
6
|
export declare const HL_FILE_HASH_SEP = "#";
|
|
7
7
|
/** Number of characters in a displayed file hash. */
|
|
8
8
|
export declare const HL_FILE_HASH_LENGTH = 4;
|
|
9
|
+
/**
|
|
10
|
+
* A trailing `#TAG` file hash exactly as the hashline store writes it (uppercase
|
|
11
|
+
* hex), so a name's own `#dead` or `#v2` ending survives stripping.
|
|
12
|
+
*/
|
|
13
|
+
export declare const HASHLINE_TAG_SUFFIX_RE: RegExp;
|
|
9
14
|
/** Keyword identifying a file move operation. */
|
|
10
15
|
export declare const HL_MOVE_KEYWORD = "MV";
|
|
11
16
|
/** Keyword identifying a file removal operation. */
|
|
@@ -8,6 +8,8 @@ import type { NativeToolHead, NativeToolView, ToolRenderResult } from "./rendere
|
|
|
8
8
|
export declare function displayPath(filePath: string): string;
|
|
9
9
|
/** Configured file link for a head's path target, when the path is absolute. */
|
|
10
10
|
export declare function fileHref(filePath: string | undefined): string | undefined;
|
|
11
|
+
/** Link for a head target that is a web URL (`www.` gains `https://`), else undefined. */
|
|
12
|
+
export declare function urlHref(target: string | undefined): string | undefined;
|
|
11
13
|
/** Diff stats as one head `meta` string, `+8 −1` (U+2212 minus), or undefined when nothing changed. */
|
|
12
14
|
export declare function diffStatsMeta(added: number, removed: number): string | undefined;
|
|
13
15
|
/**
|
|
@@ -111,6 +111,16 @@ export declare function splitInternalUrlSel(rawPath: string, specOf?: (scheme: s
|
|
|
111
111
|
path: string;
|
|
112
112
|
sel?: string;
|
|
113
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Split a filesystem read target from its query (`data.json?q=.items` →
|
|
116
|
+
* `data.json`, `?q=.items`), the display twin of the read tool's JSON, SQLite
|
|
117
|
+
* and image-question parsers. URLs own their query string and stay whole, as
|
|
118
|
+
* does a `?` inside a file name.
|
|
119
|
+
*/
|
|
120
|
+
export declare function splitReadQuery(readPath: string): {
|
|
121
|
+
path: string;
|
|
122
|
+
query?: string;
|
|
123
|
+
};
|
|
114
124
|
/** Recognize HTTP URLs and www-prefixed external read targets. */
|
|
115
125
|
export declare function isReadableUrlPath(value: string): boolean;
|
|
116
126
|
/** Return the first selected line when every range has valid one-based bounds. */
|
|
@@ -17,6 +17,8 @@ export interface JobSnapshot {
|
|
|
17
17
|
type: "bash" | "task" | "eval";
|
|
18
18
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
19
19
|
label: string;
|
|
20
|
+
/** What the job is for, in the model's words (a process job's `label` is its command line). */
|
|
21
|
+
intent?: string;
|
|
20
22
|
durationMs: number;
|
|
21
23
|
/** Process exit status when the job reports one. */
|
|
22
24
|
exitCode?: number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.15.0",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.
|
|
44
|
+
"@oh-my-pi/omptype": "18.15.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.15.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.15.0",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.15.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.15.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.15.0",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.15.0",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.15.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
@@ -37,7 +37,7 @@ import { theme } from "../theme";
|
|
|
37
37
|
import {
|
|
38
38
|
assistantHasVisibleContent,
|
|
39
39
|
assistantUsageIsBilled,
|
|
40
|
-
|
|
40
|
+
buildAsyncResultComponent,
|
|
41
41
|
buildFileMentionBlock,
|
|
42
42
|
buildIrcMessageCard,
|
|
43
43
|
buildLaunchCompletionBlock,
|
|
@@ -599,7 +599,9 @@ export class ChatTranscriptBuilder {
|
|
|
599
599
|
#appendCustomMessage(message: Extract<AgentMessage, { role: "custom" | "hookMessage" }>): void {
|
|
600
600
|
if (!message.display) return;
|
|
601
601
|
if (message.customType === "async-result") {
|
|
602
|
-
const
|
|
602
|
+
const renderer = this.#deps.getMessageRenderer?.(message.customType);
|
|
603
|
+
const component = buildAsyncResultComponent(message, renderer);
|
|
604
|
+
this.#trackExpandable(component);
|
|
603
605
|
this.container.addChild(component);
|
|
604
606
|
return;
|
|
605
607
|
}
|
|
@@ -3,7 +3,7 @@ import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import { type Component } from "../tui";
|
|
4
4
|
import { Container } from "../tui";
|
|
5
5
|
import { Text } from "../components/text";
|
|
6
|
-
import { getLanguageFromPath, theme } from "../theme";
|
|
6
|
+
import { getLanguageFromPath, theme, type ThemeColor } from "../theme";
|
|
7
7
|
import { parseLineRanges, selectorLineRanges } from "../tools/line-ranges";
|
|
8
8
|
import {
|
|
9
9
|
type ReadRenderArgs,
|
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
readResultIsMarkdown,
|
|
13
13
|
readSourceFsPath,
|
|
14
14
|
splitPathAndSel,
|
|
15
|
+
splitReadQuery,
|
|
15
16
|
} from "../tools/read";
|
|
16
|
-
import { PREVIEW_LIMITS, shortenPath } from "../render/render-utils";
|
|
17
|
+
import { FILE_NAME_COLOR, formatStyledPath, PREVIEW_LIMITS, shortenPath } from "../render/render-utils";
|
|
17
18
|
import { fileHyperlink, renderCodeCell, renderMarkdownCell } from "../render";
|
|
18
19
|
import { wrapTreeLine } from "../render/hanging-wrap";
|
|
19
20
|
import { wrapTextWithAnsi } from "../utils";
|
|
@@ -21,7 +22,7 @@ import { canonicalizeMessage } from "./thinking-display";
|
|
|
21
22
|
import { internalUrlSchemeSpec, splitUrlScheme } from "../tools/url-scheme-host";
|
|
22
23
|
import type { ToolExecutionHandle } from "./tool-execution";
|
|
23
24
|
import { formatUsageRow } from "../overlays/usage-row";
|
|
24
|
-
import { formatCount, sanitizeText, sanitizeTextDeep } from "@oh-my-pi/pi-utils";
|
|
25
|
+
import { formatCount, isUrlLikePath, sanitizeText, sanitizeTextDeep } from "@oh-my-pi/pi-utils";
|
|
25
26
|
import type { TspCardStatus, TspSpan, TspText } from "@oh-my-pi/pi-wire";
|
|
26
27
|
import type { NativeToolHead } from "../tools/renderer";
|
|
27
28
|
import { card, code, keyed, md, node, span, text, withHidden } from "../native/describe";
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
} from "../native/node";
|
|
35
36
|
import { plainText } from "../native/spans";
|
|
36
37
|
import { Memo } from "../native/memo";
|
|
37
|
-
import { fileHref, fileRow, inlineErrorView } from "../tools/native-view";
|
|
38
|
+
import { fileHref, fileRow, inlineErrorView, urlHref } from "../tools/native-view";
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Extract the read call's target path. `path` is the canonical arg; `file_path`
|
|
@@ -158,6 +159,21 @@ const COLLAPSED_PREVIEW_LINES = PREVIEW_LIMITS.OUTPUT_COLLAPSED;
|
|
|
158
159
|
*/
|
|
159
160
|
const TRIMMED_BODY_PREVIEW = { lines: 1000 } as const;
|
|
160
161
|
|
|
162
|
+
/** How a read path is painted: its path (`isUrl` for a whole URL) and its selectors' color. */
|
|
163
|
+
interface PathPaint {
|
|
164
|
+
path(filePath: string, isUrl: boolean): string;
|
|
165
|
+
selectors: ThemeColor;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** A `Read <path>` header line keeps the accent subject color. */
|
|
169
|
+
const HEADER_PATH_PAINT: PathPaint = { path: filePath => theme.fg("accent", filePath), selectors: "accent" };
|
|
170
|
+
|
|
171
|
+
/** Group rows share the tool-body path style with muted selectors. */
|
|
172
|
+
const ROW_PATH_PAINT: PathPaint = {
|
|
173
|
+
path: (filePath, isUrl) => (isUrl ? theme.fg(FILE_NAME_COLOR, filePath) : formatStyledPath(filePath, theme)),
|
|
174
|
+
selectors: "muted",
|
|
175
|
+
};
|
|
176
|
+
|
|
161
177
|
/** Native item tone per read status; successful reads stay neutral. */
|
|
162
178
|
const READ_STATUS_TONE: Record<ReadEntry["status"], "pending" | "warning" | "error" | undefined> = {
|
|
163
179
|
success: undefined,
|
|
@@ -695,7 +711,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
695
711
|
return {
|
|
696
712
|
target: filePath ? `${filePath}${split.sel ? `:${split.sel}` : ""}` : "…",
|
|
697
713
|
targetKind: "path",
|
|
698
|
-
href: fileHref(linkPathForTargets(row.targets)),
|
|
714
|
+
href: fileHref(linkPathForTargets(row.targets)) ?? urlHref(split.path),
|
|
699
715
|
meta: correctedFrom ? [`corrected from ${shortenPath(correctedFrom)}`] : undefined,
|
|
700
716
|
badges: conflicts
|
|
701
717
|
? [{ text: `${conflicts} conflict${conflicts === 1 ? "" : "s"}`, tone: "warning" }]
|
|
@@ -718,7 +734,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
718
734
|
: undefined;
|
|
719
735
|
return fileRow(shortenPath(split.sel ? split.path : row.targetPath), {
|
|
720
736
|
range: split.sel ? `:${split.sel}` : undefined,
|
|
721
|
-
href: fileHref(linkPathForTargets(row.targets)),
|
|
737
|
+
href: fileHref(linkPathForTargets(row.targets)) ?? urlHref(split.path),
|
|
722
738
|
icon: status === "error" ? "x-circle" : undefined,
|
|
723
739
|
tone: READ_STATUS_TONE[status],
|
|
724
740
|
chip: conflicts ? { text: `${conflicts} conflict${conflicts === 1 ? "" : "s"}` } : undefined,
|
|
@@ -915,7 +931,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
915
931
|
const row = displayRows[0]!;
|
|
916
932
|
if (!this.#shouldRenderPreviewRow(row)) {
|
|
917
933
|
const statusSymbol = this.#formatStatus(this.#statusForTargets(row.targets));
|
|
918
|
-
const pathDisplay = this.#formatRowPath(row);
|
|
934
|
+
const pathDisplay = this.#formatRowPath(row, HEADER_PATH_PAINT);
|
|
919
935
|
const lines = [` ${statusSymbol} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay}`.trimEnd()];
|
|
920
936
|
const usageRows = this.#usageRowsBySummaryRow(displayRows).get(0) ?? [];
|
|
921
937
|
this.#appendUsageRows(lines, usageRows, " ");
|
|
@@ -1084,12 +1100,13 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
1084
1100
|
return `${statusPrefix}${this.#formatRowPath(row)}`;
|
|
1085
1101
|
}
|
|
1086
1102
|
|
|
1087
|
-
#formatRowPath(row: ReadSummaryRow): string {
|
|
1088
|
-
return this.#formatPathValue(row.targetPath, {
|
|
1103
|
+
#formatRowPath(row: ReadSummaryRow, paint = ROW_PATH_PAINT): string {
|
|
1104
|
+
return this.#formatPathValue(row.targetPath, paint, {
|
|
1089
1105
|
correctedFrom: this.#correctedFromForTargets(row.targets),
|
|
1090
1106
|
conflictCount: this.#conflictCountForTargets(row.targets),
|
|
1091
1107
|
line: firstSelectorLineForTargets(row.targets),
|
|
1092
1108
|
linkPath: linkPathForTargets(row.targets),
|
|
1109
|
+
basePath: row.basePath,
|
|
1093
1110
|
});
|
|
1094
1111
|
}
|
|
1095
1112
|
|
|
@@ -1137,25 +1154,35 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
1137
1154
|
|
|
1138
1155
|
#formatPathValue(
|
|
1139
1156
|
value: string,
|
|
1140
|
-
|
|
1157
|
+
paint: PathPaint,
|
|
1158
|
+
options: {
|
|
1159
|
+
correctedFrom?: string;
|
|
1160
|
+
conflictCount?: number;
|
|
1161
|
+
line?: number;
|
|
1162
|
+
linkPath?: string;
|
|
1163
|
+
/** The path `value` selects from; merged selectors (`1-5,…,9`) do not parse back out of it. */
|
|
1164
|
+
basePath?: string;
|
|
1165
|
+
} = {},
|
|
1141
1166
|
): string {
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1167
|
+
const { basePath } = options;
|
|
1168
|
+
const split =
|
|
1169
|
+
basePath !== undefined && value.startsWith(`${basePath}:`)
|
|
1170
|
+
? { path: basePath, sel: value.slice(basePath.length + 1) }
|
|
1171
|
+
: splitPathAndSel(value);
|
|
1172
|
+
// `?q=…` queries read like selectors: they follow the path, outside its link.
|
|
1173
|
+
const target = splitReadQuery(split.sel ? split.path : value);
|
|
1174
|
+
const filePath = shortenPath(target.path);
|
|
1175
|
+
const selectors = split.sel ? `${target.query ?? ""}:${split.sel}` : (target.query ?? "");
|
|
1176
|
+
let pathDisplay = filePath ? paint.path(filePath, isUrlLikePath(target.path)) : theme.fg("toolOutput", "…");
|
|
1147
1177
|
if (filePath && options.linkPath) {
|
|
1148
1178
|
const linkOptions = options.line !== undefined ? { line: options.line } : undefined;
|
|
1149
1179
|
pathDisplay = fileHyperlink(options.linkPath, pathDisplay, linkOptions);
|
|
1150
1180
|
}
|
|
1151
|
-
if (
|
|
1152
|
-
pathDisplay += theme.fg("accent", selectorSuffix);
|
|
1153
|
-
}
|
|
1181
|
+
if (selectors) pathDisplay += theme.fg(paint.selectors, selectors);
|
|
1154
1182
|
if (options.correctedFrom) {
|
|
1155
1183
|
pathDisplay += theme.fg("dim", ` (corrected from ${shortenPath(options.correctedFrom)})`);
|
|
1156
1184
|
}
|
|
1157
|
-
pathDisplay
|
|
1158
|
-
return pathDisplay;
|
|
1185
|
+
return pathDisplay + this.#formatConflictBadge(options.conflictCount);
|
|
1159
1186
|
}
|
|
1160
1187
|
|
|
1161
1188
|
#formatConflictBadge(conflictCount: number | undefined): string {
|
|
@@ -1174,7 +1201,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
1174
1201
|
const lang = getLanguageFromPath(split.path);
|
|
1175
1202
|
const pathValue = shortenPath(entry.path);
|
|
1176
1203
|
const pathDisplay = pathValue
|
|
1177
|
-
? this.#formatPathValue(entry.path, {
|
|
1204
|
+
? this.#formatPathValue(entry.path, HEADER_PATH_PAINT, {
|
|
1178
1205
|
correctedFrom: entry.correctedFrom,
|
|
1179
1206
|
conflictCount: entry.conflictCount,
|
|
1180
1207
|
line: firstSelectorLine(split.sel),
|
|
@@ -15,9 +15,11 @@ import { type CustomMessage, type FileMentionMessage, resolveAbortLabel, shouldR
|
|
|
15
15
|
import { createIrcMessageCard } from "../tools/wait";
|
|
16
16
|
import { formatArtifactErrorNotice, type OutputMeta } from "../tools/output-meta";
|
|
17
17
|
import type { OutputArtifactError } from "../tools/streaming-output";
|
|
18
|
-
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../render/render-utils";
|
|
18
|
+
import { formatStyledPath, replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../render/render-utils";
|
|
19
19
|
import { canonicalizeMessage } from "./thinking-display";
|
|
20
20
|
import { ToolActivityContainer } from "../chrome/tool-activity";
|
|
21
|
+
import { CustomMessageComponent } from "./custom-message";
|
|
22
|
+
import type { MessageRenderer } from "./extension-types";
|
|
21
23
|
import { type TranscriptBlock } from "../chrome/transcript-container";
|
|
22
24
|
import { TranscriptStatusBlock, type TranscriptStatusRow } from "../chrome/transcript-status";
|
|
23
25
|
import { theme } from "../theme";
|
|
@@ -81,6 +83,38 @@ export function buildAsyncResultBlock(message: CustomOrHookMessage): ToolActivit
|
|
|
81
83
|
return new ToolActivityContainer(new TranscriptStatusBlock(rows));
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
/** Hosts an extension renderer; relays `display.hideToolActivity` to the nested built-in fallback block. */
|
|
87
|
+
class AsyncResultCard extends CustomMessageComponent {
|
|
88
|
+
readonly #block: ToolActivityContainer;
|
|
89
|
+
|
|
90
|
+
constructor(message: CustomOrHookMessage, renderer: MessageRenderer, block: ToolActivityContainer) {
|
|
91
|
+
super(message as CustomMessage<unknown>, (delegate, options, rendererTheme) => {
|
|
92
|
+
try {
|
|
93
|
+
return renderer(delegate, options, rendererTheme) ?? block;
|
|
94
|
+
} catch {
|
|
95
|
+
return block;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
this.#block = block;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setToolActivityVisible(visible: boolean): void {
|
|
102
|
+
this.#block.setToolActivityVisible(visible);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The `async-result` card for both transcript surfaces: a registered renderer owns it (#14131);
|
|
108
|
+
* the built-in block covers no renderer, a declining renderer, and a throwing one.
|
|
109
|
+
*/
|
|
110
|
+
export function buildAsyncResultComponent(
|
|
111
|
+
message: CustomOrHookMessage,
|
|
112
|
+
renderer: MessageRenderer | undefined,
|
|
113
|
+
): Component & { setExpanded(expanded: boolean): void } {
|
|
114
|
+
const block = buildAsyncResultBlock(message);
|
|
115
|
+
return renderer ? new AsyncResultCard(message, renderer, block) : block;
|
|
116
|
+
}
|
|
117
|
+
|
|
84
118
|
/** The warning row under a background job whose output artifact could not be saved. */
|
|
85
119
|
function artifactErrorRow(error: OutputArtifactError): TranscriptStatusRow {
|
|
86
120
|
const notice = formatArtifactErrorNotice(error);
|
|
@@ -203,7 +237,7 @@ export function buildFileMentionBlock(files: FileMentionMessage["files"], indent
|
|
|
203
237
|
parts: [
|
|
204
238
|
{ text: theme.tree.last, color: "dim" },
|
|
205
239
|
{ text: "Read", color: "muted" },
|
|
206
|
-
{ text: file.path,
|
|
240
|
+
{ text: formatStyledPath(file.path, theme) },
|
|
207
241
|
{ text: `(${detail})`, color: "dim" },
|
|
208
242
|
],
|
|
209
243
|
indent,
|