@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.
@@ -1,4 +1,5 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-DEPwyr2j.cjs';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-CDeDKQHe.cjs';
2
+ export { e as CodingSessionsAgentsOptions } from '../index-CDeDKQHe.cjs';
2
3
  import '@ai-matrx/agents/portable';
3
4
  import '@ai-matrx/data';
4
5
  import '@ai-matrx/agents/portable/mcp';
@@ -1,4 +1,5 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-DEPwyr2j.js';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-CDeDKQHe.js';
2
+ export { e as CodingSessionsAgentsOptions } from '../index-CDeDKQHe.js';
2
3
  import '@ai-matrx/agents/portable';
3
4
  import '@ai-matrx/data';
4
5
  import '@ai-matrx/agents/portable/mcp';
@@ -73,8 +73,25 @@ function createRawBackupScheduler(options) {
73
73
  // src/engine/agent-attach.ts
74
74
  import fs2 from "fs/promises";
75
75
  import path from "path";
76
+
77
+ // src/normalize/agent-block.ts
78
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
79
+ function stripAgentBlock(text) {
80
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
81
+ if (removed) return text.slice(removed[0].length);
82
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
83
+ const end = text.indexOf("</ai_matrx_agent>");
84
+ if (end < 0) return text;
85
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
86
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
87
+ if (skills) rest = rest.slice(skills[0].length);
88
+ return rest.replace(/^\s+/, "");
89
+ }
90
+
91
+ // src/engine/agent-attach.ts
76
92
  var AGENT_MCP_SERVER_NAME = "matrx-agent";
77
93
  var AGENT_PLUGIN_NAME = "matrx-agent";
94
+ 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.";
78
95
  function escapeAttr(value) {
79
96
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
80
97
  }
@@ -123,6 +140,20 @@ function renderCodexSkillsList(bundle, skillsRoot) {
123
140
  ];
124
141
  return lines.join("\n");
125
142
  }
