@ai-matrx/coding-sessions 0.1.83 → 0.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,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,379 @@ function createRawBackupScheduler(options) {
114
114
  };
115
115
  }
116
116
 
117
+ // src/engine/agent-attach.ts
118
+ var import_promises2 = __toESM(require("fs/promises"), 1);
119
+ var import_node_path = __toESM(require("path"), 1);
120
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
121
+ var AGENT_PLUGIN_NAME = "matrx-agent";
122
+ function escapeAttr(value) {
123
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
124
+ }
125
+ function versionWords(bundle) {
126
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
127
+ }
128
+ function renderAgentInstructions(bundle, options = {}) {
129
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
130
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
131
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
132
+ if (included.length > 0) {
133
+ parts.push(
134
+ "## Skills (always in effect)",
135
+ ...included.map((skill) => `### ${skill.label}
136
+
137
+ ${skill.body.trim()}`)
138
+ );
139
+ }
140
+ if (options.claudeSkills && listed2.length > 0) {
141
+ parts.push(
142
+ [
143
+ "## Your skills",
144
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
145
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
146
+ ].join("\n")
147
+ );
148
+ }
149
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
150
+ parts.push(bundle.instructions.trim());
151
+ parts.push("</ai_matrx_agent>");
152
+ return `${parts.filter(Boolean).join("\n\n")}
153
+ `;
154
+ }
155
+ function renderCodexSkillsList(bundle, skillsRoot) {
156
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
157
+ if (listed2.length === 0) return "";
158
+ const lines = [
159
+ "<skills_instructions>",
160
+ "## AI Matrx agent skills",
161
+ "A skill is a set of instructions stored in a `SKILL.md` file. Each entry has a name, a description and the absolute path of its file. When a task matches a skill's description, read that file and follow it.",
162
+ "### Available skills",
163
+ ...listed2.map(
164
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
165
+ ),
166
+ "</skills_instructions>"
167
+ ];
168
+ return lines.join("\n");
169
+ }
170
+ function tomlString(value) {
171
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
172
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
173
+ }
174
+ async function writeAgentAttachment(dir, bundle, options) {
175
+ const pluginDir = import_node_path.default.join(dir, "plugin");
176
+ const skillsRoot = import_node_path.default.join(dir, "skills");
177
+ await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
178
+ await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
179
+ const instructions = renderAgentInstructions(bundle);
180
+ const instructionsFile = import_node_path.default.join(dir, "instructions.md");
181
+ await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
182
+ await import_promises2.default.writeFile(
183
+ import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
184
+ `${JSON.stringify(
185
+ {
186
+ name: AGENT_PLUGIN_NAME,
187
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
188
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
189
+ },
190
+ null,
191
+ 2
192
+ )}
193
+ `,
194
+ { mode: 384 }
195
+ );
196
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
197
+ const wanted = new Set(listed2.map((skill) => skill.slug));
198
+ for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
199
+ await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
200
+ for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
201
+ if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
202
+ }
203
+ for (const skill of listed2) {
204
+ const skillDir = import_node_path.default.join(root, skill.slug);
205
+ await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
206
+ await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
207
+ }
208
+ }
209
+ let mcpConfigFile = null;
210
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
211
+ const mcpPath = import_node_path.default.join(dir, "mcp.json");
212
+ if (mcpUrl) {
213
+ mcpConfigFile = mcpPath;
214
+ await import_promises2.default.writeFile(
215
+ mcpPath,
216
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
217
+ `,
218
+ { mode: 384 }
219
+ );
220
+ } else {
221
+ await import_promises2.default.rm(mcpPath, { force: true });
222
+ }
223
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
224
+ return {
225
+ dir,
226
+ instructionsFile,
227
+ pluginDir,
228
+ mcpConfigFile,
229
+ mcpUrl,
230
+ // Same order as Claude's: the skills list first, the agent's words last.
231
+ codexInstructions: skillsList ? `${skillsList}
232
+
233
+ ${instructions}` : instructions
234
+ };
235
+ }
236
+ function agentArgsFor(provider, attachment, options) {
237
+ if (provider === "claude") {
238
+ const args2 = [
239
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
240
+ "--append-system-prompt-file",
241
+ attachment.instructionsFile,
242
+ "--system-prompt-snapshot",
243
+ "off",
244
+ "--plugin-dir",
245
+ attachment.pluginDir
246
+ ];
247
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
248
+ return args2;
249
+ }
250
+ const args = [];
251
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
252
+ if (attachment.mcpUrl) {
253
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
254
+ args.push(
255
+ "-c",
256
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
257
+ "-c",
258
+ `${key}.default_tools_approval_mode="approve"`
259
+ );
260
+ }
261
+ return args;
262
+ }
263
+ function sessionAgentRecord(bundle, options) {
264
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
265
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
266
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
267
+ if (!options.toolsServed && bundle.tools.length > 0) {
268
+ for (const tool of bundle.tools) {
269
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
270
+ }
271
+ }
272
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
273
+ const loaded = [
274
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
275
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
276
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
277
+ ];
278
+ return {
279
+ agentId: bundle.agent_id,
280
+ versionId: bundle.version_id,
281
+ versionNumber: bundle.version_number,
282
+ name: bundle.name,
283
+ definitionHash: bundle.definition_hash,
284
+ loaded,
285
+ unavailable
286
+ };
287
+ }
288
+
289
+ // src/engine/agents.ts
290
+ var import_node_crypto2 = require("crypto");
291
+ var import_promises3 = __toESM(require("fs/promises"), 1);
292
+ var import_node_path2 = __toESM(require("path"), 1);
293
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
294
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
295
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
296
+ "agx_get_list_full",
297
+ "agx_search",
298
+ "agx_resolve_agent_address",
299
+ "ues_get_bulk",
300
+ "ues_list",
301
+ "ues_set",
302
+ "ues_touch"
303
+ ]);
304
+ var agentsClient = async () => {
305
+ const [matrx, portable, mcp] = await Promise.all([
306
+ import("@ai-matrx/agents/matrx"),
307
+ import("@ai-matrx/agents/portable"),
308
+ import("@ai-matrx/agents/portable/mcp")
309
+ ]);
310
+ return { ...matrx, ...portable, ...mcp };
311
+ };
312
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
313
+ function userIdOf(credential) {
314
+ if (!credential || credential.kind !== "user") return null;
315
+ const payload = credential.accessToken.split(".")[1];
316
+ if (!payload) return null;
317
+ try {
318
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
319
+ return typeof claims.sub === "string" ? claims.sub : null;
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ function createAgentConnection(options, dataDir) {
325
+ const credentials = options?.credentials ?? null;
326
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
327
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
328
+ const supabaseKey = options?.supabaseKey ?? null;
329
+ const root = import_node_path2.default.join(dataDir, "agents");
330
+ let mcpBaseUrl = null;
331
+ const handlers = /* @__PURE__ */ new Map();
332
+ const secretToSession = /* @__PURE__ */ new Map();
333
+ const organizationId = () => {
334
+ const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
335
+ return raw && raw.trim() ? raw.trim() : null;
336
+ };
337
+ const transport = async () => (await agentsClient()).createMatrxTransport({
338
+ baseUrl: serverUrl,
339
+ ...credentials ? { credentials } : {},
340
+ ...organizationId() ? { organizationId: () => organizationId() } : {},
341
+ source: "codingSessionsAgents"
342
+ });
343
+ const credential = async () => credentials ? credentials.get() : null;
344
+ const requireSignedIn = async () => {
345
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
346
+ };
347
+ const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
348
+ async function meta(sessionId) {
349
+ const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
350
+ const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
351
+ if (existing?.mcpSecret && existing.conversationId) return existing;
352
+ const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
353
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
354
+ await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
355
+ return created;
356
+ }
357
+ async function findSessionBySecret(secret) {
358
+ for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
359
+ const found = await import_promises3.default.readFile(import_node_path2.default.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
360
+ if (found) {
361
+ secretToSession.set(secret, name);
362
+ return name;
363
+ }
364
+ }
365
+ return null;
366
+ }
367
+ async function readSessionBundle(sessionId) {
368
+ return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
369
+ }
370
+ return {
371
+ async access() {
372
+ const current = await credential().catch(() => null);
373
+ const userId = userIdOf(current);
374
+ return {
375
+ configured: credentials !== null && supabaseKey !== null,
376
+ signedIn: userId !== null,
377
+ userId,
378
+ organizationId: organizationId()
379
+ };
380
+ },
381
+ async catalogRpc(request) {
382
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
383
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
384
+ }
385
+ const current = await credential();
386
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
387
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
388
+ const query = new URLSearchParams();
389
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
390
+ if (request.range) {
391
+ query.set("offset", String(request.range[0]));
392
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
393
+ }
394
+ const headers = {
395
+ apikey: supabaseKey,
396
+ Authorization: `Bearer ${current.accessToken}`,
397
+ "Content-Type": "application/json",
398
+ // Every catalogue RPC lives in `public`; the database's default
399
+ // exposed schema is not `public`, so name it (supabase-js does).
400
+ "Content-Profile": "public",
401
+ "Accept-Profile": "public"
402
+ };
403
+ if (request.count) headers.Prefer = `count=${request.count}`;
404
+ const qs = query.toString();
405
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
406
+ method: "POST",
407
+ headers,
408
+ body: JSON.stringify(request.args ?? {})
409
+ });
410
+ const text = await response.text();
411
+ const body = text ? JSON.parse(text) : null;
412
+ if (!response.ok) {
413
+ const err = body ?? {};
414
+ return {
415
+ data: null,
416
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
417
+ };
418
+ }
419
+ const range = response.headers.get("content-range");
420
+ const total = range?.split("/")[1];
421
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
422
+ },
423
+ async details(agentId) {
424
+ await requireSignedIn();
425
+ const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
426
+ const [bundle, versions] = await Promise.all([
427
+ fetchPortableAgent(await transport(), { agentId }),
428
+ fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
429
+ ]);
430
+ return {
431
+ agentId: bundle.agent_id,
432
+ name: bundle.name,
433
+ variables: bundle.variables,
434
+ versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
435
+ latestVersionNumber: bundle.version_number
436
+ };
437
+ },
438
+ async loadBundle(selection) {
439
+ await requireSignedIn();
440
+ const { fetchPortableAgent } = await agentsClient();
441
+ return fetchPortableAgent(
442
+ await transport(),
443
+ selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
444
+ { variables: selection.variables ?? {} }
445
+ );
446
+ },
447
+ async saveSessionBundle(sessionId, bundle) {
448
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
449
+ await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
450
+ await meta(sessionId);
451
+ },
452
+ readSessionBundle,
453
+ sessionDir,
454
+ async mcpUrlFor(sessionId) {
455
+ if (!mcpBaseUrl) return null;
456
+ const { mcpSecret } = await meta(sessionId);
457
+ secretToSession.set(mcpSecret, sessionId);
458
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
459
+ },
460
+ setMcpBaseUrl(baseUrl) {
461
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
462
+ },
463
+ async handleMcp(secret, body) {
464
+ let handler = handlers.get(secret);
465
+ if (!handler) {
466
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
467
+ if (!sessionId) return null;
468
+ const bundle = await readSessionBundle(sessionId);
469
+ if (!bundle) return null;
470
+ const { conversationId } = await meta(sessionId);
471
+ const { createAgentToolsMcpHandler } = await agentsClient();
472
+ handler = createAgentToolsMcpHandler({
473
+ bundle,
474
+ credentials,
475
+ organizationId: () => organizationId(),
476
+ baseUrl: serverUrl,
477
+ conversationId
478
+ });
479
+ handlers.set(secret, handler);
480
+ }
481
+ return handler.handle(body);
482
+ }
483
+ };
484
+ }
485
+
117
486
  // src/engine/index.ts
118
- var import_promises6 = __toESM(require("fs/promises"), 1);
487
+ var import_promises8 = __toESM(require("fs/promises"), 1);
119
488
  var import_node_os5 = __toESM(require("os"), 1);
120
- var import_node_path7 = __toESM(require("path"), 1);
489
+ var import_node_path9 = __toESM(require("path"), 1);
121
490
  var import_node_url = require("url");
122
491
 
123
492
  // src/normalize/claude.ts
@@ -787,7 +1156,7 @@ function describeProblem(provider, problem) {
787
1156
  var import_node_child_process = require("child_process");
788
1157
  var import_node_fs = require("fs");
789
1158
  var import_node_os = __toESM(require("os"), 1);
790
- var import_node_path = __toESM(require("path"), 1);
1159
+ var import_node_path3 = __toESM(require("path"), 1);
791
1160
  var SOURCE_LABEL = {
792
1161
  codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
793
1162
  claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
@@ -836,10 +1205,10 @@ function exeName(name, platform) {
836
1205
  }
837
1206
  function allOnPath(name, pathValue, platform = process.platform) {
838
1207
  const found = [];
839
- for (const dir of pathValue.split(import_node_path.default.delimiter)) {
1208
+ for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
840
1209
  if (!dir) continue;
841
1210
  for (const file of exeName(name, platform)) {
842
- const candidate = import_node_path.default.join(dir, file);
1211
+ const candidate = import_node_path3.default.join(dir, file);
843
1212
  if (executable(candidate)) {
844
1213
  found.push(candidate);
845
1214
  break;
@@ -852,17 +1221,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
852
1221
  if (platform !== "darwin") return [];
853
1222
  if (provider === "codex") {
854
1223
  return applications.map(
855
- (apps) => import_node_path.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1224
+ (apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
856
1225
  );
857
1226
  }
858
- const root = import_node_path.default.join(home, "Library", "Application Support", "Claude", "claude-code");
1227
+ const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
859
1228
  let versions = [];
860
1229
  try {
861
1230
  versions = (0, import_node_fs.readdirSync)(root);
862
1231
  } catch {
863
1232
  return [];
864
1233
  }
865
- return versions.map((v) => import_node_path.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1234
+ return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
866
1235
  }
867
1236
  function npmGlobalPrefix(env) {
868
1237
  const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
@@ -880,13 +1249,13 @@ function declaredVersion(provider, source, file) {
880
1249
  if (source !== "desktop-app") return null;
881
1250
  if (provider === "codex") {
882
1251
  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"));
1252
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.dirname(file)), "codex-package.json"), "utf8"));
884
1253
  return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
885
1254
  } catch {
886
1255
  return null;
887
1256
  }
888
1257
  }
889
- return parseVersion(import_node_path.default.basename(import_node_path.default.resolve(file, "..", "..", "..", "..")));
1258
+ return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
890
1259
  }
891
1260
  function runVersion(file, env) {
892
1261
  return new Promise((resolve) => {
@@ -900,14 +1269,14 @@ async function discoverBinaries(provider, options) {
900
1269
  const platform = options.platform ?? process.platform;
901
1270
  const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
902
1271
  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" })),
1272
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
904
1273
  ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
905
1274
  ];
906
1275
  const prefix = await npmGlobalPrefix(options.env);
907
1276
  if (prefix) {
908
- const dir = platform === "win32" ? prefix : import_node_path.default.join(prefix, "bin");
1277
+ const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
909
1278
  for (const file of exeName(provider, platform)) {
910
- const candidate = import_node_path.default.join(dir, file);
1279
+ const candidate = import_node_path3.default.join(dir, file);
911
1280
  if (executable(candidate)) {
912
1281
  listed2.push({ path: candidate, source: "npm-global" });
913
1282
  break;
@@ -953,7 +1322,7 @@ async function resolveBinary(provider, options) {
953
1322
 
954
1323
  // src/engine/clean-env.ts
955
1324
  var import_node_os2 = __toESM(require("os"), 1);
956
- var import_node_path2 = __toESM(require("path"), 1);
1325
+ var import_node_path4 = __toESM(require("path"), 1);
957
1326
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
958
1327
  "HOME",
959
1328
  "USER",
@@ -975,11 +1344,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
975
1344
  var ALLOWED_PREFIXES = ["LC_"];
976
1345
  function wellKnownBinDirs(home) {
977
1346
  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"),
1347
+ import_node_path4.default.join(home, ".local", "bin"),
1348
+ import_node_path4.default.join(home, ".claude", "local"),
1349
+ import_node_path4.default.join(home, ".npm-global", "bin"),
1350
+ import_node_path4.default.join(home, ".bun", "bin"),
1351
+ import_node_path4.default.join(home, ".volta", "bin"),
983
1352
  "/opt/homebrew/bin",
984
1353
  "/usr/local/bin",
985
1354
  "/usr/bin",
@@ -999,21 +1368,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
999
1368
  }
1000
1369
  const home = out.HOME ?? import_node_os2.default.homedir();
1001
1370
  out.HOME = home;
1002
- const hostPath = (hostEnv.PATH ?? "").split(import_node_path2.default.delimiter).filter(Boolean);
1371
+ const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
1003
1372
  const merged = [];
1004
1373
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
1005
1374
  if (!merged.includes(dir)) merged.push(dir);
1006
1375
  }
1007
- out.PATH = merged.join(import_node_path2.default.delimiter);
1376
+ out.PATH = merged.join(import_node_path4.default.delimiter);
1008
1377
  for (const [name, value] of Object.entries(extra)) out[name] = value;
1009
1378
  return out;
1010
1379
  }
1011
1380
 
1012
1381
  // src/engine/files.ts
1013
1382
  var import_node_fs2 = require("fs");
1014
- var import_promises2 = __toESM(require("fs/promises"), 1);
1383
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1015
1384
  var import_node_os3 = __toESM(require("os"), 1);
1016
- var import_node_path3 = __toESM(require("path"), 1);
1385
+ var import_node_path5 = __toESM(require("path"), 1);
1017
1386
 
1018
1387
  // src/engine/activity.ts
1019
1388
  var import_node_child_process2 = require("child_process");
@@ -1082,15 +1451,15 @@ function describeActivity(input) {
1082
1451
  // src/engine/files.ts
1083
1452
  function defaultProviderHomes(home = import_node_os3.default.homedir()) {
1084
1453
  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")
1454
+ claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
1455
+ codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
1456
+ codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
1088
1457
  };
1089
1458
  }
1090
1459
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
1091
1460
  async function safeReaddir(dir) {
1092
1461
  try {
1093
- return await import_promises2.default.readdir(dir, { withFileTypes: true });
1462
+ return await import_promises4.default.readdir(dir, { withFileTypes: true });
1094
1463
  } catch {
1095
1464
  return [];
1096
1465
  }
@@ -1099,7 +1468,7 @@ async function statCandidate(provider, file) {
1099
1468
  const match = UUIDISH.exec(file);
1100
1469
  if (!match?.[1]) return null;
1101
1470
  try {
1102
- const stat = await import_promises2.default.stat(file);
1471
+ const stat = await import_promises4.default.stat(file);
1103
1472
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
1104
1473
  } catch {
1105
1474
  return null;
@@ -1109,10 +1478,10 @@ async function claudeCandidates(root) {
1109
1478
  const out = [];
1110
1479
  for (const project of await safeReaddir(root)) {
1111
1480
  if (!project.isDirectory()) continue;
1112
- const dir = import_node_path3.default.join(root, project.name);
1481
+ const dir = import_node_path5.default.join(root, project.name);
1113
1482
  const entries = await safeReaddir(dir);
1114
1483
  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)))
1484
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
1116
1485
  );
1117
1486
  for (const s of stats) if (s) out.push(s);
1118
1487
  }
@@ -1122,14 +1491,14 @@ async function codexCandidates(root) {
1122
1491
  const out = [];
1123
1492
  for (const year of await safeReaddir(root)) {
1124
1493
  if (!year.isDirectory()) continue;
1125
- for (const month of await safeReaddir(import_node_path3.default.join(root, year.name))) {
1494
+ for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
1126
1495
  if (!month.isDirectory()) continue;
1127
- for (const day of await safeReaddir(import_node_path3.default.join(root, year.name, month.name))) {
1496
+ for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
1128
1497
  if (!day.isDirectory()) continue;
1129
- const dir = import_node_path3.default.join(root, year.name, month.name, day.name);
1498
+ const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
1130
1499
  const entries = await safeReaddir(dir);
1131
1500
  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)))
1501
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
1133
1502
  );
1134
1503
  for (const s of stats) if (s) out.push(s);
1135
1504
  }
@@ -1138,7 +1507,7 @@ async function codexCandidates(root) {
1138
1507
  return out;
1139
1508
  }
1140
1509
  async function readSlice(file, start, length) {
1141
- const handle = await import_promises2.default.open(file, "r");
1510
+ const handle = await import_promises4.default.open(file, "r");
1142
1511
  try {
1143
1512
  const buffer = Buffer.alloc(length);
1144
1513
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1211,9 +1580,9 @@ async function readCodexIndex(file) {
1211
1580
  const names = /* @__PURE__ */ new Map();
1212
1581
  let text;
1213
1582
  try {
1214
- const stat = await import_promises2.default.stat(file);
1583
+ const stat = await import_promises4.default.stat(file);
1215
1584
  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);
1585
+ text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1217
1586
  } catch {
1218
1587
  return names;
1219
1588
  }
@@ -1334,7 +1703,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1334
1703
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1335
1704
  let size;
1336
1705
  try {
1337
- size = (await import_promises2.default.stat(file)).size;
1706
+ size = (await import_promises4.default.stat(file)).size;
1338
1707
  } catch {
1339
1708
  return {
1340
1709
  items: [],
@@ -1388,9 +1757,9 @@ async function describeSessionFile(candidate, codexNames) {
1388
1757
  }
1389
1758
 
1390
1759
  // 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);
1760
+ var import_node_crypto3 = require("crypto");
1761
+ var import_promises5 = __toESM(require("fs/promises"), 1);
1762
+ var import_node_path6 = __toESM(require("path"), 1);
1394
1763
  var PreferenceStore = class {
1395
1764
  constructor(dir) {
1396
1765
  this.dir = dir;
@@ -1399,11 +1768,11 @@ var PreferenceStore = class {
1399
1768
  data = {};
1400
1769
  writing = Promise.resolve();
1401
1770
  get file() {
1402
- return import_node_path4.default.join(this.dir, "preferences.json");
1771
+ return import_node_path6.default.join(this.dir, "preferences.json");
1403
1772
  }
1404
1773
  async load() {
1405
1774
  try {
1406
- const parsed = JSON.parse(await import_promises3.default.readFile(this.file, "utf8"));
1775
+ const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
1407
1776
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1408
1777
  } catch {
1409
1778
  this.data = {};
@@ -1441,10 +1810,10 @@ var PreferenceStore = class {
1441
1810
  persist() {
1442
1811
  const snapshot = JSON.stringify(this.data, null, 1);
1443
1812
  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);
1813
+ await import_promises5.default.mkdir(this.dir, { recursive: true });
1814
+ const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1815
+ await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
1816
+ await import_promises5.default.rename(tmp, this.file);
1448
1817
  }).catch(() => {
1449
1818
  });
1450
1819
  }
@@ -1601,9 +1970,9 @@ async function searchLocalChats(query, options = {}) {
1601
1970
  }
1602
1971
 
1603
1972
  // 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);
1973
+ var import_node_crypto4 = require("crypto");
1974
+ var import_promises6 = __toESM(require("fs/promises"), 1);
1975
+ var import_node_path7 = __toESM(require("path"), 1);
1607
1976
  var MAX_RUNS = 2e3;
1608
1977
  var RunIndex = class {
1609
1978
  constructor(dir) {
@@ -1613,12 +1982,12 @@ var RunIndex = class {
1613
1982
  data = { version: 1, sessions: [], runs: [] };
1614
1983
  writing = Promise.resolve();
1615
1984
  get file() {
1616
- return import_node_path5.default.join(this.dir, "index.json");
1985
+ return import_node_path7.default.join(this.dir, "index.json");
1617
1986
  }
1618
1987
  async load() {
1619
- await import_promises4.default.mkdir(this.dir, { recursive: true });
1988
+ await import_promises6.default.mkdir(this.dir, { recursive: true });
1620
1989
  try {
1621
- const parsed = JSON.parse(await import_promises4.default.readFile(this.file, "utf8"));
1990
+ const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
1622
1991
  this.data = {
1623
1992
  version: 1,
1624
1993
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1660,9 +2029,9 @@ var RunIndex = class {
1660
2029
  persist() {
1661
2030
  const snapshot = JSON.stringify(this.data, null, 1);
1662
2031
  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);
2032
+ const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
2033
+ await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
2034
+ await import_promises6.default.rename(tmp, this.file);
1666
2035
  }).catch(() => {
1667
2036
  });
1668
2037
  }
@@ -1672,27 +2041,27 @@ var RunIndex = class {
1672
2041
  /** A stable Paperclip agent/company identity for this install, so the
1673
2042
  * driver's scratch state is reused rather than growing per run. */
1674
2043
  async identity() {
1675
- const file = import_node_path5.default.join(this.dir, "identity.json");
2044
+ const file = import_node_path7.default.join(this.dir, "identity.json");
1676
2045
  try {
1677
- const parsed = JSON.parse(await import_promises4.default.readFile(file, "utf8"));
2046
+ const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
1678
2047
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1679
2048
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1680
2049
  }
1681
2050
  } catch {
1682
2051
  }
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 });
2052
+ const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
2053
+ await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
1685
2054
  return identity;
1686
2055
  }
1687
2056
  };
1688
2057
 
1689
2058
  // src/engine/user-defaults.ts
1690
- var import_promises5 = __toESM(require("fs/promises"), 1);
2059
+ var import_promises7 = __toESM(require("fs/promises"), 1);
1691
2060
  var import_node_os4 = __toESM(require("os"), 1);
1692
- var import_node_path6 = __toESM(require("path"), 1);
2061
+ var import_node_path8 = __toESM(require("path"), 1);
1693
2062
  async function readJson(file) {
1694
2063
  try {
1695
- const parsed = JSON.parse(await import_promises5.default.readFile(file, "utf8"));
2064
+ const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
1696
2065
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1697
2066
  } catch {
1698
2067
  return null;
@@ -1703,8 +2072,8 @@ function str3(value) {
1703
2072
  }
1704
2073
  async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1705
2074
  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")
2075
+ ...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
2076
+ import_node_path8.default.join(home, ".claude", "settings.json")
1708
2077
  ];
1709
2078
  let model = null;
1710
2079
  let effort = null;
@@ -1720,7 +2089,7 @@ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1720
2089
  async function codexDefaults(home = import_node_os4.default.homedir()) {
1721
2090
  let text;
1722
2091
  try {
1723
- text = await import_promises5.default.readFile(import_node_path6.default.join(home, ".codex", "config.toml"), "utf8");
2092
+ text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
1724
2093
  } catch {
1725
2094
  return { model: null, effort: null };
1726
2095
  }
@@ -1744,31 +2113,31 @@ var EFFORT_LEVELS = {
1744
2113
  // src/engine/index.ts
1745
2114
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1746
2115
  function moduleDir() {
1747
- return import_node_path7.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2116
+ return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
1748
2117
  }
1749
2118
  function resolveWorkerPath() {
1750
2119
  const dir = moduleDir();
1751
2120
  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")
2121
+ import_node_path9.default.join(dir, "worker.js"),
2122
+ import_node_path9.default.join(dir, "worker.ts"),
2123
+ import_node_path9.default.join(dir, "..", "engine", "worker.js"),
2124
+ import_node_path9.default.join(dir, "..", "engine", "worker.ts")
1756
2125
  ]) {
1757
2126
  if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
1758
2127
  }
1759
2128
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1760
2129
  }
1761
2130
  async function installShim(dataDir) {
1762
- const source = import_node_path7.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2131
+ const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1763
2132
  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);
2133
+ const body = await import_promises8.default.readFile(source, "utf8");
2134
+ const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
2135
+ const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
1767
2136
  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 });
2137
+ await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
2138
+ await import_promises8.default.writeFile(target, body, { mode: 493 });
1770
2139
  }
1771
- await import_promises6.default.chmod(target, 493);
2140
+ await import_promises8.default.chmod(target, 493);
1772
2141
  return target;
1773
2142
  }
1774
2143
  function nowIso() {
@@ -1868,29 +2237,30 @@ function describeAuthStatus(provider, probe) {
1868
2237
  };
1869
2238
  }
1870
2239
  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");
2240
+ const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
2241
+ const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
1873
2242
  const homes = options.homes ?? defaultProviderHomes();
1874
2243
  const index = new RunIndex(dataDir);
1875
2244
  await index.load();
1876
2245
  const preferences = new PreferenceStore(dataDir);
1877
2246
  await preferences.load();
1878
- await import_promises6.default.mkdir(stateDir, { recursive: true });
2247
+ await import_promises8.default.mkdir(stateDir, { recursive: true });
1879
2248
  const identity = await index.identity();
2249
+ const agents = createAgentConnection(options.agents, dataDir);
1880
2250
  const backups = options.rawBackup ? createRawBackupScheduler({
1881
2251
  sink: options.rawBackup,
1882
2252
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1883
2253
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1884
2254
  }) : null;
1885
2255
  const discoveredSeen = /* @__PURE__ */ new Map();
1886
- const healthFile = import_node_path7.default.join(dataDir, "provider-health.json");
2256
+ const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
1887
2257
  let health = {};
1888
2258
  try {
1889
- health = JSON.parse(await import_promises6.default.readFile(healthFile, "utf8"));
2259
+ health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
1890
2260
  } catch {
1891
2261
  health = {};
1892
2262
  }
1893
- const saveHealth = () => import_promises6.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2263
+ const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1894
2264
  function noteRunOutcome(provider, status, texts) {
1895
2265
  if (status === "completed") {
1896
2266
  if (health[provider]) {
@@ -2007,7 +2377,7 @@ async function createCodingSessionsEngine(options = {}) {
2007
2377
  async function startRun(session, prompt) {
2008
2378
  const provider = session.provider;
2009
2379
  const realCommand = await commandFor(provider);
2010
- const runId = (0, import_node_crypto4.randomUUID)();
2380
+ const runId = (0, import_node_crypto5.randomUUID)();
2011
2381
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
2012
2382
  const run = {
2013
2383
  runId,
@@ -2045,7 +2415,26 @@ async function createCodingSessionsEngine(options = {}) {
2045
2415
  let fileOffset = null;
2046
2416
  if (session.providerSessionId) {
2047
2417
  const file = await findSessionFile(provider, session.providerSessionId, homes);
2048
- if (file) fileOffset = (await import_promises6.default.stat(file).catch(() => null))?.size ?? null;
2418
+ if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
2419
+ }
2420
+ let agentArgs = [];
2421
+ if (session.agent) {
2422
+ const bundle = await agents.readSessionBundle(session.id);
2423
+ if (!bundle) {
2424
+ const failed = saveRun({
2425
+ ...run,
2426
+ status: "failed",
2427
+ endedAt: nowIso(),
2428
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2429
+ });
2430
+ const current = index.session(session.id);
2431
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2432
+ return failed;
2433
+ }
2434
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2435
+ mcpUrl: await agents.mcpUrlFor(session.id)
2436
+ });
2437
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2049
2438
  }
2050
2439
  const child = forkWorker();
2051
2440
  const liveRun = {
@@ -2219,7 +2608,8 @@ async function createCodingSessionsEngine(options = {}) {
2219
2608
  resumeSessionId: session.providerSessionId,
2220
2609
  shimPath,
2221
2610
  realCommand,
2222
- identity
2611
+ identity,
2612
+ agentArgs
2223
2613
  });
2224
2614
  return run;
2225
2615
  }
@@ -2238,7 +2628,7 @@ async function createCodingSessionsEngine(options = {}) {
2238
2628
  }, 3e3);
2239
2629
  }
2240
2630
  async function pruneDriverScratch() {
2241
- const runsDir = import_node_path7.default.join(
2631
+ const runsDir = import_node_path9.default.join(
2242
2632
  stateDir,
2243
2633
  "instances",
2244
2634
  "default",
@@ -2249,9 +2639,9 @@ async function createCodingSessionsEngine(options = {}) {
2249
2639
  "claude-runtime",
2250
2640
  "runs"
2251
2641
  );
2252
- const entries = await import_promises6.default.readdir(runsDir).catch(() => []);
2642
+ const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
2253
2643
  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))
2644
+ entries.filter((name) => !live.has(name)).map((name) => import_promises8.default.rm(import_node_path9.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2255
2645
  );
2256
2646
  }
2257
2647
  async function probe() {
@@ -2292,7 +2682,7 @@ async function createCodingSessionsEngine(options = {}) {
2292
2682
  function newSession(input) {
2293
2683
  const at = nowIso();
2294
2684
  return {
2295
- id: (0, import_node_crypto4.randomUUID)(),
2685
+ id: (0, import_node_crypto5.randomUUID)(),
2296
2686
  provider: input.provider,
2297
2687
  providerSessionId: input.providerSessionId,
2298
2688
  cwd: input.cwd,
@@ -2310,17 +2700,17 @@ async function createCodingSessionsEngine(options = {}) {
2310
2700
  function resolveFolder(raw) {
2311
2701
  const trimmed = raw.trim();
2312
2702
  if (!trimmed) return null;
2313
- return import_node_path7.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2703
+ return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2314
2704
  }
2315
2705
  async function checkFolder(raw) {
2316
2706
  const target = resolveFolder(raw);
2317
2707
  if (!target) return { path: "", exists: false, isDirectory: false };
2318
- const stat = await import_promises6.default.stat(target).catch(() => null);
2708
+ const stat = await import_promises8.default.stat(target).catch(() => null);
2319
2709
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2320
2710
  }
2321
2711
  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);
2712
+ const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2713
+ const stat = await import_promises8.default.stat(resolved).catch(() => null);
2324
2714
  if (!stat?.isDirectory()) {
2325
2715
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2326
2716
  }
@@ -2382,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
2382
2772
  folders.map(async ([folder, lastUsedAt]) => ({
2383
2773
  path: folder,
2384
2774
  lastUsedAt,
2385
- exists: (await import_promises6.default.stat(folder).catch(() => null))?.isDirectory() === true
2775
+ exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
2386
2776
  }))
2387
2777
  );
2388
2778
  },
@@ -2427,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
2427
2817
  async createFolder(raw) {
2428
2818
  const target = resolveFolder(raw);
2429
2819
  if (!target) throw new Error("Type a folder path first.");
2430
- await import_promises6.default.mkdir(target, { recursive: true });
2820
+ await import_promises8.default.mkdir(target, { recursive: true });
2431
2821
  return checkFolder(target);
2432
2822
  },
2433
2823
  async sessionActivity(provider, providerSessionId) {
@@ -2465,31 +2855,35 @@ async function createCodingSessionsEngine(options = {}) {
2465
2855
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2466
2856
  };
2467
2857
  }
2468
- const stat = await import_promises6.default.stat(file);
2858
+ const stat = await import_promises8.default.stat(file);
2469
2859
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2470
2860
  return {
2471
2861
  available: false,
2472
2862
  reason: `This transcript is ${(0, import_format.formatFileSize)(stat.size)}, larger than the ${(0, import_format.formatFileSize)(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
2473
2863
  };
2474
2864
  }
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 };
2865
+ const content = await import_promises8.default.readFile(file, "utf8");
2866
+ return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
2477
2867
  },
