zidane 5.14.4 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/dist/{acp-24uU2WDs.js → acp-Irby04Us.js} +7 -8
  2. package/dist/{acp-24uU2WDs.js.map → acp-Irby04Us.js.map} +1 -1
  3. package/dist/acp-cli.js +7 -7
  4. package/dist/acp.d.ts +2 -3
  5. package/dist/acp.d.ts.map +1 -1
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-Cch2ayTt.js → agent-DkSmGkDM.js} +10871 -5411
  8. package/dist/agent-DkSmGkDM.js.map +1 -0
  9. package/dist/agent.d.ts +1 -2
  10. package/dist/agent.js +2 -2
  11. package/dist/{anthropic-RGqsJlC8.js → anthropic-CTHsdFG9.js} +46 -6
  12. package/dist/anthropic-CTHsdFG9.js.map +1 -0
  13. package/dist/{auth-C-ms5N2x.js → auth-BDSu_0t3.js} +13 -86
  14. package/dist/auth-BDSu_0t3.js.map +1 -0
  15. package/dist/chat/pure.d.ts +4 -4
  16. package/dist/chat/pure.js +4 -3
  17. package/dist/chat.d.ts +45 -8
  18. package/dist/chat.d.ts.map +1 -1
  19. package/dist/chat.js +9 -6
  20. package/dist/chat.js.map +1 -1
  21. package/dist/completion-core-CXYSVhZo.js +147 -0
  22. package/dist/completion-core-CXYSVhZo.js.map +1 -0
  23. package/dist/{context-breakdown-kO-pDsay.js → context-breakdown-Dzo25N45.js} +2 -23
  24. package/dist/context-breakdown-Dzo25N45.js.map +1 -0
  25. package/dist/contexts/daytona.d.ts +3 -3
  26. package/dist/contexts/daytona.js +1 -1
  27. package/dist/contexts/docker.d.ts +10 -2
  28. package/dist/contexts/docker.d.ts.map +1 -1
  29. package/dist/contexts/docker.js +142 -103
  30. package/dist/contexts/docker.js.map +1 -1
  31. package/dist/contexts/e2b.d.ts +2 -2
  32. package/dist/contexts/e2b.js +1 -1
  33. package/dist/contexts/sandbox.d.ts +1 -1
  34. package/dist/contexts/sandbox.js +4 -1
  35. package/dist/contexts/sandbox.js.map +1 -1
  36. package/dist/{contexts-Lzf5huID.js → contexts-Vone3qQQ.js} +26 -37
  37. package/dist/contexts-Vone3qQQ.js.map +1 -0
  38. package/dist/contexts.d.ts +3 -3
  39. package/dist/contexts.js +1 -1
  40. package/dist/errors.d.ts +1 -2
  41. package/dist/errors.js +1 -1
  42. package/dist/eval.d.ts +1 -1
  43. package/dist/eval.js +3 -3
  44. package/dist/exec-file-size-PXkr-MGA.js +63 -0
  45. package/dist/exec-file-size-PXkr-MGA.js.map +1 -0
  46. package/dist/extensions.d.ts +2 -0
  47. package/dist/extensions.js +2 -0
  48. package/dist/generate-data-WL_yGVL6.js +324 -0
  49. package/dist/generate-data-WL_yGVL6.js.map +1 -0
  50. package/dist/headless.d.ts +1 -1
  51. package/dist/headless.js +15 -10
  52. package/dist/headless.js.map +1 -1
  53. package/dist/index-B8UtiZy7.d.ts +14967 -0
  54. package/dist/index-B8UtiZy7.d.ts.map +1 -0
  55. package/dist/{index-B3ciFUZx.d.ts → index-DsPQbyUn.d.ts} +2 -2
  56. package/dist/index-DsPQbyUn.d.ts.map +1 -0
  57. package/dist/index.d.ts +6 -12
  58. package/dist/index.js +20 -43
  59. package/dist/index.js.map +1 -1
  60. package/dist/{logger-Ktm-lj1s.js → logger-DBX9uYOw.js} +108 -5
  61. package/dist/logger-DBX9uYOw.js.map +1 -0
  62. package/dist/login-DOF8pMVM.js +539 -0
  63. package/dist/login-DOF8pMVM.js.map +1 -0
  64. package/dist/{mcp-Dn5W65Lv.js → mcp-CcvuVXB9.js} +2 -2
  65. package/dist/{mcp-Dn5W65Lv.js.map → mcp-CcvuVXB9.js.map} +1 -1
  66. package/dist/mcp.d.ts +1 -1
  67. package/dist/mcp.js +1 -1
  68. package/dist/{messages-YZQLKaz2.js → messages-CwujiS74.js} +4 -4
  69. package/dist/messages-CwujiS74.js.map +1 -0
  70. package/dist/{openai-compat-0Y7jcncn.js → openai-compat-BvgYGSR1.js} +7 -6
  71. package/dist/{openai-compat-0Y7jcncn.js.map → openai-compat-BvgYGSR1.js.map} +1 -1
  72. package/dist/output/stream-json.d.ts +1 -2
  73. package/dist/output/stream-json.d.ts.map +1 -1
  74. package/dist/output/terminal.d.ts +1 -2
  75. package/dist/output/terminal.d.ts.map +1 -1
  76. package/dist/{glob-DluQFSw1.js → output-cap-kBUHBlnq.js} +50 -2
  77. package/dist/output-cap-kBUHBlnq.js.map +1 -0
  78. package/dist/presets.d.ts +1 -2
  79. package/dist/presets.js +1 -1
  80. package/dist/providers/anthropic.d.ts +2 -2
  81. package/dist/providers/anthropic.js +2 -2
  82. package/dist/providers/arcee.d.ts +1 -1
  83. package/dist/providers/arcee.js +1 -1
  84. package/dist/providers/baseten.d.ts +1 -1
  85. package/dist/providers/baseten.js +1 -1
  86. package/dist/providers/cerebras.d.ts +1 -1
  87. package/dist/providers/cerebras.js +1 -1
  88. package/dist/providers/local.d.ts +1 -1
  89. package/dist/providers/local.js +1 -1
  90. package/dist/providers/openai-compat.d.ts +1 -1
  91. package/dist/providers/openai-compat.js +1 -1
  92. package/dist/providers/openai.d.ts +1 -1
  93. package/dist/providers/openai.js +4 -4
  94. package/dist/providers/openai.js.map +1 -1
  95. package/dist/providers/openrouter.d.ts +1 -1
  96. package/dist/providers/openrouter.js +1 -1
  97. package/dist/providers/xai.d.ts +1 -1
  98. package/dist/providers/xai.js +1 -1
  99. package/dist/{providers-DgmdYGKm.js → providers-z6LDMAr3.js} +3 -3
  100. package/dist/{providers-DgmdYGKm.js.map → providers-z6LDMAr3.js.map} +1 -1
  101. package/dist/providers.d.ts +1 -1
  102. package/dist/providers.js +4 -4
  103. package/dist/{read-state-BymF41Jb.js → read-state-Dq_TXUX1.js} +2 -2
  104. package/dist/{read-state-BymF41Jb.js.map → read-state-Dq_TXUX1.js.map} +1 -1
  105. package/dist/{interpolate-BtIgcCuz.js → resolve-Cr8JSL_O.js} +562 -136
  106. package/dist/resolve-Cr8JSL_O.js.map +1 -0
  107. package/dist/restate.d.ts +2 -2
  108. package/dist/restate.js +2 -2
  109. package/dist/{sandbox-Dgy-m6UF.d.ts → sandbox-CgjG2VvQ.d.ts} +2 -2
  110. package/dist/{sandbox-Dgy-m6UF.d.ts.map → sandbox-CgjG2VvQ.d.ts.map} +1 -1
  111. package/dist/session/sqlite.d.ts +1 -1
  112. package/dist/{session-CqrAFAKJ.js → session-CsdDIPY8.js} +108 -3
  113. package/dist/session-CsdDIPY8.js.map +1 -0
  114. package/dist/session.d.ts +2 -2
  115. package/dist/session.js +3 -3
  116. package/dist/skills-Bsb0rvWI.js +295 -0
  117. package/dist/skills-Bsb0rvWI.js.map +1 -0
  118. package/dist/skills.d.ts +2 -3
  119. package/dist/skills.js +3 -24
  120. package/dist/{errors-CkTvg97v.d.ts → timeout-8vSIRjzl.d.ts} +10 -2
  121. package/dist/timeout-8vSIRjzl.d.ts.map +1 -0
  122. package/dist/{timeout-kGGSXagK.js → timeout-BDetbuN6.js} +59 -2
  123. package/dist/timeout-BDetbuN6.js.map +1 -0
  124. package/dist/tool-formatters-DY0TDb1r.d.ts +378 -0
  125. package/dist/tool-formatters-DY0TDb1r.d.ts.map +1 -0
  126. package/dist/tools/fetch-url.d.ts +1 -1
  127. package/dist/tools/web-search.d.ts +1 -1
  128. package/dist/tools.d.ts +2 -3
  129. package/dist/tools.js +3 -4
  130. package/dist/{transcript-anchors-BHIgMlPq.d.ts → transcript-anchors-Cb-bDbNH.d.ts} +233 -640
  131. package/dist/transcript-anchors-Cb-bDbNH.d.ts.map +1 -0
  132. package/dist/{transcript-anchors-DsoBHg43.js → transcript-anchors-DSRPWjiH.js} +391 -961
  133. package/dist/transcript-anchors-DSRPWjiH.js.map +1 -0
  134. package/dist/tui.d.ts +335 -11
  135. package/dist/tui.d.ts.map +1 -1
  136. package/dist/tui.js +6375 -1892
  137. package/dist/tui.js.map +1 -1
  138. package/dist/{turn-operations-Dw02ZoSQ.d.ts → turn-operations-CgMROLsr.d.ts} +29 -4
  139. package/dist/turn-operations-CgMROLsr.d.ts.map +1 -0
  140. package/dist/{turn-operations-tG0vuBNc.js → turn-operations-Dq9Kx7m2.js} +321 -81
  141. package/dist/turn-operations-Dq9Kx7m2.js.map +1 -0
  142. package/dist/{types-BqdTeBaC.d.ts → types-BDccavwj.d.ts} +42 -1
  143. package/dist/types-BDccavwj.d.ts.map +1 -0
  144. package/dist/{types-CyVGdbia.js → types-BIarq1qE.js} +90 -2
  145. package/dist/types-BIarq1qE.js.map +1 -0
  146. package/dist/types.d.ts +5 -6
  147. package/dist/types.js +1 -1
  148. package/dist/{xai-tPdbAGkq.js → xai-B_e6TOA8.js} +2 -2
  149. package/dist/{xai-tPdbAGkq.js.map → xai-B_e6TOA8.js.map} +1 -1
  150. package/docs/ARCHITECTURE.md +1 -1
  151. package/docs/CHAT.md +7 -6
  152. package/docs/EXECUTION_CONTEXT.md +16 -0
  153. package/docs/EXTENSIONS.md +1063 -0
  154. package/docs/SKILL.md +16 -0
  155. package/docs/TUI.md +10 -3
  156. package/package.json +12 -3
  157. package/dist/agent-Cch2ayTt.js.map +0 -1
  158. package/dist/agent-EFWt6uQ_.d.ts +0 -6914
  159. package/dist/agent-EFWt6uQ_.d.ts.map +0 -1
  160. package/dist/anthropic-RGqsJlC8.js.map +0 -1
  161. package/dist/auth-C-ms5N2x.js.map +0 -1
  162. package/dist/context-breakdown-kO-pDsay.js.map +0 -1
  163. package/dist/contexts-Lzf5huID.js.map +0 -1
  164. package/dist/errors-CkTvg97v.d.ts.map +0 -1
  165. package/dist/glob-DluQFSw1.js.map +0 -1
  166. package/dist/glob-shell-rJMoCIGb.js +0 -21
  167. package/dist/glob-shell-rJMoCIGb.js.map +0 -1
  168. package/dist/index-B3ciFUZx.d.ts.map +0 -1
  169. package/dist/index-BtzE3Uaq.d.ts +0 -2738
  170. package/dist/index-BtzE3Uaq.d.ts.map +0 -1
  171. package/dist/index-tSzOaWNt.d.ts +0 -316
  172. package/dist/index-tSzOaWNt.d.ts.map +0 -1
  173. package/dist/interpolate-BtIgcCuz.js.map +0 -1
  174. package/dist/logger-DGiGf7DW.d.ts +0 -102
  175. package/dist/logger-DGiGf7DW.d.ts.map +0 -1
  176. package/dist/logger-Ktm-lj1s.js.map +0 -1
  177. package/dist/login-CCA-1lgK.js +0 -267
  178. package/dist/login-CCA-1lgK.js.map +0 -1
  179. package/dist/messages-YZQLKaz2.js.map +0 -1
  180. package/dist/policy-DcGlpaNs.d.ts +0 -129
  181. package/dist/policy-DcGlpaNs.d.ts.map +0 -1
  182. package/dist/presets-BQWYMHdd.js +0 -114
  183. package/dist/presets-BQWYMHdd.js.map +0 -1
  184. package/dist/run-summary-DkN8KsHM.d.ts +0 -249
  185. package/dist/run-summary-DkN8KsHM.d.ts.map +0 -1
  186. package/dist/session-CqrAFAKJ.js.map +0 -1
  187. package/dist/shell-quote-BmnhZmdM.js +0 -33
  188. package/dist/shell-quote-BmnhZmdM.js.map +0 -1
  189. package/dist/skills.js.map +0 -1
  190. package/dist/timeout-CpFm0jJ6.d.ts +0 -10
  191. package/dist/timeout-CpFm0jJ6.d.ts.map +0 -1
  192. package/dist/timeout-kGGSXagK.js.map +0 -1
  193. package/dist/tool-formatters-C6iLgc9f.d.ts +0 -1454
  194. package/dist/tool-formatters-C6iLgc9f.d.ts.map +0 -1
  195. package/dist/tools-CCd8e-HT.js +0 -1739
  196. package/dist/tools-CCd8e-HT.js.map +0 -1
  197. package/dist/transcript-anchors-BHIgMlPq.d.ts.map +0 -1
  198. package/dist/transcript-anchors-DsoBHg43.js.map +0 -1
  199. package/dist/turn-operations-Dw02ZoSQ.d.ts.map +0 -1
  200. package/dist/turn-operations-tG0vuBNc.js.map +0 -1
  201. package/dist/types-BqdTeBaC.d.ts.map +0 -1
  202. package/dist/types-CyVGdbia.js.map +0 -1
