@ai-matrx/coding-sessions 0.1.81 → 0.1.85

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.
@@ -43,18 +43,18 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
43
43
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
44
44
 
45
45
  // src/server/index.ts
46
- var import_node_crypto5 = require("crypto");
47
- var import_node_fs4 = require("fs");
48
- var import_promises7 = __toESM(require("fs/promises"), 1);
46
+ var import_node_crypto6 = require("crypto");
47
+ var import_node_fs5 = require("fs");
48
+ var import_promises9 = __toESM(require("fs/promises"), 1);
49
49
  var import_node_http = __toESM(require("http"), 1);
50
- var import_node_path7 = __toESM(require("path"), 1);
50
+ var import_node_path10 = __toESM(require("path"), 1);
51
51
  var import_node_url2 = require("url");
52
52
  var import_ws = require("ws");
53
53
 
54
54
  // src/engine/index.ts
55
- var import_node_child_process2 = require("child_process");
56
- var import_node_crypto4 = require("crypto");
57
- var import_node_fs3 = require("fs");
55
+ var import_node_child_process3 = require("child_process");
56
+ var import_node_crypto5 = require("crypto");
57
+ var import_node_fs4 = require("fs");
58
58
  var import_format = require("@ai-matrx/kit/format");
59
59
 
60
60
  // src/engine/raw-backup.ts
@@ -114,10 +114,379 @@ function createRawBackupScheduler(options) {
114
114
  };
115
115
  }
116
116
 
