@oh-my-pi/pi-tui 18.16.0 → 18.17.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 (184) hide show
  1. package/CHANGELOG.md +54 -125
  2. package/README.md +4 -1
  3. package/dist/types/apps/shell-visualizer.d.ts +24 -0
  4. package/dist/types/autocomplete.d.ts +20 -0
  5. package/dist/types/chat/cache-invalidation-marker.d.ts +15 -7
  6. package/dist/types/chat/chat-transcript-builder.d.ts +3 -1
  7. package/dist/types/chat/display-preferences.d.ts +2 -0
  8. package/dist/types/chat/extension-types.d.ts +72 -1
  9. package/dist/types/chat/read-tool-group.d.ts +15 -2
  10. package/dist/types/chat/skill-message.d.ts +1 -0
  11. package/dist/types/chat/spotlight.d.ts +203 -0
  12. package/dist/types/chat/tool-execution.d.ts +26 -5
  13. package/dist/types/chat/transcript-actions.d.ts +11 -0
  14. package/dist/types/chat/transcript-browser.d.ts +9 -0
  15. package/dist/types/chat/transcript-presentation.d.ts +164 -0
  16. package/dist/types/chat/ttsr-notification.d.ts +2 -0
  17. package/dist/types/chrome/message-frame.d.ts +2 -0
  18. package/dist/types/chrome/transcript-container.d.ts +27 -6
  19. package/dist/types/components/loader.d.ts +0 -2
  20. package/dist/types/components/markdown.d.ts +11 -1
  21. package/dist/types/components/settings-list.d.ts +12 -2
  22. package/dist/types/frame-rate.d.ts +18 -0
  23. package/dist/types/index.d.ts +3 -0
  24. package/dist/types/keybinding-matchers.d.ts +4 -1
  25. package/dist/types/kitty-graphics.d.ts +1 -1
  26. package/dist/types/motion-effects.d.ts +10 -0
  27. package/dist/types/native/encode.d.ts +2 -0
  28. package/dist/types/native/measure.d.ts +10 -0
  29. package/dist/types/native/overlay.d.ts +1 -1
  30. package/dist/types/native/quiet.d.ts +5 -0
  31. package/dist/types/native/reconcile.d.ts +6 -1
  32. package/dist/types/native/spans.d.ts +6 -0
  33. package/dist/types/native/state.d.ts +4 -0
  34. package/dist/types/overlays/agent-hub.d.ts +6 -0
  35. package/dist/types/overlays/link-browser.d.ts +55 -0
  36. package/dist/types/overlays/spotlight-sheet.d.ts +36 -0
  37. package/dist/types/prompt/composer-attachments.d.ts +5 -2
  38. package/dist/types/prompt/composer-cache.d.ts +7 -2
  39. package/dist/types/prompt/composer.d.ts +6 -11
  40. package/dist/types/prompt/custom-editor.d.ts +8 -2
  41. package/dist/types/prompt/gradient-highlight.d.ts +2 -2
  42. package/dist/types/prompt/magic-keywords.d.ts +2 -0
  43. package/dist/types/prompt/model-mention-autocomplete.d.ts +9 -7
  44. package/dist/types/prompt/model-mention-syntax.d.ts +6 -0
  45. package/dist/types/prompt/prompt-action-autocomplete.d.ts +2 -1
  46. package/dist/types/prompt/welcome.d.ts +23 -4
  47. package/dist/types/render/extension-links.d.ts +0 -6
  48. package/dist/types/render/index.d.ts +1 -0
  49. package/dist/types/render/link-hints.d.ts +78 -0
  50. package/dist/types/render/render-utils.d.ts +0 -2
  51. package/dist/types/setup/scenes/types.d.ts +8 -0
  52. package/dist/types/setup/scenes/updates.d.ts +6 -0
  53. package/dist/types/setup/setup-version.d.ts +1 -1
  54. package/dist/types/setup/wizard.d.ts +7 -2
  55. package/dist/types/spinner-clock.d.ts +34 -0
  56. package/dist/types/status-line/component.d.ts +6 -1
  57. package/dist/types/status-line/host.d.ts +62 -0
  58. package/dist/types/status-line/index.d.ts +1 -0
  59. package/dist/types/status-line/prompt-cache.d.ts +14 -0
  60. package/dist/types/status-line/schema.d.ts +1 -1
  61. package/dist/types/status-line/startup.d.ts +6 -0
  62. package/dist/types/status-line/types.d.ts +7 -1
  63. package/dist/types/theme/schema.d.ts +28 -1
  64. package/dist/types/theme/symbols.d.ts +1 -1
  65. package/dist/types/theme/theme-class.d.ts +12 -2
  66. package/dist/types/theme/theme.d.ts +1 -1
  67. package/dist/types/theme/tui-adapters.d.ts +7 -0
  68. package/dist/types/thinking.d.ts +12 -0
  69. package/dist/types/tools/ask.d.ts +1 -1
  70. package/dist/types/tools/bash.d.ts +11 -0
  71. package/dist/types/tools/find.d.ts +9 -1
  72. package/dist/types/tools/grep.d.ts +2 -1
  73. package/dist/types/tools/grouped-file-output.d.ts +17 -0
  74. package/dist/types/tools/job-widget.d.ts +2 -2
  75. package/dist/types/tools/lsp.d.ts +4 -2
  76. package/dist/types/tools/native-view.d.ts +4 -0
  77. package/dist/types/tools/renderer.d.ts +9 -1
  78. package/dist/types/tools/shell-trace.d.ts +10 -0
  79. package/dist/types/tools/shell-viz-render.d.ts +6 -0
  80. package/dist/types/tools/shell-viz.d.ts +16 -0
  81. package/dist/types/tools/wait.d.ts +2 -2
  82. package/dist/types/tools/web-search-types.d.ts +1 -0
  83. package/dist/types/tui.d.ts +15 -1
  84. package/package.json +46 -42
  85. package/src/apps/shell-visualizer.ts +167 -0
  86. package/src/autocomplete.ts +14 -0
  87. package/src/chat/assistant-message.ts +11 -6
  88. package/src/chat/cache-invalidation-marker.ts +18 -9
  89. package/src/chat/chat-transcript-builder.ts +14 -4
  90. package/src/chat/display-preferences.ts +3 -0
  91. package/src/chat/extension-types.ts +74 -1
  92. package/src/chat/read-tool-group.ts +201 -41
  93. package/src/chat/skill-message.ts +4 -0
  94. package/src/chat/spotlight.ts +460 -0
  95. package/src/chat/tool-execution.ts +360 -38
  96. package/src/chat/transcript-actions.ts +13 -1
  97. package/src/chat/transcript-browser.ts +18 -0
  98. package/src/chat/transcript-presentation.ts +642 -0
  99. package/src/chat/ttsr-notification.ts +5 -0
  100. package/src/chrome/message-frame.ts +7 -0
  101. package/src/chrome/transcript-container.ts +317 -66
  102. package/src/components/editor.ts +25 -1
  103. package/src/components/loader.ts +30 -18
  104. package/src/components/markdown.ts +41 -16
  105. package/src/components/settings-list.ts +29 -13
  106. package/src/frame-rate.ts +36 -0
  107. package/src/hotkeys-markdown.ts +4 -1
  108. package/src/index.ts +3 -0
  109. package/src/keybinding-matchers.ts +13 -12
  110. package/src/kitty-graphics.ts +3 -2
  111. package/src/motion-effects.ts +27 -0
  112. package/src/native/backend.ts +66 -7
  113. package/src/native/encode.ts +2 -0
  114. package/src/native/measure.ts +185 -0
  115. package/src/native/overlay.ts +1 -1
  116. package/src/native/quiet.ts +105 -0
  117. package/src/native/reconcile.ts +16 -10
  118. package/src/native/spans.ts +40 -0
  119. package/src/native/state.ts +18 -0
  120. package/src/overlays/agent-hub.ts +15 -2
  121. package/src/overlays/ask-dialog.ts +1 -0
  122. package/src/overlays/link-browser.ts +295 -0
  123. package/src/overlays/settings-selector.ts +17 -3
  124. package/src/overlays/spotlight-sheet.ts +478 -0
  125. package/src/overlays/usage-dashboard.ts +6 -5
  126. package/src/prompt/composer-attachments.ts +6 -6
  127. package/src/prompt/composer-cache.ts +17 -16
  128. package/src/prompt/composer.ts +30 -123
  129. package/src/prompt/custom-editor.ts +17 -13
  130. package/src/prompt/gradient-highlight.ts +5 -4
  131. package/src/prompt/magic-keywords.ts +4 -1
  132. package/src/prompt/model-mention-autocomplete.ts +73 -20
  133. package/src/prompt/model-mention-syntax.ts +9 -0
  134. package/src/prompt/prompt-action-autocomplete.ts +49 -33
  135. package/src/prompt/tips.txt +1 -0
  136. package/src/prompt/welcome.ts +106 -7
  137. package/src/render/extension-links.ts +0 -20
  138. package/src/render/index.ts +1 -0
  139. package/src/render/link-hints.ts +299 -0
  140. package/src/render/render-utils.ts +0 -5
  141. package/src/setup/scenes/glyph.ts +1 -1
  142. package/src/setup/scenes/types.ts +8 -0
  143. package/src/setup/scenes/updates.ts +116 -0
  144. package/src/setup/setup-version.ts +1 -1
  145. package/src/setup/wizard.ts +19 -8
  146. package/src/spinner-clock.ts +79 -0
  147. package/src/status-line/component.ts +143 -26
  148. package/src/status-line/host.ts +51 -0
  149. package/src/status-line/index.ts +1 -0
  150. package/src/status-line/presets.ts +2 -0
  151. package/src/status-line/prompt-cache.ts +154 -0
  152. package/src/status-line/schema.ts +1 -0
  153. package/src/status-line/segments.ts +33 -5
  154. package/src/status-line/startup.ts +22 -4
  155. package/src/status-line/types.ts +7 -1
  156. package/src/stdin-buffer.ts +89 -46
  157. package/src/terminal-capabilities.ts +3 -3
  158. package/src/terminal.ts +1 -8
  159. package/src/theme/loader.ts +3 -2
  160. package/src/theme/schema.ts +30 -1
  161. package/src/theme/symbols.ts +15 -3
  162. package/src/theme/theme-class.ts +23 -0
  163. package/src/theme/theme-schema.json +1 -1
  164. package/src/theme/theme.ts +3 -7
  165. package/src/theme/tui-adapters.ts +33 -1
  166. package/src/thinking.ts +31 -0
  167. package/src/tools/ask.ts +99 -38
  168. package/src/tools/ast-edit.ts +51 -32
  169. package/src/tools/ast-grep.ts +31 -19
  170. package/src/tools/bash.ts +130 -24
  171. package/src/tools/find.ts +23 -2
  172. package/src/tools/grep.ts +52 -36
  173. package/src/tools/grouped-file-output.ts +20 -2
  174. package/src/tools/job-widget.ts +2 -2
  175. package/src/tools/lsp.ts +537 -642
  176. package/src/tools/native-view.ts +8 -2
  177. package/src/tools/renderer.ts +9 -1
  178. package/src/tools/shell-trace.ts +80 -0
  179. package/src/tools/shell-viz-render.ts +156 -0
  180. package/src/tools/shell-viz.ts +181 -0
  181. package/src/tools/streaming-output.ts +18 -8
  182. package/src/tools/wait.ts +39 -34
  183. package/src/tools/web-search-types.ts +1 -0
  184. package/src/tui.ts +63 -6
