zidane 6.6.4 → 7.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 (157) hide show
  1. package/dist/{acp-BQqhozmt.js → acp-Wr8l4IDI.js} +7 -180
  2. package/dist/acp-Wr8l4IDI.js.map +1 -0
  3. package/dist/acp-cli.js +6 -6
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.d.ts.map +1 -1
  6. package/dist/acp.js +2 -1
  7. package/dist/{agent-DTxH7RHW.js → agent-C-camKQW.js} +162 -12
  8. package/dist/agent-C-camKQW.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{anthropic-DRNRnd6W.js → anthropic-BDOGzhtj.js} +3 -3
  12. package/dist/{anthropic-DRNRnd6W.js.map → anthropic-BDOGzhtj.js.map} +1 -1
  13. package/dist/{transcript-anchors-BjH-68k2.js → approvals-CqqxE5Vh.js} +4924 -275
  14. package/dist/approvals-CqqxE5Vh.js.map +1 -0
  15. package/dist/{auth-7rv6ZfWk.js → auth-BdwDROYg.js} +12 -8
  16. package/dist/{auth-7rv6ZfWk.js.map → auth-BdwDROYg.js.map} +1 -1
  17. package/dist/chat/pure.d.ts +221 -4
  18. package/dist/chat/pure.d.ts.map +1 -0
  19. package/dist/chat/pure.js +4 -2
  20. package/dist/chat.d.ts +6 -2911
  21. package/dist/chat.js +8 -261
  22. package/dist/{context-transport-D8yeGdpd.js → context-transport-BqYwzJYE.js} +2 -2
  23. package/dist/{context-transport-D8yeGdpd.js.map → context-transport-BqYwzJYE.js.map} +1 -1
  24. package/dist/contexts/daytona.d.ts +1 -1
  25. package/dist/contexts/daytona.js +1 -1
  26. package/dist/contexts/e2b.d.ts +1 -1
  27. package/dist/contexts/e2b.js +1 -1
  28. package/dist/daemon-cli.d.ts +1 -0
  29. package/dist/daemon-cli.js +90 -0
  30. package/dist/daemon-cli.js.map +1 -0
  31. package/dist/eval.d.ts +1 -1
  32. package/dist/eval.js +1 -1
  33. package/dist/extensions.d.ts +2 -2
  34. package/dist/extensions.js +2 -2
  35. package/dist/headless.d.ts +1 -1
  36. package/dist/headless.js +4 -4
  37. package/dist/{index-D0O-QO7Z.d.ts → index-B-FSaM3_.d.ts} +2863 -2375
  38. package/dist/index-B-FSaM3_.d.ts.map +1 -0
  39. package/dist/index-DgdzXRPH.d.ts +3590 -0
  40. package/dist/index-DgdzXRPH.d.ts.map +1 -0
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +14 -1179
  43. package/dist/ipc-CIv87xuH.js +331 -0
  44. package/dist/ipc-CIv87xuH.js.map +1 -0
  45. package/dist/ipc.d.ts +849 -0
  46. package/dist/ipc.d.ts.map +1 -0
  47. package/dist/ipc.js +4 -0
  48. package/dist/json-rpc-rSWMUh9b.js +192 -0
  49. package/dist/json-rpc-rSWMUh9b.js.map +1 -0
  50. package/dist/{logger-DBX9uYOw.js → logger--C1VQjwJ.js} +3 -13
  51. package/dist/logger--C1VQjwJ.js.map +1 -0
  52. package/dist/logs-view-CWG8aUAG.js +44 -0
  53. package/dist/logs-view-CWG8aUAG.js.map +1 -0
  54. package/dist/{mcp-CVtSQTWg.js → mcp-BIWa57eY.js} +3 -3
  55. package/dist/{mcp-CVtSQTWg.js.map → mcp-BIWa57eY.js.map} +1 -1
  56. package/dist/mcp.d.ts +1 -1
  57. package/dist/mcp.js +1 -1
  58. package/dist/{messages-CZMUuKzo.js → messages-21XfHxyF.js} +3 -3
  59. package/dist/{messages-CZMUuKzo.js.map → messages-21XfHxyF.js.map} +1 -1
  60. package/dist/{openai-compat-BoTi-9fd.js → openai-compat-DY-AKxCg.js} +2 -2
  61. package/dist/{openai-compat-BoTi-9fd.js.map → openai-compat-DY-AKxCg.js.map} +1 -1
  62. package/dist/output/stream-json.d.ts +1 -1
  63. package/dist/output/terminal.d.ts +1 -1
  64. package/dist/{plugins-onDvTHEm.js → plugins-B531Tz5b.js} +2 -2
  65. package/dist/{plugins-onDvTHEm.js.map → plugins-B531Tz5b.js.map} +1 -1
  66. package/dist/plugins.d.ts +1 -1
  67. package/dist/plugins.js +1 -1
  68. package/dist/presets.d.ts +1 -1
  69. package/dist/presets.js +1 -1
  70. package/dist/providers/anthropic.d.ts +1 -1
  71. package/dist/providers/anthropic.js +1 -1
  72. package/dist/providers/arcee.d.ts +1 -1
  73. package/dist/providers/arcee.js +1 -1
  74. package/dist/providers/baseten.d.ts +1 -1
  75. package/dist/providers/baseten.js +1 -1
  76. package/dist/providers/cerebras.d.ts +1 -1
  77. package/dist/providers/cerebras.js +1 -1
  78. package/dist/providers/local.d.ts +1 -1
  79. package/dist/providers/local.js +1 -1
  80. package/dist/providers/openai-compat.d.ts +1 -1
  81. package/dist/providers/openai-compat.js +1 -1
  82. package/dist/providers/openai.d.ts +1 -1
  83. package/dist/providers/openai.js +3 -3
  84. package/dist/providers/openrouter.d.ts +1 -1
  85. package/dist/providers/openrouter.js +1 -1
  86. package/dist/providers/xai.d.ts +1 -1
  87. package/dist/providers/xai.js +1 -1
  88. package/dist/{providers-CmhdtD7w.js → providers-DX91jwir.js} +2 -2
  89. package/dist/{providers-CmhdtD7w.js.map → providers-DX91jwir.js.map} +1 -1
  90. package/dist/providers.d.ts +1 -1
  91. package/dist/providers.js +3 -3
  92. package/dist/restate.d.ts +1 -1
  93. package/dist/restate.js +8 -8
  94. package/dist/restate.js.map +1 -1
  95. package/dist/server-DJqp9dIR.js +1317 -0
  96. package/dist/server-DJqp9dIR.js.map +1 -0
  97. package/dist/session/sqlite.d.ts +1 -1
  98. package/dist/session/sqlite.d.ts.map +1 -1
  99. package/dist/session/sqlite.js +42 -4
  100. package/dist/session/sqlite.js.map +1 -1
  101. package/dist/{session-CyRLSsVa.js → session-K3icbPH9.js} +8 -2
  102. package/dist/session-K3icbPH9.js.map +1 -0
  103. package/dist/session.d.ts +1 -1
  104. package/dist/session.js +2 -2
  105. package/dist/{skills-BMGH_KK1.js → skills-PJyiSRCp.js} +3 -21
  106. package/dist/skills-PJyiSRCp.js.map +1 -0
  107. package/dist/skills.d.ts +1 -1
  108. package/dist/skills.js +1 -1
  109. package/dist/src-DzzhE2YO.js +2461 -0
  110. package/dist/src-DzzhE2YO.js.map +1 -0
  111. package/dist/todo-edits-Dw2oO2ON.js +378 -0
  112. package/dist/todo-edits-Dw2oO2ON.js.map +1 -0
  113. package/dist/{tool-formatters-NrzD6Fkk.d.ts → tool-formatters-DO56YBBw.d.ts} +104 -4
  114. package/dist/tool-formatters-DO56YBBw.d.ts.map +1 -0
  115. package/dist/tools/fetch-url.d.ts +1 -1
  116. package/dist/tools/web-search.d.ts +1 -1
  117. package/dist/tools.d.ts +1 -1
  118. package/dist/tools.js +1 -1
  119. package/dist/{transcript-anchors-UhC58Tmj.d.ts → transcript-anchors-DXqWWGbq.d.ts} +53 -4
  120. package/dist/transcript-anchors-DXqWWGbq.d.ts.map +1 -0
  121. package/dist/tui.d.ts +94 -13
  122. package/dist/tui.d.ts.map +1 -1
  123. package/dist/tui.js +2241 -1009
  124. package/dist/tui.js.map +1 -1
  125. package/dist/{turn-operations-mMxDRgLY.js → turn-operations-CZLsNgw-.js} +896 -11
  126. package/dist/turn-operations-CZLsNgw-.js.map +1 -0
  127. package/dist/{turn-operations-Cuc9pwiB.d.ts → turn-operations-DKFYWP3O.d.ts} +501 -4
  128. package/dist/turn-operations-DKFYWP3O.d.ts.map +1 -0
  129. package/dist/{types-BIarq1qE.js → types-CWgENVLY.js} +24 -2
  130. package/dist/types-CWgENVLY.js.map +1 -0
  131. package/dist/types.d.ts +1 -1
  132. package/dist/types.js +1 -1
  133. package/dist/xml-D5HBhJle.js +22 -0
  134. package/dist/xml-D5HBhJle.js.map +1 -0
  135. package/docs/CHAT.md +39 -0
  136. package/docs/IPC.md +420 -0
  137. package/docs/TUI.md +62 -0
  138. package/package.json +8 -2
  139. package/dist/acp-BQqhozmt.js.map +0 -1
  140. package/dist/agent-DTxH7RHW.js.map +0 -1
  141. package/dist/chat.d.ts.map +0 -1
  142. package/dist/chat.js.map +0 -1
  143. package/dist/index-D0O-QO7Z.d.ts.map +0 -1
  144. package/dist/index.js.map +0 -1
  145. package/dist/inline-autocomplete-D0MPvBaS.js +0 -596
  146. package/dist/inline-autocomplete-D0MPvBaS.js.map +0 -1
  147. package/dist/logger-DBX9uYOw.js.map +0 -1
  148. package/dist/login-CB23LyI1.js +0 -707
  149. package/dist/login-CB23LyI1.js.map +0 -1
  150. package/dist/session-CyRLSsVa.js.map +0 -1
  151. package/dist/skills-BMGH_KK1.js.map +0 -1
  152. package/dist/tool-formatters-NrzD6Fkk.d.ts.map +0 -1
  153. package/dist/transcript-anchors-BjH-68k2.js.map +0 -1
  154. package/dist/transcript-anchors-UhC58Tmj.d.ts.map +0 -1
  155. package/dist/turn-operations-Cuc9pwiB.d.ts.map +0 -1
  156. package/dist/turn-operations-mMxDRgLY.js.map +0 -1
  157. package/dist/types-BIarq1qE.js.map +0 -1
