@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.
@@ -1,6 +1,6 @@
1
1
  // src/engine/index.ts
2
2
  import { execFile as execFile3, fork } from "child_process";
3
- import { randomUUID as randomUUID3 } from "crypto";
3
+ import { randomUUID as randomUUID4 } from "crypto";
4
4
  import { existsSync } from "fs";
5
5
  import { formatFileSize } from "@ai-matrx/kit/format";
6
6
 
@@ -98,10 +98,379 @@ function createAiMatrxRawBackupSink(options) {
98
98
  };
99
99
  }
100
100
 
101
+ // src/engine/agent-attach.ts
102
+ import fs2 from "fs/promises";
103
+ import path from "path";
104
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
105
+ var AGENT_PLUGIN_NAME = "matrx-agent";
106
+ function escapeAttr(value) {
107
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
108
+ }
109
+ function versionWords(bundle) {
110
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
111
+ }
112
+ function renderAgentInstructions(bundle, options = {}) {
113
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
114
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
115
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
116
+ if (included.length > 0) {
117
+ parts.push(
118
+ "## Skills (always in effect)",
119
+ ...included.map((skill) => `### ${skill.label}
120
+
121
+ ${skill.body.trim()}`)
122
+ );
123
+ }
124
+ if (options.claudeSkills && listed2.length > 0) {
125
+ parts.push(
126
+ [
127
+ "## Your skills",
128
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
129
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
130
+ ].join("\n")
131
+ );
132
+ }
133
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
134
+ parts.push(bundle.instructions.trim());
135
+ parts.push("</ai_matrx_agent>");
136
+ return `${parts.filter(Boolean).join("\n\n")}
137
+ `;
138
+ }
139
+ function renderCodexSkillsList(bundle, skillsRoot) {
140
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
141
+ if (listed2.length === 0) return "";
142
+ const lines = [
143
+ "<skills_instructions>",
144
+ "## AI Matrx agent skills",
145
+ "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.",
146
+ "### Available skills",
147
+ ...listed2.map(
148
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${path.join(skillsRoot, skill.slug, "SKILL.md")})`
149
+ ),
150
+ "</skills_instructions>"
151
+ ];
152
+ return lines.join("\n");
153
+ }
154
+ function tomlString(value) {
155
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
156
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
157
+ }
158
+ async function writeAgentAttachment(dir, bundle, options) {
159
+ const pluginDir = path.join(dir, "plugin");
160
+ const skillsRoot = path.join(dir, "skills");
161
+ await fs2.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
162
+ await fs2.mkdir(skillsRoot, { recursive: true, mode: 448 });
163
+ const instructions = renderAgentInstructions(bundle);
164
+ const instructionsFile = path.join(dir, "instructions.md");
165
+ await fs2.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
166
+ await fs2.writeFile(
167
+ path.join(pluginDir, ".claude-plugin", "plugin.json"),
168
+ `${JSON.stringify(
169
+ {
170
+ name: AGENT_PLUGIN_NAME,
171
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
172
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
173
+ },
174
+ null,
175
+ 2
176
+ )}
177
+ `,
178
+ { mode: 384 }
179
+ );
180
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
181
+ const wanted = new Set(listed2.map((skill) => skill.slug));
182
+ for (const root of [path.join(pluginDir, "skills"), skillsRoot]) {
183
+ await fs2.mkdir(root, { recursive: true, mode: 448 });
184
+ for (const existing of await fs2.readdir(root).catch(() => [])) {
185
+ if (!wanted.has(existing)) await fs2.rm(path.join(root, existing), { recursive: true, force: true });
186
+ }
187
+ for (const skill of listed2) {
188
+ const skillDir = path.join(root, skill.slug);
189
+ await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
190
+ await fs2.writeFile(path.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
191
+ }
192
+ }
193
+ let mcpConfigFile = null;
194
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
195
+ const mcpPath = path.join(dir, "mcp.json");
196
+ if (mcpUrl) {
197
+ mcpConfigFile = mcpPath;
198
+ await fs2.writeFile(
199
+ mcpPath,
200
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
201
+ `,
202
+ { mode: 384 }
203
+ );
204
+ } else {
205
+ await fs2.rm(mcpPath, { force: true });
206
+ }
207
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
208
+ return {
209
+ dir,
210
+ instructionsFile,
211
+ pluginDir,
212
+ mcpConfigFile,
213
+ mcpUrl,
214
+ // Same order as Claude's: the skills list first, the agent's words last.
215
+ codexInstructions: skillsList ? `${skillsList}
216
+
217
+ ${instructions}` : instructions
218
+ };
219
+ }
220
+ function agentArgsFor(provider, attachment, options) {
221
+ if (provider === "claude") {
222
+ const args2 = [
223
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
224
+ "--append-system-prompt-file",
225
+ attachment.instructionsFile,
226
+ "--system-prompt-snapshot",
227
+ "off",
228
+ "--plugin-dir",
229
+ attachment.pluginDir
230
+ ];
231
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
232
+ return args2;
233
+ }
234
+ const args = [];
235
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
236
+ if (attachment.mcpUrl) {
237
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
238
+ args.push(
239
+ "-c",
240
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
241
+ "-c",
242
+ `${key}.default_tools_approval_mode="approve"`
243
+ );
244
+ }
245
+ return args;
246
+ }
247
+ function sessionAgentRecord(bundle, options) {
248
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
249
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
250
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
251
+ if (!options.toolsServed && bundle.tools.length > 0) {
252
+ for (const tool of bundle.tools) {
253
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
254
+ }
255
+ }
256
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
257
+ const loaded = [
258
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
259
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
260
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
261
+ ];
262
+ return {
263
+ agentId: bundle.agent_id,
264
+ versionId: bundle.version_id,
265
+ versionNumber: bundle.version_number,
266
+ name: bundle.name,
267
+ definitionHash: bundle.definition_hash,
268
+ loaded,
269
+ unavailable
270
+ };
271
+ }
272
+
273
+ // src/engine/agents.ts
274
+ import { randomBytes, randomUUID } from "crypto";
275
+ import fs3 from "fs/promises";
276
+ import path2 from "path";
277
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
278
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
279
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
280
+ "agx_get_list_full",
281
+ "agx_search",
282
+ "agx_resolve_agent_address",
283
+ "ues_get_bulk",
284
+ "ues_list",
285
+ "ues_set",
286
+ "ues_touch"
287
+ ]);
288
+ var agentsClient = async () => {
289
+ const [matrx, portable, mcp] = await Promise.all([
290
+ import("@ai-matrx/agents/matrx"),
291
+ import("@ai-matrx/agents/portable"),
292
+ import("@ai-matrx/agents/portable/mcp")
293
+ ]);
294
+ return { ...matrx, ...portable, ...mcp };
295
+ };
296
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
297
+ function userIdOf(credential) {
298
+ if (!credential || credential.kind !== "user") return null;
299
+ const payload = credential.accessToken.split(".")[1];
300
+ if (!payload) return null;
301
+ try {
302
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
303
+ return typeof claims.sub === "string" ? claims.sub : null;
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+ function createAgentConnection(options, dataDir) {
309
+ const credentials = options?.credentials ?? null;
310
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
311
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
312
+ const supabaseKey = options?.supabaseKey ?? null;
313
+ const root = path2.join(dataDir, "agents");
314
+ let mcpBaseUrl = null;
315
+ const handlers = /* @__PURE__ */ new Map();
316
+ const secretToSession = /* @__PURE__ */ new Map();
317
+ const organizationId = () => {
318
+ const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
319
+ return raw && raw.trim() ? raw.trim() : null;
320
+ };
321
+ const transport = async () => (await agentsClient()).createMatrxTransport({
322
+ baseUrl: serverUrl,
323
+ ...credentials ? { credentials } : {},
324
+ ...organizationId() ? { organizationId: () => organizationId() } : {},
325
+ source: "codingSessionsAgents"
326
+ });
327
+ const credential = async () => credentials ? credentials.get() : null;
328
+ const requireSignedIn = async () => {
329
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
330
+ };
331
+ const sessionDir = (sessionId) => path2.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
332
+ async function meta(sessionId) {
333
+ const file = path2.join(sessionDir(sessionId), "session.json");
334
+ const existing = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
335
+ if (existing?.mcpSecret && existing.conversationId) return existing;
336
+ const created = { mcpSecret: randomBytes(24).toString("base64url"), conversationId: randomUUID() };
337
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
338
+ await fs3.writeFile(file, JSON.stringify(created), { mode: 384 });
339
+ return created;
340
+ }
341
+ async function findSessionBySecret(secret) {
342
+ for (const name of await fs3.readdir(root).catch(() => [])) {
343
+ const found = await fs3.readFile(path2.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
344
+ if (found) {
345
+ secretToSession.set(secret, name);
346
+ return name;
347
+ }
348
+ }
349
+ return null;
350
+ }
351
+ async function readSessionBundle(sessionId) {
352
+ return fs3.readFile(path2.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
353
+ }
354
+ return {
355
+ async access() {
356
+ const current = await credential().catch(() => null);
357
+ const userId = userIdOf(current);
358
+ return {
359
+ configured: credentials !== null && supabaseKey !== null,
360
+ signedIn: userId !== null,
361
+ userId,
362
+ organizationId: organizationId()
363
+ };
364
+ },
365
+ async catalogRpc(request) {
366
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
367
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
368
+ }
369
+ const current = await credential();
370
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
371
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
372
+ const query = new URLSearchParams();
373
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
374
+ if (request.range) {
375
+ query.set("offset", String(request.range[0]));
376
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
377
+ }
378
+ const headers = {
379
+ apikey: supabaseKey,
380
+ Authorization: `Bearer ${current.accessToken}`,
381
+ "Content-Type": "application/json",
382
+ // Every catalogue RPC lives in `public`; the database's default
383
+ // exposed schema is not `public`, so name it (supabase-js does).
384
+ "Content-Profile": "public",
385
+ "Accept-Profile": "public"
386
+ };
387
+ if (request.count) headers.Prefer = `count=${request.count}`;
388
+ const qs = query.toString();
389
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
390
+ method: "POST",
391
+ headers,
392
+ body: JSON.stringify(request.args ?? {})
393
+ });
394
+ const text = await response.text();
395
+ const body = text ? JSON.parse(text) : null;
396
+ if (!response.ok) {
397
+ const err = body ?? {};
398
+ return {
399
+ data: null,
400
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
401
+ };
402
+ }
403
+ const range = response.headers.get("content-range");
404
+ const total = range?.split("/")[1];
405
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
406
+ },
407
+ async details(agentId) {
408
+ await requireSignedIn();
409
+ const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
410
+ const [bundle, versions] = await Promise.all([
411
+ fetchPortableAgent(await transport(), { agentId }),
412
+ fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
413
+ ]);
414
+ return {
415
+ agentId: bundle.agent_id,
416
+ name: bundle.name,
417
+ variables: bundle.variables,
418
+ versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
419
+ latestVersionNumber: bundle.version_number
420
+ };
421
+ },
422
+ async loadBundle(selection) {
423
+ await requireSignedIn();
424
+ const { fetchPortableAgent } = await agentsClient();
425
+ return fetchPortableAgent(
426
+ await transport(),
427
+ selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
428
+ { variables: selection.variables ?? {} }
429
+ );
430
+ },
431
+ async saveSessionBundle(sessionId, bundle) {
432
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
433
+ await fs3.writeFile(path2.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
434
+ await meta(sessionId);
435
+ },
436
+ readSessionBundle,
437
+ sessionDir,
438
+ async mcpUrlFor(sessionId) {
439
+ if (!mcpBaseUrl) return null;
440
+ const { mcpSecret } = await meta(sessionId);
441
+ secretToSession.set(mcpSecret, sessionId);
442
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
443
+ },
444
+ setMcpBaseUrl(baseUrl) {
445
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
446
+ },
447
+ async handleMcp(secret, body) {
448
+ let handler = handlers.get(secret);
449
+ if (!handler) {
450
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
451
+ if (!sessionId) return null;
452
+ const bundle = await readSessionBundle(sessionId);
453
+ if (!bundle) return null;
454
+ const { conversationId } = await meta(sessionId);
455
+ const { createAgentToolsMcpHandler } = await agentsClient();
456
+ handler = createAgentToolsMcpHandler({
457
+ bundle,
458
+ credentials,
459
+ organizationId: () => organizationId(),
460
+ baseUrl: serverUrl,
461
+ conversationId
462
+ });
463
+ handlers.set(secret, handler);
464
+ }
465
+ return handler.handle(body);
466
+ }
467
+ };
468
+ }
469
+
101
470
  // src/engine/index.ts
102
- import fs6 from "fs/promises";
471
+ import fs8 from "fs/promises";
103
472
  import os5 from "os";
104
- import path7 from "path";
473
+ import path9 from "path";
105
474
  import { fileURLToPath } from "url";
106
475
 
107
476
  // src/normalize/claude.ts
@@ -771,7 +1140,7 @@ function describeProblem(provider, problem) {
771
1140
  import { execFile } from "child_process";
772
1141
  import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
773
1142
  import os from "os";
774
- import path from "path";
1143
+ import path3 from "path";
775
1144
  var SOURCE_LABEL = {
776
1145
  codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
777
1146
  claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
@@ -820,10 +1189,10 @@ function exeName(name, platform) {
820
1189
  }
821
1190
  function allOnPath(name, pathValue, platform = process.platform) {
822
1191
  const found = [];
823
- for (const dir of pathValue.split(path.delimiter)) {
1192
+ for (const dir of pathValue.split(path3.delimiter)) {
824
1193
  if (!dir) continue;
825
1194
  for (const file of exeName(name, platform)) {
826
- const candidate = path.join(dir, file);
1195
+ const candidate = path3.join(dir, file);
827
1196
  if (executable(candidate)) {
828
1197
  found.push(candidate);
829
1198
  break;
@@ -836,17 +1205,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
836
1205
  if (platform !== "darwin") return [];
837
1206
  if (provider === "codex") {
838
1207
  return applications.map(
839
- (apps) => path.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1208
+ (apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
840
1209
  );
841
1210
  }
842
- const root = path.join(home, "Library", "Application Support", "Claude", "claude-code");
1211
+ const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
843
1212
  let versions = [];
844
1213
  try {
845
1214
  versions = readdirSync(root);
846
1215
  } catch {
847
1216
  return [];
848
1217
  }
849
- return versions.map((v) => path.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1218
+ return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
850
1219
  }
851
1220
  function npmGlobalPrefix(env) {
852
1221
  const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
@@ -864,13 +1233,13 @@ function declaredVersion(provider, source, file) {
864
1233
  if (source !== "desktop-app") return null;
865
1234
  if (provider === "codex") {
866
1235
  try {
867
- const pkg = JSON.parse(readFileSync(path.join(path.dirname(path.dirname(file)), "codex-package.json"), "utf8"));
1236
+ const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
868
1237
  return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
869
1238
  } catch {
870
1239
  return null;
871
1240
  }
872
1241
  }
873
- return parseVersion(path.basename(path.resolve(file, "..", "..", "..", "..")));
1242
+ return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
874
1243
  }
875
1244
  function runVersion(file, env) {
876
1245
  return new Promise((resolve) => {
@@ -884,14 +1253,14 @@ async function discoverBinaries(provider, options) {
884
1253
  const platform = options.platform ?? process.platform;
885
1254
  const home = options.home ?? options.env.HOME ?? os.homedir();
886
1255
  const listed2 = [
887
- ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1256
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
888
1257
  ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
889
1258
  ];
890
1259
  const prefix = await npmGlobalPrefix(options.env);
891
1260
  if (prefix) {
892
- const dir = platform === "win32" ? prefix : path.join(prefix, "bin");
1261
+ const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
893
1262
  for (const file of exeName(provider, platform)) {
894
- const candidate = path.join(dir, file);
1263
+ const candidate = path3.join(dir, file);
895
1264
  if (executable(candidate)) {
896
1265
  listed2.push({ path: candidate, source: "npm-global" });
897
1266
  break;
@@ -937,7 +1306,7 @@ async function resolveBinary(provider, options) {
937
1306
 
938
1307
  // src/engine/clean-env.ts
939
1308
  import os2 from "os";
940
- import path2 from "path";
1309
+ import path4 from "path";
941
1310
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
942
1311
  "HOME",
943
1312
  "USER",
@@ -959,11 +1328,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
959
1328
  var ALLOWED_PREFIXES = ["LC_"];
960
1329
  function wellKnownBinDirs(home) {
961
1330
  return [
962
- path2.join(home, ".local", "bin"),
963
- path2.join(home, ".claude", "local"),
964
- path2.join(home, ".npm-global", "bin"),
965
- path2.join(home, ".bun", "bin"),
966
- path2.join(home, ".volta", "bin"),
1331
+ path4.join(home, ".local", "bin"),
1332
+ path4.join(home, ".claude", "local"),
1333
+ path4.join(home, ".npm-global", "bin"),
1334
+ path4.join(home, ".bun", "bin"),
1335
+ path4.join(home, ".volta", "bin"),
967
1336
  "/opt/homebrew/bin",
968
1337
  "/usr/local/bin",
969
1338
  "/usr/bin",
@@ -983,21 +1352,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
983
1352
  }
984
1353
  const home = out.HOME ?? os2.homedir();
985
1354
  out.HOME = home;
986
- const hostPath = (hostEnv.PATH ?? "").split(path2.delimiter).filter(Boolean);
1355
+ const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
987
1356
  const merged = [];
988
1357
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
989
1358
  if (!merged.includes(dir)) merged.push(dir);
990
1359
  }
991
- out.PATH = merged.join(path2.delimiter);
1360
+ out.PATH = merged.join(path4.delimiter);
992
1361
  for (const [name, value] of Object.entries(extra)) out[name] = value;
993
1362
  return out;
994
1363
  }
995
1364
 
996
1365
  // src/engine/files.ts
997
1366
  import { createReadStream } from "fs";
998
- import fs2 from "fs/promises";
1367
+ import fs4 from "fs/promises";
999
1368
  import os3 from "os";
1000
- import path3 from "path";
1369
+ import path5 from "path";
1001
1370
 
1002
1371
  // src/engine/activity.ts
1003
1372
  import { execFile as execFile2 } from "child_process";
@@ -1066,15 +1435,15 @@ function describeActivity(input) {
1066
1435
  // src/engine/files.ts
1067
1436
  function defaultProviderHomes(home = os3.homedir()) {
1068
1437
  return {
1069
- claudeProjects: path3.join(home, ".claude", "projects"),
1070
- codexSessions: path3.join(home, ".codex", "sessions"),
1071
- codexIndex: path3.join(home, ".codex", "session_index.jsonl")
1438
+ claudeProjects: path5.join(home, ".claude", "projects"),
1439
+ codexSessions: path5.join(home, ".codex", "sessions"),
1440
+ codexIndex: path5.join(home, ".codex", "session_index.jsonl")
1072
1441
  };
1073
1442
  }
1074
1443
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
1075
1444
  async function safeReaddir(dir) {
1076
1445
  try {
1077
- return await fs2.readdir(dir, { withFileTypes: true });
1446
+ return await fs4.readdir(dir, { withFileTypes: true });
1078
1447
  } catch {
1079
1448
  return [];
1080
1449
  }
@@ -1083,7 +1452,7 @@ async function statCandidate(provider, file) {
1083
1452
  const match = UUIDISH.exec(file);
1084
1453
  if (!match?.[1]) return null;
1085
1454
  try {
1086
- const stat = await fs2.stat(file);
1455
+ const stat = await fs4.stat(file);
1087
1456
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
1088
1457
  } catch {
1089
1458
  return null;
@@ -1093,10 +1462,10 @@ async function claudeCandidates(root) {
1093
1462
  const out = [];
1094
1463
  for (const project of await safeReaddir(root)) {
1095
1464
  if (!project.isDirectory()) continue;
1096
- const dir = path3.join(root, project.name);
1465
+ const dir = path5.join(root, project.name);
1097
1466
  const entries = await safeReaddir(dir);
1098
1467
  const stats = await Promise.all(
1099
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path3.join(dir, e.name)))
1468
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path5.join(dir, e.name)))
1100
1469
  );
1101
1470
  for (const s of stats) if (s) out.push(s);
1102
1471
  }
@@ -1106,14 +1475,14 @@ async function codexCandidates(root) {
1106
1475
  const out = [];
1107
1476
  for (const year of await safeReaddir(root)) {
1108
1477
  if (!year.isDirectory()) continue;
1109
- for (const month of await safeReaddir(path3.join(root, year.name))) {
1478
+ for (const month of await safeReaddir(path5.join(root, year.name))) {
1110
1479
  if (!month.isDirectory()) continue;
1111
- for (const day of await safeReaddir(path3.join(root, year.name, month.name))) {
1480
+ for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
1112
1481
  if (!day.isDirectory()) continue;
1113
- const dir = path3.join(root, year.name, month.name, day.name);
1482
+ const dir = path5.join(root, year.name, month.name, day.name);
1114
1483
  const entries = await safeReaddir(dir);
1115
1484
  const stats = await Promise.all(
1116
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path3.join(dir, e.name)))
1485
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
1117
1486
  );
1118
1487
  for (const s of stats) if (s) out.push(s);
1119
1488
  }
@@ -1122,7 +1491,7 @@ async function codexCandidates(root) {
1122
1491
  return out;
1123
1492
  }
1124
1493
  async function readSlice(file, start, length) {
1125
- const handle = await fs2.open(file, "r");
1494
+ const handle = await fs4.open(file, "r");
1126
1495
  try {
1127
1496
  const buffer = Buffer.alloc(length);
1128
1497
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1195,9 +1564,9 @@ async function readCodexIndex(file) {
1195
1564
  const names = /* @__PURE__ */ new Map();
1196
1565
  let text;
1197
1566
  try {
1198
- const stat = await fs2.stat(file);
1567
+ const stat = await fs4.stat(file);
1199
1568
  const budget = 4 * 1024 * 1024;
1200
- text = stat.size <= budget ? await fs2.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1569
+ text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1201
1570
  } catch {
1202
1571
  return names;
1203
1572
  }
@@ -1318,7 +1687,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1318
1687
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1319
1688
  let size;
1320
1689
  try {
1321
- size = (await fs2.stat(file)).size;
1690
+ size = (await fs4.stat(file)).size;
1322
1691
  } catch {
1323
1692
  return {
1324
1693
  items: [],
@@ -1372,9 +1741,9 @@ async function describeSessionFile(candidate, codexNames) {
1372
1741
  }
1373
1742
 
1374
1743
  // src/engine/preferences.ts
1375
- import { randomUUID } from "crypto";
1376
- import fs3 from "fs/promises";
1377
- import path4 from "path";
1744
+ import { randomUUID as randomUUID2 } from "crypto";
1745
+ import fs5 from "fs/promises";
1746
+ import path6 from "path";
1378
1747
  var PreferenceStore = class {
1379
1748
  constructor(dir) {
1380
1749
  this.dir = dir;
@@ -1383,11 +1752,11 @@ var PreferenceStore = class {
1383
1752
  data = {};
1384
1753
  writing = Promise.resolve();
1385
1754
  get file() {
1386
- return path4.join(this.dir, "preferences.json");
1755
+ return path6.join(this.dir, "preferences.json");
1387
1756
  }
1388
1757
  async load() {
1389
1758
  try {
1390
- const parsed = JSON.parse(await fs3.readFile(this.file, "utf8"));
1759
+ const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
1391
1760
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1392
1761
  } catch {
1393
1762
  this.data = {};
@@ -1425,10 +1794,10 @@ var PreferenceStore = class {
1425
1794
  persist() {
1426
1795
  const snapshot = JSON.stringify(this.data, null, 1);
1427
1796
  this.writing = this.writing.then(async () => {
1428
- await fs3.mkdir(this.dir, { recursive: true });
1429
- const tmp = `${this.file}.${randomUUID()}.tmp`;
1430
- await fs3.writeFile(tmp, snapshot, { mode: 384 });
1431
- await fs3.rename(tmp, this.file);
1797
+ await fs5.mkdir(this.dir, { recursive: true });
1798
+ const tmp = `${this.file}.${randomUUID2()}.tmp`;
1799
+ await fs5.writeFile(tmp, snapshot, { mode: 384 });
1800
+ await fs5.rename(tmp, this.file);
1432
1801
  }).catch(() => {
1433
1802
  });
1434
1803
  }
@@ -1585,9 +1954,9 @@ async function searchLocalChats(query, options = {}) {
1585
1954
  }
1586
1955
 
1587
1956
  // src/engine/store.ts
1588
- import { randomUUID as randomUUID2 } from "crypto";
1589
- import fs4 from "fs/promises";
1590
- import path5 from "path";
1957
+ import { randomUUID as randomUUID3 } from "crypto";
1958
+ import fs6 from "fs/promises";
1959
+ import path7 from "path";
1591
1960
  var MAX_RUNS = 2e3;
1592
1961
  var RunIndex = class {
1593
1962
  constructor(dir) {
@@ -1597,12 +1966,12 @@ var RunIndex = class {
1597
1966
  data = { version: 1, sessions: [], runs: [] };
1598
1967
  writing = Promise.resolve();
1599
1968
  get file() {
1600
- return path5.join(this.dir, "index.json");
1969
+ return path7.join(this.dir, "index.json");
1601
1970
  }
1602
1971
  async load() {
1603
- await fs4.mkdir(this.dir, { recursive: true });
1972
+ await fs6.mkdir(this.dir, { recursive: true });
1604
1973
  try {
1605
- const parsed = JSON.parse(await fs4.readFile(this.file, "utf8"));
1974
+ const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
1606
1975
  this.data = {
1607
1976
  version: 1,
1608
1977
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1644,9 +2013,9 @@ var RunIndex = class {
1644
2013
  persist() {
1645
2014
  const snapshot = JSON.stringify(this.data, null, 1);
1646
2015
  this.writing = this.writing.then(async () => {
1647
- const tmp = `${this.file}.${randomUUID2()}.tmp`;
1648
- await fs4.writeFile(tmp, snapshot, { mode: 384 });
1649
- await fs4.rename(tmp, this.file);
2016
+ const tmp = `${this.file}.${randomUUID3()}.tmp`;
2017
+ await fs6.writeFile(tmp, snapshot, { mode: 384 });
2018
+ await fs6.rename(tmp, this.file);
1650
2019
  }).catch(() => {
1651
2020
  });
1652
2021
  }
@@ -1656,27 +2025,27 @@ var RunIndex = class {
1656
2025
  /** A stable Paperclip agent/company identity for this install, so the
1657
2026
  * driver's scratch state is reused rather than growing per run. */
1658
2027
  async identity() {
1659
- const file = path5.join(this.dir, "identity.json");
2028
+ const file = path7.join(this.dir, "identity.json");
1660
2029
  try {
1661
- const parsed = JSON.parse(await fs4.readFile(file, "utf8"));
2030
+ const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
1662
2031
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1663
2032
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1664
2033
  }
1665
2034
  } catch {
1666
2035
  }
1667
- const identity = { companyId: randomUUID2(), agentId: randomUUID2() };
1668
- await fs4.writeFile(file, JSON.stringify(identity), { mode: 384 });
2036
+ const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
2037
+ await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
1669
2038
  return identity;
1670
2039
  }
1671
2040
  };
1672
2041
 
1673
2042
  // src/engine/user-defaults.ts
1674
- import fs5 from "fs/promises";
2043
+ import fs7 from "fs/promises";
1675
2044
  import os4 from "os";
1676
- import path6 from "path";
2045
+ import path8 from "path";
1677
2046
  async function readJson(file) {
1678
2047
  try {
1679
- const parsed = JSON.parse(await fs5.readFile(file, "utf8"));
2048
+ const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
1680
2049
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1681
2050
  } catch {
1682
2051
  return null;
@@ -1687,8 +2056,8 @@ function str3(value) {
1687
2056
  }
1688
2057
  async function claudeDefaults(cwd, home = os4.homedir()) {
1689
2058
  const files = [
1690
- ...cwd ? [path6.join(cwd, ".claude", "settings.local.json"), path6.join(cwd, ".claude", "settings.json")] : [],
1691
- path6.join(home, ".claude", "settings.json")
2059
+ ...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
2060
+ path8.join(home, ".claude", "settings.json")
1692
2061
  ];
1693
2062
  let model = null;
1694
2063
  let effort = null;
@@ -1704,7 +2073,7 @@ async function claudeDefaults(cwd, home = os4.homedir()) {
1704
2073
  async function codexDefaults(home = os4.homedir()) {
1705
2074
  let text;
1706
2075
  try {
1707
- text = await fs5.readFile(path6.join(home, ".codex", "config.toml"), "utf8");
2076
+ text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
1708
2077
  } catch {
1709
2078
  return { model: null, effort: null };
1710
2079
  }
@@ -1728,31 +2097,31 @@ var EFFORT_LEVELS = {
1728
2097
  // src/engine/index.ts
1729
2098
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1730
2099
  function moduleDir() {
1731
- return path7.dirname(fileURLToPath(import.meta.url));
2100
+ return path9.dirname(fileURLToPath(import.meta.url));
1732
2101
  }
1733
2102
  function resolveWorkerPath() {
1734
2103
  const dir = moduleDir();
1735
2104
  for (const candidate of [
1736
- path7.join(dir, "worker.js"),
1737
- path7.join(dir, "worker.ts"),
1738
- path7.join(dir, "..", "engine", "worker.js"),
1739
- path7.join(dir, "..", "engine", "worker.ts")
2105
+ path9.join(dir, "worker.js"),
2106
+ path9.join(dir, "worker.ts"),
2107
+ path9.join(dir, "..", "engine", "worker.js"),
2108
+ path9.join(dir, "..", "engine", "worker.ts")
1740
2109
  ]) {
1741
2110
  if (existsSync(candidate)) return candidate;
1742
2111
  }
1743
2112
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1744
2113
  }
1745
2114
  async function installShim(dataDir) {
1746
- const source = path7.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2115
+ const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1747
2116
  if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1748
- const body = await fs6.readFile(source, "utf8");
1749
- const target = path7.join(dataDir, "bin", "cli-shim.sh");
1750
- const current = await fs6.readFile(target, "utf8").catch(() => null);
2117
+ const body = await fs8.readFile(source, "utf8");
2118
+ const target = path9.join(dataDir, "bin", "cli-shim.sh");
2119
+ const current = await fs8.readFile(target, "utf8").catch(() => null);
1751
2120
  if (current !== body) {
1752
- await fs6.mkdir(path7.dirname(target), { recursive: true });
1753
- await fs6.writeFile(target, body, { mode: 493 });
2121
+ await fs8.mkdir(path9.dirname(target), { recursive: true });
2122
+ await fs8.writeFile(target, body, { mode: 493 });
1754
2123
  }
1755
- await fs6.chmod(target, 493);
2124
+ await fs8.chmod(target, 493);
1756
2125
  return target;
1757
2126
  }
1758
2127
  function nowIso() {
@@ -1852,29 +2221,30 @@ function describeAuthStatus(provider, probe) {
1852
2221
  };
1853
2222
  }
1854
2223
  async function createCodingSessionsEngine(options = {}) {
1855
- const dataDir = options.dataDir ?? path7.join(os5.homedir(), ".matrx", "coding-sessions");
1856
- const stateDir = path7.join(dataDir, "paperclip-state");
2224
+ const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
2225
+ const stateDir = path9.join(dataDir, "paperclip-state");
1857
2226
  const homes = options.homes ?? defaultProviderHomes();
1858
2227
  const index = new RunIndex(dataDir);
1859
2228
  await index.load();
1860
2229
  const preferences = new PreferenceStore(dataDir);
1861
2230
  await preferences.load();
1862
- await fs6.mkdir(stateDir, { recursive: true });
2231
+ await fs8.mkdir(stateDir, { recursive: true });
1863
2232
  const identity = await index.identity();
2233
+ const agents = createAgentConnection(options.agents, dataDir);
1864
2234
  const backups = options.rawBackup ? createRawBackupScheduler({
1865
2235
  sink: options.rawBackup,
1866
2236
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1867
2237
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1868
2238
  }) : null;
1869
2239
  const discoveredSeen = /* @__PURE__ */ new Map();
1870
- const healthFile = path7.join(dataDir, "provider-health.json");
2240
+ const healthFile = path9.join(dataDir, "provider-health.json");
1871
2241
  let health = {};
1872
2242
  try {
1873
- health = JSON.parse(await fs6.readFile(healthFile, "utf8"));
2243
+ health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
1874
2244
  } catch {
1875
2245
  health = {};
1876
2246
  }
1877
- const saveHealth = () => fs6.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2247
+ const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1878
2248
  function noteRunOutcome(provider, status, texts) {
1879
2249
  if (status === "completed") {
1880
2250
  if (health[provider]) {
@@ -1991,7 +2361,7 @@ async function createCodingSessionsEngine(options = {}) {
1991
2361
  async function startRun(session, prompt) {
1992
2362
  const provider = session.provider;
1993
2363
  const realCommand = await commandFor(provider);
1994
- const runId = randomUUID3();
2364
+ const runId = randomUUID4();
1995
2365
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1996
2366
  const run = {
1997
2367
  runId,
@@ -2029,7 +2399,26 @@ async function createCodingSessionsEngine(options = {}) {
2029
2399
  let fileOffset = null;
2030
2400
  if (session.providerSessionId) {
2031
2401
  const file = await findSessionFile(provider, session.providerSessionId, homes);
2032
- if (file) fileOffset = (await fs6.stat(file).catch(() => null))?.size ?? null;
2402
+ if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
2403
+ }
2404
+ let agentArgs = [];
2405
+ if (session.agent) {
2406
+ const bundle = await agents.readSessionBundle(session.id);
2407
+ if (!bundle) {
2408
+ const failed = saveRun({
2409
+ ...run,
2410
+ status: "failed",
2411
+ endedAt: nowIso(),
2412
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2413
+ });
2414
+ const current = index.session(session.id);
2415
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2416
+ return failed;
2417
+ }
2418
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2419
+ mcpUrl: await agents.mcpUrlFor(session.id)
2420
+ });
2421
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2033
2422
  }
2034
2423
  const child = forkWorker();
2035
2424
  const liveRun = {
@@ -2203,7 +2592,8 @@ async function createCodingSessionsEngine(options = {}) {
2203
2592
  resumeSessionId: session.providerSessionId,
2204
2593
  shimPath,
2205
2594
  realCommand,
2206
- identity
2595
+ identity,
2596
+ agentArgs
2207
2597
  });
2208
2598
  return run;
2209
2599
  }
@@ -2222,7 +2612,7 @@ async function createCodingSessionsEngine(options = {}) {
2222
2612
  }, 3e3);
2223
2613
  }
2224
2614
  async function pruneDriverScratch() {
2225
- const runsDir = path7.join(
2615
+ const runsDir = path9.join(
2226
2616
  stateDir,
2227
2617
  "instances",
2228
2618
  "default",
@@ -2233,9 +2623,9 @@ async function createCodingSessionsEngine(options = {}) {
2233
2623
  "claude-runtime",
2234
2624
  "runs"
2235
2625
  );
2236
- const entries = await fs6.readdir(runsDir).catch(() => []);
2626
+ const entries = await fs8.readdir(runsDir).catch(() => []);
2237
2627
  await Promise.all(
2238
- entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path7.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2628
+ entries.filter((name) => !live.has(name)).map((name) => fs8.rm(path9.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2239
2629
  );
2240
2630
  }
2241
2631
  async function probe() {
@@ -2276,7 +2666,7 @@ async function createCodingSessionsEngine(options = {}) {
2276
2666
  function newSession(input) {
2277
2667
  const at = nowIso();
2278
2668
  return {
2279
- id: randomUUID3(),
2669
+ id: randomUUID4(),
2280
2670
  provider: input.provider,
2281
2671
  providerSessionId: input.providerSessionId,
2282
2672
  cwd: input.cwd,
@@ -2294,17 +2684,17 @@ async function createCodingSessionsEngine(options = {}) {
2294
2684
  function resolveFolder(raw) {
2295
2685
  const trimmed = raw.trim();
2296
2686
  if (!trimmed) return null;
2297
- return path7.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2687
+ return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2298
2688
  }
2299
2689
  async function checkFolder(raw) {
2300
2690
  const target = resolveFolder(raw);
2301
2691
  if (!target) return { path: "", exists: false, isDirectory: false };
2302
- const stat = await fs6.stat(target).catch(() => null);
2692
+ const stat = await fs8.stat(target).catch(() => null);
2303
2693
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2304
2694
  }
2305
2695
  async function assertFolder(cwd) {
2306
- const resolved = path7.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2307
- const stat = await fs6.stat(resolved).catch(() => null);
2696
+ const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2697
+ const stat = await fs8.stat(resolved).catch(() => null);
2308
2698
  if (!stat?.isDirectory()) {
2309
2699
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2310
2700
  }
@@ -2366,7 +2756,7 @@ async function createCodingSessionsEngine(options = {}) {
2366
2756
  folders.map(async ([folder, lastUsedAt]) => ({
2367
2757
  path: folder,
2368
2758
  lastUsedAt,
2369
- exists: (await fs6.stat(folder).catch(() => null))?.isDirectory() === true
2759
+ exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
2370
2760
  }))
2371
2761
  );
2372
2762
  },
@@ -2411,7 +2801,7 @@ async function createCodingSessionsEngine(options = {}) {
2411
2801
  async createFolder(raw) {
2412
2802
  const target = resolveFolder(raw);
2413
2803
  if (!target) throw new Error("Type a folder path first.");
2414
- await fs6.mkdir(target, { recursive: true });
2804
+ await fs8.mkdir(target, { recursive: true });
2415
2805
  return checkFolder(target);
2416
2806
  },
2417
2807
  async sessionActivity(provider, providerSessionId) {
@@ -2449,31 +2839,35 @@ async function createCodingSessionsEngine(options = {}) {
2449
2839
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2450
2840
  };
2451
2841
  }
2452
- const stat = await fs6.stat(file);
2842
+ const stat = await fs8.stat(file);
2453
2843
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2454
2844
  return {
2455
2845
  available: false,
2456
2846
  reason: `This transcript is ${formatFileSize(stat.size)}, larger than the ${formatFileSize(RAW_TRANSCRIPT_MAX_BYTES)} this page can hand over. It is at ${file}.`
2457
2847
  };
2458
2848
  }
2459
- const content = await fs6.readFile(file, "utf8");
2460
- return { available: true, fileName: path7.basename(file), bytes: stat.size, content };
2849
+ const content = await fs8.readFile(file, "utf8");
2850
+ return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
2461
2851
  },
2462
2852
  async start(input) {
2463
2853
  const prompt = input.prompt.trim();
2464
2854
  if (!prompt) throw new Error("Type what you want done before starting.");
2465
2855
  const cwd = await assertFolder(input.cwd);
2466
- const session = saveSession(
2467
- newSession({
2468
- provider: input.provider,
2469
- cwd,
2470
- title: titleFrom(prompt),
2471
- model: input.model?.trim() || null,
2472
- effort: input.effort?.trim() || null,
2473
- origin: "started",
2474
- providerSessionId: null
2475
- })
2476
- );
2856
+ const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2857
+ const draft = newSession({
2858
+ provider: input.provider,
2859
+ cwd,
2860
+ title: titleFrom(prompt),
2861
+ model: input.model?.trim() || null,
2862
+ effort: input.effort?.trim() || null,
2863
+ origin: "started",
2864
+ providerSessionId: null
2865
+ });
2866
+ if (bundle) {
2867
+ await agents.saveSessionBundle(draft.id, bundle);
2868
+ draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2869
+ }
2870
+ const session = saveSession(draft);
2477
2871
  const run = await startRun(session, prompt);
2478
2872
  return { session: index.session(session.id) ?? session, run, queued: false };
2479
2873
  },
@@ -2542,6 +2936,21 @@ async function createCodingSessionsEngine(options = {}) {
2542
2936
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2543
2937
  return saveSession(next);
2544
2938
  },
2939
+ async agentAccess() {
2940
+ return agents.access();
2941
+ },
2942
+ async agentCatalogRpc(request) {
2943
+ return agents.catalogRpc(request);
2944
+ },
2945
+ async agentDetails(agentId) {
2946
+ return agents.details(String(agentId ?? ""));
2947
+ },
2948
+ setMcpBaseUrl(baseUrl) {
2949
+ agents.setMcpBaseUrl(baseUrl);
2950
+ },
2951
+ async handleMcp(secret, body) {
2952
+ return agents.handleMcp(secret, body);
2953
+ },
2545
2954
  async dispose() {
2546
2955
  for (const liveRun of live.values()) {
2547
2956
  liveRun.stopRequested = true;