@oh-my-pi/pi-tui 18.6.1 → 18.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +50 -58
  2. package/README.md +6 -2
  3. package/dist/types/app-keybindings.d.ts +7 -2
  4. package/dist/types/autocomplete.d.ts +5 -2
  5. package/dist/types/charts/chart-plan.d.ts +92 -0
  6. package/dist/types/charts/chart-svg.d.ts +30 -0
  7. package/dist/types/charts/table-data.d.ts +107 -0
  8. package/dist/types/chat/assistant-message.d.ts +10 -1
  9. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  10. package/dist/types/chat/figure-markdown.d.ts +42 -0
  11. package/dist/types/chat/svg-figure.d.ts +33 -0
  12. package/dist/types/chat/svg-source.d.ts +49 -0
  13. package/dist/types/chat/table-chart.d.ts +88 -0
  14. package/dist/types/chat/user-message.d.ts +2 -2
  15. package/dist/types/chrome/status-notice.d.ts +15 -3
  16. package/dist/types/components/editor.d.ts +5 -1
  17. package/dist/types/components/image.d.ts +23 -0
  18. package/dist/types/components/input.d.ts +1 -0
  19. package/dist/types/components/loader.d.ts +5 -6
  20. package/dist/types/components/markdown.d.ts +9 -0
  21. package/dist/types/fuzzy.d.ts +14 -0
  22. package/dist/types/key-hint-format.d.ts +8 -0
  23. package/dist/types/keybindings.d.ts +1 -1
  24. package/dist/types/native/backend.d.ts +2 -2
  25. package/dist/types/native/node.d.ts +6 -0
  26. package/dist/types/native/overlay.d.ts +3 -3
  27. package/dist/types/overlays/agent-hub.d.ts +1 -0
  28. package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -0
  29. package/dist/types/overlays/agents-hub.d.ts +9 -0
  30. package/dist/types/overlays/annotation-overlay.d.ts +5 -2
  31. package/dist/types/overlays/btw-history-panel.d.ts +5 -1
  32. package/dist/types/overlays/copy-selector.d.ts +1 -0
  33. package/dist/types/overlays/model-browser.d.ts +25 -3
  34. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  35. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  36. package/dist/types/overlays/usage-dashboard.d.ts +4 -4
  37. package/dist/types/prompt/custom-editor.d.ts +10 -4
  38. package/dist/types/prompt/interactive-context-helpers.d.ts +3 -0
  39. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  40. package/dist/types/render/composite.d.ts +2 -0
  41. package/dist/types/space-hold.d.ts +40 -25
  42. package/dist/types/terminal-capabilities.d.ts +13 -0
  43. package/dist/types/tools/glob.d.ts +4 -0
  44. package/dist/types/tui.d.ts +5 -0
  45. package/dist/types/utils.d.ts +6 -0
  46. package/package.json +9 -9
  47. package/src/app-keybindings.ts +6 -1
  48. package/src/apps/git/diff-pane.ts +42 -16
  49. package/src/apps/git/git-tui.ts +9 -9
  50. package/src/apps/git/sidebar.ts +6 -1
  51. package/src/autocomplete.ts +8 -10
  52. package/src/charts/chart-plan.ts +367 -0
  53. package/src/charts/chart-svg.ts +693 -0
  54. package/src/charts/table-data.ts +436 -0
  55. package/src/chat/assistant-message.ts +165 -25
  56. package/src/chat/chat-transcript-builder.ts +8 -0
  57. package/src/chat/figure-markdown.ts +174 -0
  58. package/src/chat/svg-figure.ts +364 -0
  59. package/src/chat/svg-source.ts +188 -0
  60. package/src/chat/table-chart.ts +301 -0
  61. package/src/chat/tool-execution.ts +6 -1
  62. package/src/chat/user-message.ts +16 -5
  63. package/src/chrome/status-notice.ts +25 -7
  64. package/src/chrome/transcript-container.ts +13 -4
  65. package/src/components/editor.ts +66 -30
  66. package/src/components/image.ts +95 -2
  67. package/src/components/input.ts +14 -6
  68. package/src/components/loader.ts +10 -17
  69. package/src/components/markdown.ts +6 -3
  70. package/src/fuzzy.ts +59 -19
  71. package/src/hotkeys-markdown.ts +9 -2
  72. package/src/key-hint-format.ts +25 -10
  73. package/src/keybindings.ts +1 -1
  74. package/src/native/backend.ts +10 -3
  75. package/src/native/node.ts +6 -0
  76. package/src/native/overlay.ts +4 -4
  77. package/src/overlays/agent-hub.ts +4 -0
  78. package/src/overlays/agent-transcript-viewer.ts +4 -0
  79. package/src/overlays/agents-hub.ts +14 -1
  80. package/src/overlays/annotation-overlay.ts +179 -80
  81. package/src/overlays/ask-dialog.ts +1 -1
  82. package/src/overlays/btw-history-panel.ts +9 -2
  83. package/src/overlays/copy-selector.ts +2 -0
  84. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  85. package/src/overlays/model-browser.ts +169 -76
  86. package/src/overlays/pause-screen.ts +2 -2
  87. package/src/overlays/plan-review-overlay.ts +41 -4
  88. package/src/overlays/rewind-selector.ts +2 -0
  89. package/src/overlays/usage-dashboard.ts +17 -8
  90. package/src/overlays/usage-row.ts +22 -8
  91. package/src/prompt/composer.ts +2 -14
  92. package/src/prompt/custom-editor.ts +188 -67
  93. package/src/prompt/interactive-context-helpers.ts +5 -0
  94. package/src/prompt/model-mention-autocomplete.ts +14 -14
  95. package/src/prompt/queued-messages.ts +6 -4
  96. package/src/render/composite.ts +62 -0
  97. package/src/space-hold.ts +156 -52
  98. package/src/status-line/segments.ts +3 -1
  99. package/src/terminal-capabilities.ts +42 -23
  100. package/src/tools/glob.ts +23 -6
  101. package/src/tui.ts +154 -119
  102. package/src/utils.ts +19 -0
