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.
- package/dist/{acp-24uU2WDs.js → acp-Irby04Us.js} +7 -8
- package/dist/{acp-24uU2WDs.js.map → acp-Irby04Us.js.map} +1 -1
- package/dist/acp-cli.js +7 -7
- package/dist/acp.d.ts +2 -3
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-Cch2ayTt.js → agent-DkSmGkDM.js} +10871 -5411
- package/dist/agent-DkSmGkDM.js.map +1 -0
- package/dist/agent.d.ts +1 -2
- package/dist/agent.js +2 -2
- package/dist/{anthropic-RGqsJlC8.js → anthropic-CTHsdFG9.js} +46 -6
- package/dist/anthropic-CTHsdFG9.js.map +1 -0
- package/dist/{auth-C-ms5N2x.js → auth-BDSu_0t3.js} +13 -86
- package/dist/auth-BDSu_0t3.js.map +1 -0
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +4 -3
- package/dist/chat.d.ts +45 -8
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +9 -6
- package/dist/chat.js.map +1 -1
- package/dist/completion-core-CXYSVhZo.js +147 -0
- package/dist/completion-core-CXYSVhZo.js.map +1 -0
- package/dist/{context-breakdown-kO-pDsay.js → context-breakdown-Dzo25N45.js} +2 -23
- package/dist/context-breakdown-Dzo25N45.js.map +1 -0
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/daytona.js +1 -1
- package/dist/contexts/docker.d.ts +10 -2
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +142 -103
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/contexts/e2b.js +1 -1
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +4 -1
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-Lzf5huID.js → contexts-Vone3qQQ.js} +26 -37
- package/dist/contexts-Vone3qQQ.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/errors.d.ts +1 -2
- package/dist/errors.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +3 -3
- package/dist/exec-file-size-PXkr-MGA.js +63 -0
- package/dist/exec-file-size-PXkr-MGA.js.map +1 -0
- package/dist/extensions.d.ts +2 -0
- package/dist/extensions.js +2 -0
- package/dist/generate-data-WL_yGVL6.js +324 -0
- package/dist/generate-data-WL_yGVL6.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +15 -10
- package/dist/headless.js.map +1 -1
- package/dist/index-B8UtiZy7.d.ts +14967 -0
- package/dist/index-B8UtiZy7.d.ts.map +1 -0
- package/dist/{index-B3ciFUZx.d.ts → index-DsPQbyUn.d.ts} +2 -2
- package/dist/index-DsPQbyUn.d.ts.map +1 -0
- package/dist/index.d.ts +6 -12
- package/dist/index.js +20 -43
- package/dist/index.js.map +1 -1
- package/dist/{logger-Ktm-lj1s.js → logger-DBX9uYOw.js} +108 -5
- package/dist/logger-DBX9uYOw.js.map +1 -0
- package/dist/login-DOF8pMVM.js +539 -0
- package/dist/login-DOF8pMVM.js.map +1 -0
- package/dist/{mcp-Dn5W65Lv.js → mcp-CcvuVXB9.js} +2 -2
- package/dist/{mcp-Dn5W65Lv.js.map → mcp-CcvuVXB9.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-YZQLKaz2.js → messages-CwujiS74.js} +4 -4
- package/dist/messages-CwujiS74.js.map +1 -0
- package/dist/{openai-compat-0Y7jcncn.js → openai-compat-BvgYGSR1.js} +7 -6
- package/dist/{openai-compat-0Y7jcncn.js.map → openai-compat-BvgYGSR1.js.map} +1 -1
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/{glob-DluQFSw1.js → output-cap-kBUHBlnq.js} +50 -2
- package/dist/output-cap-kBUHBlnq.js.map +1 -0
- package/dist/presets.d.ts +1 -2
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +4 -4
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-DgmdYGKm.js → providers-z6LDMAr3.js} +3 -3
- package/dist/{providers-DgmdYGKm.js.map → providers-z6LDMAr3.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/{read-state-BymF41Jb.js → read-state-Dq_TXUX1.js} +2 -2
- package/dist/{read-state-BymF41Jb.js.map → read-state-Dq_TXUX1.js.map} +1 -1
- package/dist/{interpolate-BtIgcCuz.js → resolve-Cr8JSL_O.js} +562 -136
- package/dist/resolve-Cr8JSL_O.js.map +1 -0
- package/dist/restate.d.ts +2 -2
- package/dist/restate.js +2 -2
- package/dist/{sandbox-Dgy-m6UF.d.ts → sandbox-CgjG2VvQ.d.ts} +2 -2
- package/dist/{sandbox-Dgy-m6UF.d.ts.map → sandbox-CgjG2VvQ.d.ts.map} +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-CqrAFAKJ.js → session-CsdDIPY8.js} +108 -3
- package/dist/session-CsdDIPY8.js.map +1 -0
- package/dist/session.d.ts +2 -2
- package/dist/session.js +3 -3
- package/dist/skills-Bsb0rvWI.js +295 -0
- package/dist/skills-Bsb0rvWI.js.map +1 -0
- package/dist/skills.d.ts +2 -3
- package/dist/skills.js +3 -24
- package/dist/{errors-CkTvg97v.d.ts → timeout-8vSIRjzl.d.ts} +10 -2
- package/dist/timeout-8vSIRjzl.d.ts.map +1 -0
- package/dist/{timeout-kGGSXagK.js → timeout-BDetbuN6.js} +59 -2
- package/dist/timeout-BDetbuN6.js.map +1 -0
- package/dist/tool-formatters-DY0TDb1r.d.ts +378 -0
- package/dist/tool-formatters-DY0TDb1r.d.ts.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +2 -3
- package/dist/tools.js +3 -4
- package/dist/{transcript-anchors-BHIgMlPq.d.ts → transcript-anchors-Cb-bDbNH.d.ts} +233 -640
- package/dist/transcript-anchors-Cb-bDbNH.d.ts.map +1 -0
- package/dist/{transcript-anchors-DsoBHg43.js → transcript-anchors-DSRPWjiH.js} +391 -961
- package/dist/transcript-anchors-DSRPWjiH.js.map +1 -0
- package/dist/tui.d.ts +335 -11
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +6375 -1892
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-Dw02ZoSQ.d.ts → turn-operations-CgMROLsr.d.ts} +29 -4
- package/dist/turn-operations-CgMROLsr.d.ts.map +1 -0
- package/dist/{turn-operations-tG0vuBNc.js → turn-operations-Dq9Kx7m2.js} +321 -81
- package/dist/turn-operations-Dq9Kx7m2.js.map +1 -0
- package/dist/{types-BqdTeBaC.d.ts → types-BDccavwj.d.ts} +42 -1
- package/dist/types-BDccavwj.d.ts.map +1 -0
- package/dist/{types-CyVGdbia.js → types-BIarq1qE.js} +90 -2
- package/dist/types-BIarq1qE.js.map +1 -0
- package/dist/types.d.ts +5 -6
- package/dist/types.js +1 -1
- package/dist/{xai-tPdbAGkq.js → xai-B_e6TOA8.js} +2 -2
- package/dist/{xai-tPdbAGkq.js.map → xai-B_e6TOA8.js.map} +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHAT.md +7 -6
- package/docs/EXECUTION_CONTEXT.md +16 -0
- package/docs/EXTENSIONS.md +1063 -0
- package/docs/SKILL.md +16 -0
- package/docs/TUI.md +10 -3
- package/package.json +12 -3
- package/dist/agent-Cch2ayTt.js.map +0 -1
- package/dist/agent-EFWt6uQ_.d.ts +0 -6914
- package/dist/agent-EFWt6uQ_.d.ts.map +0 -1
- package/dist/anthropic-RGqsJlC8.js.map +0 -1
- package/dist/auth-C-ms5N2x.js.map +0 -1
- package/dist/context-breakdown-kO-pDsay.js.map +0 -1
- package/dist/contexts-Lzf5huID.js.map +0 -1
- package/dist/errors-CkTvg97v.d.ts.map +0 -1
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/glob-shell-rJMoCIGb.js +0 -21
- package/dist/glob-shell-rJMoCIGb.js.map +0 -1
- package/dist/index-B3ciFUZx.d.ts.map +0 -1
- package/dist/index-BtzE3Uaq.d.ts +0 -2738
- package/dist/index-BtzE3Uaq.d.ts.map +0 -1
- package/dist/index-tSzOaWNt.d.ts +0 -316
- package/dist/index-tSzOaWNt.d.ts.map +0 -1
- package/dist/interpolate-BtIgcCuz.js.map +0 -1
- package/dist/logger-DGiGf7DW.d.ts +0 -102
- package/dist/logger-DGiGf7DW.d.ts.map +0 -1
- package/dist/logger-Ktm-lj1s.js.map +0 -1
- package/dist/login-CCA-1lgK.js +0 -267
- package/dist/login-CCA-1lgK.js.map +0 -1
- package/dist/messages-YZQLKaz2.js.map +0 -1
- package/dist/policy-DcGlpaNs.d.ts +0 -129
- package/dist/policy-DcGlpaNs.d.ts.map +0 -1
- package/dist/presets-BQWYMHdd.js +0 -114
- package/dist/presets-BQWYMHdd.js.map +0 -1
- package/dist/run-summary-DkN8KsHM.d.ts +0 -249
- package/dist/run-summary-DkN8KsHM.d.ts.map +0 -1
- package/dist/session-CqrAFAKJ.js.map +0 -1
- package/dist/shell-quote-BmnhZmdM.js +0 -33
- package/dist/shell-quote-BmnhZmdM.js.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/timeout-CpFm0jJ6.d.ts +0 -10
- package/dist/timeout-CpFm0jJ6.d.ts.map +0 -1
- package/dist/timeout-kGGSXagK.js.map +0 -1
- package/dist/tool-formatters-C6iLgc9f.d.ts +0 -1454
- package/dist/tool-formatters-C6iLgc9f.d.ts.map +0 -1
- package/dist/tools-CCd8e-HT.js +0 -1739
- package/dist/tools-CCd8e-HT.js.map +0 -1
- package/dist/transcript-anchors-BHIgMlPq.d.ts.map +0 -1
- package/dist/transcript-anchors-DsoBHg43.js.map +0 -1
- package/dist/turn-operations-Dw02ZoSQ.d.ts.map +0 -1
- package/dist/turn-operations-tG0vuBNc.js.map +0 -1
- package/dist/types-BqdTeBaC.d.ts.map +0 -1
- package/dist/types-CyVGdbia.js.map +0 -1
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
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 {
|
|
11
|
-
import {
|
|
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 {
|
|
15
|
-
import {
|
|
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,
|
|
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.
|
|
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) ?? "
|
|
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
|
-
|
|
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(() =>
|
|
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
|
|
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 (!
|
|
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
|
|
3091
|
-
|
|
3092
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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)), [
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
8519
|
+
//# sourceMappingURL=transcript-anchors-DSRPWjiH.js.map
|