zidane 5.6.14 → 5.7.4

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 (119) hide show
  1. package/README.md +3 -1
  2. package/dist/{agent-ClkpElCZ.d.ts → agent-BNS2nx_T.d.ts} +535 -15
  3. package/dist/agent-BNS2nx_T.d.ts.map +1 -0
  4. package/dist/chat/pure.d.ts +4 -0
  5. package/dist/chat/pure.js +3 -0
  6. package/dist/chat.d.ts +31 -661
  7. package/dist/chat.d.ts.map +1 -1
  8. package/dist/chat.js +5 -3
  9. package/dist/chat.js.map +1 -1
  10. package/dist/contexts/docker.d.ts +1 -1
  11. package/dist/contexts/docker.d.ts.map +1 -1
  12. package/dist/contexts/docker.js.map +1 -1
  13. package/dist/{contexts-BOtMvzli.js → contexts-BD2U_xpi.js} +2 -2
  14. package/dist/{contexts-BOtMvzli.js.map → contexts-BD2U_xpi.js.map} +1 -1
  15. package/dist/contexts.d.ts +3 -3
  16. package/dist/contexts.js +1 -1
  17. package/dist/edit-utils-DnfNoj16.js +574 -0
  18. package/dist/edit-utils-DnfNoj16.js.map +1 -0
  19. package/dist/{errors-DdZXnyXE.js → errors-CoQnKRf1.js} +32 -2
  20. package/dist/{errors-DdZXnyXE.js.map → errors-CoQnKRf1.js.map} +1 -1
  21. package/dist/fetch-url-CPxfiXDa.js +518 -0
  22. package/dist/fetch-url-CPxfiXDa.js.map +1 -0
  23. package/dist/image-sniff-B7uFSNO1.js +90 -0
  24. package/dist/image-sniff-B7uFSNO1.js.map +1 -0
  25. package/dist/{index-CbS75MD3.d.ts → index-CZOwAJIX.d.ts} +2 -2
  26. package/dist/index-CZOwAJIX.d.ts.map +1 -0
  27. package/dist/{index-CTDMMdIy.d.ts → index-Ck_AWt8P.d.ts} +3 -4
  28. package/dist/index-Ck_AWt8P.d.ts.map +1 -0
  29. package/dist/{index-v3Tzobqr.d.ts → index-KiS7w0dC.d.ts} +3 -3
  30. package/dist/index-KiS7w0dC.d.ts.map +1 -0
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +13 -12
  33. package/dist/index.js.map +1 -1
  34. package/dist/{interpolate-DM1UcKeQ.js → interpolate-TySiqKzc.js} +23 -23
  35. package/dist/{interpolate-DM1UcKeQ.js.map → interpolate-TySiqKzc.js.map} +1 -1
  36. package/dist/{login-7tHcckmX.js → login-BDeqENSe.js} +7 -58
  37. package/dist/login-BDeqENSe.js.map +1 -0
  38. package/dist/{mcp-DGeB7-3D.js → mcp-Kqzz-Rs_.js} +8 -6
  39. package/dist/mcp-Kqzz-Rs_.js.map +1 -0
  40. package/dist/mcp.d.ts +2 -2
  41. package/dist/mcp.js +1 -1
  42. package/dist/{messages-Dym8S_YH.js → messages-CvRQTdbR.js} +118 -39
  43. package/dist/messages-CvRQTdbR.js.map +1 -0
  44. package/dist/{presets-w9Px_aAm.js → presets-JuOnSI-i.js} +2 -2
  45. package/dist/{presets-w9Px_aAm.js.map → presets-JuOnSI-i.js.map} +1 -1
  46. package/dist/presets.d.ts +3 -3
  47. package/dist/presets.js +1 -1
  48. package/dist/{providers-beXyD9W9.js → providers-h4HJPbbv.js} +485 -31
  49. package/dist/providers-h4HJPbbv.js.map +1 -0
  50. package/dist/providers.d.ts +2 -2
  51. package/dist/providers.js +3 -3
  52. package/dist/restate.d.ts +1 -1
  53. package/dist/restate.d.ts.map +1 -1
  54. package/dist/restate.js.map +1 -1
  55. package/dist/session/sqlite.d.ts +1 -1
  56. package/dist/session/sqlite.d.ts.map +1 -1
  57. package/dist/session/sqlite.js +1 -1
  58. package/dist/session/sqlite.js.map +1 -1
  59. package/dist/{session-BRIsmBSY.js → session-BzLou2_-.js} +2 -2
  60. package/dist/{session-BRIsmBSY.js.map → session-BzLou2_-.js.map} +1 -1
  61. package/dist/session.d.ts +2 -2
  62. package/dist/session.js +2 -2
  63. package/dist/skills.d.ts +3 -3
  64. package/dist/skills.js +1 -1
  65. package/dist/skills.js.map +1 -1
  66. package/dist/{stats-Lc3zL3RM.js → stats-DAKBEKjc.js} +12 -2
  67. package/dist/stats-DAKBEKjc.js.map +1 -0
  68. package/dist/{stdio-loader-EVAF5KlU.js → stdio-loader-Ce68wUmM.js} +4 -4
  69. package/dist/stdio-loader-Ce68wUmM.js.map +1 -0
  70. package/dist/tool-formatters-CU-j3a3e.d.ts +1471 -0
  71. package/dist/tool-formatters-CU-j3a3e.d.ts.map +1 -0
  72. package/dist/tools/fetch-url.d.ts +70 -0
  73. package/dist/tools/fetch-url.d.ts.map +1 -0
  74. package/dist/tools/fetch-url.js +2 -0
  75. package/dist/tools/web-search.d.ts +7 -0
  76. package/dist/tools/web-search.d.ts.map +1 -0
  77. package/dist/tools/web-search.js +190 -0
  78. package/dist/tools/web-search.js.map +1 -0
  79. package/dist/{tools-DhrLrOEr.js → tools-BGtJK0vo.js} +1368 -421
  80. package/dist/tools-BGtJK0vo.js.map +1 -0
  81. package/dist/tools.d.ts +3 -3
  82. package/dist/tools.js +1 -1
  83. package/dist/{turn-operations-UAkOjO-u.js → transcript-anchors-BTSZAPVc.js} +147 -2713
  84. package/dist/transcript-anchors-BTSZAPVc.js.map +1 -0
  85. package/dist/{transcript-anchors-D0TR6djV.d.ts → transcript-anchors-DX90kXc4.d.ts} +13 -1299
  86. package/dist/transcript-anchors-DX90kXc4.d.ts.map +1 -0
  87. package/dist/tui.d.ts +58 -28
  88. package/dist/tui.d.ts.map +1 -1
  89. package/dist/tui.js +1349 -422
  90. package/dist/tui.js.map +1 -1
  91. package/dist/turn-operations-CCHfR9eC.js +1938 -0
  92. package/dist/turn-operations-CCHfR9eC.js.map +1 -0
  93. package/dist/turn-operations-DDIl4YVk.d.ts +658 -0
  94. package/dist/turn-operations-DDIl4YVk.d.ts.map +1 -0
  95. package/dist/{types-oKPBdCmL.js → types-BPw_i5vb.js} +1 -1
  96. package/dist/types-BPw_i5vb.js.map +1 -0
  97. package/dist/{types-KukEp-mi.d.ts → types-CEAMIUXw.d.ts} +1 -1
  98. package/dist/types-CEAMIUXw.d.ts.map +1 -0
  99. package/dist/types.d.ts +4 -4
  100. package/dist/types.js +3 -3
  101. package/docs/CHAT.md +53 -6
  102. package/docs/SKILL.md +3 -0
  103. package/docs/TUI.md +7 -0
  104. package/package.json +18 -2
  105. package/dist/agent-ClkpElCZ.d.ts.map +0 -1
  106. package/dist/index-CTDMMdIy.d.ts.map +0 -1
  107. package/dist/index-CbS75MD3.d.ts.map +0 -1
  108. package/dist/index-v3Tzobqr.d.ts.map +0 -1
  109. package/dist/login-7tHcckmX.js.map +0 -1
  110. package/dist/mcp-DGeB7-3D.js.map +0 -1
  111. package/dist/messages-Dym8S_YH.js.map +0 -1
  112. package/dist/providers-beXyD9W9.js.map +0 -1
  113. package/dist/stats-Lc3zL3RM.js.map +0 -1
  114. package/dist/stdio-loader-EVAF5KlU.js.map +0 -1
  115. package/dist/tools-DhrLrOEr.js.map +0 -1
  116. package/dist/transcript-anchors-D0TR6djV.d.ts.map +0 -1
  117. package/dist/turn-operations-UAkOjO-u.js.map +0 -1
  118. package/dist/types-KukEp-mi.d.ts.map +0 -1
  119. package/dist/types-oKPBdCmL.js.map +0 -1
