@oh-my-pi/pi-tui 18.14.0 → 18.14.2

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 (58) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +6 -0
  3. package/dist/types/charts/table-data.d.ts +4 -2
  4. package/dist/types/chat/figure-markdown.d.ts +2 -0
  5. package/dist/types/chat/svg-figure.d.ts +5 -0
  6. package/dist/types/chat/table-chart.d.ts +2 -0
  7. package/dist/types/chat/transcript-render-helpers.d.ts +3 -3
  8. package/dist/types/components/image.d.ts +6 -0
  9. package/dist/types/native/describe.d.ts +4 -2
  10. package/dist/types/native/overlay.d.ts +42 -2
  11. package/dist/types/native/state.d.ts +4 -0
  12. package/dist/types/overlays/btw-history-panel.d.ts +2 -0
  13. package/dist/types/prompt/composer.d.ts +13 -5
  14. package/dist/types/render/render-utils.d.ts +2 -0
  15. package/dist/types/theme/theme.d.ts +1 -1
  16. package/dist/types/theme/tui-adapters.d.ts +2 -0
  17. package/dist/types/thinking.d.ts +8 -6
  18. package/dist/types/tools/ast-grep.d.ts +2 -0
  19. package/dist/types/tools/eval.d.ts +2 -0
  20. package/dist/types/tools/find.d.ts +1 -1
  21. package/dist/types/tools/grouped-file-output.d.ts +24 -11
  22. package/dist/types/tools/read.d.ts +4 -4
  23. package/dist/types/tools/task.d.ts +2 -2
  24. package/package.json +9 -9
  25. package/src/apps/git/git-tui.ts +10 -17
  26. package/src/apps/git/sidebar.ts +21 -35
  27. package/src/apps/ps-top.ts +4 -12
  28. package/src/charts/chart-plan.ts +0 -29
  29. package/src/charts/table-data.ts +55 -6
  30. package/src/chat/figure-markdown.ts +6 -4
  31. package/src/chat/svg-figure.ts +55 -16
  32. package/src/chat/table-chart.ts +19 -2
  33. package/src/chat/transcript-render-helpers.ts +18 -16
  34. package/src/components/image.ts +13 -0
  35. package/src/native/backend.ts +7 -3
  36. package/src/native/describe.ts +9 -4
  37. package/src/native/overlay.ts +124 -16
  38. package/src/native/state.ts +25 -5
  39. package/src/overlays/btw-history-panel.ts +8 -0
  40. package/src/overlays/extensions/extension-dashboard.ts +7 -8
  41. package/src/overlays/jobs-panel.ts +38 -4
  42. package/src/overlays/login-dialog.ts +8 -26
  43. package/src/overlays/tree-selector.ts +81 -27
  44. package/src/overlays/usage-dashboard.ts +2 -4
  45. package/src/prompt/composer.ts +17 -5
  46. package/src/render/render-utils.ts +2 -0
  47. package/src/theme/theme-class.ts +12 -3
  48. package/src/theme/theme.ts +1 -0
  49. package/src/theme/tui-adapters.ts +8 -3
  50. package/src/thinking.ts +17 -11
  51. package/src/tools/ast-grep.ts +10 -2
  52. package/src/tools/eval.ts +2 -0
  53. package/src/tools/find.ts +7 -2
  54. package/src/tools/grep.ts +8 -2
  55. package/src/tools/grouped-file-output.ts +172 -47
  56. package/src/tools/lsp.ts +158 -8
  57. package/src/tools/read.ts +6 -5
  58. package/src/tools/task.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.14.2] - 2026-10-31
