@ai-matrx/coding-sessions 0.1.81 → 0.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,9 +60,9 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
60
60
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
61
61
 
62
62
  // src/engine/index.ts
63
- var import_node_child_process2 = require("child_process");
64
- var import_node_crypto4 = require("crypto");
65
- var import_node_fs3 = require("fs");
63
+ var import_node_child_process3 = require("child_process");
64
+ var import_node_crypto5 = require("crypto");
65
+ var import_node_fs4 = require("fs");
66
66
  var import_format = require("@ai-matrx/kit/format");
67
67
 
68
68
  // src/engine/raw-backup.ts
@@ -159,10 +159,379 @@ function createAiMatrxRawBackupSink(options) {
159
159
  };
160
160
  }
161
161
 
162
+ // src/engine/agent-attach.ts
163
+ var import_promises2 = __toESM(require("fs/promises"), 1);
164
+ var import_node_path = __toESM(require("path"), 1);
165
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
166
+ var AGENT_PLUGIN_NAME = "matrx-agent";
167
+ function escapeAttr(value) {
168
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
169
+ }
170
+ function versionWords(bundle) {
171
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
172
+ }
173
+ function renderAgentInstructions(bundle, options = {}) {
174
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
175
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
176
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
177
+ if (included.length > 0) {
178
+ parts.push(
179
+ "## Skills (always in effect)",
180
+ ...included.map((skill) => `### ${skill.label}
181
+
182
+ ${skill.body.trim()}`)
183
+ );
184
+ }
185
+ if (options.claudeSkills && listed2.length > 0) {
186
+ parts.push(
187
+ [
188
+ "## Your skills",
189
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
190
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
191
+ ].join("\n")
192
+ );
193
+ }
194
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
195
+ parts.push(bundle.instructions.trim());
196
+ parts.push("</ai_matrx_agent>");
197
+ return `${parts.filter(Boolean).join("\n\n")}
198
+ `;
199
+ }
200
+ function renderCodexSkillsList(bundle, skillsRoot) {
201
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
202
+ if (listed2.length === 0) return "";
203
+ const lines = [
204
+ "<skills_instructions>",
205
+ "## AI Matrx agent skills",
206
+ "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.",
207
+ "### Available skills",
208
+ ...listed2.map(
209
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${import_node_path.default.join(skillsRoot, skill.slug, "SKILL.md")})`
210
+ ),
211
+ "</skills_instructions>"
212
+ ];
213
+ return lines.join("\n");
214
+ }
215
+ function tomlString(value) {
216
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
217
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
218
+ }
219
+ async function writeAgentAttachment(dir, bundle, options) {
220
+ const pluginDir = import_node_path.default.join(dir, "plugin");
221
+ const skillsRoot = import_node_path.default.join(dir, "skills");
222
+ await import_promises2.default.mkdir(import_node_path.default.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
223
+ await import_promises2.default.mkdir(skillsRoot, { recursive: true, mode: 448 });
224
+ const instructions = renderAgentInstructions(bundle);
225
+ const instructionsFile = import_node_path.default.join(dir, "instructions.md");
226
+ await import_promises2.default.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
227
+ await import_promises2.default.writeFile(
228
+ import_node_path.default.join(pluginDir, ".claude-plugin", "plugin.json"),
229
+ `${JSON.stringify(
230
+ {
231
+ name: AGENT_PLUGIN_NAME,
232
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
233
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
234
+ },
235
+ null,
236
+ 2
237
+ )}
238
+ `,
239
+ { mode: 384 }
240
+ );
241
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
242
+ const wanted = new Set(listed2.map((skill) => skill.slug));
243
+ for (const root of [import_node_path.default.join(pluginDir, "skills"), skillsRoot]) {
244
+ await import_promises2.default.mkdir(root, { recursive: true, mode: 448 });
245
+ for (const existing of await import_promises2.default.readdir(root).catch(() => [])) {
246
+ if (!wanted.has(existing)) await import_promises2.default.rm(import_node_path.default.join(root, existing), { recursive: true, force: true });
247
+ }
248
+ for (const skill of listed2) {
249
+ const skillDir = import_node_path.default.join(root, skill.slug);
250
+ await import_promises2.default.mkdir(skillDir, { recursive: true, mode: 448 });
251
+ await import_promises2.default.writeFile(import_node_path.default.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
252
+ }
253
+ }
254
+ let mcpConfigFile = null;
255
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
256
+ const mcpPath = import_node_path.default.join(dir, "mcp.json");
257
+ if (mcpUrl) {
258
+ mcpConfigFile = mcpPath;
259
+ await import_promises2.default.writeFile(
260
+ mcpPath,
261
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
262
+ `,
263
+ { mode: 384 }
264
+ );
265
+ } else {
266
+ await import_promises2.default.rm(mcpPath, { force: true });
267
+ }
268
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
269
+ return {
270
+ dir,
271
+ instructionsFile,
272
+ pluginDir,
273
+ mcpConfigFile,
274
+ mcpUrl,
275
+ // Same order as Claude's: the skills list first, the agent's words last.
276
+ codexInstructions: skillsList ? `${skillsList}
277
+
278
+ ${instructions}` : instructions
279
+ };
280
+ }
281
+ function agentArgsFor(provider, attachment, options) {
282
+ if (provider === "claude") {
283
+ const args2 = [
284
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
285
+ "--append-system-prompt-file",
286
+ attachment.instructionsFile,
287
+ "--system-prompt-snapshot",
288
+ "off",
289
+ "--plugin-dir",
290
+ attachment.pluginDir
291
+ ];
292
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
293
+ return args2;
294
+ }
295
+ const args = [];
296
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
297
+ if (attachment.mcpUrl) {
298
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
299
+ args.push(
300
+ "-c",
301
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
302
+ "-c",
303
+ `${key}.default_tools_approval_mode="approve"`
304
+ );
305
+ }
306
+ return args;
307
+ }
308
+ function sessionAgentRecord(bundle, options) {
309
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
310
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
311
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
312
+ if (!options.toolsServed && bundle.tools.length > 0) {
313
+ for (const tool of bundle.tools) {
314
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
315
+ }
316
+ }
317
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
318
+ const loaded = [
319
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
320
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
321
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
322
+ ];
323
+ return {
324
+ agentId: bundle.agent_id,
325
+ versionId: bundle.version_id,
326
+ versionNumber: bundle.version_number,
327
+ name: bundle.name,
328
+ definitionHash: bundle.definition_hash,
329
+ loaded,
330
+ unavailable
331
+ };
332
+ }
333
+
334
+ // src/engine/agents.ts
335
+ var import_node_crypto2 = require("crypto");
336
+ var import_promises3 = __toESM(require("fs/promises"), 1);
337
+ var import_node_path2 = __toESM(require("path"), 1);
338
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
339
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
340
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
341
+ "agx_get_list_full",
342
+ "agx_search",
343
+ "agx_resolve_agent_address",
344
+ "ues_get_bulk",
345
+ "ues_list",
346
+ "ues_set",
347
+ "ues_touch"
348
+ ]);
349
+ var agentsClient = async () => {
350
+ const [matrx, portable, mcp] = await Promise.all([
351
+ import("@ai-matrx/agents/matrx"),
352
+ import("@ai-matrx/agents/portable"),
353
+ import("@ai-matrx/agents/portable/mcp")
354
+ ]);
355
+ return { ...matrx, ...portable, ...mcp };
356
+ };
357
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
358
+ function userIdOf(credential) {
359
+ if (!credential || credential.kind !== "user") return null;
360
+ const payload = credential.accessToken.split(".")[1];
361
+ if (!payload) return null;
362
+ try {
363
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
364
+ return typeof claims.sub === "string" ? claims.sub : null;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+ function createAgentConnection(options, dataDir) {
370
+ const credentials = options?.credentials ?? null;
371
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
372
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
373
+ const supabaseKey = options?.supabaseKey ?? null;
374
+ const root = import_node_path2.default.join(dataDir, "agents");
375
+ let mcpBaseUrl = null;
376
+ const handlers = /* @__PURE__ */ new Map();
377
+ const secretToSession = /* @__PURE__ */ new Map();
378
+ const organizationId = () => {
379
+ const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
380
+ return raw && raw.trim() ? raw.trim() : null;
381
+ };
382
+ const transport = async () => (await agentsClient()).createMatrxTransport({
383
+ baseUrl: serverUrl,
384
+ ...credentials ? { credentials } : {},
385
+ ...organizationId() ? { organizationId: () => organizationId() } : {},
386
+ source: "codingSessionsAgents"
387
+ });
388
+ const credential = async () => credentials ? credentials.get() : null;
389
+ const requireSignedIn = async () => {
390
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
391
+ };
392
+ const sessionDir = (sessionId) => import_node_path2.default.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
393
+ async function meta(sessionId) {
394
+ const file = import_node_path2.default.join(sessionDir(sessionId), "session.json");
395
+ const existing = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
396
+ if (existing?.mcpSecret && existing.conversationId) return existing;
397
+ const created = { mcpSecret: (0, import_node_crypto2.randomBytes)(24).toString("base64url"), conversationId: (0, import_node_crypto2.randomUUID)() };
398
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
399
+ await import_promises3.default.writeFile(file, JSON.stringify(created), { mode: 384 });
400
+ return created;
401
+ }
402
+ async function findSessionBySecret(secret) {
403
+ for (const name of await import_promises3.default.readdir(root).catch(() => [])) {
404
+ 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);
405
+ if (found) {
406
+ secretToSession.set(secret, name);
407
+ return name;
408
+ }
409
+ }
410
+ return null;
411
+ }
412
+ async function readSessionBundle(sessionId) {
413
+ return import_promises3.default.readFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
414
+ }
415
+ return {
416
+ async access() {
417
+ const current = await credential().catch(() => null);
418
+ const userId = userIdOf(current);
419
+ return {
420
+ configured: credentials !== null && supabaseKey !== null,
421
+ signedIn: userId !== null,
422
+ userId,
423
+ organizationId: organizationId()
424
+ };
425
+ },
426
+ async catalogRpc(request) {
427
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
428
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
429
+ }
430
+ const current = await credential();
431
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
432
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
433
+ const query = new URLSearchParams();
434
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
435
+ if (request.range) {
436
+ query.set("offset", String(request.range[0]));
437
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
438
+ }
439
+ const headers = {
440
+ apikey: supabaseKey,
441
+ Authorization: `Bearer ${current.accessToken}`,
442
+ "Content-Type": "application/json",
443
+ // Every catalogue RPC lives in `public`; the database's default
444
+ // exposed schema is not `public`, so name it (supabase-js does).
445
+ "Content-Profile": "public",
446
+ "Accept-Profile": "public"
447
+ };
448
+ if (request.count) headers.Prefer = `count=${request.count}`;
449
+ const qs = query.toString();
450
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
451
+ method: "POST",
452
+ headers,
453
+ body: JSON.stringify(request.args ?? {})
454
+ });
455
+ const text = await response.text();
456
+ const body = text ? JSON.parse(text) : null;
457
+ if (!response.ok) {
458
+ const err = body ?? {};
459
+ return {
460
+ data: null,
461
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
462
+ };
463
+ }
464
+ const range = response.headers.get("content-range");
465
+ const total = range?.split("/")[1];
466
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
467
+ },
468
+ async details(agentId) {
469
+ await requireSignedIn();
470
+ const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
471
+ const [bundle, versions] = await Promise.all([
472
+ fetchPortableAgent(await transport(), { agentId }),
473
+ fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
474
+ ]);
475
+ return {
476
+ agentId: bundle.agent_id,
477
+ name: bundle.name,
478
+ variables: bundle.variables,
479
+ versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
480
+ latestVersionNumber: bundle.version_number
481
+ };
482
+ },
483
+ async loadBundle(selection) {
484
+ await requireSignedIn();
485
+ const { fetchPortableAgent } = await agentsClient();
486
+ return fetchPortableAgent(
487
+ await transport(),
488
+ selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
489
+ { variables: selection.variables ?? {} }
490
+ );
491
+ },
492
+ async saveSessionBundle(sessionId, bundle) {
493
+ await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
494
+ await import_promises3.default.writeFile(import_node_path2.default.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
495
+ await meta(sessionId);
496
+ },
497
+ readSessionBundle,
498
+ sessionDir,
499
+ async mcpUrlFor(sessionId) {
500
+ if (!mcpBaseUrl) return null;
501
+ const { mcpSecret } = await meta(sessionId);
502
+ secretToSession.set(mcpSecret, sessionId);
503
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
504
+ },
505
+ setMcpBaseUrl(baseUrl) {
506
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
507
+ },
508
+ async handleMcp(secret, body) {
509
+ let handler = handlers.get(secret);
510
+ if (!handler) {
511
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
512
+ if (!sessionId) return null;
513
+ const bundle = await readSessionBundle(sessionId);
514
+ if (!bundle) return null;
515
+ const { conversationId } = await meta(sessionId);
516
+ const { createAgentToolsMcpHandler } = await agentsClient();
517
+ handler = createAgentToolsMcpHandler({
518
+ bundle,
519
+ credentials,
520
+ organizationId: () => organizationId(),
521
+ baseUrl: serverUrl,
522
+ conversationId
523
+ });
524
+ handlers.set(secret, handler);
525
+ }
526
+ return handler.handle(body);
527
+ }
528
+ };
529
+ }
530
+
162
531
  // src/engine/index.ts