@@ -0,0 +1,88 @@
1
+ import type { Tokens } from "@oh-my-pi/pi-utils/marked";
2
+ import { type ChartPlan } from "../charts/chart-plan.js";
3
+ import { type TableAnalysis } from "../charts/table-data.js";
4
+ import type { ImageBudget } from "../components/image.js";
5
+ import type { NativeNode } from "../native/node.js";
6
+ import type { Component } from "../tui.js";
7
+ /**
8
+ * `smart`: a model picks the kind and columns of multi-series tables;
9
+ * `always`: the local best guess charts every table it reads as numeric;
10
+ * `off`: tables stay tables.
11
+ */
12
+ export type TableChartMode = "smart" | "always" | "off";
13
+ /** A multi-series table the host's planner picks a chart for. */
14
+ export interface TableChartRequest {
15
+ /** The table's Markdown source. */
16
+ readonly markdown: string;
17
+ readonly table: TableAnalysis;
18
+ /** The local best guess; the planner may keep it. */
19
+ readonly guess: ChartPlan;
20
+ }
21
+ /**
22
+ * Picks the chart for a multi-series table under `smart`: a plan, or `null`
23
+ * for no chart. A rejection falls back to the local guess.
24
+ */
25
+ export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null>;
26
+ /** A drawn chart, ready to resolve against the theme. */
27
+ export interface TableChart {
28
+ /** SVG colored with figure tokens (`var(--accent)`, …). */
29
+ readonly svg: string;
30
+ readonly alt: string;
31
+ /** Size in SVG user units. */
32
+ readonly width: number;
33
+ readonly height: number;
34
+ }
35
+ /** The chart under a table: drawn, none, or a smart pick on its way (the promise settles once the cache holds it). */
36
+ export type ChartLookup = TableChart | null | Promise<void>;
37
+ /** One run of a Markdown block: prose (ending at a charted table), or the chart drawn after that table. */
38
+ export type ChartSegment = {
39
+ readonly kind: "markdown";
40
+ readonly text: string;
41
+ } | {
42
+ readonly kind: "chart";
43
+ readonly table: Tokens.Table;
44
+ };
45
+ /** Set how tables become charts (the host's `tui.autoGraph`) and its `smart` planner; the host rebuilds the transcript after. */
46
+ export declare function setTableCharts(mode: TableChartMode, planner?: TableChartPlanner): void;
47
+ /** How assistant tables currently become charts. */
48
+ export declare function tableChartMode(): TableChartMode;
49
+ /** Whether `markdown` may hold a table {@link splitTableCharts} would chart. */
50
+ export declare function hasChartTable(markdown: string): boolean;
51
+ /**
52
+ * Split `markdown` after each top-level table that has (or may get) a chart.
53
+ * A table still streaming at the block's tail is left whole: rows may follow.
54
+ */
55
+ export declare function splitTableCharts(markdown: string, streaming: boolean): ChartSegment[];
56
+ /**
57
+ * The chart for `table` under the current mode, read once per table source.
58
+ * Under `smart` the first lookup of a multi-series table asks the planner.
59
+ */
60
+ export declare function lookupTableChart(table: Tokens.Table): ChartLookup;
61
+ /**
62
+ * A native `image` node drawing `chart` from its SVG — no raster — resolved
63
+ * against the active theme; rebuilt when the theme changes.
64
+ */
65
+ export declare function describeTableChart(chart: TableChart, key: string): NativeNode;
66
+ export interface TableChartFigureOptions {
67
+ /** Shared inline-image budget. */
68
+ budget?: ImageBudget;
69
+ /** The chart's rows changed outside a text update: its pick or raster landed. */
70
+ onChange: () => void;
71
+ }
72
+ /**
73
+ * The chart under one table, as a terminal image a blank row below it. Looks
74
+ * the chart up on first render — a block rebuilt mid-stream never asks for a
75
+ * pick it drops — and renders nothing until it is drawn, or when the table
76
+ * gets no chart.
77
+ */
78
+ export declare class TableChartFigure implements Component {
79
+ #private;
80
+ constructor(options: TableChartFigureOptions);
81
+ /** Point the figure at `table`. */
82
+ update(table: Tokens.Table): void;
83
+ /** Whether the chart is still being looked up, picked or drawn. */
84
+ get pending(): boolean;
85
+ render(width: number): readonly string[];
86
+ invalidate(): void;
87
+ dispose(): void;
88
+ }
@@ -1,7 +1,7 @@
1
1
  import { type Component, Container } from "../tui.js";