@@ -1,26 +1,25 @@
1
- import { s as toolResultToText } from "./types-CyVGdbia.js";
2
- import { u as shell } from "./agent-Cch2ayTt.js";
3
- import { a as multiEdit, c as grep, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles, t as writeFile$1, u as edit } from "./tools-CCd8e-HT.js";
1
+ import { l as toolResultToText } from "./types-BIarq1qE.js";
2
+ import { Ct as listFiles, Dt as edit, Et as glob, Ht as readKeybindingsRaw, I as EMPTY_EXTENSION_REGISTRY, St as multiEdit, Tt as grep, Ut as resolveExtensionKeybindings, Y as buildExtensionPreset, at as definePreset, ct as writeFile$1, ft as createSpawnTool, it as composePresets, kt as resolveStorageDirs, xt as readFile$1, yt as shell, zt as mergeKeybindings } from "./agent-DkSmGkDM.js";
4
3
  import { c as shortId, r as fmtTokens, s as previewLine } from "./format-BNOXpl-1.js";
5
4
  import { l as errorMessage } from "./errors-DJUxZg9b.js";
6
- import { joinSystemPrompt } from "./prompt.js";
5
+ import { M as findGitRoot$1, i as discoverSkills, j as projectUserPaths } from "./resolve-Cr8JSL_O.js";
7
6
  import { t as writeFileAtomic } from "./atomic-write-B7zr1ddF.js";
8
- import { o as discoverSkills } from "./interpolate-BtIgcCuz.js";
9
7
  import { r as formatTokenUsage } from "./stats-DAKBEKjc.js";
10
- import { a as normalizeMcpServers, n as connectMcpServers } from "./mcp-Dn5W65Lv.js";
11
- import { n as definePreset, t as composePresets } from "./presets-BQWYMHdd.js";
8
+ import { joinSystemPrompt } from "./prompt.js";
9
+ import { a as normalizeMcpServers, n as connectMcpServers } from "./mcp-CcvuVXB9.js";
10
+ import { i as findActiveTriggers, n as collectReferences, o as mergeSuggestions, t as applyInsert } from "./completion-core-CXYSVhZo.js";
12
11
  import { r as fetchUrl } from "./fetch-url-CWE8X5OD.js";
13
12
  import { webSearch } from "./tools/web-search.js";
14
- import { b as modelsForDescriptor, i as credentialsPath, n as resolveStorageDirs, r as applyApiKeyEnv, t as detectAuth, u as BUILTIN_PROVIDERS, w as restoreModelOptions, y as modelSupportsReasoning } from "./auth-C-ms5N2x.js";
15
- import { C as collectReferences, J as parseEditOutcomesFromResult, S as applyInsert, et as chatReferenceTokensFromText, u as ownerOf, w as findActiveTrigger, z as extractEditPayload } from "./turn-operations-tG0vuBNc.js";
13
+ import { C as restoreModelOptions, l as BUILTIN_PROVIDERS, n as applyApiKeyEnv, r as credentialsPath, t as detectAuth, v as modelSupportsReasoning, y as modelsForDescriptor } from "./auth-BDSu_0t3.js";
14
+ import { H as extractEditPayload, Z as parseEditOutcomesFromResult, p as ownerOf, rt as chatReferenceTokensFromText, u as capTranscriptEvents } from "./turn-operations-Dq9Kx7m2.js";
16
15
  import { createRequire } from "node:module";
17
16
  import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
18
17
  import { Buffer as Buffer$1 } from "node:buffer";
19
- import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
18
+ import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
20
19
  import { readdir, stat, writeFile } from "node:fs/promises";
21
20
  import { spawn } from "node:child_process";
22
- import { createHash } from "node:crypto";
23
21
  import { homedir, tmpdir } from "node:os";
22
+ import { createHash } from "node:crypto";
24
23
  import { createGunzip } from "node:zlib";
25
24
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
26
25
  import { jsx } from "react/jsx-runtime";
@@ -102,19 +101,26 @@ function titleFromTurns(turns) {
102
101
  return null;
103
102
  }
104
103
  /**
104
+ * Fallback title for a session with no stored `metadata.title` and no
105
+ * turns to derive one from yet — i.e. a freshly created session. Shown
106
+ * in the sessions list, chat header, and details modal until the first
107
+ * message (or an explicitly generated title) gives it a real name.
108
+ */
109
+ const DEFAULT_SESSION_TITLE = "New session";
110
+ /**
105
111
  * Display title for a session — preferred sources, in order:
106
112
  *
107
113
  * 1. `metadata.title` if it's a non-empty string (typically set by
108
114
  * the session-details modal's "generate title" action).
109
115
  * 2. {@link titleFromTurns} — the first user message, one-line + clipped.
110
- * 3. The literal `'untitled'`.
116
+ * 3. {@link DEFAULT_SESSION_TITLE} (`'New session'`).
111
117
  *
112
118
  * Total / pure / defensive on non-string metadata values.
113
119
  */
114
120
  function deriveSessionTitle(turns, metadata) {
115
121
  const stored = typeof metadata?.title === "string" ? metadata.title.trim() : "";
116
122
  if (stored.length > 0) return stored;
117
- return titleFromTurns(turns) ?? "untitled";
123
+ return titleFromTurns(turns) ?? "New session";
118
124
  }
119
125
  /**
120
126
  * Replay persisted turns as a viewable transcript. Mirrors the event shape
@@ -194,7 +200,7 @@ function eventsFromTurns(turns, runs = []) {
194
200
  }
195
201
  }
196
202
  const pending = pendingToolCallsPerRun.get(rid) ?? /* @__PURE__ */ new Set();
197
- for (const block of turn.content) if (block.type === "tool_call") pending.add(block.id);
203
+ for (const block of turn.content) if (block.type === "tool_call" && /spawn/i.test(block.name)) pending.add(block.id);
198
204
  else if (block.type === "tool_result") pending.delete(block.callId);
199
205
  pendingToolCallsPerRun.set(rid, pending);
200
206
  }
@@ -304,7 +310,21 @@ function eventsFromTurns(turns, runs = []) {
304
310
  size: sibling.text.length
305
311
  });
306
312
  }
