@ai-matrx/coding-sessions 0.1.85 → 0.1.87

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.
@@ -117,8 +117,25 @@ function createRawBackupScheduler(options) {
117
117
  // src/engine/agent-attach.ts
118
118
  var import_promises2 = __toESM(require("fs/promises"), 1);
119
119
  var import_node_path = __toESM(require("path"), 1);
120
+
121
+ // src/normalize/agent-block.ts
122
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
123
+ function stripAgentBlock(text) {
124
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
125
+ if (removed) return text.slice(removed[0].length);
126
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
127
+ const end = text.indexOf("</ai_matrx_agent>");
128
+ if (end < 0) return text;
129
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
130
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
131
+ if (skills) rest = rest.slice(skills[0].length);
132
+ return rest.replace(/^\s+/, "");
133
+ }
134
+
135
+ // src/engine/agent-attach.ts
120
136
  var AGENT_MCP_SERVER_NAME = "matrx-agent";
121
137
  var AGENT_PLUGIN_NAME = "matrx-agent";
138
+ var CODEX_REPLACEMENT_CLAUSE = "If a later message in this chat begins with an <ai_matrx_agent> block, that block replaces these AI Matrx agent instructions from then on; an <ai_matrx_agent_removed> block withdraws them.";
122
139
  function escapeAttr(value) {
123
140
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
124
141
  }
@@ -167,6 +184,20 @@ function renderCodexSkillsList(bundle, skillsRoot) {
167
184
  ];
168
185
  return lines.join("\n");
169
186
  }
187
+ function renderFallbackBlock(bundle, skillsRoot, options = {}) {
188
+ const skills = renderCodexSkillsList(bundle, skillsRoot);
189
+ const lead = options.replaces ? `These instructions REPLACE the earlier AI Matrx agent "${options.replaces}" from this message on. Where they differ from anything said before \u2014 including how earlier replies ended \u2014 follow these.` : "The instructions below are your AI Matrx agent for this chat from now on; they replace any AI Matrx agent instructions given earlier.";
190
+ const body = renderAgentInstructions(bundle).trim().replace(/^(<ai_matrx_agent [^>]*>)/, `$1
191
+
192
+ ${lead}`);
193
+ return skills ? `${body}
194
+
195
+ ${skills}
196
+
197
+ ` : `${body}
198
+
199
+ `;
200
+ }
170
201
  function tomlString(value) {
171
202
  const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
172
203
  return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
@@ -227,10 +258,16 @@ async function writeAgentAttachment(dir, bundle, options) {
227
258
  pluginDir,
228
259
  mcpConfigFile,
229
260
  mcpUrl,
230
- // Same order as Claude's: the skills list first, the agent's words last.
231
- codexInstructions: skillsList ? `${skillsList}
261
+ // Same order as Claude's: the skills list first, the agent's words last —
262
+ // then the one sentence that lets a later block replace them. Codex ranks
263
+ // developer instructions above a user message, so without this permission
264
+ // a mid-chat switch or update is ignored (measured live: the v2 block was
265
+ // delivered and Codex kept ending with v1's line).
266
+ codexInstructions: `${skillsList ? `${skillsList}
232
267
 
233
- ${instructions}` : instructions
268
+ ` : ""}${instructions}
269
+ ${CODEX_REPLACEMENT_CLAUSE}
270
+ `
234
271
  };
235
272
  }
236
273
  function agentArgsFor(provider, attachment, options) {
@@ -282,9 +319,29 @@ function sessionAgentRecord(bundle, options) {
282
319
  name: bundle.name,
283
320
  definitionHash: bundle.definition_hash,
284
321
  loaded,
285
- unavailable
322
+ unavailable,
323
+ skills: bundle.skills.map((skill) => skill.label),
324
+ tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
325
+ ...options.variables ? { variables: options.variables } : {}
286
326
  };
287
327
  }
328
+ var AGENT_REMOVED_BLOCK = "<ai_matrx_agent_removed>The AI Matrx agent instructions given earlier in this chat no longer apply \u2014 including any rule about how replies end. Answer normally from this message on.</ai_matrx_agent_removed>\n\n";
329
+ async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
330
+ const text = await import_promises2.default.readFile(rolloutFile, "utf8").catch(() => "");
331
+ if (!text.includes('"type":"compacted"')) return false;
332
+ let latest = null;
333
+ for (const line of text.split("\n")) {
334
+ if (!line.includes('"type":"compacted"')) continue;
335
+ try {
336
+ const entry = JSON.parse(line);
337
+ if (entry.type === "compacted") latest = entry;
338
+ } catch {
339
+ }
340
+ }
341
+ if (!latest) return false;
342
+ if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
343
+ return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
344
+ }
288
345
 
