@oh-my-pi/pi-tui 18.4.1 → 18.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/types/chat/thinking-display.d.ts +2 -0
  3. package/dist/types/chrome/transcript-container.d.ts +15 -1
  4. package/dist/types/components/editor.d.ts +5 -0
  5. package/dist/types/loop-watchdog.d.ts +9 -0
  6. package/dist/types/overlays/model-browser.d.ts +15 -0
  7. package/dist/types/overlays/model-picker.d.ts +2 -1
  8. package/dist/types/overlays/rewind-selector.d.ts +2 -1
  9. package/dist/types/prompt/composer-attachments.d.ts +7 -0
  10. package/dist/types/prompt/model-mention-autocomplete.d.ts +4 -1
  11. package/dist/types/render/width-aware-text.d.ts +6 -0
  12. package/dist/types/setup/scenes/sign-in.d.ts +9 -9
  13. package/dist/types/setup/scenes/types.d.ts +0 -26
  14. package/dist/types/stdin-buffer.d.ts +10 -7
  15. package/dist/types/terminal.d.ts +16 -0
  16. package/dist/types/theme/color.d.ts +1 -0
  17. package/dist/types/theme/session-color.d.ts +3 -3
  18. package/dist/types/theme/shimmer.d.ts +1 -1
  19. package/dist/types/theme/theme-class.d.ts +1 -1
  20. package/dist/types/tools/web-search-types.d.ts +90 -0
  21. package/dist/types/tools/web-search.d.ts +1 -168
  22. package/dist/types/tui.d.ts +3 -2
  23. package/package.json +9 -9
  24. package/src/chat/assistant-message.ts +192 -40
  25. package/src/chat/thinking-display.ts +73 -11
  26. package/src/chat/tool-execution.ts +24 -4
  27. package/src/chrome/transcript-container.ts +178 -31
  28. package/src/components/editor.ts +51 -15
  29. package/src/components/image.ts +15 -7
  30. package/src/components/loader.ts +37 -17
  31. package/src/components/markdown.ts +110 -56
  32. package/src/loop-watchdog.ts +38 -10
  33. package/src/overlays/agent-hub.ts +16 -6
  34. package/src/overlays/copy-selector.ts +5 -0
  35. package/src/overlays/model-browser.ts +106 -25
  36. package/src/overlays/model-picker.ts +9 -6
  37. package/src/overlays/rewind-selector.ts +10 -2
  38. package/src/overlays/settings-selector.ts +2 -10
  39. package/src/prompt/composer-attachments.ts +54 -7
  40. package/src/prompt/composer.ts +5 -2
  41. package/src/prompt/custom-editor.ts +24 -6
  42. package/src/prompt/model-mention-autocomplete.ts +22 -10
  43. package/src/render/width-aware-text.ts +9 -0
  44. package/src/setup/scenes/sign-in.ts +17 -12
  45. package/src/setup/scenes/types.ts +0 -27
  46. package/src/setup/wizard.ts +1 -1
  47. package/src/status-line/component.ts +229 -134
  48. package/src/status-line/segments.ts +34 -29
  49. package/src/stdin-buffer.ts +27 -21
  50. package/src/terminal.ts +60 -20
  51. package/src/theme/color.ts +15 -0
  52. package/src/theme/mermaid-cache.ts +10 -1
  53. package/src/theme/session-color.ts +26 -4
  54. package/src/theme/shimmer.ts +55 -76
  55. package/src/theme/theme-class.ts +29 -12
  56. package/src/theme/tui-adapters.ts +14 -1
  57. package/src/tools/bash.ts +53 -13
  58. package/src/tools/edit.ts +110 -29
  59. package/src/tools/streaming-output.ts +79 -16
  60. package/src/tools/web-search-types.ts +99 -0
  61. package/src/tools/web-search.ts +1 -172
  62. package/src/tui.ts +143 -113
  63. package/src/utils.ts +15 -0
  64. package/dist/types/setup/scenes/providers.d.ts +0 -3
  65. package/dist/types/setup/scenes/web-search.d.ts +0 -22
  66. package/src/setup/scenes/providers.ts +0 -110
  67. package/src/setup/scenes/web-search.ts +0 -165
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.4.3] - 2026-09-28
6
+
7
+ ### Changed
8
+
9
+ - Reduced frame spikes and memory while long assistant replies retire into scrollback mid-stream: retiring rows no longer re-renders the whole published reply once per row, and the transcript no longer rescans the entire session history every frame ([#13650](https://github.com/can1357/oh-my-pi/pull/13650) by [@H4vC](https://github.com/H4vC)).
10
+ - Reduced memory held by finished messages: streamed Markdown blocks release their streaming row caches, frozen lex tokens, and syntax-highlight streams when they finalize, and the Mermaid render cache is now size-bounded ([#13650](https://github.com/can1357/oh-my-pi/pull/13650) by [@H4vC](https://github.com/H4vC)).
11
+ - Reduced per-frame CPU while streaming Markdown, edit previews (header facts are reused across frames; replace previews process only the visible lines), bash previews (highlighting is deferred to paint and the highlight cache is size-bounded), interleaved thinking blocks, and the live bash/ssh output tail ([#13650](https://github.com/can1357/oh-my-pi/pull/13650) by [@H4vC](https://github.com/H4vC)).
12
+ - Changed git status refresh interval to 10 seconds and added generation tracking to avoid stale counts after HEAD moves
13
+
14
+ ### Fixed
15
+
16
+ - Fixed streaming bash previews showing fields that follow the `env` object (e.g. `command`) as environment assignments ([#13650](https://github.com/can1357/oh-my-pi/pull/13650) by [@H4vC](https://github.com/H4vC)).
17
+ - Fixed model picker latency by avoiding unnecessary catalog rebuilds
18
+ - Fixed the `@` completion popup showing a `Searching…` placeholder while a refreshed file search is pending; the popup now stays hidden until results arrive, and Escape is no longer swallowed by it
19
+ - Fixed multi-line IME and dictation input (for example, voice input in Ghostty or cmux) being sent as one message per line; it now lands in the prompt as a single multi-line draft, while Enter typed during a UI freeze still submits ([#13378](https://github.com/can1357/oh-my-pi/pull/13378) by [@goransh-walia](https://github.com/goransh-walia))
20
+
21
+ ### Removed
22
+
23
+ - Removed the setup wizard's "Web search" tab; the providers scene is now sign-in only, and web search is chosen through the `web` model role like other kind roles.
24
+
25
+ ## [18.4.2] - 2026-09-28
26
+
27
+ ### Changed
28
+
29
+ - Optimized activity clock rendering by caching timestamp formatting strings
30
+ - Reduced CPU overhead during animated terminal redraws, transcript updates, inline-image bookkeeping, and status-line layout.
31
+ - Reduced per-keystroke CPU in `^` model-mention autocomplete and per-frame CPU while streaming (status line, live transcript blocks, tool cards, frame writes).
32
+
33
+ ### Fixed
34
+
35
+ - Fixed the `@` completion popup swallowing Tab and cursor-movement keys while a narrowed filter matched nothing: with no candidate to accept they now fall through to their normal completion and cursor roles instead of being trapped ([#13046](https://github.com/can1357/oh-my-pi/pull/13046) by [@jchanghong023](https://github.com/jchanghong023)).
36
+ - Fixed the Esc-Esc rewind and fullscreen `/copy` selectors getting stuck at the oldest turn of a long session's recent tail; stepping past it now loads the earlier history
37
+ - Fixed the composer attachment band showing chip `#1` (and other prefix IDs) as still present when only `#10` remained in the prompt ([#13605](https://github.com/can1357/oh-my-pi/issues/13605))
38
+
5
39
  ## [18.4.1] - 2026-09-28
6
40
 
7
41
  ### Breaking Changes
@@ -1,4 +1,6 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
+ /** Drop every memo slot so the next call recomputes from scratch. Tests only. */
3
+ export declare function resetThinkingDisplayCacheForTests(): void;
2
4
  export declare function canonicalizeMessage(text: string | null | undefined): string;
3
5
  /**
4
6
  * Thinking text prepared for display. Both modes drop empty `<!-- -->`
@@ -96,6 +96,16 @@ export declare class TranscriptContainer extends Container {
96
96
  * quit is pure write volume. An already offered replay batch stays valid.
97
97
  */
98
98
  cancelReplay(): void;
99
+ /**
100
+ * Open one composed frame: every live-block measurement until this frame's
101
+ * {@link renderViewport} returns renders against `frame` and is taken once,
102
+ * so the retirement peek and the viewport share each block's render.
103
+ *
104
+ * Callers MUST call {@link renderViewport} with the same `frame` in the same
105
+ * synchronous composition, without mutating any transcript block in
106
+ * between: the shared rows are only as fresh as that first measurement.
107
+ */
108
+ beginFrame(frame: AnimationFrame): void;
99
109
  /**
100
110
  * Total rows the live, un-emitted tail occupies at `width`.
101
111
  *
@@ -107,7 +117,11 @@ export declare class TranscriptContainer extends Container {
107
117
  liveRowCount(width: number, limit?: number): number;
108
118
  /** Block spans of the last `renderViewport` output, in output coordinates. Empty when the tail is empty. */
109
119
  getLastViewportSpans(): readonly TranscriptViewportSpan[];
110
- /** Render the live tail, constrained to the supplied transcript height. */
120
+ /**
121
+ * Render the live tail, constrained to the supplied transcript height.
122
+ * Closes the frame {@link beginFrame} opened; a different `frame` discards
123
+ * its measurements first.
124
+ */
111
125
  renderViewport(width: number, rows: number, frame: AnimationFrame): readonly string[];
112
126
  /** Offers stable-head emission or the shortest finalized prefix needed under pressure. */
113
127
  peekFinalizedBatch(width: number, capacity: number): HistoryBatch | undefined;
@@ -278,5 +278,10 @@ export declare class Editor implements Component, Focusable {
278
278
  * {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
279
279
  * this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
280
280
  insertPaste(content: string): void;
281
+ /**
282
+ * Whether an autocomplete popup is on screen. An `@` list narrowed to no match
283
+ * while its refresh is pending stays open internally but is hidden, so it does
284
+ * not claim keys (Escape, Vim mode switches) meant for the editor or app.
285
+ */
281
286
  isShowingAutocomplete(): boolean;
282
287
  }
@@ -45,5 +45,14 @@ export declare class LoopWatchdog {
45
45
  constructor(options?: LoopWatchdogOptions);
46
46
  start(): void;
47
47
  stop(): void;
48
+ /**
49
+ * Whether the loop is blocked right now — the armed tick is already more
50
+ * than `thresholdMs` overdue — or a detected block ended within the last
51
+ * `thresholdMs`. Input read at such a moment may be keystrokes the block
52
+ * batched into one read. Resolves both orders in which a resumed loop can
53
+ * run the late tick and the queued stdin read. A suspended-process gap is
54
+ * not a stall, and a stopped watchdog never reports one.
55
+ */
56
+ isStalled(): boolean;
48
57
  }
49
58
  export {};
@@ -37,6 +37,11 @@ export interface ResolvedModelRoleValue {
37
37
  }
38
38
  /** Host-provided preferences and model-role resolution for the browser. */
39
39
  export interface ModelBrowserSource extends ModelRoleLookup {
40
+ /**
41
+ * Changes whenever any preference this source reads or resolves against changes,
42
+ * except the storage-backed `mruOrder` and `modelPerf`. Keys derived-scope caches.
43
+ */
44
+ readonly revision: number;
40
45
  readonly defaultThinkingLevel: string;
41
46
  readonly modelProviderOrder: readonly string[];
42
47
  readonly knownRoleIds: readonly string[];
@@ -104,6 +109,16 @@ export interface SessionModelScope {
104
109
  }
105
110
  /** Build the session picker's current scope without creating an interactive browser. */
106
111
  export declare function buildSessionModelScope(settings: ModelBrowserSource, registry: ModelBrowserRegistry, scopedModels: ReadonlyArray<Model>): SessionModelScope;
112
+ /**
113
+ * {@link buildSessionModelScope} for per-keystroke callers: returns the same
114
+ * scope until the source revision, MRU order, scoped models, or the registry's
115
+ * available models, catalog, or load error change.
116
+ */
117
+ export declare class SessionModelScopeCache {
118
+ #private;
119
+ constructor(settings: ModelBrowserSource, registry: ModelBrowserRegistry);
120
+ get(scopedModels: ReadonlyArray<Model>): SessionModelScope;
121
+ }
107
122
  /** User affinity used to order search matches within one relevance tier. */
108
123
  export interface SearchAffinity {
109
124
  /** `provider/id` (lowercased) → rank; configured-role models first, then MRU. */
@@ -19,7 +19,8 @@ export interface ResolvedRoleModel {
19
19
  }
20
20
  /** Catalog refresh capability used by the session picker. */
21
21
  export interface ModelPickerRegistry extends ModelBrowserRegistry {
22
- refresh(strategy: "offline"): Promise<void>;
22
+ /** Bring the catalog up to date without rebuilding a current one; `true` when the picker must re-read it. */
23
+ refreshIfStale(): Promise<boolean>;
23
24
  }
24
25
  export interface ModelPickerCallbacks {
25
26
  /**
@@ -22,7 +22,8 @@
22
22
  * variants at a fork and jump between user turns elsewhere, `f` opens a
23
23
  * filter (typing narrows the current path to matching items; Esc leaves the
24
24
  * filter with the selection kept), Enter rewinds to the outlined item, A loads
25
- * earlier turns without changing selection, Esc cancels.
25
+ * earlier turns without changing selection (stepping above the oldest replayed
26
+ * turn loads them too), Esc cancels.
26
27
  */
27
28
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
28
29
  import { type Component, type TUI } from "../index.js";
@@ -37,6 +37,13 @@ export declare function attachmentSgr(kind: ChipKind, n: number): string;
37
37
  export declare const PLACEHOLDER_REGEX: RegExp;
38
38
  /** Matches an expanded attachment marker, a compact attachment chip, or a skill chip. */
39
39
  export declare const COMPOSER_TOKEN_REGEX: RegExp;
40
+ /**
41
+ * Attachment indices referenced by a composer buffer, per kind. Image/video count compact chips
42
+ * and expanded markers; paste counts compact chips only, since `[Paste #N]` markers number the
43
+ * base editor's separate paste buffer. Registered labels take precedence over theme glyphs so
44
+ * tokens created before a theme switch keep their kind; full numbers prevent `#1` matching `#10`.
45
+ */
46
+ export declare function referencedAttachments(text: string, recorded: ReadonlyMap<string, ChipKind>): Record<ChipKind, Set<number>>;
40
47
  /** Add the registered model-chip labels to the composer placeholder matcher. */
41
48
  export declare function composerTokenRegex(mentionLabels: Iterable<string>): RegExp;
42
49
  /** Offsets image marker indices, including matching `attachment://` references. */
@@ -17,7 +17,10 @@ export declare function applyModelMentionCompletion(lines: string[], cursorLine:
17
17
  cursorLine: number;
18
18
  cursorCol: number;
19
19
  };
20
- /** Create a fresh session-scoped model candidate lookup using picker ordering. */
20
+ /**
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.
23
+ */
21
24
  export declare function createModelMentionSource(host: {
22
25
  source: ModelBrowserSource;
23
26
  registry: ModelBrowserRegistry;
@@ -19,5 +19,11 @@ export declare class WidthAwareText implements Component {
19
19
  setCustomBgFn(customBgFn?: (text: string) => string): void;
20
20
  setIgnoreTight(ignore: boolean): this;
21
21
  invalidate(): void;
22
+ /**
23
+ * Re-run the formatter on the next render because its inputs changed.
24
+ * Unlike {@link invalidate}, the inner `Text` keeps its wrap cache, so a
25
+ * reformat that yields the same string skips re-wrapping and re-tinting.
26
+ */
27
+ reformat(): void;
22
28
  render(width: number): readonly string[];
23
29
  }
@@ -1,17 +1,15 @@
1
1
  import { type SgrMouseEvent } from "../../mouse.js";
2
- import type { SetupSceneHost, SetupTab } from "./types.js";
2
+ import type { SetupScene, SetupSceneController, SetupSceneHost } from "./types.js";
3
3
  /**
4
- * "Sign in" panel: lets the user authenticate one or more model providers via
5
- * OAuth. Unlike a standalone scene it never auto-advances the wizard — the user
6
- * may sign in to several providers and then continue with Esc.
4
+ * "Sign in" scene: lets the user authenticate one or more model providers via
5
+ * OAuth. It never auto-advances the wizard — the user may sign in to several
6
+ * providers and then continue with Esc.
7
7
  */
8
- export declare class SignInTab implements SetupTab {
8
+ export declare class SignInScene implements SetupSceneController {
9
9
  #private;
10
- readonly id = "sign-in";
11
- readonly label = "Sign in";
10
+ readonly title = "Sign in to your providers";
11
+ get subtitle(): string;
12
12
  constructor(host: SetupSceneHost);
13
- /** Modal while an OAuth flow is running so the scene won't switch tabs or finish. */
14
- get modal(): boolean;
15
13
  dispose(): void;
16
14
  invalidate(): void;
17
15
  handleInput(data: string): void;
@@ -19,3 +17,5 @@ export declare class SignInTab implements SetupTab {
19
17
  routeMouse(event: SgrMouseEvent, line: number, col: number): void;
20
18
  render(width: number, maxLines?: number): readonly string[];
21
19
  }
20
+ /** Onboarding scene for provider sign-in. */
21
+ export declare const providersSetupScene: SetupScene;
@@ -5,7 +5,6 @@ import type { ComposerPreviewStatusSource } from "../../overlays/composer-shape-
5
5
  import type { ComposerShape } from "../../overlays/composer-shape-registry.js";
6
6
  import type { ModelBrowserSource } from "../../overlays/model-browser.js";
7
7
  import type { SymbolPreset } from "../../theme/theme.js";
8
- import type { SearchProviderId } from "../../tools/web-search.js";
9
8
  import type { Component, TUI } from "../../tui.js";
10
9
  /** Terminal capabilities used by setup overlays and the startup splash. */
11
10
  export interface SetupUiHost {
@@ -21,7 +20,6 @@ export interface SetupHost extends SetupUiHost {
21
20
  readonly composerShape: ComposerShape;
22
21
  readonly symbolPreset: SymbolPreset;
23
22
  readonly colorBlindMode: boolean;
24
- readonly webSearchOrder: readonly string[];
25
23
  readonly disabledProviders: readonly string[];
26
24
  readonly authStorage: AuthStorage;
27
25
  readonly modelSource: ModelBrowserSource;
@@ -37,8 +35,6 @@ export interface SetupHost extends SetupUiHost {
37
35
  saveSymbolPreset(preset: SymbolPreset): void;
38
36
  saveColorBlindMode(enabled: boolean): void;
39
37
  saveTheme(mode: "dark" | "light", name: string): void;
40
- isSearchProviderAvailable(id: SearchProviderId): Promise<boolean>;
41
- saveSearchProvider(id: SearchProviderId | "auto"): void;
42
38
  captureBrowserSession(request: OAuthBrowserSessionRequest, signal?: AbortSignal): Promise<string>;
43
39
  copyToClipboard(text: string): Promise<void>;
44
40
  openInBrowser(url: string): void;
@@ -78,28 +74,6 @@ export interface SetupSceneController extends Component {
78
74
  */
79
75
  routeMouse?(event: SgrMouseEvent, line: number, col: number): void;
80
76
  }
81
- /**
82
- * A single panel inside a tabbed setup scene. The host scene owns the tab bar
83
- * and forwards rendering/input to the active tab.
84
- */
85
- export interface SetupTab {
86
- readonly id: string;
87
- readonly label: string;
88
- /**
89
- * While `true` the tab owns all keyboard input (e.g. an in-progress OAuth
90
- * login). The parent scene MUST NOT switch tabs or finish while modal.
91
- */
92
- readonly modal: boolean;
93
- /** See {@link SetupSceneController.render}: `maxLines` is the tab-local row budget. */
94
- render(width: number, maxLines?: number): readonly string[];
95
- handleInput(data: string): void;
96
- invalidate(): void;
97
- /** Called when the tab becomes active (including initial mount). */
98
- onActivate?(): void;
99
- /** Mouse routing at tab-local coordinates; see {@link SetupSceneController.routeMouse}. */
100
- routeMouse?(event: SgrMouseEvent, line: number, col: number): void;
101
- dispose(): void;
102
- }
103
77
  /** Versioned onboarding scene definition. */
104
78
  export interface SetupScene {
105
79
  id: string;
@@ -63,14 +63,17 @@ export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
63
63
  constructor(options?: StdinBufferOptions);
64
64
  process(data: string | Buffer): void;
65
65
  /**
66
- * Enable or disable unbracketed raw-paste classification. Terminal disables
67
- * it once DECRQM confirms bracketed-paste (mode 2004) support: a genuine
68
- * paste then always arrives wrapped, so the heuristic can only misfire on
69
- * keystrokes an event-loop stall batched into one read (issue #12540).
70
- * Disabling flushes any candidate already held by the classification window
71
- * as ordinary key events so no buffered input is lost.
66
+ * Gate unbracketed raw-paste classification on event-loop responsiveness.
67
+ * Terminal installs a probe once DECRQM confirms bracketed-paste (mode
68
+ * 2004) support: a genuine paste then always arrives wrapped, so an
69
+ * unmarked multiline burst is either an input-method commit — IME,
70
+ * dictation, and emoji commits are typed input, never bracketed (#13344) —
71
+ * or keystrokes an event-loop stall batched into one read (#12540). A burst
72
+ * classified while `stalled()` reports a stall is replayed as the original
73
+ * keys so every Enter submits; otherwise it is delivered as one paste.
74
+ * `undefined` restores unconditional classification.
72
75
  */
73
- setRawPasteClassification(enabled: boolean): void;
76
+ setRawPasteStallProbe(stalled: (() => boolean) | undefined): void;
74
77
  flush(): string[];
75
78
  clear(): void;
76
79
  getBuffer(): string;
@@ -96,6 +96,15 @@ export interface TerminalStartOptions {
96
96
  * echoes even while module loading blocks the event loop.
97
97
  */
98
98
  deferInput?: boolean;
99
+ /**
100
+ * Reports whether the event loop is stalled or just recovered from a
101
+ * stall. Once the terminal confirms bracketed paste, an unmarked multiline
102
+ * burst is an input-method commit (IME, dictation) delivered as one paste
103
+ * on a responsive loop, but keystrokes a stall batched into one read —
104
+ * replayed as keys so Enter submits — while this returns true. Without a
105
+ * probe every such burst is treated as an input-method commit.
106
+ */
107
+ isLoopStalled?: () => boolean;
99
108
  }
100
109
  /** Identity of an accepted explicit terminal appearance refresh request. */
101
110
  export type TerminalAppearanceRequestToken = number;
@@ -276,6 +285,13 @@ export declare class ProcessTerminal implements Terminal {
276
285
  stop(): void;
277
286
  write(data: string): void;
278
287
  get columns(): number;
288
+ /**
289
+ * With the output pump, a backlog bound at or below
290
+ * {@link STDOUT_BACKLOG_CLEAR_BYTES} is reported as-is instead of re-read:
291
+ * both consumers (the render gate and the stall watchdog) act only on a
292
+ * backlog above that level, so the bound already decides them and spares a
293
+ * native read on every frame.
294
+ */
279
295
  get pendingOutputBytes(): number;
280
296
  get hostOwnsGridOnResize(): boolean;
281
297
  get rows(): number;
@@ -5,6 +5,7 @@ export declare const FG_RESET = "\u001B[39m";
5
5
  export declare const BG_RESET = "\u001B[49m";
6
6
  /** Resolve theme color depth from the shared terminal capability model. */
7
7
  export declare function detectColorMode(env?: NodeJS.ProcessEnv): ColorMode;
8
+ /** Convert a theme color to foreground SGR at the requested depth; throws for invalid colors. */
8
9
  export declare function colorToAnsi(color: string, mode: ColorMode): string;
9
10
  export declare function fgAnsi(color: string | number, mode: ColorMode): string;
10
11
  export declare function bgAnsi(color: string | number, mode: ColorMode): string;
@@ -1,11 +1,11 @@
1
1
  /** Theme-derived inputs for {@link getSessionAccentHex}; see `Theme.sessionAccentInputs`. */
2
2
  export interface SessionAccentTheme {
3
3
  /** Theme accent hex; the session accent adopts its OKLCH lightness and chroma. */
4
- accentHex: string;
4
+ readonly accentHex: string;
5
5
  /** Major theme color hexes checked for hue collision. */
6
- colorHexes: string[];
6
+ readonly colorHexes: readonly string[];
7
7
  /** WCAG luminance of the status-line surface on light themes; undefined on dark themes. */
8
- surfaceLuminance?: number;
8
+ readonly surfaceLuminance?: number;
9
9
  }
10
10
  /**
11
11
  * Derive a stable CSS hex accent color from a session name and the active theme.
@@ -1,6 +1,6 @@
1
1
  import type { Theme, ThemeColor } from "./theme.js";
2
2
  type ShimmerTheme = Pick<Theme, "bold" | "fg" | "getFgAnsi">;
3
- /** Sweep style for animated shimmer text; `disabled` renders every tier as the low color. */
3
+ /** Sweep style for animated shimmer text; `disabled` renders every tier as the mid color. */
4
4
  export type ShimmerMode = "classic" | "kitt" | "disabled";
5
5
  /** Select the shimmer sweep style. The host pushes its `display.shimmer` preference here. */
6
6
  export declare function setShimmerMode(mode: ShimmerMode): void;
@@ -49,7 +49,7 @@ export declare class Theme {
49
49
  * Theme-derived inputs for `getSessionAccentHex`: the accent hex whose
50
50
  * OKLCH weight session accents adopt, the major colors they must not
51
51
  * hue-collide with, and the light-theme surface luminance to contrast
52
- * against.
52
+ * against. Built once per instance and frozen; callers share the object.
53
53
  */
54
54
  get sessionAccentInputs(): SessionAccentTheme;
55
55
  fg(color: ThemeColor, text: string): string;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Web search response types shared by the coding agent's search providers and
3
+ * the transcript renderer in {@link ./web-search}. Render-free, so provider code
4
+ * can import it without loading UI components.
5
+ */
6
+ /**
7
+ * Display label for every search implementation: `web/*` engines and the
8
+ * chat-model groundings. Keys are the ids providers report in
9
+ * {@link SearchResponse.provider} and search errors.
10
+ */
11
+ export declare const SEARCH_PROVIDER_LABELS: {
12
+ readonly parallel: "Parallel";
13
+ readonly perplexity: "Perplexity";
14
+ readonly gemini: "Gemini";
15
+ readonly anthropic: "Anthropic";
16
+ readonly codex: "OpenAI Codex";
17
+ readonly openai: "OpenAI API";
18
+ readonly xai: "xAI";
19
+ readonly openrouter: "OpenRouter";
20
+ readonly zai: "Z.AI";
21
+ readonly exa: "Exa";
22
+ readonly tinyfish: "TinyFish";
23
+ readonly jina: "Jina";
24
+ readonly kagi: "Kagi";
25
+ readonly tavily: "Tavily";
26
+ readonly firecrawl: "Firecrawl";
27
+ readonly brave: "Brave";
28
+ readonly kimi: "Kimi";
29
+ readonly synthetic: "Synthetic";
30
+ readonly ollama: "Ollama";
31
+ readonly searxng: "SearXNG";
32
+ readonly startpage: "Startpage";
33
+ readonly duckduckgo: "DuckDuckGo";
34
+ readonly ecosia: "Ecosia";
35
+ readonly google: "Google";
36
+ readonly mojeek: "Mojeek";
37
+ readonly public: "Public Web";
38
+ };
39
+ /** Id of a search implementation; see {@link SEARCH_PROVIDER_LABELS}. */
40
+ export type SearchProviderId = keyof typeof SEARCH_PROVIDER_LABELS;
41
+ /** Label for a provider id; ids from older transcripts fall back to the raw id. */
42
+ export declare function getSearchProviderLabel(id: SearchProviderId): string;
43
+ /** Source returned by search (all providers) */
44
+ export interface SearchSource {
45
+ title: string;
46
+ url: string;
47
+ snippet?: string;
48
+ /** ISO date string or relative ("2d ago") */
49
+ publishedDate?: string;
50
+ /** Age in seconds for consistent formatting */
51
+ ageSeconds?: number;
52
+ author?: string;
53
+ }
54
+ /** Citation with text reference (LLM-mediated providers) */
55
+ export interface SearchCitation {
56
+ url: string;
57
+ title: string;
58
+ citedText?: string;
59
+ }
60
+ /** Usage metrics */
61
+ export interface SearchUsage {
62
+ inputTokens?: number;
63
+ outputTokens?: number;
64
+ /** Anthropic: number of web search requests made */
65
+ searchRequests?: number;
66
+ /** Perplexity: combined token count */
67
+ totalTokens?: number;
68
+ }
69
+ /** Unified response across providers */
70
+ export interface SearchResponse {
71
+ provider: SearchProviderId | "none";
72
+ /** Synthesized answer text (LLM-mediated providers) */
73
+ answer?: string;
74
+ /** Search result sources */
75
+ sources: SearchSource[];
76
+ /** Text citations with context */
77
+ citations?: SearchCitation[];
78
+ /** Intermediate search queries (anthropic) */
79
+ searchQueries?: string[];
80
+ /** Follow-up question suggestions (provider-dependent) */
81
+ relatedQuestions?: string[];
82
+ /** Token usage metrics */
83
+ usage?: SearchUsage;
84
+ /** Model used */
85
+ model?: string;
86
+ /** Request ID for debugging */
87
+ requestId?: string;
88
+ /** Authentication mode used by the provider (e.g. oauth, api-key) */
89
+ authMode?: string;
90
+ }