@@ -0,0 +1,203 @@
1
+ import type { TspPropNode, TspTone } from "@oh-my-pi/pi-wire";
2
+ import type { NativeNode, NativeUiEvent } from "../native/node.js";
3
+ import type { Component } from "../tui.js";
4
+ /** Original lines a result's heat and the sheet cover; later lines are shown in neither. */
5
+ export declare const SPOTLIGHT_VIEW_LINES = 5000;
6
+ /** The original of a result whose text shaping changed. */
7
+ export interface SpotlightOriginal {
8
+ /** Session artifact holding the original text (`artifact://N`). */
9
+ url: string;
10
+ /** Its line count. */
11
+ lines: number;
12
+ /** Line number of its first line: a `read` window's first file line, else 1. */
13
+ start: number;
14
+ /** The file a `read` showed (picks the sheet's grammar). */
15
+ path?: string;
16
+ /**
17
+ * One character per original line, from its first (at most {@link SPOTLIGHT_VIEW_LINES}): Spotlight's score as
18
+ * `0`–`9` relative to the result's hottest line, `-` for a line it never scored. Lines past the end are unscored.
19
+ */
20
+ heat: string;
21
+ /** Runs of original lines the model was shown verbatim, `[first, last]` 1-based positions in the original. */
22
+ kept: [number, number][];
23
+ /**
24
+ * One {@link SpotlightReason} character per original line, over the span {@link heat} covers: why shaping showed or
25
+ * hid the line. Absent on traces written before it.
26
+ */
27
+ why?: string;
28
+ /** How the step that cut by rank chose its lines; absent when no step ranked them. */
29
+ rank?: SpotlightRank;
30
+ /**
31
+ * One entry per line of the text the model saw (of a held-out result: the text it would have seen): the original
32
+ * line it shows (1-based position in the original), the `[first, last]` run a marker stands for, or null for a line
33
+ * of its own (a shaping note).
34
+ */
35
+ map?: (number | [number, number] | null)[];
36
+ /** A held-out result's would-be text, the cut the model was not shown (`artifact://N`). */
37
+ shapedUrl?: string;
38
+ }
39
+ /**
40
+ * Why shaping showed or hid one original line ({@link SpotlightOriginal.why}). Shown: `a` won on its score, `s`
41
+ * structure kept regardless of score (head and tail lines, signatures, headings, table headers), `n` named by the
42
+ * intent or the conversation, `c` context (a neighbour of a chosen line, or a lone gap filled). Hidden: `b` ranked
43
+ * below the cutoff or past the budget, `r` seen in an earlier call, `f` folded by structure (an outline body, a doc
44
+ * section, test-run noise, dump rows). `-` unknown.
45
+ */
46
+ export type SpotlightReason = "a" | "s" | "n" | "c" | "b" | "r" | "f" | "-";
47
+ /** Whether a {@link SpotlightReason} shows the line. */
48
+ export declare function reasonShows(reason: SpotlightReason): boolean;
49
+ /** The reason for original line `i` (0-based), or undefined past the trace or without one. */
50
+ export declare function reasonAt(why: string | undefined, i: number): SpotlightReason | undefined;
51
+ /**
52
+ * The ranking of the step that cut by rank ({@link SpotlightOriginal.rank}): a line's score is the sum of its parts,
53
+ * and the step kept the best lines while its budget lasted.
54
+ */
55
+ export interface SpotlightRank {
56
+ /**
57
+ * The parts a score adds up, in order, each in [0, 1] (shaping writes one; older traces may carry two, the ranks of
58
+ * `Spotlight` and of `word overlap`).
59
+ */
60
+ parts: string[];
61
+ /** The highest score a line can reach. */
62
+ max: number;
63
+ /** Per part, its value on every original line over the span {@link SpotlightOriginal.heat} covers ({@link encodeRankPart}). */
64
+ values: string[];
65
+ /** The lowest score the step kept on rank. */
66
+ cutoff: number;
67
+ /** Characters the step could show, and how many it showed; absent for a step with no character budget. */
68
+ budget?: number;
69
+ used?: number;
70
+ /** The last line kept on rank (1-based position in the original) and the characters it cost. */
71
+ last?: {
72
+ line: number;
73
+ chars: number;
74
+ };
75
+ }
76
+ /** One {@link SpotlightRank.values} entry: two base-36 digits per line, each value relative to `max`. */
77
+ export declare function encodeRankPart(values: readonly number[], max: number): string;
78
+ /** The value of original line `i` (0-based) in an {@link encodeRankPart} string; 0 past its end. */
79
+ export declare function rankPartAt(encoded: string, i: number, max: number): number;
80
+ /**
81
+ * What a result's Spotlight requests sent besides the output lines they ranked (`POST /v1/spotlight`): the agent's
82
+ * state at the call, the command that produced the output, the question its lines were ranked under, and the model.
83
+ */
84
+ export interface SpotlightRequestParams {
85
+ /** The user's first message of the session. */
86
+ task: string;
87
+ /** Up to three earlier bash commands of the session, oldest first. */
88
+ commands: string[];
89
+ /** The agent's text before the call. */
90
+ said: string;
91
+ /** The call's intent argument. */
92
+ intent: string;
93
+ /** The command, code or `read <path>` that produced the output. */
94
+ source: string;
95
+ /** The probe the lines were ranked under: Spotlight scores each line's relevance to the agent's current call. */
96
+ probe: string;
97
+ model: string;
98
+ thinking: string;
99
+ }
100
+ /** `details.spotlight`: one tool result as shaping left it. */
101
+ export interface SpotlightDetails {
102
+ tool: string;
103
+ /** The shaping steps that ran (`outline`, `window`, …; `none` for a passthrough). */
104
+ steps: string[];
105
+ inChars: number;
106
+ outChars: number;
107
+ /** o200k tokens of the original and of the text the model saw. */
108
+ inTokens: number;
109
+ outTokens: number;
110
+ /**
111
+ * Size of the plain (unshaped) result for the same call (a spilled output's capped text, a read's page), notices
112
+ * included. Absent on traces written before pi recorded it.
113
+ */
114
+ plainChars?: number;
115
+ plainTokens?: number;
116
+ /**
117
+ * `plainTokens - outTokens` for a changed (or held-out) result, else 0: negative for a column-cap cut longer than
118
+ * the preview it replaced. On older traces `inTokens - outTokens`.
119
+ */
120
+ savedTokens: number;
121
+ /** The score-free coin skipped Spotlight: the result was shaped by the no-score rules. Absent otherwise. */
122
+ scoreFree?: boolean;
123
+ /**
124
+ * The cut saved too few tokens against the plain result to apply, so the model got the plain text (`steps` ends
125
+ * with `plain-cap`, the out sizes are the plain text's): the would-be cut's size. Absent otherwise.
126
+ */
127
+ cappedChars?: number;
128
+ cappedTokens?: number;
129
+ /**
130
+ * Shaping would have changed the text, but the result was held out at random: the model saw the plain result. The
131
+ * sizes, steps and {@link original} describe the would-be cut. Absent otherwise.
132
+ */
133
+ holdout?: boolean;
134
+ /**
135
+ * Transcripts from before the edit gate was removed: the gate kept a read pi guessed the agent would edit whole, and
136
+ * the model got the plain page. Like {@link holdout}, the sizes, steps and {@link original} describe the would-be
137
+ * cut, but `savedTokens` is 0. pi no longer writes it.
138
+ */
139
+ gated?: boolean;
140
+ /** Present only when shaping changed the text, or would have ({@link holdout}, {@link gated}). */
141
+ original?: SpotlightOriginal;
142
+ /** Present with {@link original} when Spotlight ranked the result's lines. */
143
+ request?: SpotlightRequestParams;
144
+ }
145
+ /**
146
+ * Tokens the shaped text saves against the plain result (negative: it costs more), or against the original on
147
+ * traces without {@link SpotlightDetails.plainTokens}.
148
+ */
149
+ export declare function spotlightSaving(spotlight: SpotlightDetails): number;
150
+ /** The trace a result's details carry, or undefined when the experiment did not shape it. */
151
+ export declare function spotlightDetails(details: unknown): SpotlightDetails | undefined;
152
+ /** The {@link SpotlightOriginal.heat} of per-line scores: each relative to the best, in ten steps. */
153
+ export declare function encodeHeat(scores: readonly (number | null)[]): string;
154
+ /** The heat of original line `i` (0-based) in [0, 1], or undefined when Spotlight never scored it. */
155
+ export declare function heatAt(heat: string, i: number): number | undefined;
156
+ /** {@link SpotlightOriginal.kept} of a per-line kept mask. */
157
+ export declare function keptRanges(kept: readonly boolean[]): [number, number][];
158
+ /** Whether original line `i` (0-based) scored hot: a heat digit of {@link HOT_DIGIT} or more. */
159
+ export declare function spotlightHot(heat: string, i: number): boolean;
160
+ /**
161
+ * The tone the sheet and the notice give original line `i` (0-based): why shaping showed it (`accent` score,
162
+ * `neutral` structure, `user` named, `info` context), `error` for a hidden line that scored hot; undefined for any
163
+ * other hidden line and for a line without a reason.
164
+ */
165
+ export declare function spotlightTone(original: SpotlightOriginal, i: number): TspTone | undefined;
166
+ /**
167
+ * The hover card of a changed result's notice: its heat as at most {@link NOTICE_BARS} bars (each the hottest of its
168
+ * lines, tinted by the most notable reason among them), then how many lines the model was shown, or of a held-out
169
+ * result would have been.
170
+ */
171
+ export declare function spotlightNoticeCard(original: SpotlightOriginal, heldOut?: boolean): TspPropNode;
172
+ /**
173
+ * The notice row under one tool card (role `omp.tool.spotlight`, key `spotlight:<toolCallId>`): muted for a
174
+ * passthrough (its star alone, named by its tooltip) and for a held-out result, accent for a changed result
175
+ * ({@link noticeSpans}). One with an original (changed or held out) has a hover card charting its heat
176
+ * ({@link spotlightNoticeCard}) and a click that opens its sheet through the host's transcript actions. A native
177
+ * transcript block of its own, placed right after its card ({@link Component.nativeTrailers}), so it shows while the
178
+ * card is collapsed; nothing in the terminal rows.
179
+ */
180
+ export declare class SpotlightNotice implements Component {
181
+ #private;
182
+ constructor(toolCallId: string, spotlight: SpotlightDetails, reduced: string);
183
+ update(spotlight: SpotlightDetails, reduced: string): void;
184
+ /** Hidden with its card (tool activity hidden). */
185
+ setHidden(hidden: boolean): void;
186
+ describe(): NativeNode;
187
+ /** Never mounted in the terminal rows. */
188
+ render(): readonly string[];
189
+ invalidate(): void;
190
+ handleNativeEvent(event: NativeUiEvent): void;
191
+ }
192
+ /** The notices of one transcript block's calls, in the order the calls first reported a trace. */
193
+ export declare class SpotlightNotices {
194
+ #private;
195
+ /** Records call `toolCallId`'s final result; true when a notice appeared or went. */
196
+ set(toolCallId: string, details: unknown, reduced: string): boolean;
197
+ setHidden(hidden: boolean): void;
198
+ /**
199
+ * The notice blocks, the same array while none came or went; `shown` drops the notices of calls a
200
+ * transcript presenter hides (a fresh array then).
201
+ */
202
+ list(shown?: (toolCallId: string) => boolean): readonly Component[];
203
+ }
@@ -11,11 +11,18 @@ export interface ToolExecutionUi {
11
11
  requestComponentRender(component: Component): void;
12
12
  resetDisplay(): void;
13
13
  imageBudget?: TUI["imageBudget"];
14
+ showOverlay?: TUI["showOverlay"];
15
+ setFocus?: TUI["setFocus"];
16
+ terminal?: {
17
+ readonly rows: number;
18
+ };
14
19
  }