289
346
  // src/engine/agents.ts
290
347
  var import_node_crypto2 = require("crypto");
@@ -292,6 +349,7 @@ var import_promises3 = __toESM(require("fs/promises"), 1);
292
349
  var import_node_path2 = __toESM(require("path"), 1);
293
350
  var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
294
351
  var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
352
+ var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
295
353
  var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
296
354
  "agx_get_list_full",
297
355
  "agx_search",
@@ -309,6 +367,10 @@ var agentsClient = async () => {
309
367
  ]);
310
368
  return { ...matrx, ...portable, ...mcp };
311
369
  };
370
+ function sortedValues(values) {
371
+ return Object.keys(values).sort().map((key) => [key, values[key]]);
372
+ }
373
+ var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
312
374
  var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
313
375
  function userIdOf(credential) {
314
376
  if (!credential || credential.kind !== "user") return null;
@@ -325,21 +387,26 @@ function createAgentConnection(options, dataDir) {
325
387
  const credentials = options?.credentials ?? null;
326
388
  const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
327
389
  const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
328
- const supabaseKey = options?.supabaseKey ?? null;
390
+ const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
329
391
  const root = import_node_path2.default.join(dataDir, "agents");
330
392
  let mcpBaseUrl = null;
331
393
  const handlers = /* @__PURE__ */ new Map();
332
394
  const secretToSession = /* @__PURE__ */ new Map();
333
- const organizationId = () => {
334
- const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
395
+ const organizationId = async () => {
396
+ const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
335
397
  return raw && raw.trim() ? raw.trim() : null;
336
398
  };
337
- const transport = async () => (await agentsClient()).createMatrxTransport({
338
- baseUrl: serverUrl,
339
- ...credentials ? { credentials } : {},
340
- ...organizationId() ? { organizationId: () => organizationId() } : {},
341
- source: "codingSessionsAgents"
342
- });
399
+ const transport = async () => {
400
+ const org = await organizationId();
401
+ return (await agentsClient()).createMatrxTransport({
402
+ baseUrl: serverUrl,
403
+ ...credentials ? { credentials } : {},
404
+ ...org ? { organizationId: org } : {},
405
+ source: "codingSessionsAgents"
406
+ });
407
+ };
408
+ const cacheDir = import_node_path2.default.join(dataDir, "agents", ".bundles");
409
+ const cacheKey = (definitionHash, selection, surface) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
343
410
  const credential = async () => credentials ? credentials.get() : null;
344
411
  const requireSignedIn = async () => {
345
412
  if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
@@ -375,7 +442,7 @@ function createAgentConnection(options, dataDir) {
375
442
  configured: credentials !== null && supabaseKey !== null,
376
443
  signedIn: userId !== null,
377
444
  userId,
378
- organizationId: organizationId()
445
+ organizationId: await organizationId().catch(() => null)
379
446
  };
380
447
  },
381
448
  async catalogRpc(request) {
@@ -422,27 +489,39 @@ function createAgentConnection(options, dataDir) {
422
489
  },
423
490
  async details(agentId) {
424
491
  await requireSignedIn();
425
- const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
426
- const [bundle, versions] = await Promise.all([
427
- fetchPortableAgent(await transport(), { agentId }),
428
- fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
429
- ]);
492
+ const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
430
493
  return {
431
- agentId: bundle.agent_id,
432
- name: bundle.name,
433
- variables: bundle.variables,
434
- versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
435
- latestVersionNumber: bundle.version_number
494
+ agentId: summary.agent_id,
495
+ name: summary.name,
496
+ variables: summary.variables,
497
+ versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
498
+ latestVersionNumber: summary.version_number,
499
+ latestVersionId: summary.version_id
436
500
  };
437
501
  },
438
502
  async loadBundle(selection) {
439
503
  await requireSignedIn();
440
- const { fetchPortableAgent } = await agentsClient();
441
- return fetchPortableAgent(
504
+ const client = await agentsClient();
505
+ const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
506
+ const versionId = selection.versionId ?? summary.version_id;
507
+ const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
508
+ const surface = "matrx-user/code-editor";
509
+ const file = definitionHash ? import_node_path2.default.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
510
+ if (file) {
511
+ const cached = await import_promises3.default.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
512
+ const fresh = await import_promises3.default.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
513
+ if (cached && fresh && cached.definition_hash === definitionHash) return cached;
514
+ }
515
+ const bundle = await client.fetchPortableAgent(
442
516
  await transport(),
443
- selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
444
- { variables: selection.variables ?? {} }
517
+ versionId ? { versionId } : { agentId: selection.agentId },
518
+ { variables: selection.variables ?? {}, surface }
445
519
  );
520
+ if (file && bundle.definition_hash === definitionHash) {
521
+ await import_promises3.default.mkdir(cacheDir, { recursive: true, mode: 448 });
522
+ await import_promises3.default.writeFile(file, JSON.stringify(bundle), { mode: 384 });
523
+ }
524
+ return bundle;
446
525
  },
447
526
  async saveSessionBundle(sessionId, bundle) {
448
527
  await import_promises3.default.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
@@ -578,7 +657,7 @@ function createClaudeNormalizer() {
578
657
  if (typeof content === "string") {
579
658
  if (isSyntheticClaudeUserText(content) || content.trim().length === 0) return [];
580
659
  userCounter += 1;
581
- return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: content, at }];
660
+ return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: stripAgentBlock(content), at }];
582
661
  }
583
662
  if (!Array.isArray(content)) return [];
584
663
  const items = [];
@@ -602,7 +681,7 @@ function createClaudeNormalizer() {
602
681
  items.unshift({
603
682
  kind: "user",
604
683
  id: str(entry.uuid) ?? `user-${userCounter}`,
605
- text: typed.join("\n\n"),
684
+ text: stripAgentBlock(typed.join("\n\n")),
606
685
  at
607
686
  });
608
687
  }
@@ -857,7 +936,7 @@ function rolloutItem(item, fallbackId, at) {
857
936
  const id = str2(item.id) ?? fallbackId;
858
937
  switch (item.type) {
859
938
  case "UserMessage": {
860
- const text = stripCodexInjectedContext(itemText(item.content));
939
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
861
940
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
862
941
  }
863
942
  case "AgentMessage": {
@@ -940,7 +1019,7 @@ function createCodexRolloutNormalizer() {
940
1019
  return rolloutItem(payload.item, fallbackId, at);
941
1020
  }
942
1021
  if (payload.type === "user_message" && !appItems) {
943
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
1022
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
944
1023
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
945
1024
  }
946
1025
  return [];
@@ -2418,6 +2497,12 @@ async function createCodingSessionsEngine(options = {}) {
2418
2497
  if (file) fileOffset = (await import_promises8.default.stat(file).catch(() => null))?.size ?? null;
2419
2498
  }
2420
2499
  let agentArgs = [];
2500
+ let cliPrompt = prompt;
2501
+ let delivered = null;
2502
+ if (!session.agent && session.agentRemovedDue) {
2503
+ cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
2504
+ delivered = { agentRemovedDue: false };
2505
+ }
2421
2506
  if (session.agent) {
2422
2507
  const bundle = await agents.readSessionBundle(session.id);
2423
2508
  if (!bundle) {
@@ -2435,6 +2520,10 @@ async function createCodingSessionsEngine(options = {}) {
2435
2520
  mcpUrl: await agents.mcpUrlFor(session.id)
2436
2521
  });
2437
2522
  agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2523
+ if (session.agent.blockDue) {
2524
+ cliPrompt = `${renderFallbackBlock(bundle, import_node_path9.default.join(attachment.dir, "skills"), { replaces: session.agent.replaces })}${prompt}`;
2525
+ }
2526
+ delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
2438
2527
  }
2439
2528
  const child = forkWorker();
2440
2529
  const liveRun = {
@@ -2450,6 +2539,10 @@ async function createCodingSessionsEngine(options = {}) {
2450
2539
  };
2451
2540
  live.set(runId, liveRun);
2452
2541
  emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
2542
+ if (delivered) {
2543
+ const current = index.session(session.id);
2544
+ if (current) saveSession({ ...current, ...delivered });
2545
+ }
2453
2546
  const splitter = createLineSplitter();
2454
2547
  const claude = provider === "claude" ? createClaudeNormalizer() : null;
2455
2548
  const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
@@ -2488,6 +2581,7 @@ async function createCodingSessionsEngine(options = {}) {
2488
2581
  }
2489
2582
  }
2490
2583
  void pruneDriverScratch();
2584
+ if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
2491
2585
  };
2492
2586
  let settled = false;
2493
2587
  child.on("message", (raw) => {
@@ -2602,7 +2696,7 @@ async function createCodingSessionsEngine(options = {}) {
2602
2696
  provider,
2603
2697
  runId,
2604
2698
  cwd: session.cwd,
2605
- prompt,
2699
+ prompt: cliPrompt,
2606
2700
  model,
2607
2701
  effort: session.effort ?? null,
2608
2702
  resumeSessionId: session.providerSessionId,
@@ -2613,6 +2707,29 @@ async function createCodingSessionsEngine(options = {}) {
2613
2707
  });
2614
2708
  return run;
2615
2709
  }
2710
+ async function checkCompaction(sessionId) {
2711
+ const session = index.session(sessionId);
2712
+ if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
2713
+ const file = await findSessionFile("codex", session.providerSessionId, homes);
2714
+ if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
2715
+ const current = index.session(sessionId);
2716
+ if (current?.agent) {
2717
+ saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
2718
+ }
2719
+ }
2720
+ async function attachAgent(sessionId, selection, options2) {
2721
+ const bundle = await agents.loadBundle(selection);
2722
+ await agents.saveSessionBundle(sessionId, bundle);
2723
+ return {
2724
+ ...sessionAgentRecord(bundle, {
2725
+ toolsServed: await agents.mcpUrlFor(sessionId) !== null,
2726
+ variables: selection.variables ?? {}
2727
+ }),
2728
+ blockDue: options2.blockDue,
2729
+ deliveredAt: null,
2730
+ replaces: options2.replaces ?? null
2731
+ };
2732
+ }
2616
2733
  function killGroup(liveRun) {
2617
2734
  const target = liveRun.pgid;
2618
2735
  if (target === null) return;
@@ -2869,7 +2986,6 @@ async function createCodingSessionsEngine(options = {}) {
2869
2986
  const prompt = input.prompt.trim();
2870
2987
  if (!prompt) throw new Error("Type what you want done before starting.");
2871
2988
  const cwd = await assertFolder(input.cwd);
2872
- const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2873
2989
  const draft = newSession({
2874
2990
  provider: input.provider,
2875
2991
  cwd,
@@ -2879,28 +2995,43 @@ async function createCodingSessionsEngine(options = {}) {
2879
2995
  origin: "started",
2880
2996
  providerSessionId: null
2881
2997
  });
2882
- if (bundle) {
2883
- await agents.saveSessionBundle(draft.id, bundle);
2884
- draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2885
- }
2998
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
2886
2999
  const session = saveSession(draft);
2887
3000
  const run = await startRun(session, prompt);
2888
3001
  return { session: index.session(session.id) ?? session, run, queued: false };
2889
3002
  },
2890
3003
  async takeover(input) {
2891
3004
  const existing = index.session(input.providerSessionId);
2892
- if (existing) return existing;
3005
+ if (existing) {
3006
+ if (!input.agent) return existing;
3007
+ return engine.setAgent(existing.id, input.agent);
3008
+ }
2893
3009
  const cwd = await assertFolder(input.cwd);
2894
- return saveSession(
2895
- newSession({
2896
- provider: input.provider,
2897
- cwd,
2898
- title: input.title?.trim() || "Taken-over chat",
2899
- model: null,
2900
- origin: "takeover",
2901
- providerSessionId: input.providerSessionId
2902
- })
2903
- );
3010
+ const draft = newSession({
3011
+ provider: input.provider,
3012
+ cwd,
3013
+ title: input.title?.trim() || "Taken-over chat",
3014
+ model: null,
3015
+ origin: "takeover",
3016
+ providerSessionId: input.providerSessionId
3017
+ });
3018
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
3019
+ return saveSession(draft);
3020
+ },
3021
+ async setAgent(sessionId, selection) {
3022
+ const session = index.session(String(sessionId ?? ""));
3023
+ if (!session) throw new Error("This session is not in the list anymore.");
3024
+ if (!selection) {
3025
+ const told = session.agent?.deliveredAt != null;
3026
+ return saveSession({ ...session, agent: null, agentRemovedDue: told });
3027
+ }
3028
+ const previous = session.agent && session.agent.deliveredAt != null ? session.agent : null;
3029
+ const started = session.providerSessionId !== null;
3030
+ const agent = await attachAgent(session.id, selection, {
3031
+ blockDue: started,
3032
+ replaces: previous ? `${previous.name}${previous.versionNumber !== null ? ` v${previous.versionNumber}` : ""}` : null
3033
+ });
3034
+ return saveSession({ ...session, agent, agentRemovedDue: false });
2904
3035
  },
2905
3036
  async send(sessionId, rawPrompt) {
2906
3037
  const prompt = rawPrompt.trim();
@@ -3039,7 +3170,8 @@ var BRIDGE_METHODS = [
3039
3170
  "setSessionOptions",
3040
3171
  "agentAccess",
3041
3172
  "agentCatalogRpc",
3042
- "agentDetails"
3173
+ "agentDetails",
3174
+ "setAgent"
3043
3175
  ];
3044
3176
  var BridgeError = class extends Error {
3045
3177
  constructor(message, code) {