@ai-matrx/coding-sessions 0.1.83 → 0.1.86

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,17 +43,17 @@ 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");
46
+ var import_node_crypto6 = require("crypto");
47
47
  var import_node_fs5 = require("fs");
48
- var import_promises7 = __toESM(require("fs/promises"), 1);
48
+ var import_promises9 = __toESM(require("fs/promises"), 1);
49
49
  var import_node_http = __toESM(require("http"), 1);
50
- var import_node_path8 = __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
55
  var import_node_child_process3 = require("child_process");
56
- var import_node_crypto4 = require("crypto");
56
+ var import_node_crypto5 = require("crypto");
57
57
  var import_node_fs4 = require("fs");
58
58
  var import_format = require("@ai-matrx/kit/format");
59
59
 
@@ -114,10 +114,448 @@ 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
+
121
+ // src/normalize/agent-block.ts
122
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
123
+ function stripAgentBlock(text) {
124
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
125
+ if (removed) return text.slice(removed[0].length);
126
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
127
+ const end = text.indexOf("</ai_matrx_agent>");
128
+ if (end < 0) return text;
129
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
130
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
131
+ if (skills) rest = rest.slice(skills[0].length);
132
+ return rest.replace(/^\s+/, "");
133
+ }
134
+
135
+ // src/engine/agent-attach.ts
136
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
137
+ var AGENT_PLUGIN_NAME = "matrx-agent";
138
+ function escapeAttr(value) {
139
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
140
+ }
141
+ function versionWords(bundle) {
142
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
143
+ }
144
+ function renderAgentInstructions(bundle, options = {}) {
145
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
146
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
147
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
148
+ if (included.length > 0) {
149
+ parts.push(
150
+ "## Skills (always in effect)",
151
+ ...included.map((skill) => `### ${skill.label}
152
+
153
+ ${skill.body.trim()}`)
154
+ );
155
+ }
156
+ if (options.claudeSkills && listed2.length > 0) {
157
+ parts.push(
158
+ [
159
+ "## Your skills",
160
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
161
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
162
+ ].join("\n")
163
+ );
164
+ }
165
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
166
+ parts.push(bundle.instructions.trim());
167
+ parts.push("</ai_matrx_agent>");
168
+ return `${parts.filter(Boolean).join("\n\n")}
169
+ `;
170
+ }
171
+ function renderCodexSkillsList(bundle, skillsRoot) {
172
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
173
+ if (listed2.length === 0) return "";
174
+ const lines = [
175
+ "<skills_instructions>",
176
+ "## AI Matrx agent skills",
177
+ "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.",
178
+ "### Available skills",
179
+ ...listed2.map(
180
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
181
+ ),
182
+ "</skills_instructions>"
183
+ ];
184
+ return lines.join("\n");
185
+ }
186
+ function renderFallbackBlock(bundle, skillsRoot) {
187
+ const skills = renderCodexSkillsList(bundle, skillsRoot);
188
+ const body = renderAgentInstructions(bundle).trim().replace(/^(<ai_matrx_agent [^>]*>)/, "$1\n\nThe instructions below are your AI Matrx agent for this chat from now on; they replace any AI Matrx agent instructions given earlier.");
189
+ return skills ? `${body}
190
+
191
+ ${skills}
192
+
193
+ ` : `${body}
194
+
195
+ `;
196
+ }
197
+ function tomlString(value) {
198
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
199
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
200
+ }
201
+ async function writeAgentAttachment(dir, bundle, options) {
202
+ const pluginDir = import_node_path.default.join(dir, "plugin");
203
+ const skillsRoot = import_node_path.default.join(dir, "skills");
204
+ await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
205
+ await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
206
+ const instructions = renderAgentInstructions(bundle);
207
+ const instructionsFile = import_node_path.default.join(dir, "instructions.md");
208
+ await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
209
+ await import_promises2.default.writeFile(
210
+ import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
211
+ `${JSON.stringify(
212
+ {
213
+ name: AGENT_PLUGIN_NAME,
214
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
215
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
216
+ },
217
+ null,
218
+ 2
219
+ )}
220
+ `,
221
+ { mode: 384 }
222
+ );
223
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
224
+ const wanted = new Set(listed2.map((skill) => skill.slug));
225
+ for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
226
+ await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
227
+ for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
228
+ if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
229
+ }
230
+ for (const skill of listed2) {
231
+ const skillDir = import_node_path.default.join(root, skill.slug);
232
+ await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
233
+ await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
234
+ }
235
+ }
236
+ let mcpConfigFile = null;
237
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
238
+ const mcpPath = import_node_path.default.join(dir, "mcp.json");
239
+ if (mcpUrl) {
240
+ mcpConfigFile = mcpPath;
241
+ await import_promises2.default.writeFile(
242
+ mcpPath,
243
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
244
+ `,
245
+ { mode: 384 }
246
+ );
247
+ } else {
248
+ await import_promises2.default.rm(mcpPath, { force: true });
249
+ }
250
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
251
+ return {
252
+ dir,
253
+ instructionsFile,
254
+ pluginDir,
255
+ mcpConfigFile,
256
+ mcpUrl,
257
+ // Same order as Claude's: the skills list first, the agent's words last.
258
+ codexInstructions: skillsList ? `${skillsList}
259
+
260
+ ${instructions}` : instructions
261
+ };
262
+ }
263
+ function agentArgsFor(provider, attachment, options) {
264
+ if (provider === "claude") {
265
+ const args2 = [
266
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
267
+ "--append-system-prompt-file",
268
+ attachment.instructionsFile,
269
+ "--system-prompt-snapshot",
270
+ "off",
271
+ "--plugin-dir",
272
+ attachment.pluginDir
273
+ ];
274
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
275
+ return args2;
276
+ }
277
+ const args = [];
278
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
279
+ if (attachment.mcpUrl) {
280
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
281
+ args.push(
282
+ "-c",
283
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
284
+ "-c",
285
+ `${key}.default_tools_approval_mode="approve"`
286
+ );
287
+ }
288
+ return args;
289
+ }
290
+ function sessionAgentRecord(bundle, options) {
291
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
292
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
293
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
294
+ if (!options.toolsServed && bundle.tools.length > 0) {
295
+ for (const tool of bundle.tools) {
296
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
297
+ }
298
+ }
299
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
300
+ const loaded = [
301
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
302
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
303
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
304
+ ];
305
+ return {
306
+ agentId: bundle.agent_id,
307
+ versionId: bundle.version_id,
308
+ versionNumber: bundle.version_number,
309
+ name: bundle.name,
310
+ definitionHash: bundle.definition_hash,
311
+ loaded,
312
+ unavailable,
313
+ skills: bundle.skills.map((skill) => skill.label),
314
+ tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
315
+ ...options.variables ? { variables: options.variables } : {}
316
+ };
317
+ }
318
+ var AGENT_REMOVED_BLOCK = "<ai_matrx_agent_removed>The AI Matrx agent instructions given earlier in this chat no longer apply.</ai_matrx_agent_removed>\n\n";
319
+ async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
320
+ const text = await import_promises2.default.readFile(rolloutFile, "utf8").catch(() => "");
321
+ if (!text.includes('"type":"compacted"')) return false;
322
+ let latest = null;
323
+ for (const line of text.split("\n")) {
324
+ if (!line.includes('"type":"compacted"')) continue;
325
+ try {
326
+ const entry = JSON.parse(line);
327
+ if (entry.type === "compacted") latest = entry;
328
+ } catch {
329
+ }
330
+ }
331
+ if (!latest) return false;
332
+ if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
333
+ return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
334
+ }
335
+
336
+ // src/engine/agents.ts
337
+ var import_node_crypto2 = require("crypto");
338
+ var import_promises3 = __toESM(require("fs/promises"), 1);
339
+ var import_node_path2 = __toESM(require("path"), 1);
340
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
341
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
342
+ var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
343
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
344
+ "agx_get_list_full",
345
+ "agx_search",
346
+ "agx_resolve_agent_address",
347
+ "ues_get_bulk",
348
+ "ues_list",
349
+ "ues_set",
350
+ "ues_touch"
351
+ ]);
352
+ var agentsClient = async () => {
353
+ const [matrx, portable, mcp] = await Promise.all([
354
+ import("@ai-matrx/agents/matrx"),
355
+ import("@ai-matrx/agents/portable"),
356
+ import("@ai-matrx/agents/portable/mcp")
357
+ ]);
358
+ return { ...matrx, ...portable, ...mcp };
359
+ };
360
+ function sortedValues(values) {
361
+ return Object.keys(values).sort().map((key) => [key, values[key]]);
362
+ }
363
+ var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
364
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
365
+ function userIdOf(credential) {
366
+ if (!credential || credential.kind !== "user") return null;
367
+ const payload = credential.accessToken.split(".")[1];
368
+ if (!payload) return null;
369
+ try {
370
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
371
+ return typeof claims.sub === "string" ? claims.sub : null;
372
+ } catch {
373
+ return null;
374
+ }
375
+ }
376
+ function createAgentConnection(options, dataDir) {
377
+ const credentials = options?.credentials ?? null;
378
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
379
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
380
+ const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
381
+ const root = import_node_path2.default.join(dataDir, "agents");
382
+ let mcpBaseUrl = null;
383
+ const handlers = /* @__PURE__ */ new Map();
384
+ const secretToSession = /* @__PURE__ */ new Map();
385
+ const organizationId = async () => {
386
+ const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
387
+ return raw && raw.trim() ? raw.trim() : null;
388
+ };
389
+ const transport = async () => {
390
+ const org = await organizationId();
391
+ return (await agentsClient()).createMatrxTransport({
392
+ baseUrl: serverUrl,
393
+ ...credentials ? { credentials } : {},
394
+ ...org ? { organizationId: org } : {},
395
+ source: "codingSessionsAgents"
396
+ });
397
+ };
398
+ const cacheDir = import_node_path2.default.join(dataDir, "agents", ".bundles");
399
+ const cacheKey = (definitionHash, selection, surface) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
400
+ const credential = async () => credentials ? credentials.get() : null;
401
+ const requireSignedIn = async () => {
402
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
403
+ };
404
+ const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
405
+ async function meta(sessionId) {
406
+ const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
407
+ const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
408
+ if (existing?.mcpSecret && existing.conversationId) return existing;
409
+ const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
410
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
411
+ await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
412
+ return created;
413
+ }
414
+ async function findSessionBySecret(secret) {
415
+ for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
416
+ 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);
417
+ if (found) {
418
+ secretToSession.set(secret, name);
419
+ return name;
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ async function readSessionBundle(sessionId) {
425
+ return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
426
+ }
427
+ return {
428
+ async access() {
429
+ const current = await credential().catch(() => null);
430
+ const userId = userIdOf(current);
431
+ return {
432
+ configured: credentials !== null && supabaseKey !== null,
433
+ signedIn: userId !== null,
434
+ userId,
435
+ organizationId: await organizationId().catch(() => null)
436
+ };
437
+ },
438
+ async catalogRpc(request) {
439
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
440
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
441
+ }
442
+ const current = await credential();
443
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
444
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
445
+ const query = new URLSearchParams();
446
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
447
+ if (request.range) {
448
+ query.set("offset", String(request.range[0]));
449
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
450
+ }
451
+ const headers = {
452
+ apikey: supabaseKey,
453
+ Authorization: `Bearer ${current.accessToken}`,
454
+ "Content-Type": "application/json",
455
+ // Every catalogue RPC lives in `public`; the database's default
456
+ // exposed schema is not `public`, so name it (supabase-js does).
457
+ "Content-Profile": "public",
458
+ "Accept-Profile": "public"
459
+ };
460
+ if (request.count) headers.Prefer = `count=${request.count}`;
461
+ const qs = query.toString();
462
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
463
+ method: "POST",
464
+ headers,
465
+ body: JSON.stringify(request.args ?? {})
466
+ });
467
+ const text = await response.text();
468
+ const body = text ? JSON.parse(text) : null;
469
+ if (!response.ok) {
470
+ const err = body ?? {};
471
+ return {
472
+ data: null,
473
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
474
+ };
475
+ }
476
+ const range = response.headers.get("content-range");
477
+ const total = range?.split("/")[1];
478
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
479
+ },
480
+ async details(agentId) {
481
+ await requireSignedIn();
482
+ const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
483
+ return {
484
+ agentId: summary.agent_id,
485
+ name: summary.name,
486
+ variables: summary.variables,
487
+ versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
488
+ latestVersionNumber: summary.version_number,
489
+ latestVersionId: summary.version_id
490
+ };
491
+ },
492
+ async loadBundle(selection) {
493
+ await requireSignedIn();
494
+ const client = await agentsClient();
495
+ const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
496
+ const versionId = selection.versionId ?? summary.version_id;
497
+ const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
498
+ const surface = "matrx-user/code-editor";
499
+ const file = definitionHash ? import_node_path2.default.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
500
+ if (file) {
501
+ const cached = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
502
+ const fresh = await import_promises3.default.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
503
+ if (cached && fresh && cached.definition_hash === definitionHash) return cached;
504
+ }
505
+ const bundle = await client.fetchPortableAgent(
506
+ await transport(),
507
+ versionId ? { versionId } : { agentId: selection.agentId },
508
+ { variables: selection.variables ?? {}, surface }
509
+ );
510
+ if (file && bundle.definition_hash === definitionHash) {
511
+ await import_promises3.default.mkdir(cacheDir, { recursive: true, mode: 448 });
512
+ await import_promises3.default.writeFile(file, JSON.stringify(bundle), { mode: 384 });
513
+ }
514
+ return bundle;
515
+ },
516
+ async saveSessionBundle(sessionId, bundle) {
517
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
518
+ await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
519
+ await meta(sessionId);
520
+ },
521
+ readSessionBundle,
522
+ sessionDir,
523
+ async mcpUrlFor(sessionId) {
524
+ if (!mcpBaseUrl) return null;
525
+ const { mcpSecret } = await meta(sessionId);
526
+ secretToSession.set(mcpSecret, sessionId);
527
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
528
+ },
529
+ setMcpBaseUrl(baseUrl) {
530
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
531
+ },
532
+ async handleMcp(secret, body) {
533
+ let handler = handlers.get(secret);
534
+ if (!handler) {
535
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
536
+ if (!sessionId) return null;
537
+ const bundle = await readSessionBundle(sessionId);
538
+ if (!bundle) return null;
539
+ const { conversationId } = await meta(sessionId);
540
+ const { createAgentToolsMcpHandler } = await agentsClient();
541
+ handler = createAgentToolsMcpHandler({
542
+ bundle,
543
+ credentials,
544
+ organizationId: () => organizationId(),
545
+ baseUrl: serverUrl,
546
+ conversationId
547
+ });
548
+ handlers.set(secret, handler);
549
+ }
550
+ return handler.handle(body);
551
+ }
552
+ };
553
+ }
554
+
117
555
  // src/engine/index.ts
118
- var import_promises6 = __toESM(require("fs/promises"), 1);
556
+ var import_promises8 = __toESM(require("fs/promises"), 1);
119
557
  var import_node_os5 = __toESM(require("os"), 1);
120
- var import_node_path7 = __toESM(require("path"), 1);
558
+ var import_node_path9 = __toESM(require("path"), 1);
121
559
  var import_node_url = require("url");
122
560
 
123
561
  // src/normalize/claude.ts
@@ -488,7 +926,7 @@ function rolloutItem(item, fallbackId, at) {
488
926
  const id = str2(item.id) ?? fallbackId;
489
927
  switch (item.type) {
490
928
  case "UserMessage": {
491
- const text = stripCodexInjectedContext(itemText(item.content));
929
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
492
930
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
493
931
  }
494
932
  case "AgentMessage": {
@@ -571,7 +1009,7 @@ function createCodexRolloutNormalizer() {
571
1009
  return rolloutItem(payload.item, fallbackId, at);
572
1010
  }
573
1011
  if (payload.type === "user_message" && !appItems) {
574
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
1012
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
575
1013
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
576
1014
  }
577
1015
  return [];
@@ -787,7 +1225,7 @@ function describeProblem(provider, problem) {
787
1225
  var import_node_child_process = require("child_process");
788
1226
  var import_node_fs = require("fs");
789
1227
  var import_node_os = __toESM(require("os"), 1);
790
- var import_node_path = __toESM(require("path"), 1);
1228
+ var import_node_path3 = __toESM(require("path"), 1);
791
1229
  var SOURCE_LABEL = {
792
1230
  codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
793
1231
  claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
@@ -836,10 +1274,10 @@ function exeName(name, platform) {
836
1274
  }
837
1275
  function allOnPath(name, pathValue, platform = process.platform) {
838
1276
  const found = [];
839
- for (const dir of pathValue.split(import_node_path.default.delimiter)) {
1277
+ for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
840
1278
  if (!dir) continue;
841
1279
  for (const file of exeName(name, platform)) {
842
- const candidate = import_node_path.default.join(dir, file);
1280
+ const candidate = import_node_path3.default.join(dir, file);
843
1281
  if (executable(candidate)) {
844
1282
  found.push(candidate);
845
1283
  break;
@@ -852,17 +1290,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
852
1290
  if (platform !== "darwin") return [];
853
1291
  if (provider === "codex") {
854
1292
  return applications.map(
855
- (apps) => import_node_path.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1293
+ (apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
856
1294
  );
857
1295
  }
858
- const root = import_node_path.default.join(home, "Library", "Application Support", "Claude", "claude-code");
1296
+ const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
859
1297
  let versions = [];
860
1298
  try {
861
1299
  versions = (0, import_node_fs.readdirSync)(root);
862
1300
  } catch {
863
1301
  return [];
864
1302
  }
865
- return versions.map((v) => import_node_path.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1303
+ return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
866
1304
  }
867
1305
  function npmGlobalPrefix(env) {
868
1306
  const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
@@ -880,13 +1318,13 @@ function declaredVersion(provider, source, file) {
880
1318
  if (source !== "desktop-app") return null;
881
1319
  if (provider === "codex") {
882
1320
  try {
883
- const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path.default.join(import_node_path.default.dirname(import_node_path.default.dirname(file)), "codex-package.json"), "utf8"));
1321
+ 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"));
884
1322
  return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
885
1323
  } catch {
886
1324
  return null;
887
1325
  }
888
1326
  }
889
- return parseVersion(import_node_path.default.basename(import_node_path.default.resolve(file, "..", "..", "..", "..")));
1327
+ return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
890
1328
  }
891
1329
  function runVersion(file, env) {
892
1330
  return new Promise((resolve) => {
@@ -900,14 +1338,14 @@ async function discoverBinaries(provider, options) {
900
1338
  const platform = options.platform ?? process.platform;
901
1339
  const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
902
1340
  const listed2 = [
903
- ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1341
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
904
1342
  ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
905
1343
  ];
906
1344
  const prefix = await npmGlobalPrefix(options.env);
907
1345
  if (prefix) {
908
- const dir = platform === "win32" ? prefix : import_node_path.default.join(prefix, "bin");
1346
+ const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
909
1347
  for (const file of exeName(provider, platform)) {
910
- const candidate = import_node_path.default.join(dir, file);
1348
+ const candidate = import_node_path3.default.join(dir, file);
911
1349
  if (executable(candidate)) {
912
1350
  listed2.push({ path: candidate, source: "npm-global" });
913
1351
  break;
@@ -953,7 +1391,7 @@ async function resolveBinary(provider, options) {
953
1391
 
954
1392
  // src/engine/clean-env.ts
955
1393
  var import_node_os2 = __toESM(require("os"), 1);
956
- var import_node_path2 = __toESM(require("path"), 1);
1394
+ var import_node_path4 = __toESM(require("path"), 1);
957
1395
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
958
1396
  "HOME",
959
1397
  "USER",
@@ -975,11 +1413,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
975
1413
  var ALLOWED_PREFIXES = ["LC_"];
976
1414
  function wellKnownBinDirs(home) {
977
1415
  return [
978
- import_node_path2.default.join(home, ".local", "bin"),
979
- import_node_path2.default.join(home, ".claude", "local"),
980
- import_node_path2.default.join(home, ".npm-global", "bin"),
981
- import_node_path2.default.join(home, ".bun", "bin"),
982
- import_node_path2.default.join(home, ".volta", "bin"),
1416
+ import_node_path4.default.join(home, ".local", "bin"),
1417
+ import_node_path4.default.join(home, ".claude", "local"),
1418
+ import_node_path4.default.join(home, ".npm-global", "bin"),
1419
+ import_node_path4.default.join(home, ".bun", "bin"),
1420
+ import_node_path4.default.join(home, ".volta", "bin"),
983
1421
  "/opt/homebrew/bin",
984
1422
  "/usr/local/bin",
985
1423
  "/usr/bin",
@@ -999,21 +1437,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
999
1437
  }
1000
1438
  const home = out.HOME ?? import_node_os2.default.homedir();
1001
1439
  out.HOME = home;
1002
- const hostPath = (hostEnv.PATH ?? "").split(import_node_path2.default.delimiter).filter(Boolean);
1440
+ const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
1003
1441
  const merged = [];
1004
1442
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
1005
1443
  if (!merged.includes(dir)) merged.push(dir);
1006
1444
  }
1007
- out.PATH = merged.join(import_node_path2.default.delimiter);
1445
+ out.PATH = merged.join(import_node_path4.default.delimiter);
1008
1446
  for (const [name, value] of Object.entries(extra)) out[name] = value;
1009
1447
  return out;
1010
1448
  }
1011
1449
 
1012
1450
  // src/engine/files.ts
1013
1451
  var import_node_fs2 = require("fs");
1014
- var import_promises2 = __toESM(require("fs/promises"), 1);
1452
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1015
1453
  var import_node_os3 = __toESM(require("os"), 1);
1016
- var import_node_path3 = __toESM(require("path"), 1);
1454
+ var import_node_path5 = __toESM(require("path"), 1);
1017
1455
 
1018
1456
  // src/engine/activity.ts
1019
1457
  var import_node_child_process2 = require("child_process");
@@ -1082,15 +1520,15 @@ function describeActivity(input) {
1082
1520
  // src/engine/files.ts
1083
1521
  function defaultProviderHomes(home = import_node_os3.default.homedir()) {
1084
1522
  return {
1085
- claudeProjects: import_node_path3.default.join(home, ".claude", "projects"),
1086
- codexSessions: import_node_path3.default.join(home, ".codex", "sessions"),
1087
- codexIndex: import_node_path3.default.join(home, ".codex", "session_index.jsonl")
1523
+ claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
1524
+ codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
1525
+ codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
1088
1526
  };
1089
1527
  }
1090
1528
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
1091
1529
  async function safeReaddir(dir) {
1092
1530
  try {
1093
- return await import_promises2.default.readdir(dir, { withFileTypes: true });
1531
+ return await import_promises4.default.readdir(dir, { withFileTypes: true });
1094
1532
  } catch {
1095
1533
  return [];
1096
1534
  }
@@ -1099,7 +1537,7 @@ async function statCandidate(provider, file) {
1099
1537
  const match = UUIDISH.exec(file);
1100
1538
  if (!match?.[1]) return null;
1101
1539
  try {
1102
- const stat = await import_promises2.default.stat(file);
1540
+ const stat = await import_promises4.default.stat(file);
1103
1541
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
1104
1542
  } catch {
1105
1543
  return null;
@@ -1109,10 +1547,10 @@ async function claudeCandidates(root) {
1109
1547
  const out = [];
1110
1548
  for (const project of await safeReaddir(root)) {
1111
1549
  if (!project.isDirectory()) continue;
1112
- const dir = import_node_path3.default.join(root, project.name);
1550
+ const dir = import_node_path5.default.join(root, project.name);
1113
1551
  const entries = await safeReaddir(dir);
1114
1552
  const stats = await Promise.all(
1115
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path3.default.join(dir, e.name)))
1553
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
1116
1554
  );
1117
1555
  for (const s of stats) if (s) out.push(s);
1118
1556
  }
@@ -1122,14 +1560,14 @@ async function codexCandidates(root) {
1122
1560
  const out = [];
1123
1561
  for (const year of await safeReaddir(root)) {
1124
1562
  if (!year.isDirectory()) continue;
1125
- for (const month of await safeReaddir(import_node_path3.default.join(root, year.name))) {
1563
+ for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
1126
1564
  if (!month.isDirectory()) continue;
1127
- for (const day of await safeReaddir(import_node_path3.default.join(root, year.name, month.name))) {
1565
+ for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
1128
1566
  if (!day.isDirectory()) continue;
1129
- const dir = import_node_path3.default.join(root, year.name, month.name, day.name);
1567
+ const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
1130
1568
  const entries = await safeReaddir(dir);
1131
1569
  const stats = await Promise.all(
1132
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path3.default.join(dir, e.name)))
1570
+ 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)))
1133
1571
  );
1134
1572
  for (const s of stats) if (s) out.push(s);
1135
1573
  }
@@ -1138,7 +1576,7 @@ async function codexCandidates(root) {
1138
1576
  return out;
1139
1577
  }
1140
1578
  async function readSlice(file, start, length) {
1141
- const handle = await import_promises2.default.open(file, "r");
1579
+ const handle = await import_promises4.default.open(file, "r");
1142
1580
  try {
1143
1581
  const buffer = Buffer.alloc(length);
1144
1582
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1211,9 +1649,9 @@ async function readCodexIndex(file) {
1211
1649
  const names = /* @__PURE__ */ new Map();
1212
1650
  let text;
1213
1651
  try {
1214
- const stat = await import_promises2.default.stat(file);
1652
+ const stat = await import_promises4.default.stat(file);
1215
1653
  const budget = 4 * 1024 * 1024;
1216
- text = stat.size <= budget ? await import_promises2.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1654
+ text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1217
1655
  } catch {
1218
1656
  return names;
1219
1657
  }
@@ -1334,7 +1772,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1334
1772
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1335
1773
  let size;
1336
1774
  try {
1337
- size = (await import_promises2.default.stat(file)).size;
1775
+ size = (await import_promises4.default.stat(file)).size;
1338
1776
  } catch {
1339
1777
  return {
1340
1778
  items: [],
@@ -1388,9 +1826,9 @@ async function describeSessionFile(candidate, codexNames) {
1388
1826
  }
1389
1827
 
1390
1828
  // src/engine/preferences.ts
1391
- var import_node_crypto2 = require("crypto");
1392
- var import_promises3 = __toESM(require("fs/promises"), 1);
1393
- var import_node_path4 = __toESM(require("path"), 1);
1829
+ var import_node_crypto3 = require("crypto");
1830
+ var import_promises5 = __toESM(require("fs/promises"), 1);
1831
+ var import_node_path6 = __toESM(require("path"), 1);
1394
1832
  var PreferenceStore = class {
1395
1833
  constructor(dir) {
1396
1834
  this.dir = dir;
@@ -1399,11 +1837,11 @@ var PreferenceStore = class {
1399
1837
  data = {};
1400
1838
  writing = Promise.resolve();
1401
1839
  get file() {
1402
- return import_node_path4.default.join(this.dir, "preferences.json");
1840
+ return import_node_path6.default.join(this.dir, "preferences.json");
1403
1841
  }
1404
1842
  async load() {
1405
1843
  try {
1406
- const parsed = JSON.parse(await import_promises3.default.readFile(this.file, "utf8"));
1844
+ const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
1407
1845
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1408
1846
  } catch {
1409
1847
  this.data = {};
@@ -1441,10 +1879,10 @@ var PreferenceStore = class {
1441
1879
  persist() {
1442
1880
  const snapshot = JSON.stringify(this.data, null, 1);
1443
1881
  this.writing = this.writing.then(async () => {
1444
- await import_promises3.default.mkdir(this.dir, { recursive: true });
1445
- const tmp = `${this.file}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
1446
- await import_promises3.default.writeFile(tmp, snapshot, { mode: 384 });
1447
- await import_promises3.default.rename(tmp, this.file);
1882
+ await import_promises5.default.mkdir(this.dir, { recursive: true });
1883
+ const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1884
+ await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
1885
+ await import_promises5.default.rename(tmp, this.file);
1448
1886
  }).catch(() => {
1449
1887
  });