117
+ // src/engine/agent-attach.ts
118
+ var import_promises2 = __toESM(require("fs/promises"), 1);
119
+ var import_node_path = __toESM(require("path"), 1);
120
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
121
+ var AGENT_PLUGIN_NAME = "matrx-agent";
122
+ function escapeAttr(value) {
123
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
124
+ }
125
+ function versionWords(bundle) {
126
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
127
+ }
128
+ function renderAgentInstructions(bundle, options = {}) {
129
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
130
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
131
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
132
+ if (included.length > 0) {
133
+ parts.push(
134
+ "## Skills (always in effect)",
135
+ ...included.map((skill) => `### ${skill.label}
136
+
137
+ ${skill.body.trim()}`)
138
+ );
139
+ }
140
+ if (options.claudeSkills && listed2.length > 0) {
141
+ parts.push(
142
+ [
143
+ "## Your skills",
144
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
145
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
146
+ ].join("\n")
147
+ );
148
+ }
149
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
150
+ parts.push(bundle.instructions.trim());
151
+ parts.push("</ai_matrx_agent>");
152
+ return `${parts.filter(Boolean).join("\n\n")}
153
+ `;
154
+ }
155
+ function renderCodexSkillsList(bundle, skillsRoot) {
156
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
157
+ if (listed2.length === 0) return "";
158
+ const lines = [
159
+ "<skills_instructions>",
160
+ "## AI Matrx agent skills",
161
+ "A skill is a set of instructions stored in a `SKILL.md` file. Each entry has a name, a description and the absolute path of its file. When a task matches a skill's description, read that file and follow it.",
162
+ "### Available skills",
163
+ ...listed2.map(
164
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
165
+ ),
166
+ "</skills_instructions>"
167
+ ];
168
+ return lines.join("\n");
169
+ }
170
+ function tomlString(value) {
171
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
172
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
173
+ }
174
+ async function writeAgentAttachment(dir, bundle, options) {
175
+ const pluginDir = import_node_path.default.join(dir, "plugin");
176
+ const skillsRoot = import_node_path.default.join(dir, "skills");
177
+ await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
178
+ await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
179
+ const instructions = renderAgentInstructions(bundle);
180
+ const instructionsFile = import_node_path.default.join(dir, "instructions.md");
181
+ await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
182
+ await import_promises2.default.writeFile(
183
+ import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
184
+ `${JSON.stringify(
185
+ {
186
+ name: AGENT_PLUGIN_NAME,
187
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
188
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
189
+ },
190
+ null,
191
+ 2
192
+ )}
193
+ `,
194
+ { mode: 384 }
195
+ );
196
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
197
+ const wanted = new Set(listed2.map((skill) => skill.slug));
198
+ for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
199
+ await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
200
+ for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
201
+ if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
202
+ }
203
+ for (const skill of listed2) {
204
+ const skillDir = import_node_path.default.join(root, skill.slug);
205
+ await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
206
+ await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
207
+ }
208
+ }
209
+ let mcpConfigFile = null;
210
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
211
+ const mcpPath = import_node_path.default.join(dir, "mcp.json");
212
+ if (mcpUrl) {
213
+ mcpConfigFile = mcpPath;
214
+ await import_promises2.default.writeFile(
215
+ mcpPath,
216
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
217
+ `,
218
+ { mode: 384 }
219
+ );
220
+ } else {
221
+ await import_promises2.default.rm(mcpPath, { force: true });
222
+ }
223
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
224
+ return {
225
+ dir,
226
+ instructionsFile,
227
+ pluginDir,
228
+ mcpConfigFile,
229
+ mcpUrl,
230
+ // Same order as Claude's: the skills list first, the agent's words last.
231
+ codexInstructions: skillsList ? `${skillsList}
232
+
233
+ ${instructions}` : instructions
234
+ };
235
+ }
236
+ function agentArgsFor(provider, attachment, options) {
237
+ if (provider === "claude") {
238
+ const args2 = [
239
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
240
+ "--append-system-prompt-file",
241
+ attachment.instructionsFile,
242
+ "--system-prompt-snapshot",
243
+ "off",
244
+ "--plugin-dir",
245
+ attachment.pluginDir
246
+ ];
247
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
248
+ return args2;
249
+ }
250
+ const args = [];
251
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
252
+ if (attachment.mcpUrl) {
253
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
254
+ args.push(
255
+ "-c",
256
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
257
+ "-c",
258
+ `${key}.default_tools_approval_mode="approve"`
259
+ );
260
+ }
261
+ return args;
262
+ }
263
+ function sessionAgentRecord(bundle, options) {
264
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
265
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
266
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
267
+ if (!options.toolsServed && bundle.tools.length > 0) {
268
+ for (const tool of bundle.tools) {
269
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
270
+ }
271
+ }
272
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
273
+ const loaded = [
274
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
275
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
276
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
277
+ ];
278
+ return {
279
+ agentId: bundle.agent_id,
280
+ versionId: bundle.version_id,
281
+ versionNumber: bundle.version_number,
282
+ name: bundle.name,
283
+ definitionHash: bundle.definition_hash,
284
+ loaded,
285
+ unavailable
286
+ };
287
+ }
288
+
289
+ // src/engine/agents.ts
290
+ var import_node_crypto2 = require("crypto");
291
+ var import_promises3 = __toESM(require("fs/promises"), 1);
292
+ var import_node_path2 = __toESM(require("path"), 1);
293
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
294
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
295
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
296
+ "agx_get_list_full",
297
+ "agx_search",
298
+ "agx_resolve_agent_address",
299
+ "ues_get_bulk",
300
+ "ues_list",
301
+ "ues_set",
302
+ "ues_touch"
303
+ ]);
304
+ var agentsClient = async () => {
305
+ const [matrx, portable, mcp] = await Promise.all([
306
+ import("@ai-matrx/agents/matrx"),
307
+ import("@ai-matrx/agents/portable"),
308
+ import("@ai-matrx/agents/portable/mcp")
309
+ ]);
310
+ return { ...matrx, ...portable, ...mcp };
311
+ };
312
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
313
+ function userIdOf(credential) {
314
+ if (!credential || credential.kind !== "user") return null;
315
+ const payload = credential.accessToken.split(".")[1];
316
+ if (!payload) return null;
317
+ try {
318
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
319
+ return typeof claims.sub === "string" ? claims.sub : null;
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ function createAgentConnection(options, dataDir) {
325
+ const credentials = options?.credentials ?? null;
326
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
327
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
328
+ const supabaseKey = options?.supabaseKey ?? null;
329
+ const root = import_node_path2.default.join(dataDir, "agents");
330
+ let mcpBaseUrl = null;
331
+ const handlers = /* @__PURE__ */ new Map();
332
+ const secretToSession = /* @__PURE__ */ new Map();
333
+ const organizationId = () => {
334
+ const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
335
+ return raw && raw.trim() ? raw.trim() : null;
336
+ };
337
+ const transport = async () => (await agentsClient()).createMatrxTransport({
338
+ baseUrl: serverUrl,
339
+ ...credentials ? { credentials } : {},
340
+ ...organizationId() ? { organizationId: () => organizationId() } : {},
341
+ source: "codingSessionsAgents"
342
+ });
343
+ const credential = async () => credentials ? credentials.get() : null;
344
+ const requireSignedIn = async () => {
345
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
346
+ };
347
+ const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
348
+ async function meta(sessionId) {
349
+ const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
350
+ const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
351
+ if (existing?.mcpSecret && existing.conversationId) return existing;
352
+ const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
353
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
354
+ await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
355
+ return created;
356
+ }
357
+ async function findSessionBySecret(secret) {
358
+ for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
359
+ const found = await import_promises3.default.readFile(import_node_path2.default.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
360
+ if (found) {
361
+ secretToSession.set(secret, name);
362
+ return name;
363
+ }
364
+ }
365
+ return null;
366
+ }
367
+ async function readSessionBundle(sessionId) {
368
+ return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
369
+ }
370
+ return {
371
+ async access() {
372
+ const current = await credential().catch(() => null);
373
+ const userId = userIdOf(current);
374
+ return {
375
+ configured: credentials !== null && supabaseKey !== null,
376
+ signedIn: userId !== null,
377
+ userId,
378
+ organizationId: organizationId()
379
+ };
380
+ },
381
+ async catalogRpc(request) {
382
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
383
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
384
+ }
385
+ const current = await credential();
386
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
387
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
388
+ const query = new URLSearchParams();
389
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
390
+ if (request.range) {
391
+ query.set("offset", String(request.range[0]));
392
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
393
+ }
394
+ const headers = {
395
+ apikey: supabaseKey,
396
+ Authorization: `Bearer ${current.accessToken}`,
397
+ "Content-Type": "application/json",
398
+ // Every catalogue RPC lives in `public`; the database's default
399
+ // exposed schema is not `public`, so name it (supabase-js does).
400
+ "Content-Profile": "public",
401
+ "Accept-Profile": "public"
402
+ };
403
+ if (request.count) headers.Prefer = `count=${request.count}`;
404
+ const qs = query.toString();
405
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
406
+ method: "POST",
407
+ headers,
408
+ body: JSON.stringify(request.args ?? {})
409
+ });
410
+ const text = await response.text();
411
+ const body = text ? JSON.parse(text) : null;
412
+ if (!response.ok) {
413
+ const err = body ?? {};
414
+ return {
415
+ data: null,
416
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
417
+ };
418
+ }
419
+ const range = response.headers.get("content-range");
420
+ const total = range?.split("/")[1];
421
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
422
+ },
423
+ async details(agentId) {
424
+ await requireSignedIn();
425
+ const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
426
+ const [bundle, versions] = await Promise.all([
427
+ fetchPortableAgent(await transport(), { agentId }),
428
+ fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
429
+ ]);
430
+ return {
431
+ agentId: bundle.agent_id,
432
+ name: bundle.name,
433
+ variables: bundle.variables,
434
+ versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
435
+ latestVersionNumber: bundle.version_number
436
+ };
437
+ },
438
+ async loadBundle(selection) {
439
+ await requireSignedIn();
440
+ const { fetchPortableAgent } = await agentsClient();
441
+ return fetchPortableAgent(
442
+ await transport(),
443
+ selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
444
+ { variables: selection.variables ?? {} }
445
+ );
446
+ },
447
+ async saveSessionBundle(sessionId, bundle) {
448
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
449
+ await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
450
+ await meta(sessionId);
451
+ },
452
+ readSessionBundle,
453
+ sessionDir,
454
+ async mcpUrlFor(sessionId) {
455
+ if (!mcpBaseUrl) return null;
456
+ const { mcpSecret } = await meta(sessionId);
457
+ secretToSession.set(mcpSecret, sessionId);
458
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
459
+ },
460
+ setMcpBaseUrl(baseUrl) {
461
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
462
+ },
463
+ async handleMcp(secret, body) {
464
+ let handler = handlers.get(secret);
465
+ if (!handler) {
466
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
467
+ if (!sessionId) return null;
468
+ const bundle = await readSessionBundle(sessionId);
469
+ if (!bundle) return null;
470
+ const { conversationId } = await meta(sessionId);
471
+ const { createAgentToolsMcpHandler } = await agentsClient();
472
+ handler = createAgentToolsMcpHandler({
473
+ bundle,
474
+ credentials,
475
+ organizationId: () => organizationId(),
476
+ baseUrl: serverUrl,
477
+ conversationId
478
+ });
479
+ handlers.set(secret, handler);
480
+ }
481
+ return handler.handle(body);
482
+ }
483
+ };
484
+ }
485
+
117
486
  // src/engine/index.ts
118
- var import_promises6 = __toESM(require("fs/promises"), 1);
119
- var import_node_os4 = __toESM(require("os"), 1);
120
- var import_node_path6 = __toESM(require("path"), 1);
487
+ var import_promises8 = __toESM(require("fs/promises"), 1);
488
+ var import_node_os5 = __toESM(require("os"), 1);
489
+ var import_node_path9 = __toESM(require("path"), 1);
121
490
  var import_node_url = require("url");
122
491
 
123
492
  // src/normalize/claude.ts
@@ -783,9 +1152,177 @@ function describeProblem(provider, problem) {
783
1152
  };
784
1153
  }
785
1154
 
786
- // src/engine/clean-env.ts
1155
+ // src/engine/binaries.ts
1156
+ var import_node_child_process = require("child_process");
1157
+ var import_node_fs = require("fs");
787
1158
  var import_node_os = __toESM(require("os"), 1);
788
- var import_node_path = __toESM(require("path"), 1);
1159
+ var import_node_path3 = __toESM(require("path"), 1);
1160
+ var SOURCE_LABEL = {
1161
+ codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
1162
+ claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
1163
+ };
1164
+ function sourceLabel(provider, source) {
1165
+ return SOURCE_LABEL[provider][source];
1166
+ }
1167
+ function parseVersion(text) {
1168
+ const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
1169
+ return match ? match[0] : null;
1170
+ }
1171
+ function compareVersions(a, b) {
1172
+ const [aCore = "", aPre] = a.split("-", 2);
1173
+ const [bCore = "", bPre] = b.split("-", 2);
1174
+ const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
1175
+ const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
1176
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1177
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1178
+ if (d !== 0) return d < 0 ? -1 : 1;
1179
+ }
1180
+ if (aPre && !bPre) return -1;
1181
+ if (!aPre && bPre) return 1;
1182
+ return 0;
1183
+ }
1184
+ function pickBinary(candidates) {
1185
+ let best = null;
1186
+ for (const c of candidates) {
1187
+ if (!best) {
1188
+ best = c;
1189
+ continue;
1190
+ }
1191
+ if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
1192
+ }
1193
+ return best;
1194
+ }
1195
+ function executable(file) {
1196
+ try {
1197
+ (0, import_node_fs.accessSync)(file, import_node_fs.constants.X_OK);
1198
+ return true;
1199
+ } catch {
1200
+ return false;
1201
+ }
1202
+ }
1203
+ function exeName(name, platform) {
1204
+ return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
1205
+ }
1206
+ function allOnPath(name, pathValue, platform = process.platform) {
1207
+ const found = [];
1208
+ for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
1209
+ if (!dir) continue;
1210
+ for (const file of exeName(name, platform)) {
1211
+ const candidate = import_node_path3.default.join(dir, file);
1212
+ if (executable(candidate)) {
1213
+ found.push(candidate);
1214
+ break;
1215
+ }
1216
+ }
1217
+ }
1218
+ return found;
1219
+ }
1220
+ function desktopAppCandidates(provider, platform, home, applications) {
1221
+ if (platform !== "darwin") return [];
1222
+ if (provider === "codex") {
1223
+ return applications.map(
1224
+ (apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1225
+ );
1226
+ }
1227
+ const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
1228
+ let versions = [];
1229
+ try {
1230
+ versions = (0, import_node_fs.readdirSync)(root);
1231
+ } catch {
1232
+ return [];
1233
+ }
1234
+ return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1235
+ }
1236
+ function npmGlobalPrefix(env) {
1237
+ const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
1238
+ if (fromEnv) return Promise.resolve(fromEnv);
1239
+ const npm = allOnPath("npm", env.PATH ?? "")[0];
1240
+ if (!npm) return Promise.resolve(null);
1241
+ return new Promise((resolve) => {
1242
+ const child = (0, import_node_child_process.execFile)(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
1243
+ resolve(error ? null : String(stdout).trim() || null);
1244
+ });
1245
+ child.stdin?.end();
1246
+ });
1247
+ }
1248
+ function declaredVersion(provider, source, file) {
1249
+ if (source !== "desktop-app") return null;
1250
+ if (provider === "codex") {
1251
+ try {
1252
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.dirname(file)), "codex-package.json"), "utf8"));
1253
+ return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
1254
+ } catch {
1255
+ return null;
1256
+ }
1257
+ }
1258
+ return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
1259
+ }
1260
+ function runVersion(file, env) {
1261
+ return new Promise((resolve) => {
1262
+ const child = (0, import_node_child_process.execFile)(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
1263
+ resolve(error ? null : parseVersion(String(stdout)));
1264
+ });
1265
+ child.stdin?.end();
1266
+ });
1267
+ }
1268
+ async function discoverBinaries(provider, options) {
1269
+ const platform = options.platform ?? process.platform;
1270
+ const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
1271
+ const listed2 = [
1272
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1273
+ ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
1274
+ ];
1275
+ const prefix = await npmGlobalPrefix(options.env);
1276
+ if (prefix) {
1277
+ const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
1278
+ for (const file of exeName(provider, platform)) {
1279
+ const candidate = import_node_path3.default.join(dir, file);
1280
+ if (executable(candidate)) {
1281
+ listed2.push({ path: candidate, source: "npm-global" });
1282
+ break;
1283
+ }
1284
+ }
1285
+ }
1286
+ const seen = /* @__PURE__ */ new Set();
1287
+ const unique = listed2.filter((c) => {
1288
+ let real = c.path;
1289
+ try {
1290
+ real = (0, import_node_fs.realpathSync)(c.path);
1291
+ } catch {
1292
+ }
1293
+ if (seen.has(real)) return false;
1294
+ seen.add(real);
1295
+ return true;
1296
+ });
1297
+ return Promise.all(
1298
+ unique.map(async (c) => ({
1299
+ ...c,
1300
+ version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
1301
+ }))
1302
+ );
1303
+ }
1304
+ async function resolveBinary(provider, options) {
1305
+ const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
1306
+ const best = pickBinary(candidates);
1307
+ const inUse = options.forced ? {
1308
+ path: options.forced,
1309
+ source: "configured",
1310
+ version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
1311
+ } : best;
1312
+ if (!inUse) return null;
1313
+ const newer = best && best.path !== inUse.path && best.version && (!inUse.version || compareVersions(best.version, inUse.version) > 0) ? { path: best.path, version: best.version, sourceLabel: sourceLabel(provider, best.source) } : null;
1314
+ return {
1315
+ path: inUse.path,
1316
+ version: inUse.version,
1317
+ source: inUse.source,
1318
+ sourceLabel: sourceLabel(provider, inUse.source),
1319
+ newer
1320
+ };
1321
+ }
1322
+
1323
+ // src/engine/clean-env.ts
1324
+ var import_node_os2 = __toESM(require("os"), 1);
1325
+ var import_node_path4 = __toESM(require("path"), 1);
789
1326
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
790
1327
  "HOME",
791
1328
  "USER",
@@ -807,11 +1344,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
807
1344
  var ALLOWED_PREFIXES = ["LC_"];
808
1345
  function wellKnownBinDirs(home) {
809
1346
  return [
810
- import_node_path.default.join(home, ".local", "bin"),
811
- import_node_path.default.join(home, ".claude", "local"),
812
- import_node_path.default.join(home, ".npm-global", "bin"),
813
- import_node_path.default.join(home, ".bun", "bin"),
814
- import_node_path.default.join(home, ".volta", "bin"),
1347
+ import_node_path4.default.join(home, ".local", "bin"),
1348
+ import_node_path4.default.join(home, ".claude", "local"),
1349
+ import_node_path4.default.join(home, ".npm-global", "bin"),
1350
+ import_node_path4.default.join(home, ".bun", "bin"),
1351
+ import_node_path4.default.join(home, ".volta", "bin"),
815
1352
  "/opt/homebrew/bin",
816
1353
  "/usr/local/bin",
817
1354
  "/usr/bin",
@@ -829,31 +1366,31 @@ function buildCleanEnv(hostEnv, extra = {}) {
829
1366
  if (typeof value !== "string") continue;
830
1367
  if (isAllowedEnvName(name)) out[name] = value;
831
1368
  }
832
- const home = out.HOME ?? import_node_os.default.homedir();
1369
+ const home = out.HOME ?? import_node_os2.default.homedir();
833
1370
  out.HOME = home;
834
- const hostPath = (hostEnv.PATH ?? "").split(import_node_path.default.delimiter).filter(Boolean);
1371
+ const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
835
1372
  const merged = [];
836
1373
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
837
1374
  if (!merged.includes(dir)) merged.push(dir);
838
1375
  }
839
- out.PATH = merged.join(import_node_path.default.delimiter);
1376
+ out.PATH = merged.join(import_node_path4.default.delimiter);
840
1377
  for (const [name, value] of Object.entries(extra)) out[name] = value;
841
1378
  return out;
842
1379
  }
843
1380
 
844
1381
  // src/engine/files.ts
845
- var import_node_fs = require("fs");
846
- var import_promises2 = __toESM(require("fs/promises"), 1);
847
- var import_node_os2 = __toESM(require("os"), 1);
848
- var import_node_path2 = __toESM(require("path"), 1);
1382
+ var import_node_fs2 = require("fs");
1383
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1384
+ var import_node_os3 = __toESM(require("os"), 1);
1385
+ var import_node_path5 = __toESM(require("path"), 1);
849
1386
 
850
1387
  // src/engine/activity.ts
851
- var import_node_child_process = require("child_process");
1388
+ var import_node_child_process2 = require("child_process");
852
1389
  var RECENT_WRITE_MS = 2 * 60 * 1e3;
853
1390
  var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
854
1391
  function listProcesses() {
855
1392
  return new Promise((resolve) => {
856
- (0, import_node_child_process.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
1393
+ (0, import_node_child_process2.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
857
1394
  if (error) {
858
1395
  resolve([]);
859
1396
  return;
@@ -912,17 +1449,17 @@ function describeActivity(input) {
912
1449
  }
913
1450
 
914
1451
  // src/engine/files.ts
915
- function defaultProviderHomes(home = import_node_os2.default.homedir()) {
1452
+ function defaultProviderHomes(home = import_node_os3.default.homedir()) {
916
1453
  return {
917
- claudeProjects: import_node_path2.default.join(home, ".claude", "projects"),
918
- codexSessions: import_node_path2.default.join(home, ".codex", "sessions"),
919
- codexIndex: import_node_path2.default.join(home, ".codex", "session_index.jsonl")
1454
+ claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
1455
+ codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
1456
+ codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
920
1457
  };
921
1458
  }
922
1459
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
923
1460
  async function safeReaddir(dir) {
924
1461
  try {
925
- return await import_promises2.default.readdir(dir, { withFileTypes: true });
1462
+ return await import_promises4.default.readdir(dir, { withFileTypes: true });
926
1463
  } catch {
927
1464
  return [];
928
1465
  }
@@ -931,7 +1468,7 @@ async function statCandidate(provider, file) {
931
1468
  const match = UUIDISH.exec(file);
932
1469
  if (!match?.[1]) return null;
933
1470
  try {
934
- const stat = await import_promises2.default.stat(file);
1471
+ const stat = await import_promises4.default.stat(file);
935
1472
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
936
1473
  } catch {
937
1474
  return null;
@@ -941,10 +1478,10 @@ async function claudeCandidates(root) {
941
1478
  const out = [];
942
1479
  for (const project of await safeReaddir(root)) {
943
1480
  if (!project.isDirectory()) continue;
944
- const dir = import_node_path2.default.join(root, project.name);
1481
+ const dir = import_node_path5.default.join(root, project.name);
945
1482
  const entries = await safeReaddir(dir);
946
1483
  const stats = await Promise.all(
947
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path2.default.join(dir, e.name)))
1484
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
948
1485
  );
949
1486
  for (const s of stats) if (s) out.push(s);
950
1487
  }
@@ -954,14 +1491,14 @@ async function codexCandidates(root) {
954
1491
  const out = [];
955
1492
  for (const year of await safeReaddir(root)) {
956
1493
  if (!year.isDirectory()) continue;
957
- for (const month of await safeReaddir(import_node_path2.default.join(root, year.name))) {
1494
+ for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
958
1495
  if (!month.isDirectory()) continue;
959
- for (const day of await safeReaddir(import_node_path2.default.join(root, year.name, month.name))) {
1496
+ for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
960
1497
  if (!day.isDirectory()) continue;
961
- const dir = import_node_path2.default.join(root, year.name, month.name, day.name);
1498
+ const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
962
1499
  const entries = await safeReaddir(dir);
963
1500
  const stats = await Promise.all(
964
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path2.default.join(dir, e.name)))
1501
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
965
1502
  );
966
1503
  for (const s of stats) if (s) out.push(s);
967
1504
  }
@@ -970,7 +1507,7 @@ async function codexCandidates(root) {
970
1507
  return out;
971
1508
  }
972
1509
  async function readSlice(file, start, length) {
973
- const handle = await import_promises2.default.open(file, "r");
1510
+ const handle = await import_promises4.default.open(file, "r");
974
1511
  try {
975
1512
  const buffer = Buffer.alloc(length);
976
1513
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1043,9 +1580,9 @@ async function readCodexIndex(file) {
1043
1580
  const names = /* @__PURE__ */ new Map();
1044
1581
  let text;
1045
1582
  try {
1046
- const stat = await import_promises2.default.stat(file);
1583
+ const stat = await import_promises4.default.stat(file);
1047
1584
  const budget = 4 * 1024 * 1024;
1048
- text = stat.size <= budget ? await import_promises2.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1585
+ text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1049
1586
  } catch {
1050
1587
  return names;
1051
1588
  }
@@ -1166,7 +1703,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1166
1703
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1167
1704
  let size;
1168
1705
  try {
1169
- size = (await import_promises2.default.stat(file)).size;
1706
+ size = (await import_promises4.default.stat(file)).size;
1170
1707
  } catch {
1171
1708
  return {
1172
1709
  items: [],
@@ -1182,7 +1719,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
1182
1719
  let droppedFirst = start > 0;
1183
1720
  let carry = "";
1184
1721
  await new Promise((resolve, reject) => {
1185
- const stream = (0, import_node_fs.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
1722
+ const stream = (0, import_node_fs2.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
1186
1723
  stream.on("data", (chunk) => {
1187
1724
  const text = carry + String(chunk);
1188
1725
  const lines = text.split("\n");
@@ -1220,9 +1757,9 @@ async function describeSessionFile(candidate, codexNames) {
1220
1757
  }
1221
1758
 
1222
1759
  // src/engine/preferences.ts
1223
- var import_node_crypto2 = require("crypto");
1224
- var import_promises3 = __toESM(require("fs/promises"), 1);
1225
- var import_node_path3 = __toESM(require("path"), 1);
1760
+ var import_node_crypto3 = require("crypto");
1761
+ var import_promises5 = __toESM(require("fs/promises"), 1);
1762
+ var import_node_path6 = __toESM(require("path"), 1);
1226
1763
  var PreferenceStore = class {
1227
1764
  constructor(dir) {
1228
1765
  this.dir = dir;
@@ -1231,11 +1768,11 @@ var PreferenceStore = class {
1231
1768
  data = {};
1232
1769
  writing = Promise.resolve();
1233
1770
  get file() {
1234
- return import_node_path3.default.join(this.dir, "preferences.json");
1771
+ return import_node_path6.default.join(this.dir, "preferences.json");
1235
1772
  }
1236
1773
  async load() {
1237
1774
  try {
1238
- const parsed = JSON.parse(await import_promises3.default.readFile(this.file, "utf8"));
1775
+ const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
1239
1776
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1240
1777
  } catch {
1241
1778
  this.data = {};
@@ -1273,10 +1810,10 @@ var PreferenceStore = class {
1273
1810
  persist() {
1274
1811
  const snapshot = JSON.stringify(this.data, null, 1);
1275
1812
  this.writing = this.writing.then(async () => {
1276
- await import_promises3.default.mkdir(this.dir, { recursive: true });
1277
- const tmp = `${this.file}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
1278
- await import_promises3.default.writeFile(tmp, snapshot, { mode: 384 });
1279
- await import_promises3.default.rename(tmp, this.file);
1813
+ await import_promises5.default.mkdir(this.dir, { recursive: true });
1814
+ const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1815
+ await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
1816
+ await import_promises5.default.rename(tmp, this.file);
1280
1817
  }).catch(() => {
1281
1818
  });
