@ai-matrx/coding-sessions 0.1.83 → 0.1.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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,448 @@ 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
+
105
+ // src/normalize/agent-block.ts
106
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
107
+ function stripAgentBlock(text) {
108
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
109
+ if (removed) return text.slice(removed[0].length);
110
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
111
+ const end = text.indexOf("</ai_matrx_agent>");
112
+ if (end < 0) return text;
113
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
114
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
115
+ if (skills) rest = rest.slice(skills[0].length);
116
+ return rest.replace(/^\s+/, "");
117
+ }
118
+
119
+ // src/engine/agent-attach.ts
120
+ var AGENT_MCP_SERVER_NAME = "matrx-agent";
121
+ var AGENT_PLUGIN_NAME = "matrx-agent";
122
+ function escapeAttr(value) {
123
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
124
+ }
125
+ function versionWords(bundle) {
126
+ return bundle.version_number !== null ? String(bundle.version_number) : "latest";
127
+ }
128
+ function renderAgentInstructions(bundle, options = {}) {
129
+ const included = bundle.skills.filter((skill) => skill.tier === "included");
130
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
131
+ const parts = [`<ai_matrx_agent name="${escapeAttr(bundle.name)}" version="${versionWords(bundle)}">`];
132
+ if (included.length > 0) {
133
+ parts.push(
134
+ "## Skills (always in effect)",
135
+ ...included.map((skill) => `### ${skill.label}
136
+
137
+ ${skill.body.trim()}`)
138
+ );
139
+ }
140
+ if (options.claudeSkills && listed2.length > 0) {
141
+ parts.push(
142
+ [
143
+ "## Your skills",
144
+ "When a request matches one of these, load it with your Skill tool and follow it before answering:",
145
+ ...listed2.map((skill) => `- ${AGENT_PLUGIN_NAME}:${skill.slug} \u2014 ${skill.description || skill.label}`)
146
+ ].join("\n")
147
+ );
148
+ }
149
+ if (included.length > 0 || options.claudeSkills && listed2.length > 0) parts.push("## Your instructions");
150
+ parts.push(bundle.instructions.trim());
151
+ parts.push("</ai_matrx_agent>");
152
+ return `${parts.filter(Boolean).join("\n\n")}
153
+ `;
154
+ }
155
+ function renderCodexSkillsList(bundle, skillsRoot) {
156
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
157
+ if (listed2.length === 0) return "";
158
+ const lines = [
159
+ "<skills_instructions>",
160
+ "## AI Matrx agent skills",
161
+ "A skill is a set of instructions stored in a `SKILL.md` file. Each entry has a name, a description and the absolute path of its file. When a task matches a skill's description, read that file and follow it.",
162
+ "### Available skills",
163
+ ...listed2.map(
164
+ (skill) => `- ${skill.slug}: ${skill.description || skill.label} (file: ${path.join(skillsRoot, skill.slug, "SKILL.md")})`
165
+ ),
166
+ "</skills_instructions>"
167
+ ];
168
+ return lines.join("\n");
169
+ }
170
+ function renderFallbackBlock(bundle, skillsRoot) {
171
+ const skills = renderCodexSkillsList(bundle, skillsRoot);
172
+ const body = renderAgentInstructions(bundle).trim().replace(/^(<ai_matrx_agent [^>]*>)/, "$1\n\nThe instructions below are your AI Matrx agent for this chat from now on; they replace any AI Matrx agent instructions given earlier.");
173
+ return skills ? `${body}
174
+
175
+ ${skills}
176
+
177
+ ` : `${body}
178
+
179
+ `;
180
+ }
181
+ function tomlString(value) {
182
+ const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
183
+ return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
184
+ }
185
+ async function writeAgentAttachment(dir, bundle, options) {
186
+ const pluginDir = path.join(dir, "plugin");
187
+ const skillsRoot = path.join(dir, "skills");
188
+ await fs2.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true, mode: 448 });
189
+ await fs2.mkdir(skillsRoot, { recursive: true, mode: 448 });
190
+ const instructions = renderAgentInstructions(bundle);
191
+ const instructionsFile = path.join(dir, "instructions.md");
192
+ await fs2.writeFile(instructionsFile, renderAgentInstructions(bundle, { claudeSkills: true }), { mode: 384 });
193
+ await fs2.writeFile(
194
+ path.join(pluginDir, ".claude-plugin", "plugin.json"),
195
+ `${JSON.stringify(
196
+ {
197
+ name: AGENT_PLUGIN_NAME,
198
+ description: `Skills of the AI Matrx agent "${bundle.name}"`,
199
+ version: versionWords(bundle) === "latest" ? "1.0.0" : `${versionWords(bundle)}.0.0`
200
+ },
201
+ null,
202
+ 2
203
+ )}
204
+ `,
205
+ { mode: 384 }
206
+ );
207
+ const listed2 = bundle.skills.filter((skill) => skill.tier === "listed");
208
+ const wanted = new Set(listed2.map((skill) => skill.slug));
209
+ for (const root of [path.join(pluginDir, "skills"), skillsRoot]) {
210
+ await fs2.mkdir(root, { recursive: true, mode: 448 });
211
+ for (const existing of await fs2.readdir(root).catch(() => [])) {
212
+ if (!wanted.has(existing)) await fs2.rm(path.join(root, existing), { recursive: true, force: true });
213
+ }
214
+ for (const skill of listed2) {
215
+ const skillDir = path.join(root, skill.slug);
216
+ await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
217
+ await fs2.writeFile(path.join(skillDir, "SKILL.md"), skill.content, { mode: 384 });
218
+ }
219
+ }
220
+ let mcpConfigFile = null;
221
+ const mcpUrl = options.mcpUrl && bundle.tools.length > 0 ? options.mcpUrl : null;
222
+ const mcpPath = path.join(dir, "mcp.json");
223
+ if (mcpUrl) {
224
+ mcpConfigFile = mcpPath;
225
+ await fs2.writeFile(
226
+ mcpPath,
227
+ `${JSON.stringify({ mcpServers: { [AGENT_MCP_SERVER_NAME]: { type: "http", url: mcpUrl } } }, null, 2)}
228
+ `,
229
+ { mode: 384 }
230
+ );
231
+ } else {
232
+ await fs2.rm(mcpPath, { force: true });
233
+ }
234
+ const skillsList = renderCodexSkillsList(bundle, skillsRoot);
235
+ return {
236
+ dir,
237
+ instructionsFile,
238
+ pluginDir,
239
+ mcpConfigFile,
240
+ mcpUrl,
241
+ // Same order as Claude's: the skills list first, the agent's words last.
242
+ codexInstructions: skillsList ? `${skillsList}
243
+
244
+ ${instructions}` : instructions
245
+ };
246
+ }
247
+ function agentArgsFor(provider, attachment, options) {
248
+ if (provider === "claude") {
249
+ const args2 = [
250
+ // Every turn: without snapshot off, a resumed Claude drops appended instructions.
251
+ "--append-system-prompt-file",
252
+ attachment.instructionsFile,
253
+ "--system-prompt-snapshot",
254
+ "off",
255
+ "--plugin-dir",
256
+ attachment.pluginDir
257
+ ];
258
+ if (attachment.mcpConfigFile) args2.push("--mcp-config", attachment.mcpConfigFile);
259
+ return args2;
260
+ }
261
+ const args = [];
262
+ if (!options.resuming) args.push("-c", `developer_instructions=${tomlString(attachment.codexInstructions)}`);
263
+ if (attachment.mcpUrl) {
264
+ const key = `mcp_servers.${AGENT_MCP_SERVER_NAME}`;
265
+ args.push(
266
+ "-c",
267
+ `${key}.url=${tomlString(attachment.mcpUrl)}`,
268
+ "-c",
269
+ `${key}.default_tools_approval_mode="approve"`
270
+ );
271
+ }
272
+ return args;
273
+ }
274
+ function sessionAgentRecord(bundle, options) {
275
+ const unavailable = bundle.unavailable.map((u) => ({ kind: u.kind, name: u.name, reason: u.reason }));
276
+ const missingSkills = unavailable.filter((u) => u.kind === "skill").length;
277
+ const missingTools = unavailable.filter((u) => u.kind === "tool").length;
278
+ if (!options.toolsServed && bundle.tools.length > 0) {
279
+ for (const tool of bundle.tools) {
280
+ unavailable.push({ kind: "tool", name: tool.canonical_name, reason: "Tools need the coding sessions host" });
281
+ }
282
+ }
283
+ const toolsLoaded = options.toolsServed ? bundle.tools.length : 0;
284
+ const loaded = [
285
+ { part: "instructions", loaded: bundle.instructions.trim() ? 1 : 0, total: 1 },
286
+ { part: "skills", loaded: bundle.skills.length, total: bundle.skills.length + missingSkills },
287
+ { part: "tools", loaded: toolsLoaded, total: bundle.tools.length + missingTools }
288
+ ];
289
+ return {
290
+ agentId: bundle.agent_id,
291
+ versionId: bundle.version_id,
292
+ versionNumber: bundle.version_number,
293
+ name: bundle.name,
294
+ definitionHash: bundle.definition_hash,
295
+ loaded,
296
+ unavailable,
297
+ skills: bundle.skills.map((skill) => skill.label),
298
+ tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
299
+ ...options.variables ? { variables: options.variables } : {}
300
+ };
301
+ }
302
+ var AGENT_REMOVED_BLOCK = "<ai_matrx_agent_removed>The AI Matrx agent instructions given earlier in this chat no longer apply.</ai_matrx_agent_removed>\n\n";
303
+ async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
304
+ const text = await fs2.readFile(rolloutFile, "utf8").catch(() => "");
305
+ if (!text.includes('"type":"compacted"')) return false;
306
+ let latest = null;
307
+ for (const line of text.split("\n")) {
308
+ if (!line.includes('"type":"compacted"')) continue;
309
+ try {
310
+ const entry = JSON.parse(line);
311
+ if (entry.type === "compacted") latest = entry;
312
+ } catch {
313
+ }
314
+ }
315
+ if (!latest) return false;
316
+ if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
317
+ return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
318
+ }
319
+
320
+ // src/engine/agents.ts
321
+ import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
322
+ import fs3 from "fs/promises";
323
+ import path2 from "path";
324
+ var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
325
+ var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
326
+ var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
327
+ var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
328
+ "agx_get_list_full",
329
+ "agx_search",
330
+ "agx_resolve_agent_address",
331
+ "ues_get_bulk",
332
+ "ues_list",
333
+ "ues_set",
334
+ "ues_touch"
335
+ ]);
336
+ var agentsClient = async () => {
337
+ const [matrx, portable, mcp] = await Promise.all([
338
+ import("@ai-matrx/agents/matrx"),
339
+ import("@ai-matrx/agents/portable"),
340
+ import("@ai-matrx/agents/portable/mcp")
341
+ ]);
342
+ return { ...matrx, ...portable, ...mcp };
343
+ };
344
+ function sortedValues(values) {
345
+ return Object.keys(values).sort().map((key) => [key, values[key]]);
346
+ }
347
+ var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
348
+ var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
349
+ function userIdOf(credential) {
350
+ if (!credential || credential.kind !== "user") return null;
351
+ const payload = credential.accessToken.split(".")[1];
352
+ if (!payload) return null;
353
+ try {
354
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
355
+ return typeof claims.sub === "string" ? claims.sub : null;
356
+ } catch {
357
+ return null;
358
+ }
359
+ }
360
+ function createAgentConnection(options, dataDir) {
361
+ const credentials = options?.credentials ?? null;
362
+ const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
363
+ const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
364
+ const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
365
+ const root = path2.join(dataDir, "agents");
366
+ let mcpBaseUrl = null;
367
+ const handlers = /* @__PURE__ */ new Map();
368
+ const secretToSession = /* @__PURE__ */ new Map();
369
+ const organizationId = async () => {
370
+ const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
371
+ return raw && raw.trim() ? raw.trim() : null;
372
+ };
373
+ const transport = async () => {
374
+ const org = await organizationId();
375
+ return (await agentsClient()).createMatrxTransport({
376
+ baseUrl: serverUrl,
377
+ ...credentials ? { credentials } : {},
378
+ ...org ? { organizationId: org } : {},
379
+ source: "codingSessionsAgents"
380
+ });
381
+ };
382
+ const cacheDir = path2.join(dataDir, "agents", ".bundles");
383
+ const cacheKey = (definitionHash, selection, surface) => createHash2("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
384
+ const credential = async () => credentials ? credentials.get() : null;
385
+ const requireSignedIn = async () => {
386
+ if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
387
+ };
388
+ const sessionDir = (sessionId) => path2.join(root, sessionId.replace(/[^A-Za-z0-9_-]/g, "_"));
389
+ async function meta(sessionId) {
390
+ const file = path2.join(sessionDir(sessionId), "session.json");
391
+ const existing = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
392
+ if (existing?.mcpSecret && existing.conversationId) return existing;
393
+ const created = { mcpSecret: randomBytes(24).toString("base64url"), conversationId: randomUUID() };
394
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
395
+ await fs3.writeFile(file, JSON.stringify(created), { mode: 384 });
396
+ return created;
397
+ }
398
+ async function findSessionBySecret(secret) {
399
+ for (const name of await fs3.readdir(root).catch(() => [])) {
400
+ const found = await fs3.readFile(path2.join(root, name, "session.json"), "utf8").then((text) => JSON.parse(text).mcpSecret === secret).catch(() => false);
401
+ if (found) {
402
+ secretToSession.set(secret, name);
403
+ return name;
404
+ }
405
+ }
406
+ return null;
407
+ }
408
+ async function readSessionBundle(sessionId) {
409
+ return fs3.readFile(path2.join(sessionDir(sessionId), "bundle.json"), "utf8").then((text) => JSON.parse(text)).catch(() => null);
410
+ }
411
+ return {
412
+ async access() {
413
+ const current = await credential().catch(() => null);
414
+ const userId = userIdOf(current);
415
+ return {
416
+ configured: credentials !== null && supabaseKey !== null,
417
+ signedIn: userId !== null,
418
+ userId,
419
+ organizationId: await organizationId().catch(() => null)
420
+ };
421
+ },
422
+ async catalogRpc(request) {
423
+ if (!AGENT_CATALOG_RPCS.has(request.fn)) {
424
+ return { data: null, error: { message: `Not an agent catalogue read: ${request.fn}` } };
425
+ }
426
+ const current = await credential();
427
+ if (!current || current.kind !== "user") return { data: null, error: { message: SIGNED_OUT_AGENT_MESSAGE, code: "signed_out" } };
428
+ if (!supabaseKey) return { data: null, error: { message: "This host has no AI Matrx database key.", code: "not_configured" } };
429
+ const query = new URLSearchParams();
430
+ if (request.order) query.set("order", `${request.order.column}.${request.order.ascending === false ? "desc" : "asc"}`);
431
+ if (request.range) {
432
+ query.set("offset", String(request.range[0]));
433
+ query.set("limit", String(request.range[1] - request.range[0] + 1));
434
+ }
435
+ const headers = {
436
+ apikey: supabaseKey,
437
+ Authorization: `Bearer ${current.accessToken}`,
438
+ "Content-Type": "application/json",
439
+ // Every catalogue RPC lives in `public`; the database's default
440
+ // exposed schema is not `public`, so name it (supabase-js does).
441
+ "Content-Profile": "public",
442
+ "Accept-Profile": "public"
443
+ };
444
+ if (request.count) headers.Prefer = `count=${request.count}`;
445
+ const qs = query.toString();
446
+ const response = await fetch(`${supabaseUrl}/rest/v1/rpc/${encodeURIComponent(request.fn)}${qs ? `?${qs}` : ""}`, {
447
+ method: "POST",
448
+ headers,
449
+ body: JSON.stringify(request.args ?? {})
450
+ });
451
+ const text = await response.text();
452
+ const body = text ? JSON.parse(text) : null;
453
+ if (!response.ok) {
454
+ const err = body ?? {};
455
+ return {
456
+ data: null,
457
+ error: { message: err.message ?? `HTTP ${response.status}`, code: err.code, details: err.details ?? null, hint: err.hint ?? null }
458
+ };
459
+ }
460
+ const range = response.headers.get("content-range");
461
+ const total = range?.split("/")[1];
462
+ return { data: body, error: null, count: total && total !== "*" ? Number(total) : null };
463
+ },
464
+ async details(agentId) {
465
+ await requireSignedIn();
466
+ const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
467
+ return {
468
+ agentId: summary.agent_id,
469
+ name: summary.name,
470
+ variables: summary.variables,
471
+ versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
472
+ latestVersionNumber: summary.version_number,
473
+ latestVersionId: summary.version_id
474
+ };
475
+ },
476
+ async loadBundle(selection) {
477
+ await requireSignedIn();
478
+ const client = await agentsClient();
479
+ const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
480
+ const versionId = selection.versionId ?? summary.version_id;
481
+ const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
482
+ const surface = "matrx-user/code-editor";
483
+ const file = definitionHash ? path2.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
484
+ if (file) {
485
+ const cached = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
486
+ const fresh = await fs3.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
487
+ if (cached && fresh && cached.definition_hash === definitionHash) return cached;
488
+ }
489
+ const bundle = await client.fetchPortableAgent(
490
+ await transport(),
491
+ versionId ? { versionId } : { agentId: selection.agentId },
492
+ { variables: selection.variables ?? {}, surface }
493
+ );
494
+ if (file && bundle.definition_hash === definitionHash) {
495
+ await fs3.mkdir(cacheDir, { recursive: true, mode: 448 });
496
+ await fs3.writeFile(file, JSON.stringify(bundle), { mode: 384 });
497
+ }
498
+ return bundle;
499
+ },
500
+ async saveSessionBundle(sessionId, bundle) {
501
+ await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
502
+ await fs3.writeFile(path2.join(sessionDir(sessionId), "bundle.json"), JSON.stringify(bundle), { mode: 384 });
503
+ await meta(sessionId);
504
+ },
505
+ readSessionBundle,
506
+ sessionDir,
507
+ async mcpUrlFor(sessionId) {
508
+ if (!mcpBaseUrl) return null;
509
+ const { mcpSecret } = await meta(sessionId);
510
+ secretToSession.set(mcpSecret, sessionId);
511
+ return `${mcpBaseUrl}/mcp/${mcpSecret}`;
512
+ },
513
+ setMcpBaseUrl(baseUrl) {
514
+ mcpBaseUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : null;
515
+ },
516
+ async handleMcp(secret, body) {
517
+ let handler = handlers.get(secret);
518
+ if (!handler) {
519
+ const sessionId = secretToSession.get(secret) ?? await findSessionBySecret(secret);
520
+ if (!sessionId) return null;
521
+ const bundle = await readSessionBundle(sessionId);
522
+ if (!bundle) return null;
523
+ const { conversationId } = await meta(sessionId);
524
+ const { createAgentToolsMcpHandler } = await agentsClient();
525
+ handler = createAgentToolsMcpHandler({
526
+ bundle,
527
+ credentials,
528
+ organizationId: () => organizationId(),
529
+ baseUrl: serverUrl,
530
+ conversationId
531
+ });
532
+ handlers.set(secret, handler);
533
+ }
534
+ return handler.handle(body);
535
+ }
536
+ };
537
+ }
538
+
101
539
  // src/engine/index.ts
102
- import fs6 from "fs/promises";
540
+ import fs8 from "fs/promises";
103
541
  import os5 from "os";
104
- import path7 from "path";
542
+ import path9 from "path";
105
543
  import { fileURLToPath } from "url";
106
544
 
107
545
  // src/normalize/claude.ts
@@ -472,7 +910,7 @@ function rolloutItem(item, fallbackId, at) {
472
910
  const id = str2(item.id) ?? fallbackId;
473
911
  switch (item.type) {
474
912
  case "UserMessage": {
475
- const text = stripCodexInjectedContext(itemText(item.content));
913
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
476
914
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
477
915
  }
478
916
  case "AgentMessage": {
@@ -555,7 +993,7 @@ function createCodexRolloutNormalizer() {
555
993
  return rolloutItem(payload.item, fallbackId, at);
556
994
  }
557
995
  if (payload.type === "user_message" && !appItems) {
558
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
996
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
559
997
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
560
998
  }
561
999
  return [];
@@ -771,7 +1209,7 @@ function describeProblem(provider, problem) {
771
1209
  import { execFile } from "child_process";
772
1210
  import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
773
1211
  import os from "os";
774
- import path from "path";
1212
+ import path3 from "path";
775
1213
  var SOURCE_LABEL = {
776
1214
  codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
777
1215
  claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
@@ -820,10 +1258,10 @@ function exeName(name, platform) {
820
1258
  }
821
1259
  function allOnPath(name, pathValue, platform = process.platform) {
822
1260
  const found = [];
823
- for (const dir of pathValue.split(path.delimiter)) {
1261
+ for (const dir of pathValue.split(path3.delimiter)) {
824
1262
  if (!dir) continue;
825
1263
  for (const file of exeName(name, platform)) {
826
- const candidate = path.join(dir, file);
1264
+ const candidate = path3.join(dir, file);
827
1265
  if (executable(candidate)) {
828
1266
  found.push(candidate);
829
1267
  break;
@@ -836,17 +1274,17 @@ function desktopAppCandidates(provider, platform, home, applications) {
836
1274
  if (platform !== "darwin") return [];
837
1275
  if (provider === "codex") {
838
1276
  return applications.map(
839
- (apps) => path.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
1277
+ (apps) => path3.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
840
1278
  );
841
1279
  }
842
- const root = path.join(home, "Library", "Application Support", "Claude", "claude-code");
1280
+ const root = path3.join(home, "Library", "Application Support", "Claude", "claude-code");
843
1281
  let versions = [];
844
1282
  try {
845
1283
  versions = readdirSync(root);
846
1284
  } catch {
847
1285
  return [];
848
1286
  }
849
- return versions.map((v) => path.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
1287
+ return versions.map((v) => path3.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
850
1288
  }
851
1289
  function npmGlobalPrefix(env) {
852
1290
  const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
@@ -864,13 +1302,13 @@ function declaredVersion(provider, source, file) {
864
1302
  if (source !== "desktop-app") return null;
865
1303
  if (provider === "codex") {
866
1304
  try {
867
- const pkg = JSON.parse(readFileSync(path.join(path.dirname(path.dirname(file)), "codex-package.json"), "utf8"));
1305
+ const pkg = JSON.parse(readFileSync(path3.join(path3.dirname(path3.dirname(file)), "codex-package.json"), "utf8"));
868
1306
  return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
869
1307
  } catch {
870
1308
  return null;
871
1309
  }
872
1310
  }
873
- return parseVersion(path.basename(path.resolve(file, "..", "..", "..", "..")));
1311
+ return parseVersion(path3.basename(path3.resolve(file, "..", "..", "..", "..")));
874
1312
  }
875
1313
  function runVersion(file, env) {
876
1314
  return new Promise((resolve) => {
@@ -884,14 +1322,14 @@ async function discoverBinaries(provider, options) {
884
1322
  const platform = options.platform ?? process.platform;
885
1323
  const home = options.home ?? options.env.HOME ?? os.homedir();
886
1324
  const listed2 = [
887
- ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
1325
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path3.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
888
1326
  ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
889
1327
  ];
890
1328
  const prefix = await npmGlobalPrefix(options.env);
891
1329
  if (prefix) {
892
- const dir = platform === "win32" ? prefix : path.join(prefix, "bin");
1330
+ const dir = platform === "win32" ? prefix : path3.join(prefix, "bin");
893
1331
  for (const file of exeName(provider, platform)) {
894
- const candidate = path.join(dir, file);
1332
+ const candidate = path3.join(dir, file);
895
1333
  if (executable(candidate)) {
896
1334
  listed2.push({ path: candidate, source: "npm-global" });
897
1335
  break;
@@ -937,7 +1375,7 @@ async function resolveBinary(provider, options) {
937
1375
 
938
1376
  // src/engine/clean-env.ts
939
1377
  import os2 from "os";
940
- import path2 from "path";
1378
+ import path4 from "path";
941
1379
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
942
1380
  "HOME",
943
1381
  "USER",
@@ -959,11 +1397,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
959
1397
  var ALLOWED_PREFIXES = ["LC_"];
960
1398
  function wellKnownBinDirs(home) {
961
1399
  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"),
1400
+ path4.join(home, ".local", "bin"),
1401
+ path4.join(home, ".claude", "local"),
1402
+ path4.join(home, ".npm-global", "bin"),
1403
+ path4.join(home, ".bun", "bin"),
1404
+ path4.join(home, ".volta", "bin"),
967
1405
  "/opt/homebrew/bin",
968
1406
  "/usr/local/bin",
969
1407
  "/usr/bin",
@@ -983,21 +1421,21 @@ function buildCleanEnv(hostEnv, extra = {}) {
983
1421
  }
984
1422
  const home = out.HOME ?? os2.homedir();
985
1423
  out.HOME = home;
986
- const hostPath = (hostEnv.PATH ?? "").split(path2.delimiter).filter(Boolean);
1424
+ const hostPath = (hostEnv.PATH ?? "").split(path4.delimiter).filter(Boolean);
987
1425
  const merged = [];
988
1426
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
989
1427
  if (!merged.includes(dir)) merged.push(dir);
990
1428
  }
991
- out.PATH = merged.join(path2.delimiter);
1429
+ out.PATH = merged.join(path4.delimiter);
992
1430
  for (const [name, value] of Object.entries(extra)) out[name] = value;
993
1431
  return out;
994
1432
  }
995
1433
 
996
1434
  // src/engine/files.ts
997
1435
  import { createReadStream } from "fs";
998
- import fs2 from "fs/promises";
1436
+ import fs4 from "fs/promises";
999
1437
  import os3 from "os";
1000
- import path3 from "path";
1438
+ import path5 from "path";
1001
1439
 
1002
1440
  // src/engine/activity.ts
1003
1441
  import { execFile as execFile2 } from "child_process";
@@ -1066,15 +1504,15 @@ function describeActivity(input) {
1066
1504
  // src/engine/files.ts
1067
1505
  function defaultProviderHomes(home = os3.homedir()) {
1068
1506
  return {
1069
- claudeProjects: path3.join(home, ".claude", "projects"),
1070
- codexSessions: path3.join(home, ".codex", "sessions"),
1071
- codexIndex: path3.join(home, ".codex", "session_index.jsonl")
1507
+ claudeProjects: path5.join(home, ".claude", "projects"),
1508
+ codexSessions: path5.join(home, ".codex", "sessions"),
1509
+ codexIndex: path5.join(home, ".codex", "session_index.jsonl")
1072
1510
  };
1073
1511
  }
1074
1512
  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
1513
  async function safeReaddir(dir) {
1076
1514
  try {
1077
- return await fs2.readdir(dir, { withFileTypes: true });
1515
+ return await fs4.readdir(dir, { withFileTypes: true });
1078
1516
  } catch {
1079
1517
  return [];
1080
1518
  }
@@ -1083,7 +1521,7 @@ async function statCandidate(provider, file) {
1083
1521
  const match = UUIDISH.exec(file);
1084
1522
  if (!match?.[1]) return null;
1085
1523
  try {
1086
- const stat = await fs2.stat(file);
1524
+ const stat = await fs4.stat(file);
1087
1525
  return { provider, file, id: match[1], mtimeMs: stat.mtimeMs, size: stat.size };
1088
1526
  } catch {
1089
1527
  return null;
@@ -1093,10 +1531,10 @@ async function claudeCandidates(root) {
1093
1531
  const out = [];
1094
1532
  for (const project of await safeReaddir(root)) {
1095
1533
  if (!project.isDirectory()) continue;
1096
- const dir = path3.join(root, project.name);
1534
+ const dir = path5.join(root, project.name);
1097
1535
  const entries = await safeReaddir(dir);
1098
1536
  const stats = await Promise.all(
1099
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path3.join(dir, e.name)))
1537
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path5.join(dir, e.name)))
1100
1538
  );
1101
1539
  for (const s of stats) if (s) out.push(s);
1102
1540
  }
@@ -1106,14 +1544,14 @@ async function codexCandidates(root) {
1106
1544
  const out = [];
1107
1545
  for (const year of await safeReaddir(root)) {
1108
1546
  if (!year.isDirectory()) continue;
1109
- for (const month of await safeReaddir(path3.join(root, year.name))) {
1547
+ for (const month of await safeReaddir(path5.join(root, year.name))) {
1110
1548
  if (!month.isDirectory()) continue;
1111
- for (const day of await safeReaddir(path3.join(root, year.name, month.name))) {
1549
+ for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
1112
1550
  if (!day.isDirectory()) continue;
1113
- const dir = path3.join(root, year.name, month.name, day.name);
1551
+ const dir = path5.join(root, year.name, month.name, day.name);
1114
1552
  const entries = await safeReaddir(dir);
1115
1553
  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)))
1554
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
1117
1555
  );
1118
1556
  for (const s of stats) if (s) out.push(s);
1119
1557
  }
@@ -1122,7 +1560,7 @@ async function codexCandidates(root) {
1122
1560
  return out;
1123
1561
  }
1124
1562
  async function readSlice(file, start, length) {
1125
- const handle = await fs2.open(file, "r");
1563
+ const handle = await fs4.open(file, "r");
1126
1564
  try {
1127
1565
  const buffer = Buffer.alloc(length);
1128
1566
  const { bytesRead } = await handle.read(buffer, 0, length, start);
@@ -1195,9 +1633,9 @@ async function readCodexIndex(file) {
1195
1633
  const names = /* @__PURE__ */ new Map();
1196
1634
  let text;
1197
1635
  try {
1198
- const stat = await fs2.stat(file);
1636
+ const stat = await fs4.stat(file);
1199
1637
  const budget = 4 * 1024 * 1024;
1200
- text = stat.size <= budget ? await fs2.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1638
+ text = stat.size <= budget ? await fs4.readFile(file, "utf8") : completeLines(await readSlice(file, stat.size - budget, budget), true);
1201
1639
  } catch {
1202
1640
  return names;
1203
1641
  }
@@ -1318,7 +1756,7 @@ var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
1318
1756
  async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUDGET) {
1319
1757
  let size;
1320
1758
  try {
1321
- size = (await fs2.stat(file)).size;
1759
+ size = (await fs4.stat(file)).size;
1322
1760
  } catch {
1323
1761
  return {
1324
1762
  items: [],
@@ -1372,9 +1810,9 @@ async function describeSessionFile(candidate, codexNames) {
1372
1810
  }
1373
1811
 
1374
1812
  // src/engine/preferences.ts
1375
- import { randomUUID } from "crypto";
1376
- import fs3 from "fs/promises";
1377
- import path4 from "path";
1813
+ import { randomUUID as randomUUID2 } from "crypto";
1814
+ import fs5 from "fs/promises";
1815
+ import path6 from "path";
1378
1816
  var PreferenceStore = class {
1379
1817
  constructor(dir) {
1380
1818
  this.dir = dir;
@@ -1383,11 +1821,11 @@ var PreferenceStore = class {
1383
1821
  data = {};
1384
1822
  writing = Promise.resolve();
1385
1823
  get file() {
1386
- return path4.join(this.dir, "preferences.json");
1824
+ return path6.join(this.dir, "preferences.json");
1387
1825
  }
1388
1826
  async load() {
1389
1827
  try {
1390
- const parsed = JSON.parse(await fs3.readFile(this.file, "utf8"));
1828
+ const parsed = JSON.parse(await fs5.readFile(this.file, "utf8"));
1391
1829
  this.data = typeof parsed === "object" && parsed !== null ? parsed : {};
1392
1830
  } catch {
1393
1831
  this.data = {};
@@ -1425,10 +1863,10 @@ var PreferenceStore = class {
1425
1863
  persist() {
1426
1864
  const snapshot = JSON.stringify(this.data, null, 1);
1427
1865
  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);
1866
+ await fs5.mkdir(this.dir, { recursive: true });
1867
+ const tmp = `${this.file}.${randomUUID2()}.tmp`;
1868
+ await fs5.writeFile(tmp, snapshot, { mode: 384 });
1869
+ await fs5.rename(tmp, this.file);
1432
1870
  }).catch(() => {
1433
1871
  });
1434
1872
  }
@@ -1585,9 +2023,9 @@ async function searchLocalChats(query, options = {}) {
1585
2023
  }
1586
2024
 
1587
2025
  // src/engine/store.ts
1588
- import { randomUUID as randomUUID2 } from "crypto";
1589
- import fs4 from "fs/promises";
1590
- import path5 from "path";
2026
+ import { randomUUID as randomUUID3 } from "crypto";
2027
+ import fs6 from "fs/promises";
2028
+ import path7 from "path";
1591
2029
  var MAX_RUNS = 2e3;
1592
2030
  var RunIndex = class {
1593
2031
  constructor(dir) {
@@ -1597,12 +2035,12 @@ var RunIndex = class {
1597
2035
  data = { version: 1, sessions: [], runs: [] };
1598
2036
  writing = Promise.resolve();
1599
2037
  get file() {
1600
- return path5.join(this.dir, "index.json");
2038
+ return path7.join(this.dir, "index.json");
1601
2039
  }
1602
2040
  async load() {
1603
- await fs4.mkdir(this.dir, { recursive: true });
2041
+ await fs6.mkdir(this.dir, { recursive: true });
1604
2042
  try {
1605
- const parsed = JSON.parse(await fs4.readFile(this.file, "utf8"));
2043
+ const parsed = JSON.parse(await fs6.readFile(this.file, "utf8"));
1606
2044
  this.data = {
1607
2045
  version: 1,
1608
2046
  sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
@@ -1644,9 +2082,9 @@ var RunIndex = class {
1644
2082
  persist() {
1645
2083
  const snapshot = JSON.stringify(this.data, null, 1);
1646
2084
  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);
2085
+ const tmp = `${this.file}.${randomUUID3()}.tmp`;
2086
+ await fs6.writeFile(tmp, snapshot, { mode: 384 });
2087
+ await fs6.rename(tmp, this.file);
1650
2088
  }).catch(() => {
1651
2089
  });
1652
2090
  }
@@ -1656,27 +2094,27 @@ var RunIndex = class {
1656
2094
  /** A stable Paperclip agent/company identity for this install, so the
1657
2095
  * driver's scratch state is reused rather than growing per run. */
1658
2096
  async identity() {
1659
- const file = path5.join(this.dir, "identity.json");
2097
+ const file = path7.join(this.dir, "identity.json");
1660
2098
  try {
1661
- const parsed = JSON.parse(await fs4.readFile(file, "utf8"));
2099
+ const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
1662
2100
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
1663
2101
  return { companyId: parsed.companyId, agentId: parsed.agentId };
1664
2102
  }
1665
2103
  } catch {
1666
2104
  }
1667
- const identity = { companyId: randomUUID2(), agentId: randomUUID2() };
1668
- await fs4.writeFile(file, JSON.stringify(identity), { mode: 384 });
2105
+ const identity = { companyId: randomUUID3(), agentId: randomUUID3() };
2106
+ await fs6.writeFile(file, JSON.stringify(identity), { mode: 384 });
1669
2107
  return identity;
1670
2108
  }
1671
2109
  };
1672
2110
 
1673
2111
  // src/engine/user-defaults.ts
1674
- import fs5 from "fs/promises";
2112
+ import fs7 from "fs/promises";
1675
2113
  import os4 from "os";
1676
- import path6 from "path";
2114
+ import path8 from "path";
1677
2115
  async function readJson(file) {
1678
2116
  try {
1679
- const parsed = JSON.parse(await fs5.readFile(file, "utf8"));
2117
+ const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
1680
2118
  return typeof parsed === "object" && parsed !== null ? parsed : null;
1681
2119
  } catch {
1682
2120
  return null;
@@ -1687,8 +2125,8 @@ function str3(value) {
1687
2125
  }
1688
2126
  async function claudeDefaults(cwd, home = os4.homedir()) {
1689
2127
  const files = [
1690
- ...cwd ? [path6.join(cwd, ".claude", "settings.local.json"), path6.join(cwd, ".claude", "settings.json")] : [],
1691
- path6.join(home, ".claude", "settings.json")
2128
+ ...cwd ? [path8.join(cwd, ".claude", "settings.local.json"), path8.join(cwd, ".claude", "settings.json")] : [],
2129
+ path8.join(home, ".claude", "settings.json")
1692
2130
  ];
1693
2131
  let model = null;
1694
2132
  let effort = null;
@@ -1704,7 +2142,7 @@ async function claudeDefaults(cwd, home = os4.homedir()) {
1704
2142
  async function codexDefaults(home = os4.homedir()) {
1705
2143
  let text;
1706
2144
  try {
1707
- text = await fs5.readFile(path6.join(home, ".codex", "config.toml"), "utf8");
2145
+ text = await fs7.readFile(path8.join(home, ".codex", "config.toml"), "utf8");
1708
2146
  } catch {
1709
2147
  return { model: null, effort: null };
1710
2148
  }
@@ -1728,31 +2166,31 @@ var EFFORT_LEVELS = {
1728
2166
  // src/engine/index.ts
1729
2167
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1730
2168
  function moduleDir() {
1731
- return path7.dirname(fileURLToPath(import.meta.url));
2169
+ return path9.dirname(fileURLToPath(import.meta.url));
1732
2170
  }
1733
2171
  function resolveWorkerPath() {
1734
2172
  const dir = moduleDir();
1735
2173
  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")
2174
+ path9.join(dir, "worker.js"),
2175
+ path9.join(dir, "worker.ts"),
2176
+ path9.join(dir, "..", "engine", "worker.js"),
2177
+ path9.join(dir, "..", "engine", "worker.ts")
1740
2178
  ]) {
1741
2179
  if (existsSync(candidate)) return candidate;
1742
2180
  }
1743
2181
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1744
2182
  }
1745
2183
  async function installShim(dataDir) {
1746
- const source = path7.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
2184
+ const source = path9.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1747
2185
  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);
2186
+ const body = await fs8.readFile(source, "utf8");
2187
+ const target = path9.join(dataDir, "bin", "cli-shim.sh");
2188
+ const current = await fs8.readFile(target, "utf8").catch(() => null);
1751
2189
  if (current !== body) {
1752
- await fs6.mkdir(path7.dirname(target), { recursive: true });
1753
- await fs6.writeFile(target, body, { mode: 493 });
2190
+ await fs8.mkdir(path9.dirname(target), { recursive: true });
2191
+ await fs8.writeFile(target, body, { mode: 493 });
1754
2192
  }
1755
- await fs6.chmod(target, 493);
2193
+ await fs8.chmod(target, 493);
1756
2194
  return target;
1757
2195
  }
1758
2196
  function nowIso() {
@@ -1852,29 +2290,30 @@ function describeAuthStatus(provider, probe) {
1852
2290
  };
1853
2291
  }
1854
2292
  async function createCodingSessionsEngine(options = {}) {
1855
- const dataDir = options.dataDir ?? path7.join(os5.homedir(), ".matrx", "coding-sessions");
1856
- const stateDir = path7.join(dataDir, "paperclip-state");
2293
+ const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
2294
+ const stateDir = path9.join(dataDir, "paperclip-state");
1857
2295
  const homes = options.homes ?? defaultProviderHomes();
1858
2296
  const index = new RunIndex(dataDir);
1859
2297
  await index.load();
1860
2298
  const preferences = new PreferenceStore(dataDir);
1861
2299
  await preferences.load();
1862
- await fs6.mkdir(stateDir, { recursive: true });
2300
+ await fs8.mkdir(stateDir, { recursive: true });
1863
2301
  const identity = await index.identity();
2302
+ const agents = createAgentConnection(options.agents, dataDir);
1864
2303
  const backups = options.rawBackup ? createRawBackupScheduler({
1865
2304
  sink: options.rawBackup,
1866
2305
  findFile: (provider, providerSessionId) => findSessionFile(provider, providerSessionId, homes),
1867
2306
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1868
2307
  }) : null;
1869
2308
  const discoveredSeen = /* @__PURE__ */ new Map();
1870
- const healthFile = path7.join(dataDir, "provider-health.json");
2309
+ const healthFile = path9.join(dataDir, "provider-health.json");
1871
2310
  let health = {};
1872
2311
  try {
1873
- health = JSON.parse(await fs6.readFile(healthFile, "utf8"));
2312
+ health = JSON.parse(await fs8.readFile(healthFile, "utf8"));
1874
2313
  } catch {
1875
2314
  health = {};
1876
2315
  }
1877
- const saveHealth = () => fs6.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
2316
+ const saveHealth = () => fs8.writeFile(healthFile, JSON.stringify(health), { mode: 384 }).catch(() => void 0);
1878
2317
  function noteRunOutcome(provider, status, texts) {
1879
2318
  if (status === "completed") {
1880
2319
  if (health[provider]) {
@@ -1991,7 +2430,7 @@ async function createCodingSessionsEngine(options = {}) {
1991
2430
  async function startRun(session, prompt) {
1992
2431
  const provider = session.provider;
1993
2432
  const realCommand = await commandFor(provider);
1994
- const runId = randomUUID3();
2433
+ const runId = randomUUID4();
1995
2434
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1996
2435
  const run = {
1997
2436
  runId,
@@ -2029,7 +2468,36 @@ async function createCodingSessionsEngine(options = {}) {
2029
2468
  let fileOffset = null;
2030
2469
  if (session.providerSessionId) {
2031
2470
  const file = await findSessionFile(provider, session.providerSessionId, homes);
2032
- if (file) fileOffset = (await fs6.stat(file).catch(() => null))?.size ?? null;
2471
+ if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
2472
+ }
2473
+ let agentArgs = [];
2474
+ let cliPrompt = prompt;
2475
+ let delivered = null;
2476
+ if (!session.agent && session.agentRemovedDue) {
2477
+ if (provider === "codex") cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
2478
+ delivered = { agentRemovedDue: false };
2479
+ }
2480
+ if (session.agent) {
2481
+ const bundle = await agents.readSessionBundle(session.id);
2482
+ if (!bundle) {
2483
+ const failed = saveRun({
2484
+ ...run,
2485
+ status: "failed",
2486
+ endedAt: nowIso(),
2487
+ error: `The agent ${session.agent.name} could not be loaded on this computer anymore. Start a new session to load it again.`
2488
+ });
2489
+ const current = index.session(session.id);
2490
+ if (current) saveSession({ ...current, lastRunStatus: "failed" });
2491
+ return failed;
2492
+ }
2493
+ const attachment = await writeAgentAttachment(agents.sessionDir(session.id), bundle, {
2494
+ mcpUrl: await agents.mcpUrlFor(session.id)
2495
+ });
2496
+ agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2497
+ if (provider === "codex" && session.agent.blockDue) {
2498
+ cliPrompt = `${renderFallbackBlock(bundle, path9.join(attachment.dir, "skills"))}${prompt}`;
2499
+ }
2500
+ delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
2033
2501
  }
2034
2502
  const child = forkWorker();
2035
2503
  const liveRun = {
@@ -2045,6 +2513,10 @@ async function createCodingSessionsEngine(options = {}) {
2045
2513
  };
2046
2514
  live.set(runId, liveRun);
2047
2515
  emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
2516
+ if (delivered) {
2517
+ const current = index.session(session.id);
2518
+ if (current) saveSession({ ...current, ...delivered });
2519
+ }
2048
2520
  const splitter = createLineSplitter();
2049
2521
  const claude = provider === "claude" ? createClaudeNormalizer() : null;
2050
2522
  const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
@@ -2083,6 +2555,7 @@ async function createCodingSessionsEngine(options = {}) {
2083
2555
  }
2084
2556
  }
2085
2557
  void pruneDriverScratch();
2558
+ if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
2086
2559
  };
2087
2560
  let settled = false;
2088
2561
  child.on("message", (raw) => {
@@ -2197,16 +2670,39 @@ async function createCodingSessionsEngine(options = {}) {
2197
2670
  provider,
2198
2671
  runId,
2199
2672
  cwd: session.cwd,
2200
- prompt,
2673
+ prompt: cliPrompt,
2201
2674
  model,
2202
2675
  effort: session.effort ?? null,
2203
2676
  resumeSessionId: session.providerSessionId,
2204
2677
  shimPath,
2205
2678
  realCommand,
2206
- identity
2679
+ identity,
2680
+ agentArgs
2207
2681
  });
2208
2682
  return run;
2209
2683
  }
2684
+ async function checkCompaction(sessionId) {
2685
+ const session = index.session(sessionId);
2686
+ if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
2687
+ const file = await findSessionFile("codex", session.providerSessionId, homes);
2688
+ if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
2689
+ const current = index.session(sessionId);
2690
+ if (current?.agent) {
2691
+ saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
2692
+ }
2693
+ }
2694
+ async function attachAgent(sessionId, selection, options2) {
2695
+ const bundle = await agents.loadBundle(selection);
2696
+ await agents.saveSessionBundle(sessionId, bundle);
2697
+ return {
2698
+ ...sessionAgentRecord(bundle, {
2699
+ toolsServed: await agents.mcpUrlFor(sessionId) !== null,
2700
+ variables: selection.variables ?? {}
2701
+ }),
2702
+ blockDue: options2.blockDue,
2703
+ deliveredAt: null
2704
+ };
2705
+ }
2210
2706
  function killGroup(liveRun) {
2211
2707
  const target = liveRun.pgid;
2212
2708
  if (target === null) return;
@@ -2222,7 +2718,7 @@ async function createCodingSessionsEngine(options = {}) {
2222
2718
  }, 3e3);
2223
2719
  }
2224
2720
  async function pruneDriverScratch() {
2225
- const runsDir = path7.join(
2721
+ const runsDir = path9.join(
2226
2722
  stateDir,
2227
2723
  "instances",
2228
2724
  "default",
@@ -2233,9 +2729,9 @@ async function createCodingSessionsEngine(options = {}) {
2233
2729
  "claude-runtime",
2234
2730
  "runs"
2235
2731
  );
2236
- const entries = await fs6.readdir(runsDir).catch(() => []);
2732
+ const entries = await fs8.readdir(runsDir).catch(() => []);
2237
2733
  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))
2734
+ entries.filter((name) => !live.has(name)).map((name) => fs8.rm(path9.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2239
2735
  );
2240
2736
  }
2241
2737
  async function probe() {
@@ -2276,7 +2772,7 @@ async function createCodingSessionsEngine(options = {}) {
2276
2772
  function newSession(input) {
2277
2773
  const at = nowIso();
2278
2774
  return {
2279
- id: randomUUID3(),
2775
+ id: randomUUID4(),
2280
2776
  provider: input.provider,
2281
2777
  providerSessionId: input.providerSessionId,
2282
2778
  cwd: input.cwd,
@@ -2294,17 +2790,17 @@ async function createCodingSessionsEngine(options = {}) {
2294
2790
  function resolveFolder(raw) {
2295
2791
  const trimmed = raw.trim();
2296
2792
  if (!trimmed) return null;
2297
- return path7.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2793
+ return path9.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2298
2794
  }
2299
2795
  async function checkFolder(raw) {
2300
2796
  const target = resolveFolder(raw);
2301
2797
  if (!target) return { path: "", exists: false, isDirectory: false };
2302
- const stat = await fs6.stat(target).catch(() => null);
2798
+ const stat = await fs8.stat(target).catch(() => null);
2303
2799
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2304
2800
  }
2305
2801
  async function assertFolder(cwd) {
2306
- const resolved = path7.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2307
- const stat = await fs6.stat(resolved).catch(() => null);
2802
+ const resolved = path9.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2803
+ const stat = await fs8.stat(resolved).catch(() => null);
2308
2804
  if (!stat?.isDirectory()) {
2309
2805
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
2310
2806
  }
@@ -2366,7 +2862,7 @@ async function createCodingSessionsEngine(options = {}) {
2366
2862
  folders.map(async ([folder, lastUsedAt]) => ({
2367
2863
  path: folder,
2368
2864
  lastUsedAt,
2369
- exists: (await fs6.stat(folder).catch(() => null))?.isDirectory() === true
2865
+ exists: (await fs8.stat(folder).catch(() => null))?.isDirectory() === true
2370
2866
  }))
2371
2867
  );
2372
2868
  },
@@ -2411,7 +2907,7 @@ async function createCodingSessionsEngine(options = {}) {
2411
2907
  async createFolder(raw) {
2412
2908
  const target = resolveFolder(raw);
2413
2909
  if (!target) throw new Error("Type a folder path first.");
2414
- await fs6.mkdir(target, { recursive: true });
2910
+ await fs8.mkdir(target, { recursive: true });
2415
2911
  return checkFolder(target);
2416
2912
  },
2417
2913
  async sessionActivity(provider, providerSessionId) {
@@ -2449,48 +2945,63 @@ async function createCodingSessionsEngine(options = {}) {
2449
2945
  reason: `${PROVIDER_LABEL[provider]} has no saved transcript for this chat on this computer.`
2450
2946
  };
2451
2947
  }
2452
- const stat = await fs6.stat(file);
2948
+ const stat = await fs8.stat(file);
2453
2949
  if (stat.size > RAW_TRANSCRIPT_MAX_BYTES) {
2454
2950
  return {
2455
2951
  available: false,
2456
2952
  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
2953
  };
2458
2954
  }
2459
- const content = await fs6.readFile(file, "utf8");
2460
- return { available: true, fileName: path7.basename(file), bytes: stat.size, content };
2955
+ const content = await fs8.readFile(file, "utf8");
2956
+ return { available: true, fileName: path9.basename(file), bytes: stat.size, content };
2461
2957
  },
2462
2958
  async start(input) {
2463
2959
  const prompt = input.prompt.trim();
2464
2960
  if (!prompt) throw new Error("Type what you want done before starting.");
2465
2961
  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
- );
2962
+ const draft = newSession({
2963
+ provider: input.provider,
2964
+ cwd,
2965
+ title: titleFrom(prompt),
2966
+ model: input.model?.trim() || null,
2967
+ effort: input.effort?.trim() || null,
2968
+ origin: "started",
2969
+ providerSessionId: null
2970
+ });
2971
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
2972
+ const session = saveSession(draft);
2477
2973
  const run = await startRun(session, prompt);
2478
2974
  return { session: index.session(session.id) ?? session, run, queued: false };
2479
2975
  },
2480
2976
  async takeover(input) {
2481
2977
  const existing = index.session(input.providerSessionId);
2482
- if (existing) return existing;
2978
+ if (existing) {
2979
+ if (!input.agent) return existing;
2980
+ return engine.setAgent(existing.id, input.agent);
2981
+ }
2483
2982
  const cwd = await assertFolder(input.cwd);
2484
- return saveSession(
2485
- newSession({
2486
- provider: input.provider,
2487
- cwd,
2488
- title: input.title?.trim() || "Taken-over chat",
2489
- model: null,
2490
- origin: "takeover",
2491
- providerSessionId: input.providerSessionId
2492
- })
2493
- );
2983
+ const draft = newSession({
2984
+ provider: input.provider,
2985
+ cwd,
2986
+ title: input.title?.trim() || "Taken-over chat",
2987
+ model: null,
2988
+ origin: "takeover",
2989
+ providerSessionId: input.providerSessionId
2990
+ });
2991
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
2992
+ return saveSession(draft);
2993
+ },
2994
+ async setAgent(sessionId, selection) {
2995
+ const session = index.session(String(sessionId ?? ""));
2996
+ if (!session) throw new Error("This session is not in the list anymore.");
2997
+ if (!selection) {
2998
+ const told = session.provider === "codex" && session.agent?.deliveredAt != null;
2999
+ return saveSession({ ...session, agent: null, agentRemovedDue: told });
3000
+ }
3001
+ const agent = await attachAgent(session.id, selection, {
3002
+ blockDue: session.provider === "codex" && session.providerSessionId !== null
3003
+ });
3004
+ return saveSession({ ...session, agent, agentRemovedDue: false });
2494
3005
  },
2495
3006
  async send(sessionId, rawPrompt) {
2496
3007
  const prompt = rawPrompt.trim();
@@ -2542,6 +3053,21 @@ async function createCodingSessionsEngine(options = {}) {
2542
3053
  if (opts && "effort" in opts) next.effort = typeof opts.effort === "string" && opts.effort.trim() ? opts.effort.trim() : null;
2543
3054
  return saveSession(next);
2544
3055
  },
3056
+ async agentAccess() {
3057
+ return agents.access();
3058
+ },
3059
+ async agentCatalogRpc(request) {
3060
+ return agents.catalogRpc(request);
3061
+ },
3062
+ async agentDetails(agentId) {
3063
+ return agents.details(String(agentId ?? ""));
3064
+ },
3065
+ setMcpBaseUrl(baseUrl) {
3066
+ agents.setMcpBaseUrl(baseUrl);
3067
+ },
3068
+ async handleMcp(secret, body) {
3069
+ return agents.handleMcp(secret, body);
3070
+ },
2545
3071
  async dispose() {
2546
3072
  for (const liveRun of live.values()) {
2547
3073
  liveRun.stopRequested = true;
@@ -2590,6 +3116,8 @@ function openFolderIn(folder, target) {
2590
3116
  });
2591
3117
  }
2592
3118
  export {
3119
+ DEFAULT_SERVER_URL,
3120
+ DEFAULT_SUPABASE_URL,
2593
3121
  EFFORT_LEVELS,
2594
3122
  PreferenceStore,
2595
3123
  RAW_TRANSCRIPT_MAX_BYTES,