@@ -0,0 +1,1317 @@
1
+ import { Mn as serializedPayloadBytes, at as createExtensionHud, jn as createExtensionBus, ot as discoverAndBuildExtensionRegistry } from "./agent-C-camKQW.js";
2
+ import { S as resolveInlineSuggestion, b as createPathAutocompleteProvider } from "./src-DzzhE2YO.js";
3
+ import { t as writeFileAtomic } from "./atomic-write-B7zr1ddF.js";
4
+ import { p as resolveStorageDirs } from "./plugins-B531Tz5b.js";
5
+ import { a as successResponse, i as errorResponse, n as JsonRpcRemoteError, r as createJsonRpcConnection } from "./json-rpc-rSWMUh9b.js";
6
+ import { Pt as isMainSession } from "./turn-operations-CZLsNgw-.js";
7
+ import { c as searchSessionHistoryIndexAsync, n as buildSessionSearchIndexAsync, u as searchSessionIndexAsync } from "./history-search-C4bk1Wz_.js";
8
+ import { Gt as modelChainMetadata, In as resolveConfig, It as createChatHost, Kt as sessionModelFallbacks, Lt as toTodosSnapshot, ai as eventsFromTurns, cn as DEFAULT_SETTINGS, fn as SETTINGS_TOGGLES, ft as createFileMcpCredentialStore, hn as enforceSettingsInvariants, ii as deriveSessionTitle } from "./approvals-CqqxE5Vh.js";
9
+ import { d as BUILTIN_PROVIDERS, n as applyApiKeyEnv, r as credentialsPath } from "./auth-BdwDROYg.js";
10
+ import { basename, dirname, join } from "node:path";
11
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync } from "node:fs";
12
+ import { spawn } from "node:child_process";
13
+ import { fileURLToPath } from "node:url";
14
+ import { connect, createServer } from "node:net";
15
+ //#region src/ipc/daemon/lifecycle.ts
16
+ /**
17
+ * Daemon lifecycle: socket-path resolution, liveness probing, stale-socket
18
+ * cleanup, and spawn-on-demand.
19
+ *
20
+ * The socket lives in a dedicated daemon subdir of the resolved state dir
21
+ * (`~/.zidane/daemon/daemon.sock` by default, honoring the same
22
+ * `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX` env knobs every other surface
23
+ * uses), overridable via `ZIDANE_DAEMON_SOCKET` for parallel installs and
24
+ * tests.
25
+ */
26
+ /**
27
+ * Build fingerprint for the handshake — the mtime of this module's file
28
+ * at import time. Client and daemon load the same file from disk, so a
29
+ * daemon spawned before a rebuild (or a source edit in dev) reports a
30
+ * different id than a freshly-started client, and the client drains +
31
+ * respawns it. This closes the "same protocol version, different code"
32
+ * gap that a hand-bumped version constant can't: during development the
33
+ * daemon process is frozen at spawn while the tree keeps moving.
34
+ *
35
+ * Compiled binaries (`/$bunfs/…` virtual paths) can't be stat'ed and
36
+ * report `'unknown'`; the check is skipped when EITHER side is unknown,
37
+ * so released builds rely on `IPC_PROTOCOL_VERSION` alone — correct,
38
+ * since their code only changes when the version does.
39
+ */
40
+ const IPC_BUILD_ID = (() => {
41
+ try {
42
+ const selfPath = fileURLToPath(import.meta.url);
43
+ const ipcRoot = dirname(dirname(selfPath));
44
+ if (!existsSync(join(ipcRoot, "protocol.ts"))) return String(Math.floor(statSync(selfPath).mtimeMs));
45
+ const srcRoot = dirname(ipcRoot);
46
+ let newest = 0;
47
+ const walk = (dir) => {
48
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
49
+ const full = join(dir, entry.name);
50
+ if (entry.isDirectory()) walk(full);
51
+ else newest = Math.max(newest, statSync(full).mtimeMs);
52
+ }
53
+ };
54
+ try {
55
+ walk(srcRoot);
56
+ } catch {
57
+ newest = statSync(selfPath).mtimeMs;
58
+ }
59
+ return String(Math.floor(newest));
60
+ } catch {
61
+ return "unknown";
62
+ }
63
+ })();
64
+ function defaultSocketPath() {
65
+ const explicit = process.env.ZIDANE_DAEMON_SOCKET;
66
+ if (explicit) return explicit;
67
+ return join(resolveStorageDirs({ ...process.env.ZIDANE_PREFIX !== void 0 ? { prefix: process.env.ZIDANE_PREFIX } : {} }).stateDir, "daemon", "daemon.sock");
68
+ }
69
+ /**
70
+ * Pre-v6 socket location for the given (v6) socket path, or null when the
71
+ * path isn't the default `<stateDir>/daemon/daemon.sock` layout (explicit
72
+ * `ZIDANE_DAEMON_SOCKET` users never had a migration to run). A daemon
73
+ * still serving the old path would be ORPHANED by the move — new clients
74
+ * never probe it, and a service-style `--idle-exit 0` daemon would hold
75
+ * agents and run-guard markers against the same sessions.db forever —
76
+ * so daemon startup probes this path and drains any survivor.
77
+ */
78
+ function legacySocketPath(socketPath) {
79
+ const dir = dirname(socketPath);
80
+ if (basename(socketPath) !== "daemon.sock" || basename(dir) !== "daemon") return null;
81
+ return join(dirname(dir), "daemon.sock");
82
+ }
83
+ /**
84
+ * Spawn command that re-enters the CURRENT zidane program as `… daemon`.
85
+ *
86
+ * Three launch shapes:
87
+ * - compiled Bun binary — `Bun.main` is the virtual `/$bunfs/…` entry the
88
+ * binary self-inserts as argv[1], so the spawn must NOT repeat it
89
+ * (verified: repeating it makes the child parse the junk path as its
90
+ * subcommand and boot the full TUI instead of the daemon).
91
+ * - dev (`bun run cli.ts`) — the script path must ride along.
92
+ * - Node hosts (no `Bun` global) — fall back to the published bin.
93
+ */
94
+ function selfDaemonSpawnCommand() {
95
+ const bunMain = globalThis.Bun?.main;
96
+ if (bunMain?.startsWith("/$bunfs")) return [process.execPath, "daemon"];
97
+ if (bunMain && /(?:^|[/\\])cli\.[cm]?[jt]s$/.test(bunMain)) return [
98
+ process.execPath,
99
+ bunMain,
100
+ "daemon"
101
+ ];
102
+ return ["zidane-daemon"];
103
+ }
104
+ /**
105
+ * True when something accepts connections on `socketPath`. Distinguishes a
106
+ * live daemon from a stale socket file left by a crashed one.
107
+ */
108
+ function probeSocket(socketPath, timeoutMs = 1e3) {
109
+ return new Promise((resolve) => {
110
+ try {
111
+ if (!existsSync(socketPath) || !statSync(socketPath).isSocket()) {
112
+ resolve(false);
113
+ return;
114
+ }
115
+ } catch {
116
+ resolve(false);
117
+ return;
118
+ }
119
+ let socket;
120
+ try {
121
+ socket = connect(socketPath);
122
+ } catch {
123
+ resolve(false);
124
+ return;
125
+ }
126
+ const done = (alive) => {
127
+ socket.destroy();
128
+ resolve(alive);
129
+ };
130
+ socket.once("connect", () => done(true));
131
+ socket.once("error", () => done(false));
132
+ socket.setTimeout(timeoutMs, () => done(false));
133
+ });
134
+ }
135
+ /**
136
+ * Make sure a daemon is listening, spawning one if needed. Resolves with
137
+ * the socket path once a connection succeeds; rejects when the daemon
138
+ * failed to come up within `timeoutMs`.
139
+ */
140
+ async function ensureDaemonRunning(options = {}) {
141
+ const socketPath = options.socketPath ?? defaultSocketPath();
142
+ if (await probeSocket(socketPath)) return socketPath;
143
+ const [cmd, ...args] = options.spawnCommand ?? ["zidane-daemon"];
144
+ const child = spawn(cmd, args, {
145
+ detached: true,
146
+ stdio: "ignore",
147
+ env: {
148
+ ...process.env,
149
+ ZIDANE_DAEMON_SOCKET: socketPath
150
+ }
151
+ });
152
+ let spawnError;
153
+ child.once("error", (err) => {
154
+ spawnError = err;
155
+ });
156
+ child.unref();
157
+ const deadline = Date.now() + (options.timeoutMs ?? 8e3);
158
+ for (;;) {
159
+ if (spawnError !== void 0) throw new Error(`Failed to spawn zidane daemon (${cmd}): ${spawnError.message}`);
160
+ if (await probeSocket(socketPath)) return socketPath;
161
+ if (Date.now() > deadline) throw new Error(`zidane daemon did not come up on ${socketPath} within ${options.timeoutMs ?? 8e3}ms.`);
162
+ await new Promise((r) => setTimeout(r, 150));
163
+ }
164
+ }
165
+ //#endregion
166
+ //#region src/ipc/protocol.ts
167
+ /**
168
+ * v6 (changes since v5):
169
+ * - `event` signals carry an optional `echo` flag marking host-generated
170
+ * SEND/STEER echoes of the user's own message.
171
+ * - `chat.pushQueued` result gained the `'queued'` delivered variant.
172
+ * - `sessions.setModel` now honors `fallbacks`: it persists the canonical
173
+ * `metadata.modelChain` and CLEARS a previously persisted chain when
174
+ * omitted/empty.
175
+ * - additive: `extension.emit` notification + `extension:signal` fan-out
176
+ * (the permission-gated per-extension cross-window bus, `ctx.bus`).
177
+ * Older daemons ignore the unknown notification; older clients drop the
178
+ * unknown signal kind — no version bump needed.
179
+ * - additive: `extension.emit` / `extension:signal` carry an optional
180
+ * `nonce` (the emitter's per-process random id) so receivers dedupe on
181
+ * `(extensionId, nonce, seq)` instead of the collision-prone
182
+ * `(extensionId, seq)`. Older peers omit it and fall back to the
183
+ * seq-only key — no version bump needed.
184
+ * - `SessionRow.modelFallbacks` is now populated from the session's
185
+ * persisted `metadata.modelChain`.
186
+ * - Default socket path moved from `<stateDir>/daemon.sock` to the
187
+ * dedicated `<stateDir>/daemon/daemon.sock` (dir chmod'd 0700) so the
188
+ * daemon can harden its socket dir without touching shared state files.
189
+ * - ADDITIVE (no version bump — older daemons answer "method not
190
+ * found", clients degrade to local compute): `suggest.resolve` RPC
191
+ * resolves inline ghost-text autocomplete daemon-side, and the
192
+ * `suggest:changed` signal fans the result out to every OTHER peer
193
+ * on the session so mirroring windows paint the same ghost without
194
+ * recomputing.
195
+ * - ADDITIVE (no version bump — optional field, older daemons simply
196
+ * omit it and clients fall back to the single `suggestion`):
197
+ * `suggest.resolve` results and `suggest:changed` signals carry the
198
+ * resolver's full ordered `candidates` list so daemon mode keeps
199
+ * ↑/↓ candidate rotation instead of degrading to one suggestion.
200
+ */
201
+ const IPC_PROTOCOL_VERSION = 8;
202
+ /**
203
+ * JSON-RPC error codes specific to the zidane daemon. Lives here (not in
204
+ * `daemon/server.ts`) so clients can import the codes without pulling the
205
+ * server's module graph.
206
+ */
207
+ const IpcErrorCode = {
208
+ VersionMismatch: -32010,
209
+ NotHandshaken: -32011
210
+ };
211
+ //#endregion
212
+ //#region src/ipc/client.ts
213
+ var IpcVersionMismatchError = class extends Error {
214
+ daemon;
215
+ constructor(message, daemon) {
216
+ super(message);
217
+ this.daemon = daemon;
218
+ this.name = "IpcVersionMismatchError";
219
+ }
220
+ };
221
+ async function connectIpcClient(options) {
222
+ const socket = await new Promise((resolvePromise, reject) => {
223
+ const s = connect(options.socketPath);
224
+ s.once("connect", () => {
225
+ s.off("error", reject);
226
+ resolvePromise(s);
227
+ });
228
+ s.once("error", reject);
229
+ });
230
+ const listeners = /* @__PURE__ */ new Set();
231
+ let closed = false;
232
+ const connection = createJsonRpcConnection({
233
+ input: socket,
234
+ output: socket,
235
+ onRequest: (message) => errorResponse(message.id, -32601, "Clients do not accept requests."),
236
+ onNotification: (message) => {
237
+ if (message.method !== "ipc.signal") return;
238
+ const signal = message.params;
239
+ if (!signal) return;
240
+ for (const cb of listeners) cb(signal);
241
+ }
242
+ });
243
+ connection.start();
244
+ const close = () => {
245
+ if (closed) return;
246
+ closed = true;
247
+ connection.close();
248
+ socket.destroy();
249
+ };
250
+ socket.on("close", () => {
251
+ if (closed) return;
252
+ closed = true;
253
+ connection.close();
254
+ options.onClose?.();
255
+ });
256
+ socket.on("error", () => {});
257
+ const HELLO_TIMEOUT_MS = 5e3;
258
+ const REQUEST_TIMEOUT_MS = 3e4;
259
+ const LONG_REQUEST_TIMEOUT_MS = 600 * 1e3;
260
+ /** RPCs that legitimately run long — full compaction, index-backed cross-session search. */
261
+ const LONG_REQUEST_METHODS = /* @__PURE__ */ new Set([
262
+ "sessions.compact",
263
+ "history.searchAll",
264
+ "history.searchSession"
265
+ ]);
266
+ let helloTimer;
267
+ let hello;
268
+ try {
269
+ hello = await Promise.race([connection.sendRequest("ipc.hello", {
270
+ protocolVersion: 8,
271
+ appVersion: options.appVersion ?? "dev",
272
+ pid: process.pid,
273
+ buildId: options.drainOnMismatch === false ? "unknown" : IPC_BUILD_ID
274
+ }), new Promise((_, reject) => {
275
+ helloTimer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Daemon handshake timed out after ${HELLO_TIMEOUT_MS}ms on ${options.socketPath}.`)), HELLO_TIMEOUT_MS);
276
+ })]);
277
+ } catch (err) {
278
+ if (err instanceof JsonRpcRemoteError && err.code === IpcErrorCode.VersionMismatch) {
279
+ if (options.drainOnMismatch !== false) await Promise.race([connection.sendRequest("daemon.shutdown", {}).catch(() => void 0), new Promise((resolve) => setTimeout(resolve, 1500))]);
280
+ const daemon = err.data?.daemon;
281
+ close();
282
+ throw new IpcVersionMismatchError(err.message, daemon);
283
+ }
284
+ close();
285
+ throw err;
286
+ } finally {
287
+ clearTimeout(helloTimer);
288
+ }
289
+ return {
290
+ hello,
291
+ notify: (method, params) => {
292
+ try {
293
+ connection.sendNotification(method, params);
294
+ } catch {}
295
+ },
296
+ request: async (method, params) => {
297
+ const timeoutMs = LONG_REQUEST_METHODS.has(method) ? LONG_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS;
298
+ let timer;
299
+ try {
300
+ return await Promise.race([connection.sendRequest(method, params), new Promise((_, reject) => {
301
+ timer = setTimeout(() => {
302
+ reject(/* @__PURE__ */ new Error(`Daemon request ${method} timed out after ${timeoutMs}ms — tearing the connection down.`));
303
+ socket.destroy();
304
+ }, timeoutMs);
305
+ timer.unref?.();
306
+ })]);
307
+ } finally {
308
+ clearTimeout(timer);
309
+ }
310
+ },
311
+ onSignal: (cb) => {
312
+ listeners.add(cb);
313
+ return () => listeners.delete(cb);
314
+ },
315
+ close
316
+ };
317
+ }
318
+ /**
319
+ * One-call convenience for windows: make sure a daemon is up (spawning if
320
+ * needed), connect, and — when the running daemon speaks an older protocol
321
+ * — drain it and spawn a fresh one before retrying once.
322
+ */
323
+ async function connectOrSpawnDaemon(options = {}) {
324
+ const socketPath = await ensureDaemonRunning(options);
325
+ try {
326
+ return await connectIpcClient({
327
+ ...options,
328
+ socketPath
329
+ });
330
+ } catch (err) {
331
+ if (!(err instanceof IpcVersionMismatchError)) throw err;
332
+ const drainDeadline = Date.now() + 2e3;
333
+ while (await probeSocket(socketPath)) {
334
+ if (Date.now() > drainDeadline) break;
335
+ await new Promise((r) => setTimeout(r, 100));
336
+ }
337
+ const retryPath = await ensureDaemonRunning(options);
338
+ return connectIpcClient({
339
+ ...options,
340
+ socketPath: retryPath
341
+ });
342
+ }
343
+ }
344
+ //#endregion
345
+ //#region src/ipc/daemon/environment.ts
346
+ /**
347
+ * File-backed pending-approvals persistence (`pending-approvals.json` next
348
+ * to `state.json`). Small, rewritten atomically enough for its purpose —
349
+ * the queue holds at most a handful of rows.
350
+ */
351
+ function createFileApprovalsStore(path) {
352
+ function read() {
353
+ if (!existsSync(path)) return [];
354
+ try {
355
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
356
+ return Array.isArray(parsed) ? parsed : [];
357
+ } catch {
358
+ return [];
359
+ }
360
+ }
361
+ function write(rows) {
362
+ if (rows.length === 0) {
363
+ try {
364
+ unlinkSync(path);
365
+ } catch {}
366
+ return;
367
+ }
368
+ mkdirSync(dirname(path), { recursive: true });
369
+ writeFileAtomic(path, JSON.stringify(rows, null, 2));
370
+ }
371
+ return {
372
+ insert(request) {
373
+ const rows = read().filter((r) => r.id !== request.id);
374
+ rows.push(request);
375
+ write(rows);
376
+ },
377
+ delete(id) {
378
+ write(read().filter((r) => r.id !== id));
379
+ },
380
+ clear() {
381
+ write([]);
382
+ },
383
+ list() {
384
+ return read();
385
+ }
386
+ };
387
+ }
388
+ async function createDaemonEnvironment(options) {
389
+ const { createTuiStore } = await import("./session/sqlite.js");
390
+ const config = resolveConfig({
391
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
392
+ store: (resolved) => createTuiStore(resolved.db)
393
+ });
394
+ const store = config.store;
395
+ if (config.paths.projectDir) process.stderr.write(`[zidane-daemon] warning: project-DB mode is active — this daemon is bound to ${config.paths.db}. Windows opened in other projects will share this project's sessions. Run a per-project daemon (ZIDANE_DAEMON_SOCKET) to isolate them.\n`);
396
+ const readSettings = () => ({
397
+ ...DEFAULT_SETTINGS,
398
+ ...config.initialSettings,
399
+ ...config.stateStore.load().settings ?? {}
400
+ });
401
+ /**
402
+ * Fields the invariant forced (only ever `false`), so the stored file records
403
+ * them too — otherwise a later read of `state.settings` would resurrect the
404
+ * value the invariant just rejected.
405
+ */
406
+ const pickInvariantFields = (merged, patch) => {
407
+ const out = {};
408
+ for (const row of SETTINGS_TOGGLES) if (row.requires && merged[row.key] === false && patch[row.key] !== false) out[row.key] = false;
409
+ return out;
410
+ };
411
+ const writeSettings = (patch) => {
412
+ const state = config.stateStore.load();
413
+ const merged = enforceSettingsInvariants({
414
+ ...DEFAULT_SETTINGS,
415
+ ...config.initialSettings,
416
+ ...state.settings ?? {},
417
+ ...patch
418
+ });
419
+ const next = {
420
+ ...state.settings ?? {},
421
+ ...patch,
422
+ ...pickInvariantFields(merged, patch)
423
+ };
424
+ config.stateStore.save({
425
+ ...state,
426
+ settings: next
427
+ });
428
+ return merged;
429
+ };
430
+ const hud = createExtensionHud();
431
+ hud.subscribe(() => {
432
+ const state = hud.getState();
433
+ options.broadcast({
434
+ kind: "hud:state",
435
+ statuses: Object.fromEntries(Object.entries(state.statuses).map(([name, s]) => [name, {
436
+ text: s.text,
437
+ loader: s.loader
438
+ }])),
439
+ banners: Object.fromEntries(Object.entries(state.banners).map(([name, lines]) => [name, [...lines]]))
440
+ });
441
+ });
442
+ hud.onNotify((n) => {
443
+ options.broadcast({
444
+ kind: "hud:notify",
445
+ extension: n.extension,
446
+ text: n.text,
447
+ tone: n.tone
448
+ });
449
+ });
450
+ const extensionBus = createExtensionBus();
451
+ extensionBus.onEmit((envelope) => {
452
+ options.broadcast({
453
+ kind: "extension:signal",
454
+ ...envelope
455
+ });
456
+ });
457
+ const RUNTIME_CACHE_MAX = 16;
458
+ const runtimeCache = /* @__PURE__ */ new Map();
459
+ /**
460
+ * Best-effort release of an evicted entry's extension registry
461
+ * (teardown hooks, HUD writers — `registry.dispose()` is idempotent).
462
+ * Fire-and-forget: eviction must never block or throw on a broken
463
+ * extension teardown, and a rejected resolve has nothing to release.
464
+ */
465
+ const disposeRuntime = (promise) => {
466
+ promise.then((config) => config.extensions.dispose()).catch(() => void 0);
467
+ };
468
+ const runtimeConfig = (cwd) => {
469
+ const settings = readSettings();
470
+ const key = [
471
+ cwd,
472
+ JSON.stringify(settings.enabledExtensions ?? null),
473
+ String(settings.loadProjectExtensions === true)
474
+ ].join("\0");
475
+ const existing = runtimeCache.get(key);
476
+ if (existing) {
477
+ runtimeCache.delete(key);
478
+ runtimeCache.set(key, existing);
479
+ return existing;
480
+ }
481
+ const promise = (async () => {
482
+ return resolveConfig({
483
+ cwd,
484
+ extensionRegistry: await discoverAndBuildExtensionRegistry({
485
+ cwd,
486
+ hud,
487
+ bus: extensionBus,
488
+ prefix: config.prefix,
489
+ ...settings.enabledExtensions !== void 0 ? { enabledAllowlist: [...settings.enabledExtensions] } : {},
490
+ trustProjectExtensions: settings.loadProjectExtensions === true,
491
+ readSettings
492
+ }),
493
+ store: () => store
494
+ });
495
+ })();
496
+ runtimeCache.set(key, promise);
497
+ while (runtimeCache.size > RUNTIME_CACHE_MAX) {
498
+ const oldest = runtimeCache.keys().next().value;
499
+ if (oldest === void 0) break;
500
+ const evicted = runtimeCache.get(oldest);
501
+ runtimeCache.delete(oldest);
502
+ if (evicted) disposeRuntime(evicted);
503
+ }
504
+ promise.catch(() => {
505
+ if (runtimeCache.get(key) === promise) runtimeCache.delete(key);
506
+ });
507
+ return promise;
508
+ };
509
+ const userDataDir = () => config.paths.userDir;
510
+ const applyAuthEnv = () => {
511
+ const dir = userDataDir();
512
+ process.env.ZIDANE_CREDENTIALS_PATH = credentialsPath(dir);
513
+ applyApiKeyEnv(dir, BUILTIN_PROVIDERS);
514
+ };
515
+ return {
516
+ multiProcess: true,
517
+ broadcast: options.broadcast,
518
+ store,
519
+ readSettings,
520
+ writeSettings,
521
+ runtimeConfig,
522
+ userDataDir,
523
+ applyAuthEnv,
524
+ mcpCredentialStore: () => createFileMcpCredentialStore(userDataDir()),
525
+ approvalsPersistence: createFileApprovalsStore(join(dirname(config.paths.state), "pending-approvals.json")),
526
+ ...options.debugStream !== void 0 ? { debugStream: options.debugStream } : {},
527
+ invalidateRuntime: () => {
528
+ for (const entry of runtimeCache.values()) disposeRuntime(entry);
529
+ runtimeCache.clear();
530
+ },
531
+ config,
532
+ extensionBus
533
+ };
534
+ }
535
+ //#endregion
536
+ //#region src/ipc/daemon/server.ts
537
+ /**
538
+ * Per-peer write backpressure bound. A wedged peer (e.g. a SIGSTOP'd
539
+ * terminal) stops reading its socket, so fan-out writes pile up in
540
+ * `socket.writableLength` without bound and can OOM the daemon. A peer
541
+ * buffering more than this is destroyed — the protocol supports re-attach
542
+ * + backfill, so killing a wedged peer is safe and self-healing.
543
+ */
544
+ const PEER_WRITE_HIGH_WATERMARK = 8 * 1024 * 1024;
545
+ async function startIpcDaemon(options) {
546
+ const { socketPath } = options;
547
+ const log = options.onLog ?? (() => void 0);
548
+ const startedAt = Date.now();
549
+ const appVersion = options.appVersion ?? "dev";
550
+ const peers = /* @__PURE__ */ new Set();
551
+ /** Sessions with a run in flight, tracked from the run signals themselves. */
552
+ const runningSessions = /* @__PURE__ */ new Set();
553
+ const broadcast = (signal) => {
554
+ broadcastExcept(null, signal);
555
+ switch (signal.kind) {
556
+ case "run:start":
557
+ runningSessions.add(signal.sessionId);
558
+ broadcastPresence();
559
+ break;
560
+ case "run:end":
561
+ runningSessions.delete(signal.sessionId);
562
+ broadcastPresence();
563
+ break;
564
+ case "approval:request":
565
+ case "approval:resolved":
566
+ case "interaction:request":
567
+ case "interaction:resolved":
568
+ broadcastPresence();
569
+ break;
570
+ default: break;
571
+ }
572
+ };
573
+ /**
574
+ * Fan out to every handshaken peer except `origin`. Draft signals use
575
+ * this so a window's own keystrokes never bounce back and fight its
576
+ * cursor — echo-safety by construction, no client-side dedupe.
577
+ */
578
+ function broadcastExcept(origin, signal) {
579
+ for (const peer of peers) {
580
+ if (!peer.ready || peer === origin) continue;
581
+ if (peer.socket.writableLength > PEER_WRITE_HIGH_WATERMARK) {
582
+ console.warn(`[ipc-daemon] peer buffered ${peer.socket.writableLength} bytes (> ${PEER_WRITE_HIGH_WATERMARK}) — destroying wedged connection`);
583
+ peer.socket.destroy();
584
+ continue;
585
+ }
586
+ try {
587
+ peer.connection.sendNotification("ipc.signal", signal);
588
+ } catch {}
589
+ }
590
+ }
591
+ /**
592
+ * Shared per-session prompt drafts. Ephemeral by design — in-memory
593
+ * only, cleared when any window submits, gone on daemon restart.
594
+ */
595
+ const drafts = /* @__PURE__ */ new Map();
596
+ /**
597
+ * Bound a peer-supplied attachment list: shape-guarded entries only,
598
+ * 24 MiB of base64 per list. Shared by `queue.add` and
599
+ * `drafts.setAttachments`.
600
+ */
601
+ function boundAttachments(list) {
602
+ let budget = 24 * 1024 * 1024;
603
+ const out = [];
604
+ for (const entry of list) {
605
+ const a = entry;
606
+ if (typeof a?.mediaType !== "string" || typeof a?.data !== "string") continue;
607
+ budget -= a.data.length;
608
+ if (budget < 0) break;
609
+ out.push({
610
+ mediaType: a.mediaType,
611
+ data: a.data,
612
+ ...typeof a.name === "string" ? { name: a.name.slice(0, 256) } : {}
613
+ });
614
+ }
615
+ return out;
616
+ }
617
+ /** Total base64 bytes currently pinned by ALL draft attachments. */
618
+ function draftAttachmentBytes() {
619
+ let total = 0;
620
+ for (const entry of drafts.values()) for (const attachment of entry.attachments ?? []) total += attachment.data.length;
621
+ return total;
622
+ }
623
+ /** Consume the draft after a submit — every window except the sender clears. */
624
+ function clearDraft(sessionId, origin) {
625
+ const entry = drafts.get(sessionId);
626
+ if (!entry) return;
627
+ drafts.delete(sessionId);
628
+ if (entry.text) broadcastExcept(origin, {
629
+ kind: "draft:changed",
630
+ sessionId,
631
+ text: ""
632
+ });
633
+ if (entry.attachments?.length) broadcastExcept(origin, {
634
+ kind: "draft:attachments",
635
+ sessionId,
636
+ attachments: []
637
+ });
638
+ }
639
+ /**
640
+ * Shared inline-autocomplete state — per-session latest-wins
641
+ * coalescing for `suggest.resolve`. `seq` is the newest sequence seen
642
+ * (stale requests return without computing); `controller` aborts an
643
+ * in-flight compute when a newer request supersedes it (the GUI's
644
+ * controller-map pattern, daemon-side). Bounded like `drafts`: keys
645
+ * only evict on session delete, so cap the map at 256 sessions.
646
+ */
647
+ const suggests = /* @__PURE__ */ new Map();
648
+ /** Drop empty draft entries so the map only holds live composing state. */
649
+ function compactDraft(sessionId, entry) {
650
+ if (!entry.text && !entry.shell && !entry.attachments?.length && !entry.search?.active) drafts.delete(sessionId);
651
+ else drafts.set(sessionId, entry);
652
+ }
653
+ const env = await (options.createEnvironment ? options.createEnvironment(broadcast) : createDaemonEnvironment({ broadcast }));
654
+ const host = createChatHost(env);
655
+ /**
656
+ * Live presence: which sessions have attached windows, and their
657
+ * run / awaiting-input state. Union of watched, live-agent, and
658
+ * pending-HITL sessions — small by construction.
659
+ */
660
+ function computePresence() {
661
+ const windowCounts = /* @__PURE__ */ new Map();
662
+ for (const peer of peers) if (peer.ready && peer.watching) windowCounts.set(peer.watching, (windowCounts.get(peer.watching) ?? 0) + 1);
663
+ const attention = /* @__PURE__ */ new Set();
664
+ for (const request of host.approvals.listPending()) attention.add(request.sessionId);
665
+ for (const pending of host.interactions.list()) attention.add(pending.sessionId);
666
+ const live = new Map(host.listLiveSessions().map((entry) => [entry.sessionId, entry.running]));
667
+ return [.../* @__PURE__ */ new Set([
668
+ ...windowCounts.keys(),
669
+ ...attention,
670
+ ...live.keys(),
671
+ ...runningSessions
672
+ ])].map((sessionId) => ({
673
+ sessionId,
674
+ windows: windowCounts.get(sessionId) ?? 0,
675
+ running: runningSessions.has(sessionId) || live.get(sessionId) === true,
676
+ attention: attention.has(sessionId)
677
+ }));
678
+ }
679
+ function broadcastPresence() {
680
+ broadcastExcept(null, {
681
+ kind: "presence:changed",
682
+ sessions: computePresence()
683
+ });
684
+ }
685
+ const SEARCH_INDEX_CACHE_MAX = 64;
686
+ /** Latest `history.searchAll` generation per peer — see the handler. */
687
+ const searchGenerations = /* @__PURE__ */ new WeakMap();
688
+ const searchIndexCache = /* @__PURE__ */ new Map();
689
+ async function sessionSearchIndex(sessionId) {
690
+ const liveTurns = host.getLiveTurns(sessionId);
691
+ let turns;
692
+ let key;
693
+ if (liveTurns) {
694
+ turns = [...liveTurns];
695
+ key = `live:${turns.length}:${turns.at(-1)?.id ?? ""}`;
696
+ } else {
697
+ const data = await env.store.load(sessionId);
698
+ if (!data) return null;
699
+ turns = data.turns;
700
+ key = `store:${data.updatedAt}:${data.turns.length}`;
701
+ }
702
+ const cached = searchIndexCache.get(sessionId);
703
+ if (cached?.key === key) {
704
+ searchIndexCache.delete(sessionId);
705
+ searchIndexCache.set(sessionId, cached);
706
+ return cached.index;
707
+ }
708
+ const index = await buildSessionSearchIndexAsync(turns);
709
+ searchIndexCache.set(sessionId, {
710
+ key,
711
+ index
712
+ });
713
+ while (searchIndexCache.size > SEARCH_INDEX_CACHE_MAX) {
714
+ const oldest = searchIndexCache.keys().next().value;
715
+ if (oldest === void 0) break;
716
+ searchIndexCache.delete(oldest);
717
+ }
718
+ return index;
719
+ }
720
+ host.approvals.rehydrate();
721
+ const store = env.store;
722
+ function toRow(data) {
723
+ const meta = data.metadata ?? {};
724
+ const title = deriveSessionTitle(data.turns, meta);
725
+ return {
726
+ id: data.id,
727
+ title,
728
+ ...isMainSession(meta) ? { isMain: true } : {},
729
+ projectRoot: data.projectRoot ?? null,
730
+ cwd: typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "",
731
+ ...typeof data.agentId === "string" ? { agentId: data.agentId } : {},
732
+ status: host.isSessionRunning(data.id) ? "running" : data.status === "running" && Date.now() - data.updatedAt >= 3e5 ? "idle" : data.status,
733
+ createdAt: data.createdAt,
734
+ updatedAt: data.updatedAt,
735
+ archived: meta.archived === true,
736
+ ...typeof meta.providerKey === "string" ? { providerKey: meta.providerKey } : {},
737
+ ...typeof meta.modelId === "string" ? { modelId: meta.modelId } : {},
738
+ ...typeof meta.effort === "string" ? { effort: meta.effort } : {},
739
+ ...meta.modelOptions && typeof meta.modelOptions === "object" ? { modelOptions: meta.modelOptions } : {},
740
+ ...(() => {
741
+ const fallbacks = sessionModelFallbacks(meta);
742
+ return fallbacks ? { modelFallbacks: fallbacks } : {};
743
+ })()
744
+ };
745
+ }
746
+ async function loadOrThrow(sessionId) {
747
+ const data = await store.load(sessionId);
748
+ if (!data) throw new Error(`Session "${sessionId}" not found.`);
749
+ return data;
750
+ }
751
+ /**
752
+ * Apply a metadata-only patch. Prefers the host's live-session path (safe
753
+ * mid-run — routes through the in-memory `Session` whose turns are the
754
+ * source of truth); only falls back to a store-level full-document
755
+ * `save()` when the session is idle, where no run is appending turns so
756
+ * `load → mutate → save` can't drop concurrently-persisted turns.
757
+ *
758
+ * `bumpUpdatedAt: false` skips the recency bump so a rename doesn't yank
759
+ * the row to the top of recency-sorted lists (mirrors the GUI).
760
+ */
761
+ async function patchSessionMeta(sessionId, patch, opts = {}) {
762
+ if (await host.patchLiveSessionMeta(sessionId, patch)) {
763
+ broadcast({
764
+ kind: "sessions:changed",
765
+ op: "update",
766
+ sessionId
767
+ });
768
+ return;
769
+ }
770
+ const data = await loadOrThrow(sessionId);
771
+ for (const [key, value] of Object.entries(patch)) data.metadata[key] = value;
772
+ if (opts.bumpUpdatedAt !== false) data.updatedAt = Date.now();
773
+ await store.save(data);
774
+ broadcast({
775
+ kind: "sessions:changed",
776
+ op: "update",
777
+ sessionId
778
+ });
779
+ }
780
+ async function patchSession(sessionId, mutate) {
781
+ const data = await loadOrThrow(sessionId);
782
+ mutate(data);
783
+ data.updatedAt = Date.now();
784
+ await store.save(data);
785
+ broadcast({
786
+ kind: "sessions:changed",
787
+ op: "update",
788
+ sessionId
789
+ });
790
+ }
791
+ async function createSessionRow(params) {
792
+ const id = await host.createSession(params);
793
+ const data = await loadOrThrow(id);
794
+ broadcast({
795
+ kind: "sessions:changed",
796
+ op: "create",
797
+ sessionId: id
798
+ });
799
+ return toRow(data);
800
+ }
801
+ const methods = {
802
+ "chat.send": async (p, peer) => {
803
+ const { wake: _stripped, ...params } = p;
804
+ const result = await host.sendPrompt(params);
805
+ clearDraft(p.sessionId, peer);
806
+ return result;
807
+ },
808
+ "chat.interrupt": (p) => host.interrupt(p.sessionId),
809
+ "chat.steer": (p) => host.steer(p.sessionId, p.message),
810
+ "chat.followUp": (p) => host.followUp(p.sessionId, p.message),
811
+ "chat.pushQueued": async (p, peer) => {
812
+ const result = await host.pushQueued(p.sessionId, p.message, p.activatedSkills);
813
+ clearDraft(p.sessionId, peer);
814
+ return result;
815
+ },
816
+ "commands.list": (p) => host.listCommands(p.cwd),
817
+ "commands.completeArgs": (p) => host.completeCommandArgs(p.cwd, p.name, p.argsPrefix),
818
+ "queue.list": (p) => host.queueList(p.sessionId),
819
+ "sessions.systemMd": (p) => host.sessionSystemMd(p.sessionId),
820
+ "queue.add": async (p, peer) => {
821
+ const text = typeof p.text === "string" ? p.text.slice(0, 64 * 1024) : "";
822
+ const activatedSkills = Array.isArray(p.activatedSkills) ? p.activatedSkills.filter((name) => typeof name === "string") : void 0;
823
+ const attachments = Array.isArray(p.attachments) ? boundAttachments(p.attachments) : void 0;
824
+ const result = await host.queueAdd(p.sessionId, text, activatedSkills, attachments);
825
+ clearDraft(p.sessionId, peer);
826
+ return result;
827
+ },
828
+ "sessions.contextSegments": (p) => host.getContextSegments(p.sessionId),
829
+ "sessions.createSystemOverride": (p) => host.createSystemOverride(p.sessionId),
830
+ "queue.remove": (p) => host.queueRemove(p.sessionId, p.id),
831
+ "queue.pushNow": (p) => host.queuePushNow(p.sessionId, p.id),
832
+ "drafts.get": (p) => {
833
+ const entry = drafts.get(p.sessionId);
834
+ return {
835
+ text: entry?.text ?? "",
836
+ ...entry?.shell ? { shell: true } : {},
837
+ ...entry?.attachments?.length ? { attachments: entry.attachments } : {},
838
+ ...entry?.search?.active ? { search: entry.search } : {}
839
+ };
840
+ },
841
+ "drafts.set": (p, peer) => {
842
+ const text = typeof p.text === "string" ? p.text.slice(0, 64 * 1024) : "";
843
+ const truncated = typeof p.text === "string" && p.text.length > text.length;
844
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
845
+ const entry = drafts.get(p.sessionId) ?? {};
846
+ entry.text = text.length > 0 ? text : void 0;
847
+ entry.shell = p.shell === true ? true : void 0;
848
+ compactDraft(p.sessionId, entry);
849
+ broadcastExcept(truncated ? null : peer, {
850
+ kind: "draft:changed",
851
+ sessionId: p.sessionId,
852
+ text,
853
+ ...p.shell === true ? { shell: true } : {}
854
+ });
855
+ },
856
+ "search.set": (p, peer) => {
857
+ const active = p.active === true;
858
+ const query = typeof p.query === "string" ? p.query.slice(0, 4 * 1024) : "";
859
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
860
+ const entry = drafts.get(p.sessionId) ?? {};
861
+ entry.search = active || query.length > 0 ? {
862
+ active,
863
+ query
864
+ } : void 0;
865
+ compactDraft(p.sessionId, entry);
866
+ broadcastExcept(peer, {
867
+ kind: "search:changed",
868
+ sessionId: p.sessionId,
869
+ active,
870
+ query
871
+ });
872
+ },
873
+ "extension.emit": async (p, peer) => {
874
+ const extensionId = typeof p.extensionId === "string" ? p.extensionId : "";
875
+ if (extensionId.length === 0 || extensionId.length > 256) return;
876
+ if (typeof p.seq !== "number" || !Number.isFinite(p.seq)) return;
877
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : void 0;
878
+ const bytes = serializedPayloadBytes(p.payload);
879
+ if (bytes === null || bytes > 65536) return;
880
+ let registry = env.config.extensions;
881
+ if (sessionId !== void 0) try {
882
+ const data = await loadOrThrow(sessionId);
883
+ const meta = data.metadata ?? {};
884
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
885
+ registry = (await env.runtimeConfig(cwd)).extensions;
886
+ } catch {
887
+ return;
888
+ }
889
+ if (!registry.loaded.some((ext) => !ext.error && ext.name === extensionId && ext.definition?.permissions?.includes("ipc") === true)) return;
890
+ const nonce = typeof p.nonce === "string" ? p.nonce : void 0;
891
+ const envelope = {
892
+ extensionId,
893
+ ...sessionId !== void 0 ? { sessionId } : {},
894
+ payload: p.payload,
895
+ seq: p.seq,
896
+ ...nonce !== void 0 ? { nonce } : {}
897
+ };
898
+ env.extensionBus?.deliver(envelope);
899
+ broadcastExcept(peer, {
900
+ kind: "extension:signal",
901
+ ...envelope
902
+ });
903
+ },
904
+ "suggest.resolve": async (p, peer) => {
905
+ const sessionId = String(p.sessionId);
906
+ const seq = typeof p.seq === "number" ? p.seq : 0;
907
+ const text = typeof p.text === "string" ? p.text.slice(0, 64 * 1024) : "";
908
+ const caret = typeof p.caret === "number" ? Math.max(0, Math.min(Math.floor(p.caret), text.length)) : text.length;
909
+ const prev = suggests.get(sessionId);
910
+ if (prev && seq < prev.seq) return {
911
+ suggestion: null,
912
+ seq
913
+ };
914
+ if (!prev && suggests.size >= 256) return {
915
+ suggestion: null,
916
+ seq
917
+ };
918
+ prev?.controller?.abort();
919
+ const controller = new AbortController();
920
+ const entry = {
921
+ seq,
922
+ controller
923
+ };
924
+ suggests.set(sessionId, entry);
925
+ try {
926
+ const data = await loadOrThrow(sessionId);
927
+ const meta = data.metadata ?? {};
928
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
929
+ const config = await env.runtimeConfig(cwd);
930
+ const result = await resolveInlineSuggestion([createPathAutocompleteProvider({ cwd: () => cwd }), ...config.extensions.inlineAutocompleteProviders], {
931
+ text,
932
+ cursor: caret,
933
+ cwd,
934
+ signal: controller.signal
935
+ });
936
+ if (controller.signal.aborted || suggests.get(sessionId) !== entry) return {
937
+ suggestion: null,
938
+ seq
939
+ };
940
+ const candidates = result ? [...result.suffixes] : [];
941
+ const suggestion = candidates[0] ?? null;
942
+ broadcastExcept(peer, {
943
+ kind: "suggest:changed",
944
+ sessionId,
945
+ seq,
946
+ basis: text,
947
+ caret,
948
+ suggestion,
949
+ ...candidates.length > 0 ? { candidates } : {}
950
+ });
951
+ return {
952
+ suggestion,
953
+ seq,
954
+ ...candidates.length > 0 ? { candidates } : {}
955
+ };
956
+ } finally {
957
+ if (suggests.get(sessionId) === entry) entry.controller = null;
958
+ }
959
+ },
960
+ "drafts.setAttachments": (p, peer) => {
961
+ const bounded = Array.isArray(p.attachments) ? boundAttachments(p.attachments) : [];
962
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
963
+ const currentBytes = (drafts.get(p.sessionId)?.attachments ?? []).reduce((sum, a) => sum + a.data.length, 0);
964
+ const incomingBytes = bounded.reduce((sum, a) => sum + a.data.length, 0);
965
+ if (draftAttachmentBytes() - currentBytes + incomingBytes > 96 * 1024 * 1024) return;
966
+ const entry = drafts.get(p.sessionId) ?? {};
967
+ entry.attachments = bounded.length > 0 ? bounded : void 0;
968
+ compactDraft(p.sessionId, entry);
969
+ broadcastExcept(peer, {
970
+ kind: "draft:attachments",
971
+ sessionId: p.sessionId,
972
+ attachments: bounded
973
+ });
974
+ },
975
+ "chat.isRunning": (p) => host.isSessionRunning(p.sessionId),
976
+ "chat.rebuildAgent": (p) => host.disposeAgentForRebuild(p.sessionId),
977
+ "chat.listWork": (p) => host.listRunningWork(p.sessionId),
978
+ "chat.cancelWork": (p) => host.cancelRunningWork(p.sessionId, p.id, p.reason),
979
+ "sessions.list": async (p) => {
980
+ const filter = p?.projectRoot !== void 0 ? { projectRoot: p.projectRoot } : void 0;
981
+ const applyArchive = (rows) => {
982
+ const filtered = rows.filter((row) => {
983
+ if (p?.archive === "active") return !row.archived;
984
+ if (p?.archive === "archived") return row.archived;
985
+ return true;
986
+ });
987
+ filtered.sort((a, b) => b.updatedAt - a.updatedAt);
988
+ return filtered;
989
+ };
990
+ if (store.listMeta) return applyArchive((await store.listMeta(filter)).map((meta) => {
991
+ const md = meta.metadata;
992
+ const title = deriveSessionTitle(meta.firstUserTurn ? [meta.firstUserTurn] : [], md);
993
+ return {
994
+ id: meta.id,
995
+ title,
996
+ projectRoot: meta.projectRoot ?? null,
997
+ cwd: typeof md.cwd === "string" ? md.cwd : meta.projectRoot ?? "",
998
+ ...meta.agentId ? { agentId: meta.agentId } : {},
999
+ status: host.isSessionRunning(meta.id) ? "running" : meta.status === "running" && Date.now() - meta.updatedAt >= 3e5 ? "idle" : meta.status ?? "idle",
1000
+ createdAt: meta.createdAt ?? 0,
1001
+ updatedAt: meta.updatedAt,
1002
+ archived: md.archived === true,
1003
+ ...typeof md.providerKey === "string" ? { providerKey: md.providerKey } : {},
1004
+ ...typeof md.modelId === "string" ? { modelId: md.modelId } : {},
1005
+ ...typeof md.effort === "string" ? { effort: md.effort } : {},
1006
+ ...md.modelOptions && typeof md.modelOptions === "object" ? { modelOptions: md.modelOptions } : {},
1007
+ ...(() => {
1008
+ const fallbacks = sessionModelFallbacks(md);
1009
+ return fallbacks ? { modelFallbacks: fallbacks } : {};
1010
+ })()
1011
+ };
1012
+ }));
1013
+ const ids = await store.list(filter);
1014
+ const rows = [];
1015
+ for (const id of ids) {
1016
+ const data = await store.load(id);
1017
+ if (!data) continue;
1018
+ rows.push(toRow(data));
1019
+ }
1020
+ return applyArchive(rows);
1021
+ },
1022
+ "sessions.create": (p) => createSessionRow(p),
1023
+ "sessions.delete": async (p) => {
1024
+ drafts.delete(p.sessionId);
1025
+ suggests.get(p.sessionId)?.controller?.abort();
1026
+ suggests.delete(p.sessionId);
1027
+ host.disposeSession(p.sessionId);
1028
+ await store.delete(p.sessionId);
1029
+ broadcast({
1030
+ kind: "sessions:changed",
1031
+ op: "delete",
1032
+ sessionId: p.sessionId
1033
+ });
1034
+ },
1035
+ "sessions.setTitle": (p) => patchSessionMeta(p.sessionId, { title: p.title }, { bumpUpdatedAt: false }),
1036
+ "sessions.setArchived": (p) => patchSessionMeta(p.sessionId, { archived: p.archived }),
1037
+ "sessions.setAgentId": async (p) => {
1038
+ if (await host.setLiveSessionAgentId(p.sessionId, p.agentId)) broadcast({
1039
+ kind: "sessions:changed",
1040
+ op: "update",
1041
+ sessionId: p.sessionId
1042
+ });
1043
+ else await patchSession(p.sessionId, (d) => {
1044
+ d.agentId = p.agentId;
1045
+ });
1046
+ host.disposeAgentForRebuild(p.sessionId);
1047
+ },
1048
+ "sessions.setEffort": (p) => patchSessionMeta(p.sessionId, { effort: p.effort }),
1049
+ "sessions.setModel": async (p) => {
1050
+ const chainPatch = p.fallbacks === void 0 ? {} : { modelChain: modelChainMetadata(p.providerKey, p.modelId, p.fallbacks) };
1051
+ await patchSessionMeta(p.sessionId, {
1052
+ providerKey: p.providerKey,
1053
+ modelId: p.modelId,
1054
+ ...chainPatch
1055
+ });
1056
+ host.disposeAgentForRebuild(p.sessionId);
1057
+ },
1058
+ "sessions.setModelOptions": (p) => patchSessionMeta(p.sessionId, { modelOptions: p.options }),
1059
+ "sessions.loadEvents": async (p) => {
1060
+ const data = await loadOrThrow(p.sessionId);
1061
+ return eventsFromTurns(data.turns, data.runs);
1062
+ },
1063
+ "sessions.getTurn": async (p) => {
1064
+ return (await store.load(p.sessionId))?.turns.find((t) => t.id === p.turnId) ?? null;
1065
+ },
1066
+ "sessions.contextBreakdown": (p) => host.getContextBreakdown(p.sessionId),
1067
+ "history.searchSession": async (p) => {
1068
+ const index = await sessionSearchIndex(p.sessionId);
1069
+ if (!index) return [];
1070
+ const limit = Number.isInteger(p.limit) && p.limit > 0 ? Math.min(p.limit, index.turns.length) : index.turns.length;
1071
+ const matches = await searchSessionIndexAsync(index, p.query, { limit });
1072
+ const order = new Map(index.turns.map((entry, i) => [entry.turn.id, i]));
1073
+ matches.sort((a, b) => (order.get(a.turn.id) ?? 0) - (order.get(b.turn.id) ?? 0));
1074
+ return matches;
1075
+ },
1076
+ "history.searchAll": async (p, peer) => {
1077
+ const generation = (searchGenerations.get(peer) ?? 0) + 1;
1078
+ searchGenerations.set(peer, generation);
1079
+ const results = {};
1080
+ const ids = Array.isArray(p.sessionIds) ? p.sessionIds.slice(0, 500) : [];
1081
+ for (const sessionId of ids) {
1082
+ if (searchGenerations.get(peer) !== generation) break;
1083
+ try {
1084
+ const index = await sessionSearchIndex(sessionId);
1085
+ if (!index) continue;
1086
+ const match = await searchSessionHistoryIndexAsync(index, p.query, p.excerptLimit ?? 3);
1087
+ if (match) results[sessionId] = match;
1088
+ } catch {}
1089
+ }
1090
+ return results;
1091
+ },
1092
+ "sessions.touch": (p) => {
1093
+ broadcast({
1094
+ kind: "sessions:changed",
1095
+ op: "update",
1096
+ sessionId: p.sessionId
1097
+ });
1098
+ },
1099
+ "sessions.compact": (p) => host.compactSessionNow(p.sessionId),
1100
+ "todos.get": (p) => {
1101
+ const state = host.getTodosSnapshot(p.sessionId);
1102
+ return state ? toTodosSnapshot(state) : null;
1103
+ },
1104
+ "todos.set": async (p) => {
1105
+ const state = await host.setTodos(p.sessionId, p.items);
1106
+ return state ? toTodosSnapshot(state) : null;
1107
+ },
1108
+ "orchestration.main": () => host.orchestration.ensureMain(),
1109
+ "orchestration.createWorker": ({ mainId, ...params }) => host.orchestration.createWorker(mainId, params),
1110
+ "orchestration.list": (p) => host.orchestration.list(p.mainId, p.limit),
1111
+ "orchestration.read": async (p) => await host.orchestration.canOrchestrate(p.mainId, p.sessionId) ? host.orchestration.read(p.sessionId) : null,
1112
+ "orchestration.send": (p) => host.orchestration.send(p.mainId, p.sessionId, p.prompt, p.wake),
1113
+ "orchestration.steer": (p) => host.orchestration.steer(p.mainId, p.sessionId, p.message),
1114
+ "orchestration.cancel": (p) => host.orchestration.cancel(p.mainId, p.sessionId),
1115
+ "orchestration.transcript": (p) => host.orchestration.transcript(p.mainId, p.sessionId, p.limit),
1116
+ "interactions.list": (p) => host.interactions.list(p?.sessionId),
1117
+ "interactions.respond": (p) => host.interactions.respond(p.requestId, p.response),
1118
+ "interactions.cancel": (p) => host.interactions.cancel(p.requestId, p.reason),
1119
+ "interactions.rehydrate": (p) => host.interactions.rehydrate(p.sessionId),
1120
+ "approvals.resolve": (p) => host.approvals.resolve(p.requestId, p.decision),
1121
+ "approvals.denyAll": (p) => host.approvals.denyAll(p?.sessionId),
1122
+ "approvals.listPending": () => host.approvals.listPending(),
1123
+ "settings.get": () => env.readSettings(),
1124
+ "settings.set": (p) => {
1125
+ const before = env.readSettings();
1126
+ const next = env.writeSettings(p.patch);
1127
+ const changed = (key) => JSON.stringify(before[key]) !== JSON.stringify(next[key]);
1128
+ if (changed("enabledExtensions") || changed("loadProjectExtensions")) {
1129
+ env.invalidateRuntime();
1130
+ host.shutdown();
1131
+ } else if (changed("enabledMcps") || changed("disabledMcpTools") || changed("enabledSkills") || changed("enabledPlugins") || changed("importForeignConfigs") || changed("importRulerConfigs")) host.shutdown();
1132
+ if (changed("interactionMode") && next.interactionMode === "skip") host.interactions.resolveAutomatically();
1133
+ broadcast({
1134
+ kind: "settings:changed",
1135
+ settings: next
1136
+ });
1137
+ return next;
1138
+ },
1139
+ "presence.list": () => computePresence(),
1140
+ "presence.watch": (p, peer) => {
1141
+ peer.watching = p.sessionId;
1142
+ broadcastPresence();
1143
+ store.load(p.sessionId).then((data) => {
1144
+ if (!data) return;
1145
+ const meta = data.metadata ?? {};
1146
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
1147
+ return env.runtimeConfig(cwd);
1148
+ }).catch(() => void 0);
1149
+ },
1150
+ "daemon.info": () => ({
1151
+ protocolVersion: 8,
1152
+ appVersion,
1153
+ pid: process.pid,
1154
+ startedAt
1155
+ }),
1156
+ "daemon.shutdown": async () => {
1157
+ setImmediate(() => {
1158
+ close().then(() => options.onIdle?.());
1159
+ });
1160
+ }
1161
+ };
1162
+ let idleTimer;
1163
+ function armIdleTimer() {
1164
+ if (!options.idleShutdownMs || !options.onIdle) return;
1165
+ clearTimeout(idleTimer);
1166
+ idleTimer = setTimeout(() => {
1167
+ if (peers.size > 0) return;
1168
+ if (host.hasActiveWork()) {
1169
+ armIdleTimer();
1170
+ return;
1171
+ }
1172
+ options.onIdle?.();
1173
+ }, options.idleShutdownMs);
1174
+ }
1175
+ function handleConnection(socket) {
1176
+ const peer = {
1177
+ socket,
1178
+ connection: void 0,
1179
+ ready: false,
1180
+ watching: null
1181
+ };
1182
+ const connection = createJsonRpcConnection({
1183
+ input: socket,
1184
+ output: socket,
1185
+ onRequest: async (message) => {
1186
+ if (message.method === "ipc.hello") {
1187
+ const params = message.params ?? {};
1188
+ const clientBuild = Number(params.buildId);
1189
+ const daemonBuild = Number(IPC_BUILD_ID);
1190
+ const staleBuild = params.buildId !== void 0 && params.buildId !== "unknown" && IPC_BUILD_ID !== "unknown" && Number.isFinite(clientBuild) && Number.isFinite(daemonBuild) && clientBuild > daemonBuild;
1191
+ if (params.protocolVersion !== 8 || staleBuild) return errorResponse(message.id, IpcErrorCode.VersionMismatch, staleBuild ? `Build mismatch: the daemon was started from an older build (daemon ${IPC_BUILD_ID}, client ${params.buildId}). Draining for respawn.` : `Protocol version mismatch: daemon speaks v8, client sent v${params.protocolVersion ?? "?"}.`, { daemon: {
1192
+ protocolVersion: 8,
1193
+ appVersion,
1194
+ pid: process.pid,
1195
+ startedAt,
1196
+ buildId: IPC_BUILD_ID
1197
+ } });
1198
+ peer.ready = true;
1199
+ const result = {
1200
+ protocolVersion: 8,
1201
+ appVersion,
1202
+ pid: process.pid,
1203
+ startedAt,
1204
+ buildId: IPC_BUILD_ID
1205
+ };
1206
+ return successResponse(message.id, result);
1207
+ }
1208
+ if (!peer.ready && message.method !== "daemon.shutdown" && message.method !== "daemon.info") return errorResponse(message.id, IpcErrorCode.NotHandshaken, "Handshake required: send ipc.hello first.");
1209
+ const handler = methods[message.method];
1210
+ if (!handler) return errorResponse(message.id, -32601, `Unknown method "${message.method}".`);
1211
+ try {
1212
+ const result = await handler(message.params, peer);
1213
+ return successResponse(message.id, result ?? null);
1214
+ } catch (err) {
1215
+ return errorResponse(message.id, -32e3, err instanceof Error ? err.message : String(err));
1216
+ }
1217
+ },
1218
+ onNotification: async (message) => {
1219
+ if (!peer.ready) return;
1220
+ const handler = methods[message.method];
1221
+ if (!handler) return;
1222
+ try {
1223
+ await handler(message.params, peer);
1224
+ } catch {}
1225
+ },
1226
+ onError: (err) => log(`[ipc-daemon] peer error: ${err.message}`)
1227
+ });
1228
+ peer.connection = connection;
1229
+ peers.add(peer);
1230
+ connection.start();
1231
+ log(`[ipc-daemon] peer connected (${peers.size} total)`);
1232
+ const drop = () => {
1233
+ if (!peers.delete(peer)) return;
1234
+ connection.close();
1235
+ log(`[ipc-daemon] peer disconnected (${peers.size} left)`);
1236
+ if (peer.watching) broadcastPresence();
1237
+ if (peers.size === 0) armIdleTimer();
1238
+ };
1239
+ socket.on("close", drop);
1240
+ socket.on("error", drop);
1241
+ }
1242
+ if (existsSync(socketPath)) {
1243
+ if (await probeSocket(socketPath)) throw new Error(`A zidane daemon is already listening on ${socketPath}.`);
1244
+ if (await probeSocket(socketPath)) throw new Error(`A zidane daemon is already listening on ${socketPath}.`);
1245
+ unlinkSync(socketPath);
1246
+ }
1247
+ const socketDir = dirname(socketPath);
1248
+ const dedicatedSocketDir = basename(socketDir) === "daemon" || !existsSync(socketDir);
1249
+ mkdirSync(socketDir, {
1250
+ recursive: true,
1251
+ mode: 448
1252
+ });
1253
+ if (dedicatedSocketDir) try {
1254
+ chmodSync(socketDir, 448);
1255
+ } catch {}
1256
+ const server = createServer(handleConnection);
1257
+ await new Promise((resolvePromise, reject) => {
1258
+ server.once("error", reject);
1259
+ server.listen(socketPath, () => {
1260
+ server.off("error", reject);
1261
+ resolvePromise();
1262
+ });
1263
+ });
1264
+ try {
1265
+ chmodSync(socketPath, 384);
1266
+ } catch {}
1267
+ log(`[ipc-daemon] listening on ${socketPath}`);
1268
+ let ownsSocketFile = true;
1269
+ armIdleTimer();
1270
+ const legacyPath = legacySocketPath(socketPath);
1271
+ if (legacyPath) (async () => {
1272
+ if (!await probeSocket(legacyPath)) return;
1273
+ log(`[ipc-daemon] draining legacy daemon on ${legacyPath}`);
1274
+ try {
1275
+ const survivor = await connectIpcClient({
1276
+ socketPath: legacyPath,
1277
+ appVersion: `${appVersion}/legacy-drain`,
1278
+ drainOnMismatch: true
1279
+ });
1280
+ await survivor.request("daemon.shutdown", {}).catch(() => void 0);
1281
+ survivor.close();
1282
+ } catch {}
1283
+ })();
1284
+ async function doClose() {
1285
+ clearTimeout(idleTimer);
1286
+ host.shutdown();
1287
+ for (const peer of peers) {
1288
+ peer.connection.close();
1289
+ peer.socket.destroy();
1290
+ }
1291
+ peers.clear();
1292
+ await new Promise((resolvePromise) => {
1293
+ server.close(() => resolvePromise());
1294
+ });
1295
+ if (ownsSocketFile) {
1296
+ ownsSocketFile = false;
1297
+ try {
1298
+ unlinkSync(socketPath);
1299
+ } catch {}
1300
+ }
1301
+ }
1302
+ let closePromise = null;
1303
+ function close() {
1304
+ closePromise ??= doClose();
1305
+ return closePromise;
1306
+ }
1307
+ return {
1308
+ socketPath,
1309
+ host,
1310
+ peerCount: () => peers.size,
1311
+ close
1312
+ };
1313
+ }
1314
+ //#endregion
1315
+ export { connectIpcClient as a, IpcErrorCode as c, ensureDaemonRunning as d, probeSocket as f, IpcVersionMismatchError as i, IPC_BUILD_ID as l, createDaemonEnvironment as n, connectOrSpawnDaemon as o, selfDaemonSpawnCommand as p, createFileApprovalsStore as r, IPC_PROTOCOL_VERSION as s, startIpcDaemon as t, defaultSocketPath as u };
1316
+
1317
+ //# sourceMappingURL=server-DJqp9dIR.js.map