1282
1819
  }
@@ -1286,7 +1823,7 @@ var PreferenceStore = class {
1286
1823
  };
1287
1824
 
1288
1825
  // src/engine/search.ts
1289
- var import_node_fs2 = require("fs");
1826
+ var import_node_fs3 = require("fs");
1290
1827
  function findInLine(provider, value, needle) {
1291
1828
  const items = provider === "claude" ? createClaudeNormalizer().push(value) : createCodexRolloutNormalizer().push(value);
1292
1829
  for (const item of items) {
@@ -1327,7 +1864,7 @@ async function scanFile(provider, file, needle, raw, deadline) {
1327
1864
  return new Promise((resolve) => {
1328
1865
  let carry = Buffer.alloc(0);
1329
1866
  let done = false;
1330
- const stream = (0, import_node_fs2.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
1867
+ const stream = (0, import_node_fs3.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
1331
1868
  const finish = (value) => {
1332
1869
  if (done) return;
1333
1870
  done = true;
@@ -1433,9 +1970,9 @@ async function searchLocalChats(query, options = {}) {
1433
1970
  }
1434
1971
 
1435
1972
  // src/engine/store.ts
1436
- var import_node_crypto3 = require("crypto");
1437
- var import_promises4 = __toESM(require("fs/promises"), 1);
1438
- var import_node_path4 = __toESM(require("path"), 1);
1973
+ var import_node_crypto4 = require("crypto");
1974
+ var import_promises6 = __toESM(require("fs/promises"), 1);
1975
+ var import_node_path7 = __toESM(require("path"), 1);
1439
1976
  var MAX_RUNS = 2e3;
1440
1977
  var RunIndex = class {
1441
1978
  constructor(dir) {
@@ -1445,12 +1982,12 @@ var RunIndex = class {
1445
1982
  data = { version: 1, sessions: [], runs: [] };
1446
1983
  writing = Promise.resolve();
1447
1984
  get file() {
1448
- return import_node_path4.default.join(this.dir, "index.json");
1985
+ return import_node_path7.default.join(this.dir, "index.json");
1449
1986
  }
1450
1987
  async load() {
1451
- await import_promises4.default.mkdir(this.dir, { recursive: true });
1988
+ await import_promises6.default.mkdir(this.dir, { recursive: true });
1452
1989
  try {
1453
- const parsed = JSON.parse(await import_promises4.default.readFile(this.file, "utf8"));
1990
+ const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
1454
1991
  this.data = {
1455
1992
  version: 1,
1456
1993
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1492,9 +2029,9 @@ var RunIndex = class {
1492
2029
  persist() {
1493
2030
  const snapshot = JSON.stringify(this.data, null, 1);
1494
2031
  this.writing = this.writing.then(async () => {
1495
- const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1496
- await import_promises4.default.writeFile(tmp, snapshot, { mode: 384 });
1497
- await import_promises4.default.rename(tmp, this.file);
2032
+ const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
2033
+ await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
2034
+ await import_promises6.default.rename(tmp, this.file);
1498
2035
  }).catch(() => {
1499
2036
  });
1500
2037
  }
@@ -1504,27 +2041,27 @@ var RunIndex = class {
1504
2041
  /** A stable Paperclip agent/company identity for this install, so the
1505
2042
  * driver's scratch state is reused rather than growing per run. */
1506
2043
  async identity() {
1507
- const file = import_node_path4.default.join(this.dir, "identity.json");
2044
+ const file = import_node_path7.default.join(this.dir, "identity.json");
1508
2045
  try {
1509
- const parsed = JSON.parse(await import_promises4.default.readFile(file, "utf8"));
2046
+ const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
1510
2047
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1511
2048
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1512
2049
  }
1513
2050
  } catch {
1514
2051
  }
1515
- const identity = { companyId: (0, import_node_crypto3.randomUUID)(), agentId: (0, import_node_crypto3.randomUUID)() };
1516
- await import_promises4.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
2052
+ const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
2053
+ await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
1517
2054
  return identity;
1518
2055
  }
1519
2056
  };
1520
2057
 
1521
2058
  // src/engine/user-defaults.ts
1522
- var import_promises5 = __toESM(require("fs/promises"), 1);
1523
- var import_node_os3 = __toESM(require("os"), 1);
1524
- var import_node_path5 = __toESM(require("path"), 1);
2059
+ var import_promises7 = __toESM(require("fs/promises"), 1);
2060
+ var import_node_os4 = __toESM(require("os"), 1);
2061
+ var import_node_path8 = __toESM(require("path"), 1);
1525
2062
  async function readJson(file) {
1526
2063
  try {
1527
- const parsed = JSON.parse(await import_promises5.default.readFile(file, "utf8"));
2064
+ const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
1528
2065
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1529
2066
  } catch {
1530
2067
  return null;
@@ -1533,10 +2070,10 @@ async function readJson(file) {
1533
2070
  function str3(value) {
1534
2071
  return typeof value === "string" && value.trim() ? value.trim() : null;
1535
2072
  }
1536
- async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
2073
+ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1537
2074
  const files = [
1538
- ...cwd ? [import_node_path5.default.join(cwd, ".claude", "settings.local.json"), import_node_path5.default.join(cwd, ".claude", "settings.json")] : [],
1539
- import_node_path5.default.join(home, ".claude", "settings.json")
2075
+ ...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
2076
+ import_node_path8.default.join(home, ".claude", "settings.json")
1540
2077
  ];
1541
2078
  let model = null;
1542
2079
  let effort = null;
@@ -1549,10 +2086,10 @@ async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
1549
2086
  }
1550
2087
  return { model, effort };
1551
2088
  }
1552
- async function codexDefaults(home = import_node_os3.default.homedir()) {
2089
+ async function codexDefaults(home = import_node_os4.default.homedir()) {
1553
2090
  let text;
1554
2091
  try {
1555
- text = await import_promises5.default.readFile(import_node_path5.default.join(home, ".codex", "config.toml"), "utf8");
2092
+ text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
1556
2093
  } catch {
1557
2094
  return { model: null, effort: null };
1558
2095
  }
@@ -1576,45 +2113,33 @@ var EFFORT_LEVELS = {
1576
2113
  // src/engine/index.ts
1577
2114
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1578
2115
  function moduleDir() {
1579
- return import_node_path6.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2116
+ return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
1580
2117
  }
1581
2118
  function resolveWorkerPath() {
1582
2119
  const dir = moduleDir();
1583
2120
  for (const candidate of [
1584
- import_node_path6.default.join(dir, "worker.js"),
1585
- import_node_path6.default.join(dir, "worker.ts"),
1586
- import_node_path6.default.join(dir, "..", "engine", "worker.js"),
1587
- import_node_path6.default.join(dir, "..", "engine", "worker.ts")
2121
+ import_node_path9.default.join(dir, "worker.js"),
2122
+ import_node_path9.default.join(dir, "worker.ts"),
2123
+ import_node_path9.default.join(dir, "..", "engine", "worker.js"),
2124
+ import_node_path9.default.join(dir, "..", "engine", "worker.ts")
1588
2125
  ]) {
1589
- if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
2126
+ if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
1590
2127
  }
1591
2128
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1592
2129
  }
1593
2130
  async function installShim(dataDir) {
1594
- const source = import_node_path6.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1595
- if (!(0, import_node_fs3.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1596
- const body = await import_promises6.default.readFile(source, "utf8");
1597
- const target = import_node_path6.default.join(dataDir, "bin", "cli-shim.sh");
1598
- const current = await import_promises6.default.readFile(target, "utf8").catch(() => null);
2131
+ const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2132
+ if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
2133
+ const body = await import_promises8.default.readFile(source, "utf8");
2134
+ const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
2135
+ const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
1599
2136
  if (current !== body) {
1600
- await import_promises6.default.mkdir(import_node_path6.default.dirname(target), { recursive: true });
1601
- await import_promises6.default.writeFile(target, body, { mode: 493 });
2137
+ await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
2138
+ await import_promises8.default.writeFile(target, body, { mode: 493 });
1602
2139
  }
1603
- await import_promises6.default.chmod(target, 493);
2140
+ await import_promises8.default.chmod(target, 493);
1604
2141
  return target;
1605
2142
  }
1606
- function whichOnPath(name, pathValue) {
1607
- for (const dir of pathValue.split(import_node_path6.default.delimiter)) {
1608
- if (!dir) continue;
1609
- const candidate = import_node_path6.default.join(dir, name);
1610
- try {
1611
- (0, import_node_fs3.accessSync)(candidate, import_node_fs3.constants.X_OK);
1612
- return candidate;
1613
- } catch {
1614
- }
1615
- }
1616
- return null;
1617
- }
1618
2143
  function nowIso() {
1619
2144
  return (/* @__PURE__ */ new Date()).toISOString();
1620
2145
  }
@@ -1653,12 +2178,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
1653
2178
  function describeAuthStatus(provider, probe) {
1654
2179
  const label = PROVIDER_LABEL[provider];
1655
2180
  const installed = probe?.installed === true;
1656
- const version = typeof probe?.version === "string" ? probe.version : null;
2181
+ const binary = probe?.binary ?? null;
2182
+ const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
1657
2183
  const base = {
1658
2184
  provider,
1659
2185
  label,
1660
2186
  installed,
1661
2187
  version,
2188
+ binary,
1662
2189
  loggedIn: false,
1663
2190
  billing: "unknown",
1664
2191
  plan: null,
@@ -1710,29 +2237,30 @@ function describeAuthStatus(provider, probe) {
1710
2237
  };
1711
2238
  }
1712
2239
  async function createCodingSessionsEngine(options = {}) {
1713
- const dataDir = options.dataDir ?? import_node_path6.default.join(import_node_os4.default.homedir(), ".matrx", "coding-sessions");
1714
- const stateDir = import_node_path6.default.join(dataDir, "paperclip-state");
2240
+ const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
2241
+ const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
1715
2242
  const homes = options.homes ?? defaultProviderHomes();
1716
2243
  const index = new RunIndex(dataDir);
1717
2244
  await index.load();
1718
2245
  const preferences = new PreferenceStore(dataDir);
1719
2246
  await preferences.load();
1720
- await import_promises6.default.mkdir(stateDir, { recursive: true });
2247
+ await import_promises8.default.mkdir(stateDir, { recursive: true });
1721
2248
  const identity = await index.identity();
2249
+ const agents = createAgentConnection(options.agents, dataDir);
1722
2250
  const backups = options.rawBackup ? createRawBackupScheduler({
1723
2251
  sink: options.rawBackup,
1724
2252
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1725
2253
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1726
2254
  }) : null;
1727
2255
  const discoveredSeen = /* @__PURE__ */ new Map();
1728
- const healthFile = import_node_path6.default.join(dataDir, "provider-health.json");
2256
+ const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
1729
2257
  let health = {};
1730
2258
  try {
1731
- health = JSON.parse(await import_promises6.default.readFile(healthFile, "utf8"));
2259
+ health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
1732
2260
  } catch {
1733
2261
  health = {};
1734
2262
  }
1735
- const saveHealth = () => import_promises6.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2263
+ const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1736
2264
  function noteRunOutcome(provider, status, texts) {
1737
2265
  if (status === "completed") {
1738
2266
  if (health[provider]) {
@@ -1760,7 +2288,24 @@ async function createCodingSessionsEngine(options = {}) {
1760
2288
  // Marks the forked process as a driver worker (see worker.ts).
1761
2289
  MATRX_CODING_SESSIONS_WORKER: "1"
1762
2290
  });
1763
- const commandFor = (provider) => options.commands?.[provider] ?? whichOnPath(provider, workerEnv.PATH ?? "");
2291
+ const binaryHome = options.hostEnv?.HOME ?? import_node_os5.default.homedir();
2292
+ const binaryCache = /* @__PURE__ */ new Map();
2293
+ function binaryFor(provider, fresh = false) {
2294
+ const cached = binaryCache.get(provider);
2295
+ if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
2296
+ const forced = options.commands?.[provider];
2297
+ const value = resolveBinary(provider, {
2298
+ env: workerEnv,
2299
+ home: binaryHome,
2300
+ forced,
2301
+ // A forced command needs no discovery to run; the status check still
2302
+ // looks, so it can say when a newer copy exists.
2303
+ discover: !forced || fresh
2304
+ }).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
2305
+ if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
2306
+ return value;
2307
+ }
2308
+ const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
1764
2309
  const workerPath = resolveWorkerPath();
1765
2310
  const shimPath = await installShim(dataDir);
1766
2311
  const listeners = /* @__PURE__ */ new Set();
@@ -1810,7 +2355,7 @@ async function createCodingSessionsEngine(options = {}) {
1810
2355
  }
1811
2356
  }
1812
2357
  function forkWorker() {
1813
- return (0, import_node_child_process2.fork)(workerPath, [], {
2358
+ return (0, import_node_child_process3.fork)(workerPath, [], {
1814
2359
  env: workerEnv,
1815
2360
  cwd: dataDir,
1816
2361
  stdio: ["ignore", "pipe", "pipe", "ipc"],
@@ -1831,8 +2376,8 @@ async function createCodingSessionsEngine(options = {}) {
1831
2376
  }
1832
2377
  async function startRun(session, prompt) {
1833
2378
  const provider = session.provider;
1834
- const realCommand = commandFor(provider);
1835
- const runId = (0, import_node_crypto4.randomUUID)();
2379
+ const realCommand = await commandFor(provider);
2380
+ const runId = (0, import_node_crypto5.randomUUID)();
1836
2381
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1837
2382
  const run = {
1838
2383
  runId,
@@ -1870,7 +2415,26 @@ async function createCodingSessionsEngine(options = {}) {
1870
2415
  let fileOffset = null;
1871
2416
  if (session.providerSessionId) {
1872
2417
  const file = await findSessionFile(provider, session.providerSessionId, homes);
1873
- if (file) fileOffset = (await import_promises6.default.stat(file).catch(() => null))?.size ?? null;
2418
+ if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
2419
+ }
2420
+ let agentArgs = [];
2421
+ if (session.agent) {
2422
+ const bundle = await agents.readSessionBundle(session.id);
2423
+ if (!bundle) {
2424
+ const failed = saveRun({
2425
+ ...run,
2426
+ status: "failed",
2427
+ endedAt: nowIso(),
2428
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2429
+ });
2430
+ const current = index.session(session.id);
2431
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2432
+ return failed;
2433
+ }
2434
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2435
+ mcpUrl: await agents.mcpUrlFor(session.id)
2436
+ });
2437
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
1874
2438
  }
1875
2439
  const child = forkWorker();
1876
2440
  const liveRun = {
@@ -2044,7 +2608,8 @@ async function createCodingSessionsEngine(options = {}) {
2044
2608
  resumeSessionId: session.providerSessionId,
2045
2609
  shimPath,
2046
2610
  realCommand,
2047
- identity
2611
+ identity,
2612
+ agentArgs
2048
2613
  });
2049
2614
  return run;
2050
2615
  }
@@ -2063,7 +2628,7 @@ async function createCodingSessionsEngine(options = {}) {
2063
2628
  }, 3e3);
2064
2629
  }
2065
2630
  async function pruneDriverScratch() {
2066
- const runsDir = import_node_path6.default.join(
2631
+ const runsDir = import_node_path9.default.join(
2067
2632
  stateDir,
2068
2633
  "instances",
2069
2634
  "default",
@@ -2074,12 +2639,13 @@ async function createCodingSessionsEngine(options = {}) {
2074
2639
  "claude-runtime",
2075
2640
  "runs"
2076
2641
  );
2077
- const entries = await import_promises6.default.readdir(runsDir).catch(() => []);
2642
+ const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
2078
2643
  await Promise.all(
2079
- entries.filter((name) => !live.has(name)).map((name) => import_promises6.default.rm(import_node_path6.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2644
+ entries.filter((name) => !live.has(name)).map((name) => import_promises8.default.rm(import_node_path9.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2080
2645
  );
2081
2646
  }
2082
2647
  async function probe() {
2648
+ const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
2083
2649
  return new Promise((resolve) => {
2084
2650
  const child = forkWorker();
2085
2651
  let answered = false;
@@ -2095,8 +2661,8 @@ async function createCodingSessionsEngine(options = {}) {
2095
2661
  answered = true;
2096
2662
  clearTimeout(timer);
2097
2663
  resolve({
2098
- claude: message.claude ?? {},
2099
- codex: message.codex ?? {}
2664
+ claude: { ...message.claude ?? {}, binary: claudeBinary },
2665
+ codex: { ...message.codex ?? {}, binary: codexBinary }
2100
2666
  });
2101
2667
  }
2102
2668
  });
@@ -2108,7 +2674,7 @@ async function createCodingSessionsEngine(options = {}) {
2108
2674
  });
2109
2675
  child.send({
2110
2676
  kind: "probe",
2111
- commands: { claude: commandFor("claude"), codex: commandFor("codex") },
2677
+ commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
2112
2678
  quota: options.quota !== false
2113
2679
  });
2114
2680
  });
@@ -2116,7 +2682,7 @@ async function createCodingSessionsEngine(options = {}) {
2116
2682
  function newSession(input) {
2117
2683
  const at = nowIso();
2118
2684
  return {
2119
- id: (0, import_node_crypto4.randomUUID)(),
2685
+ id: (0, import_node_crypto5.randomUUID)(),
2120
2686
  provider: input.provider,
2121
2687
  providerSessionId: input.providerSessionId,
2122
2688
  cwd: input.cwd,
@@ -2134,17 +2700,17 @@ async function createCodingSessionsEngine(options = {}) {
2134
2700
  function resolveFolder(raw) {
2135
2701
  const trimmed = raw.trim();
2136
2702
  if (!trimmed) return null;
2137
- return import_node_path6.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os4.default.homedir()));
2703
+ return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2138
2704
  }
2139
2705
  async function checkFolder(raw) {
2140
2706
  const target = resolveFolder(raw);
2141
2707
  if (!target) return { path: "", exists: false, isDirectory: false };
2142
- const stat = await import_promises6.default.stat(target).catch(() => null);
2708
+ const stat = await import_promises8.default.stat(target).catch(() => null);
2143
2709
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2144
2710
  }
2145
2711
  async function assertFolder(cwd) {
2146
- const resolved = import_node_path6.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os4.default.homedir()));
2147
- const stat = await import_promises6.default.stat(resolved).catch(() => null);
2712
+ const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2713
+ const stat = await import_promises8.default.stat(resolved).catch(() => null);
2148
2714
  if (!stat?.isDirectory()) {
2149
2715
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2150
2716
  }
@@ -2206,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
2206
2772
  folders.map(async ([folder, lastUsedAt]) => ({
2207
2773
  path: folder,
2208
2774
  lastUsedAt,
2209
- exists: (await import_promises6.default.stat(folder).catch(() => null))?.isDirectory() === true
2775
+ exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
2210
2776
  }))
2211
2777
  );
2212
2778
  },
@@ -2251,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
2251
2817
  async createFolder(raw) {
2252
2818
  const target = resolveFolder(raw);
2253
2819
  if (!target) throw new Error("Type a folder path first.");
2254
- await import_promises6.default.mkdir(target, { recursive: true });
2820
+ await import_promises8.default.mkdir(target, { recursive: true });
2255
2821
  return checkFolder(target);
2256
2822
  },
2257
2823
  async sessionActivity(provider, providerSessionId) {
@@ -2289,31 +2855,35 @@ async function createCodingSessionsEngine(options = {}) {
2289
2855
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2290
2856
  };
2291
2857
  }
2292
- const stat = await import_promises6.default.stat(file);
2858
+ const stat = await import_promises8.default.stat(file);
2293
2859
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2294
2860
  return {
2295
2861
  available: false,
2296
2862
  reason: `This transcript is ${(0, import_format.formatFileSize)(stat.size)}, larger than the ${(0, import_format.formatFileSize)(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
2297
2863
  };
2298
2864
  }
2299
- const content = await import_promises6.default.readFile(file, "utf8");
2300
- return { available: true, fileName: import_node_path6.default.basename(file), bytes: stat.size, content };
2865
+ const content = await import_promises8.default.readFile(file, "utf8");
2866
+ return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
2301
2867
  },
2302
2868
  async start(input) {
2303
2869
  const prompt = input.prompt.trim();
2304
2870
  if (!prompt) throw new Error("Type what you want done before starting.");
2305
2871
  const cwd = await assertFolder(input.cwd);
2306
- const session = saveSession(
2307
- newSession({
2308
- provider: input.provider,
2309
- cwd,
2310
- title: titleFrom(prompt),
2311
- model: input.model?.trim() || null,
2312
- effort: input.effort?.trim() || null,
2313
- origin: "started",
2314
- providerSessionId: null
2315
- })
2316
- );
2872
+ const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2873
+ const draft = newSession({
2874
+ provider: input.provider,
2875
+ cwd,
2876
+ title: titleFrom(prompt),
2877
+ model: input.model?.trim() || null,
2878
+ effort: input.effort?.trim() || null,
2879
+ origin: "started",
2880
+ providerSessionId: null
2881
+ });
2882
+ if (bundle) {
2883
+ await agents.saveSessionBundle(draft.id, bundle);
2884
+ draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2885
+ }
2886
+ const session = saveSession(draft);
2317
2887
  const run = await startRun(session, prompt);
2318
2888
  return { session: index.session(session.id) ?? session, run, queued: false };
2319
2889
  },
@@ -2382,6 +2952,21 @@ async function createCodingSessionsEngine(options = {}) {
2382
2952
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2383
2953
  return saveSession(next);
2384
2954
  },
2955
+ async agentAccess() {
2956
+ return agents.access();
2957
+ },
2958
+ async agentCatalogRpc(request) {
2959
+ return agents.catalogRpc(request);
2960
+ },
2961
+ async agentDetails(agentId) {
2962
+ return agents.details(String(agentId ?? ""));
2963
+ },
2964
+ setMcpBaseUrl(baseUrl) {
2965
+ agents.setMcpBaseUrl(baseUrl);
2966
+ },
2967
+ async handleMcp(secret, body) {
2968
+ return agents.handleMcp(secret, body);
2969
+ },
2385
2970
  async dispose() {
2386
2971
  for (const liveRun of live.values()) {
2387
2972
  liveRun.stopRequested = true;
@@ -2418,7 +3003,7 @@ function openFolderIn(folder, target) {
2418
3003
  args = ["-a", app.app, folder];
2419
3004
  }
2420
3005
  return new Promise((resolve, reject) => {
2421
- (0, import_node_child_process2.execFile)(command, args, { timeout: 1e4 }, (error) => {
3006
+ (0, import_node_child_process3.execFile)(command, args, { timeout: 1e4 }, (error) => {
2422
3007
  if (!error) return resolve();
2423
3008
  const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
2424
3009
  reject(
@@ -2451,7 +3036,10 @@ var BRIDGE_METHODS = [
2451
3036
  "setChatPreference",
2452
3037
  "searchChats",
2453
3038
  "openFolder",
2454
- "setSessionOptions"
3039
+ "setSessionOptions",
3040
+ "agentAccess",
3041
+ "agentCatalogRpc",
3042
+ "agentDetails"
2455
3043
  ];
2456
3044
  var BridgeError = class extends Error {
2457
3045
  constructor(message, code) {
@@ -2495,14 +3083,14 @@ var MIME = {
2495
3083
  ".map": "application/json"
2496
3084
  };
2497
3085
  function moduleDir2() {
2498
- return import_node_path7.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
3086
+ return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
2499
3087
  }
2500
3088
  function defaultUiDir() {
2501
3089
  for (const candidate of [
2502
- import_node_path7.default.resolve(moduleDir2(), "..", "ui"),
2503
- import_node_path7.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
3090
+ import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
3091
+ import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
2504
3092
  ]) {
2505
- if ((0, import_node_fs4.existsSync)(import_node_path7.default.join(candidate, "index.html"))) return candidate;
3093
+ if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
2506
3094
  }
2507
3095
  return null;
2508
3096
  }
@@ -2510,7 +3098,7 @@ function tokensMatch(expected, given) {
2510
3098
  if (!given) return false;
2511
3099
  const a = Buffer.from(expected);
2512
3100
  const b = Buffer.from(given);
2513
- return a.length === b.length && (0, import_node_crypto5.timingSafeEqual)(a, b);
3101
+ return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
2514
3102
  }
2515
3103
  var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
2516
3104
  <body style="font:15px system-ui;padding:48px;max-width:560px">
@@ -2525,19 +3113,19 @@ async function serveStatic(uiDir, req, res) {
2525
3113
  return;
2526
3114
  }
2527
3115
  const requested = decodeURIComponent(url.pathname);
2528
- let file = import_node_path7.default.resolve(uiDir, `.${requested}`);
3116
+ let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
2529
3117
  if (!file.startsWith(uiDir)) {
2530
3118
  res.writeHead(403).end();
2531
3119
  return;
2532
3120
  }
2533
- const stat = await import_promises7.default.stat(file).catch(() => null);
2534
- if (!stat || stat.isDirectory()) file = import_node_path7.default.join(uiDir, "index.html");
2535
- const body = await import_promises7.default.readFile(file).catch(() => null);
3121
+ const stat = await import_promises9.default.stat(file).catch(() => null);
3122
+ if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
3123
+ const body = await import_promises9.default.readFile(file).catch(() => null);
2536
3124
  if (!body) {
2537
3125
  res.writeHead(404).end();
2538
3126
  return;
2539
3127
  }
2540
- const ext = import_node_path7.default.extname(file);
3128
+ const ext = import_node_path10.default.extname(file);
2541
3129
  res.writeHead(200, {
2542
3130
  "content-type": MIME[ext] ?? "application/octet-stream",
2543
3131
  "cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
@@ -2546,6 +3134,46 @@ async function serveStatic(uiDir, req, res) {
2546
3134
  });
2547
3135
  res.end(body);
2548
3136
  }
3137
+ var MAX_MCP_BODY = 4 * 1024 * 1024;
3138
+ async function handleMcp(engine, secret, req, res, allowedOrigin) {
3139
+ if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
3140
+ res.writeHead(403).end();
3141
+ return;
3142
+ }
3143
+ if (req.method !== "POST") {
3144
+ res.writeHead(405, { allow: "POST" }).end();
3145
+ return;
3146
+ }
3147
+ const chunks = [];
3148
+ let size = 0;
3149
+ for await (const chunk of req) {
3150
+ size += chunk.length;
3151
+ if (size > MAX_MCP_BODY) {
3152
+ res.writeHead(413).end();
3153
+ return;
3154
+ }
3155
+ chunks.push(chunk);
3156
+ }
3157
+ let body;
3158
+ try {
3159
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3160
+ } catch {
3161
+ res.writeHead(400, { "content-type": "application/json" });
3162
+ res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
3163
+ return;
3164
+ }
3165
+ const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
3166
+ if (!reply) {
3167
+ res.writeHead(404).end();
3168
+ return;
3169
+ }
3170
+ if (reply.body === void 0) {
3171
+ res.writeHead(reply.status).end();
3172
+ return;
3173
+ }
3174
+ res.writeHead(reply.status, { "content-type": "application/json" });
3175
+ res.end(JSON.stringify(reply.body));
3176
+ }
2549
3177
  function attachSocket(socket, bridge) {
2550
3178
  const unsubscribe = bridge.subscribe((event) => {
2551
3179
  if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
@@ -2576,7 +3204,7 @@ function attachSocket(socket, bridge) {
2576
3204
  socket.on("close", unsubscribe);
2577
3205
  }
2578
3206
  async function startCodingSessionsHost(options = {}) {
2579
- const token = options.token ?? (0, import_node_crypto5.randomBytes)(24).toString("base64url");
3207
+ const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
2580
3208
  const engine = options.engine ?? await createCodingSessionsEngine(options);
2581
3209
  const bridge = createHostBridge(engine);
2582
3210
  const uiDir = options.uiDir ?? defaultUiDir();
@@ -2587,6 +3215,13 @@ async function startCodingSessionsHost(options = {}) {
2587
3215
  };
2588
3216
  const server = import_node_http.default.createServer((req, res) => {
2589
3217
  const url2 = new URL(req.url ?? "/", `http://${HOST}`);
3218
+ if (url2.pathname.startsWith("/mcp/")) {
3219
+ handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
3220
+ if (!res.headersSent) res.writeHead(500);
3221
+ res.end();
3222
+ });
3223
+ return;
3224
+ }
2590
3225
  if (url2.pathname === "/health") {
2591
3226
  res.writeHead(200, { "content-type": "application/json" });
2592
3227
  res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
@@ -2617,6 +3252,7 @@ async function startCodingSessionsHost(options = {}) {
2617
3252
  server.off("error", reject);
2618
3253
  const address = server.address();
2619
3254
  if (address && typeof address === "object") port = address.port;
3255
+ engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
2620
3256
  resolve();
2621
3257
  });
2622
3258
  });
@@ -2627,6 +3263,7 @@ async function startCodingSessionsHost(options = {}) {
2627
3263
  token,
2628
3264
  engine,
2629
3265
  async close() {
3266
+ engine.setMcpBaseUrl?.(null);
2630
3267
  for (const client of wss.clients) client.terminate();
2631
3268
  await new Promise((resolve) => wss.close(() => resolve()));
2632
3269
  await new Promise((resolve) => server.close(() => resolve()));