2478
2868
  async start(input) {
2479
2869
  const prompt = input.prompt.trim();
2480
2870
  if (!prompt) throw new Error("Type what you want done before starting.");
2481
2871
  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
- );
2872
+ const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2873
+ const draft = newSession({
2874
+ provider: input.provider,
2875
+ cwd,
2876
+ title: titleFrom(prompt),
2877
+ model: input.model?.trim() || null,
2878
+ effort: input.effort?.trim() || null,
2879
+ origin: "started",
2880
+ providerSessionId: null
2881
+ });
2882
+ if (bundle) {
2883
+ await agents.saveSessionBundle(draft.id, bundle);
2884
+ draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2885
+ }
2886
+ const session = saveSession(draft);
2493
2887
  const run = await startRun(session, prompt);
2494
2888
  return { session: index.session(session.id) ?? session, run, queued: false };
2495
2889
  },
@@ -2558,6 +2952,21 @@ async function createCodingSessionsEngine(options = {}) {
2558
2952
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2559
2953
  return saveSession(next);
2560
2954
  },
2955
+ async agentAccess() {
2956
+ return agents.access();
2957
+ },
2958
+ async agentCatalogRpc(request) {
2959
+ return agents.catalogRpc(request);
2960
+ },
2961
+ async agentDetails(agentId) {
2962
+ return agents.details(String(agentId ?? ""));
2963
+ },
2964
+ setMcpBaseUrl(baseUrl) {
2965
+ agents.setMcpBaseUrl(baseUrl);
2966
+ },
2967
+ async handleMcp(secret, body) {
2968
+ return agents.handleMcp(secret, body);
2969
+ },
2561
2970
  async dispose() {
2562
2971
  for (const liveRun of live.values()) {
2563
2972
  liveRun.stopRequested = true;
@@ -2627,7 +3036,10 @@ var BRIDGE_METHODS = [
2627
3036
  "setChatPreference",
2628
3037
  "searchChats",
2629
3038
  "openFolder",
2630
- "setSessionOptions"
3039
+ "setSessionOptions",
3040
+ "agentAccess",
3041
+ "agentCatalogRpc",
3042
+ "agentDetails"
2631
3043
  ];
2632
3044
  var BridgeError = class extends Error {
2633
3045
  constructor(message, code) {
@@ -2671,14 +3083,14 @@ var MIME = {
2671
3083
  ".map": "application/json"
2672
3084
  };
2673
3085
  function moduleDir2() {
2674
- return import_node_path8.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
3086
+ return import_node_path10.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
2675
3087
  }
2676
3088
  function defaultUiDir() {
2677
3089
  for (const candidate of [
2678
- import_node_path8.default.resolve(moduleDir2(), "..", "ui"),
2679
- import_node_path8.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
3090
+ import_node_path10.default.resolve(moduleDir2(), "..", "ui"),
3091
+ import_node_path10.default.resolve(moduleDir2(), "..", "..", "dist", "ui")
2680
3092
  ]) {
2681
- if ((0, import_node_fs5.existsSync)(import_node_path8.default.join(candidate, "index.html"))) return candidate;
3093
+ if ((0, import_node_fs5.existsSync)(import_node_path10.default.join(candidate, "index.html"))) return candidate;
2682
3094
  }
2683
3095
  return null;
2684
3096
  }
@@ -2686,7 +3098,7 @@ function tokensMatch(expected, given) {
2686
3098
  if (!given) return false;
2687
3099
  const a = Buffer.from(expected);
2688
3100
  const b = Buffer.from(given);
2689
- return a.length === b.length && (0, import_node_crypto5.timingSafeEqual)(a, b);
3101
+ return a.length === b.length && (0, import_node_crypto6.timingSafeEqual)(a, b);
2690
3102
  }
2691
3103
  var NO_UI_PAGE = `<!doctype html><meta charset="utf-8"><title>Coding sessions</title>
2692
3104
  <body style="font:15px system-ui;padding:48px;max-width:560px">
@@ -2701,19 +3113,19 @@ async function serveStatic(uiDir, req, res) {
2701
3113
  return;
2702
3114
  }
2703
3115
  const requested = decodeURIComponent(url.pathname);
2704
- let file = import_node_path8.default.resolve(uiDir, `.${requested}`);
3116
+ let file = import_node_path10.default.resolve(uiDir, `.${requested}`);
2705
3117
  if (!file.startsWith(uiDir)) {
2706
3118
  res.writeHead(403).end();
2707
3119
  return;
2708
3120
  }
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);
3121
+ const stat = await import_promises9.default.stat(file).catch(() => null);
3122
+ if (!stat || stat.isDirectory()) file = import_node_path10.default.join(uiDir, "index.html");
3123
+ const body = await import_promises9.default.readFile(file).catch(() => null);
2712
3124
  if (!body) {
2713
3125
  res.writeHead(404).end();
2714
3126
  return;
2715
3127
  }
2716
- const ext = import_node_path8.default.extname(file);
3128
+ const ext = import_node_path10.default.extname(file);
2717
3129
  res.writeHead(200, {
2718
3130
  "content-type": MIME[ext] ?? "application/octet-stream",
2719
3131
  "cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",
@@ -2722,6 +3134,46 @@ async function serveStatic(uiDir, req, res) {
2722
3134
  });
2723
3135
  res.end(body);
2724
3136
  }
3137
+ var MAX_MCP_BODY = 4 * 1024 * 1024;
3138
+ async function handleMcp(engine, secret, req, res, allowedOrigin) {
3139
+ if (!allowedOrigin(req.headers.origin) || !/^[A-Za-z0-9_-]{16,}$/.test(secret)) {
3140
+ res.writeHead(403).end();
3141
+ return;
3142
+ }
3143
+ if (req.method !== "POST") {
3144
+ res.writeHead(405, { allow: "POST" }).end();
3145
+ return;
3146
+ }
3147
+ const chunks = [];
3148
+ let size = 0;
3149
+ for await (const chunk of req) {
3150
+ size += chunk.length;
3151
+ if (size > MAX_MCP_BODY) {
3152
+ res.writeHead(413).end();
3153
+ return;
3154
+ }
3155
+ chunks.push(chunk);
3156
+ }
3157
+ let body;
3158
+ try {
3159
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3160
+ } catch {
3161
+ res.writeHead(400, { "content-type": "application/json" });
3162
+ res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error." } }));
3163
+ return;
3164
+ }
3165
+ const reply = engine.handleMcp ? await engine.handleMcp(secret, body) : null;
3166
+ if (!reply) {
3167
+ res.writeHead(404).end();
3168
+ return;
3169
+ }
3170
+ if (reply.body === void 0) {
3171
+ res.writeHead(reply.status).end();
3172
+ return;
3173
+ }
3174
+ res.writeHead(reply.status, { "content-type": "application/json" });
3175
+ res.end(JSON.stringify(reply.body));
3176
+ }
2725
3177
  function attachSocket(socket, bridge) {
2726
3178
  const unsubscribe = bridge.subscribe((event) => {
2727
3179
  if (socket.readyState === socket.OPEN) socket.send(JSON.stringify({ event }));
@@ -2752,7 +3204,7 @@ function attachSocket(socket, bridge) {
2752
3204
  socket.on("close", unsubscribe);
2753
3205
  }
2754
3206
  async function startCodingSessionsHost(options = {}) {
2755
- const token = options.token ?? (0, import_node_crypto5.randomBytes)(24).toString("base64url");
3207
+ const token = options.token ?? (0, import_node_crypto6.randomBytes)(24).toString("base64url");
2756
3208
  const engine = options.engine ?? await createCodingSessionsEngine(options);
2757
3209
  const bridge = createHostBridge(engine);
2758
3210
  const uiDir = options.uiDir ?? defaultUiDir();
@@ -2763,6 +3215,13 @@ async function startCodingSessionsHost(options = {}) {
2763
3215
  };
2764
3216
  const server = import_node_http.default.createServer((req, res) => {
2765
3217
  const url2 = new URL(req.url ?? "/", `http://${HOST}`);
3218
+ if (url2.pathname.startsWith("/mcp/")) {
3219
+ handleMcp(engine, url2.pathname.slice("/mcp/".length), req, res, allowedOrigin).catch(() => {
3220
+ if (!res.headersSent) res.writeHead(500);
3221
+ res.end();
3222
+ });
3223
+ return;
3224
+ }
2766
3225
  if (url2.pathname === "/health") {
2767
3226
  res.writeHead(200, { "content-type": "application/json" });
2768
3227
  res.end(JSON.stringify({ ok: true, service: "@ai-matrx/coding-sessions" }));
@@ -2793,6 +3252,7 @@ async function startCodingSessionsHost(options = {}) {
2793
3252
  server.off("error", reject);
2794
3253
  const address = server.address();
2795
3254
  if (address && typeof address === "object") port = address.port;
3255
+ engine.setMcpBaseUrl?.(`http://${HOST}:${port}`);
2796
3256
  resolve();
2797
3257
  });
2798
3258
  });
@@ -2803,6 +3263,7 @@ async function startCodingSessionsHost(options = {}) {
2803
3263
  token,
2804
3264
  engine,
2805
3265
  async close() {
3266
+ engine.setMcpBaseUrl?.(null);
2806
3267
  for (const client of wss.clients) client.terminate();
2807
3268
  await new Promise((resolve) => wss.close(() => resolve()));
2808
3269
  await new Promise((resolve) => server.close(() => resolve()));