2
2
  import type { ImageContent } from "@oh-my-pi/pi-ai";
3
3
  import type { ReactionTarget } from "./reaction.js";
4
- import { type NativeNode, type NativeUiEvent } from "../native/node.js";
4
+ import { type DescribeContext, type NativeNode, type NativeUiEvent } from "../native/node.js";
5
5
  /** How a user bubble styles its prose and chips (see {@link userBubbleColor}). */
6
6
  export interface UserBubbleOptions {
7
7
  /** Materialized `file://` targets per attached image, indexed by chip number. */
@@ -42,7 +42,7 @@ export declare class UserMessageComponent extends Container implements ReactionT
42
42
  * agent's reaction are chips at the bottom-right, so a reaction landing
43
43
  * later updates the frame in place, even deep in scrollback.
44
44
  */
45
- describe(): NativeNode;
45
+ describe(cx?: DescribeContext): NativeNode;
46
46
  /** Hover toolbar clicks: omp's own copy and rewind commands. */
47
47
  handleNativeEvent(event: NativeUiEvent): void;
48
48
  render(width: number): readonly string[];
@@ -1,14 +1,26 @@
1
1
  import type { NativeNode } from "../native/node.js";
2
2
  import { Container } from "../tui.js";
3
+ /** How a {@link StatusNotice} shows. */
4
+ export interface StatusNoticeOptions {
5
+ /** Styles the ANSI line; resolved at render so a theme change re-shapes it. */
6
+ styleFn?: (text: string) => string;
7
+ /**
8
+ * Whether a native terminal toasts it (default). False keeps the notice to
9
+ * the ANSI transcript: background chatter such as MCP connection progress,
10
+ * which would otherwise re-toast on every server that connects or fails.
11
+ */
12
+ toast?: boolean;
13
+ }
3
14
  /**
4
15
  * A transient status notice (`showStatus`: "Thinking blocks: hidden", "Copied
5
16
  * to clipboard"). The ANSI render appends a blank row and the dim line to the
6
17
  * transcript; a native terminal shows it as a toast instead, so notices never
7
- * pile up in the history. Updating the text re-shows the toast.
18
+ * pile up in the history. Updating the text re-shows the toast; a notice
19
+ * with `toast: false` describes nothing natively.
8
20
  */
9
21
  export declare class StatusNotice extends Container {
10
22
  #private;
11
- constructor(message: string, styleFn?: (text: string) => string);
12
- setMessage(message: string, styleFn?: (text: string) => string): void;
23
+ constructor(message: string, options?: StatusNoticeOptions);
24
+ setMessage(message: string, options?: StatusNoticeOptions): void;
13
25
  describe(): NativeNode;
14
26
  }
@@ -285,7 +285,7 @@ export declare class Editor implements Component, Focusable {
285
285
  * Mirrors the transient state the key dispatch tears down before this action so the two
286
286
  * cannot diverge: a pending character jump is cancelled by any other key, and an open
287
287
  * spelling-assist popup is dismissed by anything that is not one of its accept keys (its
288
- * debounced refresh skips assist mode, so a surviving list would hang around forever).
288
+ * refresh skips assist mode, so a surviving list would hang around forever).
289
289
  * While Vim owns the buffer (Normal or Visual) the operation is Vim's `x` — deleting the
290
290
  * selection and returning to Normal in Visual mode, the grapheme under the cursor
291
291
  * otherwise. Only Insert mode and Vim-off editors delete straight through. */
@@ -330,6 +330,10 @@ export declare class Editor implements Component, Focusable {
330
330
  * {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
331
331
  * this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
332
332
  insertPaste(content: string): void;
333
+ /** Whether a character-jump hotkey is waiting for its target character. */
334
+ get isJumpPending(): boolean;
335
+ /** Type literal text through the character-editing pipeline without keybinding dispatch. */
336
+ typeCharacter(text: string): void;
333
337
  /**
334
338
  * Whether an autocomplete popup is on screen. An `@` list narrowed to no match
335
339
  * while its refresh is pending stays open internally but is hidden, so it does
@@ -16,7 +16,21 @@ export interface ImageOptions {
16
16
  * repaint replaces the placement instead of stacking a duplicate.
17
17
  */
18
18
  imageKey?: string;
19
+ /**
20
+ * Schedules a repaint once this image's off-thread SIXEL encode lands.
21
+ * Defaults to the budget's repaint; a host without a budget must pass one,
22
+ * or the image's reserved rows stay blank until an unrelated repaint.
23
+ */
24
+ requestRender?: () => void;
19
25
  }
26
+ /**
27
+ * Run `render` for rows bound for native scrollback. Those rows are never
28
+ * repainted, so an image whose off-thread SIXEL encode has not landed encodes
29
+ * synchronously instead of committing its reserved blank rows.
30
+ */
31
+ export declare function renderForScrollback<T>(render: () => T): T;
32
+ /** Widest an {@link Image} rendered at `width` columns draws; callers sizing a raster for it (`SvgFigure`) match it. */
33
+ export declare function imageMaxColumns(width: number): number;
20
34
  /** Default count of inline images kept as live graphics before older ones fall back to text. */
21
35
  export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
22
36
  /**
@@ -52,6 +66,8 @@ export declare class ImageBudget {
52
66
  get cap(): number;
53
67
  get enabled(): boolean;
54
68
  setRequestRender(requestRender: () => void): void;
69
+ /** Ask for a repaint, e.g. once an image's off-thread encode settles. */
70
+ requestRender(): void;
55
71
  setCap(cap: number): void;
56
72
  /**
57
73
  * Stable graphics id for a logical image. A non-empty `key` maps to the same
@@ -59,6 +75,13 @@ export declare class ImageBudget {
59
75
  * gets a fresh id every call.
60
76
  */
61
77
  acquireId(key?: string): number;
78
+ /**
79
+ * Retire the graphic held under `key` as soon as no frame shows it, instead
80
+ * of when the residency sweep reaches it. For an owner that replaced the
81
+ * image for good — a streaming SVG figure's earlier raster — so superseded
82
+ * revisions never push older images (scrollback included) out of the store.
83
+ */
84
+ release(key: string): void;
62
85
  /**
63
86
  * Start an alternate-buffer lifecycle. Call once per `?1049h`, before the
64
87
  * first pass of the fullscreen overlay or resize borrow that owns the buffer.
@@ -28,6 +28,7 @@ export declare class Input implements Component, Focusable {
28
28
  setValue(value: string): void;
29
29
  setUseTerminalCursor(useTerminalCursor: boolean): void;
30
30
  getUseTerminalCursor(): boolean;
31
+ capturesInput(data: string): boolean;
31
32
  /**
32
33
  * Apply one key: the editor's text bindings (motion, deletion, kill ring,
33
34
  * undo), pastes and printable text. Returns whether the key was the
@@ -1,3 +1,4 @@
1
+ import type { KeyId } from "../keys.js";
1
2
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
2
3
  import { type ShimmerPalette } from "../theme/shimmer.js";
3
4
  import type { TUI } from "../tui.js";
@@ -23,16 +24,14 @@ export interface WorkingRowSpec {
23
24
  readonly variant?: WorkingRowVariant;
24
25
  /** Shimmer palette of the label (a session-accented intent). */
25
26
  readonly palette?: ShimmerPalette;
26
- /** Live tok/s, docked before the stop control. */
27
- readonly rate?: number;
28
27
  /** Key id that interrupts (`escape`); undefined hides the stop control (Esc would not cancel). */
29
- readonly interruptKey?: string;
28
+ readonly interruptKey?: KeyId;
30
29
  }
31
30
  /**
32
31
  * The dock's working row: `starburst` spinner (or a retry countdown ring),
33
- * the intent shimmering, `·`, the elapsed time, a grow spacer, then the stop
34
- * control whose click sends `interrupt`. Compaction adds an indeterminate
35
- * `progress` before the stop control; a retry says "Cancel".
32
+ * the elapsed time, the `·` divider, the intent shimmering, a grow spacer,
33
+ * then the stop control whose click sends `interrupt`. Compaction adds an
34
+ * indeterminate `progress` before the stop control; a retry says "Cancel".
36
35
  */
37
36
  export declare function describeWorkingRow(spec: WorkingRowSpec, cx: DescribeContext, now?: number): NativeNode;
38
37
  type ColorFn = (str: string) => string;
@@ -1,3 +1,4 @@
1
+ import { type TokensList } from "@oh-my-pi/pi-utils/marked";
1
2
  import type { DescribeContext, NativeNode } from "../native/node.js";
2
3
  import type { SymbolTheme } from "../symbols.js";
3
4
  import type { Component } from "../tui.js";
@@ -5,6 +6,14 @@ import type { Component } from "../tui.js";
5
6
  export declare function fastLineStartHazard(grownLine: string): boolean;
6
7
  /** @internal exported for tests — the windowed lexer's first-probe window size. */
7
8
  export declare const LEX_WINDOW_BYTES: number;
9
+ /**
10
+ * Lex a whole document with the renderer's rules (math, custom rules,
11
+ * strikethrough), windowing anything large enough for the quadratic scan to
12
+ * bite. `links` holds every reference definition, at any nesting depth. Callers
13
+ * that split a document along its blocks (table charts) lex through this so
14
+ * they see the blocks Markdown renders.
15
+ */
16
+ export declare function lexDocument(text: string): TokensList;
8
17
  /** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
9
18
  export interface MarkdownLink {
10
19
  /** Flattened visible label with whitespace collapsed to one row; falls back to `href` when empty. */
@@ -32,6 +32,20 @@ export declare class FuzzyText {
32
32
  /** Match `query` (space-separated tokens; all must match) against the prepared text. */
33
33
  match(query: string): FuzzyMatch;
34
34
  }
35
+ /**
36
+ * Items prepared once for repeated {@link fuzzyRank}-equivalent ranking.
37
+ *
38
+ * `fuzzyRank` resolves each candidate's search index through the module cache,
39
+ * which stops admitting entries at {@link INDEX_CACHE_MAX}; larger candidate
40
+ * lists (the full model catalog) rebuild the overflow on every query. Hold one
41
+ * corpus per stable candidate list and call {@link rank} per keystroke instead.
42
+ */
43
+ export declare class FuzzyCorpus<T> {
44
+ #private;
45
+ constructor(items: readonly T[], getText: (item: T) => string);
46
+ /** Same results as `fuzzyRank(items, query, getText)` for the prepared items. */
47
+ rank(query: string): FuzzyFilterResult<T>[];
48
+ }
35
49
  /**
36
50
  * Filter and sort items by fuzzy match quality (best matches first).
37
51
  * Supports space-separated tokens: all tokens must match.
@@ -23,6 +23,14 @@ export type KeyName = KeyId | ModifierName;
23
23
  * is capitalized. Other keys are capitalized (`f5` → `F5`).
24
24
  */
25
25
  export declare function formatKeyHint(key: KeyName): string;
26
+ /**
27
+ * Format one key for a native tooltip (a TSP `title`): unicode keycap glyphs
28
+ * whatever the theme's symbol preset (`shift+tab` → `⇧⇥`), which Tern's
29
+ * tooltip splits into keycaps, and `esc` as Tern's own keycaps read. The nerd
30
+ * preset's icons are missing from Tern's UI font, and its spaced chords would
31
+ * not parse as keys.
32
+ */
33
+ export declare function formatTooltipKey(key: KeyName): string;
26
34
  /** Format alternative keys as a slash-separated hint (`["f5", "alt+r"]` → `F5/⌥R`). */
27
35
  export declare function formatKeyHints(keys: KeyName | readonly KeyName[]): string;
28
36
  /** Format a double-tap gesture: `←←` with glyphs, `Left Left` with words. */
@@ -107,7 +107,7 @@ export declare const TUI_KEYBINDINGS: {
107
107
  readonly description: "Delete word backward";
108
108
  };
109
109
  readonly "tui.editor.deleteWordForward": {
110
- readonly defaultKeys: ["alt+delete", "alt+d", "super+alt+delete", "super+alt+d"];
110
+ readonly defaultKeys: ["alt+delete", "alt+d", "ctrl+delete", "super+alt+delete", "super+alt+d"];
111
111
  readonly description: "Delete word forward";
112
112
  };
113
113
  readonly "tui.editor.deleteToLineStart": {
@@ -83,8 +83,8 @@ export declare class NativeBackend {
83
83
  * The terminal's real `hello` reply after an optimistic start: adopt its
84
84
  * APC limit, credits, cell size, kinds, appearance and motion preference.
85
85
  * A width the terminal already reported in a `resize` event wins over the
86
- * reply's. A different vocabulary or motion preference re-describes every
87
- * component, so kinds the terminal lacks fall back.
86
+ * reply's. A different vocabulary, motion preference or clock re-describes
87
+ * every component, so kinds the terminal lacks fall back.
88
88
  */
89
89
  confirm(hello: TspHello): void;
90
90
  /** SIGWINCH: refresh the width used for `rows` fallback until the terminal reports one. */
@@ -71,6 +71,12 @@ export interface DescribeContext {
71
71
  readonly reduceMotion: boolean;
72
72
  /** The terminal's appearance is dark. */
73
73
  readonly dark: boolean;
74
+ /**
75
+ * The user's system reads a 12-hour clock (`false`: 24-hour); `undefined`
76
+ * when the terminal doesn't say. Pass it as `hour12` when formatting times:
77
+ * the process's own default locale may not know.
78
+ */
79
+ readonly hour12?: boolean;
74
80
  /** Whether the terminal renders `kind` natively (else describe something simpler or let it fall back). */
75
81
  supports(kind: TspKind): boolean;
76
82
  /**
@@ -7,7 +7,7 @@
7
7
  * component's root is a `card` whose `head` carries the title.
8
8
  */
9
9
  import type { TspProps, TspSpan, TspText } from "@oh-my-pi/pi-wire";
10
- import type { KeyName } from "../key-hint-format.js";
10
+ import { type KeyName } from "../key-hint-format.js";
11
11
  import { type Keybinding } from "../keybindings.js";
12
12
  import type { NativeChild, NativeNode } from "./node.js";
13
13
  /** One footer hint: the keys that trigger it and what it does. */
@@ -44,10 +44,10 @@ export interface ActionButtonOptions {
44
44
  /** Sibling identity key (defaults to the action id); `leafKey(event.key)` reports it back. */
45
45
  readonly key?: string;
46
46
  /** Key id of the keyboard path the button mirrors, drawn as a keycap inside it (`"r"`, `"ctrl+g"`). */
47
- readonly keys?: string;
47
+ readonly keys?: KeyName;
48
48
  /** `accent` = the view's hero action, `error` = destructive. */
49
49
  readonly tone?: "accent" | "error";
50
- /** Tooltip; defaults to the label plus the keycap (`"Refresh r"`). */
50
+ /** Tooltip; defaults to the label plus the keycap (`"Refresh r"`, see {@link formatTooltipKey}). */
51
51
  readonly title?: string;
52
52
  /** Target of an `open` action (URL or `file://` path). */
53
53
  readonly href?: string;
@@ -73,6 +73,7 @@ export interface AgentHubDeps<TRecord extends AgentRecordLike = AgentRecordLike>
73
73
  /** Mirrors the main transcript's thinking-block visibility. */
74
74
  hideThinkingBlock?: () => boolean;
75
75
  proseOnlyThinking?: () => boolean;
76
+ expandThinkingBlocks?: () => boolean;
76
77
  /** Keys toggling tool output expansion (app.tools.expand). */
77
78
  expandKeys?: KeyId[];
78
79
  /** Focus the main view on this agent's live session (ctx.focusAgentSession). When absent (collab guest, tests), Enter opens the in-hub chat view instead. */
@@ -54,6 +54,7 @@ export interface AgentTranscriptViewerDeps {
54
54
  cwd: string;
55
55
  hideThinkingBlock?: () => boolean;
56
56
  proseOnlyThinking?: () => boolean;
57
+ expandThinkingBlocks?: () => boolean;
57
58
  expandKeys: KeyId[];
58
59
  /** Keys that toggle the whole hub closed (app.agents.hub + app.session.observe). */
59
60
  hubKeys: KeyId[];
@@ -60,6 +60,15 @@ export interface AgentsHubDeps {
60
60
  export interface AgentsHubCallbacks {
61
61
  onCancel: () => void;
62
62
  }
63
+ /**
64
+ * Parse model output into a {@link GeneratedAgentSpec}. Accepts a bare JSON object
65
+ * (tried first) or one wrapped in a code fence. Returns the spec with trimmed fields.
66
+ * Throws if the output is not valid JSON or not an object, if `identifier`, `whenToUse`
67
+ * or `systemPrompt` is missing or not a string, if the identifier is not lowercase
68
+ * kebab-case with 2+ words, if `whenToUse` does not start with "Use this agent when",
69
+ * or if `systemPrompt` is empty.
70
+ */
71
+ export declare function parseGeneratedAgentSpec(raw: string): GeneratedAgentSpec;
63
72
  /**
64
73
  * The fullscreen agents hub component. Hosted via
65
74
  * `ui.showOverlay(..., { fullscreen: true })`; the host must call
@@ -1,4 +1,4 @@
1
- import { type Component, type TUI } from "../index.js";
1
+ import { type Focusable, type TUI } from "../index.js";
2
2
  import { type KeybindingsManager } from "../app-keybindings.js";
3
3
  import { type Theme } from "../theme/theme.js";
4
4
  import type { CodeReviewAnnotation, CodeReviewOverlayResult, ReviewDiffFile, TextReviewAnnotation, TextReviewOverlayResult, TextReviewSource } from "./annotation-types.js";
@@ -18,10 +18,13 @@ export interface TextReviewOverlayCallbacks extends AnnotationOverlayCallbacks {
18
18
  onComplete(result: TextReviewOverlayResult | undefined): void;
19
19
  }
20
20
  /** A fullscreen, annotated diff picker that only relies on public OMP APIs. */
21
- export declare class AnnotationOverlay implements Component {
21
+ export declare class AnnotationOverlay implements Focusable {
22
22
  #private;
23
+ focused: boolean;
23
24
  constructor(tui: TUI, theme: Theme, keybindings: KeybindingsManager, files: readonly ReviewDiffFile[], mode: string, callbacks: CodeReviewOverlayCallbacks);
24
25
  constructor(tui: TUI, theme: Theme, keybindings: KeybindingsManager, source: TextReviewSource, callbacks: TextReviewOverlayCallbacks);
26
+ /** Keep the nested editor's cursor mode aligned with the overlay focus target. */
27
+ setUseTerminalCursor(useTerminalCursor: boolean): void;
25
28
  invalidate(): void;
26
29
  dispose(): void;
27
30
  getAnnotations(): CodeReviewAnnotation[];
@@ -1,6 +1,7 @@
1
1
  import { type Component, type Focusable, Input } from "../index.js";
2
2
  import { type BtwHistoryRecord } from "./btw-history.js";
3
3
  import type { SpaceHoldHandler } from "../space-hold.js";
4
+ import { type KeyId } from "../app-keybindings.js";
4
5
  import type { NativeNode, NativeUiEvent } from "../native/node.js";
5
6
  interface BtwHistoryPanelOptions {
6
7
  records: readonly BtwHistoryRecord[];
@@ -9,7 +10,8 @@ interface BtwHistoryPanelOptions {
9
10
  onCancel: (record: BtwHistoryRecord) => void;
10
11
  canFollowUp?: (record: BtwHistoryRecord) => boolean;
11
12
  onFollowUp?: (record: BtwHistoryRecord, question: string, signal: AbortSignal) => Promise<boolean>;
12
- /** Space-bar push-to-talk for a follow-up composer: dictates into `input` while Space is held. */
13
+ /** Configured push-to-talk keys for a follow-up composer. */
14
+ spaceHoldKeys?: readonly KeyId[];
13
15
  spaceHold?: (input: Input) => SpaceHoldHandler;
14
16
  requestRender: () => void;
15
17
  getHeight: () => number;
@@ -33,6 +35,8 @@ export declare class BtwHistoryPanel implements Component, Focusable {
33
35
  openFollowUp(recordId: string): boolean;
34
36
  /** Select a record with its answer pane focused, read from the top (a just-asked /btw). */
35
37
  showRecord(recordId: string): void;
38
+ /** Only an active follow-up composer can claim its configured push-to-talk keys. */
39
+ capturesInput(data: string): boolean;
36
40
  /** Enhanced clipboard pastes belong only to the active composer. */
37
41
  pasteText(text: string): void;
38
42
  handleInput(data: string): void;
@@ -31,6 +31,7 @@ export interface CopySelectorDeps {
31
31
  cwd: string;
32
32
  hideThinkingBlock?: () => boolean;
33
33
  proseOnlyThinking?: () => boolean;
34
+ expandThinkingBlocks?: () => boolean;
34
35
  linkTargets?: ReadonlyMap<string, string>;
35
36
  requestRender: () => void;
36
37
  /** Replaces the "Copy" header when the picker is reused for another purpose. */
@@ -49,6 +49,12 @@ export interface ModelBrowserSource extends ModelRoleLookup {
49
49
  readonly knownRoleIds: readonly string[];
50
50
  readonly mruOrder: readonly string[];
51
51
  readonly modelPerf: ReadonlyMap<string, ModelBrowserPerf>;
52
+ /**
53
+ * Service tier the host would send for `model`, when it runs one. Rows with a
54
+ * measured tier aggregate (`selector@tier`) then display that tier's numbers
55
+ * instead of the standard aggregate.
56
+ */
57
+ serviceTierFor?(model: Model): string | undefined;
52
58
  getRoleInfo(role: string): ModelBrowserRoleInfo;
53
59
  defaultRoleChain(role: string): string[];
54
60
  resolveRoleValue(value: string | undefined, models: Model[], roleLookup?: ModelRoleLookup): ResolvedModelRoleValue;
@@ -134,12 +140,28 @@ export interface SearchAffinity {
134
140
  * policy, not evidence of user preference.
135
141
  */
136
142
  export declare function buildSearchAffinity(providerOrder: ReadonlyArray<string>, roles: RoleAssignments, mruOrder: ReadonlyArray<string>): SearchAffinity;
137
- /** Rank picker and mention candidates by text relevance, user affinity, and MRU/version order. */
138
- export declare function rankModelItems(query: string, items: ReadonlyArray<ModelBrowserItem>, options: {
143
+ /** Inputs to {@link rankModelItems} / {@link ModelItemRanker}. */
144
+ export interface ModelRankOptions {
139
145
  roles: RoleAssignments;
140
146
  mruOrder: ReadonlyArray<string>;
141
147
  affinity: SearchAffinity;
142
- }): ModelBrowserItem[];
148
+ }
149
+ /**
150
+ * {@link rankModelItems} prepared for one candidate list: the fuzzy index, the
151
+ * MRU/version order, and the affinity ranks depend only on the items and options,
152
+ * so per-keystroke callers (composer mentions, `/switch` arguments, the picker)
153
+ * hold one ranker and pay only the per-query fuzzy scan.
154
+ */
155
+ export declare class ModelItemRanker {
156
+ #private;
157
+ readonly items: ReadonlyArray<ModelBrowserItem>;
158
+ readonly options: ModelRankOptions;
159
+ constructor(items: ReadonlyArray<ModelBrowserItem>, options: ModelRankOptions);
160
+ /** Rank by text relevance, user affinity, and MRU/version order. */
161
+ rank(query: string): ModelBrowserItem[];
162
+ }
163
+ /** Rank picker and mention candidates by text relevance, user affinity, and MRU/version order. */
164
+ export declare function rankModelItems(query: string, items: ReadonlyArray<ModelBrowserItem>, options: ModelRankOptions): ModelBrowserItem[];
143
165
  /**
144
166
  * A slim role chip: `● default ◉` — solid dot for configured assignments,
145
167
  * hollow for auto-selected fallbacks, thinking glyph attached when set.
@@ -13,8 +13,9 @@
13
13
  * Focus regions (`toc`/`body`/`actions`) cycle with Tab/Shift+Tab; arrows move
14
14
  * within the focused region and step left into the sidebar. The default focus is
15
15
  * `actions`, so the muscle memory of the old single-target overlay carries over:
16
- * ↑/↓ select options, Enter confirms, ←/→ drives the slider when there is no
17
- * sidebar, g/G + PgUp/PgDn scroll, and the external-editor key opens the plan.
16
+ * ↑/↓ select options, Enter confirms, ←/→ drives the slider, g/G + PgUp/PgDn
17
+ * scroll, and the external-editor key opens the plan. Under Tern the options
18
+ * are a horizontal bar, so ←/→ select options and Shift+←/→ drive the slider.
18
19
  */
19
20
  import { type Component } from "../index.js";
20
21
  import type { HookSelectorSlider } from "./hook-selector.js";
@@ -48,6 +48,7 @@ export interface RewindSelectorDeps {
48
48
  cwd: string;
49
49
  hideThinkingBlock?: () => boolean;
50
50
  proseOnlyThinking?: () => boolean;
51
+ expandThinkingBlocks?: () => boolean;
51
52
  linkTargets?: ReadonlyMap<string, string>;
52
53
  requestRender: () => void;
53
54
  /** Sibling branch paths of `entryId`'s turn (excluding the turn itself). */
@@ -121,12 +121,12 @@ export declare class UsageDashboardComponent implements Component {
121
121
  * The sheet body (the terminal's `lg` overlay is the frame): a head row
122
122
  * (checked ago, Overview/Details tabs, Refresh), then either the provider
123
123
  * grid of frames with window meters and the activity heatmap, or the
124
- * per-provider detail tables. `meter`/`chart` fall back to
125
- * `progress`/`table` on terminals without them. Rebuilt only when its
126
- * inputs change.
124
+ * per-provider detail tables, then Close like the other report sheets.
125
+ * `meter`/`chart` fall back to `progress`/`table` on terminals without
126
+ * them. Rebuilt only when its inputs change.
127
127
  */
128
128
  describe(cx: DescribeContext): NativeNode;
129
- /** Tab clicks switch views like Enter/Esc; the Refresh button runs `r`. */
129
+ /** Tab clicks switch views like Enter/Esc; the Refresh button runs `r`; Close closes from either view. */
130
130
  handleNativeEvent(event: NativeUiEvent): void;
131
131
  handleInput(data: string): void;
132
132
  }
@@ -18,6 +18,10 @@ export interface ComposerNativeState {
18
18
  };
19
19
  /** Thinking effort word (`high`, `off`, `auto`); undefined when the model has no thinking. */
20
20
  readonly thinking?: string;
21
+ /** The thinking level draws as the model chip's icon instead of its own chip (where the terminal has `effort`). */
22
+ readonly thinkingInModel?: boolean;
23
+ /** Generation tok/s after the thinking level (live while running, else the last reading); undefined hides it. */
24
+ readonly rate?: number;
21
25
  /** A turn is running: the send keycap becomes a Stop button. */
22
26
  readonly running: boolean;
23
27
  /** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
@@ -229,7 +233,8 @@ export declare class CustomEditor extends Editor {
229
233
  * The TSP composer: role `omp.editor[.bash|.python]` (tone `pending` while
230
234
  * a turn runs) over the context hairline, the viewing header while a
231
235
  * subagent is focused, the attachment chips, a `line` row of the
232
- * shell-mode chip and the input, and the `bar`: model chip, effort chip,
236
+ * shell-mode chip and the input, and the `bar`: model chip, effort chip
237
+ * (or the effort glyph as the model chip's icon), the generation rate,
233
238
  * the other status facts, usage, then send (Stop while a turn runs).
234
239
  * Clicks come back as `status.model`, `thinking.cycle`, `submit`,
235
240
  * `interrupt` and `focus:<id>` actions.
@@ -271,9 +276,9 @@ export declare class CustomEditor extends Editor {
271
276
  onLeftAtStart?: () => void;
272
277
  /** Called when the viewing header asks to view agent `id` ({@link MAIN_AGENT_ID}: the main session). */
273
278
  onFocusAgent?: (id: string) => void;
274
- /** Space-bar push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so it
275
- * stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion) and away from an
276
- * open autocomplete menu. */
279
+ /** Configurable push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so
280
+ * it stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion), away from an
281
+ * open autocomplete menu, and out of a pending character jump (whose target may be the key). */
277
282
  readonly spaceHold: SpaceHoldGesture;
278
283
  setActionKeys(action: ConfigurableEditorAction, keys: KeyId[]): void;
279
284
  /**
@@ -288,6 +293,7 @@ export declare class CustomEditor extends Editor {
288
293
  * Clear all custom key handlers.
289
294
  */
290
295
  clearCustomKeyHandlers(): void;
296
+ capturesInput(data: string): boolean;
291
297
  handleInput(data: string): void;
292
298
  /**
293
299
  * Route a keystroke through the base text-editor pipeline only, skipping the
@@ -15,7 +15,10 @@ export interface AssistantMessageHost {
15
15
  readonly viewSession: AssistantMessageSession;
16
16
  readonly effectiveHideThinkingBlock: boolean;
17
17
  readonly proseOnlyThinking: boolean;
18
+ readonly expandThinkingBlocks: boolean;
18
19
  readonly assistantImagesVisible: boolean;
20
+ /** Whether the viewed session's tables get charts: the main session's do, a focused subagent's do not. */
21
+ readonly tableChartsVisible: boolean;
19
22
  readonly hideToolActivity: boolean;
20
23
  readonly toolOutputExpanded: boolean;
21
24
  readonly ui: {
@@ -19,7 +19,7 @@ export declare function applyModelMentionCompletion(lines: string[], cursorLine:
19
19
  };
20
20
  /**
21
21
  * Create a session-scoped model candidate lookup using picker ordering. The
22
- * scope and search affinity are reused across queries until their inputs change.
22
+ * scope and its prepared ranker are reused across queries until their inputs change.
23
23
  */
24
24
  export declare function createModelMentionSource(host: {
25
25
  source: ModelBrowserSource;
@@ -0,0 +1,2 @@
1
+ /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
2
+ export declare function compositeLineAt(baseLine: string, overlayLine: string, startCol: number, overlayWidth: number, totalWidth: number): string;