@@ -0,0 +1,1471 @@
1
+ import { lt as Provider, mn as ThinkingLevel } from "./agent-BNS2nx_T.js";
2
+ import { OAuthProviderInterface } from "@earendil-works/pi-ai/oauth";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/chat/providers.d.ts
6
+ /**
7
+ * Structural model metadata — compatible with pi-ai's `Model` interface but
8
+ * not coupled to it, so hosts can pass either pi-ai-shaped objects or a
9
+ * custom registry of their own.
10
+ *
11
+ * Deliberately a structural duplicate of pi-ai's `Model` rather than a
12
+ * re-export: keeps the public API stable when pi-ai bumps shape, and lets
13
+ * hosts implement their own registry without depending on pi-ai's types.
14
+ *
15
+ * Lives here (rather than `./config`) because `ProviderDescriptor.models`
16
+ * needs it — pushing it to `config.tsx` created an import cycle.
17
+ */
18
+ interface ModelInfo {
19
+ id: string;
20
+ name?: string;
21
+ contextWindow: number;
22
+ maxTokens?: number;
23
+ reasoning?: boolean;
24
+ input?: readonly ('text' | 'image')[];
25
+ cost?: {
26
+ input: number;
27
+ output: number;
28
+ cacheRead?: number;
29
+ cacheWrite?: number;
30
+ };
31
+ provider?: string;
32
+ /**
33
+ * Model-specific toggleable knobs surfaced in the UI (e.g. Anthropic's
34
+ * "fast mode"). Each option is an independent boolean the user flips on the
35
+ * active model; enabled options are collected into `StreamOptions.modelOptions`
36
+ * keyed by {@link ModelOption.id} and applied to the wire by the provider.
37
+ * Omitted / empty → the model has no custom options and the picker is hidden.
38
+ */
39
+ options?: readonly ModelOption[];
40
+ }
41
+ /**
42
+ * A single toggleable model option. Declarative metadata only — the provider
43
+ * owns the actual request-body translation (keyed on {@link id}). Kept generic
44
+ * (not "fast mode"-specific) so new per-model knobs land without a type change.
45
+ */
46
+ interface ModelOption {
47
+ /** Stable identifier; the key under which the toggle persists + reaches the provider. */
48
+ id: string;
49
+ /** Short label shown in the picker (e.g. "Fast mode"). */
50
+ label: string;
51
+ /** One-line description of the trade-off (shown in the picker row). */
52
+ description?: string;
53
+ /**
54
+ * Optional cost multipliers applied to the model's base rates while the
55
+ * option is enabled. `{ input: 2, output: 2 }` doubles both. Used by the
56
+ * cost estimator so the footer reflects premium-priced modes (fast mode).
57
+ */
58
+ costMultiplier?: {
59
+ input?: number;
60
+ output?: number;
61
+ cacheRead?: number;
62
+ cacheWrite?: number;
63
+ };
64
+ }
65
+ /**
66
+ * Free-form configuration field that a provider needs the user to supply
67
+ * alongside (or instead of) an API key — e.g. a self-hosted base URL, an
68
+ * Azure deployment name, a default model id.
69
+ *
70
+ * The wizard prompts for each field in order. Submitted values are persisted
71
+ * into the apikey credential (as `customFields[key]`) and mirrored into
72
+ * `process.env[envVar]` so the provider's factory can read them via the same
73
+ * env-var convention as `envKey`.
74
+ *
75
+ * Descriptors without an `envKey` use `customFields` as the sole credential
76
+ * mechanism — the wizard skips its API-key step in that case.
77
+ */
78
+ interface CustomField {
79
+ /** Storage key inside the credential's `customFields` record. */
80
+ key: string;
81
+ /** Human-readable label shown in the wizard step's title. */
82
+ label: string;
83
+ /** Env var name to mirror this field into at TUI launch + on submit. */
84
+ envVar: string;
85
+ /** Optional placeholder shown in the wizard input. */
86
+ placeholder?: string;
87
+ /** Optional helper copy rendered above the input. */
88
+ hint?: string;
89
+ /** When true, an empty submission is rejected by the wizard. Default: false. */
90
+ required?: boolean;
91
+ }
92
+ interface ProviderDescriptor {
93
+ /**
94
+ * Unique identifier. Used as the registry key, persisted in `state.json`
95
+ * as the resumed provider, and (by default) as the credential-file key.
96
+ */
97
+ key: string;
98
+ /** Display name shown in the TUI's provider picker and labels. */
99
+ label: string;
100
+ /**
101
+ * Factory that builds a fresh `Provider` instance. Called on every session
102
+ * activation. Some factories (e.g. zidane's built-in `anthropic`) eagerly
103
+ * resolve credentials at construction time and throw when none are
104
+ * configured — set {@link defaultModel} on the descriptor to avoid the TUI
105
+ * calling the factory before the user has picked + authed a provider.
106
+ */
107
+ factory: () => Provider;
108
+ /**
109
+ * Default model id to seed the picker with. When omitted, the TUI falls
110
+ * back to `descriptor.factory().meta.defaultModel`, which constructs the
111
+ * provider — fine for lazy-credential factories, but breaks for factories
112
+ * that throw on missing credentials before the wizard runs. Setting this
113
+ * eagerly lets the TUI render the auth wizard without ever instantiating
114
+ * the provider.
115
+ */
116
+ defaultModel?: string;
117
+ /**
118
+ * Env var checked when detecting whether the user has an API key
119
+ * configured for this provider. Optional — set to `undefined` when the
120
+ * provider only supports OAuth (or only credentials via a custom path).
121
+ */
122
+ envKey?: string;
123
+ /**
124
+ * Key under which credentials live in `credentials.json`. Defaults to
125
+ * `key`. The only built-in that overrides this is OpenAI Codex
126
+ * (`openai` → `openai-codex`) to stay compatible with the harness
127
+ * provider's existing lookup.
128
+ */
129
+ credentialFileKey?: string;
130
+ /** Placeholder shown in the wizard's API-key input. */
131
+ apiKeyPlaceholder?: string;
132
+ /**
133
+ * pi-ai (or compat) OAuth provider. When present, the wizard offers an
134
+ * OAuth option in addition to API key.
135
+ */
136
+ oauthProvider?: OAuthProviderInterface;
137
+ /**
138
+ * Optional copy appended to the wizard's "OAuth" method description.
139
+ * Use to communicate why a user might pick OAuth (e.g. subscription tier,
140
+ * org-wide SSO). Shown after `browser-based sign-in`. Skip the leading
141
+ * space — the wizard adds it. Example: `'Claude Pro/Max subscription'`.
142
+ */
143
+ oauthHint?: string;
144
+ /**
145
+ * pi-ai provider id used to look up models in pi-ai's built-in registry.
146
+ * Defaults to `key`. Only Codex differs (`openai` → `openai-codex`).
147
+ */
148
+ piProviderId?: string;
149
+ /**
150
+ * Override the model list returned for this provider's picker. When set,
151
+ * skips pi-ai's registry entirely. Useful for hosts maintaining their
152
+ * own model catalogue or for custom providers pi-ai doesn't know about.
153
+ */
154
+ models?: readonly ModelInfo[];
155
+ /**
156
+ * Additional free-form fields the wizard should collect from the user
157
+ * (e.g. base URL for a self-hosted endpoint, default model slug for a
158
+ * provider with no fixed catalogue). Each field is persisted in the
159
+ * apikey credential's `customFields` map and mirrored into the matching
160
+ * `envVar` at TUI launch.
161
+ *
162
+ * When `envKey` is also set, the wizard collects custom fields **before**
163
+ * the API key. When `envKey` is omitted, the wizard skips the API-key
164
+ * step entirely — `customFields` becomes the sole credential mechanism
165
+ * (used by the `local` provider, which authenticates via baseURL only).
166
+ */
167
+ customFields?: readonly CustomField[];
168
+ /**
169
+ * Extra models merged AHEAD of pi-ai's registry list (de-duped by id, pi-ai
170
+ * wins on collision). Unlike {@link models} this augments rather than
171
+ * replaces — used to surface freshly-shipped models pi-ai hasn't bundled
172
+ * yet without losing the rest of the registry. Ignored when {@link models}
173
+ * is set.
174
+ */
175
+ extraModels?: readonly ModelInfo[];
176
+ /**
177
+ * Resolve the custom {@link ModelOption}s a given model id supports. Used to
178
+ * attach options (e.g. fast mode) to models that come from pi-ai's registry —
179
+ * which {@link extraModels} can't reach because pi-ai wins the id collision.
180
+ * Returns `undefined` / `[]` for models with no options. Models that already
181
+ * carry `options` (e.g. via {@link extraModels}) keep theirs.
182
+ */
183
+ optionsFor?: (modelId: string) => readonly ModelOption[] | undefined;
184
+ /**
185
+ * Env var holding a user-supplied context window (in tokens) for providers
186
+ * that have no model registry to advertise one — chiefly the `local`
187
+ * provider, whose runtime (Ollama, vLLM, …) doesn't expose its loaded
188
+ * model's window over the OpenAI-compat API. When set, {@link getContextWindow}
189
+ * falls back to resolving this env var (via {@link resolveContextWindow})
190
+ * after the registry lookup comes up empty. The value is either a single
191
+ * window (`"32768"`, applied to every model) or a per-model map
192
+ * (`"llama3.1:8b=32768, qwen=128k, 64k"`, with an optional bare fallback).
193
+ * Pair it with a {@link CustomField} that mirrors the user's value into the
194
+ * same `envVar` so the footer's context indicator and auto-compaction work
195
+ * for local models.
196
+ */
197
+ contextWindowEnvVar?: string;
198
+ }
199
+ /** Convenience accessor — returns `credentialFileKey ?? key`. */
200
+ declare function credKeyOf(desc: ProviderDescriptor): string;
201
+ /** Convenience accessor — returns `piProviderId ?? key`. */
202
+ declare function piIdOf(desc: ProviderDescriptor): string;
203
+ declare const anthropicDescriptor: ProviderDescriptor;
204
+ declare const openaiDescriptor: ProviderDescriptor;
205
+ declare const openrouterDescriptor: ProviderDescriptor;
206
+ declare const cerebrasDescriptor: ProviderDescriptor;
207
+ /**
208
+ * Local OpenAI-compatible LLM (Ollama, vLLM, LM Studio, Lemonade, llama.cpp).
209
+ *
210
+ * No fixed base URL or model catalogue — both come from the user via
211
+ * `customFields`. No `envKey`, so the wizard skips the API-key prompt and
212
+ * treats the customFields-only credential as the auth signal (see
213
+ * `applyApiKeyEnv` + `detectAuth`).
214
+ *
215
+ * Vision / cache / reasoning are off by default in the factory — flip them
216
+ * by passing a custom descriptor that calls `local({ capabilities })`.
217
+ *
218
+ * `models` is a getter, not a static list: there's no fixed catalogue to ship,
219
+ * but once the user has configured a default model (mirrored into
220
+ * `LOCAL_LLM_DEFAULT_MODEL` by `applyApiKeyEnv`), we surface it as a
221
+ * single-entry list so the model picker + footer aren't empty. Resolved at
222
+ * access time because the env var is populated at TUI launch, after this
223
+ * module loads. Empty list when unconfigured — the picker hides, the user
224
+ * types a slug at the prompt as before.
225
+ */
226
+ declare const localDescriptor: ProviderDescriptor;
227
+ /**
228
+ * Default provider registry. Passed verbatim when `runTui` is invoked without
229
+ * an explicit `providers` option. Hosts that want to override per-provider
230
+ * metadata can spread this and replace specific entries:
231
+ *
232
+ * ```ts
233
+ * runTui({ providers: { ...BUILTIN_PROVIDERS, anthropic: myOwnAnthropicDescriptor } })
234
+ * ```
235
+ *
236
+ * `cursor` is intentionally NOT registered here: OAuth works, but inference
237
+ * isn't wired (`cursor.stream()` throws — Cursor speaks a protobuf/HTTP-2
238
+ * agent protocol, not OpenAI-compat). Shipping it in the picker only lets users
239
+ * select a model that errors every turn. The descriptor stays exported so a
240
+ * host can opt in (`{ ...BUILTIN_PROVIDERS, cursor: cursorDescriptor }`) once
241
+ * the transport lands — re-add it here at that point.
242
+ */
243
+ declare const BUILTIN_PROVIDERS: Readonly<Record<string, ProviderDescriptor>>;
244
+ /**
245
+ * Resolve the model list for a given provider. Honors `descriptor.models`
246
+ * when set; otherwise queries pi-ai via `descriptor.piProviderId`. Returns
247
+ * `[]` for descriptors with no known mapping (custom providers without a
248
+ * model list) — callers should hide the model picker in that case.
249
+ */
250
+ declare function modelsForDescriptor(descriptor: ProviderDescriptor): readonly ModelInfo[];
251
+ /**
252
+ * Resolve a single model's metadata via the descriptor's model source.
253
+ * Mirrors {@link modelsForDescriptor} routing: descriptor's own list wins,
254
+ * pi-ai's registry is the fallback. Returns `null` when the model isn't
255
+ * known.
256
+ */
257
+ declare function getModelInfo(descriptor: ProviderDescriptor, modelId: string): ModelInfo | null;
258
+ /**
259
+ * Look up the model's max context window via the descriptor's model source.
260
+ * Falls back to {@link ProviderDescriptor.contextWindowEnvVar} (parsed via
261
+ * {@link resolveContextWindow}, which supports a per-model map) when the
262
+ * registry has nothing — this is how local LLMs, whose runtimes don't
263
+ * advertise a window, get one. Returns `null` when neither source resolves a
264
+ * value (custom slugs, providers without a registry or a configured window);
265
+ * callers should hide the context indicator and skip auto-compaction then.
266
+ */
267
+ declare function getContextWindow(descriptor: ProviderDescriptor, modelId: string): number | null;
268
+ /**
269
+ * Reserved output budget subtracted from the raw context window when
270
+ * computing the effective ceiling for compaction / warning thresholds.
271
+ *
272
+ * Aligned with Claude Code's `COMPACT_MAX_OUTPUT_TOKENS = 20_000`
273
+ * (`utils/context.ts:12`) — covers p99.99 of summary + response output.
274
+ * A turn that consumed N input tokens leaves `effectiveWindow - N`
275
+ * headroom for the next prompt's response; once headroom shrinks
276
+ * below the threshold, auto-compaction fires.
277
+ */
278
+ declare const OUTPUT_RESERVE_TOKENS = 20000;
279
+ /**
280
+ * Effective context window — what the next user turn can actually pack
281
+ * before the provider reserves space for the assistant's reply. Equals
282
+ * `rawWindow - OUTPUT_RESERVE_TOKENS`, clamped to `>= 1` so a tiny
283
+ * (or absurd) window doesn't yield zero / negative thresholds.
284
+ *
285
+ * Used by both the footer's context indicator and the auto-compact
286
+ * trigger so the two surfaces agree on "how full are we, really".
287
+ * Pass `null` through unchanged so callers can pipe `getContextWindow`
288
+ * directly without an intermediate check.
289
+ */
290
+ declare function effectiveContextWindow(rawWindow: number | null): number | null;
291
+ /**
292
+ * Whether the given model exposes a reasoning / extended-thinking knob.
293
+ * Drives the TUI's effort picker visibility — the `ctrl+n` shortcut and
294
+ * the bottom-bar `effortName` segment only surface when this is `true`.
295
+ * Returns `false` for unknown models (no registry entry → no advertised
296
+ * capability).
297
+ */
298
+ declare function modelSupportsReasoning(descriptor: ProviderDescriptor, modelId: string): boolean;
299
+ /**
300
+ * Custom {@link ModelOption}s the given model supports (e.g. fast mode), or `[]`
301
+ * when none. Drives the TUI/GUI options picker visibility — surfaces only when
302
+ * non-empty.
303
+ */
304
+ declare function modelOptionsFor(descriptor: ProviderDescriptor, modelId: string): readonly ModelOption[];
305
+ /**
306
+ * Normalize a model-options blob to an enabled-only `{ id: true }` map.
307
+ *
308
+ * Single source of truth shared by every surface that reads or persists model
309
+ * options — the TUI run path, the GUI engine reader, and the GUI IPC handlers.
310
+ * Tolerant of arbitrary input (persisted JSON metadata may be anything): a
311
+ * non-object → `{}`, and only entries strictly equal to `true` survive. This
312
+ * keeps the persisted shape minimal (no `{ fast: false }` noise) and means
313
+ * callers never forward a disabled option to `agent.run`.
314
+ */
315
+ declare function enabledModelOptions(raw: unknown): Record<string, boolean>;
316
+ /**
317
+ * Resolve a model's remembered options for a (re)pick: keep only options the
318
+ * model still declares AND that are enabled, dropping any that no longer apply
319
+ * (e.g. switching away from a model that supported `fast`). Returns `undefined`
320
+ * when nothing applies so callers can omit the field entirely.
321
+ *
322
+ * Shared by the TUI pick path and the launch-time resume path so "which options
323
+ * survive a model switch / relaunch" is decided in exactly one place.
324
+ */
325
+ declare function restoreModelOptions(descriptor: ProviderDescriptor, modelId: string, remembered: Record<string, Record<string, boolean>> | undefined): Record<string, boolean> | undefined;
326
+ //#endregion
327
+ //#region src/chat/auth.d.ts
328
+ /**
329
+ * Provider identifier as known to the TUI. Built-in keys are `anthropic`,
330
+ * `openai`, `openrouter`, `cerebras`, but hosts can register additional
331
+ * keys via {@link ProviderDescriptor} — so the type is open.
332
+ */
333
+ type ProviderKey = string;
334
+ interface AuthMethod {
335
+ source: 'env' | 'oauth' | 'apikey';
336
+ /** Human-readable detail (env var name, expiry timestamp, …). */
337
+ detail: string;
338
+ }
339
+ interface ProviderAuth {
340
+ key: ProviderKey;
341
+ label: string;
342
+ /** True when at least one credential source is present. */
343
+ available: boolean;
344
+ methods: AuthMethod[];
345
+ }
346
+ /**
347
+ * Detect available auth for every registered provider.
348
+ *
349
+ * Resolution order per provider (a method appears in `methods` for each
350
+ * layer that has a credential — the agent itself resolves them in the same
351
+ * order via its provider factories):
352
+ *
353
+ * 1. `kind: 'apikey'` from `credentials.json` (injected into env at TUI launch)
354
+ * 2. explicit env var (descriptor's `envKey`)
355
+ * 3. `kind: 'oauth'` from `credentials.json` (or legacy `cwd/.credentials.json`)
356
+ *
357
+ * Pure read — never refreshes or rewrites the credentials file.
358
+ */
359
+ declare function detectAuth(dataDir: string, registry: Readonly<Record<string, ProviderDescriptor>>, env?: Record<string, string | undefined>): ProviderAuth[];
360
+ //#endregion
361
+ //#region src/chat/completion-core.d.ts
362
+ /**
363
+ * Prompt autocompletion framework — pure core.
364
+ *
365
+ * Provider contract types + the renderer-agnostic span/reference helpers
366
+ * (`findActiveTrigger`, `applyInsert`, `mergeReferences`, `collectReferences`).
367
+ * No React, no node — so a browser-context renderer imports these via
368
+ * `zidane/chat/pure`. The `useCompletion` React hook lives in `./completion`
369
+ * (main barrel) and re-exports everything here for back-compat.
370
+ *
371
+ * Providers plug in by registering a `trigger` character (e.g. `/` for skills,
372
+ * `@` for files) and exposing two operations:
373
+ *
374
+ * 1. `suggest(query)` — return ranked items for the live query.
375
+ * 2. `parseReferences(text)` — find all references to the provider's
376
+ * items in arbitrary text. Used to highlight in-prompt mentions and
377
+ * drive submit-time side effects (activate the skill, attach the
378
+ * file, …).
379
+ */
380
+ /**
381
+ * A discoverable, selectable thing — one row in the autocomplete popover.
382
+ * `TItem` is provider-specific; consumers can inspect `data` when they
383
+ * need the originating payload (e.g. the full `SkillConfig` for tooltips).
384
+ */
385
+ interface CompletionItem<TItem = unknown> {
386
+ /** Stable identifier within the provider. Used as React key + selection equality. */
387
+ id: string;
388
+ /** User-visible primary string ("research"). */
389
+ label: string;
390
+ /** Optional one-line secondary string ("In-depth research with citations"). */
391
+ description?: string;
392
+ /**
393
+ * Text that replaces the active span (trigger + query) on commit. Usually
394
+ * `${trigger}${label}` with a trailing space so the cursor lands ready
395
+ * for the next token.
396
+ */
397
+ insertText: string;
398
+ /** Original provider-specific payload. */
399
+ data: TItem;
400
+ }
401
+ /**
402
+ * A reference to a provider item inside arbitrary prompt text. Producers:
403
+ * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,
404
+ * the run flow for "activate every referenced skill before agent.run()".
405
+ *
406
+ * Spans are half-open `[start, end)` codepoint offsets into the source
407
+ * string. Overlapping spans from the same or different providers are
408
+ * caller-resolved — the helpers below ship a "first wins" merger.
409
+ */
410
+ interface CompletionReference<TItem = unknown> {
411
+ providerId: string;
412
+ start: number;
413
+ end: number;
414
+ itemId: string;
415
+ data: TItem;
416
+ }
417
+ /**
418
+ * Provider contract. Implementations decide their own ranking, fuzzy-match
419
+ * rules, async loading behavior, and reference grammar.
420
+ */
421
+ interface CompletionProvider<TItem = unknown> {
422
+ /** Stable id used for tagging references + React keys. */
423
+ id: string;
424
+ /**
425
+ * Single character that activates this provider. The engine considers a
426
+ * trigger "active" when it appears at the start of the buffer or
427
+ * immediately after whitespace, and is not closed by whitespace before
428
+ * the cursor (so a trigger plus query is a contiguous token).
429
+ */
430
+ trigger: string;
431
+ /** Human-readable name. Reserved for future multi-provider popover headers. */
432
+ label: string;
433
+ /**
434
+ * Returns items for the active `query` (the text between the trigger and
435
+ * the cursor, excluding the trigger itself). Synchronous return is the
436
+ * common case; promises let providers paginate or hit a backend.
437
+ *
438
+ * `signal` is aborted when the query changes or the popover closes —
439
+ * use it to cancel in-flight network calls.
440
+ */
441
+ suggest: (query: string, ctx: CompletionContext, signal: AbortSignal) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>;
442
+ /**
443
+ * Find every reference to this provider's items in `text`. Pure: must not
444
+ * mutate ctx. The TUI calls this on every keystroke for highlighting, so
445
+ * keep it cheap (linear scan, no I/O).
446
+ */
447
+ parseReferences: (text: string, ctx: CompletionContext) => CompletionReference<TItem>[];
448
+ }
449
+ /**
450
+ * Read-only view of the active prompt buffer + cursor passed to provider
451
+ * callbacks. Kept minimal so providers stay portable across renderers.
452
+ */
453
+ interface CompletionContext {
454
+ /** Full prompt text. */
455
+ text: string;
456
+ /** Codepoint offset of the cursor (0-based). */
457
+ cursor: number;
458
+ }
459
+ /**
460
+ * Identified active trigger span. Returned by `findActiveTrigger` so
461
+ * callers can show the popover, query the provider, and on commit replace
462
+ * the span with the selected item's `insertText`.
463
+ */
464
+ interface ActiveTrigger<TItem = unknown> {
465
+ provider: CompletionProvider<TItem>;
466
+ /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */
467
+ query: string;
468
+ /** `[start, end)` — span covered by the trigger + query in the source. */
469
+ span: {
470
+ start: number;
471
+ end: number;
472
+ };
473
+ }
474
+ /**
475
+ * Resolve the provider trigger active at `cursor`, or `null` when none fits.
476
+ *
477
+ * Rules:
478
+ * - The trigger character must sit at position 0 of the buffer OR be
479
+ * preceded by whitespace. This prevents `http://` from triggering the
480
+ * `/`-bound skills provider mid-URL.
481
+ * - The cursor must be at or past the trigger position.
482
+ * - Nothing between the trigger and the cursor may be whitespace (the
483
+ * query is one contiguous token).
484
+ * - The query length is bounded — `maxQueryLength` defaults to 64 — so
485
+ * a runaway buffer scan can't pin the renderer.
486
+ */
487
+ declare function findActiveTrigger<TItem>(text: string, cursor: number, providers: readonly CompletionProvider<TItem>[], options?: {
488
+ maxQueryLength?: number;
489
+ }): ActiveTrigger<TItem> | null;
490
+ /**
491
+ * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the
492
+ * mutated text and the new cursor position (end of insertion).
493
+ */
494
+ declare function applyInsert(text: string, span: {
495
+ start: number;
496
+ end: number;
497
+ }, insertText: string): {
498
+ text: string;
499
+ cursor: number;
500
+ };
501
+ /**
502
+ * Merge reference lists from multiple providers into one ordered list with
503
+ * earlier-start-wins disambiguation when spans overlap. Ties broken by
504
+ * insertion order. Spans are sorted ascending so renderers can walk them
505
+ * sequentially with a cursor through the source string.
506
+ */
507
+ declare function mergeReferences<TItem>(refs: readonly CompletionReference<TItem>[]): CompletionReference<TItem>[];
508
+ /**
509
+ * Collect every provider's references in one pass. Convenience wrapper —
510
+ * the TUI textarea component calls this on every keystroke to highlight
511
+ * in-prompt mentions.
512
+ */
513
+ declare function collectReferences<TItem>(text: string, providers: readonly CompletionProvider<TItem>[], cursor?: number): CompletionReference<TItem>[];
514
+ //#endregion
515
+ //#region src/chat/types.d.ts
516
+ type Screen = 'auth' | 'sessions' | 'chat';
517
+ /** Identifies the agent that produced a streaming event. */
518
+ type Owner = 'parent' | string;
519
+ interface StreamEvent {
520
+ kind: 'thinking' | 'tool' | 'tool-result' | 'error'
521
+ /**
522
+ * Echo of a submitted user prompt — the `❯` chevron block in the
523
+ * transcript. Distinct from `'info'` (generic informational text)
524
+ * so consumers can count user messages and rebuild prompt history
525
+ * without filtering by text-prefix regex.
526
+ *
527
+ * `text` is the **raw** prompt without the `❯ ` prefix; renderers
528
+ * prepend the chevron. `refs` offsets are aligned to this raw text.
529
+ */
530
+ | 'user-prompt'
531
+ /**
532
+ * Generic informational banner — status notice, transient message.
533
+ * Reserved for non-user content; user prompts use `'user-prompt'`.
534
+ */
535
+ | 'info' | 'separator' | 'markdown' | 'spawn-start' | 'spawn-end'
536
+ /**
537
+ * Compaction boundary — produced by `eventsFromTurns` when a turn
538
+ * carries a `compact-summary` block. Marks the point at which the
539
+ * model's view of history was replaced by an LLM-generated summary.
540
+ * `text` is the summary body; `compact` carries the extra metadata
541
+ * needed for the visual card (replaced-turn count, model, usage).
542
+ *
543
+ * The renderer is free to style this as a collapsed card; the user
544
+ * can still scroll above the marker to see the original turns —
545
+ * compaction never deletes from disk.
546
+ */
547
+ | 'compact-summary'
548
+ /**
549
+ * Background task lifecycle banner. Produced when a task started via
550
+ * `shell({ run_in_background: true })` terminates — natural exit, kill,
551
+ * or `agent.destroy()` teardown. The framework injects a
552
+ * `<task-notification>` text block into the next user-turn so the model
553
+ * sees it inline with its prompt; the renderer suppresses the raw text
554
+ * block in favor of this structured event for a cleaner transcript.
555
+ *
556
+ * `text` is a short human-readable summary (e.g. `npm run build (4.2s)
557
+ * — exited 0`); `task` carries the structured fields the banner reads.
558
+ * On replay (`eventsFromTurns`), the event is re-synthesized from the
559
+ * persisted `<task-notification>` XML inside the user turn's text block;
560
+ * the underlying text block is dropped from the rendered stream so the
561
+ * banner doesn't double-paint alongside it.
562
+ */
563
+ | 'task-notification';
564
+ text: string;
565
+ /**
566
+ * Background task metadata — only set on `task-notification` events.
567
+ * Mirrors the `<task-notification>` XML's fields. Renderer reads this
568
+ * (not `text`) for the structured banner so swapping languages /
569
+ * formats stays a renderer-level concern.
570
+ */
571
+ task?: {
572
+ taskId: string; /** `'exited'` (clean or non-zero) or `'killed'` (we issued SIGTERM). */
573
+ status: 'exited' | 'killed';
574
+ exitCode: number; /** Absolute path to the log file — banner renders an OSC 8 hyperlink to it. */
575
+ outputPath: string; /** Original command, truncated for display by the renderer if long. */
576
+ command: string; /** Wall-clock spawn → exit duration. */
577
+ durationMs: number;
578
+ };
579
+ /**
580
+ * Compaction metadata — only set on `compact-summary` events. Mirrors
581
+ * the underlying `SessionContentBlock` shape but flattened to numbers
582
+ * so the renderer doesn't need to import the agent's TurnUsage type.
583
+ */
584
+ compact?: {
585
+ /** Number of turns the summary replaces (renderer shows "N turns compacted"). */replacedCount: number; /** Model used for the summary call. */
586
+ model: string; /** Unix-ms when compaction completed. */
587
+ compactedAt: number; /** Token usage from the summary call. Renderer formats for display. */
588
+ inputTokens: number;
589
+ outputTokens: number;
590
+ cacheReadTokens: number;
591
+ cacheCreationTokens: number;
592
+ };
593
+ /**
594
+ * For `markdown` events: true while the block is still receiving deltas.
595
+ * Renderers keep their markdown parser in streaming mode while this is
596
+ * true (OpenTUI's `<markdown streaming>` leaves the trailing block
597
+ * unstable to absorb tokens as they arrive). Flipped to false on
598
+ * `turn:after`.
599
+ */
600
+ streaming?: boolean;
601
+ /** Subagent that produced this event. Absent / 'parent' = top-level agent. */
602
+ childId?: string;
603
+ /** Nesting depth — 0 = parent, ≥ 1 = subagent (matches zidane's spawn depth). */
604
+ depth?: number;
605
+ /**
606
+ * Canonical tool name for `tool` / `tool-result` events. Lets the renderer
607
+ * filter by tool — e.g. hide the parent's `spawn` tool-result when
608
+ * `hideSubagentOutput` is on, since the spawn-end marker already shows the
609
+ * same stats. Absent for non-tool events.
610
+ */
611
+ tool?: string;
612
+ /**
613
+ * Matches `tool_use.id` on the assistant turn's `tool_call` block. Lets
614
+ * out-of-process consumers (GUI, SDK, logger, replayer) pair `tool` ↔
615
+ * `tool-result` events by id instead of falling back to fragile positional
616
+ * heuristics within a tool-name bucket — the latter breaks visibly the
617
+ * moment two parallel calls share a tool name (common for `read_file`,
618
+ * `edit`, `multi_edit`).
619
+ *
620
+ * Always set when `kind` is `tool` / `tool-result` / `error` for a real
621
+ * tool call; absent for non-tool events (`markdown`, `thinking`,
622
+ * `user-prompt`, `separator`, `info`, `spawn-start`, `spawn-end`,
623
+ * `compact-summary`) and for `error` events that don't have a callId
624
+ * in scope (provider / session-level failures).
625
+ */
626
+ callId?: string;
627
+ /**
628
+ * Raw tool input arguments — only set on `tool` events. Carried so the
629
+ * renderer can run per-tool formatters (e.g. `↳ Read src/foo.ts L10-25`)
630
+ * without parsing JSON back out of `text`. Plain object, same shape the
631
+ * model emitted; renderers must treat unexpected fields as optional.
632
+ */
633
+ input?: Record<string, unknown>;
634
+ /**
635
+ * Structured payload for `edit` / `multi_edit` / `write_file` tool calls,
636
+ * extracted from the model's `input` and (for `write_file`) paired with
637
+ * a pre-write snapshot read at hook time. Drives the `EditDiffBlock`
638
+ * renderer when {@link Settings.showEditDiffs} is on; absent means
639
+ * "render as a plain `↳ name(args)` line" (the unstructured fallback).
640
+ */
641
+ edit?: EditPayload;
642
+ /**
643
+ * Highlighted spans within `text`, in source order. Used by the renderer
644
+ * to colorize completion references in submitted prompts (`user-prompt`
645
+ * events). Provider-agnostic — the renderer only reads `start`, `end`,
646
+ * `providerId`. Spans are half-open `[start, end)`; overlapping spans
647
+ * are caller-resolved before reaching here. Field name matches
648
+ * `CompletionReference.providerId` and `PromptSegment.providerId` so
649
+ * the same identifier flows end-to-end.
650
+ */
651
+ refs?: readonly {
652
+ start: number;
653
+ end: number;
654
+ providerId: string;
655
+ }[];
656
+ /**
657
+ * Attachment metadata for `user-prompt` events — name, media-type, and
658
+ * byte size of each file the user attached (image drag, multiline paste,
659
+ * etc.). The full content travels in the `PromptPart[]` sent to the
660
+ * model; only lightweight metadata is echoed to the transcript.
661
+ */
662
+ attachments?: readonly {
663
+ name: string;
664
+ mediaType: string;
665
+ size: number;
666
+ }[];
667
+ /**
668
+ * `SessionTurn.id` the event was emitted by — set both on historical
669
+ * events synthesized in `eventsFromTurns` and on live events tagged by
670
+ * the agent hooks (via `StreamHookContext.turnId` / `ToolHookContext.turnId`).
671
+ * Drives the TUI's "select turn" mode: each turn is addressable as a
672
+ * highlightable group of events.
673
+ *
674
+ * Absent on synthetic events that don't map to a turn (`spawn-start`,
675
+ * `spawn-end`, `separator`, etc.) and on events emitted before the
676
+ * first `turn:before` fires on a freshly-mounted session.
677
+ */
678
+ turnId?: string;
679
+ }
680
+ /**
681
+ * Structured edit data attached to a `tool` event for `edit` /
682
+ * `multi_edit` / `write_file`. Lets the renderer compute a unified diff
683
+ * without re-parsing the JSON input string.
684
+ *
685
+ * - `edit` produces one hunk with `replaceAll` defaulting to false.
686
+ * - `multi_edit` produces one hunk per `edits[]` step (in order).
687
+ * - `write_file` produces a single hunk where `oldString` is the
688
+ * pre-write file content (empty for a fresh create) and `newString`
689
+ * is the new content; `replaceAll` is meaningless here and absent.
690
+ *
691
+ * Live capture happens in the TUI's `tool:before` hook (where the
692
+ * pre-write snapshot can be read off disk before the tool runs).
693
+ * Historical replay from persisted turns can only reconstruct the
694
+ * edit/multi_edit shape — write_file historical events have no
695
+ * pre-write content and render with `oldString: ''` (= every line is
696
+ * an addition), matching git's "new file" diff convention.
697
+ */
698
+ interface EditPayload {
699
+ /** Tool name that produced this payload — drives the header glyph + result-suppression matrix. */
700
+ tool: 'edit' | 'multi_edit' | 'write_file';
701
+ /** Workspace-relative file path. Used as the diff header. */
702
+ path: string;
703
+ /** Ordered list of `(old, new)` pairs. Each rendered as one hunk. */
704
+ hunks: readonly EditHunk[];
705
+ /**
706
+ * Per-hunk outcome of the approval / application pipeline, 1:1 with
707
+ * {@link hunks}. Absent on a fresh tool call before the gate decides —
708
+ * the renderer treats absence as "all applied" (the legacy code path
709
+ * predates partial application).
710
+ *
711
+ * Surfaces in the transcript so a partially-approved `multi_edit` shows
712
+ * the denied hunks alongside the applied ones, dimmed / badged. Live
713
+ * approval populates this from the modal's per-edit decisions; replay
714
+ * reconstructs it from the tool_result body (which the tool serializes
715
+ * in a stable line shape — see `multi_edit`).
716
+ */
717
+ outcomes?: readonly EditOutcome[];
718
+ /**
719
+ * Pre-write file content snapshot, captured by the TUI's `tool:before`
720
+ * hook for all three edit tools. Lets the diff renderer build a
721
+ * contextual unified diff with real file line numbers (matching what
722
+ * the user's editor shows) instead of synthetic `@@ -1,N +1,M @@`
723
+ * headers anchored at line 1 within each hunk's snippet.
724
+ *
725
+ * Absent on historical replay (the field isn't persisted — payloads
726
+ * are reconstructed from `input` alone in `eventsFromTurns`). The
727
+ * renderer falls back to `buildUnifiedDiff` in that case.
728
+ *
729
+ * NOT serialized into session turns; lives only on the live in-memory
730
+ * `StreamEvent` to avoid bloating session files with snapshots of
731
+ * every edited file.
732
+ */
733
+ priorContent?: string;
734
+ }
735
+ interface EditHunk {
736
+ oldString: string;
737
+ newString: string;
738
+ /** Mirrored from the tool's `replace_all` flag — surfaced as `× N` in the hunk header. */
739
+ replaceAll?: boolean;
740
+ }
741
+ /**
742
+ * Per-hunk lifecycle state surfaced in the diff/apply viewer. Mirrors the
743
+ * states a `multi_edit` step can be in across the approve → apply pipeline:
744
+ *
745
+ * - `applied` — the gate let it through and the tool wrote it.
746
+ * - `denied` — the user (or an auto-deny rule) refused this specific edit.
747
+ * - `skipped` — the user explicitly stepped past this edit in the modal,
748
+ * or an earlier edit failed and the batch unwound.
749
+ * - `failed` — the gate let it through but the tool body rejected the
750
+ * edit (`old_string` not found, ambiguous, etc.).
751
+ * - `pending` — placeholder used while the modal is open. Never persisted.
752
+ */
753
+ type EditOutcomeKind = 'applied' | 'denied' | 'skipped' | 'failed' | 'pending';
754
+ interface EditOutcome {
755
+ kind: EditOutcomeKind;
756
+ /** Short human-readable explanation surfaced in the transcript badge. */
757
+ reason?: string;
758
+ }
759
+ interface Picked {
760
+ provider: ProviderAuth;
761
+ model: string;
762
+ /**
763
+ * Reasoning effort the user selected for `model`. Only meaningful when
764
+ * the active model exposes reasoning (`ModelInfo.reasoning === true`);
765
+ * `undefined` otherwise. Forwarded to `agent.run` as `thinking` so
766
+ * providers route it to their per-model reasoning control.
767
+ */
768
+ effort?: ThinkingLevel;
769
+ /**
770
+ * Enabled model-specific options for `model`, keyed by option id (e.g.
771
+ * `{ fast: true }`). Only meaningful for models that declare
772
+ * `ModelInfo.options`; reset whenever the model changes so a toggle never
773
+ * leaks onto a model that doesn't support it. Forwarded to `agent.run` as
774
+ * `modelOptions`.
775
+ */
776
+ modelOptions?: Record<string, boolean>;
777
+ }
778
+ interface SessionMeta {
779
+ id: string;
780
+ title: string;
781
+ /** Total turns (user + assistant + system). */
782
+ turnCount: number;
783
+ /** Count of `role: 'user'` turns — the volume of human messages. */
784
+ userMessageCount: number;
785
+ /** Top-level runs recorded against the session (one per `agent.run()` call). */
786
+ runCount: number;
787
+ /**
788
+ * Project this session was created under (see {@link SessionData.projectRoot}).
789
+ * Surfaced on the meta row so the sessions screen can show a
790
+ * per-project label when `Settings.showAllProjects` is on. Absent
791
+ * for legacy pre-tagging sessions.
792
+ */
793
+ projectRoot?: string;
794
+ updatedAt: number;
795
+ }
796
+ /**
797
+ * How `tool` events render in the transcript. Drives `Settings.toolCallDisplay`.
798
+ *
799
+ * - `'hidden'` — `tool` events filtered out entirely (the matching
800
+ * `tool-result` block still shows unless its own toggle suppresses
801
+ * it). Use when the transcript should read like a chat log.
802
+ * - `'formatted'` (default) — each tool renders a clean per-tool
803
+ * summary line (e.g. `↳ Read src/foo.ts`, `↳ Shell git status`).
804
+ * Native tools have curated formatters; unknown / MCP tools fall
805
+ * back to a compact `↳ <name>` line.
806
+ * - `'full'` — debug view: `↳ <name>` header plus the entire raw
807
+ * `input` rendered as syntax-highlighted JSON. Useful for
808
+ * inspecting what the model actually sent.
809
+ */
810
+ type ToolCallDisplay = 'hidden' | 'formatted' | 'full';
811
+ /**
812
+ * How `edit` / `multi_edit` / `write_file` diffs render in the transcript.
813
+ * Gated by {@link Settings.showEditDiffs}; when that's off, the raw `↳ name(args)`
814
+ * line is used regardless of this setting.
815
+ *
816
+ * - `'full'` — current behavior: the entire unified diff body, with
817
+ * line numbers in the gutter. Uses real file line numbers when the
818
+ * pre-write snapshot is available (live calls), synthetic when not
819
+ * (historical replay).
820
+ * - `'compact'` — header line + one short summary line per hunk
821
+ * (`L42 · +2 −1 · old → new`). The full diff body is dropped from
822
+ * the transcript; users open the approval modal or scroll the file
823
+ * to see the changes in detail.
824
+ */
825
+ type EditDiffDisplay = 'compact' | 'full';
826
+ /**
827
+ * Chat-screen chrome density. Drives a small, opinionated set of UI
828
+ * collapses without touching the runtime feature surface:
829
+ *
830
+ * - `'full'` (default) — every shortcut the chat screen advertises is
831
+ * visible: bottom-bar hints (agent / model / skills / session /
832
+ * settings / sessions / update), the prompt-overlay shortcuts
833
+ * (`↵ send`, `shift+↵ newline`, `↑↓ history`, `ctrl+s messages`) +
834
+ * the `@ files` / `/ skills` triggers, and the `ctrl+x session`
835
+ * chip on the title meta.
836
+ * - `'minimal'` — strip the chat session UI to just the controls a
837
+ * user actually keeps hands on:
838
+ * Bottom bar (idle): `agent`, `model/effort`, `keybindings`.
839
+ * Sessions / settings / session-details / update chip are
840
+ * dropped; the full catalog stays one keystroke away via the
841
+ * keybindings panel (`ctrl+y`).
842
+ * Prompt-overlay hints (idle): only the `@ files` / `/ skills`
843
+ * triggers — the resting send/newline/history line is dropped.
844
+ * Title meta: drop the `ctrl+x session` chip. Cwd + message /
845
+ * turn stats stay.
846
+ *
847
+ * Busy / pending / select-turn / queue / approval states keep their
848
+ * full hint sets in both modes — those affordances are load-bearing
849
+ * (abort, navigate, approve) and not the noise the minimal mode is
850
+ * targeting.
851
+ */
852
+ type UiMode = 'full' | 'minimal';
853
+ /** Persisted, user-toggleable transcript filters and modes. */
854
+ interface Settings {
855
+ showThinking: boolean;
856
+ /**
857
+ * Rendering mode for `tool` events. See {@link ToolCallDisplay}.
858
+ * Replaces the legacy `showToolCalls: boolean` (TUI auto-migrates
859
+ * old `state.json` files on first launch).
860
+ */
861
+ toolCallDisplay: ToolCallDisplay;
862
+ showToolResults: boolean;
863
+ /**
864
+ * Safe mode — when enabled, every tool call requires explicit user
865
+ * approval unless it's covered by the project safelist (`projects.json`)
866
+ * or the implicit read-only allow-list. Default: on.
867
+ */
868
+ safeMode: boolean;
869
+ /**
870
+ * Hide a subagent's internal events (its streamed markdown, thinking,
871
+ * tool calls, and tool results) from the transcript. The `🌱` start and
872
+ * `✓` end markers stay visible so the user still sees that a subagent
873
+ * is working and when it finished. Default: on — subagent runs are
874
+ * usually noisy and the parent's final response already summarizes them.
875
+ *
876
+ * When off, those events are visible and wrapped in a dim bordered box
877
+ * for clear visual hierarchy.
878
+ */
879
+ hideSubagentOutput: boolean;
880
+ /**
881
+ * Active theme id — looked up in `BUILTIN_THEMES` to resolve the palette,
882
+ * select styling, syntax highlight tokens, etc. Default: `'default'`.
883
+ * Unknown ids fall back to `DEFAULT_THEME` rather than throwing.
884
+ */
885
+ theme: string;
886
+ /**
887
+ * Show sessions from every project (and pre-tagging legacy ones)
888
+ * in the sessions list, instead of filtering to the current
889
+ * project. Off by default — each project shows only its own
890
+ * conversations. Flip on to inspect / jump across projects without
891
+ * leaving the TUI. Per-row project labels appear when on.
892
+ */
893
+ showAllProjects: boolean;
894
+ /**
895
+ * Auto-resume the previously-active session on launch. Default: on.
896
+ * When off, the TUI always lands on the sessions list (or a fresh
897
+ * session if the list is empty), regardless of what `state.json`
898
+ * remembers as the last session id.
899
+ *
900
+ * The setting is read once on launch — toggling it mid-session
901
+ * affects the NEXT start, not the current one. `state.lastSessionId`
902
+ * is still tracked underneath so flipping the setting back on
903
+ * restores the resume behavior without losing the pointer.
904
+ */
905
+ resumeLastSession: boolean;
906
+ /**
907
+ * Persist large `tool_result` outputs to disk and substitute a
908
+ * `<persisted-output>` stub (preview + filesystem path) inline. Built-in
909
+ * profiles default to 8 KiB threshold via `behavior.persistThreshold`;
910
+ * this setting is the user-facing kill-switch. When off, the TUI
911
+ * overrides `behavior.persistThreshold = 0` at agent build time so the
912
+ * loop's persistence branch never fires.
913
+ *
914
+ * The setting takes effect on the next session activation — toggling
915
+ * mid-stream doesn't tear down an in-flight tool call. Persisted blobs
916
+ * already on disk stay readable via `read_file`; only future tool
917
+ * results are affected by the flip.
918
+ *
919
+ * Default: on.
920
+ */
921
+ persistToolResults: boolean;
922
+ /**
923
+ * Fire {@link compactConversation} automatically when the latest turn's
924
+ * input-token usage crosses {@link Settings.autoCompactThreshold} of the
925
+ * model's effective context window (raw window minus a fixed output
926
+ * reserve — see `OUTPUT_RESERVE_TOKENS` in `chat/providers.ts`).
927
+ *
928
+ * When the trigger fires, the TUI:
929
+ * - Appends an `info` event to the transcript announcing the compaction.
930
+ * - Runs `compactConversation` in the background — the prompt area stays
931
+ * visible so the user can compose the next message while it runs.
932
+ * - The next prompt submission `await`s the in-flight compaction before
933
+ * calling `agent.run()`, so the new run sees the post-compaction
934
+ * message history (no race with the summary's `appendTurns`).
935
+ * - Does NOT auto-continue the conversation after compaction completes
936
+ * — the user controls when the next turn fires.
937
+ *
938
+ * Default: on.
939
+ */
940
+ autoCompact: boolean;
941
+ /**
942
+ * Threshold for {@link Settings.autoCompact}, expressed as a fraction of
943
+ * the effective context window. `0.8` means "fire when the latest turn
944
+ * used 80% of the effective window". Values outside `(0, 1)` are
945
+ * treated as misconfiguration and skip the trigger.
946
+ *
947
+ * The settings modal cycles through `0.6 / 0.7 / 0.8 / 0.9` — coarse
948
+ * enough that users don't get lost, fine enough to tune for verbose
949
+ * workflows (lower) versus context-heavy ones (higher).
950
+ *
951
+ * Default: `0.8`.
952
+ */
953
+ autoCompactThreshold: number;
954
+ /**
955
+ * Render `edit` / `multi_edit` / `write_file` tool calls as a clean
956
+ * unified diff (red `-` / green `+` lines) instead of the raw
957
+ * `↳ name({"path":"…","old_string":"…"})` JSON dump. When on, the
958
+ * paired successful `tool-result` ("Edited path: …") is suppressed
959
+ * too since the diff already conveys success; errors still show.
960
+ *
961
+ * Off falls back to the unstructured `↳ name(args)` line + the
962
+ * regular `tool-result` block — same display the TUI had before
963
+ * the diff feature shipped. Default: on.
964
+ */
965
+ showEditDiffs: boolean;
966
+ /**
967
+ * Density of the edit-diff rendering. See {@link EditDiffDisplay}.
968
+ * Only consulted when {@link showEditDiffs} is on. Default: `'full'`.
969
+ */
970
+ editDiffDisplay: EditDiffDisplay;
971
+ /**
972
+ * Renderer target + max fps (the same value drives both — uncapping
973
+ * `maxFps` past the target just burns CPU on a terminal that's
974
+ * already painting at the target rate). The Settings modal cycles
975
+ * through `30 / 60 / 120`:
976
+ *
977
+ * - `30` — easy on CPU + battery; matches OpenTUI's historical
978
+ * default. Streaming still feels fine; modal animations and
979
+ * cursor blink visibly stutter on fast input.
980
+ * - `60` — recommended default. Matches the refresh rate of
981
+ * virtually every non-ProMotion display; sweet spot between
982
+ * smoothness and load.
983
+ * - `120` — only worth it on a 120Hz / ProMotion display + a
984
+ * modern terminal (Ghostty, Kitty, Alacritty, iTerm2 ≥ 3.5).
985
+ * Costs roughly 2× the CPU of 60 for marginal gains on most
986
+ * setups — sustained 120fps over SSH / nested tmux can add
987
+ * input latency from stdout backpressure.
988
+ *
989
+ * Applied live via `renderer.targetFps` / `renderer.maxFps` — flipping
990
+ * the setting takes effect on the next frame, no restart needed.
991
+ *
992
+ * Default: `60`.
993
+ */
994
+ targetFps: number;
995
+ /**
996
+ * Expose interactive tools (`ask_user`, `present_plan`) to the agent.
997
+ * When off, both tools are omitted from the agent's tool set at session
998
+ * activation — the model can't pause for clarifying questions and can't
999
+ * submit a structured plan, so it must proceed on its best guess and
1000
+ * narrate any plan inline in its final message instead.
1001
+ *
1002
+ * The system prompt also adapts: the `INTERACTION_GUIDANCE` block is
1003
+ * replaced with a short note explaining the lack of interactive tools,
1004
+ * and Plan-mode's "explore → ask → propose" loop becomes
1005
+ * "explore → propose-in-chat".
1006
+ *
1007
+ * Takes effect on the next session activation — flipping mid-stream
1008
+ * doesn't tear down an in-flight `ask_user` / `present_plan` call.
1009
+ * Default: on.
1010
+ */
1011
+ allowInteraction: boolean;
1012
+ /**
1013
+ * Drip-feed the assistant's text content character-by-character at a
1014
+ * smooth cadence (typewriter effect) instead of committing each
1015
+ * provider chunk in batches as it arrives. The display rate is capped
1016
+ * so fast LLMs don't blast a wall of text into the transcript; an
1017
+ * adaptive burst kicks in when the buffer falls behind, and turn
1018
+ * boundaries always flush any remaining buffered content immediately
1019
+ * — so the response never APPEARS slower to finish than it actually
1020
+ * did, just easier to read mid-flight.
1021
+ *
1022
+ * Off falls back to the legacy ~30Hz batched flush (every provider
1023
+ * delta lands in the transcript on the next tick, unsmoothed).
1024
+ *
1025
+ * Takes effect on the next provider delta — flipping mid-stream is
1026
+ * safe; the buffer reads the setting on every tick.
1027
+ *
1028
+ * Default: on.
1029
+ */
1030
+ smoothStreaming: boolean;
1031
+ /**
1032
+ * Show the subtle one-line "in progress todo" indicator above the
1033
+ * prompt input. Hidden entirely when there's no session, no active
1034
+ * run, or no `in_progress` item in the active run's `todowrite` list
1035
+ * — so flipping this off is a clean "I don't want even the bar".
1036
+ *
1037
+ * The dedicated modal (`ctrl+t` by default — see {@link KeyAction})
1038
+ * is unaffected: opening it is an explicit user gesture.
1039
+ *
1040
+ * Default: on.
1041
+ */
1042
+ showTodoIndicator: boolean;
1043
+ /**
1044
+ * Render the inline gradient throbber (cycling hex/symbol glyphs +
1045
+ * optional "Thinking…" label) at the tail of the transcript while a
1046
+ * run is streaming. The throbber is purely decorative — `busy` state
1047
+ * is also surfaced by the prompt's status row and the streaming
1048
+ * markdown's incremental updates, so users who find the animated
1049
+ * glyphs noisy / distracting can flip this off without losing the
1050
+ * "assistant is working" signal.
1051
+ *
1052
+ * Default: off — opt-in to keep the transcript calm by default.
1053
+ */
1054
+ showThrobber: boolean;
1055
+ /**
1056
+ * Quietly check the npm registry for a newer release of the host
1057
+ * package (`zidane-tui` for the shipped binary; any consumer can
1058
+ * pass their own via `<App auto-update>` / `useUpdateCheck`).
1059
+ *
1060
+ * Hot path is cache-driven: one HTTP roundtrip per 24 h, persisted
1061
+ * under `userDir/update-check-<pkg>@<channel>.json`. Boot is never
1062
+ * blocked — the check fires after first paint with a 3-second
1063
+ * timeout and degrades silently on network failure. CI runners
1064
+ * (env `CI`), the npm-ecosystem `NO_UPDATE_NOTIFIER`, and the
1065
+ * project-specific `ZIDANE_NO_UPDATE` env vars all force-disable
1066
+ * regardless of this setting.
1067
+ *
1068
+ * Surfaces in the footer as a passive `↑ vX.Y.Z` chip (renderable
1069
+ * by `useUpdateCheck` and `buildUpdateHint`). The explicit
1070
+ * `zidane upgrade` subcommand bypasses both this toggle and the
1071
+ * env opt-outs (it's an explicit user gesture).
1072
+ *
1073
+ * Default: on.
1074
+ */
1075
+ checkForUpdates: boolean;
1076
+ /**
1077
+ * Chat-screen chrome density. See {@link UiMode} for the contract —
1078
+ * `'full'` keeps every advertised shortcut visible, `'minimal'`
1079
+ * collapses the chat session UI to agent / model / keybindings + the
1080
+ * `@` / `/` triggers, with the rest one keystroke away via `ctrl+y`.
1081
+ * Default: `'full'`.
1082
+ */
1083
+ uiMode: UiMode;
1084
+ /**
1085
+ * Which scope(s) of personal instructions to inject into the system
1086
+ * prompt at session activation. Discovered files:
1087
+ *
1088
+ * - `'none'` — skip discovery entirely. No `AGENTS.md` / `CLAUDE.md`
1089
+ * files are loaded. Useful for reproducible reviews,
1090
+ * demos, debugging the base system prompt, or running
1091
+ * zidane against a repo whose committed `AGENTS.md`
1092
+ * you don't trust yet.
1093
+ * - `'user'` — `~/.agents/AGENTS.md`, `~/.{prefix}/AGENTS.md`,
1094
+ * `~/.claude/CLAUDE.md`. Cross-project preferences
1095
+ * (coding style, "always run lint before commit", …).
1096
+ * - `'project'` — `<root>/AGENTS.md`, `<root>/.agents/AGENTS.md`,
1097
+ * `<root>/.{prefix}/AGENTS.md`, `<root>/CLAUDE.md`.
1098
+ * Repo-local context ("use pnpm, not npm").
1099
+ * - `'both'` — union; user files render first (broader), project
1100
+ * second (closer to the conversation, narrower
1101
+ * context wins in the model's eyes).
1102
+ *
1103
+ * Within each enabled scope, EVERY matching file is loaded — no
1104
+ * shadowing. Empty / whitespace-only files are skipped.
1105
+ *
1106
+ * Takes effect on the next session activation. Default: `'both'`.
1107
+ */
1108
+ userInstructionsScope: 'both' | 'none' | 'project' | 'user';
1109
+ /**
1110
+ * Allowlist of skill names to expose to the agent + slash-command picker.
1111
+ * `undefined` means "every discovered skill" (default). An empty array
1112
+ * fully disables the skills subsystem (no scan, no tool injection).
1113
+ */
1114
+ enabledSkills?: readonly string[];
1115
+ /**
1116
+ * Allowlist of MCP server names (from `.{prefix}/mcps.json`). Same
1117
+ * semantics as `enabledSkills`: `undefined` enables every discovered
1118
+ * server; `[]` disables all.
1119
+ */
1120
+ enabledMcps?: readonly string[];
1121
+ /**
1122
+ * Per-server **deny-list** of tool names to hide from the model. Maps
1123
+ * 1:1 onto `McpServerConfig.disabledTools` at agent-construction
1124
+ * time. Polarity is intentionally opposite to `enabledMcps`:
1125
+ *
1126
+ * - missing entry (or `undefined`) → every advertised tool enabled
1127
+ * - `[]` → every advertised tool enabled
1128
+ * - `['big_tool']` → drop `big_tool`; keep the rest
1129
+ *
1130
+ * The deny-list semantics mean a server that ships a new tool in a
1131
+ * later release auto-enables for users who haven't explicitly opted
1132
+ * it out — opposite preference from server-level toggles (where a
1133
+ * new server should NOT silently appear). The TUI's MCP settings
1134
+ * panel writes here when the user unchecks a tool checkbox.
1135
+ *
1136
+ * Composes with `McpServerConfig.disabledTools` on the JSON-side via
1137
+ * union — a tool listed in either is dropped. JSON-side wins on
1138
+ * conflict (you can't re-enable a JSON-pinned tool from the UI).
1139
+ */
1140
+ disabledMcpTools?: Record<string, readonly string[]>;
1141
+ /**
1142
+ * Master switch for the prompt textarea's paste-as-attachment
1143
+ * pipeline. When `true` (default), the host intercepts paste events
1144
+ * and folds:
1145
+ *
1146
+ * - single-line pastes that resolve to a real file on disk
1147
+ * (drag-and-drop or `file://` URLs) into a binary attachment
1148
+ * tagged with the matching MIME type, and
1149
+ * - multiline pastes into a `paste.txt` document attachment so
1150
+ * the prompt textarea doesn't balloon vertically.
1151
+ *
1152
+ * Flip to `false` to opt out entirely — every paste lands inline in
1153
+ * the textarea verbatim, regardless of size, shape, or whether the
1154
+ * content matches a file path on disk. Useful for users who paste
1155
+ * short snippets often and find the auto-attach behavior breaks
1156
+ * their composing flow.
1157
+ *
1158
+ * Doesn't affect the rest of the multimodal pipeline — embedders
1159
+ * that build `PromptPart[]` directly (image messages from a GUI,
1160
+ * dropped files via a custom handler) still flow through unchanged.
1161
+ * Default: on.
1162
+ */
1163
+ attachmentsEnabled: boolean;
1164
+ /**
1165
+ * What happens when the user presses Ctrl+C.
1166
+ *
1167
+ * - `'quit'` — exit immediately (default, legacy behavior).
1168
+ * - `'quit-confirm'` — show a confirmation modal; Enter / y confirms,
1169
+ * Escape / n cancels. A second Ctrl+C while the
1170
+ * modal is open also confirms.
1171
+ * - `'stop-then-quit'` — first press aborts the active run or clears the
1172
+ * input; second consecutive press (within 1.5 s)
1173
+ * exits. If there's nothing to stop and the input
1174
+ * is empty, the first press exits directly.
1175
+ */
1176
+ ctrlCBehavior: 'quit' | 'quit-confirm' | 'stop-then-quit';
1177
+ }
1178
+ //#endregion
1179
+ //#region src/chat/turn-selection.d.ts
1180
+ /** Tools whose `tool-result` event is suppressed when `showEditDiffs` is on. */
1181
+ declare const EDIT_TOOL_NAMES: ReadonlySet<string>;
1182
+ /**
1183
+ * Recognize a tool-result body as carrying NON-success information so the
1184
+ * renderer doesn't suppress it under `showEditDiffs`. Three categories:
1185
+ *
1186
+ * - `edit` → "Edit error: …"
1187
+ * - `write_file` permission errors wrapped by the loop → "Tool failed: …"
1188
+ * - `multi_edit` → legacy single-line error `multi_edit error: …`, OR
1189
+ * a result carrying an `<edit-outcomes>…</edit-outcomes>` annotation
1190
+ * block. The TUI only appends the annotation when at least one hunk
1191
+ * was NOT applied, so its mere presence is the signal — the result
1192
+ * body needs to stay visible next to the diff so the user can read
1193
+ * denial / skip / failure reasons longer than the per-hunk badge.
1194
+ * - Fully-denied gate emit (`[fully denied] <edit-outcomes>…`) likewise
1195
+ * stays visible.
1196
+ *
1197
+ * Exported for unit-testability of the visibility matrix.
1198
+ */
1199
+ declare function isEditErrorResult(text: string): boolean;
1200
+ /**
1201
+ * Per-event visibility — filters honor user toggles and the
1202
+ * `hideSubagentOutput` setting. When subagent output is hidden:
1203
+ * - Child-agent events are filtered down to the `spawn-start` /
1204
+ * `spawn-end` markers so the user still sees "🌱 working… 🌳 done".
1205
+ * - The parent's `tool-result` for `spawn` is hidden too. Its body
1206
+ * duplicates `spawn-end`'s stats line *and* the parent's next
1207
+ * markdown turn; showing it again produces an extra
1208
+ * `┃ [sub-agent child-1] Completed …` block users just want gone.
1209
+ *
1210
+ * Renderer-agnostic — returns plain `boolean` so TUI / GUI consumers
1211
+ * can filter events identically.
1212
+ */
1213
+ declare function isVisible(event: StreamEvent, settings: Settings): boolean;
1214
+ /**
1215
+ * Build the `resultTurnId → owningAssistantTurnId` map used by the select-
1216
+ * turn mode to coalesce a tool-call's surrounding turns into ONE navigation
1217
+ * stop.
1218
+ *
1219
+ * Protocol shape: every `tool_call` block in an assistant turn is closed by
1220
+ * a matching `tool_result` block in the *next* user turn (the agent loop's
1221
+ * history validator depends on this). When the next user turn's only events
1222
+ * are `tool-result`s — i.e. it's pure plumbing for the prior assistant
1223
+ * turn — we map it back to that assistant turn here. The select-turn nav
1224
+ * index ({@link selectableTurnIds}) skips owned turns, and the renderer's
1225
+ * highlight gate ({@link isTurnHighlighted}) extends the selection accent
1226
+ * from the assistant turn to the events of any turn it owns. Net effect:
1227
+ *
1228
+ * - Navigation never lands the cursor on a result-only turn whose own
1229
+ * events may be hidden by `showToolResults: false` — the cursor
1230
+ * wouldn't be visible.
1231
+ * - Selecting an assistant turn highlights the call AND its result as
1232
+ * one unit, matching the user's mental model of "one message".
1233
+ *
1234
+ * Owner-lookup is conservative: result-only turns with no matching prior
1235
+ * assistant turn (orphaned — usually because the parent was deleted)
1236
+ * stay selectable so the user can act on them via the turn-details modal.
1237
+ *
1238
+ * Subagent (`childId` set) events are ignored — they live in a separate
1239
+ * conversation tree.
1240
+ */
1241
+ declare function turnSelectionOwnership(events: readonly StreamEvent[]): Map<string, string>;
1242
+ /**
1243
+ * Render-time check: should `event` paint with the selection accent?
1244
+ *
1245
+ * `true` when the event's own turn is selected, OR when the selected turn
1246
+ * `owns` the event's turn via {@link turnSelectionOwnership} (the call and
1247
+ * its tool-result rows highlight together). `false` when nothing is
1248
+ * selected or the relationship doesn't apply.
1249
+ *
1250
+ * Pure. Renderer-agnostic — the TUI's `<Transcript>` uses it; a GUI's
1251
+ * equivalent walks the same rule.
1252
+ */
1253
+ declare function isTurnHighlighted(event: Pick<StreamEvent, 'turnId'>, selectedTurnId: string | null, ownership: ReadonlyMap<string, string>): boolean;
1254
+ /**
1255
+ * Deduplicated, in-order list of **parent-conversation** turn ids that appear
1256
+ * in a rendered transcript — the navigation index for the TUI's select-turn
1257
+ * mode. Three classes of turns are deliberately skipped:
1258
+ *
1259
+ * - **Subagent turns** (`childId` set). Nested execution detail; the
1260
+ * user's mental model of a "message" is the conversational exchange,
1261
+ * not each spawn turn. Also filtered out by `isVisible` under
1262
+ * `hideSubagentOutput: true` — selecting them would highlight nothing.
1263
+ * - **Result-only turns** — see {@link turnSelectionOwnership}. These get
1264
+ * coalesced into the assistant turn that emitted their tool_calls.
1265
+ * - **Settings-hidden turns** (when `settings` is supplied). A turn whose
1266
+ * every event fails {@link isVisible} would render no rows — landing
1267
+ * the cursor there hides it from the user entirely. The check is opt-
1268
+ * in so SDK callers without a Settings object keep the legacy
1269
+ * "everything visible" behavior.
1270
+ *
1271
+ * Synthetic events (separator, spawn-start, spawn-end) have no `turnId` and
1272
+ * are skipped naturally.
1273
+ */
1274
+ declare function selectableTurnIds(events: readonly StreamEvent[], settings?: Settings): string[];
1275
+ //#endregion
1276
+ //#region src/chat/safe-mode-context.d.ts
1277
+ /**
1278
+ * Outcome of an approval prompt. Four bulk decisions match the original
1279
+ * single-edit modal contract; `partial` is the multi-edit branch — the
1280
+ * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.
1281
+ *
1282
+ * `partial.mask` is 1:1 with the call's hunks (in input order): `true`
1283
+ * means apply, `false` means deny. The gate handler walks the mask to
1284
+ * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the
1285
+ * approved subset and the renderer reconstructs the full per-hunk view
1286
+ * from the `<edit-outcomes>` annotation appended to the tool result.
1287
+ */
1288
+ type ApprovalDecision = 'accept-once' | 'accept-session' | 'accept-safelist' | 'deny' | {
1289
+ kind: 'partial';
1290
+ mask: readonly boolean[];
1291
+ };
1292
+ /**
1293
+ * Identifies the caller behind an approval prompt — the parent agent or
1294
+ * a specific subagent (`child-N`). The modal's right-side title pins the
1295
+ * label so the user knows which agent issued the call when subagents
1296
+ * bubble their gates up through the parent's hook bus.
1297
+ */
1298
+ type ApprovalOriginator = {
1299
+ kind: 'parent';
1300
+ } | {
1301
+ kind: 'child';
1302
+ label: string;
1303
+ };
1304
+ interface ApprovalRequest {
1305
+ id: string;
1306
+ tool: string;
1307
+ input: Record<string, unknown>;
1308
+ resolve: (decision: ApprovalDecision) => void;
1309
+ /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */
1310
+ originator?: ApprovalOriginator;
1311
+ }
1312
+ /** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */
1313
+ type RequestApproval = (tool: string, input: Record<string, unknown>, originator?: ApprovalOriginator) => Promise<ApprovalDecision>;
1314
+ interface SafeModeActions {
1315
+ /** Request a decision; resolves once the user picks. */
1316
+ requestApproval: RequestApproval;
1317
+ /** Resolve the head and shift the queue forward. */
1318
+ resolveHead: (decision: ApprovalDecision) => void;
1319
+ /** Resolve all pending with `deny`. Used on abort / hard exit. */
1320
+ denyAll: () => void;
1321
+ }
1322
+ /**
1323
+ * Owns the queue + actions. Splits the value across two contexts so a queue
1324
+ * change doesn't invalidate every callback memo that closes over the actions.
1325
+ */
1326
+ declare function SafeModeProvider({
1327
+ children
1328
+ }: {
1329
+ children: ReactNode;
1330
+ }): import("react/jsx-runtime").JSX.Element;
1331
+ declare function useSafeModeQueue(): readonly ApprovalRequest[];
1332
+ declare function useSafeModeActions(): SafeModeActions;
1333
+ //#endregion
1334
+ //#region src/chat/prompt-segments.d.ts
1335
+ /**
1336
+ * Pure string + reference math for rendering a submitted prompt with chip
1337
+ * pills around completion references. Renderer-agnostic — the TUI walks
1338
+ * the segments into OpenTUI `<text>` nodes, a GUI walks them into JSX
1339
+ * spans + `<span class="chip">` pills. No layout engine assumptions.
1340
+ */
1341
+ /**
1342
+ * Highlight span — half-open `[start, end)` over the source string.
1343
+ *
1344
+ * Offsets are JS string indices (UTF-16 code units) — the same convention used
1345
+ * everywhere else in the chat layer (`text[i]`, `m.index`, `String.slice`).
1346
+ * Surrogate pairs that straddle a span boundary would render malformed, but
1347
+ * realistic prompts in this surface (terminal text + ASCII triggers) keep that
1348
+ * risk theoretical; callers feeding emoji-heavy content should normalize to
1349
+ * codepoint walks upstream.
1350
+ */
1351
+ interface PromptSegmentRef {
1352
+ start: number;
1353
+ end: number;
1354
+ /** Provider id tagging this span (`'skills'`, `'files'`, …). Free-form. */
1355
+ providerId: string;
1356
+ }
1357
+ /**
1358
+ * One atomic unit emitted by {@link splitPromptSegments}. Plain segments
1359
+ * are word-sized so wraps land cleanly between words; chip segments
1360
+ * carry the source `providerId` (`'skills'`, `'files'`, …) so renderers
1361
+ * can pick per-kind colors without re-walking the ref list.
1362
+ */
1363
+ type PromptSegment = {
1364
+ kind: 'plain';
1365
+ text: string;
1366
+ } | {
1367
+ kind: 'chip';
1368
+ text: string;
1369
+ providerId: string;
1370
+ };
1371
+ /**
1372
+ * Split a prompt buffer into word-sized atomic segments suitable for a
1373
+ * flex-row + flex-wrap renderer (TUI) or a `display: inline` flow with
1374
+ * inline-block chips (GUI). Each chip becomes one segment (atomic —
1375
+ * never broken across rows); each plain run is split into "word +
1376
+ * trailing space" units so wraps land at clean word boundaries.
1377
+ *
1378
+ * Robust to:
1379
+ * - Overlapping refs — sorted by start; later refs that overlap are
1380
+ * dropped via the first-wins rule.
1381
+ * - Out-of-bounds refs — dropped entirely when `end > text.length` or
1382
+ * `start >= text.length`. Partial clipping would silently truncate
1383
+ * a chip's label; the caller is in a better position to surface the
1384
+ * mismatch (typically a stale `refs` array referencing a previous text).
1385
+ * - Whitespace-only plain runs — emitted as their own plain segment
1386
+ * so chip-adjacent-to-chip cases keep the original spacing.
1387
+ *
1388
+ * Word splitter rationale: `\S+\s*` keeps trailing whitespace attached
1389
+ * to its preceding word so wrap boundaries land between words (cleanly).
1390
+ * A leading-whitespace-only segment is captured by `\s+` so we don't
1391
+ * drop it entirely when the plain run starts with a space.
1392
+ */
1393
+ declare function splitPromptSegments(text: string, refs: readonly PromptSegmentRef[]): PromptSegment[];
1394
+ //#endregion
1395
+ //#region src/chat/tool-formatters.d.ts
1396
+ /**
1397
+ * Per-tool display metadata + one-line formatters consumed by any
1398
+ * surface that renders a `tool` event in `'formatted'` mode (see
1399
+ * `Settings.toolCallDisplay`).
1400
+ *
1401
+ * Each native tool gets a curated entry — a `displayName` verb that
1402
+ * reads in sentence case (e.g. "Read", "Shell") and a `format` callback
1403
+ * that pulls the most informative bits out of the model's raw input to
1404
+ * a single scannable line. Unknown tools (MCP servers, host-added
1405
+ * tools, future zidane additions) fall back to {@link formatToolCall}
1406
+ * returning `null` — the renderer then shows a minimal `↳ <name>` line.
1407
+ *
1408
+ * Renderer-agnostic: returns plain data (`{ target, meta }`) so the
1409
+ * TUI's React/OpenTUI surface and any future GUI consumer can paint
1410
+ * the same shape in their own style. Lives in `zidane/chat` because
1411
+ * it has no rendering concerns; the TUI just consumes it.
1412
+ */
1413
+ interface ToolFormatLine {
1414
+ /**
1415
+ * Primary target — typically a path, command, pattern, or
1416
+ * task description. Renderer paints this in the model accent color
1417
+ * so the eye lands on it first.
1418
+ */
1419
+ target?: string;
1420
+ /**
1421
+ * Secondary annotations rendered after the target, joined with
1422
+ * ` · ` separators. Use for line ranges (`L10-25`), limits, flags,
1423
+ * or any short suffix that adds context without bloating the line.
1424
+ */
1425
+ meta?: readonly string[];
1426
+ }
1427
+ interface ToolDisplayMeta {
1428
+ /**
1429
+ * Title-case display verb (e.g. `Read`, `Shell`, `Edit`). When the
1430
+ * label depends on an input field (e.g. `skills_use`'s
1431
+ * `mode: 'activate' | 'deactivate'` → `Enable` / `Disable`), supply
1432
+ * a function instead and read the field defensively. The function
1433
+ * form is called whenever {@link displayNameFor} has the call's
1434
+ * input in scope; callers without input fall back to the function's
1435
+ * `input: undefined` branch, which should return a stable default.
1436
+ */
1437
+ displayName: string | ((input: Record<string, unknown> | undefined) => string);
1438
+ /**
1439
+ * Pull a {@link ToolFormatLine} out of the raw model input. Returns
1440
+ * `null` when the shape isn't what the tool expects (typed defensively
1441
+ * — we never want a malformed call to crash the transcript).
1442
+ */
1443
+ format: (input: Record<string, unknown>) => ToolFormatLine | null;
1444
+ }
1445
+ declare const TOOL_DISPLAY: Readonly<Record<string, ToolDisplayMeta>>;
1446
+ /**
1447
+ * Resolve the display verb for a tool. Native tools use their curated
1448
+ * entry from {@link TOOL_DISPLAY}; everything else gets a sentence-case
1449
+ * version of the raw name (`my_host_tool` → `My host tool`) so an MCP /
1450
+ * host tool still reads cleanly in the transcript without shouting
1451
+ * Title Case at every word.
1452
+ *
1453
+ * MCP convention: every tool surfaced by `mcp/connectMcpServers` is
1454
+ * namespaced as `mcp_<server>_<tool>` (see `src/mcp/index.ts`). The
1455
+ * `mcp_` prefix is plumbing — strip it before casing so the label
1456
+ * reads as `Github create issue` instead of `Mcp github create issue`.
1457
+ * The server name leads, which doubles as a free visual grouping
1458
+ * affordance ("everything starting with `Github` came from the github
1459
+ * MCP server").
1460
+ */
1461
+ declare function displayNameFor(name: string, input?: Record<string, unknown>): string;
1462
+ /**
1463
+ * Run a tool's curated formatter and return the result, or `null` when
1464
+ * no formatter is registered / the input shape doesn't match. Renderer
1465
+ * decides what to do with `null` — typically: show `↳ <displayName>`
1466
+ * with no target / meta tail.
1467
+ */
1468
+ declare function formatToolCall(name: string, input: Record<string, unknown>): ToolFormatLine | null;
1469
+ //#endregion
1470
+ export { anthropicDescriptor as $, SessionMeta as A, collectReferences as B, EditHunk as C, Owner as D, EditPayload as E, CompletionContext as F, ProviderKey as G, mergeReferences as H, CompletionItem as I, CustomField as J, detectAuth as K, CompletionProvider as L, StreamEvent as M, ToolCallDisplay as N, Picked as O, ActiveTrigger as P, ProviderDescriptor as Q, CompletionReference as R, EditDiffDisplay as S, EditOutcomeKind as T, AuthMethod as U, findActiveTrigger as V, ProviderAuth as W, ModelOption as X, ModelInfo as Y, OUTPUT_RESERVE_TOKENS as Z, isEditErrorResult as _, formatToolCall as a, getModelInfo as at, selectableTurnIds as b, splitPromptSegments as c, modelSupportsReasoning as ct, RequestApproval as d, openrouterDescriptor as dt, cerebrasDescriptor as et, SafeModeActions as f, piIdOf as ft, EDIT_TOOL_NAMES as g, useSafeModeQueue as h, displayNameFor as i, getContextWindow as it, Settings as j, Screen as k, ApprovalDecision as l, modelsForDescriptor as lt, useSafeModeActions as m, ToolDisplayMeta as n, effectiveContextWindow as nt, PromptSegment as o, localDescriptor as ot, SafeModeProvider as p, restoreModelOptions as pt, BUILTIN_PROVIDERS as q, ToolFormatLine as r, enabledModelOptions as rt, PromptSegmentRef as s, modelOptionsFor as st, TOOL_DISPLAY as t, credKeyOf as tt, ApprovalRequest as u, openaiDescriptor as ut, isTurnHighlighted as v, EditOutcome as w, turnSelectionOwnership as x, isVisible as y, applyInsert as z };
1471
+ //# sourceMappingURL=tool-formatters-CU-j3a3e.d.ts.map