15
20
  export interface ToolExecutionOptions {
16
21
  showImages?: boolean;
17
22
  /** Allow the name-keyed renderer registry only when the active tool is the built-in implementation. */
18
23
  useBuiltInRenderer?: boolean;
24
+ /** Ask the extension transcript presenters; false for a replayed transcript (overlays). Default: true. */
25
+ transcriptPresenters?: boolean;
19
26
  }
20
27
  /** A tool result or progress frame as a card shows it. */
21
28
  export interface ToolResultFrame {
@@ -59,21 +66,25 @@ export declare function isSettledJobFrame(details: unknown): boolean;
59
66
  * a cancelled one keeps the output it got to and seals the card as cancelled.
60
67
  */
61
68
  export declare function settleWithJobFrame(component: ToolExecutionHandle, frame: ToolResultFrame, toolCallId: string): void;
62
- /** Phase-locked spinner glyph index shared by every live tool block so parallel
63
- * spinners advance in lockstep instead of each tracking its own start time. */
64
- export declare function sharedSpinnerFrame(frameCount: number, now?: number): number;
65
69
  /** Stop the shared spinner ticker and drop every registered live block.
66
70
  * Called on interactive-mode teardown so a stray live block cannot keep the
67
- * process-wide 80ms interval alive past shutdown (lingering event-loop
71
+ * process-wide spinner timer alive past shutdown (lingering event-loop
68
72
  * handles pin the process; cf. `postmortem.quit`). Test files that assert on
69
73
  * ticker arming also use this to start from a clean slate. */
70
74
  export declare function stopSharedSpinnerTicker(): void;
75
+ /** Whether the shared spinner ticker has a tick armed. Test seam: other timers on a fake clock
76
+ * (collab relay heartbeats) make a raw timer count ambiguous. */
77
+ export declare function sharedSpinnerTickerArmed(): boolean;
71
78
  /**
72
79
  * Component that renders a tool call with its result (updateable)
73
80
  */
74
81
  export declare class ToolExecutionComponent extends Container {
75
82
  #private;
76
83
  constructor(toolName: string, args: unknown, options: ToolExecutionOptions | undefined, tool: AgentTool | undefined, ui: ToolExecutionUi, _cwd?: string, toolCallId?: string);
84
+ /** Handle exactly the shared native action vocabulary; inspector uses this same controller. */
85
+ shellVizAction(action: string): void;
86
+ /** Open the focused inspector without intercepting composer input while it is closed. */
87
+ openShellVisualizer(): void;
77
88
  updateArgs(args: unknown, _toolCallId?: string): void;
78
89
  /**
79
90
  * Signal that args are complete (tool is about to execute).
@@ -146,6 +157,16 @@ export declare class ToolExecutionComponent extends Container {
146
157
  setExpanded(expanded: boolean): void;
147
158
  /** Mirrors the card collapsed or expanded in the terminal, so `Ctrl+O` and native toggles share one state. */
148
159
  handleNativeEvent(event: NativeUiEvent): void;
160
+ /**
161
+ * Blocks of their own right after the card: the result's Spotlight notice (visible while it is
162
+ * collapsed), and late diagnostics the card absorbed while a presenter compacts or hides it, so
163
+ * they stay visible.
164
+ */
165
+ nativeTrailers(): readonly Component[];
166
+ refreshTranscriptPresentation(ids?: ReadonlySet<string>): boolean;
167
+ /** The streamed call's id changed (or an empty one was filled in): presenters see, and refresh by, the new one. */
168
+ renameToolCallId(toolCallId: string): void;
169
+ isTranscriptBlockCompact(): boolean;
149
170
  /** Whether this call targets `filePath` (the late-diagnostics router matches with it). */
150
171
  matchesPath(filePath: string): boolean;
151
172
  /**
@@ -163,7 +184,7 @@ export declare class ToolExecutionComponent extends Container {
163
184
  * `elapsed` timer in the head and terminal-local collapse clamped to the
164
185
  * view's preview. Renderers without describe hooks (and extension tools
165
186
  * with only render hooks) get the generic card. Hidden tool activity stays
166
- * mounted so toggling it is one prop change.
187
+ * mounted so toggling it is one prop change; a presenter's answer applies on top.
167
188
  */
168
189
  describe(cx?: DescribeContext): NativeNode;
169
190
  /** Apply the transcript allocator's current viewport reservation. */
@@ -4,6 +4,7 @@
4
4
  * the interactive host registers the one handler that runs omp's existing
5
5
  * commands for them, so a click and the key binding take the same path.
6
6
  */
7
+ import type { SpotlightDetails } from "./spotlight.js";
7
8
  export type TranscriptAction =
8
9
  /** Retry the last failed turn (`app.retry`). */
9
10
  {
@@ -26,6 +27,16 @@ export type TranscriptAction =
26
27
  | {
27
28
  readonly act: "copy";
28
29
  readonly text: string;
30
+ }
31
+ /**
32
+ * Open the Spotlight sheet of a shaped tool result: its original beside `reduced`, the text the model saw (a
33
+ * held-out result's sheet shows the would-be text its trace's `shapedUrl` names instead).
34
+ */
35
+ | {
36
+ readonly act: "spotlight";
37
+ readonly toolCallId: string;
38
+ readonly spotlight: SpotlightDetails;
39
+ readonly reduced: string;
29
40
  };
30
41
  type Handler = (action: TranscriptAction) => void;
31
42
  /** Register (or clear, with `undefined`) the host's handler. */
@@ -79,8 +79,17 @@ export declare class TranscriptBrowser implements Component {
79
79
  handleScrollKey(data: string): boolean;
80
80
  scrollToTop(): void;
81
81
  scrollToBottom(): void;
82
+ /** Whether the view shows the first body row. */
83
+ get atTop(): boolean;
84
+ /** One past the last logical body row in view. */
85
+ get visibleEnd(): number;
82
86
  /** Whether an anchor identity has successfully been applied to the viewport. */
83
87
  hasAnchored(id: string): boolean;
88
+ /**
89
+ * What to add to a body row of the last frame to get its logical body row, as
90
+ * {@link toContentPoint} maps it; it follows the scroll, so a content row keeps its sum.
91
+ */
92
+ get frameRowOffset(): number;
84
93
  /**
85
94
  * Translate a 0-based frame mouse point into the logical, scrolled body.
86
95
  * Returns undefined for borders, header/footer rows, and unpainted body rows.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Extension transcript presenters (`pi.registerTranscriptPresenter`). The host installs them with
3
+ * {@link setTranscriptPresenters}; each presentable block draws from its
4
+ * {@link TranscriptPresentationState}.
5
+ */
6
+ import type { TspText } from "@oh-my-pi/pi-wire";
7
+ import { type Component, Container } from "../tui.js";
8
+ import { type DescribeContext, type NativeNode, type NativeUiEvent } from "../native/node.js";
9
+ import type { AnimationFrame, CompactTranscriptBlock } from "../chrome/transcript-container.js";
10
+ import type { TranscriptBlock, TranscriptPresentation, TranscriptPresenter } from "./extension-types.js";
11
+ import { type BashExecutionMessage, type CustomMessage, type HookMessage, type PythonExecutionMessage } from "./messages.js";
12
+ /** A block's presentation once every presenter has answered. */
13
+ export type ResolvedPresentation = {
14
+ readonly mode: "full";
15
+ } | {
16
+ readonly mode: "hidden";
17
+ } | {
18
+ readonly mode: "compact";
19
+ readonly label?: TspText;
20
+ readonly rows: readonly TspText[];
21
+ };
22
+ export type CompactPresentation = Extract<ResolvedPresentation, {
23
+ mode: "compact";
24
+ }>;
25
+ /**
26
+ * Install the presenters the transcript draws with, in extension load order; true when the set
27
+ * changed. They are called as given: the installer guards them (the extension runner reports a
28
+ * throw or an unknown answer, and answers `"full"` instead).
29
+ */
30
+ export declare function setTranscriptPresenters(next: readonly TranscriptPresenter[]): boolean;
31
+ /** An extension registered or disposed a presenter; the host installs the set again and refreshes. */
32
+ export declare function notifyTranscriptPresentersChanged(): void;
33
+ /** Call `listener` on {@link notifyTranscriptPresentersChanged}; returns the unsubscribe. */
34
+ export declare function onTranscriptPresentersChanged(listener: () => void): () => void;
35
+ /**
36
+ * Call `listener` when a block draws a presentation other than the one it last drew, outside a
37
+ * refresh (a late result, say): the host replays the scrollback when that block has rows in it.
38
+ * Returns the unsubscribe.
39
+ */
40
+ export declare function onTranscriptPresentationRedrawn(listener: (owner: Component) => void): () => void;
41
+ /**
42
+ * A presenter's answer as plain data when it is a {@link TranscriptPresentation}; `null` for
43
+ * anything else (a misspelt mode, the Promise of an async presenter, a `compact` that is not a
44
+ * plain object), which draws the block in full. It reads every field the transcript uses once,
45
+ * so a throwing getter or Proxy in the answer throws here, inside the caller's guard, and never
46
+ * while drawing. Labels and rows that are not `TspText` are dropped.
47
+ */
48
+ export declare function readTranscriptPresentation(answer: unknown): TranscriptPresentation | null;
49
+ /**
50
+ * One block's presentation, resolved lazily after {@link markStale}. The resolved object keeps
51
+ * its identity while the answer is the same, so it can sit in render memo keys.
52
+ */
53
+ export declare class TranscriptPresentationState {
54
+ #private;
55
+ /**
56
+ * `snapshot` is the block presenters see (`undefined`: always full); `expanded` tells whether
57
+ * the user expanded it, which always draws it in full. `owner` is the transcript block it draws
58
+ * ({@link onTranscriptPresentationRedrawn} names it).
59
+ */
60
+ constructor(snapshot: () => TranscriptBlock | undefined, expanded: () => boolean, owner?: Component);
61
+ /** The block's data changed: ask again before the next draw. */
62
+ markStale(): void;
63
+ /** What the block draws now. */
64
+ get shown(): ResolvedPresentation;
65
+ /** The last answer {@link shown} resolved, without asking the presenters again. */
66
+ get resolved(): ResolvedPresentation;
67
+ /** {@link shown}, for the block's render or description: what {@link refresh} compares against. */
68
+ draw(): ResolvedPresentation;
69
+ /**
70
+ * The block's data changed where nothing may draw it again (rows retired to scrollback): ask now,
71
+ * and report an answer other than the drawn one as {@link draw} would. What was drawn stays the
72
+ * baseline until the block draws again.
73
+ */
74
+ recheck(): void;
75
+ /**
76
+ * Ask again now when `ids` selects this block (every block when `undefined`); true when the
77
+ * answer differs from what the block last drew. A block in terminal scrollback is not drawn
78
+ * again, so a change its data made since (a late result) still counts. A change it reports is
79
+ * the caller's to redraw, so the next draw does not report it again.
80
+ */
81
+ refresh(ids?: ReadonlySet<string>): boolean;
82
+ }
83
+ /** A transcript block whose display presenters choose. */
84
+ export interface TranscriptPresentable {
85
+ /**
86
+ * Ask again for the parts whose id is in `ids` (all when `undefined`); true when the drawing
87
+ * changed. True obliges the caller to redraw the block (and replay scrollback holding its rows):
88
+ * the change is not reported again.
89
+ */
90
+ refreshTranscriptPresentation(ids?: ReadonlySet<string>): boolean;
91
+ }
92
+ export declare function isTranscriptPresentable(component: Component): component is Component & TranscriptPresentable;
93
+ /**
94
+ * A compact block's terminal rows: `• <head>` (the presenter's `label` replacing `head`), then one
95
+ * indented row per presenter row. Plain strings arrive sanitized (see `normalize`) and keep their
96
+ * spacing; span arrays are styled.
97
+ */
98
+ export declare function compactRows(head: string, presentation: CompactPresentation, width: number, glyph?: string): string[];
99
+ /** omp's own one-row head: the bold title, then a muted detail (raw input, sanitized to one line). */
100
+ export declare function compactHead(title: string, detail?: string): string;
101
+ /** How a block names itself in a compact native card when its full drawing is not a `tool` node. */
102
+ export interface CompactNativeHead {
103
+ readonly role: string;
104
+ readonly title: string;
105
+ readonly target?: TspText;
106
+ }
107
+ /**
108
+ * A block's native drawing under `presentation`. Hidden stays mounted through the `hidden` prop.
109
+ * Compact folds a `tool` node to one inline, collapsed row (its toggle asks for the full block);
110
+ * any other drawing becomes the body of a collapsed card headed by `head`.
111
+ */
112
+ export declare function presentNative(full: NativeNode, presentation: ResolvedPresentation, head: CompactNativeHead): NativeNode;
113
+ type WrappedTranscriptBlock = Exclude<TranscriptBlock, {
114
+ type: "toolCall";
115
+ }>;
116
+ /** The `message` block of a session message: its id is the decimal `timestamp`. */
117
+ export declare function messageBlock(message: CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage): WrappedTranscriptBlock;
118
+ /** A process-unique id for an `event` block. */
119
+ export declare function eventBlockId(kind: string): string;
120
+ /** Transcript hooks a wrapped component may have; the wrapper forwards the ones it has. */
121
+ interface WrappedComponent extends Component, CompactTranscriptBlock {
122
+ setExpanded?(expanded: boolean): void;
123
+ /**
124
+ * Whether the user expanded it, including through a nested disclosure no root toggle reports
125
+ * (the skill's `prompt`). True forces the block full, so it must default to false.
126
+ */
127
+ isExpanded?(): boolean;
128
+ setToolActivityVisible?(visible: boolean): void;
129
+ isTranscriptBlockFinalized?(): boolean;
130
+ setTranscriptAllocation?(rows: number, frame: AnimationFrame): void;
131
+ }
132
+ /**
133
+ * A non-tool transcript block (advisor, skill, late diagnostics, `!`/`$` run, background job,
134
+ * supervised process, TTSR, todo reminder) with the presenters' answer applied around the
135
+ * component that draws it. In full it draws and describes as `inner` does; expansion, native
136
+ * events, tool-activity visibility and finalization reach `inner`.
137
+ */
138
+ export declare class PresentedBlock<T extends WrappedComponent = WrappedComponent> extends Container implements TranscriptPresentable {
139
+ #private;
140
+ readonly inner: T;
141
+ /** Only where `inner` has it: the transcript container detects tool activity by its presence. */
142
+ readonly setToolActivityVisible?: (visible: boolean) => void;
143
+ constructor(inner: T, block: WrappedTranscriptBlock);
144
+ get block(): WrappedTranscriptBlock;
145
+ /**
146
+ * The block's data changed (a `!`/`$` run was recorded, TTSR rules merged in). A block already
147
+ * in scrollback is not drawn again, so a changed answer is reported now for a one-time replay.
148
+ */
149
+ setBlock(block: WrappedTranscriptBlock): void;
150
+ setExpanded(expanded: boolean): void;
151
+ /** The compact row's toggle, or `inner`'s own root toggle, expands the block. */
152
+ handleNativeEvent(event: NativeUiEvent): void;
153
+ refreshTranscriptPresentation(ids?: ReadonlySet<string>): boolean;
154
+ isTranscriptBlockFinalized(): boolean;
155
+ /** A squeezed compact block keeps its head and as many rows as fit; in full, `inner` decides. */
156
+ setTranscriptAllocation(rows: number, frame: AnimationFrame): void;
157
+ /** Compact while drawn as compact rows; in full, as compact as `inner` says. */
158
+ isTranscriptBlockCompact(): boolean;
159
+ /** The cached head row carries theme colours and glyphs. */
160
+ invalidate(): void;
161
+ render(width: number): readonly string[];
162
+ describe(cx: DescribeContext): NativeNode | null;
163
+ }
164
+ export {};
@@ -16,6 +16,8 @@ export declare class TtsrNotificationComponent extends Container {
16
16
  #private;
17
17
  constructor(rules: NotificationRule[]);
18
18
  setToolActivityVisible(visible: boolean): void;
19
+ /** A snapshot: {@link addRules} grows the block's own list in place. */
20
+ get rules(): readonly NotificationRule[];
19
21
  /** Merge additional rules into this block (deduped by rule name). */
20
22
  addRules(rules: NotificationRule[]): void;
21
23
  setExpanded(expanded: boolean): void;
@@ -59,4 +59,6 @@ export declare class FramedMessageComponent<M extends FramedMessage> extends Con
59
59
  */
60
60
  describe(): NativeNode;
61
61
  handleNativeEvent(event: NativeUiEvent): void;
62
+ /** Packs with neighboring compact blocks while the extension renderer's component reports compact rows. */
63
+ isTranscriptBlockCompact(): boolean;
62
64
  }
@@ -1,5 +1,5 @@
1
1
  import { type Component, Container, type HistoryBatch } from "../tui.js";
2
- import type { NativeNode } from "../native/node.js";
2
+ import type { DescribeContext, NativeNode } from "../native/node.js";
3
3
  /** Shared animation time supplied by the constrained transcript root. */
4
4
  export interface AnimationFrame {
5
5
  readonly tick: number;
@@ -42,6 +42,17 @@ export interface AppendOnlyTranscriptBlock {
42
42
  */
43
43
  resetTranscriptStableRows?(): void;
44
44
  }
45
+ /**
46
+ * Lets a block pack against its neighbors. Two consecutive compact blocks
47
+ * (blocks that draw nothing between them don't count) have no blank separator
48
+ * row between them in the terminal, and a native (TSP) surface groups each
49
+ * such run into one gapless entry. The hook is read every frame, so a block
50
+ * may change its answer at any time (expanded, collapsed, re-presented).
51
+ */
52
+ export interface CompactTranscriptBlock {
53
+ /** Whether the block's current presentation is compact rows. */
54
+ isTranscriptBlockCompact?(): boolean;
55
+ }
45
56
  /**
46
57
  * Block lifecycle:
47
58
  * - `active`: still mutating; renders live and counts against tool admission.
@@ -156,17 +167,27 @@ export declare class TranscriptContainer extends Container {
156
167
  */
157
168
  renderTail(width: number, maxRows: number): readonly string[];
158
169
  /**
159
- * The transcript's blocks for a native (TSP) surface, one component per
160
- * `main` child in transcript order; ids follow component identity, so
170
+ * The transcript's blocks for a native (TSP) surface, one `main` child
171
+ * per block in transcript order; ids follow component identity, so
161
172
  * inserts, removals (rewind, displacement) and reorders become targeted ops.
162
173
  * Finalized blocks are settled as a hint; they stay editable, so a later
163
174
  * expansion, late result or reaction still reaches them wherever they sit.
175
+ * A block's {@link Component.nativeTrailers} follow it as blocks of their own.
164
176
  * No retirement happens on this path: the whole transcript is one document.
165
- * Returns the same array while the block list is unchanged.
177
+ *
178
+ * Each run of two or more consecutive compact blocks
179
+ * ({@link CompactTranscriptBlock}), with any blocks between them that
180
+ * describe nothing at `cx`, is one gapless entry whose members stay
181
+ * component children, keeping their own description, wire id and native
182
+ * events. A compact block's trailers are members of its run; a trailer of
183
+ * any other block is not. The entry is the same object while the same block
184
+ * leads the run, and it never settles: a member joining later may still be live.
185
+ *
186
+ * Returns the same array while the entries are unchanged.
166
187
  */
167
- nativeBlocks(): readonly Component[];
188
+ nativeBlocks(cx: DescribeContext): readonly Component[];
168
189
  /** Embedded as a child (transcript viewers): a stack of the {@link nativeBlocks}. */
169
- describe(): NativeNode;
190
+ describe(cx: DescribeContext): NativeNode;
170
191
  /** Full semantic render used by exports and non-terminal commands. */
171
192
  render(width: number): readonly string[];
172
193
  /** Rendered row where a child's block begins in the last full render() (transcript deep-links). */
@@ -3,8 +3,6 @@ import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.
3
3
  import { type ShimmerPalette } from "../theme/shimmer.js";
4
4
  import type { TUI } from "../tui.js";
5
5
  import { Text } from "./text.js";
6
- /** Milliseconds between spinner-frame advances; exported so time-derived spinners elsewhere tick at the Loader cadence. */
7
- export declare const SPINNER_ADVANCE_MS = 80;
8
6
  /** A working-row variant (§6.5): a retry countdown, or context compaction. */
9
7
  export type WorkingRowVariant = {
10
8
  readonly kind: "retry";
@@ -138,7 +138,8 @@ export declare function getMarkdownLinkUrls(text: string): string[];
138
138
  * Each function takes text and returns styled text with ANSI codes.
139
139
  */
140
140
  export interface MarkdownTheme {
141
- heading: (text: string) => string;
141
+ /** Color a heading; `depth` is its level, 1–6. Bold/underline/`#` prefix are already applied. */
142
+ heading: (text: string, depth: number) => string;
142
143
  link: (text: string) => string;
143
144
  linkUrl: (text: string) => string;
144
145
  /** Resolve the OSC 8 destination without changing visible text; undefined preserves the authored URL. */
@@ -154,6 +155,15 @@ export interface MarkdownTheme {
154
155
  italic: (text: string) => string;
155
156
  strikethrough: (text: string) => string;
156
157
  underline: (text: string) => string;
158
+ /**
159
+ * Style `**strong**` spans, including their weight. Omitted → {@link bold}.
160
+ * A theme that colors them owns the span's foreground over a `proseFg` default.
161
+ */
162
+ strong?: (text: string) => string;
163
+ /** Style `*emphasis*` spans, including their slant. Omitted → {@link italic}; colors win like {@link strong}. */
164
+ emphasis?: (text: string) => string;
165
+ /** Style table header cells, including their weight. Omitted → {@link bold}; colors win like {@link strong}. */
166
+ tableHeader?: (text: string) => string;
157
167
  highlightCode?: (code: string, lang?: string) => string[];
158
168
  /**
159
169
  * Create a stateful incremental highlighter for one streaming code fence.
@@ -3,6 +3,8 @@ import type { TspPrefsControl, TspPrefsRow, TspPrefsSection } from "@oh-my-pi/pi
3
3
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
4
4
  import type { Component } from "../tui.js";
5
5
  import { FormField, type FormFieldOptions, type FormFieldTheme } from "./form.js";
6
+ /** Builds a row's editor from its current value; `done` closes it, with the value picked if one was. */
7
+ export type SettingEditor = (currentValue: string, done: (selectedValue?: string) => void) => Component;
6
8
  export interface SettingItem {
7
9
  /** Unique identifier for this setting */
8
10
  id: string;
@@ -17,7 +19,12 @@ export interface SettingItem {
17
19
  /** If provided, Enter/Space cycles through these values */
18
20
  values?: string[];
19
21
  /** If provided, Enter opens this submenu. Receives current value and done callback. */
20
- submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
22
+ submenu?: SettingEditor;
23
+ /**
24
+ * A `values` row's choices as a menu: a native settings page's popup button
25
+ * opens it ({@link SettingsList.openSubmenuFor}); Enter/Space still cycle.
26
+ */
27
+ menu?: SettingEditor;
21
28
  /** True when the displayed setting differs from its default value. */
22
29
  changed?: boolean;
23
30
  /** The default, as a native settings page names it in the changed dot's title. */
@@ -142,7 +149,10 @@ export declare class SettingsList implements Component {
142
149
  prefsEditing(inlineMenu: (id: string) => boolean): PrefsEditing | null;
143
150
  /** Applies `value` to a cycling (boolean/enum) row the way Enter does: the row shows it and `onChange` runs. */
144
151
  applyValue(id: string, value: string): void;
145
- /** Opens row `id`'s submenu the way Enter does (leaving one already open for it); returns it. */
152
+ /**
153
+ * Opens row `id`'s submenu (a cycling row's `menu`) the way Enter opens a
154
+ * submenu, leaving one already open for it; returns it.
155
+ */
146
156
  openSubmenuFor(id: string): Component | undefined;
147
157
  /** Resize the visible viewport (fullscreen hosts call this every render). */
148
158
  setMaxVisible(rows: number): void;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The frame-rate ceiling (`tui.maxFps`): the render scheduler paints no closer than
3
+ * {@link minFrameIntervalMs}, except frames a keystroke is waiting on. Dependency-free: the host's
4
+ * flag table imports it during CLI bootstrap.
5
+ */
6
+ /** Default ceiling, in frames per second (the historical scheduler cadence). */
7
+ export declare const DEFAULT_MAX_FPS = 30;
8
+ /** Shortest interval between two frames at the live ceiling, in milliseconds. */
9
+ export declare function minFrameIntervalMs(): number;
10
+ /** Set the ceiling; callers pass a value {@link parseMaxFps} accepted. Applies from the next scheduled frame. */
11
+ export declare function setMaxFps(next: number): void;
12
+ /**
13
+ * Canonicalize a configured ceiling: an integer from 1 to 120. Numeric strings are accepted so one
14
+ * parser serves the CLI flag, the environment variable, and the setting.
15
+ *
16
+ * @throws Error on a non-numeric, fractional, or out-of-range value.
17
+ */
18
+ export declare function parseMaxFps(value: unknown): number;