6
+
7
+ ### Added
8
+
9
+ - Added `assertModelSupportsEffortCeiling()`, which throws when a model has no supported thinking effort at or below a `task.maxEffort` ceiling ([#12229](https://github.com/can1357/oh-my-pi/pull/12229) by [@Xytronix](https://github.com/Xytronix))
10
+ - Added `iconButton()`, `quietButton()` and `segmentedTabs()` to `native/overlay`, and `disabled`, `busy` and `icon` options to `actionButton()`
11
+ - Added `nativeTail` to `Composer.setRuntimeChildren` options: containers whose children continue the native surface's `main` after the transcript instead of riding in the dock
12
+ - Added `isHighlightLanguage` and `createCodeFrameStyler` for renderers that highlight source lines in their file's language ([#17550](https://github.com/can1357/oh-my-pi/pull/17550) by [@JustasMasiulis](https://github.com/JustasMasiulis))
13
+
14
+ ### Changed
15
+
16
+ - Grep and read results with gaps between line runs now render in Tern as one code block that scrolls sideways as a whole, with `…` gap rows aligned in the line-number gutter
17
+ - Tern builds that draw the `button` kind get omp's buttons as real buttons (pointer cursor, accessible pressed/disabled state, a disabled button's tooltip says why) and segmented controls as `tabs` `variant: "segmented"`; older Tern builds keep the role-styled rows
18
+ - Source lines in Grep, AST Grep and Find results and LSP symbol names are syntax-highlighted in their file's language instead of drawn in one color; consecutive lines highlight together, so multi-line strings and comments keep their colors ([#17550](https://github.com/can1357/oh-my-pi/pull/17550) by [@JustasMasiulis](https://github.com/JustasMasiulis))
19
+
20
+ ### Fixed
21
+
22
+ - Fixed table charts plotting a ratio column against the timings it divides (`ratio` 0.997 beside `C++ (ms)` drew as "719× less"): `analyzeTable` reads a column equal to the quotient of two others as restating them, written bare or as `×`
23
+ - Fixed IRC coordination cards remaining visible when Hide Tool Activity is enabled, including incoming messages, automatic replies, relays, and workpool messages in live and replayed transcripts ([#17498](https://github.com/can1357/oh-my-pi/pull/17498) by [@dannyboy-ai](https://github.com/dannyboy-ai)).
24
+ - Fixed text after a nested color losing the outer color, such as the rest of the `/mcp` and `/ssh` hints after the highlighted command (`Run /mcp list to see all configured servers.`) falling back to the default text color instead of staying muted ([#17521](https://github.com/can1357/oh-my-pi/pull/17521) by [@JustasMasiulis](https://github.com/JustasMasiulis))
25
+ - Fixed LSP definition, type definition and implementation results showing the raw `Found 1 definition(s):` text; the response now counts them (`1 definition`) and shows each location over its source lines, its own line marked like a grep match ([#17549](https://github.com/can1357/oh-my-pi/pull/17549) by [@JustasMasiulis](https://github.com/JustasMasiulis))
26
+ - Fixed an LSP definition, type definition or implementation location whose path starts with whitespace being dropped, with its source lines shown under the previous location
27
+ - Fixed session tree user and assistant turns looking identical in themes whose `accent` and `success` colors match or nearly match (`dark-terminal`, `dark-poimandres`, `light-poimandres`): user rows are now bold, the `assistant:` label switches to the path color, and custom and advisor messages are muted ([#17520](https://github.com/can1357/oh-my-pi/pull/17520) by [@wolfiesch](https://github.com/wolfiesch))
28
+
29
+ ## [18.14.1] - 2026-10-31
30
+
31
+ ### Added
32
+
33
+ - Added a `maxRows` callback to `FigureMarkdown`, `SvgFigure` and `TableChartFigure` so surfaces shorter than the screen can fit their figures; zero hides a figure without delaying transcript completion ([#15661](https://github.com/can1357/oh-my-pi/issues/15661), [#15853](https://github.com/can1357/oh-my-pi/pull/15853) by [@KunoLu](https://github.com/KunoLu))
34
+
35
+ ### Fixed
36
+
37
+ - Fixed IME composition text in the BTW history `Follow up:` input appearing at the left edge instead of at the caret when the hardware cursor is enabled ([#17427](https://github.com/can1357/oh-my-pi/issues/17427))
38
+ - Fixed session tree rows overlapping when messages, tool arguments, or labels contain carriage returns or Unicode line/paragraph separators (`U+2028`, `U+2029`), while keeping multiline formatting in the selected entry preview ([#17430](https://github.com/can1357/oh-my-pi/pull/17430) by [@lincicomb](https://github.com/lincicomb)).
39
+ - Fixed the Background jobs sheet dropping a finished job and its output while you were reading it; the selected job now stays until you select another ([#17506](https://github.com/can1357/oh-my-pi/pull/17506) by [@H4vC](https://github.com/H4vC))
40
+
5
41
  ## [18.14.0] - 2026-10-29
6
42
 
7
43
  ### Breaking Changes
package/README.md CHANGED
@@ -521,6 +521,12 @@ SIXEL images encode off the JavaScript thread: the first render reserves the ima
521
521
 
522
522
  Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
523
523
 
524
+ ### Figures
525
+
526
+ `FigureMarkdown`, `SvgFigure`, and `TableChartFigure` size their images to a share of the terminal viewport. A surface shorter than the screen (an overlay, a pane) passes `maxRows: () => rows`, read on every render: the rows each figure may fill, a table chart's separator included. Zero hides a figure and keeps it from holding up transcript completion. Callers reserve room for prose and chrome themselves.
527
+
528
+ Lowering the ceiling refits the drawn image at once and redraws it at the new size; a SIXEL reserves its rows while each re-encode runs off the JavaScript thread, as for a width change. Changing the ceiling of rows already committed to native scrollback forces a resync, so leave committed figures alone.
529
+
524
530
  ## Autocomplete
525
531
 
526
532
  ### CombinedAutocompleteProvider
@@ -101,8 +101,10 @@ export interface TableColumn {
101
101
  /**
102
102
  * Index of the measure column this one restates, read from the values: a
103
103
  * proportional copy (`pass` 33/38 beside `pass%` 87%, `Samples` beside `% of
104
- * Total`, `Leverage vs $200` beside the cost) or a running total of it
105
- * (`Offset` summing `Verts`). Such a column is never a measure of its own.
104
+ * Total`, `Leverage vs $200` beside the cost), a running total of it
105
+ * (`Offset` summing `Verts`), or its ratio to another column of its unit
106
+ * (`ratio` 0.997 for `Python (ms)` ÷ `C++ (ms)`, `Speedup` 4.1×: the
107
+ * numerator's index). Such a column is never a measure of its own.
106
108
  */
107
109
  readonly restates?: number;
108
110
  }
@@ -8,6 +8,8 @@ export interface FigureMarkdownOptions {
8
8
  charts: boolean;
9
9
  /** Shared inline-image budget for the figures. */
10
10
  budget?: ImageBudget;
11
+ /** Rows each figure may fill, read on every render; callers reserve prose, chrome and separators themselves. Unset or `undefined` takes a share of the terminal viewport. */
12
+ maxRows?: () => number | undefined;
11
13
  /** A figure's rows changed outside a text update (raster landed, fell back to code). */
12
14
  onChange: () => void;
13
15
  }
@@ -16,6 +16,11 @@ export interface SvgFigureOptions {
16
16
  markdown?: (text: string) => Markdown;
17
17
  /** Shared inline-image budget; superseded rasters are released from it. */
18
18
  budget?: ImageBudget;
19
+ /**
20
+ * Rows the figure may fill, fallback code included, read on every render;
21
+ * zero hides it. Unset or `undefined` takes a share of the terminal viewport.
22
+ */
23
+ maxRows?: () => number | undefined;
19
24
  /** The figure's rows changed outside a text update: a raster landed or rendering fell back to code. */
20
25
  onChange: () => void;
21
26
  }
@@ -64,6 +64,8 @@ export declare function chartFence(svg: string): string;
64
64
  export interface TableChartFigureOptions {
65
65
  /** Shared inline-image budget. */
66
66
  budget?: ImageBudget;
67
+ /** Rows the chart may fill, its separator included, read on every render; under two hides it. Unset or `undefined` takes a share of the terminal viewport. */
68
+ maxRows?: () => number | undefined;
67
69
  /** The chart's rows changed outside a text update: its pick or raster landed. */
68
70
  onChange: () => void;
69
71
  }
@@ -28,9 +28,9 @@ export declare function buildAsyncResultBlock(message: CustomOrHookMessage): Too
28
28
  */
29
29
  export declare function buildLaunchCompletionBlock(message: CustomOrHookMessage): ToolActivityContainer;
30
30
  /**
31
- * Render a live IRC traffic custom message (`irc:incoming` / `irc:autoreply` /
32
- * `irc:relay`) as a transcript card. `getExpanded` supplies the live
33
- * expanded-state getter for the cached card.
31
+ * Render an IRC coordination custom message (`irc:incoming` / `irc:autoreply` /
32
+ * `irc:relay` / `irc:workpool`) as a tool-activity card. `getExpanded` supplies
33
+ * the live expanded-state getter for the cached card.
34
34
  */
35
35
  export declare function buildIrcMessageCard(message: CustomOrHookMessage, getExpanded: () => boolean): Component;
36
36
  /**
@@ -205,6 +205,12 @@ export declare class Image implements Component {
205
205
  /** Return source metadata without exposing the encoded image buffer. */
206
206
  debugState(): Record<string, unknown>;
207
207
  invalidate(): void;
208
+ /**
209
+ * Refit to a new row ceiling; a SIXEL reserves its rows until the off-thread
210
+ * encode lands. A demoted fallback keeps the height already drawn only
211
+ * within the new ceiling: the owner chose to shrink the block.
212
+ */
213
+ setMaxHeightCells(maxHeightCells: number): void;
208
214
  releaseRenderCaches(): void;
209
215
  /**
210
216
  * A native `image` backed by a content-addressed blob; the terminal fits
@@ -56,7 +56,9 @@ export declare function list(items: readonly NativeChild[], p?: TspProps<"list">
56
56
  /** Terminal-clocked elapsed counter: `age` ms already elapsed; `stopped` freezes it there. */
57
57
  export declare function elapsed(age: number, stopped?: boolean): NativeNode;
58
58
  /**
59
- * Keycap for one key id (`"ctrl+o"` → keys `["ctrl", "o"]`); the terminal
60
- * draws platform glyphs. A literal `+` key survives (`"+"`, `"ctrl++"`).
59
+ * The key names of one key id (`"ctrl+o"` → `["ctrl", "o"]`), as `kbd` and
60
+ * `button` `keys` take them. A literal `+` key survives (`"+"`, `"ctrl++"`).
61
61
  */
62
+ export declare function keyNames(key: string): string[];
63
+ /** Keycap for one key id ({@link keyNames}); the terminal draws platform glyphs. */
62
64
  export declare function kbd(key: string, nodeKey?: string): NativeNode;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Overlay chrome for `describe()`: root cards, key-hint footers, selectable
3
- * lists and annotation callouts.
2
+ * Overlay chrome for `describe()`: root cards, key-hint footers, buttons and
3
+ * segmented controls, selectable lists and annotation callouts.
4
4
  *
5
5
  * The overlay frame itself (glass, ring, placement) is the terminal's: the
6
6
  * engine wraps every overlay-stack component in an `overlay` node. An overlay
@@ -53,11 +53,23 @@ export interface ActionButtonOptions {
53
53
  readonly title?: string;
54
54
  /** Target of an `open` action (URL or `file://` path). */
55
55
  readonly href?: string;
56
+ /** Why the action can't run now: the button dims and a click sends nothing. */
57
+ readonly disabled?: string;
58
+ /** The button's work is running: a spinner leads the label. */
59
+ readonly busy?: boolean;
60
+ /** Icon beside the label (before it on a `button` node, after it on a row). */
61
+ readonly icon?: string;
56
62
  }
57
63
  /**
58
64
  * A native button (role `omp.btn`): a label plus an optional keycap. A click
59
65
  * sends `act` back as an `action` event (or runs a terminal-local `open`/`copy`),
60
66
  * which the component routes to the same code path as the key.
67
+ *
68
+ * A terminal that draws the `button` kind ({@link nativeButtons}) gets one
69
+ * (tone `accent` → variant `primary`, `error` → `danger`, `disabled` with its
70
+ * reason as the tooltip); older ones get the row their stylesheets draw by
71
+ * role. Both keep the role and tone the omp sheets' context rules key on; a
72
+ * disabled button's tone is `muted`.
61
73
  */
62
74
  export declare function actionButton(label: string, act: string, options?: ActionButtonOptions): NativeNode;
63
75
  /**
@@ -65,6 +77,34 @@ export declare function actionButton(label: string, act: string, options?: Actio
65
77
  * `act` (default `close`), which the component routes to its Esc path.
66
78
  */
67
79
  export declare function escCloseButton(act?: string, key?: string): NativeNode;
80
+ /** Options of an {@link iconButton}. */
81
+ export interface IconButtonOptions {
82
+ /** Sibling identity key; unkeyed buttons take their position. */
83
+ readonly key?: string;
84
+ /** Accessible name; defaults to the title. */
85
+ readonly aria?: string;
86
+ /** A toggle that is on (role `omp.app.ibtn.set`). */
87
+ readonly on?: boolean;
88
+ }
89
+ /**
90
+ * A quiet icon-only button (role `omp.app.ibtn`, the kit's icon button) for an
91
+ * app head or toolbar: a `button` node where the terminal draws one
92
+ * ({@link nativeButtons}), else an `icon` with a click action.
93
+ */
94
+ export declare function iconButton(name: string, act: string, title: string, options?: IconButtonOptions): NativeNode;
95
+ /**
96
+ * A quiet text button (role `omp.app.qbtn`, the kit's quiet button) for a
97
+ * section head or toolbar, keyed by its action: a small quiet `button` where
98
+ * the terminal draws one ({@link nativeButtons}), else a row.
99
+ */
100
+ export declare function quietButton(label: string, act: string, title: string): NativeNode;
101
+ /**
102
+ * A segmented control: `tabs` drawn as a well with a raised thumb on the
103
+ * active tab. A terminal that draws the `button` kind ({@link nativeButtons})
104
+ * draws `variant: "segmented"` too; older ones restyle the tabs by role
105
+ * (`omp.app.seg`) or parent.
106
+ */
107
+ export declare function segmentedTabs(p: TspProps<"tabs">, key: string): NativeNode;
68
108
  /** A row of {@link actionButton}s (role `omp.actions`); `null` entries become the spacer that end-aligns what follows. */
69
109
  export declare function actionBar(buttons: readonly (NativeNode | null)[], key?: string): NativeNode;
70
110
  /** Spans of an annotation callout: a dim `label:` then the note. */
@@ -2,5 +2,9 @@
2
2
  export declare function isNativeRendering(): boolean;
3
3
  /** Called by the native backend on surface open and close. */
4
4
  export declare function setNativeRendering(on: boolean): void;
5
+ /** Whether omp's buttons go out as `button` nodes (see {@link setNativeButtons}). */
6
+ export declare function nativeButtons(): boolean;
7
+ /** Called by the native backend when it adopts a `hello` reply and when it stops. */
8
+ export declare function setNativeButtons(on: boolean): void;
5
9
  /** Call `listener` whenever native rendering starts or stops; returns the unsubscribe. */
6
10
  export declare function onNativeRenderingChange(listener: (on: boolean) => void): () => void;
@@ -33,6 +33,8 @@ export declare class BtwHistoryPanel implements Component, Focusable {
33
33
  constructor(options: BtwHistoryPanelOptions);
34
34
  get focused(): boolean;
35
35
  set focused(value: boolean);
36
+ /** Keep the follow-up input's cursor mode aligned so the hardware cursor (and IME preedit) lands on its caret. */
37
+ setUseTerminalCursor(useTerminalCursor: boolean): void;
36
38
  update(records: readonly BtwHistoryRecord[]): void;
37
39
  /** Visual confirmation that `c` copied this record's answer to the clipboard. */
38
40
  markCopied(recordId: string, text: string): void;
@@ -2,7 +2,7 @@ import type { StatusLineComponent } from "../status-line/component.js";
2
2
  import type { StatusLineSession } from "../status-line/host.js";
3
3
  import { type StatusLineStartupData } from "../status-line/startup.js";
4
4
  import { type Terminal } from "../terminal.js";
5
- import { type Component, type ResizeScrollbackMode, type TerminalFramePlan, type TerminalFrameProvider, TUI, type TUIOptions, type ViewportSize } from "../tui.js";
5
+ import { type Component, Container, type ResizeScrollbackMode, type TerminalFramePlan, type TerminalFrameProvider, TUI, type TUIOptions, type ViewportSize } from "../tui.js";
6
6
  import type { NativeSurface, NativeSurfaceProvider } from "../native/node.js";
7
7
  import { CustomEditor } from "./custom-editor.js";
8
8
  import type { WordCompletionMethod } from "./word-completion.js";
@@ -61,6 +61,13 @@ export interface RuntimeChildrenOptions {
61
61
  * to the roots below the transcript in render order.
62
62
  */
63
63
  readonly nativeDock?: readonly Component[];
64
+ /**
65
+ * Below-transcript containers whose children continue `main` on a TSP
66
+ * terminal, one block each after the transcript's, instead of riding in the
67
+ * dock: output on its way into the transcript (shell runs deferred until the
68
+ * next prompt) reads as its tail, and keeps its block when it moves in.
69
+ */
70
+ readonly nativeTail?: readonly Container[];
64
71
  /**
65
72
  * Below-transcript roots the user opens and closes (the editor surface that
66
73
  * drafts and dialogs swap into, docked reports). Their rows never become
@@ -141,10 +148,11 @@ export declare class Composer implements TerminalFrameProvider, NativeSurfacePro
141
148
  /**
142
149
  * Describe the whole surface for a TSP terminal. `main` is the flowing
143
150
  * document: the header (welcome, extras) and every transcript block as its
144
- * own component child. `dock` is the live chrome below the transcript in
145
- * render order: pending messages, HUDs, the working row, the editor and the
146
- * status line. There is no history/viewport split: retirement, resize
147
- * replay and hover bands are ANSI concerns.
151
+ * own component child, then the children of the
152
+ * {@link RuntimeChildrenOptions.nativeTail} roots. `dock` is the live chrome
153
+ * below the transcript in render order: pending messages, HUDs, the working
154
+ * row, the editor and the status line. There is no history/viewport split:
155
+ * retirement, resize replay and hover bands are ANSI concerns.
148
156
  */
149
157
  describeSurface(): NativeSurface;
150
158
  /**
@@ -49,6 +49,8 @@ export declare const PREVIEW_LIMITS: {
49
49
  readonly EXPANDED_LINES: 12;
50
50
  /** Items (files, results) shown in collapsed view */
51
51
  readonly COLLAPSED_ITEMS: 8;
52
+ /** Definition, type-definition and implementation locations shown in collapsed view */
53
+ readonly LOCATIONS_COLLAPSED: 2;
52
54
  /** Output preview lines in collapsed view */
53
55
  readonly OUTPUT_COLLAPSED: 3;
54
56
  /** Output preview lines in expanded view */
@@ -6,7 +6,7 @@ export { getAvailableThemes, getAvailableThemesWithPaths, getThemeByName, type T
6
6
  export { isValidThemeColor, type ThemeBg, type ThemeColor } from "./schema.js";
7
7
  export { getAvailableSymbolPresets, isValidSymbolPreset, type SpinnerType, type SymbolKey, type SymbolPreset, } from "./symbols.js";
8
8
  export { Theme } from "./theme-class.js";
9
- export { createHighlightStream, getEditorTheme, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getSelectListTheme, getSettingsListTheme, getSymbolTheme, highlightCode, setMarkdownMermaidRendering, warmHighlighter, } from "./tui-adapters.js";
9
+ export { createHighlightStream, getEditorTheme, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getSelectListTheme, getSettingsListTheme, getSymbolTheme, highlightCode, isHighlightLanguage, setMarkdownMermaidRendering, warmHighlighter, } from "./tui-adapters.js";
10
10
  /**
11
11
  * Subscribe to native fallback appearance changes independently of theme changes.
12
12
  * Consumers must re-check the effective background: COLORFGBG can override it.
@@ -6,6 +6,8 @@ import type { SettingsListTheme } from "../components/settings-list.js";
6
6
  import type { SymbolTheme } from "../symbols.js";
7
7
  import type { SlashCommandIconName } from "./symbols.js";
8
8
  import type { Theme } from "./theme-class.js";
9
+ /** Whether {@link highlightCode} colors `lang`; text in any other language comes back unstyled. */
10
+ export declare function isHighlightLanguage(lang: string | undefined): lang is string;
9
11
  /**
10
12
  * Highlight code with syntax coloring based on file extension or language.
11
13
  * Returns array of highlighted lines.
@@ -137,12 +137,12 @@ export type TaskEffort = (typeof TASK_EFFORTS)[number];
137
137
  export declare function resolveTaskEffortLevel(model: Model | undefined, effort: TaskEffort, maxEffort?: Effort): Effort | undefined;
138
138
  /**
139
139
  * Clamps a concrete thinking selector to a per-session effort ceiling (e.g. a
140
- * task spawn's `task.maxEffort`-capped effort hint). `off`/`inherit`/
141
- * `undefined` pass through, as do levels already at or below the ceiling.
142
- * Levels above it snap to the highest model-supported effort at or below the
143
- * ceiling. A model whose floor exceeds the ceiling has nothing valid to snap
144
- * to; the requested level is returned unchanged and the caller is responsible
145
- * for rejecting or skipping such models (see {@link modelSupportsEffortCeiling}).
140
+ * task spawn's `task.maxEffort`). Unset/`inherit`/`off` retain their existing
141
+ * meaning, as do levels already at or below the ceiling. Levels above it snap
142
+ * to the highest model-supported effort at or below the ceiling. A model whose
143
+ * floor exceeds the ceiling has nothing valid to snap to; the requested level
144
+ * is returned unchanged and the caller must reject or skip such models (see
145
+ * {@link modelSupportsEffortCeiling}).
146
146
  */
147
147
  export declare function clampThinkingLevelToCeiling(model: Model | undefined, level: Effort | undefined, ceiling: Effort | undefined): Effort | undefined;
148
148
  export declare function clampThinkingLevelToCeiling(model: Model | undefined, level: ThinkingLevel | undefined, ceiling: Effort | undefined): ThinkingLevel | undefined;
@@ -153,6 +153,8 @@ export declare function clampThinkingLevelToCeiling(model: Model | undefined, le
153
153
  * skip models whose floor would force the session above the ceiling.
154
154
  */
155
155
  export declare function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boolean;
156
+ /** Throws when `model` has no supported effort at or below `task.maxEffort`, so a capped spawn fails instead of running at its floor. */
157
+ export declare function assertModelSupportsEffortCeiling(model: Model, maxEffort: Effort): void;
156
158
  /**
157
159
  * The provisional concrete level shown while `auto` is configured but before a
158
160
  * turn has been classified, and the fallback when classification fails. Prefers
@@ -34,6 +34,8 @@ interface AstGrepRenderArgs {
34
34
  /** Legacy pre-`path` argument name; kept so historical transcripts still render a scope. */
35
35
  paths?: string[];
36
36
  skip?: number;
37
+ /** Language the search parsed the files as; highlighting follows it over the file extension. */
38
+ lang?: string;
37
39
  }
38
40
  /** Render AST grep calls and results. */
39
41
  export declare const astGrepToolRenderer: {
@@ -71,6 +71,8 @@ export interface EvalToolDetails {
71
71
  statusEvents?: EvalStatusEvent[];
72
72
  /** Evictable events dropped from the front of `statusEvents` (see {@link recordStatusEvent}). */
73
73
  statusEventsElided?: number;
74
+ /** Text the cell's tool calls sent to `out://`, oldest first; transcripts draw each as a reply after the call. */
75
+ replies?: string[];
74
76
  isError?: boolean;
75
77
  meta?: OutputMeta;
76
78
  /** First backend that produced cells. Kept for transcript compatibility. */
@@ -1,4 +1,4 @@
1
- import type { Theme } from "../theme/theme.js";
1
+ import { type Theme } from "../theme/theme.js";
2
2
  import type { Component } from "../tui.js";
3
3
  import type { OutputMeta } from "./output-meta.js";
4
4
  import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer.js";
@@ -1,4 +1,5 @@
1
1
  import type { NativeNode } from "../native/node.js";
2
+ import { type Theme, type ThemeColor } from "../theme/theme.js";
2
3
  /**
3
4
  * One file's contribution to a grouped file output. The header itself is generated
4
5
  * by `formatGroupedFiles` (one `#` per nesting level); use `headerSuffix` to tack
@@ -85,6 +86,15 @@ export declare function classifyGroupedLines(lines: readonly string[], headerBas
85
86
  * indices lets callers slice parallel arrays (raw lines, styled lines, contexts).
86
87
  */
87
88
  export declare function groupLineIndicesByBlank(rawLines: readonly string[]): number[][];
89
+ /**
90
+ * Styled code-frame rows of grouped search output, highlighted in each owning
91
+ * file's language (`lang` overrides it when the highlighter knows it). A run of
92
+ * consecutive line numbers in one file highlights together on its first
93
+ * request; a gap row, a jump in line numbers or another file starts a new run.
94
+ * Rows without a numbered code frame (headers, gap rows, notes) return
95
+ * undefined, for the caller to style.
96
+ */
97
+ export declare function createCodeFrameStyler(lines: readonly string[], contexts: readonly GroupedLineContext[], color: ThemeColor, theme: Theme, lang?: string): (index: number) => string | undefined;
88
98
  /** One event of {@link walkGroupedOutput}: a directory header, a file header, or a non-blank body line. */
89
99
  export type GroupedOutputEvent = {
90
100
  readonly kind: "dir";
@@ -102,31 +112,34 @@ export type GroupedOutputEvent = {
102
112
  * joined with the name) and dropping blank separator lines.
103
113
  */
104
114
  export declare function walkGroupedOutput(lines: readonly string[]): Generator<GroupedOutputEvent>;
105
- /** One source line for {@link numberedCode}: its number (`null` for an elision marker), text and match mark. */
115
+ /** One source line for {@link numberedCode}: its number (`null` for an elision marker, which adds no line), text and match mark. */
106
116
  export interface NumberedLine {
107
117
  n: number | null;
108
118
  text: string;
109
119
  mark?: boolean;
110
120
  }
111
121
  /**
112
- * Numbered `code` for possibly non-contiguous source lines: one block per run
113
- * of consecutive line numbers (`start` = the run's first line, match lines
114
- * `mark`ed), with a `…` row wherever the numbers jump or an elision marker
115
- * (`n: null`) sits. `path` is the file the lines come from: Tern picks their
116
- * grammar from it and draws no header (the row or head above names the file).
117
- * `wrap` soft-wraps long lines instead of scrolling the block sideways.
122
+ * One numbered `code` block for possibly non-contiguous source lines (match
123
+ * lines `mark`ed by their line number): `start` when the numbers run on
124
+ * consecutively, else `nums` (each line's number), from whose jumps Tern draws
125
+ * the `…` gap rows in the gutter. Elision markers (`n: null`) add no line; the
126
+ * numbers jump around them. `path` is the file the lines come from: Tern picks
127
+ * their grammar from it and draws no header (the row or head above names the
128
+ * file). `wrap` soft-wraps long lines instead of scrolling the block sideways.
129
+ * `undefined` when no line is numbered.
118
130
  */
119
131
  export declare function numberedCode(lines: readonly NumberedLine[], opts?: {
120
132
  path?: string;
121
133
  wrap?: boolean;
122
- }): NativeNode[];
134
+ }): NativeNode | undefined;
123
135
  /**
124
136
  * Describe grouped search output (grep / ast-grep / ast-edit display content)
125
137
  * by file (§7.3 grep): per file a 22px path row (dim dir, strong name, the
126
138
  * header's suffix or the match count as a chip), then its lines as
127
- * {@link numberedCode} with match lines marked and `…` between non-adjacent
128
- * runs. Unnumbered body lines stay plain `code`. `maxFiles` keeps only the
129
- * first files (the caller says how many were left out). `file` owns the lines
139
+ * one {@link numberedCode} block with match lines marked (gaps between
140
+ * non-adjacent runs drawn in its gutter). Unnumbered body lines stay plain
141
+ * `code`. `maxFiles` keeps only the first files (the caller says how many
142
+ * were left out). `file` owns the lines
130
143
  * before any header: a single-file search prints none, yet its lines get the
131
144
  * same file row as a directory search's.
132
145
  */
@@ -139,11 +139,11 @@ export declare function readSourceFsPath(details: unknown): string | undefined;
139
139
  */
140
140
  export declare function readResultIsMarkdown(details: ReadToolDetails | undefined, args: ReadRenderArgs | undefined): boolean;
141
141
  /**
142
- * Read content as numbered, wrapped `code` (no path header: the head names
143
- * the file), matching the ANSI preview's hanging wrap. Numbers come from
142
+ * Read content as one numbered, wrapped `code` block (no path header: the head
143
+ * names the file), matching the ANSI preview's hanging wrap. Numbers come from
144
144
  * `lineNumbers` (`displayContent`'s per-line numbers, `null` for an elided
145
- * block; runs split with `…` between) when they cover every line, else count
146
- * up from `start`.
145
+ * block, which Tern shows as a `…` gap row where the numbers jump) when they
146
+ * cover every line, else count up from `start`.
147
147
  */
148
148
  export declare function readContentCode(content: string, start: number, numbers: readonly (number | null)[] | undefined, path: string | undefined): NativeNode[];
149
149
  /** Render file, image, and URL reads in the transcript. */
@@ -104,7 +104,7 @@ export interface TaskItem {
104
104
  agent?: string;
105
105
  /** Historical source session selected by the bundled consult agent. */
106
106
  sourceSession?: string;
107
- /** Optional model selector override for the historical source session. */
107
+ /** Bundled seance: override for the source session's saved model. Any other agent: one exact per-call model, gated by `task.perCallModel`. */
108
108
  model?: string | string[];
109
109
  /** Inline instructions; exactly one of task or taskFile is required. */
110
110
  task?: string;
@@ -135,7 +135,7 @@ export interface TaskParams {
135
135
  agent?: string;
136
136
  /** Historical source session selected by the bundled consult agent. */
137
137
  sourceSession?: string;
138
- /** Optional model selector override for the historical source session. */
138
+ /** See {@link TaskItem.model}. */
139
139
  model?: string | string[];
140
140
  /** The work (flat form). */
141
141
  task?: string;
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.14.0",
4
+ "version": "18.14.2",
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.14.0",
45
- "@oh-my-pi/pi-agent-core": "18.14.0",
46
- "@oh-my-pi/pi-ai": "18.14.0",
47
- "@oh-my-pi/pi-catalog": "18.14.0",
48
- "@oh-my-pi/pi-natives": "18.14.0",
49
- "@oh-my-pi/pi-utils": "18.14.0",
50
- "@oh-my-pi/pi-wire": "18.14.0",
51
- "@oh-my-pi/snapcompact": "18.14.0"
44
+ "@oh-my-pi/omptype": "18.14.2",
45
+ "@oh-my-pi/pi-agent-core": "18.14.2",
46
+ "@oh-my-pi/pi-ai": "18.14.2",
47
+ "@oh-my-pi/pi-catalog": "18.14.2",
48
+ "@oh-my-pi/pi-natives": "18.14.2",
49
+ "@oh-my-pi/pi-utils": "18.14.2",
50
+ "@oh-my-pi/pi-wire": "18.14.2",
51
+ "@oh-my-pi/snapcompact": "18.14.2"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -37,7 +37,7 @@ import { routeSgrMouseInput } from "../../mouse";
37
37
  import { col, compact, keyed, node, row, span, stableKey, text } from "../../native/describe";
38
38
  import { Memo } from "../../native/memo";
39
39
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../../native/node";
40
- import { actionButton } from "../../native/overlay";
40
+ import { actionButton, iconButton, segmentedTabs } from "../../native/overlay";
41
41
  import { type Component, type OverlayHandle, TUI } from "../../tui";
42
42
  import { truncateToWidth, visibleWidth } from "../../utils";
43
43
  import { theme, warmHighlighter } from "../../theme/theme";
@@ -1039,35 +1039,28 @@ class GitTuiComponent implements Component {
1039
1039
  } else {
1040
1040
  left.push(text([span(this.#model.branch ?? "detached", "muted")], { role: "omp.app.git.path" }));
1041
1041
  }
1042
- const iconButton = (name: string, act: string, title: string, on?: boolean): NativeNode =>
1043
- node(
1044
- "icon",
1045
- { name, title, aria: title, role: on ? "omp.app.ibtn.set" : "omp.app.ibtn", actions: { click: act } },
1046
- undefined,
1047
- act,
1048
- );
1042
+ const tool = (name: string, act: string, title: string, on?: boolean): NativeNode =>
1043
+ iconButton(name, act, title, { key: act, on });
1049
1044
  const right: NativeNode[] = [
1050
1045
  row(
1051
1046
  [
1052
- iconButton("chev-up", "prev-hunk", `Previous change (${formatTooltipKey("alt+up")})`),
1053
- iconButton("chev", "next-hunk", `Next change (${formatTooltipKey("alt+down")})`),
1047
+ tool("chev-up", "prev-hunk", `Previous change (${formatTooltipKey("alt+up")})`),
1048
+ tool("chev", "next-hunk", `Next change (${formatTooltipKey("alt+down")})`),
1054
1049
  ],
1055
1050
  { gap: "none", align: "center" },
1056
1051
  ),
1057
- node(
1058
- "tabs",
1052
+ segmentedTabs(
1059
1053
  {
1060
1054
  items: VIEW_TABS.map(tab => ({ id: tab.id, label: tab.label })),
1061
1055
  active: this.#pane.mode,
1062
1056
  role: "omp.app.seg",
1063
1057
  },
1064
- undefined,
1065
1058
  "views",
1066
1059
  ),
1067
1060
  ];
1068
1061
  if (!narrow) {
1069
1062
  right.push(
1070
- iconButton(
1063
+ tool(
1071
1064
  "type",
1072
1065
  "whitespace",
1073
1066
  this.#whitespace === "off"
@@ -1077,7 +1070,7 @@ class GitTuiComponent implements Component {
1077
1070
  : `Ignoring formatting and imports; show all (${formatTooltipKey("b")})`,
1078
1071
  this.#whitespace !== "off",
1079
1072
  ),
1080
- iconButton("corner-down-right", "wrap", `Wrap lines (${formatTooltipKey("w")})`, this.#pane.wrap),
1073
+ tool("corner-down-right", "wrap", `Wrap lines (${formatTooltipKey("w")})`, this.#pane.wrap),
1081
1074
  );
1082
1075
  }
1083
1076
  if (file?.area === "unstaged" || file?.area === "staged") {
@@ -1089,8 +1082,8 @@ class GitTuiComponent implements Component {
1089
1082
  );
1090
1083
  }
1091
1084
  right.push(
1092
- iconButton("keyboard", "help", `Keyboard shortcuts (${formatTooltipKey("?")})`),
1093
- iconButton("x", "close", `Close (${formatTooltipKey("q")})`),
1085
+ tool("keyboard", "help", `Keyboard shortcuts (${formatTooltipKey("?")})`),
1086
+ tool("x", "close", `Close (${formatTooltipKey("q")})`),
1094
1087
  );
1095
1088
  return keyed(
1096
1089
  row(