@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.
@@ -1,15 +1,15 @@
1
1
  // src/server/index.ts
2
- import { randomBytes, timingSafeEqual } from "crypto";
2
+ import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
3
3
  import { existsSync as existsSync2 } from "fs";
4
- import fs7 from "fs/promises";
4
+ import fs9 from "fs/promises";
5
5
  import http from "http";
6
- import path8 from "path";
6
+ import path10 from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
  import { WebSocketServer } from "ws";
9
9
 
10
10
  // src/engine/index.ts
11
11
  import { execFile as execFile3, fork } from "child_process";
12
- import { randomUUID as randomUUID3 } from "crypto";
12
+ import { randomUUID as randomUUID4 } from "crypto";
13
13
  import { existsSync } from "fs";
14
14
  import { formatFileSize } from "@ai-matrx/kit/format";
15
15
 
@@ -70,10 +70,448 @@ function createRawBackupScheduler(options) {
70
70
  };
71
71
  }
72
72
 
73
+ // src/engine/agent-attach.ts
74
+ import fs2 from "fs/promises";
75
+ import path from "path";
76
+
77
+ // src/normalize/agent-block.ts
78
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
79
+ function stripAgentBlock(text) {
80
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
81
+ if (removed) return text.slice(removed[0].length);
82
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
83
+ const end = text.indexOf("</ai_matrx_agent>");
84
+ if (end < 0) return text;
85
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
86
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
87
+ if (skills) rest = rest.slice(skills[0].length);
88
+ return rest.replace(/^\s+/, "");
89
+ }
90
+
91
+ // src/engine/agent-attach.ts
92
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
93
+ var AGENT_PLUGIN_NAME = "matrx-agent";
94
+ function escapeAttr(value) {
95
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
96
+ }
97
+ function versionWords(bundle) {
98
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
99
+ }
100
+ function renderAgentInstructions(bundle, options = {}) {
101
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
102
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
103
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
104
+ if (included.length > 0) {
105
+ parts.push(
106
+ "## Skills (always in effect)",
107
+ ...included.map((skill) => `### ${skill.label}
108
+
109
+ ${skill.body.trim()}`)
110
+ );
111
+ }
112
+ if (options.claudeSkills && listed2.length > 0) {
113
+ parts.push(
114
+ [
115
+ "## Your skills",
116
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
117
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
118
+ ].join("\n")
119
+ );
120
+ }
121
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
122
+ parts.push(bundle.instructions.trim());
123
+ parts.push("</ai_matrx_agent>");
124
+ return `${parts.filter(Boolean).join("\n\n")}
125
+ `;
126
+ }
127
+ function renderCodexSkillsList(bundle, skillsRoot) {
128
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
129
+ if (listed2.length === 0) return "";
130
+ const lines = [
131
+ "<skills_instructions>",
132
+ "## AI Matrx agent skills",
133
+ "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.",
134
+ "### Available skills",
135
+ ...listed2.map(
136
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${path.join(skillsRoot, skill.slug, "SKILL.md")})`
137
+ ),
138
+ "</skills_instructions>"
139
+ ];
140
+ return lines.join("\n");
141
+ }
142
+ function renderFallbackBlock(bundle, skillsRoot) {
143
+ const skills = renderCodexSkillsList(bundle, skillsRoot);
144
+ 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.");
145
+ return skills ? `${body}
146
+
147
+ ${skills}
148
+
149
+ ` : `${body}
150
+
151
+ `;
152
+ }
153
+ function tomlString(value) {
154
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
155
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
156
+ }
157
+ async function writeAgentAttachment(dir, bundle, options) {
158
+ const pluginDir = path.join(dir, "plugin");
159
+ const skillsRoot = path.join(dir, "skills");
160
+ await fs2.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
161
+ await fs2.mkdir(skillsRoot, { recursive: true, mode: 448 });
162
+ const instructions = renderAgentInstructions(bundle);
163
+ const instructionsFile = path.join(dir, "instructions.md");
164
+ await fs2.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
165
+ await fs2.writeFile(
166
+ path.join(pluginDir, ".claude-plugin", "plugin.json"),
167
+ `${JSON.stringify(
168
+ {
169
+ name: AGENT_PLUGIN_NAME,
170
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
171
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
172
+ },
173
+ null,
174
+ 2
175
+ )}
176
+ `,
177
+ { mode: 384 }
178
+ );
179
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
180
+ const wanted = new Set(listed2.map((skill) => skill.slug));
181
+ for (const root of [path.join(pluginDir, "skills"), skillsRoot]) {
182
+ await fs2.mkdir(root, { recursive: true, mode: 448 });
183
+ for (const existing of await fs2.readdir(root).catch(() => [])) {
184
+ if (!wanted.has(existing)) await fs2.rm(path.join(root, existing), { recursive: true, force: true });
185
+ }
186
+ for (const skill of listed2) {
187
+ const skillDir = path.join(root, skill.slug);
188
+ await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
189
+ await fs2.writeFile(path.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
190
+ }
191
+ }
192
+ let mcpConfigFile = null;
193
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
194
+ const mcpPath = path.join(dir, "mcp.json");
195
+ if (mcpUrl) {
196
+ mcpConfigFile = mcpPath;
197
+ await fs2.writeFile(
198
+ mcpPath,
199
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
200
+ `,
201
+ { mode: 384 }
202
+ );
203
+ } else {
204
+ await fs2.rm(mcpPath, { force: true });
205
+ }
206
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
207
+ return {
208
+ dir,
209
+ instructionsFile,
210
+ pluginDir,
211
+ mcpConfigFile,
212
+ mcpUrl,
213
+ // Same order as Claude's: the skills list first, the agent's words last.
214
+ codexInstructions: skillsList ? `${skillsList}
215
+
216
+ ${instructions}` : instructions
217
+ };
218
+ }
219
+ function agentArgsFor(provider, attachment, options) {
220
+ if (provider === "claude") {
221
+ const args2 = [
222
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
223
+ "--append-system-prompt-file",
224
+ attachment.instructionsFile,
225
+ "--system-prompt-snapshot",
226
+ "off",
227
+ "--plugin-dir",
228
+ attachment.pluginDir
229
+ ];
230
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
231
+ return args2;
232
+ }
233
+ const args = [];
234
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
235
+ if (attachment.mcpUrl) {
236
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
237
+ args.push(
238
+ "-c",
239
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
240
+ "-c",
241
+ `${key}.default_tools_approval_mode="approve"`
242
+ );
243
+ }
244
+ return args;
245
+ }
246
+ function sessionAgentRecord(bundle, options) {
247
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
248
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
249
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
250
+ if (!options.toolsServed && bundle.tools.length > 0) {
251
+ for (const tool of bundle.tools) {
252
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
253
+ }
254
+ }
255
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
256
+ const loaded = [
257
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
258
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
259
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
260
+ ];
261
+ return {
262
+ agentId: bundle.agent_id,
263
+ versionId: bundle.version_id,
264
+ versionNumber: bundle.version_number,
265
+ name: bundle.name,
266
+ definitionHash: bundle.definition_hash,
267
+ loaded,
268
+ unavailable,
269
+ skills: bundle.skills.map((skill) => skill.label),
270
+ tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
271
+ ...options.variables ? { variables: options.variables } : {}
272
+ };
273
+ }
274
+ 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";
275
+ async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
276
+ const text = await fs2.readFile(rolloutFile, "utf8").catch(() => "");
277
+ if (!text.includes('"type":"compacted"')) return false;
278
+ let latest = null;
279
+ for (const line of text.split("\n")) {
280
+ if (!line.includes('"type":"compacted"')) continue;
281
+ try {
282
+ const entry = JSON.parse(line);
283
+ if (entry.type === "compacted") latest = entry;
284
+ } catch {
285
+ }
286
+ }
287
+ if (!latest) return false;
288
+ if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
289
+ return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
290
+ }
291
+
292
+ // src/engine/agents.ts
293
+ import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
294
+ import fs3 from "fs/promises";
295
+ import path2 from "path";
296
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
297
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
298
+ var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
299
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
300
+ "agx_get_list_full",
301
+ "agx_search",
302
+ "agx_resolve_agent_address",
303
+ "ues_get_bulk",
304
+ "ues_list",
305
+ "ues_set",
306
+ "ues_touch"
307
+ ]);
308
+ var agentsClient = async () => {
309
+ const [matrx, portable, mcp] = await Promise.all([
310
+ import("@ai-matrx/agents/matrx"),
311
+ import("@ai-matrx/agents/portable"),
312
+ import("@ai-matrx/agents/portable/mcp")
313
+ ]);
314
+ return { ...matrx, ...portable, ...mcp };
315
+ };
316
+ function sortedValues(values) {
317
+ return Object.keys(values).sort().map((key) => [key, values[key]]);
318
+ }
319
+ var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
320
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
321
+ function userIdOf(credential) {
322
+ if (!credential || credential.kind !== "user") return null;
323
+ const payload = credential.accessToken.split(".")[1];
324
+ if (!payload) return null;
325
+ try {
326
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
327
+ return typeof claims.sub === "string" ? claims.sub : null;
328
+ } catch {
329
+ return null;
330
+ }
331
+ }
332
+ function createAgentConnection(options, dataDir) {
333
+ const credentials = options?.credentials ?? null;
334
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
335
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
336
+ const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
337
+ const root = path2.join(dataDir, "agents");
338
+ let mcpBaseUrl = null;
339
+ const handlers = /* @__PURE__ */ new Map();
340
+ const secretToSession = /* @__PURE__ */ new Map();
341
+ const organizationId = async () => {
342
+ const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
343
+ return raw && raw.trim() ? raw.trim() : null;
344
+ };
345
+ const transport = async () => {
346
+ const org = await organizationId();
347
+ return (await agentsClient()).createMatrxTransport({
348
+ baseUrl: serverUrl,
349
+ ...credentials ? { credentials } : {},
350
+ ...org ? { organizationId: org } : {},
351
+ source: "codingSessionsAgents"
352
+ });
353
+ };
354
+ const cacheDir = path2.join(dataDir, "agents", ".bundles");
355
+ const cacheKey = (definitionHash, selection, surface) => createHash2("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
356
+ const credential = async () => credentials ? credentials.get() : null;
357
+ const requireSignedIn = async () => {
358
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
359
+ };
360
+ const sessionDir = (sessionId) => path2.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
361
+ async function meta(sessionId) {
362
+ const file = path2.join(sessionDir(sessionId), "session.json");
363
+ const existing = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
364
+ if (existing?.mcpSecret && existing.conversationId) return existing;
365
+ const created = { mcpSecret: randomBytes(24).toString("base64url"), conversationId: randomUUID() };
366
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
367
+ await fs3.writeFile(file, JSON.stringify(created), { mode: 384 });
368
+ return created;
369
+ }
370
+ async function findSessionBySecret(secret) {
371
+ for (const name of await fs3.readdir(root).catch(() => [])) {
372
+ const found = await fs3.readFile(path2.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
373
+ if (found) {
374
+ secretToSession.set(secret, name);
375
+ return name;
376
+ }
377
+ }
378
+ return null;
379
+ }
380
+ async function readSessionBundle(sessionId) {
381
+ return fs3.readFile(path2.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
382
+ }
383
+ return {
384
+ async access() {
385
+ const current = await credential().catch(() => null);
386
+ const userId = userIdOf(current);
387
+ return {
388
+ configured: credentials !== null && supabaseKey !== null,
389
+ signedIn: userId !== null,
390
+ userId,
391
+ organizationId: await organizationId().catch(() => null)
392
+ };
393
+ },
394
+ async catalogRpc(request) {
395
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
396
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
397
+ }
398
+ const current = await credential();
399
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
400
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
401
+ const query = new URLSearchParams();
402
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
403
+ if (request.range) {
404
+ query.set("offset", String(request.range[0]));
405
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
406
+ }
407
+ const headers = {
408
+ apikey: supabaseKey,
409
+ Authorization: `Bearer ${current.accessToken}`,
410
+ "Content-Type": "application/json",
411
+ // Every catalogue RPC lives in `public`; the database's default
412
+ // exposed schema is not `public`, so name it (supabase-js does).
413
+ "Content-Profile": "public",
414
+ "Accept-Profile": "public"
415
+ };
416
+ if (request.count) headers.Prefer = `count=${request.count}`;
417
+ const qs = query.toString();
418
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
419
+ method: "POST",
420
+ headers,
421
+ body: JSON.stringify(request.args ?? {})
422
+ });
423
+ const text = await response.text();
424
+ const body = text ? JSON.parse(text) : null;
425
+ if (!response.ok) {
426
+ const err = body ?? {};
427
+ return {
428
+ data: null,
429
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
430
+ };
431
+ }
432
+ const range = response.headers.get("content-range");
433
+ const total = range?.split("/")[1];
434
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
435
+ },
436
+ async details(agentId) {
437
+ await requireSignedIn();
438
+ const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
439
+ return {
440
+ agentId: summary.agent_id,
441
+ name: summary.name,
442
+ variables: summary.variables,
443
+ versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
444
+ latestVersionNumber: summary.version_number,
445
+ latestVersionId: summary.version_id
446
+ };
447
+ },
448
+ async loadBundle(selection) {
449
+ await requireSignedIn();
450
+ const client = await agentsClient();
451
+ const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
452
+ const versionId = selection.versionId ?? summary.version_id;
453
+ const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
454
+ const surface = "matrx-user/code-editor";
455
+ const file = definitionHash ? path2.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
456
+ if (file) {
457
+ const cached = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
458
+ const fresh = await fs3.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
459
+ if (cached && fresh && cached.definition_hash === definitionHash) return cached;
460
+ }
461
+ const bundle = await client.fetchPortableAgent(
462
+ await transport(),
463
+ versionId ? { versionId } : { agentId: selection.agentId },
464
+ { variables: selection.variables ?? {}, surface }
465
+ );
466
+ if (file && bundle.definition_hash === definitionHash) {
467
+ await fs3.mkdir(cacheDir, { recursive: true, mode: 448 });
468
+ await fs3.writeFile(file, JSON.stringify(bundle), { mode: 384 });
469
+ }
470
+ return bundle;
471
+ },
472
+ async saveSessionBundle(sessionId, bundle) {
473
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
474
+ await fs3.writeFile(path2.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
475
+ await meta(sessionId);
476
+ },
477
+ readSessionBundle,
478
+ sessionDir,
479
+ async mcpUrlFor(sessionId) {
480
+ if (!mcpBaseUrl) return null;
481
+ const { mcpSecret } = await meta(sessionId);
482
+ secretToSession.set(mcpSecret, sessionId);
483
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
484
+ },
485
+ setMcpBaseUrl(baseUrl) {
486
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
487
+ },
488
+ async handleMcp(secret, body) {
489
+ let handler = handlers.get(secret);
490
+ if (!handler) {
491
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
492
+ if (!sessionId) return null;
493
+ const bundle = await readSessionBundle(sessionId);
494
+ if (!bundle) return null;
495
+ const { conversationId } = await meta(sessionId);
496
+ const { createAgentToolsMcpHandler } = await agentsClient();
497
+ handler = createAgentToolsMcpHandler({
498
+ bundle,
499
+ credentials,
500
+ organizationId: () => organizationId(),
501
+ baseUrl: serverUrl,
502
+ conversationId
503
+ });
504
+ handlers.set(secret, handler);
505
+ }
506
+ return handler.handle(body);
507
+ }
508
+ };
509
+ }
510
+
73
511
  // src/engine/index.ts
74
- import fs6 from "fs/promises";
512
+ import fs8 from "fs/promises";
75
513
  import os5 from "os";
76
- import path7 from "path";
514
+ import path9 from "path";
77
515
  import { fileURLToPath } from "url";
78
516
 
79
517
  // src/normalize/claude.ts
@@ -444,7 +882,7 @@ function rolloutItem(item, fallbackId, at) {
444
882
  const id = str2(item.id) ?? fallbackId;
445
883
  switch (item.type) {
446
884
  case "UserMessage": {
447
- const text = stripCodexInjectedContext(itemText(item.content));
885
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
448
886
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
449
887
  }
450
888
  case "AgentMessage": {
@@ -527,7 +965,7 @@ function createCodexRolloutNormalizer() {
527
965
  return rolloutItem(payload.item, fallbackId, at);
528
966
  }
529
967
  if (payload.type === "user_message" && !appItems) {
530
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
968
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
531
969
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
532
970
  }
533
971
  return [];
@@ -743,7 +1181,7 @@ function describeProblem(provider, problem) {
743
1181
  import { execFile } from "child_process";
744
1182
  import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
745
1183
  import os from "os";
746
- import path from "path";
1184
+ import path3 from "path";
747
1185
  var SOURCE_LABEL = {
748
1186
  codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
749
1187
  claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
@@ -792,10 +1230,10 @@ function exeName(name, platform) {
792
1230
  }
793
1231
  function allOnPath(name, pathValue, platform = process.platform) {
794
1232
  const found = [];
795
- for (const dir of pathValue.split(path.delimiter)) {
1233
+ for (const dir of pathValue.split(path3.delimiter)) {
796
1234
  if (!dir) continue;
797
1235
  for (const file of exeName(name, platform)) {
798
- const candidate = path.join(dir, file);
1236
+ const candidate = path3.join(dir, file);
799
1237
  if (executable(candidate)) {
800
1238
  found.push(candidate);
801
1239
  break;
@@ -808,17 +1246,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
808
1246
  if (platform !== "darwin") return [];
809
1247
  if (provider === "codex") {
810
1248
  return applications.map(
811
- (apps) => path.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1249
+ (apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
812
1250
  );
813
1251
  }
814
- const root = path.join(home, "Library", "Application Support", "Claude", "claude-code");
1252
+ const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
815
1253
  let versions = [];
816
1254
  try {
817
1255
  versions = readdirSync(root);
818
1256
  } catch {
819
1257
  return [];
820
1258
  }
821
- return versions.map((v) => path.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1259
+ return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
822
1260
  }
823
1261
  function npmGlobalPrefix(env) {
824
1262
  const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
@@ -836,13 +1274,13 @@ function declaredVersion(provider, source, file) {
836
1274
  if (source !== "desktop-app") return null;
837
1275
  if (provider === "codex") {
838
1276
  try {
839
- const pkg = JSON.parse(readFileSync(path.join(path.dirname(path.dirname(file)), "codex-package.json"), "utf8"));
1277
+ const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
840
1278
  return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
841
1279
  } catch {
842
1280
  return null;
843
1281
  }
844
1282
  }
845
- return parseVersion(path.basename(path.resolve(file, "..", "..", "..", "..")));
1283
+ return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
846
1284
  }
847
1285
  function runVersion(file, env) {
848
1286
  return new Promise((resolve) => {
@@ -856,14 +1294,14 @@ async function discoverBinaries(provider, options) {
856
1294
  const platform = options.platform ?? process.platform;
857
1295
  const home = options.home ?? options.env.HOME ?? os.homedir();
858
1296
  const listed2 = [
859
- ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1297
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
860
1298
  ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
861
1299
  ];
862
1300
  const prefix = await npmGlobalPrefix(options.env);
863
1301
  if (prefix) {
864
- const dir = platform === "win32" ? prefix : path.join(prefix, "bin");
1302
+ const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
865
1303
  for (const file of exeName(provider, platform)) {
866
- const candidate = path.join(dir, file);
1304
+ const candidate = path3.join(dir, file);
867
1305
  if (executable(candidate)) {
868
1306
  listed2.push({ path: candidate, source: "npm-global" });
869
1307
  break;
@@ -909,7 +1347,7 @@ async function resolveBinary(provider, options) {
909
1347
 
910
1348
  // src/engine/clean-env.ts
911
1349
  import os2 from "os";
912
- import path2 from "path";
1350
+ import path4 from "path";
913
1351
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
914
1352
  "HOME",
915
1353
  "USER",
@@ -931,11 +1369,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
931
1369
  var ALLOWED_PREFIXES = ["LC_"];
932
1370
  function wellKnownBinDirs(home) {
933
1371
  return [
934
- path2.join(home, ".local", "bin"),
935
- path2.join(home, ".claude", "local"),
936
- path2.join(home, ".npm-global", "bin"),
937
- path2.join(home, ".bun", "bin"),
938
- path2.join(home, ".volta", "bin"),
1372
+ path4.join(home, ".local", "bin"),
1373
+ path4.join(home, ".claude", "local"),
1374
+ path4.join(home, ".npm-global", "bin"),
1375
+ path4.join(home, ".bun", "bin"),
1376
+ path4.join(home, ".volta", "bin"),
939
1377
  "/opt/homebrew/bin",
940
1378
  "/usr/local/bin",
941
1379
  "/usr/bin",
@@ -955,21 +1393,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
955
1393
  }
956
1394
  const home = out.HOME ?? os2.homedir();
957
1395
  out.HOME = home;
958
- const hostPath = (hostEnv.PATH ?? "").split(path2.delimiter).filter(Boolean);
1396
+ const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
959
1397
  const merged = [];
960
1398
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
961
1399
  if (!merged.includes(dir)) merged.push(dir);
962
1400
  }
963
- out.PATH = merged.join(path2.delimiter);
1401
+ out.PATH = merged.join(path4.delimiter);
964
1402
  for (const [name, value] of Object.entries(extra)) out[name] = value;
965
1403
  return out;
966
1404
  }
967
1405
 
968
1406
  // src/engine/files.ts
969
1407
  import { createReadStream } from "fs";
970
- import fs2 from "fs/promises";
1408
+ import fs4 from "fs/promises";
971
1409
  import os3 from "os";
972
- import path3 from "path";
1410
+ import path5 from "path";
973
1411
 
974
1412
  // src/engine/activity.ts
975
1413
  import { execFile as execFile2 } from "child_process";
@@ -1038,15 +1476,15 @@ function describeActivity(input) {
1038
1476
  // src/engine/files.ts
1039
1477
  function defaultProviderHomes(home = os3.homedir()) {
1040
1478
  return {
1041
- claudeProjects: path3.join(home, ".claude", "projects"),
1042
- codexSessions: path3.join(home, ".codex", "sessions"),
1043
- codexIndex: path3.join(home, ".codex", "session_index.jsonl")
1479
+ claudeProjects: path5.join(home, ".claude", "projects"),
1480
+ codexSessions: path5.join(home, ".codex", "sessions"),
1481
+ codexIndex: path5.join(home, ".codex", "session_index.jsonl")
1044
1482
  };
1045
1483
  }
1046
1484
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
1047
1485
  async function safeReaddir(dir) {
1048
1486
  try {
1049
- return await fs2.readdir(dir, { withFileTypes: true });
1487
+ return await fs4.readdir(dir, { withFileTypes: true });
1050
1488
  } catch {
1051
1489
  return [];
1052
1490
  }
@@ -1055,7 +1493,7 @@ async function statCandidate(provider, file) {
1055
1493
  const match = UUIDISH.exec(file);
1056
1494
  if (!match?.[1]) return null;
1057
1495
  try {
1058
- const stat = await fs2.stat(file);
1496
+ const stat = await fs4.stat(file);
1059
1497
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
1060
1498
  } catch {
1061
1499
  return null;
@@ -1065,10 +1503,10 @@ async function claudeCandidates(root) {
1065
1503
  const out = [];
1066
1504
  for (const project of await safeReaddir(root)) {
1067
1505
  if (!project.isDirectory()) continue;
1068
- const dir = path3.join(root, project.name);
1506
+ const dir = path5.join(root, project.name);
1069
1507
  const entries = await safeReaddir(dir);
1070
1508
  const stats = await Promise.all(
1071
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path3.join(dir, e.name)))
1509
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path5.join(dir, e.name)))
1072
1510
  );
1073
1511
  for (const s of stats) if (s) out.push(s);
1074
1512
  }
@@ -1078,14 +1516,14 @@ async function codexCandidates(root) {
1078
1516
  const out = [];
1079
1517
  for (const year of await safeReaddir(root)) {
1080
1518
  if (!year.isDirectory()) continue;
1081
- for (const month of await safeReaddir(path3.join(root, year.name))) {
1519
+ for (const month of await safeReaddir(path5.join(root, year.name))) {
1082
1520
  if (!month.isDirectory()) continue;
1083
- for (const day of await safeReaddir(path3.join(root, year.name, month.name))) {
1521
+ for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
1084
1522
  if (!day.isDirectory()) continue;
1085
- const dir = path3.join(root, year.name, month.name, day.name);
1523
+ const dir = path5.join(root, year.name, month.name, day.name);
1086
1524
  const entries = await safeReaddir(dir);
1087
1525
  const stats = await Promise.all(
1088
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path3.join(dir, e.name)))
1526
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
1089
1527
  );
1090
1528
  for (const s of stats) if (s) out.push(s);
1091
1529
  }
@@ -1094,7 +1532,7 @@ async function codexCandidates(root) {
1094
1532
  return out;
1095
1533
  }
1096
1534
  async function readSlice(file, start, length) {
1097
- const handle = await fs2.open(file, "r");
1535
+ const handle = await fs4.open(file, "r");
1098
1536
  try {
1099
1537
  const buffer = Buffer.alloc(length);
1100
1538
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1167,9 +1605,9 @@ async function readCodexIndex(file) {
1167
1605
  const names = /* @__PURE__ */ new Map();
1168
1606
  let text;
1169
1607
  try {
1170
- const stat = await fs2.stat(file);
1608
+ const stat = await fs4.stat(file);
1171
1609
  const budget = 4 * 1024 * 1024;
1172
- text = stat.size <= budget ? await fs2.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1610
+ text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1173
1611
  } catch {
1174
1612
  return names;
1175
1613
  }
@@ -1290,7 +1728,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1290
1728
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1291
1729
  let size;
1292
1730
  try {
1293
- size = (await fs2.stat(file)).size;
1731
+ size = (await fs4.stat(file)).size;
1294
1732
  } catch {
1295
1733
  return {
1296
1734
  items: [],
@@ -1344,9 +1782,9 @@ async function describeSessionFile(candidate, codexNames) {
1344
1782
  }
1345
1783
 
1346
1784
  // src/engine/preferences.ts
1347
- import { randomUUID } from "crypto";
1348
- import fs3 from "fs/promises";
1349
- import path4 from "path";
1785
+ import { randomUUID as randomUUID2 } from "crypto";
1786
+ import fs5 from "fs/promises";
1787
+ import path6 from "path";
1350
1788
  var PreferenceStore = class {
1351
1789
  constructor(dir) {
1352
1790
  this.dir = dir;
@@ -1355,11 +1793,11 @@ var PreferenceStore = class {
1355
1793
  data = {};
1356
1794
  writing = Promise.resolve();
1357
1795
  get file() {
1358
- return path4.join(this.dir, "preferences.json");
1796
+ return path6.join(this.dir, "preferences.json");
1359
1797
  }
1360
1798
  async load() {
1361
1799
  try {
1362
- const parsed = JSON.parse(await fs3.readFile(this.file, "utf8"));
1800
+ const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
1363
1801
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1364
1802
  } catch {
1365
1803
  this.data = {};
@@ -1397,10 +1835,10 @@ var PreferenceStore = class {
1397
1835
  persist() {
1398
1836
  const snapshot = JSON.stringify(this.data, null, 1);
1399
1837
  this.writing = this.writing.then(async () => {
1400
- await fs3.mkdir(this.dir, { recursive: true });
1401
- const tmp = `${this.file}.${randomUUID()}.tmp`;
1402
- await fs3.writeFile(tmp, snapshot, { mode: 384 });
1403
- await fs3.rename(tmp, this.file);
1838
+ await fs5.mkdir(this.dir, { recursive: true });
1839
+ const tmp = `${this.file}.${randomUUID2()}.tmp`;
1840
+ await fs5.writeFile(tmp, snapshot, { mode: 384 });
1841
+ await fs5.rename(tmp, this.file);
1404
1842
  }).catch(() => {
1405
1843
  });
1406
1844
  }
@@ -1557,9 +1995,9 @@ async function searchLocalChats(query, options = {}) {
1557
1995
  }
1558
1996
 
1559
1997
  // src/engine/store.ts
1560
- import { randomUUID as randomUUID2 } from "crypto";
1561
- import fs4 from "fs/promises";
1562
- import path5 from "path";
1998
+ import { randomUUID as randomUUID3 } from "crypto";
1999
+ import fs6 from "fs/promises";
2000
+ import path7 from "path";
1563
2001
  var MAX_RUNS = 2e3;
1564
2002
  var RunIndex = class {
1565
2003
  constructor(dir) {
@@ -1569,12 +2007,12 @@ var RunIndex = class {
1569
2007
  data = { version: 1, sessions: [], runs: [] };
1570
2008
  writing = Promise.resolve();
1571
2009
  get file() {
1572
- return path5.join(this.dir, "index.json");
2010
+ return path7.join(this.dir, "index.json");
1573
2011
  }
1574
2012
  async load() {
1575
- await fs4.mkdir(this.dir, { recursive: true });
2013
+ await fs6.mkdir(this.dir, { recursive: true });
1576
2014
  try {
1577
- const parsed = JSON.parse(await fs4.readFile(this.file, "utf8"));
2015
+ const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
1578
2016
  this.data = {
1579
2017
  version: 1,
1580
2018
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1616,9 +2054,9 @@ var RunIndex = class {
1616
2054
  persist() {
1617
2055
  const snapshot = JSON.stringify(this.data, null, 1);
1618
2056
  this.writing = this.writing.then(async () => {
1619
- const tmp = `${this.file}.${randomUUID2()}.tmp`;
1620
- await fs4.writeFile(tmp, snapshot, { mode: 384 });
1621
- await fs4.rename(tmp, this.file);
2057
+ const tmp = `${this.file}.${randomUUID3()}.tmp`;
2058
+ await fs6.writeFile(tmp, snapshot, { mode: 384 });
2059
+ await fs6.rename(tmp, this.file);
1622
2060
  }).catch(() => {
1623
2061
  });
1624
2062
  }
@@ -1628,27 +2066,27 @@ var RunIndex = class {
1628
2066
  /** A stable Paperclip agent/company identity for this install, so the
1629
2067
  * driver's scratch state is reused rather than growing per run. */
1630
2068
  async identity() {
1631
- const file = path5.join(this.dir, "identity.json");
2069
+ const file = path7.join(this.dir, "identity.json");
1632
2070
  try {
1633
- const parsed = JSON.parse(await fs4.readFile(file, "utf8"));
2071
+ const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
1634
2072
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1635
2073
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1636
2074
  }
1637
2075
  } catch {
1638
2076
  }
1639
- const identity = { companyId: randomUUID2(), agentId: randomUUID2() };
1640
- await fs4.writeFile(file, JSON.stringify(identity), { mode: 384 });
2077
+ const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
2078
+ await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
1641
2079
  return identity;
1642
2080
  }
1643
2081
  };
1644
2082
 
1645
2083
  // src/engine/user-defaults.ts
1646
- import fs5 from "fs/promises";
2084
+ import fs7 from "fs/promises";
1647
2085
  import os4 from "os";
1648
- import path6 from "path";
2086
+ import path8 from "path";
1649
2087
  async function readJson(file) {
1650
2088
  try {
1651
- const parsed = JSON.parse(await fs5.readFile(file, "utf8"));
2089
+ const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
1652
2090
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1653
2091
  } catch {
1654
2092
  return null;
@@ -1659,8 +2097,8 @@ function str3(value) {
1659
2097
  }
1660
2098
  async function claudeDefaults(cwd, home = os4.homedir()) {
1661
2099
  const files = [
1662
- ...cwd ? [path6.join(cwd, ".claude", "settings.local.json"), path6.join(cwd, ".claude", "settings.json")] : [],
1663
- path6.join(home, ".claude", "settings.json")
2100
+ ...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
2101
+ path8.join(home, ".claude", "settings.json")
1664
2102
  ];
1665
2103
  let model = null;
1666
2104
  let effort = null;
@@ -1676,7 +2114,7 @@ async function claudeDefaults(cwd, home = os4.homedir()) {
1676
2114
  async function codexDefaults(home = os4.homedir()) {
1677
2115
  let text;
1678
2116
  try {
1679
- text = await fs5.readFile(path6.join(home, ".codex", "config.toml"), "utf8");
2117
+ text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
1680
2118
  } catch {
1681
2119
  return { model: null, effort: null };
1682
2120
  }
@@ -1700,31 +2138,31 @@ var EFFORT_LEVELS = {
1700
2138
  // src/engine/index.ts
1701
2139
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1702
2140
  function moduleDir() {
1703
- return path7.dirname(fileURLToPath(import.meta.url));
2141
+ return path9.dirname(fileURLToPath(import.meta.url));
1704
2142
  }
1705
2143
  function resolveWorkerPath() {
1706
2144
  const dir = moduleDir();
1707
2145
  for (const candidate of [
1708
- path7.join(dir, "worker.js"),
1709
- path7.join(dir, "worker.ts"),
1710
- path7.join(dir, "..", "engine", "worker.js"),
1711
- path7.join(dir, "..", "engine", "worker.ts")
2146
+ path9.join(dir, "worker.js"),
2147
+ path9.join(dir, "worker.ts"),
2148
+ path9.join(dir, "..", "engine", "worker.js"),
2149
+ path9.join(dir, "..", "engine", "worker.ts")
1712
2150
  ]) {
1713
2151
  if (existsSync(candidate)) return candidate;
1714
2152
  }
1715
2153
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1716
2154
  }
1717
2155
  async function installShim(dataDir) {
1718
- const source = path7.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2156
+ const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1719
2157
  if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1720
- const body = await fs6.readFile(source, "utf8");
1721
- const target = path7.join(dataDir, "bin", "cli-shim.sh");
1722
- const current = await fs6.readFile(target, "utf8").catch(() => null);
2158
+ const body = await fs8.readFile(source, "utf8");
2159
+ const target = path9.join(dataDir, "bin", "cli-shim.sh");
2160
+ const current = await fs8.readFile(target, "utf8").catch(() => null);
1723
2161
  if (current !== body) {
1724
- await fs6.mkdir(path7.dirname(target), { recursive: true });
1725
- await fs6.writeFile(target, body, { mode: 493 });
2162
+ await fs8.mkdir(path9.dirname(target), { recursive: true });
2163
+ await fs8.writeFile(target, body, { mode: 493 });
1726
2164
  }
1727
- await fs6.chmod(target, 493);
2165
+ await fs8.chmod(target, 493);
1728
2166
  return target;
1729
2167
  }
1730
2168
  function nowIso() {
@@ -1824,29 +2262,30 @@ function describeAuthStatus(provider, probe) {
1824
2262
  };
1825
2263
  }
1826
2264
  async function createCodingSessionsEngine(options = {}) {
1827
- const dataDir = options.dataDir ?? path7.join(os5.homedir(), ".matrx", "coding-sessions");
1828
- const stateDir = path7.join(dataDir, "paperclip-state");
2265
+ const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
2266
+ const stateDir = path9.join(dataDir, "paperclip-state");
1829
2267
  const homes = options.homes ?? defaultProviderHomes();
1830
2268
  const index = new RunIndex(dataDir);
1831
2269
  await index.load();
1832
2270
  const preferences = new PreferenceStore(dataDir);
1833
2271
  await preferences.load();
1834
- await fs6.mkdir(stateDir, { recursive: true });
2272
+ await fs8.mkdir(stateDir, { recursive: true });
1835
2273
  const identity = await index.identity();
2274
+ const agents = createAgentConnection(options.agents, dataDir);
1836
2275
  const backups = options.rawBackup ? createRawBackupScheduler({
1837
2276
  sink: options.rawBackup,
1838
2277
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1839
2278
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1840
2279
  }) : null;
1841
2280
  const discoveredSeen = /* @__PURE__ */ new Map();
1842
- const healthFile = path7.join(dataDir, "provider-health.json");
2281
+ const healthFile = path9.join(dataDir, "provider-health.json");
1843
2282
  let health = {};
1844
2283
  try {
1845
- health = JSON.parse(await fs6.readFile(healthFile, "utf8"));
2284
+ health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
1846
2285
  } catch {
1847
2286
  health = {};
1848
2287
  }
1849
- const saveHealth = () => fs6.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2288
+ const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1850
2289
  function noteRunOutcome(provider, status, texts) {
1851
2290
  if (status === "completed") {
1852
2291
  if (health[provider]) {
@@ -1963,7 +2402,7 @@ async function createCodingSessionsEngine(options = {}) {
1963
2402
  async function startRun(session, prompt) {
1964
2403
  const provider = session.provider;
1965
2404
  const realCommand = await commandFor(provider);
1966
- const runId = randomUUID3();
2405
+ const runId = randomUUID4();
1967
2406
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1968
2407
  const run = {
1969
2408
  runId,
@@ -2001,7 +2440,36 @@ async function createCodingSessionsEngine(options = {}) {
2001
2440
  let fileOffset = null;
2002
2441
  if (session.providerSessionId) {
2003
2442
  const file = await findSessionFile(provider, session.providerSessionId, homes);
2004
- if (file) fileOffset = (await fs6.stat(file).catch(() => null))?.size ?? null;
2443
+ if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
2444
+ }
2445
+ let agentArgs = [];
2446
+ let cliPrompt = prompt;
2447
+ let delivered = null;
2448
+ if (!session.agent && session.agentRemovedDue) {
2449
+ if (provider === "codex") cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
2450
+ delivered = { agentRemovedDue: false };
2451
+ }
2452
+ if (session.agent) {
2453
+ const bundle = await agents.readSessionBundle(session.id);
2454
+ if (!bundle) {
2455
+ const failed = saveRun({
2456
+ ...run,
2457
+ status: "failed",
2458
+ endedAt: nowIso(),
2459
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2460
+ });
2461
+ const current = index.session(session.id);
2462
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2463
+ return failed;
2464
+ }
2465
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2466
+ mcpUrl: await agents.mcpUrlFor(session.id)
2467
+ });
2468
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2469
+ if (provider === "codex" && session.agent.blockDue) {
2470
+ cliPrompt = `${renderFallbackBlock(bundle, path9.join(attachment.dir, "skills"))}${prompt}`;
2471
+ }
2472
+ delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
2005
2473
  }
2006
2474
  const child = forkWorker();
2007
2475
  const liveRun = {
@@ -2017,6 +2485,10 @@ async function createCodingSessionsEngine(options = {}) {
2017
2485
  };
2018
2486
  live.set(runId, liveRun);
2019
2487
  emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
2488
+ if (delivered) {
2489
+ const current = index.session(session.id);
2490
+ if (current) saveSession({ ...current, ...delivered });
2491
+ }
2020
2492
  const splitter = createLineSplitter();
2021
2493
  const claude = provider === "claude" ? createClaudeNormalizer() : null;
2022
2494
  const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
@@ -2055,6 +2527,7 @@ async function createCodingSessionsEngine(options = {}) {
2055
2527
  }
2056
2528
  }
2057
2529
  void pruneDriverScratch();
2530
+ if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
2058
2531
  };
2059
2532
  let settled = false;
2060
2533
  child.on("message", (raw) => {
@@ -2169,16 +2642,39 @@ async function createCodingSessionsEngine(options = {}) {
2169
2642
  provider,
2170
2643
  runId,
2171
2644
  cwd: session.cwd,
2172
- prompt,
2645
+ prompt: cliPrompt,
2173
2646
  model,
2174
2647
  effort: session.effort ?? null,
2175
2648
  resumeSessionId: session.providerSessionId,
2176
2649
  shimPath,
2177
2650
  realCommand,
2178
- identity
2651
+ identity,
2652
+ agentArgs
2179
2653
  });
2180
2654
  return run;
2181
2655
  }
2656
+ async function checkCompaction(sessionId) {
2657
+ const session = index.session(sessionId);
2658
+ if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
2659
+ const file = await findSessionFile("codex", session.providerSessionId, homes);
2660
+ if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
2661
+ const current = index.session(sessionId);
2662
+ if (current?.agent) {
2663
+ saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
2664
+ }
2665
+ }
2666
+ async function attachAgent(sessionId, selection, options2) {
2667
+ const bundle = await agents.loadBundle(selection);
2668
+ await agents.saveSessionBundle(sessionId, bundle);
2669
+ return {
2670
+ ...sessionAgentRecord(bundle, {
2671
+ toolsServed: await agents.mcpUrlFor(sessionId) !== null,
2672
+ variables: selection.variables ?? {}
2673
+ }),
2674
+ blockDue: options2.blockDue,
2675
+ deliveredAt: null
2676
+ };
2677
+ }
2182
2678
  function killGroup(liveRun) {
2183
2679
  const target = liveRun.pgid;
2184
2680
  if (target === null) return;
@@ -2194,7 +2690,7 @@ async function createCodingSessionsEngine(options = {}) {
2194
2690
  }, 3e3);
2195
2691
  }
2196
2692
  async function pruneDriverScratch() {
2197
- const runsDir = path7.join(
2693
+ const runsDir = path9.join(
2198
2694
  stateDir,
2199
2695
  "instances",
2200
2696
  "default",
@@ -2205,9 +2701,9 @@ async function createCodingSessionsEngine(options = {}) {
2205
2701
  "claude-runtime",
2206
2702
  "runs"
2207
2703
  );
2208
- const entries = await fs6.readdir(runsDir).catch(() => []);
2704
+ const entries = await fs8.readdir(runsDir).catch(() => []);
2209
2705
  await Promise.all(
2210
- entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path7.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2706
+ entries.filter((name) => !live.has(name)).map((name) => fs8.rm(path9.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2211
2707
  );
2212
2708
  }
2213
2709
  async function probe() {
@@ -2248,7 +2744,7 @@ async function createCodingSessionsEngine(options = {}) {
2248
2744
  function newSession(input) {
2249
2745
  const at = nowIso();
2250
2746
  return {
2251
- id: randomUUID3(),
2747
+ id: randomUUID4(),
2252
2748
  provider: input.provider,
2253
2749
  providerSessionId: input.providerSessionId,
2254
2750
  cwd: input.cwd,
@@ -2266,17 +2762,17 @@ async function createCodingSessionsEngine(options = {}) {
2266
2762
  function resolveFolder(raw) {
2267
2763
  const trimmed = raw.trim();
2268
2764
  if (!trimmed) return null;
2269
- return path7.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2765
+ return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2270
2766
  }
2271
2767
  async function checkFolder(raw) {
2272
2768
  const target = resolveFolder(raw);
2273
2769
  if (!target) return { path: "", exists: false, isDirectory: false };
2274
- const stat = await fs6.stat(target).catch(() => null);
2770
+ const stat = await fs8.stat(target).catch(() => null);
2275
2771
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2276
2772
  }
2277
2773
  async function assertFolder(cwd) {
2278
- const resolved = path7.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2279
- const stat = await fs6.stat(resolved).catch(() => null);
2774
+ const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2775
+ const stat = await fs8.stat(resolved).catch(() => null);
2280
2776
  if (!stat?.isDirectory()) {
2281
2777
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2282
2778
  }
@@ -2338,7 +2834,7 @@ async function createCodingSessionsEngine(options = {}) {
2338
2834
  folders.map(async ([folder, lastUsedAt]) => ({
2339
2835
  path: folder,
2340
2836
  lastUsedAt,
2341
- exists: (await fs6.stat(folder).catch(() => null))?.isDirectory() === true
2837
+ exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
2342
2838
  }))
2343
2839
  );
2344
2840
  },
@@ -2383,7 +2879,7 @@ async function createCodingSessionsEngine(options = {}) {
2383
2879
  async createFolder(raw) {
2384
2880
  const target = resolveFolder(raw);
2385
2881
  if (!target) throw new Error("Type a folder path first.");
2386
- await fs6.mkdir(target, { recursive: true });
2882
+ await fs8.mkdir(target, { recursive: true });
2387
2883
  return checkFolder(target);
2388
2884
  },
2389
2885
  async sessionActivity(provider, providerSessionId) {
@@ -2421,48 +2917,63 @@ async function createCodingSessionsEngine(options = {}) {
2421
2917
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2422
2918
  };
2423
2919
  }
2424
- const stat = await fs6.stat(file);
2920
+ const stat = await fs8.stat(file);
2425
2921
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2426
2922
  return {
2427
2923
  available: false,
2428
2924
  reason: `This transcript is ${formatFileSize(stat.size)}, larger than the ${formatFileSize(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
2429
2925
  };
2430
2926
  }
2431
- const content = await fs6.readFile(file, "utf8");
2432
- return { available: true, fileName: path7.basename(file), bytes: stat.size, content };
2927
+ const content = await fs8.readFile(file, "utf8");
2928
+ return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
2433
2929
  },
2434
2930
  async start(input) {
2435
2931
  const prompt = input.prompt.trim();
2436
2932
  if (!prompt) throw new Error("Type what you want done before starting.");
2437
2933
  const cwd = await assertFolder(input.cwd);
2438
- const session = saveSession(
2439
- newSession({
2440
- provider: input.provider,
2441
- cwd,
2442
- title: titleFrom(prompt),
2443
- model: input.model?.trim() || null,
2444
- effort: input.effort?.trim() || null,
2445
- origin: "started",
2446
- providerSessionId: null
2447
- })
2448
- );
2934
+ const draft = newSession({
2935
+ provider: input.provider,
2936
+ cwd,
2937
+ title: titleFrom(prompt),
2938
+ model: input.model?.trim() || null,
2939
+ effort: input.effort?.trim() || null,
2940
+ origin: "started",
2941
+ providerSessionId: null
2942
+ });
2943
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
2944
+ const session = saveSession(draft);
2449
2945
  const run = await startRun(session, prompt);
2450
2946
  return { session: index.session(session.id) ?? session, run, queued: false };
2451
2947
  },
2452
2948
  async takeover(input) {
2453
2949
  const existing = index.session(input.providerSessionId);
2454
- if (existing) return existing;
2950
+ if (existing) {
2951
+ if (!input.agent) return existing;
2952
+ return engine.setAgent(existing.id, input.agent);
2953
+ }
2455
2954
  const cwd = await assertFolder(input.cwd);
2456
- return saveSession(
2457
- newSession({
2458
- provider: input.provider,
2459
- cwd,
2460
- title: input.title?.trim() || "Taken-over chat",
2461
- model: null,
2462
- origin: "takeover",
2463
- providerSessionId: input.providerSessionId
2464
- })
2465
- );
2955
+ const draft = newSession({
2956
+ provider: input.provider,
2957
+ cwd,
2958
+ title: input.title?.trim() || "Taken-over chat",
2959
+ model: null,
2960
+ origin: "takeover",
2961
+ providerSessionId: input.providerSessionId
2962
+ });
2963
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
2964
+ return saveSession(draft);
2965
+ },
2966
+ async setAgent(sessionId, selection) {
2967
+ const session = index.session(String(sessionId ?? ""));
2968
+ if (!session) throw new Error("This session is not in the list anymore.");
2969
+ if (!selection) {
2970
+ const told = session.provider === "codex" && session.agent?.deliveredAt != null;
2971
+ return saveSession({ ...session, agent: null, agentRemovedDue: told });
2972
+ }
2973
+ const agent = await attachAgent(session.id, selection, {
2974
+ blockDue: session.provider === "codex" && session.providerSessionId !== null
2975
+ });
2976
+ return saveSession({ ...session, agent, agentRemovedDue: false });
2466
2977
  },
2467
2978
  async send(sessionId, rawPrompt) {
2468
2979
  const prompt = rawPrompt.trim();
@@ -2514,6 +3025,21 @@ async function createCodingSessionsEngine(options = {}) {
2514
3025
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2515
3026
  return saveSession(next);
2516
3027
  },
3028
+ async agentAccess() {
3029
+ return agents.access();
3030
+ },
3031
+ async agentCatalogRpc(request) {
3032
+ return agents.catalogRpc(request);
3033
+ },
3034
+ async agentDetails(agentId) {
3035
+ return agents.details(String(agentId ?? ""));
3036
+ },
3037
+ setMcpBaseUrl(baseUrl) {
3038
+ agents.setMcpBaseUrl(baseUrl);
3039
+ },
3040
+ async handleMcp(secret, body) {
3041
+ return agents.handleMcp(secret, body);
3042
+ },
2517
3043
  async dispose() {
2518
3044
  for (const liveRun of live.values()) {
2519
3045
  liveRun.stopRequested = true;
@@ -2583,7 +3109,11 @@ var BRIDGE_METHODS = [
2583
3109
  "setChatPreference",
2584
3110
  "searchChats",
2585
3111
  "openFolder",
2586
- "setSessionOptions"
3112
+ "setSessionOptions",
3113
+ "agentAccess",
3114
+ "agentCatalogRpc",
3115
+ "agentDetails",
3116
+ "setAgent"
2587
3117
  ];
2588
3118
  var BridgeError = class extends Error {
2589
3119
  constructor(message, code) {
@@ -2627,14 +3157,14 @@ var MIME = {
2627
3157
  ".map": "application/json"
2628
3158
  };
2629
3159
  function moduleDir2() {
2630
- return path8.dirname(fileURLToPath2(import.meta.url));
3160
+ return path10.dirname(fileURLToPath2(import.meta.url));
2631
3161
  }
2632
3162
  function defaultUiDir() {
2633
3163
  for (const candidate of [
2634
- path8.resolve(moduleDir2(), "..", "ui"),
2635
- path8.resolve(moduleDir2(), "..", "..", "dist", "ui")
3164
+ path10.resolve(moduleDir2(), "..", "ui"),
3165
+ path10.resolve(moduleDir2(), "..", "..", "dist", "ui")
2636
3166
  ]) {
2637
- if (existsSync2(path8.join(candidate, "index.html"))) return candidate;
3167
+ if (existsSync2(path10.join(candidate, "index.html"))) return candidate;
2638
3168
  }
2639
3169
  return null;
2640
3170
  }
@@ -2657,19 +3187,19 @@ async function serveStatic(uiDir, req, res) {
2657
3187
  return;
2658
3188
  }
2659
3189
  const requested = decodeURIComponent(url.pathname);
2660
- let file = path8.resolve(uiDir, `.${requested}`);
3190
+ let file = path10.resolve(uiDir, `.${requested}`);
2661
3191
  if (!file.startsWith(uiDir)) {
2662
3192
  res.writeHead(403).end();
2663
3193
  return;
2664
3194
  }
2665
- const stat = await fs7.stat(file).catch(() => null);
2666
- if (!stat || stat.isDirectory()) file = path8.join(uiDir, "index.html");
2667
- const body = await fs7.readFile(file).catch(() => null);
3195
+ const stat = await fs9.stat(file).catch(() => null);
3196
+ if (!stat || stat.isDirectory()) file = path10.join(uiDir, "index.html");
3197
+ const body = await fs9.readFile(file).catch(() => null);
2668
3198
  if (!body) {
2669
3199
  res.writeHead(404).end();
2670
3200
  return;
2671
3201
  }
2672
- const ext = path8.extname(file);
3202
+ const ext = path10.extname(file);
2673
3203
  res.writeHead(200, {
2674
3204
  "content-type": MIME[ext] ?? "application/octet-stream",
2675
3205
  "cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
@@ -2678,6 +3208,46 @@ async function serveStatic(uiDir, req, res) {
2678
3208
  });
2679
3209
  res.end(body);
2680
3210
  }
3211
+ var MAX_MCP_BODY = 4 * 1024 * 1024;
3212
+ async function handleMcp(engine, secret, req, res, allowedOrigin) {
3213
+ if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
3214
+ res.writeHead(403).end();
3215
+ return;
3216
+ }
3217
+ if (req.method !== "POST") {
3218
+ res.writeHead(405, { allow: "POST" }).end();
3219
+ return;
3220
+ }
3221
+ const chunks = [];
3222
+ let size = 0;
3223
+ for await (const chunk of req) {
3224
+ size += chunk.length;
3225
+ if (size > MAX_MCP_BODY) {
3226
+ res.writeHead(413).end();
3227
+ return;
3228
+ }
3229
+ chunks.push(chunk);
3230
+ }
3231
+ let body;
3232
+ try {
3233
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3234
+ } catch {
3235
+ res.writeHead(400, { "content-type": "application/json" });
3236
+ res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
3237
+ return;
3238
+ }
3239
+ const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
3240
+ if (!reply) {
3241
+ res.writeHead(404).end();
3242
+ return;
3243
+ }
3244
+ if (reply.body === void 0) {
3245
+ res.writeHead(reply.status).end();
3246
+ return;
3247
+ }
3248
+ res.writeHead(reply.status, { "content-type": "application/json" });
3249
+ res.end(JSON.stringify(reply.body));
3250
+ }
2681
3251
  function attachSocket(socket, bridge) {
2682
3252
  const unsubscribe = bridge.subscribe((event) => {
2683
3253
  if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
@@ -2708,7 +3278,7 @@ function attachSocket(socket, bridge) {
2708
3278
  socket.on("close", unsubscribe);
2709
3279
  }
2710
3280
  async function startCodingSessionsHost(options = {}) {
2711
- const token = options.token ?? randomBytes(24).toString("base64url");
3281
+ const token = options.token ?? randomBytes2(24).toString("base64url");
2712
3282
  const engine = options.engine ?? await createCodingSessionsEngine(options);
2713
3283
  const bridge = createHostBridge(engine);
2714
3284
  const uiDir = options.uiDir ?? defaultUiDir();
@@ -2719,6 +3289,13 @@ async function startCodingSessionsHost(options = {}) {
2719
3289
  };
2720
3290
  const server = http.createServer((req, res) => {
2721
3291
  const url2 = new URL(req.url ?? "/", `http://${HOST}`);
3292
+ if (url2.pathname.startsWith("/mcp/")) {
3293
+ handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
3294
+ if (!res.headersSent) res.writeHead(500);
3295
+ res.end();
3296
+ });
3297
+ return;
3298
+ }
2722
3299
  if (url2.pathname === "/health") {
2723
3300
  res.writeHead(200, { "content-type": "application/json" });
2724
3301
  res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
@@ -2749,6 +3326,7 @@ async function startCodingSessionsHost(options = {}) {
2749
3326
  server.off("error", reject);
2750
3327
  const address = server.address();
2751
3328
  if (address && typeof address === "object") port = address.port;
3329
+ engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
2752
3330
  resolve();
2753
3331
  });
2754
3332
  });
@@ -2759,6 +3337,7 @@ async function startCodingSessionsHost(options = {}) {
2759
3337
  token,
2760
3338
  engine,
2761
3339
  async close() {
3340
+ engine.setMcpBaseUrl?.(null);
2762
3341
  for (const client of wss.clients) client.terminate();
2763
3342
  await new Promise((resolve) => wss.close(() => resolve()));
2764
3343
  await new Promise((resolve) => server.close(() => resolve()));