163
- var import_promises6 = __toESM(require("fs/promises"), 1);
164
- var import_node_os4 = __toESM(require("os"), 1);
165
- var import_node_path6 = __toESM(require("path"), 1);
532
+ var import_promises8 = __toESM(require("fs/promises"), 1);
533
+ var import_node_os5 = __toESM(require("os"), 1);
534
+ var import_node_path9 = __toESM(require("path"), 1);
166
535
  var import_node_url = require("url");
167
536
 
168
537
  // src/normalize/claude.ts
@@ -828,9 +1197,177 @@ function describeProblem(provider, problem) {
828
1197
  };
829
1198
  }
830
1199
 
831
- // src/engine/clean-env.ts
1200
+ // src/engine/binaries.ts
1201
+ var import_node_child_process = require("child_process");
1202
+ var import_node_fs = require("fs");
832
1203
  var import_node_os = __toESM(require("os"), 1);
833
- var import_node_path = __toESM(require("path"), 1);
1204
+ var import_node_path3 = __toESM(require("path"), 1);
1205
+ var SOURCE_LABEL = {
1206
+ codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
1207
+ claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
1208
+ };
1209
+ function sourceLabel(provider, source) {
1210
+ return SOURCE_LABEL[provider][source];
1211
+ }
1212
+ function parseVersion(text) {
1213
+ const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
1214
+ return match ? match[0] : null;
1215
+ }
1216
+ function compareVersions(a, b) {
1217
+ const [aCore = "", aPre] = a.split("-", 2);
1218
+ const [bCore = "", bPre] = b.split("-", 2);
1219
+ const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
1220
+ const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
1221
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1222
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1223
+ if (d !== 0) return d < 0 ? -1 : 1;
1224
+ }
1225
+ if (aPre && !bPre) return -1;
1226
+ if (!aPre && bPre) return 1;
1227
+ return 0;
1228
+ }
1229
+ function pickBinary(candidates) {
1230
+ let best = null;
1231
+ for (const c of candidates) {
1232
+ if (!best) {
1233
+ best = c;
1234
+ continue;
1235
+ }
1236
+ if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
1237
+ }
1238
+ return best;
1239
+ }
1240
+ function executable(file) {
1241
+ try {
1242
+ (0, import_node_fs.accessSync)(file, import_node_fs.constants.X_OK);
1243
+ return true;
1244
+ } catch {
1245
+ return false;
1246
+ }
1247
+ }
1248
+ function exeName(name, platform) {
1249
+ return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
1250
+ }
1251
+ function allOnPath(name, pathValue, platform = process.platform) {
1252
+ const found = [];
1253
+ for (const dir of pathValue.split(import_node_path3.default.delimiter)) {
1254
+ if (!dir) continue;
1255
+ for (const file of exeName(name, platform)) {
1256
+ const candidate = import_node_path3.default.join(dir, file);
1257
+ if (executable(candidate)) {
1258
+ found.push(candidate);
1259
+ break;
1260
+ }
1261
+ }
1262
+ }
1263
+ return found;
1264
+ }
1265
+ function desktopAppCandidates(provider, platform, home, applications) {
1266
+ if (platform !== "darwin") return [];
1267
+ if (provider === "codex") {
1268
+ return applications.map(
1269
+ (apps) => import_node_path3.default.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1270
+ );
1271
+ }
1272
+ const root = import_node_path3.default.join(home, "Library", "Application Support", "Claude", "claude-code");
1273
+ let versions = [];
1274
+ try {
1275
+ versions = (0, import_node_fs.readdirSync)(root);
1276
+ } catch {
1277
+ return [];
1278
+ }
1279
+ return versions.map((v) => import_node_path3.default.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1280
+ }
1281
+ function npmGlobalPrefix(env) {
1282
+ const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
1283
+ if (fromEnv) return Promise.resolve(fromEnv);
1284
+ const npm = allOnPath("npm", env.PATH ?? "")[0];
1285
+ if (!npm) return Promise.resolve(null);
1286
+ return new Promise((resolve) => {
1287
+ const child = (0, import_node_child_process.execFile)(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
1288
+ resolve(error ? null : String(stdout).trim() || null);
1289
+ });
1290
+ child.stdin?.end();
1291
+ });
1292
+ }
1293
+ function declaredVersion(provider, source, file) {
1294
+ if (source !== "desktop-app") return null;
1295
+ if (provider === "codex") {
1296
+ try {
1297
+ 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"));
1298
+ return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
1299
+ } catch {
1300
+ return null;
1301
+ }
1302
+ }
1303
+ return parseVersion(import_node_path3.default.basename(import_node_path3.default.resolve(file, "..", "..", "..", "..")));
1304
+ }
1305
+ function runVersion(file, env) {
1306
+ return new Promise((resolve) => {
1307
+ const child = (0, import_node_child_process.execFile)(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
1308
+ resolve(error ? null : parseVersion(String(stdout)));
1309
+ });
1310
+ child.stdin?.end();
1311
+ });
1312
+ }
1313
+ async function discoverBinaries(provider, options) {
1314
+ const platform = options.platform ?? process.platform;
1315
+ const home = options.home ?? options.env.HOME ?? import_node_os.default.homedir();
1316
+ const listed2 = [
1317
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", import_node_path3.default.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1318
+ ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
1319
+ ];
1320
+ const prefix = await npmGlobalPrefix(options.env);
1321
+ if (prefix) {
1322
+ const dir = platform === "win32" ? prefix : import_node_path3.default.join(prefix, "bin");
1323
+ for (const file of exeName(provider, platform)) {
1324
+ const candidate = import_node_path3.default.join(dir, file);
1325
+ if (executable(candidate)) {
1326
+ listed2.push({ path: candidate, source: "npm-global" });
1327
+ break;
1328
+ }
1329
+ }
1330
+ }
1331
+ const seen = /* @__PURE__ */ new Set();
1332
+ const unique = listed2.filter((c) => {
1333
+ let real = c.path;
1334
+ try {
1335
+ real = (0, import_node_fs.realpathSync)(c.path);
1336
+ } catch {
1337
+ }
1338
+ if (seen.has(real)) return false;
1339
+ seen.add(real);
1340
+ return true;
1341
+ });
1342
+ return Promise.all(
1343
+ unique.map(async (c) => ({
1344
+ ...c,
1345
+ version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
1346
+ }))
1347
+ );
1348
+ }
1349
+ async function resolveBinary(provider, options) {
1350
+ const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
1351
+ const best = pickBinary(candidates);
1352
+ const inUse = options.forced ? {
1353
+ path: options.forced,
1354
+ source: "configured",
1355
+ version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
1356
+ } : best;
1357
+ if (!inUse) return null;
1358
+ const newer = best && best.path !== inUse.path && best.version && (!inUse.version || compareVersions(best.version, inUse.version) > 0) ? { path: best.path, version: best.version, sourceLabel: sourceLabel(provider, best.source) } : null;
1359
+ return {
1360
+ path: inUse.path,
1361
+ version: inUse.version,
1362
+ source: inUse.source,
1363
+ sourceLabel: sourceLabel(provider, inUse.source),
1364
+ newer
1365
+ };
1366
+ }
1367
+
1368
+ // src/engine/clean-env.ts
1369
+ var import_node_os2 = __toESM(require("os"), 1);
1370
+ var import_node_path4 = __toESM(require("path"), 1);
834
1371
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
835
1372
  "HOME",