143
+ function renderFallbackBlock(bundle, skillsRoot, options = {}) {
144
+ const skills = renderCodexSkillsList(bundle, skillsRoot);
145
+ 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.";
146
+ const body = renderAgentInstructions(bundle).trim().replace(/^(<ai_matrx_agent [^>]*>)/, `$1
147
+
148
+ ${lead}`);
149
+ return skills ? `${body}
150
+
151
+ ${skills}
152
+
153
+ ` : `${body}
154
+
155
+ `;
156
+ }
126
157
  function tomlString(value) {
127
158
  const scalar = value.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
128
159
  return JSON.stringify(scalar).replace(/\u007f/g, "\\u007f");
@@ -183,10 +214,16 @@ async function writeAgentAttachment(dir, bundle, options) {
183
214
  pluginDir,
184
215
  mcpConfigFile,
185
216
  mcpUrl,
186
- // Same order as Claude's: the skills list first, the agent's words last.
187
- codexInstructions: skillsList ? `${skillsList}
217
+ // Same order as Claude's: the skills list first, the agent's words last —
218
+ // then the one sentence that lets a later block replace them. Codex ranks
219
+ // developer instructions above a user message, so without this permission
220
+ // a mid-chat switch or update is ignored (measured live: the v2 block was
221
+ // delivered and Codex kept ending with v1's line).
222
+ codexInstructions: `${skillsList ? `${skillsList}
188
223
 
189
- ${instructions}` : instructions
224
+ ` : ""}${instructions}
225
+ ${CODEX_REPLACEMENT_CLAUSE}
226
+ `
190
227
  };
191
228
  }
192
229
  function agentArgsFor(provider, attachment, options) {
@@ -238,16 +275,37 @@ function sessionAgentRecord(bundle, options) {
238
275
  name: bundle.name,
239
276
  definitionHash: bundle.definition_hash,
240
277
  loaded,
241
- unavailable
278
+ unavailable,
279
+ skills: bundle.skills.map((skill) => skill.label),
280
+ tools: options.toolsServed ? bundle.tools.map((tool) => tool.canonical_name) : [],
281
+ ...options.variables ? { variables: options.variables } : {}
242
282
  };
243
283
  }
284
+ 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";
285
+ async function codexCompactionDroppedAgent(rolloutFile, sinceIso) {
286
+ const text = await fs2.readFile(rolloutFile, "utf8").catch(() => "");
287
+ if (!text.includes('"type":"compacted"')) return false;
288
+ let latest = null;
289
+ for (const line of text.split("\n")) {
290
+ if (!line.includes('"type":"compacted"')) continue;
291
+ try {
292
+ const entry = JSON.parse(line);
293
+ if (entry.type === "compacted") latest = entry;
294
+ } catch {
295
+ }
296
+ }
297
+ if (!latest) return false;
298
+ if (sinceIso && latest.timestamp && latest.timestamp <= sinceIso) return false;
299
+ return !JSON.stringify(latest.payload?.replacement_history ?? "").includes(AGENT_BLOCK_MARKER.trim());
300
+ }
244
301
 
245
302
  // src/engine/agents.ts
246
- import { randomBytes, randomUUID } from "crypto";
303
+ import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
247
304
  import fs3 from "fs/promises";
248
305
  import path2 from "path";
249
306
  var DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
250
307
  var DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
308
+ var DEFAULT_SUPABASE_KEY = "sb_publishable_scm0jzEJiFUB5ItiqOx5Fg_8BOjB6oA";
251
309
  var AGENT_CATALOG_RPCS = /* @__PURE__ */ new Set([
252
310
  "agx_get_list_full",
253
311
  "agx_search",
@@ -265,6 +323,10 @@ var agentsClient = async () => {
265
323
  ]);
266
324
  return { ...matrx, ...portable, ...mcp };
267
325
  };
326
+ function sortedValues(values) {
327
+ return Object.keys(values).sort().map((key) => [key, values[key]]);
328
+ }
329
+ var BUNDLE_CACHE_MS = 24 * 60 * 60 * 1e3;
268
330
  var SIGNED_OUT_AGENT_MESSAGE = "Sign in to AI Matrx to use your agents.";
269
331
  function userIdOf(credential) {
270
332
  if (!credential || credential.kind !== "user") return null;
@@ -281,21 +343,26 @@ function createAgentConnection(options, dataDir) {
281
343
  const credentials = options?.credentials ?? null;
282
344
  const serverUrl = (options?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
283
345
  const supabaseUrl = (options?.supabaseUrl ?? DEFAULT_SUPABASE_URL).replace(/\/+$/, "");
284
- const supabaseKey = options?.supabaseKey ?? null;
346
+ const supabaseKey = options?.supabaseKey ?? (options?.supabaseUrl ? null : DEFAULT_SUPABASE_KEY);
285
347
  const root = path2.join(dataDir, "agents");
286
348
  let mcpBaseUrl = null;
287
349
  const handlers = /* @__PURE__ */ new Map();
288
350
  const secretToSession = /* @__PURE__ */ new Map();
289
- const organizationId = () => {
290
- const raw = typeof options?.organizationId === "function" ? options.organizationId() : options?.organizationId;
351
+ const organizationId = async () => {
352
+ const raw = typeof options?.organizationId === "function" ? await options.organizationId() : options?.organizationId;
291
353
  return raw && raw.trim() ? raw.trim() : null;
292
354
  };
293
- const transport = async () => (await agentsClient()).createMatrxTransport({
294
- baseUrl: serverUrl,
295
- ...credentials ? { credentials } : {},
296
- ...organizationId() ? { organizationId: () => organizationId() } : {},
297
- source: "codingSessionsAgents"
298
- });
355
+ const transport = async () => {
356
+ const org = await organizationId();
357
+ return (await agentsClient()).createMatrxTransport({
358
+ baseUrl: serverUrl,
359
+ ...credentials ? { credentials } : {},
360
+ ...org ? { organizationId: org } : {},
361
+ source: "codingSessionsAgents"
362
+ });
363
+ };
364
+ const cacheDir = path2.join(dataDir, "agents", ".bundles");
365
+ const cacheKey = (definitionHash, selection, surface) => createHash2("sha256").update(JSON.stringify([definitionHash, sortedValues(selection.variables ?? {}), surface])).digest("hex");
299
366
  const credential = async () => credentials ? credentials.get() : null;
300
367
  const requireSignedIn = async () => {
301
368
  if (!await credential()) throw new Error(SIGNED_OUT_AGENT_MESSAGE);
@@ -331,7 +398,7 @@ function createAgentConnection(options, dataDir) {
331
398
  configured: credentials !== null && supabaseKey !== null,
332
399
  signedIn: userId !== null,
333
400
  userId,
334
- organizationId: organizationId()
401
+ organizationId: await organizationId().catch(() => null)
335
402
  };
336
403
  },
337
404
  async catalogRpc(request) {
@@ -378,27 +445,39 @@ function createAgentConnection(options, dataDir) {
378
445
  },
379
446
  async details(agentId) {
380
447
  await requireSignedIn();
381
- const { fetchPortableAgent, fetchPortableAgentVersions } = await agentsClient();
382
- const [bundle, versions] = await Promise.all([
383
- fetchPortableAgent(await transport(), { agentId }),
384
- fetchPortableAgentVersions(await transport(), agentId).catch(() => [])
385
- ]);
448
+ const summary = await (await agentsClient()).fetchPortableAgentSummary(await transport(), agentId);
386
449
  return {
387
- agentId: bundle.agent_id,
388
- name: bundle.name,
389
- variables: bundle.variables,
390
- versions: versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })).sort((a, b) => b.versionNumber - a.versionNumber),
391
- latestVersionNumber: bundle.version_number
450
+ agentId: summary.agent_id,
451
+ name: summary.name,
452
+ variables: summary.variables,
453
+ versions: summary.versions.map((v) => ({ id: v.id, versionNumber: v.version_number, changedAt: v.changed_at })),
454
+ latestVersionNumber: summary.version_number,
455
+ latestVersionId: summary.version_id
392
456
  };
393
457
  },
394
458
  async loadBundle(selection) {
395
459
  await requireSignedIn();
396
- const { fetchPortableAgent } = await agentsClient();
397
- return fetchPortableAgent(
460
+ const client = await agentsClient();
461
+ const summary = await client.fetchPortableAgentSummary(await transport(), selection.agentId);
462
+ const versionId = selection.versionId ?? summary.version_id;
463
+ const definitionHash = versionId ? summary.versions.find((v) => v.id === versionId)?.definition_hash ?? null : summary.definition_hash;
464
+ const surface = "matrx-user/code-editor";
465
+ const file = definitionHash ? path2.join(cacheDir, `${cacheKey(definitionHash, selection, surface)}.json`) : null;
466
+ if (file) {
467
+ const cached = await fs3.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
468
+ const fresh = await fs3.stat(file).then((st) => Date.now() - st.mtimeMs < BUNDLE_CACHE_MS).catch(() => false);
469
+ if (cached && fresh && cached.definition_hash === definitionHash) return cached;
470
+ }
471
+ const bundle = await client.fetchPortableAgent(
398
472
  await transport(),
399
- selection.versionId ? { versionId: selection.versionId } : { agentId: selection.agentId },
400
- { variables: selection.variables ?? {} }
473
+ versionId ? { versionId } : { agentId: selection.agentId },
474
+ { variables: selection.variables ?? {}, surface }
401
475
  );
476
+ if (file && bundle.definition_hash === definitionHash) {
477
+ await fs3.mkdir(cacheDir, { recursive: true, mode: 448 });
478
+ await fs3.writeFile(file, JSON.stringify(bundle), { mode: 384 });
479
+ }
480
+ return bundle;
402
481
  },
403
482
  async saveSessionBundle(sessionId, bundle) {
404
483
  await fs3.mkdir(sessionDir(sessionId), { recursive: true, mode: 448 });
@@ -534,7 +613,7 @@ function createClaudeNormalizer() {
534
613
  if (typeof content === "string") {
535
614
  if (isSyntheticClaudeUserText(content) || content.trim().length === 0) return [];
536
615
  userCounter += 1;
537
- return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: content, at }];
616
+ return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: stripAgentBlock(content), at }];
538
617
  }
539
618
  if (!Array.isArray(content)) return [];
540
619
  const items = [];
@@ -558,7 +637,7 @@ function createClaudeNormalizer() {
558
637
  items.unshift({
559
638
  kind: "user",
560
639
  id: str(entry.uuid) ?? `user-${userCounter}`,
561
- text: typed.join("\n\n"),
640
+ text: stripAgentBlock(typed.join("\n\n")),
562
641
  at
563
642
  });
564
643
  }
@@ -813,7 +892,7 @@ function rolloutItem(item, fallbackId, at) {
813
892
  const id = str2(item.id) ?? fallbackId;
814
893
  switch (item.type) {
815
894
  case "UserMessage": {
816
- const text = stripCodexInjectedContext(itemText(item.content));
895
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
817
896
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
818
897
  }
819
898
  case "AgentMessage": {
@@ -896,7 +975,7 @@ function createCodexRolloutNormalizer() {
896
975
  return rolloutItem(payload.item, fallbackId, at);
897
976
  }
898
977
  if (payload.type === "user_message" && !appItems) {
899
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
978
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
900
979
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
901
980
  }
902
981
  return [];
@@ -2374,6 +2453,12 @@ async function createCodingSessionsEngine(options = {}) {
2374
2453
  if (file) fileOffset = (await fs8.stat(file).catch(() => null))?.size ?? null;
2375
2454
  }
2376
2455
  let agentArgs = [];
2456
+ let cliPrompt = prompt;
2457
+ let delivered = null;
2458
+ if (!session.agent && session.agentRemovedDue) {
2459
+ cliPrompt = `${AGENT_REMOVED_BLOCK}${prompt}`;
2460
+ delivered = { agentRemovedDue: false };
2461
+ }
2377
2462
  if (session.agent) {
2378
2463
  const bundle = await agents.readSessionBundle(session.id);
2379
2464
  if (!bundle) {
@@ -2391,6 +2476,10 @@ async function createCodingSessionsEngine(options = {}) {
2391
2476
  mcpUrl: await agents.mcpUrlFor(session.id)
2392
2477
  });
2393
2478
  agentArgs = agentArgsFor(provider, attachment, { resuming: session.providerSessionId !== null });
2479
+ if (session.agent.blockDue) {
2480
+ cliPrompt = `${renderFallbackBlock(bundle, path9.join(attachment.dir, "skills"), { replaces: session.agent.replaces })}${prompt}`;
2481
+ }
2482
+ delivered = { agent: { ...session.agent, blockDue: false, deliveredAt: run.startedAt } };
2394
2483
  }
2395
2484
  const child = forkWorker();
2396
2485
  const liveRun = {
@@ -2406,6 +2495,10 @@ async function createCodingSessionsEngine(options = {}) {
2406
2495
  };
2407
2496
  live.set(runId, liveRun);
2408
2497
  emitItem(liveRun, { kind: "user", id: `run-${runId}`, text: prompt, at: run.startedAt });
2498
+ if (delivered) {
2499
+ const current = index.session(session.id);
2500
+ if (current) saveSession({ ...current, ...delivered });
2501
+ }
2409
2502
  const splitter = createLineSplitter();
2410
2503
  const claude = provider === "claude" ? createClaudeNormalizer() : null;
2411
2504
  const codex = provider === "codex" ? createCodexStreamNormalizer(runId.slice(0, 8)) : null;
@@ -2444,6 +2537,7 @@ async function createCodingSessionsEngine(options = {}) {
2444
2537
  }
2445
2538
  }
2446
2539
  void pruneDriverScratch();
2540
+ if (provider === "codex" && final.status === "completed") void checkCompaction(session.id);
2447
2541
  };
2448
2542
  let settled = false;
2449
2543
  child.on("message", (raw) => {
@@ -2558,7 +2652,7 @@ async function createCodingSessionsEngine(options = {}) {
2558
2652
  provider,
2559
2653
  runId,
2560
2654
  cwd: session.cwd,
2561
- prompt,
2655
+ prompt: cliPrompt,
2562
2656
  model,
2563
2657
  effort: session.effort ?? null,
2564
2658
  resumeSessionId: session.providerSessionId,
@@ -2569,6 +2663,29 @@ async function createCodingSessionsEngine(options = {}) {
2569
2663
  });
2570
2664
  return run;
2571
2665
  }
2666
+ async function checkCompaction(sessionId) {
2667
+ const session = index.session(sessionId);
2668
+ if (!session?.agent || session.agent.blockDue || !session.providerSessionId) return;
2669
+ const file = await findSessionFile("codex", session.providerSessionId, homes);
2670
+ if (!file || !await codexCompactionDroppedAgent(file, session.agent.deliveredAt ?? null)) return;
2671
+ const current = index.session(sessionId);
2672
+ if (current?.agent) {
2673
+ saveSession({ ...current, agent: { ...current.agent, blockDue: true } });
2674
+ }
2675
+ }
2676
+ async function attachAgent(sessionId, selection, options2) {
2677
+ const bundle = await agents.loadBundle(selection);
2678
+ await agents.saveSessionBundle(sessionId, bundle);
2679
+ return {
2680
+ ...sessionAgentRecord(bundle, {
2681
+ toolsServed: await agents.mcpUrlFor(sessionId) !== null,
2682
+ variables: selection.variables ?? {}
2683
+ }),
2684
+ blockDue: options2.blockDue,
2685
+ deliveredAt: null,
2686
+ replaces: options2.replaces ?? null
2687
+ };
2688
+ }
2572
2689
  function killGroup(liveRun) {
2573
2690
  const target = liveRun.pgid;
2574
2691
  if (target === null) return;
@@ -2825,7 +2942,6 @@ async function createCodingSessionsEngine(options = {}) {
2825
2942
  const prompt = input.prompt.trim();
2826
2943
  if (!prompt) throw new Error("Type what you want done before starting.");
2827
2944
  const cwd = await assertFolder(input.cwd);
2828
- const bundle = input.agent ? await agents.loadBundle(input.agent) : null;
2829
2945
  const draft = newSession({
2830
2946
  provider: input.provider,
2831
2947
  cwd,
@@ -2835,28 +2951,43 @@ async function createCodingSessionsEngine(options = {}) {
2835
2951
  origin: "started",
2836
2952
  providerSessionId: null
2837
2953
  });
2838
- if (bundle) {
2839
- await agents.saveSessionBundle(draft.id, bundle);
2840
- draft.agent = sessionAgentRecord(bundle, { toolsServed: await agents.mcpUrlFor(draft.id) !== null });
2841
- }
2954
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: false });
2842
2955
  const session = saveSession(draft);
2843
2956
  const run = await startRun(session, prompt);
2844
2957
  return { session: index.session(session.id) ?? session, run, queued: false };
2845
2958
  },
2846
2959
  async takeover(input) {
2847
2960
  const existing = index.session(input.providerSessionId);
2848
- if (existing) return existing;
2961
+ if (existing) {
2962
+ if (!input.agent) return existing;
2963
+ return engine.setAgent(existing.id, input.agent);
2964
+ }
2849
2965
  const cwd = await assertFolder(input.cwd);
2850
- return saveSession(
2851
- newSession({
2852
- provider: input.provider,
2853
- cwd,
2854
- title: input.title?.trim() || "Taken-over chat",
2855
- model: null,
2856
- origin: "takeover",
2857
- providerSessionId: input.providerSessionId
2858
- })
2859
- );
2966
+ const draft = newSession({
2967
+ provider: input.provider,
2968
+ cwd,
2969
+ title: input.title?.trim() || "Taken-over chat",
2970
+ model: null,
2971
+ origin: "takeover",
2972
+ providerSessionId: input.providerSessionId
2973
+ });
2974
+ if (input.agent) draft.agent = await attachAgent(draft.id, input.agent, { blockDue: input.provider === "codex" });
2975
+ return saveSession(draft);
2976
+ },
2977
+ async setAgent(sessionId, selection) {
2978
+ const session = index.session(String(sessionId ?? ""));
2979
+ if (!session) throw new Error("This session is not in the list anymore.");
2980
+ if (!selection) {
2981
+ const told = session.agent?.deliveredAt != null;
2982
+ return saveSession({ ...session, agent: null, agentRemovedDue: told });
2983
+ }
2984
+ const previous = session.agent && session.agent.deliveredAt != null ? session.agent : null;
2985
+ const started = session.providerSessionId !== null;
2986
+ const agent = await attachAgent(session.id, selection, {
2987
+ blockDue: started,
2988
+ replaces: previous ? `${previous.name}${previous.versionNumber !== null ? ` v${previous.versionNumber}` : ""}` : null
2989
+ });
2990
+ return saveSession({ ...session, agent, agentRemovedDue: false });
2860
2991
  },
2861
2992
  async send(sessionId, rawPrompt) {
2862
2993
  const prompt = rawPrompt.trim();
@@ -2995,7 +3126,8 @@ var BRIDGE_METHODS = [
2995
3126
  "setSessionOptions",
2996
3127
  "agentAccess",
2997
3128
  "agentCatalogRpc",
2998
- "agentDetails"
3129
+ "agentDetails",
3130
+ "setAgent"
2999
3131
  ];
3000
3132
  var BridgeError = class extends Error {
3001
3133
  constructor(message, code) {