307
- events.push({
313
+ const commandOrigin = turn.runId ? runById.get(turn.runId)?.origin : void 0;
314
+ const commandDisplay = commandOrigin?.kind === "command" ? commandOrigin.display : void 0;
315
+ if (commandDisplay) {
316
+ events.push({
317
+ kind: "user-prompt",
318
+ text: commandDisplay,
319
+ turnId: turn.id
320
+ });
321
+ events.push({
322
+ kind: "command-detail",
323
+ label: "prompt",
324
+ text: block.text,
325
+ ...tag
326
+ });
327
+ } else events.push({
308
328
  kind: "user-prompt",
309
329
  text: block.text,
310
330
  turnId: turn.id,
@@ -326,6 +346,46 @@ function eventsFromTurns(turns, runs = []) {
326
346
  lastDepthAtEmission = depth;
327
347
  continue;
328
348
  }
349
+ if (turn.role === "command") {
350
+ if (lastDepthAtEmission === 0) events.push({
351
+ kind: "separator",
352
+ text: ""
353
+ });
354
+ for (const block of turn.content) if (block.type === "text" && block.text.trim()) events.push({
355
+ kind: "markdown",
356
+ text: block.text,
357
+ streaming: false,
358
+ ...tag
359
+ });
360
+ else if (block.type === "tool_result") {
361
+ const meta = block.metadata?.command;
362
+ const commandMeta = meta && typeof meta === "object" && !Array.isArray(meta) ? meta : void 0;
363
+ const customMeta = commandMeta?.custom;
364
+ if (customMeta && typeof customMeta === "object" && !Array.isArray(customMeta) && typeof customMeta.renderer === "string") {
365
+ const bound = customMeta;
366
+ events.push({
367
+ kind: "custom",
368
+ text: toolResultText(block.output),
369
+ custom: {
370
+ renderer: bound.renderer,
371
+ ...bound.data !== void 0 ? { data: bound.data } : {}
372
+ },
373
+ ...tag
374
+ });
375
+ continue;
376
+ }
377
+ const label = typeof commandMeta?.label === "string" ? commandMeta.label : void 0;
378
+ events.push({
379
+ kind: "command-detail",
380
+ text: toolResultText(block.output),
381
+ ...label ? { label } : {},
382
+ ...commandMeta?.syntax === "diff" ? { syntax: "diff" } : {},
383
+ ...tag
384
+ });
385
+ }
386
+ lastDepthAtEmission = depth;
387
+ continue;
388
+ }
329
389
  if (turn.role === "assistant") {
330
390
  for (const block of turn.content) if (block.type === "text" && block.text.trim()) events.push({
331
391
  kind: "markdown",
@@ -552,12 +612,14 @@ const MARGIN_TOP = {
552
612
  "thinking": 1,
553
613
  "tool": 1,
554
614
  "tool-result": 0,
615
+ "command-detail": 1,
555
616
  "error": 1,
556
617
  "markdown": 1,
557
618
  "spawn-start": 1,
558
619
  "spawn-end": 0,
559
620
  "compact-summary": 1,
560
- "task-notification": 1
621
+ "task-notification": 1,
622
+ "custom": 1
561
623
  };
562
624
  const TOOL_KINDS = new Set(["tool", "tool-result"]);
563
625
  /**
@@ -1911,62 +1973,6 @@ function singleAgentRegistry(preset) {
1911
1973
  } };
1912
1974
  }
1913
1975
  //#endregion
1914
- //#region src/chat/project-root.ts
1915
- /**
1916
- * Git root detection — walks parents from `cwd` looking for a `.git`
1917
- * entry, returning the absolute path of the repo root or `null` when
1918
- * the search reaches the filesystem root.
1919
- *
1920
- * Why parent-walk over `git rev-parse --show-toplevel`:
1921
- *
1922
- * - Zero dependencies (no shell-out, no git binary requirement,
1923
- * works even when `git` is missing from `PATH`).
1924
- * - Pure-sync, deterministic, easy to test against tmp dirs.
1925
- * - Equally fast in practice — most lookups are a single `existsSync`.
1926
- *
1927
- * Recognizes `.git` as either:
1928
- *
1929
- * - A directory (standard repo layout).
1930
- * - A file containing `gitdir: …` (worktrees + submodules).
1931
- *
1932
- * Bare repos (no working tree) are intentionally NOT detected here —
1933
- * the TUI's data dir contract is "project working tree", and a bare
1934
- * repo has no working files to scope sessions against.
1935
- */
1936
- /**
1937
- * Walk parents of `cwd` looking for a `.git` entry. Returns the
1938
- * absolute path of the directory containing `.git`, or `null` when no
1939
- * git repo is found above `cwd`.
1940
- *
1941
- * Stops at the filesystem root (`/`) — no infinite loop on `dirname`'s
1942
- * idempotent root case (`dirname('/')` returns `/`).
1943
- */
1944
- function findGitRoot$1(cwd = process.cwd()) {
1945
- let dir = resolve(cwd);
1946
- for (let depth = 0; depth < 64; depth++) {
1947
- if (hasGitMarker(dir)) return dir;
1948
- const parent = dirname(dir);
1949
- if (parent === dir) return null;
1950
- dir = parent;
1951
- }
1952
- return null;
1953
- }
1954
- /**
1955
- * `.git` exists as a directory (standard repo) OR as a file containing
1956
- * a `gitdir:` pointer (worktree / submodule). Tolerant on stat failures
1957
- * — a permission error on a parent directory shouldn't crash the walk.
1958
- */
1959
- function hasGitMarker(dir) {
1960
- const candidate = resolve(dir, ".git");
1961
- try {
1962
- if (!existsSync(candidate)) return false;
1963
- const stat = statSync(candidate);
1964
- return stat.isDirectory() || stat.isFile();
1965
- } catch {
1966
- return false;
1967
- }
1968
- }
1969
- //#endregion
1970
1976
  //#region src/chat/agents-md.ts
1971
1977
  /**
1972
1978
  * Personal instructions discovery — load user + project `AGENTS.md` /
@@ -2148,7 +2154,11 @@ function chatAutoCompactBehavior(opts) {
2148
2154
  const contextWindow = typeof opts.rawContextWindow === "number" && Number.isFinite(opts.rawContextWindow) && opts.rawContextWindow > 0 ? opts.rawContextWindow : null;
2149
2155
  return {
2150
2156
  threshold: opts.threshold,
2151
- ...contextWindow ? { contextWindow } : {}
2157
+ ...contextWindow ? { contextWindow } : {},
2158
+ ...opts.summarizer ? {
2159
+ provider: opts.summarizer.provider,
2160
+ model: opts.summarizer.model
2161
+ } : {}
2152
2162
  };
2153
2163
  }
2154
2164
  //#endregion
@@ -3054,17 +3064,22 @@ function useCompletion(input, providers, options = {}) {
3054
3064
  const { text, cursor } = input;
3055
3065
  const { maxQueryLength } = options;
3056
3066
  const [dismissedTriggerStart, setDismissedTriggerStart] = useState(null);
3057
- const rawTrigger = useMemo(() => findActiveTrigger(text, cursor, providers, { maxQueryLength }), [
3067
+ const rawTrigger = useMemo(() => findActiveTriggers(text, cursor, providers, { maxQueryLength }), [
3058
3068
  text,
3059
3069
  cursor,
3060
3070
  providers,
3061
3071
  maxQueryLength
3062
3072
  ]);
3063
- const active = useMemo(() => {
3073
+ const activeMulti = useMemo(() => {
3064
3074
  if (!rawTrigger) return null;
3065
3075
  if (rawTrigger.span.start === dismissedTriggerStart) return null;
3066
3076
  return rawTrigger;
3067
3077
  }, [rawTrigger, dismissedTriggerStart]);
3078
+ const active = useMemo(() => activeMulti ? {
3079
+ provider: activeMulti.providers[0],
3080
+ query: activeMulti.query,
3081
+ span: activeMulti.span
3082
+ } : null, [activeMulti]);
3068
3083
  useEffect(() => {
3069
3084
  if (!rawTrigger) setDismissedTriggerStart(null);
3070
3085
  }, [rawTrigger]);
@@ -3074,7 +3089,7 @@ function useCompletion(input, providers, options = {}) {
3074
3089
  const abortRef = useRef(null);
3075
3090
  useEffect(() => {
3076
3091
  abortRef.current?.abort();
3077
- if (!active) {
3092
+ if (!activeMulti) {
3078
3093
  setItems([]);
3079
3094
  setLoading(false);
3080
3095
  setSelectedIndex(0);
@@ -3087,30 +3102,35 @@ function useCompletion(input, providers, options = {}) {
3087
3102
  cursor
3088
3103
  };
3089
3104
  let cancelled = false;
3090
- const out = active.provider.suggest(active.query, ctx, controller.signal);
3091
- if (Array.isArray(out)) {
3092
- setItems(out);
3105
+ const outs = activeMulti.providers.map((provider) => ({
3106
+ provider,
3107
+ out: provider.suggest(activeMulti.query, ctx, controller.signal)
3108
+ }));
3109
+ if (outs.every((o) => Array.isArray(o.out))) {
3110
+ setItems(mergeSuggestions(outs.map((o) => ({
3111
+ provider: o.provider,
3112
+ items: o.out
3113
+ }))));
3093
3114
  setSelectedIndex(0);
3094
3115
  setLoading(false);
3095
3116
  return;
3096
3117
  }
3097
3118
  setLoading(true);
3098
- out.then((next) => {
3119
+ Promise.allSettled(outs.map(async (o) => ({
3120
+ provider: o.provider,
3121
+ items: await o.out
3122
+ }))).then((results) => {
3099
3123
  if (cancelled) return;
3100
- setItems(next);
3124
+ setItems(mergeSuggestions(results.flatMap((r) => r.status === "fulfilled" ? [r.value] : [])));
3101
3125
  setSelectedIndex(0);
3102
3126
  setLoading(false);
3103
- }, () => {
3104
- if (cancelled) return;
3105
- setItems([]);
3106
- setLoading(false);
3107
3127
  });
3108
3128
  return () => {
3109
3129
  cancelled = true;
3110
3130
  controller.abort();
3111
3131
  };
3112
3132
  }, [
3113
- active,
3133
+ activeMulti,
3114
3134
  text,
3115
3135
  cursor
3116
3136
  ]);
@@ -3160,531 +3180,6 @@ function useCompletion(input, providers, options = {}) {
3160
3180
  ]);
3161
3181
  }
3162
3182
  //#endregion
3163
- //#region src/chat/keybindings.ts
3164
- /**
3165
- * Keybindings — a single source of truth for the app's global shortcuts.
3166
- *
3167
- * Every customizable action is declared once in {@link KEYBINDING_DEFS}
3168
- * with its default trigger, label, and description. The TUI imports
3169
- * `DEFAULT_KEYBINDINGS` for the baked-in defaults; on launch we load
3170
- * `<userDir>/keybindings.json` (when present) and merge user overrides
3171
- * on top so the user can rebind any action without recompiling.
3172
- *
3173
- * Scope (deliberate):
3174
- *
3175
- * - We expose only the **global, action-opening** shortcuts here —
3176
- * "open the Settings modal", "cycle agent profile", "enter
3177
- * select-turn mode", etc. Modal-internal letter shortcuts (`f`
3178
- * fork, `d` delete, `c` copy on the turn-details modal) and
3179
- * universal navigation (`↑/↓/↵/⎋`) stay hardcoded — they live
3180
- * too close to a renderable's input contract to remap safely.
3181
- *
3182
- * - Storage lives at the **user level** (`<userDir>/keybindings.json`),
3183
- * never inside a project's `.{prefix}/`. Shortcut muscle memory is
3184
- * a per-user concern, not a per-repo one.
3185
- *
3186
- * Format on disk: JSON with `//` and `/* … *\/` comments allowed
3187
- * (parsed via a small JSONC stripper). Each entry maps an action id
3188
- * to a binding spec string. Specs are parsed by {@link parseBindingSpec};
3189
- * unknown / malformed entries fall back to the default for that action
3190
- * (with a debug log under `ZIDANE_DEBUG`) so a syntax slip never bricks
3191
- * the TUI.
3192
- */
3193
- /**
3194
- * The canonical action catalog. Order is the source of truth for the
3195
- * generated keybindings file and the settings UI.
3196
- */
3197
- const KEYBINDING_DEFS = [
3198
- {
3199
- action: "openSettings",
3200
- default: "ctrl+o",
3201
- label: "settings",
3202
- description: "open the Settings modal (toggles, theme, keybindings)",
3203
- group: "Global"
3204
- },
3205
- {
3206
- action: "openSessionDetails",
3207
- default: "ctrl+x",
3208
- label: "session",
3209
- description: "open the session details modal (stats, export, delete, rename)",
3210
- group: "Global"
3211
- },
3212
- {
3213
- action: "newSession",
3214
- default: "ctrl+n",
3215
- label: "new session",
3216
- description: "start a fresh chat session with the active provider/model (same as the \"+ new session\" row)",
3217
- group: "Global"
3218
- },
3219
- {
3220
- action: "openModelPicker",
3221
- default: "ctrl+m",
3222
- label: "model",
3223
- description: "open the cross-provider model picker",
3224
- group: "Global"
3225
- },
3226
- {
3227
- action: "openEffortPicker",
3228
- default: "ctrl+l",
3229
- label: "options",
3230
- description: "open the model-options picker — reasoning effort + feature toggles (e.g. fast mode)",
3231
- group: "Global"
3232
- },
3233
- {
3234
- action: "openTodos",
3235
- default: "ctrl+t",
3236
- label: "todos",
3237
- description: "open the active run's todo list (the agent's `todowrite` checkpoints)",
3238
- group: "Global"
3239
- },
3240
- {
3241
- action: "openContextPanel",
3242
- default: "ctrl+g",
3243
- label: "context",
3244
- description: "open the context-usage breakdown (what is taking up the context window)",
3245
- group: "Global"
3246
- },
3247
- {
3248
- action: "openKeybindings",
3249
- default: "ctrl+y",
3250
- label: "keybindings",
3251
- description: "open the keybindings panel — list every shortcut and jump to the keybindings.json file",
3252
- group: "Global"
3253
- },
3254
- {
3255
- action: "cycleAgent",
3256
- default: "shift+tab",
3257
- label: "cycle agent",
3258
- description: "cycle to the next agent profile (when multiple profiles are registered)",
3259
- group: "Global"
3260
- },
3261
- {
3262
- action: "enterSelectTurnMode",
3263
- default: "ctrl+s",
3264
- label: "messages",
3265
- description: "enter select-turn mode to navigate previous messages",
3266
- group: "Global"
3267
- },
3268
- {
3269
- action: "cancelToolCall",
3270
- default: "ctrl+k",
3271
- label: "cancel tool",
3272
- description: "open the in-flight tool picker to cancel a single tool call without aborting the run (esc still aborts the whole run)",
3273
- group: "Global"
3274
- },
3275
- {
3276
- action: "changeCwd",
3277
- default: "ctrl+h",
3278
- label: "cwd",
3279
- description: "open the directory picker to change the working directory",
3280
- group: "Global"
3281
- },
3282
- {
3283
- action: "attachClipboardImage",
3284
- default: "ctrl+v",
3285
- label: "paste image",
3286
- description: "attach an image from the OS clipboard to the prompt (needs a platform helper: pngpaste on macOS, wl-clipboard/xclip on Linux)",
3287
- group: "Global"
3288
- },
3289
- {
3290
- action: "enterQueueSelection",
3291
- default: "",
3292
- label: "select queue",
3293
- description: "move focus from the prompt textarea into the type-ahead queue box (also: ↑ on an empty prompt)",
3294
- group: "Message queue"
3295
- },
3296
- {
3297
- action: "pushQueuedMessage",
3298
- default: "ctrl+return",
3299
- label: "push",
3300
- description: "steer the selected queued message into the live run (delivered between tool calls)",
3301
- group: "Message queue"
3302
- },
3303
- {
3304
- action: "dropQueuedMessage",
3305
- default: "backspace",
3306
- label: "drop",
3307
- description: "remove the selected queued message — exits back to the prompt when the queue empties (the big \"remove\" key works everywhere: backspace on Win/Linux, the laptop key labeled \"delete\" on Mac, which sends backspace)",
3308
- group: "Message queue"
3309
- },
3310
- {
3311
- action: "turnFork",
3312
- default: "f",
3313
- label: "fork",
3314
- description: "fork the session at the selected turn (two-press confirm)",
3315
- group: "Turn details"
3316
- },
3317
- {
3318
- action: "turnDelete",
3319
- default: "d",
3320
- label: "delete",
3321
- description: "delete the selected turn (two-press confirm)",
3322
- group: "Turn details"
3323
- },
3324
- {
3325
- action: "turnCopy",
3326
- default: "c",
3327
- label: "copy",
3328
- description: "copy the selected turn content to the clipboard (OSC 52)",
3329
- group: "Turn details"
3330
- },
3331
- {
3332
- action: "turnEdit",
3333
- default: "e",
3334
- label: "edit",
3335
- description: "edit the text content of the selected turn",
3336
- group: "Turn details"
3337
- },
3338
- {
3339
- action: "sessionDelete",
3340
- default: "d",
3341
- label: "delete",
3342
- description: "delete the entire session (two-press confirm)",
3343
- group: "Session details"
3344
- },
3345
- {
3346
- action: "sessionCopyId",
3347
- default: "c",
3348
- label: "copy id",
3349
- description: "copy the full session id to the clipboard (OSC 52)",
3350
- group: "Session details"
3351
- },
3352
- {
3353
- action: "sessionGenerateTitle",
3354
- default: "g",
3355
- label: "generate title",
3356
- description: "generate a title for the session via the active provider/model",
3357
- group: "Session details"
3358
- },
3359
- {
3360
- action: "sessionExportMarkdown",
3361
- default: "e",
3362
- label: "export md",
3363
- description: "export the session as Markdown under .{prefix}/sessions/",
3364
- group: "Session details"
3365
- },
3366
- {
3367
- action: "sessionExportJson",
3368
- default: "j",
3369
- label: "export json",
3370
- description: "export the session as JSON under .{prefix}/sessions/",
3371
- group: "Session details"
3372
- },
3373
- {
3374
- action: "sessionCompact",
3375
- default: "k",
3376
- label: "compact",
3377
- description: "compact older turns via an LLM summary (model still sees everything from the boundary down)",
3378
- group: "Session details"
3379
- }
3380
- ];
3381
- /** Index by action for O(1) lookup of label / description / default. */
3382
- const KEYBINDING_DEF_BY_ACTION = KEYBINDING_DEFS.reduce((acc, def) => Object.assign(acc, { [def.action]: def }), {});
3383
- /** Defaults, derived from {@link KEYBINDING_DEFS} so the two never drift. */
3384
- const DEFAULT_KEYBINDINGS = KEYBINDING_DEFS.reduce((acc, def) => Object.assign(acc, { [def.action]: def.default }), {});
3385
- /**
3386
- * Aliases the parser accepts on input so the on-disk format can be
3387
- * forgiving (different terminals + docs label modifiers + keys
3388
- * differently). Output names are canonicalized to one form per key so
3389
- * `matchesBinding` only has to compare one canonical against a
3390
- * KeyEvent's `name`. Unknown names pass through verbatim — that's
3391
- * useful for terminal-specific keys we don't have to enumerate.
3392
- */
3393
- const NAME_ALIASES = {
3394
- esc: "escape",
3395
- enter: "return",
3396
- ret: "return",
3397
- space: "space",
3398
- spc: "space",
3399
- del: "delete",
3400
- ins: "insert",
3401
- pgup: "pageup",
3402
- pgdn: "pagedown",
3403
- pagup: "pageup",
3404
- pagedn: "pagedown"
3405
- };
3406
- const MODIFIER_ALIASES = {
3407
- ctrl: "ctrl",
3408
- control: "ctrl",
3409
- shift: "shift",
3410
- alt: "alt",
3411
- option: "alt",
3412
- opt: "alt",
3413
- meta: "meta",
3414
- cmd: "meta",
3415
- command: "meta",
3416
- super: "meta",
3417
- win: "meta"
3418
- };
3419
- /**
3420
- * Parse a `'ctrl+shift+o'`-style spec into structured fields. Returns
3421
- * `null` for empty / malformed inputs; the caller falls back to the
3422
- * default for that action.
3423
- *
3424
- * Tolerant by design:
3425
- * - Case-insensitive (`Ctrl+O` → same as `ctrl+o`).
3426
- * - Modifier order doesn't matter (`shift+ctrl+o` works).
3427
- * - Mixed `+` and `-` separators allowed (`ctrl-o`, `ctrl+o`).
3428
- * - Trailing / leading whitespace is stripped.
3429
- * - Duplicate modifiers collapse silently.
3430
- */
3431
- function parseBindingSpec(spec) {
3432
- if (typeof spec !== "string") return null;
3433
- const trimmed = spec.trim().toLowerCase();
3434
- if (!trimmed) return null;
3435
- const parts = trimmed.split(/[+\-]/).map((p) => p.trim()).filter(Boolean);
3436
- if (parts.length === 0) return null;
3437
- const result = {
3438
- ctrl: false,
3439
- shift: false,
3440
- alt: false,
3441
- meta: false,
3442
- name: ""
3443
- };
3444
- for (const part of parts) {
3445
- const modifier = MODIFIER_ALIASES[part];
3446
- if (modifier) {
3447
- result[modifier] = true;
3448
- continue;
3449
- }
3450
- if (result.name) return null;
3451
- result.name = NAME_ALIASES[part] ?? part;
3452
- }
3453
- if (!result.name) return null;
3454
- return result;
3455
- }
3456
- /**
3457
- * Test a KeyEvent against a parsed-or-string binding. Accepts either
3458
- * form so callers can pre-parse (hot path) or pass the raw spec for
3459
- * one-off checks.
3460
- *
3461
- * Modifier semantics: ALL declared modifiers must be pressed AND no
3462
- * extra modifiers may be pressed. So `'o'` does NOT match `ctrl+o`,
3463
- * and `'ctrl+o'` does NOT match `ctrl+shift+o`. This avoids surprises
3464
- * where a binding that should be specific accidentally fires for a
3465
- * superset of key combinations.
3466
- */
3467
- function matchesBinding(event, spec) {
3468
- const parsed = typeof spec === "string" ? parseBindingSpec(spec) : spec;
3469
- if (!parsed) return false;
3470
- const altPressed = !!event.option || !!event.alt;
3471
- if (parsed.ctrl !== !!event.ctrl) return false;
3472
- if (parsed.shift !== !!event.shift) return false;
3473
- if (parsed.alt !== altPressed) return false;
3474
- if (parsed.meta !== !!event.meta) return false;
3475
- if ((event.name ?? "").toLowerCase() !== parsed.name) return false;
3476
- return true;
3477
- }
3478
- /** Verbose key names → compact single-cell glyphs used by hint rows. */
3479
- const KEY_GLYPHS = {
3480
- up: "↑",
3481
- down: "↓",
3482
- left: "←",
3483
- right: "→",
3484
- return: "↵",
3485
- enter: "↵",
3486
- delete: "⌫",
3487
- backspace: "⌫",
3488
- escape: "esc",
3489
- space: "␣",
3490
- tab: "⇥"
3491
- };
3492
- /**
3493
- * Render a binding spec (`"ctrl+return"`, `"backspace"`, `"delete"`) as a
3494
- * compact display string the user recognizes at a glance. Substitutes
3495
- * arrow / enter / backspace glyphs for their verbose names so the hint
3496
- * row stays narrow; modifier names + plain keys pass through unchanged.
3497
- *
3498
- * Empty / missing specs return an empty string — callers decide whether
3499
- * to render a placeholder (e.g. `'—'` for an unbound action in the
3500
- * keybindings panel) or hide the surface entirely (e.g. hint rows that
3501
- * fall back to a different chord when the binding is empty).
3502
- */
3503
- function formatBindingForDisplay(spec) {
3504
- if (!spec) return "";
3505
- const segments = spec.toLowerCase().split("+");
3506
- const key = segments.pop() ?? "";
3507
- const modifiers = segments.join("+");
3508
- const glyph = KEY_GLYPHS[key] ?? key;
3509
- return modifiers ? `${modifiers}+${glyph}` : glyph;
3510
- }
3511
- /**
3512
- * Merge a partial map of user overrides into the defaults. Unknown
3513
- * action keys are dropped (a future TUI version may have retired the
3514
- * action); invalid spec strings (parse fails) fall back to the default
3515
- * for that action.
3516
- */
3517
- function mergeKeybindings(overrides) {
3518
- if (!overrides || typeof overrides !== "object") return { ...DEFAULT_KEYBINDINGS };
3519
- const merged = { ...DEFAULT_KEYBINDINGS };
3520
- for (const def of KEYBINDING_DEFS) {
3521
- const raw = overrides[def.action];
3522
- if (typeof raw !== "string") continue;
3523
- if (!parseBindingSpec(raw)) continue;
3524
- merged[def.action] = raw;
3525
- }
3526
- return merged;
3527
- }
3528
- /** Path of the user-level keybindings file. */
3529
- function keybindingsPath(userDir) {
3530
- return resolve(userDir, "keybindings.json");
3531
- }
3532
- /**
3533
- * Walk {@link KEYBINDING_DEFS} in order and bucket rows into contiguous
3534
- * sections by `group`. Shared between the standalone `KeybindingsModal`
3535
- * and the `Keybindings` tab inside `SettingsModal` so both surfaces
3536
- * render the same catalog with the same ordering rules.
3537
- */
3538
- function groupBindings(bindings) {
3539
- const sections = [];
3540
- for (const def of KEYBINDING_DEFS) {
3541
- const last = sections[sections.length - 1];
3542
- const spec = bindings[def.action] ?? "";
3543
- if (last && last.group === def.group) {
3544
- last.rows.push({
3545
- def,
3546
- spec
3547
- });
3548
- continue;
3549
- }
3550
- sections.push({
3551
- group: def.group,
3552
- rows: [{
3553
- def,
3554
- spec
3555
- }]
3556
- });
3557
- }
3558
- return sections;
3559
- }
3560
- /**
3561
- * Width (in cells) of the fixed key column used by every binding-row
3562
- * renderer. Derived once from {@link KEYBINDING_DEFS} so a new action
3563
- * with a wider default spec (`ctrl+shift+x`, …) automatically grows
3564
- * the column instead of truncating the label.
3565
- */
3566
- const KEYBINDING_KEY_COL_WIDTH = (() => {
3567
- let max = 8;
3568
- for (const def of KEYBINDING_DEFS) {
3569
- const width = formatBindingForDisplay(def.default).length;
3570
- if (width > max) max = width;
3571
- }
3572
- return max + 2;
3573
- })();
3574
- /**
3575
- * Load + merge the user's keybindings file (when present). Always
3576
- * returns a fully-resolved {@link KeyBindings} — defaults fill in for
3577
- * any missing / invalid entries so consumers can treat the result as
3578
- * total.
3579
- *
3580
- * Failure modes (all tolerated, all fall back to defaults):
3581
- * - File missing
3582
- * - File not valid JSON / JSONC (after comment stripping)
3583
- * - Top-level is not an object
3584
- * - Individual entries have invalid spec strings
3585
- */
3586
- function readKeybindings(userDir) {
3587
- const path = keybindingsPath(userDir);
3588
- if (!existsSync(path)) return { ...DEFAULT_KEYBINDINGS };
3589
- try {
3590
- const raw = readFileSync(path, "utf-8");
3591
- const parsed = JSON.parse(stripJsonComments(raw));
3592
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { ...DEFAULT_KEYBINDINGS };
3593
- return mergeKeybindings(parsed);
3594
- } catch {
3595
- return { ...DEFAULT_KEYBINDINGS };
3596
- }
3597
- }
3598
- /**
3599
- * Create the user-level `keybindings.json` if it doesn't exist,
3600
- * prefilled with every default. Returns the absolute file path either
3601
- * way so the caller can open it in `$EDITOR`. Idempotent: an existing
3602
- * user file is NEVER overwritten — the user's customizations are
3603
- * sacred.
3604
- */
3605
- function ensureKeybindingsFile(userDir) {
3606
- const path = keybindingsPath(userDir);
3607
- if (existsSync(path)) return path;
3608
- writeFileAtomic(path, renderDefaultFile(), { ensureDir: true });
3609
- return path;
3610
- }
3611
- /**
3612
- * Render the default `keybindings.json` body. JSONC with a header
3613
- * explaining the format + per-action comments derived from
3614
- * {@link KEYBINDING_DEFS}'s descriptions. The user sees the catalog +
3615
- * defaults at a glance the moment they open the file.
3616
- */
3617
- function renderDefaultFile() {
3618
- const lines = [];
3619
- lines.push("// Zidane keybindings — edit values below to customize.");
3620
- lines.push("//");
3621
- lines.push("// Format:");
3622
- lines.push("// \"<action>\": \"<modifier>+<modifier>+<key>\"");
3623
- lines.push("//");
3624
- lines.push("// Modifiers: ctrl, shift, alt (option), meta (cmd).");
3625
- lines.push("// Keys: a-z, 0-9, tab, return, escape, space, delete, …");
3626
- lines.push("//");
3627
- lines.push("// Comments (`//` line, `/* block */`) are stripped before parsing.");
3628
- lines.push("// Restart the TUI to apply changes.");
3629
- lines.push("{");
3630
- KEYBINDING_DEFS.forEach((def, i) => {
3631
- const trailingComma = i < KEYBINDING_DEFS.length - 1 ? "," : "";
3632
- lines.push(` // ${def.description}`);
3633
- lines.push(` ${JSON.stringify(def.action)}: ${JSON.stringify(def.default)}${trailingComma}`);
3634
- });
3635
- lines.push("}");
3636
- lines.push("");
3637
- return lines.join("\n");
3638
- }
3639
- /**
3640
- * Strip `//` line comments and `/* … *\/` block comments from a JSONC
3641
- * source string. Stays out of string literals so a `"foo // bar"` value
3642
- * keeps its slashes. Exported for unit-tests; the only runtime caller
3643
- * is {@link readKeybindings}.
3644
- *
3645
- * Trade-off: this is a tiny state-machine, not a full JSONC parser. It
3646
- * doesn't understand `\\` outside strings (irrelevant for JSON) and
3647
- * doesn't track line numbers in error messages. Good enough for a
3648
- * config file — corruption in production gracefully falls back to the
3649
- * defaults via the try/catch in `readKeybindings`.
3650
- */
3651
- function stripJsonComments(input) {
3652
- let out = "";
3653
- let i = 0;
3654
- let inString = false;
3655
- let escape = false;
3656
- while (i < input.length) {
3657
- const c = input[i];
3658
- if (inString) {
3659
- out += c;
3660
- if (escape) escape = false;
3661
- else if (c === "\\") escape = true;
3662
- else if (c === "\"") inString = false;
3663
- i++;
3664
- continue;
3665
- }
3666
- if (c === "\"") {
3667
- inString = true;
3668
- out += c;
3669
- i++;
3670
- continue;
3671
- }
3672
- if (c === "/" && input[i + 1] === "/") {
3673
- while (i < input.length && input[i] !== "\n") i++;
3674
- continue;
3675
- }
3676
- if (c === "/" && input[i + 1] === "*") {
3677
- i += 2;
3678
- while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
3679
- i += 2;
3680
- continue;
3681
- }
3682
- out += c;
3683
- i++;
3684
- }
3685
- return out;
3686
- }
3687
- //#endregion
3688
3183
  //#region src/chat/user-config.ts
3689
3184
  /**
3690
3185
  * User-level config at `~/.{prefix}/config.json`. Holds global flags
@@ -3759,9 +3254,18 @@ function resolveConfig(options) {
3759
3254
  const store = typeof options.store === "function" ? options.store(paths) : options.store;
3760
3255
  const stateStore = createStateStore(paths.state);
3761
3256
  const initialState = stateStore.load();
3762
- const providers = options.providers ?? BUILTIN_PROVIDERS;
3257
+ const extensions = options.extensionRegistry ?? EMPTY_EXTENSION_REGISTRY;
3258
+ const baseProviders = options.providers ?? BUILTIN_PROVIDERS;
3259
+ const providers = wrapProvidersWithExtensionMiddleware(Object.keys(extensions.providers).length === 0 ? baseProviders : {
3260
+ ...extensions.providers,
3261
+ ...baseProviders
3262
+ }, extensions);
3763
3263
  if (options.agents && options.preset) throw new Error("resolveConfig: pass either `preset` (single-agent legacy) or `agents` (multi-profile registry), not both. Migrate `preset` into an entry inside `agents` if you want both worlds.");
3764
- const agents = options.agents ?? (options.preset ? singleAgentRegistry(options.preset) : BUILTIN_AGENTS);
3264
+ const baseAgents = options.agents ?? (options.preset ? singleAgentRegistry(options.preset) : BUILTIN_AGENTS);
3265
+ const agents = Object.keys(extensions.agents).length === 0 ? baseAgents : {
3266
+ ...extensions.agents,
3267
+ ...baseAgents
3268
+ };
3765
3269
  if (Object.keys(agents).length === 0) throw new Error("resolveConfig: `agents` registry is empty — at least one profile is required.");
3766
3270
  process.env.ZIDANE_CREDENTIALS_PATH = credentialsPath(paths.userDir);
3767
3271
  applyApiKeyEnv(paths.userDir, providers);
@@ -3774,7 +3278,9 @@ function resolveConfig(options) {
3774
3278
  }) : resumeProvider ? pickInitial(resumeProvider, providers, initialState) : null;
3775
3279
  const initialAgentId = resolveAgentId(agents, initialState.lastAgent, options.defaultAgent ?? "build");
3776
3280
  if (!initialAgentId) throw new Error("resolveConfig: failed to resolve an initial agent id from a non-empty registry.");
3777
- const keybindings = readKeybindings(paths.userDir);
3281
+ const rawKeybindings = readKeybindingsRaw(paths.userDir);
3282
+ const keybindings = mergeKeybindings(rawKeybindings);
3283
+ const extensionKeybindings = extensions.tuiContributions.keybindings.length === 0 ? [] : resolveExtensionKeybindings(extensions.tuiContributions.keybindings, rawKeybindings);
3778
3284
  return {
3779
3285
  prefix,
3780
3286
  storageDir,
@@ -3790,7 +3296,18 @@ function resolveConfig(options) {
3790
3296
  resumeProvider,
3791
3297
  initialPicked,
3792
3298
  keybindings,
3299
+ extensionKeybindings,
3793
3300
  autoUpdate: options.autoUpdate ?? null,
3301
+ extensions,
3302
+ extensionScanPaths: options.extensionScanPaths ?? [],
3303
+ extensionPreset: buildExtensionPreset(extensions),
3304
+ extensionSystemFragments: extensions.systemFragments,
3305
+ extensionSubagents: extensions.subagents,
3306
+ extensionSubagentOverrides: extensions.subagentOverrides,
3307
+ extensionConfigCategories: extensions.configCategories,
3308
+ extensionConfigItems: extensions.configItems,
3309
+ trustProjectExtensions: options.trustProjectExtensions ?? process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === "1",
3310
+ rawKeybindings,
3794
3311
  launch: options.launch ?? null
3795
3312
  };
3796
3313
  }
@@ -3910,6 +3427,33 @@ function makeModelsResolver(registry) {
3910
3427
  return descriptor ? modelsForDescriptor(descriptor) : [];
3911
3428
  };
3912
3429
  }
3430
+ function wrapProvidersWithExtensionMiddleware(providers, extensions) {
3431
+ if (extensions.providerMiddlewares.length === 0) return providers;
3432
+ const out = {};
3433
+ for (const [key, descriptor] of Object.entries(providers)) out[key] = {
3434
+ ...descriptor,
3435
+ factory: () => {
3436
+ let provider = descriptor.factory();
3437
+ for (const entry of extensions.providerMiddlewares) try {
3438
+ const next = entry.value(provider, {
3439
+ extensionName: entry.extensionName,
3440
+ source: entry.source,
3441
+ descriptor
3442
+ });
3443
+ if (isProviderLike(next)) provider = next;
3444
+ } catch (err) {
3445
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/extensions] ${entry.extensionName} provider middleware failed: ${errorMessage(err)}\n`);
3446
+ }
3447
+ return provider;
3448
+ }
3449
+ };
3450
+ return out;
3451
+ }
3452
+ function isProviderLike(value) {
3453
+ if (!value || typeof value !== "object") return false;
3454
+ const provider = value;
3455
+ return typeof provider.name === "string" && !!provider.meta && typeof provider.meta === "object" && typeof provider.formatTools === "function" && typeof provider.userMessage === "function" && typeof provider.assistantMessage === "function" && typeof provider.toolResultsMessage === "function" && typeof provider.stream === "function";
3456
+ }
3913
3457
  function resolveResumeProvider(state, providers, storageDir) {
3914
3458
  if (!state.lastProvider) {
3915
3459
  if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/chat] resume: no \`lastProvider\` in state — auth screen will show. (Has \`${storageDir}/state.json\` been deleted or never written?)\n`);
@@ -5255,8 +4799,11 @@ const BUILTIN_THEMES = {
5255
4799
  [VAPORWAVE_THEME.id]: VAPORWAVE_THEME
5256
4800
  };
5257
4801
  /** Resolve a theme id to its full `Theme`, falling back to default on unknown ids. */
5258
- function resolveTheme(id) {
5259
- if (id && BUILTIN_THEMES[id]) return BUILTIN_THEMES[id];
4802
+ function resolveTheme(id, extraThemes) {
4803
+ if (id) {
4804
+ if (BUILTIN_THEMES[id]) return BUILTIN_THEMES[id];
4805
+ if (extraThemes && extraThemes[id]) return extraThemes[id];
4806
+ }
5260
4807
  return DEFAULT_THEME;
5261
4808
  }
5262
4809
  //#endregion
@@ -5266,6 +4813,7 @@ const DEFAULT_SETTINGS = {
5266
4813
  toolCallDisplay: "formatted",
5267
4814
  showToolResults: true,
5268
4815
  safeMode: true,
4816
+ loadProjectExtensions: false,
5269
4817
  hideSubagentOutput: true,
5270
4818
  theme: DEFAULT_THEME.id,
5271
4819
  showAllProjects: false,
@@ -5273,18 +4821,30 @@ const DEFAULT_SETTINGS = {
5273
4821
  persistToolResults: true,
5274
4822
  autoCompact: true,
5275
4823
  autoCompactThreshold: .8,
4824
+ resumeOnMaxTokens: false,
4825
+ maxOutputTokens: 0,
4826
+ maxTurns: 0,
4827
+ maxCostUsd: 0,
4828
+ maxTotalTokens: 0,
4829
+ maxWallMinutes: 0,
5276
4830
  showEditDiffs: true,
5277
4831
  editDiffDisplay: "full",
4832
+ showCompactionSummary: false,
4833
+ compactionModel: "",
5278
4834
  targetFps: 60,
5279
4835
  allowInteraction: true,
5280
4836
  smoothStreaming: true,
4837
+ fileLogging: false,
5281
4838
  showTodoIndicator: true,
5282
4839
  showThrobber: false,
5283
4840
  checkForUpdates: true,
5284
4841
  uiMode: "full",
5285
4842
  userInstructionsScope: "both",
5286
4843
  ctrlCBehavior: "quit",
5287
- attachmentsEnabled: true
4844
+ terminalTitle: "off",
4845
+ sessionIcon: "",
4846
+ attachmentsEnabled: true,
4847
+ externalEditor: true
5288
4848
  };
5289
4849
  /**
5290
4850
  * Hard-clamp a `targetFps` value to a safe range before handing it to
@@ -5383,18 +4943,36 @@ const SETTINGS_TOGGLES = [
5383
4943
  label: "Persist large tool results",
5384
4944
  description: "write >8 KiB tool outputs to disk and inline a preview"
5385
4945
  },
4946
+ {
4947
+ category: "agent",
4948
+ key: "loadProjectExtensions",
4949
+ label: "Load project extensions",
4950
+ description: "run extensions under <project>/.zidane/extensions — off by default (they execute arbitrary local code)"
4951
+ },
5386
4952
  {
5387
4953
  category: "agent",
5388
4954
  key: "attachmentsEnabled",
5389
4955
  label: "Paste attachments",
5390
4956
  description: "fold dragged files + multiline pastes into prompt attachments (off = everything pastes inline)"
5391
4957
  },
4958
+ {
4959
+ category: "agent",
4960
+ key: "externalEditor",
4961
+ label: "Edit prompt in $EDITOR",
4962
+ description: "open the current prompt in $EDITOR / $VISUAL on the openExternalEditor key (ctrl+g), reloading it when you save & quit (git-style)"
4963
+ },
5392
4964
  {
5393
4965
  category: "agent",
5394
4966
  key: "autoCompact",
5395
4967
  label: "Auto-compact",
5396
4968
  description: "summarize the conversation when context fills past the threshold below"
5397
4969
  },
4970
+ {
4971
+ category: "agent",
4972
+ key: "resumeOnMaxTokens",
4973
+ label: "Auto-resume on output limit",
4974
+ description: "when a turn hits the model’s output-token ceiling mid-answer, continue it instead of stopping (off = stop at the limit)"
4975
+ },
5398
4976
  {
5399
4977
  category: "agent",
5400
4978
  key: "checkForUpdates",
@@ -5431,6 +5009,12 @@ const SETTINGS_TOGGLES = [
5431
5009
  label: "Edit diffs",
5432
5010
  description: "render edit / multi_edit / write_file as a unified diff"
5433
5011
  },
5012
+ {
5013
+ category: "ui",
5014
+ key: "showCompactionSummary",
5015
+ label: "Compaction summaries",
5016
+ description: "show the generated summary text under the ⎯⎯ compacted boundary row (off = metadata only)"
5017
+ },
5434
5018
  {
5435
5019
  category: "ui",
5436
5020
  key: "smoothStreaming",
@@ -5448,6 +5032,12 @@ const SETTINGS_TOGGLES = [
5448
5032
  key: "showThrobber",
5449
5033
  label: "Streaming throbber",
5450
5034
  description: "animated gradient glyphs at the transcript tail while the assistant is working"
5035
+ },
5036
+ {
5037
+ category: "ui",
5038
+ key: "fileLogging",
5039
+ label: "File logging",
5040
+ description: "persist this session's logs to ~/.zidane/logs/<datetime>.txt (the Logs tab shows them regardless)"
5451
5041
  }
5452
5042
  ];
5453
5043
  const SETTINGS_CHOICES = [
@@ -5499,6 +5089,146 @@ const SETTINGS_CHOICES = [
5499
5089
  }
5500
5090
  ]
5501
5091
  },
5092
+ {
5093
+ category: "agent",
5094
+ key: "maxOutputTokens",
5095
+ label: "Max output tokens",
5096
+ description: "hard cap on tokens the model may emit per turn (Model max = no cap, follow the model)",
5097
+ options: [
5098
+ {
5099
+ value: 0,
5100
+ label: "Model max"
5101
+ },
5102
+ {
5103
+ value: 8192,
5104
+ label: "8K"
5105
+ },
5106
+ {
5107
+ value: 16384,
5108
+ label: "16K"
5109
+ },
5110
+ {
5111
+ value: 32e3,
5112
+ label: "32K"
5113
+ },
5114
+ {
5115
+ value: 64e3,
5116
+ label: "64K"
5117
+ }
5118
+ ]
5119
+ },
5120
+ {
5121
+ category: "agent",
5122
+ key: "maxTurns",
5123
+ label: "Max turns / run",
5124
+ description: "stop a run after this many agent turns (Unlimited = follow the trajectory to completion)",
5125
+ options: [
5126
+ {
5127
+ value: 0,
5128
+ label: "Unlimited"
5129
+ },
5130
+ {
5131
+ value: 25,
5132
+ label: "25"
5133
+ },
5134
+ {
5135
+ value: 50,
5136
+ label: "50"
5137
+ },
5138
+ {
5139
+ value: 100,
5140
+ label: "100"
5141
+ },
5142
+ {
5143
+ value: 200,
5144
+ label: "200"
5145
+ }
5146
+ ]
5147
+ },
5148
+ {
5149
+ category: "agent",
5150
+ key: "maxCostUsd",
5151
+ label: "Max cost / run",
5152
+ description: "stop a run once its estimated cost crosses this budget (Unlimited = no ceiling)",
5153
+ options: [
5154
+ {
5155
+ value: 0,
5156
+ label: "Unlimited"
5157
+ },
5158
+ {
5159
+ value: 1,
5160
+ label: "$1"
5161
+ },
5162
+ {
5163
+ value: 5,
5164
+ label: "$5"
5165
+ },
5166
+ {
5167
+ value: 10,
5168
+ label: "$10"
5169
+ },
5170
+ {
5171
+ value: 25,
5172
+ label: "$25"
5173
+ }
5174
+ ]
5175
+ },
5176
+ {
5177
+ category: "agent",
5178
+ key: "maxTotalTokens",
5179
+ label: "Max tokens / run",
5180
+ description: "stop a run once total tokens cross this budget (Unlimited = no ceiling)",
5181
+ options: [
5182
+ {
5183
+ value: 0,
5184
+ label: "Unlimited"
5185
+ },
5186
+ {
5187
+ value: 5e5,
5188
+ label: "500K"
5189
+ },
5190
+ {
5191
+ value: 1e6,
5192
+ label: "1M"
5193
+ },
5194
+ {
5195
+ value: 2e6,
5196
+ label: "2M"
5197
+ },
5198
+ {
5199
+ value: 5e6,
5200
+ label: "5M"
5201
+ }
5202
+ ]
5203
+ },
5204
+ {
5205
+ category: "agent",
5206
+ key: "maxWallMinutes",
5207
+ label: "Max run time",
5208
+ description: "stop a run after this much wall-clock time (Unlimited = no ceiling)",
5209
+ options: [
5210
+ {
5211
+ value: 0,
5212
+ label: "Unlimited"
5213
+ },
5214
+ {
5215
+ value: 5,
5216
+ label: "5 min"
5217
+ },
5218
+ {
5219
+ value: 15,
5220
+ label: "15 min"
5221
+ },
5222
+ {
5223
+ value: 30,
5224
+ label: "30 min"
5225
+ },
5226
+ {
5227
+ value: 60,
5228
+ label: "60 min"
5229
+ }
5230
+ ]
5231
+ },
5502
5232
  {
5503
5233
  category: "ui",
5504
5234
  key: "toolCallDisplay",
@@ -5594,8 +5324,39 @@ const SETTINGS_CHOICES = [
5594
5324
  label: "Stop, then quit"
5595
5325
  }
5596
5326
  ]
5327
+ },
5328
+ {
5329
+ category: "ui",
5330
+ key: "terminalTitle",
5331
+ label: "Terminal title",
5332
+ description: "what the window / tab title shows — off leaves it alone · zidane pins the app name · session tracks the active session title · icon + session prefixes it with your session icon",
5333
+ options: [
5334
+ {
5335
+ value: "off",
5336
+ label: "Off"
5337
+ },
5338
+ {
5339
+ value: "app",
5340
+ label: "zidane"
5341
+ },
5342
+ {
5343
+ value: "session",
5344
+ label: "Session title"
5345
+ },
5346
+ {
5347
+ value: "icon-session",
5348
+ label: "Icon + session"
5349
+ }
5350
+ ]
5597
5351
  }
5598
5352
  ];
5353
+ const SETTINGS_INPUTS = [{
5354
+ category: "ui",
5355
+ key: "sessionIcon",
5356
+ label: "Session icon",
5357
+ description: "icon prefixed before the session title when \"Terminal title\" is set to icon + session (empty = ⚽)",
5358
+ placeholder: "⚽"
5359
+ }];
5599
5360
  //#endregion
5600
5361
  //#region src/chat/enabled-toggle-set.ts
5601
5362
  /**
@@ -5628,7 +5389,7 @@ const SETTINGS_CHOICES = [
5628
5389
  */
5629
5390
  function useEnabledToggleSet(opts) {
5630
5391
  const { settings, setSetting } = useSettings();
5631
- const { catalog, keyOf, settingKey } = opts;
5392
+ const { catalog, keyOf, settingKey, gcKeys } = opts;
5632
5393
  const persisted = settings[settingKey];
5633
5394
  const enabledSet = useMemo(() => {
5634
5395
  if (persisted === void 0) return new Set(catalog.map(keyOf));
@@ -5638,7 +5399,11 @@ function useEnabledToggleSet(opts) {
5638
5399
  catalog,
5639
5400
  keyOf
5640
5401
  ]);
5641
- const catalogKeys = useMemo(() => new Set(catalog.map(keyOf)), [catalog, keyOf]);
5402
+ const catalogKeys = useMemo(() => new Set(gcKeys ?? catalog.map(keyOf)), [
5403
+ gcKeys,
5404
+ catalog,
5405
+ keyOf
5406
+ ]);
5642
5407
  return {
5643
5408
  enabledSet,
5644
5409
  toggle: useCallback((name) => {
@@ -5968,10 +5733,6 @@ function buildHints(options) {
5968
5733
  label: inFlightToolCount === 1 ? "cancel task" : `cancel task (${inFlightToolCount})`
5969
5734
  } : null;
5970
5735
  if (uiMode === "minimal") return [
5971
- {
5972
- key: keybindings.openContextPanel,
5973
- label: "context"
5974
- },
5975
5736
  ...hasMultipleAgents ? [{
5976
5737
  key: keybindings.cycleAgent,
5977
5738
  label: agentLabel,
@@ -5984,10 +5745,6 @@ function buildHints(options) {
5984
5745
  }
5985
5746
  ];
5986
5747
  return [
5987
- {
5988
- key: keybindings.openContextPanel,
5989
- label: "context"
5990
- },
5991
5748
  ...hasMultipleAgents ? [{
5992
5749
  key: keybindings.cycleAgent,
5993
5750
  label: agentLabel,
@@ -6023,110 +5780,6 @@ function shortChord(spec) {
6023
5780
  return spec.startsWith("ctrl+") ? `/${spec.slice(5)}` : spec;
6024
5781
  }
6025
5782
  //#endregion
6026
- //#region src/chat/generate-title.ts
6027
- /** Hard cap on the result length. Anything longer is truncated client-side. */
6028
- const TITLE_MAX_CHARS = 60;
6029
- /** Default turn count fed to the model. 10 covers most exchanges without bloat. */
6030
- const DEFAULT_MAX_TURNS = 10;
6031
- /** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */
6032
- const TITLE_RESPONSE_MAX_TOKENS = 64;
6033
- /** Per-turn body cap when assembling the prompt — keeps the request payload tight. */
6034
- const PROMPT_TURN_CHAR_MAX = 2e3;
6035
- /**
6036
- * Drive the provider's `stream()` once and return a concise title for
6037
- * the conversation represented by `turns`. Throws when:
6038
- * - `turns` contains no text-bearing content (nothing to summarize),
6039
- * - the provider stream completes with no output text,
6040
- * - `signal` is aborted (rethrown verbatim).
6041
- *
6042
- * Output is trimmed, single-line, with surrounding quotes / trailing
6043
- * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.
6044
- */
6045
- async function generateSessionTitle({ provider, model, turns, maxTurns = DEFAULT_MAX_TURNS, signal }) {
6046
- const transcript = renderTurnsForPrompt(turns.slice(-Math.max(1, maxTurns)));
6047
- if (!transcript) throw new Error("No text content in the recent turns to summarize.");
6048
- const system = [
6049
- "You generate concise, descriptive titles for chat conversations.",
6050
- "Reply with ONLY the title — no quotes, no markdown, no trailing punctuation,",
6051
- "no preamble (do not say \"Title:\" or similar).",
6052
- `Aim for 3 to 7 words and stay under ${TITLE_MAX_CHARS} characters.`
6053
- ].join(" ");
6054
- const userPrompt = `Generate a title for this conversation:\n\n${transcript}`;
6055
- let text = "";
6056
- const result = await provider.stream({
6057
- model,
6058
- system,
6059
- tools: [],
6060
- messages: [provider.userMessage(userPrompt)],
6061
- maxTokens: TITLE_RESPONSE_MAX_TOKENS,
6062
- ...signal ? { signal } : {}
6063
- }, { onText: (delta) => {
6064
- text += delta;
6065
- } });
6066
- return cleanTitle(text.trim().length > 0 ? text : result.text);
6067
- }
6068
- /**
6069
- * Compact a turn list into a transcript-style prompt:
6070
- *
6071
- * user: …
6072
- * assistant: …
6073
- * user: …
6074
- *
6075
- * Tool calls and tool results are summarized to keep the request
6076
- * payload small — the model needs the rough flow of the conversation,
6077
- * not full JSON blobs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`
6078
- * so a single huge code dump doesn't blow the budget.
6079
- *
6080
- * Returns an empty string when the slice has no text-bearing content
6081
- * (e.g. a turn list that's only tool plumbing).
6082
- */
6083
- function renderTurnsForPrompt(turns) {
6084
- const lines = [];
6085
- for (const turn of turns) {
6086
- const body = textBodyOf(turn);
6087
- if (!body) continue;
6088
- const role = turn.role === "assistant" ? "assistant" : turn.role === "user" ? "user" : "system";
6089
- lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`);
6090
- }
6091
- return lines.join("\n\n");
6092
- }
6093
- function textBodyOf(turn) {
6094
- const parts = [];
6095
- for (const block of turn.content) if (block.type === "text" && block.text.trim()) parts.push(block.text.trim());
6096
- else if (block.type === "tool_call") parts.push(`[tool: ${block.name}]`);
6097
- else if (block.type === "tool_result") parts.push(`[tool result]`);
6098
- return parts.join(" ");
6099
- }
6100
- /**
6101
- * Normalize a model-generated title into the shape we want to persist:
6102
- *
6103
- * - Collapse internal whitespace + take the first line only (some
6104
- * models emit "Title: foo\n\nReason: …" despite instructions).
6105
- * - Strip surrounding quote characters (`"foo"`, `'foo'`, `` `foo` ``).
6106
- * - Strip a leading `Title:` / `title -` prefix if the model added one.
6107
- * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner
6108
- * without it.
6109
- * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.
6110
- *
6111
- * Exported as `cleanTitle` so tests can pin the normalization rules
6112
- * without going through a mock provider.
6113
- */
6114
- function cleanTitle(raw) {
6115
- let s = raw.split("\n")[0]?.trim() ?? "";
6116
- s = s.replace(/^\s*title\s*[:\-–—]\s*/i, "");
6117
- if (s.length >= 2) {
6118
- const first = s.charAt(0);
6119
- const last = s.charAt(s.length - 1);
6120
- if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`" || first === "“" && last === "”") s = s.slice(1, -1).trim();
6121
- }
6122
- s = s.replace(/[.!?]+$/, "").trim();
6123
- if (s.length === 0) throw new Error("Model returned an empty title.");
6124
- return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s;
6125
- }
6126
- function clip(text, max) {
6127
- return text.length > max ? `${text.slice(0, max)}…` : text;
6128
- }
6129
- //#endregion
6130
5783
  //#region src/chat/hints.ts
6131
5784
  /**
6132
5785
  * Truncate `text` to at most `max` characters, replacing the trailing
@@ -6665,181 +6318,6 @@ function makeRequestInteraction(actions) {
6665
6318
  });
6666
6319
  }
6667
6320
  //#endregion
6668
- //#region src/chat/markdown-segments.ts
6669
- /**
6670
- * Split markdown text into alternating prose / fenced-code segments.
6671
- *
6672
- * Recognizes both ` ``` ` and `~~~` fences, with arbitrary length ≥ 3,
6673
- * matching CommonMark's "fence indicator must be at least as long to
6674
- * close" rule. Info-strings (the bit after the opening fence) are kept
6675
- * as the `lang` hint; any trailing whitespace is trimmed. Closing
6676
- * fences are detected on a line of their own (whitespace tolerated).
6677
- *
6678
- * Unclosed fences fall back to emitting the would-be-code body as a
6679
- * trailing prose segment so no content is dropped — finalized markdown
6680
- * isn't expected to ship one, but the fallback keeps the renderer
6681
- * truthful when the model produces malformed output.
6682
- *
6683
- * Exported for unit tests.
6684
- */
6685
- function splitMarkdownCodeBlocks(text) {
6686
- const lines = text.split("\n");
6687
- const segments = [];
6688
- let prose = [];
6689
- let code = [];
6690
- let inFence = false;
6691
- let fenceChar = "";
6692
- let fenceLen = 0;
6693
- let lang = "";
6694
- const flushProse = () => {
6695
- if (prose.length > 0 && prose[prose.length - 1] === "") prose.pop();
6696
- if (prose.length > 0) {
6697
- segments.push({
6698
- kind: "prose",
6699
- content: prose.join("\n")
6700
- });
6701
- prose = [];
6702
- }
6703
- };
6704
- let trimLeadingProseBlank = false;
6705
- for (const line of lines) if (!inFence) {
6706
- const open = line.match(/^(`{3,}|~{3,})([^\n`~]*)$/);
6707
- if (open) {
6708
- flushProse();
6709
- inFence = true;
6710
- fenceChar = open[1][0];
6711
- fenceLen = open[1].length;
6712
- lang = open[2].trim();
6713
- code = [];
6714
- continue;
6715
- }
6716
- if (trimLeadingProseBlank) {
6717
- trimLeadingProseBlank = false;
6718
- if (line === "" && prose.length === 0) continue;
6719
- }
6720
- prose.push(line);
6721
- } else {
6722
- const close = line.match(/^(`{3,}|~{3,})\s*$/);
6723
- if (close && close[1][0] === fenceChar && close[1].length >= fenceLen) {
6724
- segments.push({
6725
- kind: "code",
6726
- content: code.join("\n"),
6727
- lang
6728
- });
6729
- inFence = false;
6730
- code = [];
6731
- trimLeadingProseBlank = true;
6732
- continue;
6733
- }
6734
- code.push(line);
6735
- }
6736
- if (inFence) prose.push(`${fenceChar.repeat(fenceLen)}${lang}`, ...code);
6737
- flushProse();
6738
- return segments;
6739
- }
6740
- const LIST_ITEM_RE = /^( *)(?:[-+*]|\d+[.)]) +/;
6741
- /**
6742
- * Lift indented fenced code blocks to column 0 while preserving the code
6743
- * body's relative indentation.
6744
- *
6745
- * OpenTUI's markdown renderNode hook only receives top-level code tokens.
6746
- * A common assistant shape is:
6747
- *
6748
- * - Run this:
6749
- * ```bash
6750
- * command
6751
- * ```
6752
- *
6753
- * Marked parses that as code inside a list token, so the TUI loses the
6754
- * code-panel wrapper and copy affordance. Normalizing just the fence's list
6755
- * indentation keeps the prose readable while routing the snippet through
6756
- * the same top-level renderer as ordinary fences.
6757
- */
6758
- function normalizeMarkdownCodeFences(text) {
6759
- const lines = text.split("\n");
6760
- const out = [];
6761
- const listIndents = [];
6762
- let inFence = false;
6763
- let fenceChar = "";
6764
- let fenceLen = 0;
6765
- let lift = 0;
6766
- for (const line of lines) {
6767
- if (inFence) {
6768
- const normalized = stripFenceIndent(line, lift);
6769
- out.push(normalized);
6770
- if (isFenceClose(normalized, fenceChar, fenceLen)) {
6771
- inFence = false;
6772
- fenceChar = "";
6773
- fenceLen = 0;
6774
- lift = 0;
6775
- }
6776
- continue;
6777
- }
6778
- updateListIndentStack(listIndents, line);
6779
- const open = parseFenceOpen(line);
6780
- if (!open) {
6781
- out.push(line);
6782
- continue;
6783
- }
6784
- lift = [...listIndents].reverse().find((i) => i <= open.indent) ?? (open.indent <= 3 ? open.indent : 0);
6785
- const normalized = stripFenceIndent(line, lift);
6786
- out.push(normalized);
6787
- inFence = true;
6788
- fenceChar = open.char;
6789
- fenceLen = open.length;
6790
- }
6791
- return out.join("\n");
6792
- }
6793
- function parseFenceOpen(line) {
6794
- const indent = leadingSpaces(line);
6795
- const char = line[indent];
6796
- if (char !== "`" && char !== "~") return null;
6797
- const length = countRun(line, indent, char);
6798
- if (length < 3) return null;
6799
- const info = line.slice(indent + length);
6800
- if (char === "`" && info.includes("`")) return null;
6801
- return {
6802
- indent,
6803
- char,
6804
- length
6805
- };
6806
- }
6807
- function isFenceClose(line, fenceChar, fenceLen) {
6808
- const indent = leadingSpaces(line);
6809
- if (line[indent] !== fenceChar) return false;
6810
- const length = countRun(line, indent, fenceChar);
6811
- if (length < fenceLen) return false;
6812
- return onlySpaces(line, indent + length);
6813
- }
6814
- function updateListIndentStack(listIndents, line) {
6815
- const contentIndent = leadingSpaces(line);
6816
- if (line.trim().length > 0) while (listIndents.length > 0 && contentIndent < listIndents[listIndents.length - 1]) listIndents.pop();
6817
- const item = line.match(LIST_ITEM_RE);
6818
- if (!item) return;
6819
- const indent = item[0].length;
6820
- if (indent > (listIndents[listIndents.length - 1] ?? 0)) listIndents.push(indent);
6821
- }
6822
- function leadingSpaces(line) {
6823
- let i = 0;
6824
- while (i < line.length && line[i] === " ") i++;
6825
- return i;
6826
- }
6827
- function countRun(line, start, char) {
6828
- let i = start;
6829
- while (i < line.length && line[i] === char) i++;
6830
- return i - start;
6831
- }
6832
- function onlySpaces(line, start) {
6833
- for (let i = start; i < line.length; i++) if (line[i] !== " ") return false;
6834
- return true;
6835
- }
6836
- function stripFenceIndent(line, columns) {
6837
- if (columns <= 0) return line;
6838
- let i = 0;
6839
- while (i < columns && line[i] === " ") i++;
6840
- return line.slice(i);
6841
- }
6842
- //#endregion
6843
6321
  //#region src/chat/mcp-auth-state.ts
6844
6322
  /**
6845
6323
  * Apply one event to the state map. Pure, immutable — returns a new map
@@ -7302,58 +6780,6 @@ function isCachedEntry(value) {
7302
6780
  return true;
7303
6781
  }
7304
6782
  //#endregion
7305
- //#region src/chat/project-user-paths.ts
7306
- /**
7307
- * Shared search-path builder for project + user config discovery.
7308
- *
7309
- * Both Skills (`skills/<name>/SKILL.md`) and MCP servers (`mcps.json`)
7310
- * follow the same first-found-wins order:
7311
- *
7312
- * 1. `{project-root}/.agents/<sub>` — project, agnostic convention
7313
- * 2. `{project-root}/.{prefix}/<sub>` — project, zidane (`.zidane` default)
7314
- * 3. `~/.agents/<sub>` — user, agnostic
7315
- * 4. `~/.{prefix}/<sub>` — user, zidane
7316
- *
7317
- * `{project-root}` is the git root walked up from `cwd` when present,
7318
- * else `cwd` itself. This pairs with the project-scoped session
7319
- * storage (`resolveStoragePaths`) so all three project-aware surfaces
7320
- * — sessions, mcps, skills — anchor at the same `.{prefix}/`.
7321
- *
7322
- * Centralizing the pattern keeps the four paths in one place — a third
7323
- * discovery surface (hooks, rules, …) gets the same convention for free
7324
- * and stays in lock-step on a prefix bump.
7325
- */
7326
- /**
7327
- * Resolve the four search paths for a given subdirectory (`skills`,
7328
- * `mcps.json`, …). Returns absolute paths regardless of whether they
7329
- * exist — the caller chooses to `existsSync`-filter or feed straight
7330
- * into a tolerant discovery routine.
7331
- */
7332
- function projectUserPaths(opts) {
7333
- const cwd = opts.cwd ?? process.cwd();
7334
- const home = opts.home ?? homedir();
7335
- const projectRoot = findGitRoot$1(cwd) ?? cwd;
7336
- const prefix = (opts.prefix ?? ".zidane").replace(/^\./, "");
7337
- return [
7338
- {
7339
- path: resolve(projectRoot, `.agents/${opts.subPath}`),
7340
- source: "project"
7341
- },
7342
- {
7343
- path: resolve(projectRoot, `.${prefix}/${opts.subPath}`),
7344
- source: "project"
7345
- },
7346
- {
7347
- path: resolve(home, `.agents/${opts.subPath}`),
7348
- source: "user"
7349
- },
7350
- {
7351
- path: resolve(home, `.${prefix}/${opts.subPath}`),
7352
- source: "user"
7353
- }
7354
- ];
7355
- }
7356
- //#endregion
7357
6783
  //#region src/chat/mcps-discovery.ts
7358
6784
  /**
7359
6785
  * File names searched in each of the four project/user roots. Order
@@ -8835,6 +8261,9 @@ function useStreamBuffer(setEvents, options) {
8835
8261
  const lastTickMsRef = useRef(0);
8836
8262
  const getSmoothRef = useRef(options?.getSmooth);
8837
8263
  getSmoothRef.current = options?.getSmooth;
8264
+ /** Live mirror so drains read the current cap without re-memoizing. */
8265
+ const maxEventsRef = useRef(options?.maxTranscriptEvents);
8266
+ maxEventsRef.current = options?.maxTranscriptEvents;
8838
8267
  /**
8839
8268
  * Updaters queued while smooth-streaming was still draining backlog —
8840
8269
  * tool-call appends, finalize-markdown transforms, error events.
@@ -8886,7 +8315,8 @@ function useStreamBuffer(setEvents, options) {
8886
8315
  for (const bucket of buckets) merged = applyBucket(merged, bucket);
8887
8316
  for (const u of queued) merged = u(merged);
8888
8317
  if (updater) merged = updater(merged);
8889
- return merged;
8318
+ const cap = maxEventsRef.current;
8319
+ return cap === null ? merged : capTranscriptEvents(merged, cap ?? void 0);
8890
8320
  });
8891
8321
  }, [setEvents, stopTicker]);
8892
8322
  /**
@@ -9084,6 +8514,6 @@ function computeTurnAnchors(items) {
9084
8514
  };
9085
8515
  }
9086
8516
  //#endregion
9087
- export { useMcpAuthDispatch as $, COMMUNICATION_DOCTRINE as $n, KEYBINDING_DEFS as $t, runOAuthLogin as A, BUILTIN_AGENTS as An, SettingsProvider as At, refreshMcpToolsCatalog as B, TODOWRITE_TOOL as Bn, CATPPUCCIN_FRAPPE as Bt, projectsFilePath as C, performSelfUpdate as Cn, stripSpawnTokensLine as Cr, shortChord as Ct, formatPathForCwd as D, renderAgentsMdBlock as Dn, toolResultText as Dr, SETTINGS_CATEGORIES as Dt, writeProjects as E, discoverAgentsMd as En, toolCallPreview as Er, DEFAULT_SETTINGS as Et, parseMcpsFile as F, accentColor as Fn, resolveChipColor as Ft, useMcpToolToggleSet as G, getTodosForRun as Gn, DiscoveryProvider as Gt, subscribeMcpToolsCache as H, TODO_WRITE_COUNTS_METADATA_KEY as Hn, CATPPUCCIN_MACCHIATO as Ht, projectUserPaths as I, resolveAgentId as In, resolveTheme as It, parentServerName as J, pruneTodosByRun as Jn, ConfigProvider as Jt, buildVisibleMcpRows as K, isTodoTool as Kn, useDiscovery as Kt, clearMcpToolsCache as L, singleAgentRegistry as Ln, VAPORWAVE_THEME as Lt, buildMcpServers as M, DEFAULT_BUDGET_EXCLUDE_TOOLS as Mn, useSettings as Mt, defaultMcpsConfigPaths as N, DEFAULT_PERSIST_EXCLUDE_TOOLS as Nn, BUILTIN_THEMES as Nt, fetchOAuthRedirect as O, findGitRoot$1 as On, updateToolEventOutcomes as Or, SETTINGS_CHOICES as Ot, discoverProjectMcps as P, PLAN_AGENT as Pn, DEFAULT_THEME as Pt, McpAuthProvider as Q, ACTIONS_WITH_CARE_DOCTRINE as Qn, DEFAULT_KEYBINDINGS as Qt, loadMcpToolsCache as R, TODOREAD_TOOL as Rn, GRUVBOX_DARK as Rt, matchesSafelistEntry as S, performInPlaceSelfUpdate as Sn, serverToolResultSummary as Sr, buildHints as St, suggestSafelistEntries as T, chatAutoCompactBehavior as Tn, titleFromTurns as Tr, useEnabledToggleSet as Tt, buildToolToggle as U, createTodoTools as Un, CATPPUCCIN_MOCHA as Ut, saveMcpToolsCache as V, TODO_STATUS_GLYPHS as Vn, CATPPUCCIN_LATTE as Vt, useMcpToolToggleMap as W, getArchivedTodosForRun as Wn, createDiscoverySlot as Wt, mcpCredentialsPath as X, setTodosForRun as Xn, resolveConfig as Xt, createFileMcpCredentialStore as Y, selectActiveTodos as Yn, useConfig as Yt, patchMcpCredential as Z, useActiveTodos as Zn, resolveStoragePaths as Zt, useSafeModeQueue as _, checkForUpdate as _n, lastContextSizeFromTurns as _r, clipHintsToWidth as _t, useSurfaces as a, keybindingsPath as an, PLAN_MODE_DOCTRINE_NO_PROMPTS as ar, ASK_USER_TOOL as at, getSafelist as b, detectPackageManager as bn, marginTopFor as br, cleanTitle as bt, useStreamBuffer as c, parseBindingSpec as cn, buildBuildSystem as cr, buildResumedToolResultsTurn as ct, discoverProjectSkills as d, useCompletion as dn, chatReferenceTokensForPrompt as dr, makeRequestInteraction as dt, KEYBINDING_DEF_BY_ACTION as en, DOING_TASKS_DOCTRINE as er, useMcpAuthState as et, renderSession as f, tryOpenBrowser as fn, createChatTurnsReadTool as fr, pendingInteractionsFromTurns as ft, useSafeModeActions as g, useUpdateCheck as gn, eventsFromTurns as gr, EMPTY_HINTS as gt, SafeModeProvider as h, buildUpdateHint as hn, deriveSessionTitle as hr, useInteractionsQueue as ht, useSelectStyle as i, groupBindings as in, PLAN_MODE_DOCTRINE as ir, splitMarkdownCodeBlocks as it, supportsOAuth as j, DEFAULT_AGENT_ID as jn, clampFps as jt, oauthUsesManualCodePaste as k, BUILD_AGENT as kn, SETTINGS_TOGGLES as kt, buildSkillsConfig as l, readKeybindings as ln, buildPlanSystem as lr, createInteractionTools as lt, writeSessionExport as m, bootTick as mn, createStateStore as mr, useInteractionsActions as mt, ThemeProvider as n, ensureKeybindingsFile as nn, INTERACTION_GUIDANCE as nr, reduceMcpAuth as nt, useSyntaxStyles as o, matchesBinding as on, SUBAGENT_GUIDANCE as or, InteractionsProvider as ot, resolveSessionExportTarget as p, bootProfileEnabled as pn, compactSummaryEvent as pr, serializeInteractionResponse as pt, indexOfServerRow as q, pickActiveRunId as qn, useDiscoveryOptional as qt, useColors as r, formatBindingForDisplay as rn, INTERACTION_GUIDANCE_NO_PROMPTS as rr, normalizeMarkdownCodeFences as rt, useTheme as s, mergeKeybindings as sn, TOKEN_DISCIPLINE_DOCTRINE as sr, PRESENT_PLAN_TOOL as st, computeTurnAnchors as t, KEYBINDING_KEY_COL_WIDTH as tn, IDENTITY_PREFIX as tr, getMcpAuthStatus as tt, defaultSkillScanPaths as u, stripJsonComments as un, envSection as ur, isInteractionTool as ut, IMPLICITLY_SAFE_TOOLS as v, compareSemver as vn, listSessionMeta as vr, hintsLength as vt, readProjects as w, resolvePlatformPackage as wn, sumRunCosts as wr, listProjectFiles as wt, isOnSafelist as x, parseSemver as xn, saveState as xr, generateSessionTitle as xt, addToSafelist as y, detectLibc as yn, loadState as yr, truncateTrailing as yt, mcpToolsCachePath as z, TODOS_METADATA_KEY as zn, GRUVBOX_LIGHT as zt };
8517
+ export { useMcpAuthState as $, eventsFromTurns as $n, useUpdateCheck as $t, runOAuthLogin as A, pruneTodosByRun as An, DEFAULT_THEME as At, saveMcpToolsCache as B, PLAN_MODE_DOCTRINE as Bn, createDiscoverySlot as Bt, projectsFilePath as C, TODO_STATUS_GLYPHS as Cn, SETTINGS_CHOICES as Ct, formatPathForCwd as D, getTodosForRun as Dn, clampFps as Dt, writeProjects as E, getArchivedTodosForRun as En, SettingsProvider as Et, parseMcpsFile as F, COMMUNICATION_DOCTRINE as Fn, GRUVBOX_LIGHT as Ft, buildVisibleMcpRows as G, buildPlanSystem as Gn, useConfig as Gt, buildToolToggle as H, SUBAGENT_GUIDANCE as Hn, useDiscovery as Ht, clearMcpToolsCache as I, DOING_TASKS_DOCTRINE as In, CATPPUCCIN_FRAPPE as It, createFileMcpCredentialStore as J, createChatTurnsReadTool as Jn, useCompletion as Jt, indexOfServerRow as K, envSection as Kn, resolveConfig as Kt, loadMcpToolsCache as L, IDENTITY_PREFIX as Ln, CATPPUCCIN_LATTE as Lt, buildMcpServers as M, setTodosForRun as Mn, resolveTheme as Mt, defaultMcpsConfigPaths as N, useActiveTodos as Nn, VAPORWAVE_THEME as Nt, fetchOAuthRedirect as O, isTodoTool as On, useSettings as Ot, discoverProjectMcps as P, ACTIONS_WITH_CARE_DOCTRINE as Pn, GRUVBOX_DARK as Pt, useMcpAuthDispatch as Q, deriveSessionTitle as Qn, buildUpdateHint as Qt, mcpToolsCachePath as R, INTERACTION_GUIDANCE as Rn, CATPPUCCIN_MACCHIATO as Rt, matchesSafelistEntry as S, TODOWRITE_TOOL as Sn, SETTINGS_CATEGORIES as St, suggestSafelistEntries as T, createTodoTools as Tn, SETTINGS_TOGGLES as Tt, useMcpToolToggleMap as U, TOKEN_DISCIPLINE_DOCTRINE as Un, useDiscoveryOptional as Ut, subscribeMcpToolsCache as V, PLAN_MODE_DOCTRINE_NO_PROMPTS as Vn, DiscoveryProvider as Vt, useMcpToolToggleSet as W, buildBuildSystem as Wn, ConfigProvider as Wt, patchMcpCredential as X, compactSummaryEvent as Xn, bootProfileEnabled as Xt, mcpCredentialsPath as Y, DEFAULT_SESSION_TITLE as Yn, tryOpenBrowser as Yt, McpAuthProvider as Z, createStateStore as Zn, bootTick as Zt, useSafeModeQueue as _, accentColor as _n, buildHints as _t, useSurfaces as a, performInPlaceSelfUpdate as an, serverToolResultSummary as ar, buildResumedToolResultsTurn as at, getSafelist as b, TODOREAD_TOOL as bn, useEnabledToggleSet as bt, useStreamBuffer as c, chatAutoCompactBehavior as cn, titleFromTurns as cr, makeRequestInteraction as ct, discoverProjectSkills as d, BUILD_AGENT as dn, updateToolEventOutcomes as dr, useInteractionsActions as dt, checkForUpdate as en, lastContextSizeFromTurns as er, getMcpAuthStatus as et, renderSession as f, BUILTIN_AGENTS as fn, useInteractionsQueue as ft, useSafeModeActions as g, PLAN_AGENT as gn, truncateTrailing as gt, SafeModeProvider as h, DEFAULT_PERSIST_EXCLUDE_TOOLS as hn, hintsLength as ht, useSelectStyle as i, parseSemver as in, saveState as ir, PRESENT_PLAN_TOOL as it, supportsOAuth as j, selectActiveTodos as jn, resolveChipColor as jt, oauthUsesManualCodePaste as k, pickActiveRunId as kn, BUILTIN_THEMES as kt, buildSkillsConfig as l, discoverAgentsMd as ln, toolCallPreview as lr, pendingInteractionsFromTurns as lt, writeSessionExport as m, DEFAULT_BUDGET_EXCLUDE_TOOLS as mn, clipHintsToWidth as mt, ThemeProvider as n, detectLibc as nn, loadState as nr, ASK_USER_TOOL as nt, useSyntaxStyles as o, performSelfUpdate as on, stripSpawnTokensLine as or, createInteractionTools as ot, resolveSessionExportTarget as p, DEFAULT_AGENT_ID as pn, EMPTY_HINTS as pt, parentServerName as q, chatReferenceTokensForPrompt as qn, resolveStoragePaths as qt, useColors as r, detectPackageManager as rn, marginTopFor as rr, InteractionsProvider as rt, useTheme as s, resolvePlatformPackage as sn, sumRunCosts as sr, isInteractionTool as st, computeTurnAnchors as t, compareSemver as tn, listSessionMeta as tr, reduceMcpAuth as tt, defaultSkillScanPaths as u, renderAgentsMdBlock as un, toolResultText as ur, serializeInteractionResponse as ut, IMPLICITLY_SAFE_TOOLS as v, resolveAgentId as vn, shortChord as vt, readProjects as w, TODO_WRITE_COUNTS_METADATA_KEY as wn, SETTINGS_INPUTS as wt, isOnSafelist as x, TODOS_METADATA_KEY as xn, DEFAULT_SETTINGS as xt, addToSafelist as y, singleAgentRegistry as yn, listProjectFiles as yt, refreshMcpToolsCatalog as z, INTERACTION_GUIDANCE_NO_PROMPTS as zn, CATPPUCCIN_MOCHA as zt };
9088
8518
 
9089
- //# sourceMappingURL=transcript-anchors-DsoBHg43.js.map
8519
+ //# sourceMappingURL=transcript-anchors-DSRPWjiH.js.map