1450
1888
  }
@@ -1601,9 +2039,9 @@ async function searchLocalChats(query, options = {}) {
1601
2039
  }
1602
2040
 
1603
2041
  // src/engine/store.ts
1604
- var import_node_crypto3 = require("crypto");
1605
- var import_promises4 = __toESM(require("fs/promises"), 1);
1606
- var import_node_path5 = __toESM(require("path"), 1);
2042
+ var import_node_crypto4 = require("crypto");
2043
+ var import_promises6 = __toESM(require("fs/promises"), 1);
2044
+ var import_node_path7 = __toESM(require("path"), 1);
1607
2045
  var MAX_RUNS = 2e3;
1608
2046
  var RunIndex = class {
1609
2047
  constructor(dir) {
@@ -1613,12 +2051,12 @@ var RunIndex = class {
1613
2051
  data = { version: 1, sessions: [], runs: [] };
1614
2052
  writing = Promise.resolve();
1615
2053
  get file() {
1616
- return import_node_path5.default.join(this.dir, "index.json");
2054
+ return import_node_path7.default.join(this.dir, "index.json");
1617
2055
  }
1618
2056
  async load() {
1619
- await import_promises4.default.mkdir(this.dir, { recursive: true });
2057
+ await import_promises6.default.mkdir(this.dir, { recursive: true });
1620
2058
  try {
1621
- const parsed = JSON.parse(await import_promises4.default.readFile(this.file, "utf8"));
2059
+ const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
1622
2060
  this.data = {
1623
2061
  version: 1,
1624
2062
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1660,9 +2098,9 @@ var RunIndex = class {
1660
2098
  persist() {
1661
2099
  const snapshot = JSON.stringify(this.data, null, 1);
1662
2100
  this.writing = this.writing.then(async () => {
1663
- const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1664
- await import_promises4.default.writeFile(tmp, snapshot, { mode: 384 });
1665
- await import_promises4.default.rename(tmp, this.file);
2101
+ const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
2102
+ await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
2103
+ await import_promises6.default.rename(tmp, this.file);
1666
2104
  }).catch(() => {
1667
2105
  });
1668
2106
  }
@@ -1672,27 +2110,27 @@ var RunIndex = class {
1672
2110
  /** A stable Paperclip agent/company identity for this install, so the
1673
2111
  * driver's scratch state is reused rather than growing per run. */
1674
2112
  async identity() {
1675
- const file = import_node_path5.default.join(this.dir, "identity.json");
2113
+ const file = import_node_path7.default.join(this.dir, "identity.json");
1676
2114
  try {
1677
- const parsed = JSON.parse(await import_promises4.default.readFile(file, "utf8"));
2115
+ const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
1678
2116
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1679
2117
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1680
2118
  }
1681
2119
  } catch {
1682
2120
  }
1683
- const identity = { companyId: (0, import_node_crypto3.randomUUID)(), agentId: (0, import_node_crypto3.randomUUID)() };
1684
- await import_promises4.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
2121
+ const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
2122
+ await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
1685
2123
  return identity;
1686
2124
  }
1687
2125
  };
1688
2126
 
1689
2127
  // src/engine/user-defaults.ts
1690
- var import_promises5 = __toESM(require("fs/promises"), 1);
2128
+ var import_promises7 = __toESM(require("fs/promises"), 1);
1691
2129
  var import_node_os4 = __toESM(require("os"), 1);
1692
- var import_node_path6 = __toESM(require("path"), 1);
2130
+ var import_node_path8 = __toESM(require("path"), 1);
1693
2131
  async function readJson(file) {
1694
2132
  try {
1695
- const parsed = JSON.parse(await import_promises5.default.readFile(file, "utf8"));
2133
+ const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
1696
2134
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1697
2135
  } catch {
1698
2136
  return null;
@@ -1703,8 +2141,8 @@ function str3(value) {
1703
2141
  }
1704
2142
  async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1705
2143
  const files = [
1706
- ...cwd ? [import_node_path6.default.join(cwd, ".claude", "settings.local.json"), import_node_path6.default.join(cwd, ".claude", "settings.json")] : [],
1707
- import_node_path6.default.join(home, ".claude", "settings.json")
2144
+ ...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
2145
+ import_node_path8.default.join(home, ".claude", "settings.json")
1708
2146
  ];
1709
2147
  let model = null;
1710
2148
  let effort = null;
@@ -1720,7 +2158,7 @@ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1720
2158
  async function codexDefaults(home = import_node_os4.default.homedir()) {
1721
2159
  let text;
1722
2160
  try {
1723
- text = await import_promises5.default.readFile(import_node_path6.default.join(home, ".codex", "config.toml"), "utf8");
2161
+ text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
1724
2162
  } catch {
1725
2163
  return { model: null, effort: null };
1726
2164
  }
@@ -1744,31 +2182,31 @@ var EFFORT_LEVELS = {
1744
2182
  // src/engine/index.ts
1745
2183
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1746
2184
  function moduleDir() {
1747
- return import_node_path7.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2185
+ return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
1748
2186
  }
1749
2187
  function resolveWorkerPath() {
1750
2188
  const dir = moduleDir();
1751
2189
  for (const candidate of [
1752
- import_node_path7.default.join(dir, "worker.js"),
1753
- import_node_path7.default.join(dir, "worker.ts"),
1754
- import_node_path7.default.join(dir, "..", "engine", "worker.js"),
1755
- import_node_path7.default.join(dir, "..", "engine", "worker.ts")
2190
+ import_node_path9.default.join(dir, "worker.js"),
2191
+ import_node_path9.default.join(dir, "worker.ts"),
2192
+ import_node_path9.default.join(dir, "..", "engine", "worker.js"),
2193
+ import_node_path9.default.join(dir, "..", "engine", "worker.ts")
1756
2194
  ]) {
1757
2195
  if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
1758
2196
  }
1759
2197
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1760
2198
  }
1761
2199
  async function installShim(dataDir) {
1762
- const source = import_node_path7.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2200
+ const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1763
2201
  if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1764
- const body = await import_promises6.default.readFile(source, "utf8");
1765
- const target = import_node_path7.default.join(dataDir, "bin", "cli-shim.sh");
1766
- const current = await import_promises6.default.readFile(target, "utf8").catch(() => null);
2202
+ const body = await import_promises8.default.readFile(source, "utf8");
2203
+ const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
2204
+ const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
1767
2205
  if (current !== body) {
1768
- await import_promises6.default.mkdir(import_node_path7.default.dirname(target), { recursive: true });
1769
- await import_promises6.default.writeFile(target, body, { mode: 493 });
2206
+ await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
2207
+ await import_promises8.default.writeFile(target, body, { mode: 493 });
1770
2208
  }
1771
- await import_promises6.default.chmod(target, 493);
2209
+ await import_promises8.default.chmod(target, 493);
1772
2210
  return target;
1773
2211
  }
1774
2212
  function nowIso() {
@@ -1868,29 +2306,30 @@ function describeAuthStatus(provider, probe) {
1868
2306
  };
1869
2307
  }
1870
2308
  async function createCodingSessionsEngine(options = {}) {
1871
- const dataDir = options.dataDir ?? import_node_path7.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
1872
- const stateDir = import_node_path7.default.join(dataDir, "paperclip-state");
2309
+ const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
2310
+ const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
1873
2311
  const homes = options.homes ?? defaultProviderHomes();
1874
2312
  const index = new RunIndex(dataDir);
1875
2313
  await index.load();
1876
2314
  const preferences = new PreferenceStore(dataDir);
1877
2315
  await preferences.load();
1878
- await import_promises6.default.mkdir(stateDir, { recursive: true });
2316
+ await import_promises8.default.mkdir(stateDir, { recursive: true });
1879
2317
  const identity = await index.identity();
2318
+ const agents = createAgentConnection(options.agents, dataDir);
1880
2319
  const backups = options.rawBackup ? createRawBackupScheduler({
1881
2320
  sink: options.rawBackup,
1882
2321
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1883
2322
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1884
2323
  }) : null;
1885
2324
  const discoveredSeen = /* @__PURE__ */ new Map();
1886
- const healthFile = import_node_path7.default.join(dataDir, "provider-health.json");
2325
+ const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
1887
2326
  let health = {};
1888
2327
  try {
1889
- health = JSON.parse(await import_promises6.default.readFile(healthFile, "utf8"));
2328
+ health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
1890
2329
  } catch {
1891
2330
  health = {};
1892
2331
  }
1893
- const saveHealth = () => import_promises6.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2332
+ const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1894
2333
  function noteRunOutcome(provider, status, texts) {
1895
2334
  if (status === "completed") {
1896
2335
  if (health[provider]) {
@@ -2007,7 +2446,7 @@ async function createCodingSessionsEngine(options = {}) {
2007
2446
  async function startRun(session, prompt) {
2008
2447
  const provider = session.provider;
2009
2448
  const realCommand = await commandFor(provider);
2010
- const runId = (0, import_node_crypto4.randomUUID)();
2449
+ const runId = (0, import_node_crypto5.randomUUID)();
2011
2450
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
2012
2451
  const run = {
2013
2452
  runId,
@@ -2045,7 +2484,36 @@ async function createCodingSessionsEngine(options = {}) {
2045
2484
  let fileOffset = null;
2046
2485
  if (session.providerSessionId) {
2047
2486
  const file = await findSessionFile(provider, session.providerSessionId, homes);
2048
- if (file) fileOffset = (await import_promises6.default.stat(file).catch(() => null))?.size ?? null;
2487
+ if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
2488
+ }
2489
+ let agentArgs = [];
2490
+ let cliPrompt = prompt;
2491
+ let delivered = null;
2492
+ if (!session.agent && session.agentRemovedDue) {
2493
+ if (provider === "codex") cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
2494
+ delivered = { agentRemovedDue: false };
2495
+ }
2496
+ if (session.agent) {
2497
+ const bundle = await agents.readSessionBundle(session.id);
2498
+ if (!bundle) {
2499
+ const failed = saveRun({
2500
+ ...run,
2501
+ status: "failed",
2502
+ endedAt: nowIso(),
2503
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2504
+ });
2505
+ const current = index.session(session.id);
2506
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2507
+ return failed;
2508
+ }
2509
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2510
+ mcpUrl: await agents.mcpUrlFor(session.id)
2511
+ });
2512
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2513
+ if (provider === "codex" && session.agent.blockDue) {
2514
+ cliPrompt = `${renderFallbackBlock(bundle, import_node_path9.default.join(attachment.dir, "skills"))}${prompt}`;
2515
+ }
2516
+ delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
2049
2517
  }
2050
2518
  const child = forkWorker();
2051
2519
  const liveRun = {
@@ -2061,6 +2529,10 @@ async function createCodingSessionsEngine(options = {}) {
2061
2529
  };
2062
2530
  live.set(runId, liveRun);
2063
2531
  emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
2532
+ if (delivered) {
2533
+ const current = index.session(session.id);
2534
+ if (current) saveSession({ ...current, ...delivered });
2535
+ }
2064
2536
  const splitter = createLineSplitter();
2065
2537
  const claude = provider === "claude" ? createClaudeNormalizer() : null;
2066
2538
  const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
@@ -2099,6 +2571,7 @@ async function createCodingSessionsEngine(options = {}) {
2099
2571
  }
2100
2572
  }
2101
2573
  void pruneDriverScratch();
2574
+ if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
2102
2575
  };
2103
2576
  let settled = false;
2104
2577
  child.on("message", (raw) => {
@@ -2213,16 +2686,39 @@ async function createCodingSessionsEngine(options = {}) {
2213
2686
  provider,
2214
2687
  runId,
2215
2688
  cwd: session.cwd,
2216
- prompt,
2689
+ prompt: cliPrompt,
2217
2690
  model,
2218
2691
  effort: session.effort ?? null,
2219
2692
  resumeSessionId: session.providerSessionId,
2220
2693
  shimPath,
2221
2694
  realCommand,
2222
- identity
2695
+ identity,
2696
+ agentArgs
2223
2697
  });
2224
2698
  return run;
2225
2699
  }
2700
+ async function checkCompaction(sessionId) {
2701
+ const session = index.session(sessionId);
2702
+ if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
2703
+ const file = await findSessionFile("codex", session.providerSessionId, homes);
2704
+ if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
2705
+ const current = index.session(sessionId);
2706
+ if (current?.agent) {
2707
+ saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
2708
+ }
2709
+ }
2710
+ async function attachAgent(sessionId, selection, options2) {
2711
+ const bundle = await agents.loadBundle(selection);
2712
+ await agents.saveSessionBundle(sessionId, bundle);
2713
+ return {
2714
+ ...sessionAgentRecord(bundle, {
2715
+ toolsServed: await agents.mcpUrlFor(sessionId) !== null,
2716
+ variables: selection.variables ?? {}
2717
+ }),
2718
+ blockDue: options2.blockDue,
2719
+ deliveredAt: null
2720
+ };
2721
+ }
2226
2722
  function killGroup(liveRun) {
2227
2723
  const target = liveRun.pgid;
2228
2724
  if (target === null) return;
@@ -2238,7 +2734,7 @@ async function createCodingSessionsEngine(options = {}) {
2238
2734
  }, 3e3);
2239
2735
  }
2240
2736
  async function pruneDriverScratch() {
2241
- const runsDir = import_node_path7.default.join(
2737
+ const runsDir = import_node_path9.default.join(
2242
2738
  stateDir,
2243
2739
  "instances",
2244
2740
  "default",
@@ -2249,9 +2745,9 @@ async function createCodingSessionsEngine(options = {}) {
2249
2745
  "claude-runtime",
2250
2746
  "runs"
2251
2747
  );
2252
- const entries = await import_promises6.default.readdir(runsDir).catch(() => []);
2748
+ const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
2253
2749
  await Promise.all(
2254
- entries.filter((name) => !live.has(name)).map((name) => import_promises6.default.rm(import_node_path7.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2750
+ 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))
2255
2751
  );
2256
2752
  }
2257
2753
  async function probe() {
@@ -2292,7 +2788,7 @@ async function createCodingSessionsEngine(options = {}) {
2292
2788
  function newSession(input) {
2293
2789
  const at = nowIso();
2294
2790
  return {
2295
- id: (0, import_node_crypto4.randomUUID)(),
2791
+ id: (0, import_node_crypto5.randomUUID)(),
2296
2792
  provider: input.provider,
2297
2793
  providerSessionId: input.providerSessionId,
2298
2794
  cwd: input.cwd,
@@ -2310,17 +2806,17 @@ async function createCodingSessionsEngine(options = {}) {
2310
2806
  function resolveFolder(raw) {
2311
2807
  const trimmed = raw.trim();
2312
2808
  if (!trimmed) return null;
2313
- return import_node_path7.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2809
+ return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2314
2810
  }
2315
2811
  async function checkFolder(raw) {
2316
2812
  const target = resolveFolder(raw);
2317
2813
  if (!target) return { path: "", exists: false, isDirectory: false };
2318
- const stat = await import_promises6.default.stat(target).catch(() => null);
2814
+ const stat = await import_promises8.default.stat(target).catch(() => null);
2319
2815
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2320
2816
  }
2321
2817
  async function assertFolder(cwd) {
2322
- const resolved = import_node_path7.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2323
- const stat = await import_promises6.default.stat(resolved).catch(() => null);
2818
+ const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2819
+ const stat = await import_promises8.default.stat(resolved).catch(() => null);
2324
2820
  if (!stat?.isDirectory()) {
2325
2821
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2326
2822
  }
@@ -2382,7 +2878,7 @@ async function createCodingSessionsEngine(options = {}) {
2382
2878
  folders.map(async ([folder, lastUsedAt]) => ({
2383
2879
  path: folder,
2384
2880
  lastUsedAt,
2385
- exists: (await import_promises6.default.stat(folder).catch(() => null))?.isDirectory() === true
2881
+ exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
2386
2882
  }))
2387
2883
  );
2388
2884
  },
@@ -2427,7 +2923,7 @@ async function createCodingSessionsEngine(options = {}) {
2427
2923
  async createFolder(raw) {
2428
2924
  const target = resolveFolder(raw);
2429
2925
  if (!target) throw new Error("Type a folder path first.");
2430
- await import_promises6.default.mkdir(target, { recursive: true });
2926
+ await import_promises8.default.mkdir(target, { recursive: true });
2431
2927
  return checkFolder(target);
2432
2928
  },
2433
2929
  async sessionActivity(provider, providerSessionId) {
@@ -2465,48 +2961,63 @@ async function createCodingSessionsEngine(options = {}) {
2465
2961
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2466
2962
  };
2467
2963
  }
2468
- const stat = await import_promises6.default.stat(file);
2964
+ const stat = await import_promises8.default.stat(file);
2469
2965
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2470
2966
  return {
2471
2967
  available: false,
2472
2968
  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}.`
2473
2969
  };
2474
2970
  }
2475
- const content = await import_promises6.default.readFile(file, "utf8");
2476
- return { available: true, fileName: import_node_path7.default.basename(file), bytes: stat.size, content };
2971
+ const content = await import_promises8.default.readFile(file, "utf8");
2972
+ return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
2477
2973
  },
2478
2974
  async start(input) {
2479
2975
  const prompt = input.prompt.trim();
2480
2976
  if (!prompt) throw new Error("Type what you want done before starting.");
2481
2977
  const cwd = await assertFolder(input.cwd);
2482
- const session = saveSession(
2483
- newSession({
2484
- provider: input.provider,
2485
- cwd,
2486
- title: titleFrom(prompt),
2487
- model: input.model?.trim() || null,
2488
- effort: input.effort?.trim() || null,
2489
- origin: "started",
2490
- providerSessionId: null
2491
- })
2492
- );
2978
+ const draft = newSession({
2979
+ provider: input.provider,
2980
+ cwd,
2981
+ title: titleFrom(prompt),
2982
+ model: input.model?.trim() || null,
2983
+ effort: input.effort?.trim() || null,
2984
+ origin: "started",
2985
+ providerSessionId: null
2986
+ });
2987
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
2988
+ const session = saveSession(draft);
2493
2989
  const run = await startRun(session, prompt);
2494
2990
  return { session: index.session(session.id) ?? session, run, queued: false };
2495
2991
  },
2496
2992
  async takeover(input) {
2497
2993
  const existing = index.session(input.providerSessionId);
2498
- if (existing) return existing;
2994
+ if (existing) {
2995
+ if (!input.agent) return existing;
2996
+ return engine.setAgent(existing.id, input.agent);
2997
+ }
2499
2998
  const cwd = await assertFolder(input.cwd);
2500
- return saveSession(
2501
- newSession({
2502
- provider: input.provider,
2503
- cwd,
2504
- title: input.title?.trim() || "Taken-over chat",
2505
- model: null,
2506
- origin: "takeover",
2507
- providerSessionId: input.providerSessionId
2508
- })
2509
- );
2999
+ const draft = newSession({
3000
+ provider: input.provider,
3001
+ cwd,
3002
+ title: input.title?.trim() || "Taken-over chat",
3003
+ model: null,
3004
+ origin: "takeover",
3005
+ providerSessionId: input.providerSessionId
3006
+ });
3007
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
3008
+ return saveSession(draft);
3009
+ },
3010
+ async setAgent(sessionId, selection) {
3011
+ const session = index.session(String(sessionId ?? ""));
3012
+ if (!session) throw new Error("This session is not in the list anymore.");
3013
+ if (!selection) {
3014
+ const told = session.provider === "codex" && session.agent?.deliveredAt != null;
3015
+ return saveSession({ ...session, agent: null, agentRemovedDue: told });
3016
+ }
3017
+ const agent = await attachAgent(session.id, selection, {
3018
+ blockDue: session.provider === "codex" && session.providerSessionId !== null
3019
+ });
3020
+ return saveSession({ ...session, agent, agentRemovedDue: false });
2510
3021
  },
2511
3022
  async send(sessionId, rawPrompt) {
2512
3023
  const prompt = rawPrompt.trim();
@@ -2558,6 +3069,21 @@ async function createCodingSessionsEngine(options = {}) {
2558
3069
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2559
3070
  return saveSession(next);
2560
3071
  },
3072
+ async agentAccess() {
3073
+ return agents.access();
3074
+ },
3075
+ async agentCatalogRpc(request) {
3076
+ return agents.catalogRpc(request);
3077
+ },
3078
+ async agentDetails(agentId) {
3079
+ return agents.details(String(agentId ?? ""));
3080
+ },
3081
+ setMcpBaseUrl(baseUrl) {
3082
+ agents.setMcpBaseUrl(baseUrl);
3083
+ },
3084
+ async handleMcp(secret, body) {
3085
+ return agents.handleMcp(secret, body);
3086
+ },
2561
3087
  async dispose() {
2562
3088
  for (const liveRun of live.values()) {
2563
3089
  liveRun.stopRequested = true;
@@ -2627,7 +3153,11 @@ var BRIDGE_METHODS = [
2627
3153
  "setChatPreference",
2628
3154
  "searchChats",
2629
3155
  "openFolder",
2630
- "setSessionOptions"
3156
+ "setSessionOptions",
3157
+ "agentAccess",
3158
+ "agentCatalogRpc",
3159
+ "agentDetails",
3160
+ "setAgent"
2631
3161
  ];
2632
3162
  var BridgeError = class extends Error {
2633
3163
  constructor(message, code) {
@@ -2671,14 +3201,14 @@ var MIME = {
2671
3201
  ".map": "application/json"
2672
3202
  };
2673
3203
  function moduleDir2() {
2674
- return import_node_path8.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
3204
+ return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
2675
3205
  }
2676
3206
  function defaultUiDir() {
2677
3207
  for (const candidate of [
2678
- import_node_path8.default.resolve(moduleDir2(), "..", "ui"),
2679
- import_node_path8.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
3208
+ import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
3209
+ import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
2680
3210
  ]) {
2681
- if ((0, import_node_fs5.existsSync)(import_node_path8.default.join(candidate, "index.html"))) return candidate;
3211
+ if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
2682
3212
  }
2683
3213
  return null;
2684
3214
  }
@@ -2686,7 +3216,7 @@ function tokensMatch(expected, given) {
2686
3216
  if (!given) return false;
2687
3217
  const a = Buffer.from(expected);
2688
3218
  const b = Buffer.from(given);
2689
- return a.length === b.length && (0, import_node_crypto5.timingSafeEqual)(a, b);
3219
+ return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
2690
3220
  }
2691
3221
  var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
2692
3222
  <body style="font:15px system-ui;padding:48px;max-width:560px">
@@ -2701,19 +3231,19 @@ async function serveStatic(uiDir, req, res) {
2701
3231
  return;
2702
3232
  }
2703
3233
  const requested = decodeURIComponent(url.pathname);
2704
- let file = import_node_path8.default.resolve(uiDir, `.${requested}`);
3234
+ let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
2705
3235
  if (!file.startsWith(uiDir)) {
2706
3236
  res.writeHead(403).end();
2707
3237
  return;
2708
3238
  }
2709
- const stat = await import_promises7.default.stat(file).catch(() => null);
2710
- if (!stat || stat.isDirectory()) file = import_node_path8.default.join(uiDir, "index.html");
2711
- const body = await import_promises7.default.readFile(file).catch(() => null);
3239
+ const stat = await import_promises9.default.stat(file).catch(() => null);
3240
+ if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
3241
+ const body = await import_promises9.default.readFile(file).catch(() => null);
2712
3242
  if (!body) {
2713
3243
  res.writeHead(404).end();
2714
3244
  return;
2715
3245
  }
2716
- const ext = import_node_path8.default.extname(file);
3246
+ const ext = import_node_path10.default.extname(file);
2717
3247
  res.writeHead(200, {
2718
3248
  "content-type": MIME[ext] ?? "application/octet-stream",
2719
3249
  "cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
@@ -2722,6 +3252,46 @@ async function serveStatic(uiDir, req, res) {
2722
3252
  });
2723
3253
  res.end(body);
2724
3254
  }
3255
+ var MAX_MCP_BODY = 4 * 1024 * 1024;
3256
+ async function handleMcp(engine, secret, req, res, allowedOrigin) {
3257
+ if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
3258
+ res.writeHead(403).end();
3259
+ return;
3260
+ }
3261
+ if (req.method !== "POST") {
3262
+ res.writeHead(405, { allow: "POST" }).end();
3263
+ return;
3264
+ }
3265
+ const chunks = [];
3266
+ let size = 0;
3267
+ for await (const chunk of req) {
3268
+ size += chunk.length;
3269
+ if (size > MAX_MCP_BODY) {
3270
+ res.writeHead(413).end();
3271
+ return;
3272
+ }
3273
+ chunks.push(chunk);
3274
+ }
3275
+ let body;
3276
+ try {
3277
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3278
+ } catch {
3279
+ res.writeHead(400, { "content-type": "application/json" });
3280
+ res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
3281
+ return;
3282
+ }
3283
+ const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
3284
+ if (!reply) {
3285
+ res.writeHead(404).end();
3286
+ return;
3287
+ }
3288
+ if (reply.body === void 0) {
3289
+ res.writeHead(reply.status).end();
3290
+ return;
3291
+ }
3292
+ res.writeHead(reply.status, { "content-type": "application/json" });
3293
+ res.end(JSON.stringify(reply.body));
3294
+ }
2725
3295
  function attachSocket(socket, bridge) {
2726
3296
  const unsubscribe = bridge.subscribe((event) => {
2727
3297
  if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
@@ -2752,7 +3322,7 @@ function attachSocket(socket, bridge) {
2752
3322
  socket.on("close", unsubscribe);
2753
3323
  }
2754
3324
  async function startCodingSessionsHost(options = {}) {
2755
- const token = options.token ?? (0, import_node_crypto5.randomBytes)(24).toString("base64url");
3325
+ const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
2756
3326
  const engine = options.engine ?? await createCodingSessionsEngine(options);
2757
3327
  const bridge = createHostBridge(engine);
2758
3328
  const uiDir = options.uiDir ?? defaultUiDir();
@@ -2763,6 +3333,13 @@ async function startCodingSessionsHost(options = {}) {
2763
3333
  };
2764
3334
  const server = import_node_http.default.createServer((req, res) => {
2765
3335
  const url2 = new URL(req.url ?? "/", `http://${HOST}`);
3336
+ if (url2.pathname.startsWith("/mcp/")) {
3337
+ handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
3338
+ if (!res.headersSent) res.writeHead(500);
3339
+ res.end();
3340
+ });
3341
+ return;
3342
+ }
2766
3343
  if (url2.pathname === "/health") {
2767
3344
  res.writeHead(200, { "content-type": "application/json" });
2768
3345
  res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
@@ -2793,6 +3370,7 @@ async function startCodingSessionsHost(options = {}) {
2793
3370
  server.off("error", reject);
2794
3371
  const address = server.address();
2795
3372
  if (address && typeof address === "object") port = address.port;
3373
+ engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
2796
3374
  resolve();
2797
3375
  });
2798
3376
  });
@@ -2803,6 +3381,7 @@ async function startCodingSessionsHost(options = {}) {
2803
3381
  token,
2804
3382
  engine,
2805
3383
  async close() {
3384
+ engine.setMcpBaseUrl?.(null);
2806
3385
  for (const client of wss.clients) client.terminate();
2807
3386
  await new Promise((resolve) => wss.close(() => resolve()));
2808
3387
  await new Promise((resolve) => server.close(() => resolve()));