836
1373
  "USER",
@@ -852,11 +1389,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
852
1389
  var ALLOWED_PREFIXES = ["LC_"];
853
1390
  function wellKnownBinDirs(home) {
854
1391
  return [
855
- import_node_path.default.join(home, ".local", "bin"),
856
- import_node_path.default.join(home, ".claude", "local"),
857
- import_node_path.default.join(home, ".npm-global", "bin"),
858
- import_node_path.default.join(home, ".bun", "bin"),
859
- import_node_path.default.join(home, ".volta", "bin"),
1392
+ import_node_path4.default.join(home, ".local", "bin"),
1393
+ import_node_path4.default.join(home, ".claude", "local"),
1394
+ import_node_path4.default.join(home, ".npm-global", "bin"),
1395
+ import_node_path4.default.join(home, ".bun", "bin"),
1396
+ import_node_path4.default.join(home, ".volta", "bin"),
860
1397
  "/opt/homebrew/bin",
861
1398
  "/usr/local/bin",
862
1399
  "/usr/bin",
@@ -874,31 +1411,31 @@ function buildCleanEnv(hostEnv, extra = {}) {
874
1411
  if (typeof value !== "string") continue;
875
1412
  if (isAllowedEnvName(name)) out[name] = value;
876
1413
  }
877
- const home = out.HOME ?? import_node_os.default.homedir();
1414
+ const home = out.HOME ?? import_node_os2.default.homedir();
878
1415
  out.HOME = home;
879
- const hostPath = (hostEnv.PATH ?? "").split(import_node_path.default.delimiter).filter(Boolean);
1416
+ const hostPath = (hostEnv.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
880
1417
  const merged = [];
881
1418
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
882
1419
  if (!merged.includes(dir)) merged.push(dir);
883
1420
  }
884
- out.PATH = merged.join(import_node_path.default.delimiter);
1421
+ out.PATH = merged.join(import_node_path4.default.delimiter);
885
1422
  for (const [name, value] of Object.entries(extra)) out[name] = value;
886
1423
  return out;
887
1424
  }
888
1425
 
889
1426
  // src/engine/files.ts
890
- var import_node_fs = require("fs");
891
- var import_promises2 = __toESM(require("fs/promises"), 1);
892
- var import_node_os2 = __toESM(require("os"), 1);
893
- var import_node_path2 = __toESM(require("path"), 1);
1427
+ var import_node_fs2 = require("fs");
1428
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1429
+ var import_node_os3 = __toESM(require("os"), 1);
1430
+ var import_node_path5 = __toESM(require("path"), 1);
894
1431
 
895
1432
  // src/engine/activity.ts
896
- var import_node_child_process = require("child_process");
1433
+ var import_node_child_process2 = require("child_process");
897
1434
  var RECENT_WRITE_MS = 2 * 60 * 1e3;
898
1435
  var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
899
1436
  function listProcesses() {
900
1437
  return new Promise((resolve) => {
901
- (0, import_node_child_process.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
1438
+ (0, import_node_child_process2.execFile)("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
902
1439
  if (error) {
903
1440
  resolve([]);
904
1441
  return;
@@ -957,17 +1494,17 @@ function describeActivity(input) {
957
1494
  }
958
1495
 
959
1496
  // src/engine/files.ts
960
- function defaultProviderHomes(home = import_node_os2.default.homedir()) {
1497
+ function defaultProviderHomes(home = import_node_os3.default.homedir()) {
961
1498
  return {
962
- claudeProjects: import_node_path2.default.join(home, ".claude", "projects"),
963
- codexSessions: import_node_path2.default.join(home, ".codex", "sessions"),
964
- codexIndex: import_node_path2.default.join(home, ".codex", "session_index.jsonl")
1499
+ claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
1500
+ codexSessions: import_node_path5.default.join(home, ".codex", "sessions"),
1501
+ codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
965
1502
  };
966
1503
  }
967
1504
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
968
1505
  async function safeReaddir(dir) {
969
1506
  try {
970
- return await import_promises2.default.readdir(dir, { withFileTypes: true });
1507
+ return await import_promises4.default.readdir(dir, { withFileTypes: true });
971
1508
  } catch {
972
1509
  return [];
973
1510
  }
@@ -976,7 +1513,7 @@ async function statCandidate(provider, file) {
976
1513
  const match = UUIDISH.exec(file);
977
1514
  if (!match?.[1]) return null;
978
1515
  try {
979
- const stat = await import_promises2.default.stat(file);
1516
+ const stat = await import_promises4.default.stat(file);
980
1517
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
981
1518
  } catch {
982
1519
  return null;
@@ -986,10 +1523,10 @@ async function claudeCandidates(root) {
986
1523
  const out = [];
987
1524
  for (const project of await safeReaddir(root)) {
988
1525
  if (!project.isDirectory()) continue;
989
- const dir = import_node_path2.default.join(root, project.name);
1526
+ const dir = import_node_path5.default.join(root, project.name);
990
1527
  const entries = await safeReaddir(dir);
991
1528
  const stats = await Promise.all(
992
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path2.default.join(dir, e.name)))
1529
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", import_node_path5.default.join(dir, e.name)))
993
1530
  );
994
1531
  for (const s of stats) if (s) out.push(s);
995
1532
  }
@@ -999,14 +1536,14 @@ async function codexCandidates(root) {
999
1536
  const out = [];
1000
1537
  for (const year of await safeReaddir(root)) {
1001
1538
  if (!year.isDirectory()) continue;
1002
- for (const month of await safeReaddir(import_node_path2.default.join(root, year.name))) {
1539
+ for (const month of await safeReaddir(import_node_path5.default.join(root, year.name))) {
1003
1540
  if (!month.isDirectory()) continue;
1004
- for (const day of await safeReaddir(import_node_path2.default.join(root, year.name, month.name))) {
1541
+ for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
1005
1542
  if (!day.isDirectory()) continue;
1006
- const dir = import_node_path2.default.join(root, year.name, month.name, day.name);
1543
+ const dir = import_node_path5.default.join(root, year.name, month.name, day.name);
1007
1544
  const entries = await safeReaddir(dir);
1008
1545
  const stats = await Promise.all(
1009
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path2.default.join(dir, e.name)))
1546
+ 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)))
1010
1547
  );
1011
1548
  for (const s of stats) if (s) out.push(s);
1012
1549
  }
@@ -1015,7 +1552,7 @@ async function codexCandidates(root) {
1015
1552
  return out;
1016
1553
  }
1017
1554
  async function readSlice(file, start, length) {
1018
- const handle = await import_promises2.default.open(file, "r");
1555
+ const handle = await import_promises4.default.open(file, "r");
1019
1556
  try {
1020
1557
  const buffer = Buffer.alloc(length);
1021
1558
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1088,9 +1625,9 @@ async function readCodexIndex(file) {
1088
1625
  const names = /* @__PURE__ */ new Map();
1089
1626
  let text;
1090
1627
  try {
1091
- const stat = await import_promises2.default.stat(file);
1628
+ const stat = await import_promises4.default.stat(file);
1092
1629
  const budget = 4 * 1024 * 1024;
1093
- text = stat.size <= budget ? await import_promises2.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1630
+ text = stat.size <= budget ? await import_promises4.default.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1094
1631
  } catch {
1095
1632
  return names;
1096
1633
  }
@@ -1211,7 +1748,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1211
1748
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1212
1749
  let size;
1213
1750
  try {
1214
- size = (await import_promises2.default.stat(file)).size;
1751
+ size = (await import_promises4.default.stat(file)).size;
1215
1752
  } catch {
1216
1753
  return {
1217
1754
  items: [],
@@ -1227,7 +1764,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
1227
1764
  let droppedFirst = start > 0;
1228
1765
  let carry = "";
1229
1766
  await new Promise((resolve, reject) => {
1230
- const stream = (0, import_node_fs.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
1767
+ const stream = (0, import_node_fs2.createReadStream)(file, { start, end: Math.max(start, end - 1), encoding: "utf8" });
1231
1768
  stream.on("data", (chunk) => {
1232
1769
  const text = carry + String(chunk);
1233
1770
  const lines = text.split("\n");
@@ -1265,9 +1802,9 @@ async function describeSessionFile(candidate, codexNames) {
1265
1802
  }
1266
1803
 
1267
1804
  // src/engine/preferences.ts
1268
- var import_node_crypto2 = require("crypto");
1269
- var import_promises3 = __toESM(require("fs/promises"), 1);
1270
- var import_node_path3 = __toESM(require("path"), 1);
1805
+ var import_node_crypto3 = require("crypto");
1806
+ var import_promises5 = __toESM(require("fs/promises"), 1);
1807
+ var import_node_path6 = __toESM(require("path"), 1);
1271
1808
  var PreferenceStore = class {
1272
1809
  constructor(dir) {
1273
1810
  this.dir = dir;
@@ -1276,11 +1813,11 @@ var PreferenceStore = class {
1276
1813
  data = {};
1277
1814
  writing = Promise.resolve();
1278
1815
  get file() {
1279
- return import_node_path3.default.join(this.dir, "preferences.json");
1816
+ return import_node_path6.default.join(this.dir, "preferences.json");
1280
1817
  }
1281
1818
  async load() {
1282
1819
  try {
1283
- const parsed = JSON.parse(await import_promises3.default.readFile(this.file, "utf8"));
1820
+ const parsed = JSON.parse(await import_promises5.default.readFile(this.file, "utf8"));
1284
1821
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1285
1822
  } catch {
1286
1823
  this.data = {};
@@ -1318,10 +1855,10 @@ var PreferenceStore = class {
1318
1855
  persist() {
1319
1856
  const snapshot = JSON.stringify(this.data, null, 1);
1320
1857
  this.writing = this.writing.then(async () => {
1321
- await import_promises3.default.mkdir(this.dir, { recursive: true });
1322
- const tmp = `${this.file}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
1323
- await import_promises3.default.writeFile(tmp, snapshot, { mode: 384 });
1324
- await import_promises3.default.rename(tmp, this.file);
1858
+ await import_promises5.default.mkdir(this.dir, { recursive: true });
1859
+ const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1860
+ await import_promises5.default.writeFile(tmp, snapshot, { mode: 384 });
1861
+ await import_promises5.default.rename(tmp, this.file);
1325
1862
  }).catch(() => {
1326
1863
  });
1327
1864
  }
@@ -1331,7 +1868,7 @@ var PreferenceStore = class {
1331
1868
  };
1332
1869
 
1333
1870
  // src/engine/search.ts
1334
- var import_node_fs2 = require("fs");
1871
+ var import_node_fs3 = require("fs");
1335
1872
  function findInLine(provider, value, needle) {
1336
1873
  const items = provider === "claude" ? createClaudeNormalizer().push(value) : createCodexRolloutNormalizer().push(value);
1337
1874
  for (const item of items) {
@@ -1372,7 +1909,7 @@ async function scanFile(provider, file, needle, raw, deadline) {
1372
1909
  return new Promise((resolve) => {
1373
1910
  let carry = Buffer.alloc(0);
1374
1911
  let done = false;
1375
- const stream = (0, import_node_fs2.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
1912
+ const stream = (0, import_node_fs3.createReadStream)(file, { highWaterMark: 4 * 1024 * 1024 });
1376
1913
  const finish = (value) => {
1377
1914
  if (done) return;
1378
1915
  done = true;
@@ -1478,9 +2015,9 @@ async function searchLocalChats(query, options = {}) {
1478
2015
  }
1479
2016
 
1480
2017
  // src/engine/store.ts
1481
- var import_node_crypto3 = require("crypto");
1482
- var import_promises4 = __toESM(require("fs/promises"), 1);
1483
- var import_node_path4 = __toESM(require("path"), 1);
2018
+ var import_node_crypto4 = require("crypto");
2019
+ var import_promises6 = __toESM(require("fs/promises"), 1);
2020
+ var import_node_path7 = __toESM(require("path"), 1);
1484
2021
  var MAX_RUNS = 2e3;
1485
2022
  var RunIndex = class {
1486
2023
  constructor(dir) {
@@ -1490,12 +2027,12 @@ var RunIndex = class {
1490
2027
  data = { version: 1, sessions: [], runs: [] };
1491
2028
  writing = Promise.resolve();
1492
2029
  get file() {
1493
- return import_node_path4.default.join(this.dir, "index.json");
2030
+ return import_node_path7.default.join(this.dir, "index.json");
1494
2031
  }
1495
2032
  async load() {
1496
- await import_promises4.default.mkdir(this.dir, { recursive: true });
2033
+ await import_promises6.default.mkdir(this.dir, { recursive: true });
1497
2034
  try {
1498
- const parsed = JSON.parse(await import_promises4.default.readFile(this.file, "utf8"));
2035
+ const parsed = JSON.parse(await import_promises6.default.readFile(this.file, "utf8"));
1499
2036
  this.data = {
1500
2037
  version: 1,
1501
2038
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1537,9 +2074,9 @@ var RunIndex = class {
1537
2074
  persist() {
1538
2075
  const snapshot = JSON.stringify(this.data, null, 1);
1539
2076
  this.writing = this.writing.then(async () => {
1540
- const tmp = `${this.file}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
1541
- await import_promises4.default.writeFile(tmp, snapshot, { mode: 384 });
1542
- await import_promises4.default.rename(tmp, this.file);
2077
+ const tmp = `${this.file}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
2078
+ await import_promises6.default.writeFile(tmp, snapshot, { mode: 384 });
2079
+ await import_promises6.default.rename(tmp, this.file);
1543
2080
  }).catch(() => {
1544
2081
  });
1545
2082
  }
@@ -1549,27 +2086,27 @@ var RunIndex = class {
1549
2086
  /** A stable Paperclip agent/company identity for this install, so the
1550
2087
  * driver's scratch state is reused rather than growing per run. */
1551
2088
  async identity() {
1552
- const file = import_node_path4.default.join(this.dir, "identity.json");
2089
+ const file = import_node_path7.default.join(this.dir, "identity.json");
1553
2090
  try {
1554
- const parsed = JSON.parse(await import_promises4.default.readFile(file, "utf8"));
2091
+ const parsed = JSON.parse(await import_promises6.default.readFile(file, "utf8"));
1555
2092
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1556
2093
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1557
2094
  }
1558
2095
  } catch {
1559
2096
  }
1560
- const identity = { companyId: (0, import_node_crypto3.randomUUID)(), agentId: (0, import_node_crypto3.randomUUID)() };
1561
- await import_promises4.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
2097
+ const identity = { companyId: (0, import_node_crypto4.randomUUID)(), agentId: (0, import_node_crypto4.randomUUID)() };
2098
+ await import_promises6.default.writeFile(file, JSON.stringify(identity), { mode: 384 });
1562
2099
  return identity;
1563
2100
  }
1564
2101
  };
1565
2102
 
1566
2103
  // src/engine/user-defaults.ts
1567
- var import_promises5 = __toESM(require("fs/promises"), 1);
1568
- var import_node_os3 = __toESM(require("os"), 1);
1569
- var import_node_path5 = __toESM(require("path"), 1);
2104
+ var import_promises7 = __toESM(require("fs/promises"), 1);
2105
+ var import_node_os4 = __toESM(require("os"), 1);
2106
+ var import_node_path8 = __toESM(require("path"), 1);
1570
2107
  async function readJson(file) {
1571
2108
  try {
1572
- const parsed = JSON.parse(await import_promises5.default.readFile(file, "utf8"));
2109
+ const parsed = JSON.parse(await import_promises7.default.readFile(file, "utf8"));
1573
2110
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1574
2111
  } catch {
1575
2112
  return null;
@@ -1578,10 +2115,10 @@ async function readJson(file) {
1578
2115
  function str3(value) {
1579
2116
  return typeof value === "string" && value.trim() ? value.trim() : null;
1580
2117
  }
1581
- async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
2118
+ async function claudeDefaults(cwd, home = import_node_os4.default.homedir()) {
1582
2119
  const files = [
1583
- ...cwd ? [import_node_path5.default.join(cwd, ".claude", "settings.local.json"), import_node_path5.default.join(cwd, ".claude", "settings.json")] : [],
1584
- import_node_path5.default.join(home, ".claude", "settings.json")
2120
+ ...cwd ? [import_node_path8.default.join(cwd, ".claude", "settings.local.json"), import_node_path8.default.join(cwd, ".claude", "settings.json")] : [],
2121
+ import_node_path8.default.join(home, ".claude", "settings.json")
1585
2122
  ];
1586
2123
  let model = null;
1587
2124
  let effort = null;
@@ -1594,10 +2131,10 @@ async function claudeDefaults(cwd, home = import_node_os3.default.homedir()) {
1594
2131
  }
1595
2132
  return { model, effort };
1596
2133
  }
1597
- async function codexDefaults(home = import_node_os3.default.homedir()) {
2134
+ async function codexDefaults(home = import_node_os4.default.homedir()) {
1598
2135
  let text;
1599
2136
  try {
1600
- text = await import_promises5.default.readFile(import_node_path5.default.join(home, ".codex", "config.toml"), "utf8");
2137
+ text = await import_promises7.default.readFile(import_node_path8.default.join(home, ".codex", "config.toml"), "utf8");
1601
2138
  } catch {
1602
2139
  return { model: null, effort: null };
1603
2140
  }
@@ -1621,45 +2158,33 @@ var EFFORT_LEVELS = {
1621
2158
  // src/engine/index.ts
1622
2159
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1623
2160
  function moduleDir() {
1624
- return import_node_path6.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2161
+ return import_node_path9.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
1625
2162
  }
1626
2163
  function resolveWorkerPath() {
1627
2164
  const dir = moduleDir();
1628
2165
  for (const candidate of [
1629
- import_node_path6.default.join(dir, "worker.js"),
1630
- import_node_path6.default.join(dir, "worker.ts"),
1631
- import_node_path6.default.join(dir, "..", "engine", "worker.js"),
1632
- import_node_path6.default.join(dir, "..", "engine", "worker.ts")
2166
+ import_node_path9.default.join(dir, "worker.js"),
2167
+ import_node_path9.default.join(dir, "worker.ts"),
2168
+ import_node_path9.default.join(dir, "..", "engine", "worker.js"),
2169
+ import_node_path9.default.join(dir, "..", "engine", "worker.ts")
1633
2170
  ]) {
1634
- if ((0, import_node_fs3.existsSync)(candidate)) return candidate;
2171
+ if ((0, import_node_fs4.existsSync)(candidate)) return candidate;
1635
2172
  }
1636
2173
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1637
2174
  }
1638
2175
  async function installShim(dataDir) {
1639
- const source = import_node_path6.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1640
- if (!(0, import_node_fs3.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1641
- const body = await import_promises6.default.readFile(source, "utf8");
1642
- const target = import_node_path6.default.join(dataDir, "bin", "cli-shim.sh");
1643
- const current = await import_promises6.default.readFile(target, "utf8").catch(() => null);
2176
+ const source = import_node_path9.default.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2177
+ if (!(0, import_node_fs4.existsSync)(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
2178
+ const body = await import_promises8.default.readFile(source, "utf8");
2179
+ const target = import_node_path9.default.join(dataDir, "bin", "cli-shim.sh");
2180
+ const current = await import_promises8.default.readFile(target, "utf8").catch(() => null);
1644
2181
  if (current !== body) {
1645
- await import_promises6.default.mkdir(import_node_path6.default.dirname(target), { recursive: true });
1646
- await import_promises6.default.writeFile(target, body, { mode: 493 });
2182
+ await import_promises8.default.mkdir(import_node_path9.default.dirname(target), { recursive: true });
2183
+ await import_promises8.default.writeFile(target, body, { mode: 493 });
1647
2184
  }
1648
- await import_promises6.default.chmod(target, 493);
2185
+ await import_promises8.default.chmod(target, 493);
1649
2186
  return target;
1650
2187
  }
1651
- function whichOnPath(name, pathValue) {
1652
- for (const dir of pathValue.split(import_node_path6.default.delimiter)) {
1653
- if (!dir) continue;
1654
- const candidate = import_node_path6.default.join(dir, name);
1655
- try {
1656
- (0, import_node_fs3.accessSync)(candidate, import_node_fs3.constants.X_OK);
1657
- return candidate;
1658
- } catch {
1659
- }
1660
- }
1661
- return null;
1662
- }
1663
2188
  function nowIso() {
1664
2189
  return (/* @__PURE__ */ new Date()).toISOString();
1665
2190
  }
@@ -1698,12 +2223,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
1698
2223
  function describeAuthStatus(provider, probe) {
1699
2224
  const label = PROVIDER_LABEL[provider];
1700
2225
  const installed = probe?.installed === true;
1701
- const version = typeof probe?.version === "string" ? probe.version : null;
2226
+ const binary = probe?.binary ?? null;
2227
+ const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
1702
2228
  const base = {
1703
2229
  provider,
1704
2230
  label,
1705
2231
  installed,
1706
2232
  version,
2233
+ binary,
1707
2234
  loggedIn: false,
1708
2235
  billing: "unknown",
1709
2236
  plan: null,
@@ -1755,29 +2282,30 @@ function describeAuthStatus(provider, probe) {
1755
2282
  };
1756
2283
  }
1757
2284
  async function createCodingSessionsEngine(options = {}) {
1758
- const dataDir = options.dataDir ?? import_node_path6.default.join(import_node_os4.default.homedir(), ".matrx", "coding-sessions");
1759
- const stateDir = import_node_path6.default.join(dataDir, "paperclip-state");
2285
+ const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
2286
+ const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
1760
2287
  const homes = options.homes ?? defaultProviderHomes();
1761
2288
  const index = new RunIndex(dataDir);
1762
2289
  await index.load();
1763
2290
  const preferences = new PreferenceStore(dataDir);
1764
2291
  await preferences.load();
1765
- await import_promises6.default.mkdir(stateDir, { recursive: true });
2292
+ await import_promises8.default.mkdir(stateDir, { recursive: true });
1766
2293
  const identity = await index.identity();
2294
+ const agents = createAgentConnection(options.agents, dataDir);
1767
2295
  const backups = options.rawBackup ? createRawBackupScheduler({
1768
2296
  sink: options.rawBackup,
1769
2297
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1770
2298
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1771
2299
  }) : null;
1772
2300
  const discoveredSeen = /* @__PURE__ */ new Map();
1773
- const healthFile = import_node_path6.default.join(dataDir, "provider-health.json");
2301
+ const healthFile = import_node_path9.default.join(dataDir, "provider-health.json");
1774
2302
  let health = {};
1775
2303
  try {
1776
- health = JSON.parse(await import_promises6.default.readFile(healthFile, "utf8"));
2304
+ health = JSON.parse(await import_promises8.default.readFile(healthFile, "utf8"));
1777
2305
  } catch {
1778
2306
  health = {};
1779
2307
  }
1780
- const saveHealth = () => import_promises6.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2308
+ const saveHealth = () => import_promises8.default.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1781
2309
  function noteRunOutcome(provider, status, texts) {
1782
2310
  if (status === "completed") {
1783
2311
  if (health[provider]) {
@@ -1805,7 +2333,24 @@ async function createCodingSessionsEngine(options = {}) {
1805
2333
  // Marks the forked process as a driver worker (see worker.ts).
1806
2334
  MATRX_CODING_SESSIONS_WORKER: "1"
1807
2335
  });
1808
- const commandFor = (provider) => options.commands?.[provider] ?? whichOnPath(provider, workerEnv.PATH ?? "");
2336
+ const binaryHome = options.hostEnv?.HOME ?? import_node_os5.default.homedir();
2337
+ const binaryCache = /* @__PURE__ */ new Map();
2338
+ function binaryFor(provider, fresh = false) {
2339
+ const cached = binaryCache.get(provider);
2340
+ if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
2341
+ const forced = options.commands?.[provider];
2342
+ const value = resolveBinary(provider, {
2343
+ env: workerEnv,
2344
+ home: binaryHome,
2345
+ forced,
2346
+ // A forced command needs no discovery to run; the status check still
2347
+ // looks, so it can say when a newer copy exists.
2348
+ discover: !forced || fresh
2349
+ }).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
2350
+ if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
2351
+ return value;
2352
+ }
2353
+ const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
1809
2354
  const workerPath = resolveWorkerPath();
1810
2355
  const shimPath = await installShim(dataDir);
1811
2356
  const listeners = /* @__PURE__ */ new Set();
@@ -1855,7 +2400,7 @@ async function createCodingSessionsEngine(options = {}) {
1855
2400
  }
1856
2401
  }
1857
2402
  function forkWorker() {
1858
- return (0, import_node_child_process2.fork)(workerPath, [], {
2403
+ return (0, import_node_child_process3.fork)(workerPath, [], {
1859
2404
  env: workerEnv,
1860
2405
  cwd: dataDir,
1861
2406
  stdio: ["ignore", "pipe", "pipe", "ipc"],
@@ -1876,8 +2421,8 @@ async function createCodingSessionsEngine(options = {}) {
1876
2421
  }
1877
2422
  async function startRun(session, prompt) {
1878
2423
  const provider = session.provider;
1879
- const realCommand = commandFor(provider);
1880
- const runId = (0, import_node_crypto4.randomUUID)();
2424
+ const realCommand = await commandFor(provider);
2425
+ const runId = (0, import_node_crypto5.randomUUID)();
1881
2426
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1882
2427
  const run = {
1883
2428
  runId,
@@ -1915,7 +2460,26 @@ async function createCodingSessionsEngine(options = {}) {
1915
2460
  let fileOffset = null;
1916
2461
  if (session.providerSessionId) {
1917
2462
  const file = await findSessionFile(provider, session.providerSessionId, homes);
1918
- if (file) fileOffset = (await import_promises6.default.stat(file).catch(() => null))?.size ?? null;
2463
+ if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
2464
+ }
2465
+ let agentArgs = [];
2466
+ if (session.agent) {
2467
+ const bundle = await agents.readSessionBundle(session.id);
2468
+ if (!bundle) {
2469
+ const failed = saveRun({
2470
+ ...run,
2471
+ status: "failed",
2472
+ endedAt: nowIso(),
2473
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2474
+ });
2475
+ const current = index.session(session.id);
2476
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2477
+ return failed;
2478
+ }
2479
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2480
+ mcpUrl: await agents.mcpUrlFor(session.id)
2481
+ });
2482
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
1919
2483
  }
1920
2484
  const child = forkWorker();
1921
2485
  const liveRun = {
@@ -2089,7 +2653,8 @@ async function createCodingSessionsEngine(options = {}) {
2089
2653
  resumeSessionId: session.providerSessionId,
2090
2654
  shimPath,
2091
2655
  realCommand,
2092
- identity
2656
+ identity,
2657
+ agentArgs
2093
2658
  });
2094
2659
  return run;
2095
2660
  }
@@ -2108,7 +2673,7 @@ async function createCodingSessionsEngine(options = {}) {
2108
2673
  }, 3e3);
2109
2674
  }
2110
2675
  async function pruneDriverScratch() {
2111
- const runsDir = import_node_path6.default.join(
2676
+ const runsDir = import_node_path9.default.join(
2112
2677
  stateDir,
2113
2678
  "instances",
2114
2679
  "default",
@@ -2119,12 +2684,13 @@ async function createCodingSessionsEngine(options = {}) {
2119
2684
  "claude-runtime",
2120
2685
  "runs"
2121
2686
  );
2122
- const entries = await import_promises6.default.readdir(runsDir).catch(() => []);
2687
+ const entries = await import_promises8.default.readdir(runsDir).catch(() => []);
2123
2688
  await Promise.all(
2124
- entries.filter((name) => !live.has(name)).map((name) => import_promises6.default.rm(import_node_path6.default.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2689
+ 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))
2125
2690
  );
2126
2691
  }
2127
2692
  async function probe() {
2693
+ const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
2128
2694
  return new Promise((resolve) => {
2129
2695
  const child = forkWorker();
2130
2696
  let answered = false;
@@ -2140,8 +2706,8 @@ async function createCodingSessionsEngine(options = {}) {
2140
2706
  answered = true;
2141
2707
  clearTimeout(timer);
2142
2708
  resolve({
2143
- claude: message.claude ?? {},
2144
- codex: message.codex ?? {}
2709
+ claude: { ...message.claude ?? {}, binary: claudeBinary },
2710
+ codex: { ...message.codex ?? {}, binary: codexBinary }
2145
2711
  });
2146
2712
  }
2147
2713
  });
@@ -2153,7 +2719,7 @@ async function createCodingSessionsEngine(options = {}) {
2153
2719
  });
2154
2720
  child.send({
2155
2721
  kind: "probe",
2156
- commands: { claude: commandFor("claude"), codex: commandFor("codex") },
2722
+ commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
2157
2723
  quota: options.quota !== false
2158
2724
  });
2159
2725
  });
@@ -2161,7 +2727,7 @@ async function createCodingSessionsEngine(options = {}) {
2161
2727
  function newSession(input) {
2162
2728
  const at = nowIso();
2163
2729
  return {
2164
- id: (0, import_node_crypto4.randomUUID)(),
2730
+ id: (0, import_node_crypto5.randomUUID)(),
2165
2731
  provider: input.provider,
2166
2732
  providerSessionId: input.providerSessionId,
2167
2733
  cwd: input.cwd,
@@ -2179,17 +2745,17 @@ async function createCodingSessionsEngine(options = {}) {
2179
2745
  function resolveFolder(raw) {
2180
2746
  const trimmed = raw.trim();
2181
2747
  if (!trimmed) return null;
2182
- return import_node_path6.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os4.default.homedir()));
2748
+ return import_node_path9.default.resolve(trimmed.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2183
2749
  }
2184
2750
  async function checkFolder(raw) {
2185
2751
  const target = resolveFolder(raw);
2186
2752
  if (!target) return { path: "", exists: false, isDirectory: false };
2187
- const stat = await import_promises6.default.stat(target).catch(() => null);
2753
+ const stat = await import_promises8.default.stat(target).catch(() => null);
2188
2754
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2189
2755
  }
2190
2756
  async function assertFolder(cwd) {
2191
- const resolved = import_node_path6.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os4.default.homedir()));
2192
- const stat = await import_promises6.default.stat(resolved).catch(() => null);
2757
+ const resolved = import_node_path9.default.resolve(cwd.replace(/^~(?=$|\/)/, import_node_os5.default.homedir()));
2758
+ const stat = await import_promises8.default.stat(resolved).catch(() => null);
2193
2759
  if (!stat?.isDirectory()) {
2194
2760
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2195
2761
  }
@@ -2251,7 +2817,7 @@ async function createCodingSessionsEngine(options = {}) {
2251
2817
  folders.map(async ([folder, lastUsedAt]) => ({
2252
2818
  path: folder,
2253
2819
  lastUsedAt,
2254
- exists: (await import_promises6.default.stat(folder).catch(() => null))?.isDirectory() === true
2820
+ exists: (await import_promises8.default.stat(folder).catch(() => null))?.isDirectory() === true
2255
2821
  }))
2256
2822
  );
2257
2823
  },
@@ -2296,7 +2862,7 @@ async function createCodingSessionsEngine(options = {}) {
2296
2862
  async createFolder(raw) {
2297
2863
  const target = resolveFolder(raw);
2298
2864
  if (!target) throw new Error("Type a folder path first.");
2299
- await import_promises6.default.mkdir(target, { recursive: true });
2865
+ await import_promises8.default.mkdir(target, { recursive: true });
2300
2866
  return checkFolder(target);
2301
2867
  },
2302
2868
  async sessionActivity(provider, providerSessionId) {
@@ -2334,31 +2900,35 @@ async function createCodingSessionsEngine(options = {}) {
2334
2900
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2335
2901
  };
2336
2902
  }
2337
- const stat = await import_promises6.default.stat(file);
2903
+ const stat = await import_promises8.default.stat(file);
2338
2904
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2339
2905
  return {
2340
2906
  available: false,
2341
2907
  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}.`
2342
2908
  };
2343
2909
  }
2344
- const content = await import_promises6.default.readFile(file, "utf8");
2345
- return { available: true, fileName: import_node_path6.default.basename(file), bytes: stat.size, content };
2910
+ const content = await import_promises8.default.readFile(file, "utf8");
2911
+ return { available: true, fileName: import_node_path9.default.basename(file), bytes: stat.size, content };
2346
2912
  },
2347
2913
  async start(input) {
2348
2914
  const prompt = input.prompt.trim();
2349
2915
  if (!prompt) throw new Error("Type what you want done before starting.");
2350
2916
  const cwd = await assertFolder(input.cwd);
2351
- const session = saveSession(
2352
- newSession({
2353
- provider: input.provider,
2354
- cwd,
2355
- title: titleFrom(prompt),
2356
- model: input.model?.trim() || null,
2357
- effort: input.effort?.trim() || null,
2358
- origin: "started",
2359
- providerSessionId: null
2360
- })
2361
- );
2917
+ const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2918
+ const draft = newSession({
2919
+ provider: input.provider,
2920
+ cwd,
2921
+ title: titleFrom(prompt),
2922
+ model: input.model?.trim() || null,
2923
+ effort: input.effort?.trim() || null,
2924
+ origin: "started",
2925
+ providerSessionId: null
2926
+ });
2927
+ if (bundle) {
2928
+ await agents.saveSessionBundle(draft.id, bundle);
2929
+ draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2930
+ }
2931
+ const session = saveSession(draft);
2362
2932
  const run = await startRun(session, prompt);
2363
2933
  return { session: index.session(session.id) ?? session, run, queued: false };
2364
2934
  },
@@ -2427,6 +2997,21 @@ async function createCodingSessionsEngine(options = {}) {
2427
2997
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2428
2998
  return saveSession(next);
2429
2999
  },
3000
+ async agentAccess() {
3001
+ return agents.access();
3002
+ },
3003
+ async agentCatalogRpc(request) {
3004
+ return agents.catalogRpc(request);
3005
+ },
3006
+ async agentDetails(agentId) {
3007
+ return agents.details(String(agentId ?? ""));
3008
+ },
3009
+ setMcpBaseUrl(baseUrl) {
3010
+ agents.setMcpBaseUrl(baseUrl);
3011
+ },
3012
+ async handleMcp(secret, body) {
3013
+ return agents.handleMcp(secret, body);
3014
+ },
2430
3015
  async dispose() {
2431
3016
  for (const liveRun of live.values()) {
2432
3017
  liveRun.stopRequested = true;
@@ -2463,7 +3048,7 @@ function openFolderIn(folder, target) {
2463
3048
  args = ["-a", app.app, folder];
2464
3049
  }
2465
3050
  return new Promise((resolve, reject) => {
2466
- (0, import_node_child_process2.execFile)(command, args, { timeout: 1e4 }, (error) => {
3051
+ (0, import_node_child_process3.execFile)(command, args, { timeout: 1e4 }, (error) => {
2467
3052
  if (!error) return resolve();
2468
3053
  const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
2469
3054
  reject(