@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.
@@ -111,8 +111,10 @@ async function run(request) {
111
111
  const config = {
112
112
  cwd: request.cwd,
113
113
  // The user's text, exactly. Without this the driver sends Paperclip's
114
- // heartbeat instructions.
115
- promptTemplate: request.prompt,
114
+ // heartbeat instructions. The driver renders this as a `{{path}}`
115
+ // template, so the text rides as DATA (one substitution, never re-scanned)
116
+ // — as the template itself, every `{{word}}` the person typed was erased.
117
+ promptTemplate: "{{context.matrxPrompt}}",
116
118
  // Our shim, which drops `PAPERCLIP_*` and Paperclip's `--add-dir`, then
117
119
  // execs the real CLI named in `realCommand`.
118
120
  command: request.shimPath,
@@ -161,7 +163,7 @@ async function run(request) {
161
163
  },
162
164
  runtime,
163
165
  config,
164
- context: {},
166
+ context: { matrxPrompt: request.prompt },
165
167
  onLog: async (stream, chunk) => {
166
168
  if (resuming && /retrying with a fresh session/.test(chunk)) {
167
169
  refuseNextSpawn = true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/engine/worker.ts"],"sourcesContent":["/**\n * THE DRIVER WORKER — one short-lived Node process per run (and per provider\n * probe), forked by the engine with a CLEAN environment.\n *\n * Why a separate process at all: Paperclip's drivers build the CLI's\n * environment as `{ ...process.env, ...theirs }`. Whatever process loads a\n * driver therefore leaks its whole environment into the user's Claude Code or\n * Codex — and the process that hosts this package is very often itself running\n * inside a Claude Code session (`CLAUDE_CODE_*`, `ANTHROPIC_BASE_URL`, …). A\n * CLI that inherits those talks to the wrong endpoint or thinks it is a\n * sub-agent. The spike ran under `env -i` for exactly this reason. Forking the\n * driver into a process whose environment the engine built from an allowlist\n * is the only place that guarantee can live, because the driver reads\n * `process.env` directly.\n *\n * 🚨 SELF-CONTAINED ON PURPOSE. This file imports nothing relative: it runs as\n * `node worker.ts` straight from source under Node's type stripping (tests and\n * workspace use) and as `node worker.js` from `dist/`. Keep it that way.\n *\n * Protocol (IPC, JSON): parent → { kind: \"run\" | \"probe\", ... };\n * worker → { kind: \"log\" | \"meta\" | \"spawn\" | \"refused\" | \"result\" | \"probe-result\" | \"fatal\" }.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { registerHooks } from \"node:module\";\nimport fsp from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype Provider = \"claude\" | \"codex\";\n\ninterface RunRequest {\n kind: \"run\";\n provider: Provider;\n runId: string;\n cwd: string;\n prompt: string;\n model: string | null;\n /** Reasoning effort; null leaves the provider's own default. */\n effort?: string | null;\n resumeSessionId: string | null;\n /** Absolute path of the shim that strips Paperclip's additions. */\n shimPath: string;\n /** The real CLI the shim execs. */\n realCommand: string;\n identity: { companyId: string; agentId: string };\n /** CLI args that load an AI Matrx agent (agent-attach.ts); appended to extraArgs. */\n agentArgs?: string[];\n}\n\ninterface ProbeRequest {\n kind: \"probe\";\n commands: { claude: string | null; codex: string | null };\n /** Ask for usage windows (Codex's usage endpoint, with the user's own login). */\n quota?: boolean;\n}\n\ntype Request = RunRequest | ProbeRequest;\n\nfunction send(message: unknown): Promise<void> {\n return new Promise((resolve) => {\n if (!process.send) {\n resolve();\n return;\n }\n process.send(message, () => resolve());\n });\n}\n\n/**\n * THE HISTORY GUARD. On a \"poisoned\" resume, the Claude driver deletes the\n * user's own session file under `~/.claude/projects` and silently starts a\n * fresh session. A user's chat history is theirs; nothing we drive may delete\n * it. This process is ours alone, so the guard patches the one `fs` object the\n * drivers import and refuses — out loud — any unlink/rm inside the user's\n * Claude or Codex home.\n */\nfunction installHistoryGuard(): void {\n const home = os.homedir();\n const protectedRoots = [\n path.join(home, \".claude\"),\n path.join(home, \".codex\"),\n ];\n const isProtected = (target: unknown): boolean => {\n if (typeof target !== \"string\" && !(target instanceof URL)) return false;\n const resolved = path.resolve(typeof target === \"string\" ? target : target.pathname);\n return protectedRoots.some(\n (root) => resolved === root || resolved.startsWith(`${root}${path.sep}`),\n );\n };\n const originalUnlink = fsp.unlink.bind(fsp);\n const originalRm = fsp.rm.bind(fsp);\n const refuse = async (op: string, target: unknown): Promise<never> => {\n await send({ kind: \"refused\", op, target: String(target) });\n const error = new Error(\n `[coding-sessions] Refused to ${op} ${String(target)}: it is part of your own Claude or Codex history.`,\n ) as Error & { code: string };\n error.code = \"EPERM\";\n throw error;\n };\n (fsp as { unlink: typeof fsp.unlink }).unlink = (async (target: Parameters<typeof fsp.unlink>[0]) => {\n if (isProtected(target)) return refuse(\"delete\", target);\n return originalUnlink(target);\n }) as typeof fsp.unlink;\n (fsp as { rm: typeof fsp.rm }).rm = (async (\n target: Parameters<typeof fsp.rm>[0],\n options?: Parameters<typeof fsp.rm>[1],\n ) => {\n if (isProtected(target)) return refuse(\"remove\", target);\n return originalRm(target, options);\n }) as typeof fsp.rm;\n}\n\n/**\n * THE USER'S CODEX CONFIG IS NEVER REWRITTEN.\n *\n * On every run the Codex driver's `writeManagedCodexMcpConfig` reads\n * `$CODEX_HOME/config.toml`, strips its own managed MCP block, writes the file\n * back and chmods it 0600 — even when it has zero MCP gateways to add. With\n * CODEX_HOME pointed at the person's real `~/.codex`, that is a rewrite of\n * their own config (and a mode change) on every run.\n *\n * The fix is our own copy of that one function, spliced in when the driver's\n * `codex-home.js` loads (a module load hook in this worker process only). Our\n * copy returns without touching the file when there are no gateways — always,\n * for us — and otherwise behaves exactly as Paperclip's.\n *\n * Adapted from @paperclipai/adapter-codex-local (dist/server/codex-home.js),\n * Copyright (c) 2025 Paperclip AI, MIT License — see this package's NOTICE.\n */\n/** Lets every Codex run (start, follow-up, takeover) work in any folder. */\nexport const CODEX_SKIP_GIT_REPO_CHECK = \"--skip-git-repo-check\";\n\nconst CODEX_HOME_MODULE = /\\/@paperclipai\\/adapter-codex-local\\/dist\\/server\\/codex-home\\.js$/;\nconst ORIGINAL_SIGNATURE = \"export async function writeManagedCodexMcpConfig(input) {\";\nconst VENDORED_WRITE_MANAGED_CODEX_MCP_CONFIG = `export async function writeManagedCodexMcpConfig(input) {\n const configPath = path.join(input.codexHome, \"config.toml\");\n // @ai-matrx/coding-sessions: with nothing to add, never touch the file.\n if (!Array.isArray(input.gateways) || input.gateways.length === 0) {\n return { configPath, warnings: [] };\n }\n await fs.mkdir(input.codexHome, { recursive: true });\n const existing = await fs.readFile(configPath, \"utf8\").catch((error) => {\n if (error.code === \"ENOENT\")\n return \"\";\n throw error;\n });\n const unmanagedConfig = stripManagedMcpBlock(existing);\n const { block, warnings } = buildManagedMcpBlock({\n gateways: input.gateways,\n apiBaseUrl: input.apiBaseUrl,\n existingNames: readCodexMcpServerNames(unmanagedConfig),\n });\n const next = \\`\\${unmanagedConfig}\\${unmanagedConfig ? \"\\\\n\\\\n\" : \"\"}\\${block}\\\\n\\`;\n await fs.writeFile(configPath, next, { mode: 0o600 });\n await fs.chmod(configPath, 0o600);\n return { configPath, warnings };\n}`;\n\n/** Replace the driver's function with ours in its module source; null when its shape changed. */\nexport function spliceCodexConfigGuard(source: string): string | null {\n const start = source.indexOf(ORIGINAL_SIGNATURE);\n if (start < 0) return null;\n // The function ends at the first line that is exactly \"}\" after its start.\n const end = source.indexOf(\"\\n}\\n\", start);\n if (end < 0) return null;\n return `${source.slice(0, start)}${VENDORED_WRITE_MANAGED_CODEX_MCP_CONFIG}${source.slice(end + 2)}`;\n}\n\nlet codexConfigGuard: \"installed\" | \"failed\" | \"off\" = \"off\";\n\nfunction installCodexConfigGuard(): void {\n registerHooks({\n load(url, context, nextLoad) {\n const result = nextLoad(url, context);\n if (!CODEX_HOME_MODULE.test(url) || result.source === undefined || result.source === null) return result;\n const patched = spliceCodexConfigGuard(String(result.source));\n if (patched === null) {\n codexConfigGuard = \"failed\";\n return result;\n }\n codexConfigGuard = \"installed\";\n return { ...result, source: patched };\n },\n });\n}\n\nasync function loadDriver(provider: Provider): Promise<{\n execute: (ctx: Record<string, unknown>) => Promise<Record<string, unknown>>;\n}> {\n const mod =\n provider === \"claude\"\n ? await import(\"@paperclipai/adapter-claude-local/server\")\n : await import(\"@paperclipai/adapter-codex-local/server\");\n return mod as unknown as {\n execute: (ctx: Record<string, unknown>) => Promise<Record<string, unknown>>;\n };\n}\n\nasync function run(request: RunRequest): Promise<void> {\n installHistoryGuard();\n if (request.provider === \"codex\") installCodexConfigGuard();\n const driver = await loadDriver(request.provider);\n if (request.provider === \"codex\" && codexConfigGuard !== \"installed\") {\n // Loud, never silent: the run continues (Paperclip's rewrite keeps the\n // content), but the person is told their config file may be rewritten.\n await send({ kind: \"refused\", op: \"config-guard\", target: codexConfigGuard });\n }\n const codexHome = path.join(os.homedir(), \".codex\");\n const resuming = request.resumeSessionId !== null;\n let refuseNextSpawn = false;\n\n const config: Record<string, unknown> = {\n cwd: request.cwd,\n // The user's text, exactly. Without this the driver sends Paperclip's\n // heartbeat instructions.\n promptTemplate: request.prompt,\n // Our shim, which drops `PAPERCLIP_*` and Paperclip's `--add-dir`, then\n // execs the real CLI named in `realCommand`.\n command: request.shimPath,\n // An explicit EMPTY skill set: no Paperclip skill is mounted into the\n // user's ~/.codex/skills, and Claude gets no appended skill-list prompt.\n paperclipRuntimeSkills: [],\n env: {\n MATRX_CODING_SESSIONS_REAL_COMMAND: request.realCommand,\n ...(request.provider === \"codex\" ? { CODEX_HOME: codexHome } : {}),\n },\n ...(request.provider === \"claude\"\n ? {\n // The ACP engine writes Paperclip settings into the user's repo and\n // prepends a skill preamble; the CLI engine does neither.\n engine: \"cli\",\n dangerouslySkipPermissions: true,\n // Token-level streaming for the transcript.\n extraArgs: [\"--include-partial-messages\", ...(request.agentArgs ?? [])],\n }\n : {\n engine: \"cli\",\n // `codex exec` (start AND `exec … resume`) refuses any folder that\n // is not a git repo or a project trusted in ~/.codex/config.toml:\n // \"Not inside a trusted directory and --skip-git-repo-check was not\n // specified.\" People start chats anywhere (~/code, scratch folders),\n // and we never write trust entries into their config. The driver\n // places extraArgs before `resume <id> -` and dedupes this flag\n // against its own sandbox injection.\n extraArgs: [CODEX_SKIP_GIT_REPO_CHECK, ...(request.agentArgs ?? [])],\n }),\n ...(request.model ? { model: request.model } : {}),\n ...(request.effort\n ? request.provider === \"claude\"\n ? { effort: request.effort }\n : { modelReasoningEffort: request.effort }\n : {}),\n };\n\n const runtime = resuming\n ? {\n sessionId: request.resumeSessionId,\n sessionParams:\n request.provider === \"claude\"\n ? { sessionId: request.resumeSessionId, cwd: request.cwd }\n : { sessionId: request.resumeSessionId },\n sessionDisplayId: null,\n taskKey: null,\n }\n : { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null };\n\n const result = await driver.execute({\n runId: request.runId,\n agent: {\n id: request.identity.agentId,\n companyId: request.identity.companyId,\n name: \"AI Matrx coding session\",\n adapterType: `${request.provider}_local`,\n adapterConfig: {},\n },\n runtime,\n config,\n context: {},\n onLog: async (stream: \"stdout\" | \"stderr\", chunk: string) => {\n // THE SILENT-FRESH-SESSION GUARD. When a resume fails the driver quietly\n // retries in a brand-new session, which would answer a taken-over chat\n // with no memory of it. Refuse the retry and say why instead.\n if (resuming && /retrying with a fresh session/.test(chunk)) {\n refuseNextSpawn = true;\n await send({ kind: \"refused\", op: \"fresh-session-retry\", target: chunk.trim() });\n }\n await send({ kind: \"log\", stream, chunk });\n },\n onMeta: async (meta: Record<string, unknown>) => {\n await send({\n kind: \"meta\",\n command: meta.command ?? null,\n args: Array.isArray(meta.commandArgs) ? meta.commandArgs : [],\n envKeys:\n meta.env && typeof meta.env === \"object\" ? Object.keys(meta.env as object).sort() : [],\n });\n },\n onSpawn: async (meta: { pid: number; processGroupId: number | null; startedAt: string }) => {\n if (refuseNextSpawn) {\n try {\n process.kill(-(meta.processGroupId ?? meta.pid), \"SIGKILL\");\n } catch {\n try {\n process.kill(meta.pid, \"SIGKILL\");\n } catch {\n /* already gone */\n }\n }\n return;\n }\n await send({ kind: \"spawn\", pid: meta.pid, processGroupId: meta.processGroupId });\n },\n });\n\n await send({\n kind: \"result\",\n refusedFreshSession: refuseNextSpawn,\n result: {\n exitCode: result.exitCode ?? null,\n signal: result.signal ?? null,\n timedOut: result.timedOut === true,\n errorMessage: typeof result.errorMessage === \"string\" ? result.errorMessage : null,\n errorCode: typeof result.errorCode === \"string\" ? result.errorCode : null,\n sessionId: typeof result.sessionId === \"string\" ? result.sessionId : null,\n billingType: typeof result.billingType === \"string\" ? result.billingType : null,\n model: typeof result.model === \"string\" ? result.model : null,\n usage: result.usage ?? null,\n costUsd: typeof result.costUsd === \"number\" ? result.costUsd : null,\n summary: typeof result.summary === \"string\" ? result.summary.slice(0, 2000) : null,\n },\n });\n}\n\nfunction execText(command: string, args: string[], timeoutMs = 8000): Promise<string | null> {\n return new Promise((resolve) => {\n execFile(command, args, { timeout: timeoutMs, env: process.env }, (error, stdout) => {\n resolve(error ? null : String(stdout));\n });\n });\n}\n\nasync function probe(request: ProbeRequest): Promise<void> {\n // Claude: the driver's own `claude auth status` reader.\n const claudeState: Record<string, unknown> = { installed: false };\n if (request.commands.claude) {\n const version = await execText(request.commands.claude, [\"--version\"]);\n claudeState.installed = version !== null;\n claudeState.version = version?.trim().split(/\\s+/)[0] ?? null;\n if (version !== null) {\n const claude = (await import(\"@paperclipai/adapter-claude-local/server\")) as unknown as {\n readClaudeAuthStatus: () => Promise<\n { loggedIn: boolean; authMethod: string | null; subscriptionType: string | null } | null\n >;\n };\n const status = await claude.readClaudeAuthStatus();\n claudeState.auth = status;\n }\n }\n\n // Codex: the driver's own `auth.json` reader, against the user's own home.\n const codexState: Record<string, unknown> = { installed: false };\n if (request.commands.codex) {\n const version = await execText(request.commands.codex, [\"--version\"]);\n codexState.installed = version !== null;\n codexState.version = version?.trim().split(/\\s+/).pop() ?? null;\n if (version !== null) {\n const codex = (await import(\"@paperclipai/adapter-codex-local/server\")) as unknown as {\n readCodexAuthInfo: (home?: string) => Promise<\n { planType: string | null; email: string | null } | null\n >;\n };\n const home = path.join(os.homedir(), \".codex\");\n const info = await codex.readCodexAuthInfo(home);\n let apiKey = false;\n if (!info) {\n try {\n const raw = JSON.parse(await fsp.readFile(path.join(home, \"auth.json\"), \"utf8\")) as {\n OPENAI_API_KEY?: unknown;\n };\n apiKey = typeof raw.OPENAI_API_KEY === \"string\" && raw.OPENAI_API_KEY.length > 0;\n } catch {\n apiKey = false;\n }\n }\n // Usage windows from the driver's own reader. Codex only: Claude's\n // reader falls back to driving an interactive `claude /usage` session,\n // which is not something a status check should do to a person's account.\n if (request.quota && info) {\n const quotaMod = codex as unknown as {\n getQuotaWindows?: () => Promise<{ ok?: boolean; windows?: unknown[] }>;\n };\n const answer = await Promise.race([\n quotaMod.getQuotaWindows?.().catch(() => null) ?? Promise.resolve(null),\n new Promise<null>((resolve) => setTimeout(() => resolve(null), 10000)),\n ]);\n codexState.quota = Array.isArray(answer?.windows)\n ? answer.windows.map((w) => {\n const win = (w ?? {}) as Record<string, unknown>;\n return {\n label: typeof win.label === \"string\" ? win.label : \"Usage\",\n usedPercent: typeof win.usedPercent === \"number\" ? win.usedPercent : null,\n resetsAt: typeof win.resetsAt === \"string\" ? win.resetsAt : null,\n valueLabel: typeof win.valueLabel === \"string\" ? win.valueLabel : null,\n };\n })\n : [];\n }\n // Never forward a token: only the plan and account label leave this process.\n codexState.auth = info\n ? { loggedIn: true, planType: info.planType ?? null, email: info.email ?? null }\n : { loggedIn: apiKey, planType: null, email: null, apiKey };\n }\n }\n // The model lists the drivers ship (constants only; nothing is run).\n for (const [state, spec] of [\n [claudeState, \"@paperclipai/adapter-claude-local\"],\n [codexState, \"@paperclipai/adapter-codex-local\"],\n ] as const) {\n try {\n const mod = (await import(spec)) as { models?: unknown; DEFAULT_CLAUDE_LOCAL_MODEL?: unknown };\n // The model the Claude driver forces when none is passed.\n if (typeof mod.DEFAULT_CLAUDE_LOCAL_MODEL === \"string\") state.driverDefaultModel = mod.DEFAULT_CLAUDE_LOCAL_MODEL;\n state.models = Array.isArray(mod.models)\n ? mod.models.flatMap((m) => {\n const entry = (m ?? {}) as Record<string, unknown>;\n return typeof entry.id === \"string\" && entry.id\n ? [{ id: entry.id, label: typeof entry.label === \"string\" ? entry.label : entry.id }]\n : [];\n })\n : [];\n } catch {\n state.models = [];\n }\n }\n await send({ kind: \"probe-result\", claude: claudeState, codex: codexState });\n}\n\n// Only when forked by the engine: importing this file (a test reading\n// `spliceCodexConfigGuard`) must not turn the importer into a worker.\nif (process.env.MATRX_CODING_SESSIONS_WORKER === \"1\") process.on(\"message\", (raw) => {\n const request = raw as Request;\n const work = request.kind === \"run\" ? run(request) : probe(request);\n work\n .catch(async (error: unknown) => {\n await send({\n kind: \"fatal\",\n message: error instanceof Error ? error.message : String(error),\n });\n })\n .finally(() => {\n process.disconnect?.();\n process.exit(0);\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/engine/worker.ts"],"sourcesContent":["/**\n * THE DRIVER WORKER — one short-lived Node process per run (and per provider\n * probe), forked by the engine with a CLEAN environment.\n *\n * Why a separate process at all: Paperclip's drivers build the CLI's\n * environment as `{ ...process.env, ...theirs }`. Whatever process loads a\n * driver therefore leaks its whole environment into the user's Claude Code or\n * Codex — and the process that hosts this package is very often itself running\n * inside a Claude Code session (`CLAUDE_CODE_*`, `ANTHROPIC_BASE_URL`, …). A\n * CLI that inherits those talks to the wrong endpoint or thinks it is a\n * sub-agent. The spike ran under `env -i` for exactly this reason. Forking the\n * driver into a process whose environment the engine built from an allowlist\n * is the only place that guarantee can live, because the driver reads\n * `process.env` directly.\n *\n * 🚨 SELF-CONTAINED ON PURPOSE. This file imports nothing relative: it runs as\n * `node worker.ts` straight from source under Node's type stripping (tests and\n * workspace use) and as `node worker.js` from `dist/`. Keep it that way.\n *\n * Protocol (IPC, JSON): parent → { kind: \"run\" | \"probe\", ... };\n * worker → { kind: \"log\" | \"meta\" | \"spawn\" | \"refused\" | \"result\" | \"probe-result\" | \"fatal\" }.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { registerHooks } from \"node:module\";\nimport fsp from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ntype Provider = \"claude\" | \"codex\";\n\ninterface RunRequest {\n kind: \"run\";\n provider: Provider;\n runId: string;\n cwd: string;\n prompt: string;\n model: string | null;\n /** Reasoning effort; null leaves the provider's own default. */\n effort?: string | null;\n resumeSessionId: string | null;\n /** Absolute path of the shim that strips Paperclip's additions. */\n shimPath: string;\n /** The real CLI the shim execs. */\n realCommand: string;\n identity: { companyId: string; agentId: string };\n /** CLI args that load an AI Matrx agent (agent-attach.ts); appended to extraArgs. */\n agentArgs?: string[];\n}\n\ninterface ProbeRequest {\n kind: \"probe\";\n commands: { claude: string | null; codex: string | null };\n /** Ask for usage windows (Codex's usage endpoint, with the user's own login). */\n quota?: boolean;\n}\n\ntype Request = RunRequest | ProbeRequest;\n\nfunction send(message: unknown): Promise<void> {\n return new Promise((resolve) => {\n if (!process.send) {\n resolve();\n return;\n }\n process.send(message, () => resolve());\n });\n}\n\n/**\n * THE HISTORY GUARD. On a \"poisoned\" resume, the Claude driver deletes the\n * user's own session file under `~/.claude/projects` and silently starts a\n * fresh session. A user's chat history is theirs; nothing we drive may delete\n * it. This process is ours alone, so the guard patches the one `fs` object the\n * drivers import and refuses — out loud — any unlink/rm inside the user's\n * Claude or Codex home.\n */\nfunction installHistoryGuard(): void {\n const home = os.homedir();\n const protectedRoots = [\n path.join(home, \".claude\"),\n path.join(home, \".codex\"),\n ];\n const isProtected = (target: unknown): boolean => {\n if (typeof target !== \"string\" && !(target instanceof URL)) return false;\n const resolved = path.resolve(typeof target === \"string\" ? target : target.pathname);\n return protectedRoots.some(\n (root) => resolved === root || resolved.startsWith(`${root}${path.sep}`),\n );\n };\n const originalUnlink = fsp.unlink.bind(fsp);\n const originalRm = fsp.rm.bind(fsp);\n const refuse = async (op: string, target: unknown): Promise<never> => {\n await send({ kind: \"refused\", op, target: String(target) });\n const error = new Error(\n `[coding-sessions] Refused to ${op} ${String(target)}: it is part of your own Claude or Codex history.`,\n ) as Error & { code: string };\n error.code = \"EPERM\";\n throw error;\n };\n (fsp as { unlink: typeof fsp.unlink }).unlink = (async (target: Parameters<typeof fsp.unlink>[0]) => {\n if (isProtected(target)) return refuse(\"delete\", target);\n return originalUnlink(target);\n }) as typeof fsp.unlink;\n (fsp as { rm: typeof fsp.rm }).rm = (async (\n target: Parameters<typeof fsp.rm>[0],\n options?: Parameters<typeof fsp.rm>[1],\n ) => {\n if (isProtected(target)) return refuse(\"remove\", target);\n return originalRm(target, options);\n }) as typeof fsp.rm;\n}\n\n/**\n * THE USER'S CODEX CONFIG IS NEVER REWRITTEN.\n *\n * On every run the Codex driver's `writeManagedCodexMcpConfig` reads\n * `$CODEX_HOME/config.toml`, strips its own managed MCP block, writes the file\n * back and chmods it 0600 — even when it has zero MCP gateways to add. With\n * CODEX_HOME pointed at the person's real `~/.codex`, that is a rewrite of\n * their own config (and a mode change) on every run.\n *\n * The fix is our own copy of that one function, spliced in when the driver's\n * `codex-home.js` loads (a module load hook in this worker process only). Our\n * copy returns without touching the file when there are no gateways — always,\n * for us — and otherwise behaves exactly as Paperclip's.\n *\n * Adapted from @paperclipai/adapter-codex-local (dist/server/codex-home.js),\n * Copyright (c) 2025 Paperclip AI, MIT License — see this package's NOTICE.\n */\n/** Lets every Codex run (start, follow-up, takeover) work in any folder. */\nexport const CODEX_SKIP_GIT_REPO_CHECK = \"--skip-git-repo-check\";\n\nconst CODEX_HOME_MODULE = /\\/@paperclipai\\/adapter-codex-local\\/dist\\/server\\/codex-home\\.js$/;\nconst ORIGINAL_SIGNATURE = \"export async function writeManagedCodexMcpConfig(input) {\";\nconst VENDORED_WRITE_MANAGED_CODEX_MCP_CONFIG = `export async function writeManagedCodexMcpConfig(input) {\n const configPath = path.join(input.codexHome, \"config.toml\");\n // @ai-matrx/coding-sessions: with nothing to add, never touch the file.\n if (!Array.isArray(input.gateways) || input.gateways.length === 0) {\n return { configPath, warnings: [] };\n }\n await fs.mkdir(input.codexHome, { recursive: true });\n const existing = await fs.readFile(configPath, \"utf8\").catch((error) => {\n if (error.code === \"ENOENT\")\n return \"\";\n throw error;\n });\n const unmanagedConfig = stripManagedMcpBlock(existing);\n const { block, warnings } = buildManagedMcpBlock({\n gateways: input.gateways,\n apiBaseUrl: input.apiBaseUrl,\n existingNames: readCodexMcpServerNames(unmanagedConfig),\n });\n const next = \\`\\${unmanagedConfig}\\${unmanagedConfig ? \"\\\\n\\\\n\" : \"\"}\\${block}\\\\n\\`;\n await fs.writeFile(configPath, next, { mode: 0o600 });\n await fs.chmod(configPath, 0o600);\n return { configPath, warnings };\n}`;\n\n/** Replace the driver's function with ours in its module source; null when its shape changed. */\nexport function spliceCodexConfigGuard(source: string): string | null {\n const start = source.indexOf(ORIGINAL_SIGNATURE);\n if (start < 0) return null;\n // The function ends at the first line that is exactly \"}\" after its start.\n const end = source.indexOf(\"\\n}\\n\", start);\n if (end < 0) return null;\n return `${source.slice(0, start)}${VENDORED_WRITE_MANAGED_CODEX_MCP_CONFIG}${source.slice(end + 2)}`;\n}\n\nlet codexConfigGuard: \"installed\" | \"failed\" | \"off\" = \"off\";\n\nfunction installCodexConfigGuard(): void {\n registerHooks({\n load(url, context, nextLoad) {\n const result = nextLoad(url, context);\n if (!CODEX_HOME_MODULE.test(url) || result.source === undefined || result.source === null) return result;\n const patched = spliceCodexConfigGuard(String(result.source));\n if (patched === null) {\n codexConfigGuard = \"failed\";\n return result;\n }\n codexConfigGuard = \"installed\";\n return { ...result, source: patched };\n },\n });\n}\n\nasync function loadDriver(provider: Provider): Promise<{\n execute: (ctx: Record<string, unknown>) => Promise<Record<string, unknown>>;\n}> {\n const mod =\n provider === \"claude\"\n ? await import(\"@paperclipai/adapter-claude-local/server\")\n : await import(\"@paperclipai/adapter-codex-local/server\");\n return mod as unknown as {\n execute: (ctx: Record<string, unknown>) => Promise<Record<string, unknown>>;\n };\n}\n\nasync function run(request: RunRequest): Promise<void> {\n installHistoryGuard();\n if (request.provider === \"codex\") installCodexConfigGuard();\n const driver = await loadDriver(request.provider);\n if (request.provider === \"codex\" && codexConfigGuard !== \"installed\") {\n // Loud, never silent: the run continues (Paperclip's rewrite keeps the\n // content), but the person is told their config file may be rewritten.\n await send({ kind: \"refused\", op: \"config-guard\", target: codexConfigGuard });\n }\n const codexHome = path.join(os.homedir(), \".codex\");\n const resuming = request.resumeSessionId !== null;\n let refuseNextSpawn = false;\n\n const config: Record<string, unknown> = {\n cwd: request.cwd,\n // The user's text, exactly. Without this the driver sends Paperclip's\n // heartbeat instructions. The driver renders this as a `{{path}}`\n // template, so the text rides as DATA (one substitution, never re-scanned)\n // — as the template itself, every `{{word}}` the person typed was erased.\n promptTemplate: \"{{context.matrxPrompt}}\",\n // Our shim, which drops `PAPERCLIP_*` and Paperclip's `--add-dir`, then\n // execs the real CLI named in `realCommand`.\n command: request.shimPath,\n // An explicit EMPTY skill set: no Paperclip skill is mounted into the\n // user's ~/.codex/skills, and Claude gets no appended skill-list prompt.\n paperclipRuntimeSkills: [],\n env: {\n MATRX_CODING_SESSIONS_REAL_COMMAND: request.realCommand,\n ...(request.provider === \"codex\" ? { CODEX_HOME: codexHome } : {}),\n },\n ...(request.provider === \"claude\"\n ? {\n // The ACP engine writes Paperclip settings into the user's repo and\n // prepends a skill preamble; the CLI engine does neither.\n engine: \"cli\",\n dangerouslySkipPermissions: true,\n // Token-level streaming for the transcript.\n extraArgs: [\"--include-partial-messages\", ...(request.agentArgs ?? [])],\n }\n : {\n engine: \"cli\",\n // `codex exec` (start AND `exec … resume`) refuses any folder that\n // is not a git repo or a project trusted in ~/.codex/config.toml:\n // \"Not inside a trusted directory and --skip-git-repo-check was not\n // specified.\" People start chats anywhere (~/code, scratch folders),\n // and we never write trust entries into their config. The driver\n // places extraArgs before `resume <id> -` and dedupes this flag\n // against its own sandbox injection.\n extraArgs: [CODEX_SKIP_GIT_REPO_CHECK, ...(request.agentArgs ?? [])],\n }),\n ...(request.model ? { model: request.model } : {}),\n ...(request.effort\n ? request.provider === \"claude\"\n ? { effort: request.effort }\n : { modelReasoningEffort: request.effort }\n : {}),\n };\n\n const runtime = resuming\n ? {\n sessionId: request.resumeSessionId,\n sessionParams:\n request.provider === \"claude\"\n ? { sessionId: request.resumeSessionId, cwd: request.cwd }\n : { sessionId: request.resumeSessionId },\n sessionDisplayId: null,\n taskKey: null,\n }\n : { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null };\n\n const result = await driver.execute({\n runId: request.runId,\n agent: {\n id: request.identity.agentId,\n companyId: request.identity.companyId,\n name: \"AI Matrx coding session\",\n adapterType: `${request.provider}_local`,\n adapterConfig: {},\n },\n runtime,\n config,\n context: { matrxPrompt: request.prompt },\n onLog: async (stream: \"stdout\" | \"stderr\", chunk: string) => {\n // THE SILENT-FRESH-SESSION GUARD. When a resume fails the driver quietly\n // retries in a brand-new session, which would answer a taken-over chat\n // with no memory of it. Refuse the retry and say why instead.\n if (resuming && /retrying with a fresh session/.test(chunk)) {\n refuseNextSpawn = true;\n await send({ kind: \"refused\", op: \"fresh-session-retry\", target: chunk.trim() });\n }\n await send({ kind: \"log\", stream, chunk });\n },\n onMeta: async (meta: Record<string, unknown>) => {\n await send({\n kind: \"meta\",\n command: meta.command ?? null,\n args: Array.isArray(meta.commandArgs) ? meta.commandArgs : [],\n envKeys:\n meta.env && typeof meta.env === \"object\" ? Object.keys(meta.env as object).sort() : [],\n });\n },\n onSpawn: async (meta: { pid: number; processGroupId: number | null; startedAt: string }) => {\n if (refuseNextSpawn) {\n try {\n process.kill(-(meta.processGroupId ?? meta.pid), \"SIGKILL\");\n } catch {\n try {\n process.kill(meta.pid, \"SIGKILL\");\n } catch {\n /* already gone */\n }\n }\n return;\n }\n await send({ kind: \"spawn\", pid: meta.pid, processGroupId: meta.processGroupId });\n },\n });\n\n await send({\n kind: \"result\",\n refusedFreshSession: refuseNextSpawn,\n result: {\n exitCode: result.exitCode ?? null,\n signal: result.signal ?? null,\n timedOut: result.timedOut === true,\n errorMessage: typeof result.errorMessage === \"string\" ? result.errorMessage : null,\n errorCode: typeof result.errorCode === \"string\" ? result.errorCode : null,\n sessionId: typeof result.sessionId === \"string\" ? result.sessionId : null,\n billingType: typeof result.billingType === \"string\" ? result.billingType : null,\n model: typeof result.model === \"string\" ? result.model : null,\n usage: result.usage ?? null,\n costUsd: typeof result.costUsd === \"number\" ? result.costUsd : null,\n summary: typeof result.summary === \"string\" ? result.summary.slice(0, 2000) : null,\n },\n });\n}\n\nfunction execText(command: string, args: string[], timeoutMs = 8000): Promise<string | null> {\n return new Promise((resolve) => {\n execFile(command, args, { timeout: timeoutMs, env: process.env }, (error, stdout) => {\n resolve(error ? null : String(stdout));\n });\n });\n}\n\nasync function probe(request: ProbeRequest): Promise<void> {\n // Claude: the driver's own `claude auth status` reader.\n const claudeState: Record<string, unknown> = { installed: false };\n if (request.commands.claude) {\n const version = await execText(request.commands.claude, [\"--version\"]);\n claudeState.installed = version !== null;\n claudeState.version = version?.trim().split(/\\s+/)[0] ?? null;\n if (version !== null) {\n const claude = (await import(\"@paperclipai/adapter-claude-local/server\")) as unknown as {\n readClaudeAuthStatus: () => Promise<\n { loggedIn: boolean; authMethod: string | null; subscriptionType: string | null } | null\n >;\n };\n const status = await claude.readClaudeAuthStatus();\n claudeState.auth = status;\n }\n }\n\n // Codex: the driver's own `auth.json` reader, against the user's own home.\n const codexState: Record<string, unknown> = { installed: false };\n if (request.commands.codex) {\n const version = await execText(request.commands.codex, [\"--version\"]);\n codexState.installed = version !== null;\n codexState.version = version?.trim().split(/\\s+/).pop() ?? null;\n if (version !== null) {\n const codex = (await import(\"@paperclipai/adapter-codex-local/server\")) as unknown as {\n readCodexAuthInfo: (home?: string) => Promise<\n { planType: string | null; email: string | null } | null\n >;\n };\n const home = path.join(os.homedir(), \".codex\");\n const info = await codex.readCodexAuthInfo(home);\n let apiKey = false;\n if (!info) {\n try {\n const raw = JSON.parse(await fsp.readFile(path.join(home, \"auth.json\"), \"utf8\")) as {\n OPENAI_API_KEY?: unknown;\n };\n apiKey = typeof raw.OPENAI_API_KEY === \"string\" && raw.OPENAI_API_KEY.length > 0;\n } catch {\n apiKey = false;\n }\n }\n // Usage windows from the driver's own reader. Codex only: Claude's\n // reader falls back to driving an interactive `claude /usage` session,\n // which is not something a status check should do to a person's account.\n if (request.quota && info) {\n const quotaMod = codex as unknown as {\n getQuotaWindows?: () => Promise<{ ok?: boolean; windows?: unknown[] }>;\n };\n const answer = await Promise.race([\n quotaMod.getQuotaWindows?.().catch(() => null) ?? Promise.resolve(null),\n new Promise<null>((resolve) => setTimeout(() => resolve(null), 10000)),\n ]);\n codexState.quota = Array.isArray(answer?.windows)\n ? answer.windows.map((w) => {\n const win = (w ?? {}) as Record<string, unknown>;\n return {\n label: typeof win.label === \"string\" ? win.label : \"Usage\",\n usedPercent: typeof win.usedPercent === \"number\" ? win.usedPercent : null,\n resetsAt: typeof win.resetsAt === \"string\" ? win.resetsAt : null,\n valueLabel: typeof win.valueLabel === \"string\" ? win.valueLabel : null,\n };\n })\n : [];\n }\n // Never forward a token: only the plan and account label leave this process.\n codexState.auth = info\n ? { loggedIn: true, planType: info.planType ?? null, email: info.email ?? null }\n : { loggedIn: apiKey, planType: null, email: null, apiKey };\n }\n }\n // The model lists the drivers ship (constants only; nothing is run).\n for (const [state, spec] of [\n [claudeState, \"@paperclipai/adapter-claude-local\"],\n [codexState, \"@paperclipai/adapter-codex-local\"],\n ] as const) {\n try {\n const mod = (await import(spec)) as { models?: unknown; DEFAULT_CLAUDE_LOCAL_MODEL?: unknown };\n // The model the Claude driver forces when none is passed.\n if (typeof mod.DEFAULT_CLAUDE_LOCAL_MODEL === \"string\") state.driverDefaultModel = mod.DEFAULT_CLAUDE_LOCAL_MODEL;\n state.models = Array.isArray(mod.models)\n ? mod.models.flatMap((m) => {\n const entry = (m ?? {}) as Record<string, unknown>;\n return typeof entry.id === \"string\" && entry.id\n ? [{ id: entry.id, label: typeof entry.label === \"string\" ? entry.label : entry.id }]\n : [];\n })\n : [];\n } catch {\n state.models = [];\n }\n }\n await send({ kind: \"probe-result\", claude: claudeState, codex: codexState });\n}\n\n// Only when forked by the engine: importing this file (a test reading\n// `spliceCodexConfigGuard`) must not turn the importer into a worker.\nif (process.env.MATRX_CODING_SESSIONS_WORKER === \"1\") process.on(\"message\", (raw) => {\n const request = raw as Request;\n const work = request.kind === \"run\" ? run(request) : probe(request);\n work\n .catch(async (error: unknown) => {\n await send({\n kind: \"fatal\",\n message: error instanceof Error ? error.message : String(error),\n });\n })\n .finally(() => {\n process.disconnect?.();\n process.exit(0);\n });\n});\n"],"mappings":"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","names":[]}
@@ -196,6 +196,8 @@ interface CodingSession {
196
196
  queued: string[];
197
197
  /** The AI Matrx agent loaded into every run of this session, when one was picked. */
198
198
  agent?: SessionAgent | null | undefined;
199
+ /** Codex must be told, in the next prompt, that an earlier agent no longer applies. */
200
+ agentRemovedDue?: boolean | undefined;
199
201
  }
200
202
  /**
201
203
  * One normalized transcript item — the same shape from Claude and Codex, from
@@ -309,6 +311,21 @@ interface SessionAgent {
309
311
  name: string;
310
312
  reason: string;
311
313
  }[];
314
+ /** The skills it carries (labels) and the tools it serves (names) — the agent's own set. */
315
+ skills?: string[] | undefined;
316
+ tools?: string[] | undefined;
317
+ /** The person's variable values, kept so "Update to vN" can reuse them. */
318
+ variables?: Record<string, JsonValue> | undefined;
319
+ /**
320
+ * The CLI must be TOLD in the next prompt (a delimited block): a Codex
321
+ * takeover or agent switch (Codex ignores developer_instructions on resume),
322
+ * or a Codex compaction that dropped the block. Cleared once delivered.
323
+ */
324
+ blockDue?: boolean | undefined;
325
+ /** When the block (or native instructions) last reached the CLI. */
326
+ deliveredAt?: string | null | undefined;
327
+ /** The agent this one replaced mid-chat ("Code Reviewer v1"), named in the block. */
328
+ replaces?: string | null | undefined;
312
329
  }
313
330
  /** Whether this host can reach AI Matrx as a signed-in person. */
314
331
  interface AgentAccess {
@@ -339,12 +356,13 @@ interface AgentCatalogRpcResult {
339
356
  } | null;
340
357
  count?: number | null;
341
358
  }
342
- /** What the New session form needs about one agent. */
359
+ /** What a picker needs about one agent — the LIGHT read (no skills, no tools). */
343
360
  interface AgentDetails {
344
361
  agentId: string;
345
362
  name: string;
346
363
  /** Variable declarations as authored (camelCase). */
347
364
  variables: Record<string, JsonValue>[];
365
+ /** Newest first. */
348
366
  versions: {
349
367
  id: string;
350
368
  versionNumber: number;
@@ -352,12 +370,15 @@ interface AgentDetails {
352
370
  }[];
353
371
  /** The version "Latest" resolves to right now. */
354
372
  latestVersionNumber: number | null;
373
+ latestVersionId: string | null;
355
374
  }
356
375
  interface TakeoverInput {
357
376
  provider: CodingProvider;
358
377
  providerSessionId: string;
359
378
  cwd: string;
360
379
  title?: string | undefined;
380
+ /** Continue the chat WITH an AI Matrx agent ("Continue with agent"). */
381
+ agent?: AgentSelection | null | undefined;
361
382
  }
362
383
  interface SendResult {
363
384
  session: CodingSession;
@@ -434,6 +455,12 @@ interface CodingSessionsApi {
434
455
  agentCatalogRpc(request: AgentCatalogRpcRequest): Promise<AgentCatalogRpcResult>;
435
456
  /** An agent's variables and saved versions, for the New session form. */
436
457
  agentDetails(agentId: string): Promise<AgentDetails>;
458
+ /**
459
+ * Load a different agent into a session from its next message on (or
460
+ * remove it, with `null`). Claude reads it every turn; Codex is told in the
461
+ * next prompt.
462
+ */
463
+ setAgent(sessionId: string, agent: AgentSelection | null): Promise<CodingSession>;
437
464
  }
438
465
  type RawTranscript = {
439
466
  available: true;
@@ -522,15 +549,20 @@ declare function createAiMatrxRawBackupSink(options: {
522
549
  interface CodingSessionsAgentsOptions {
523
550
  /** The person's AI Matrx sign-in. Absent or `get() → null` = signed out. */
524
551
  credentials?: CredentialsPort | null | undefined;
525
- /** The organization agent work runs in (value or getter, read per call). */
526
- organizationId?: string | null | (() => string | null | undefined) | undefined;
552
+ /**
553
+ * The organization agent work runs in: a value, or a getter read per call —
554
+ * sync or async (Matrx 2's `codingSessionsCredentials()` hands an async one).
555
+ */
556
+ organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>) | undefined;
527
557
  /** AI Matrx server. Default `https://server.app.matrxserver.com`. */
528
558
  serverUrl?: string | undefined;
529
559
  /** The platform database URL. Default `https://db.matrxserver.com`. */
530
560
  supabaseUrl?: string | undefined;
531
- /** The database's publishable key — needed for the agent picker's reads. */
561
+ /** The database's publishable key (the picker's reads). Default: the platform's. */
532
562
  supabaseKey?: string | undefined;
533
563
  }
564
+ declare const DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
565
+ declare const DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
534
566
 
535
567
  /**
536
568
  * Reading the providers' OWN files — cheaply, and never copying them.
@@ -767,4 +799,4 @@ interface CodingSessionsEngine extends CodingSessionsApi {
767
799
  declare function describeProviderStatus(provider: CodingProvider, probe: Record<string, unknown> | undefined, lastProblem?: ProviderProblem | null, quota?: QuotaWindow[]): ProviderStatus;
768
800
  declare function createCodingSessionsEngine(options?: CodingSessionsEngineOptions): Promise<CodingSessionsEngine>;
769
801
 
770
- export { snippetAround as A, type CodingSessionsMethod as C, EFFORT_LEVELS as E, PreferenceStore as P, RAW_TRANSCRIPT_MAX_BYTES as R, type CodingSessionsApi as a, type CodingEvent as b, type CodingSessionsEngine as c, type CodingSessionsEngineOptions as d, type ProviderHomes as e, type RawBackupPolicy as f, type RawBackupSink as g, type RunStatus as h, buildCleanEnv as i, claudeDefaults as j, codexDefaults as k, createAiMatrxRawBackupSink as l, createCodingSessionsEngine as m, createRawBackupScheduler as n, defaultProviderHomes as o, describeActivity as p, describeProviderStatus as q, discoverLocalSessions as r, endsWithOpenToolCall as s, findDrivingProcess as t, findSessionFile as u, isAllowedEnvName as v, listProcesses as w, readSessionFile as x, searchLocalChats as y, sessionActivity as z };
802
+ export { searchLocalChats as A, sessionActivity as B, type CodingSessionsMethod as C, DEFAULT_SERVER_URL as D, EFFORT_LEVELS as E, snippetAround as F, PreferenceStore as P, RAW_TRANSCRIPT_MAX_BYTES as R, type CodingSessionsApi as a, type CodingEvent as b, type CodingSessionsEngine as c, type CodingSessionsEngineOptions as d, type CodingSessionsAgentsOptions as e, DEFAULT_SUPABASE_URL as f, type ProviderHomes as g, type RawBackupPolicy as h, type RawBackupSink as i, type RunStatus as j, buildCleanEnv as k, claudeDefaults as l, codexDefaults as m, createAiMatrxRawBackupSink as n, createCodingSessionsEngine as o, createRawBackupScheduler as p, defaultProviderHomes as q, describeActivity as r, describeProviderStatus as s, discoverLocalSessions as t, endsWithOpenToolCall as u, findDrivingProcess as v, findSessionFile as w, isAllowedEnvName as x, listProcesses as y, readSessionFile as z };
@@ -196,6 +196,8 @@ interface CodingSession {
196
196
  queued: string[];
197
197
  /** The AI Matrx agent loaded into every run of this session, when one was picked. */
198
198
  agent?: SessionAgent | null | undefined;
199
+ /** Codex must be told, in the next prompt, that an earlier agent no longer applies. */
200
+ agentRemovedDue?: boolean | undefined;
199
201
  }
200
202
  /**
201
203
  * One normalized transcript item — the same shape from Claude and Codex, from
@@ -309,6 +311,21 @@ interface SessionAgent {
309
311
  name: string;
310
312
  reason: string;
311
313
  }[];
314
+ /** The skills it carries (labels) and the tools it serves (names) — the agent's own set. */
315
+ skills?: string[] | undefined;
316
+ tools?: string[] | undefined;
317
+ /** The person's variable values, kept so "Update to vN" can reuse them. */
318
+ variables?: Record<string, JsonValue> | undefined;
319
+ /**
320
+ * The CLI must be TOLD in the next prompt (a delimited block): a Codex
321
+ * takeover or agent switch (Codex ignores developer_instructions on resume),
322
+ * or a Codex compaction that dropped the block. Cleared once delivered.
323
+ */
324
+ blockDue?: boolean | undefined;
325
+ /** When the block (or native instructions) last reached the CLI. */
326
+ deliveredAt?: string | null | undefined;
327
+ /** The agent this one replaced mid-chat ("Code Reviewer v1"), named in the block. */
328
+ replaces?: string | null | undefined;
312
329
  }
313
330
  /** Whether this host can reach AI Matrx as a signed-in person. */
314
331
  interface AgentAccess {
@@ -339,12 +356,13 @@ interface AgentCatalogRpcResult {
339
356
  } | null;
340
357
  count?: number | null;
341
358
  }
342
- /** What the New session form needs about one agent. */
359
+ /** What a picker needs about one agent — the LIGHT read (no skills, no tools). */
343
360
  interface AgentDetails {
344
361
  agentId: string;
345
362
  name: string;
346
363
  /** Variable declarations as authored (camelCase). */
347
364
  variables: Record<string, JsonValue>[];
365
+ /** Newest first. */
348
366
  versions: {
349
367
  id: string;
350
368
  versionNumber: number;
@@ -352,12 +370,15 @@ interface AgentDetails {
352
370
  }[];
353
371
  /** The version "Latest" resolves to right now. */
354
372
  latestVersionNumber: number | null;
373
+ latestVersionId: string | null;
355
374
  }
356
375
  interface TakeoverInput {
357
376
  provider: CodingProvider;
358
377
  providerSessionId: string;
359
378
  cwd: string;
360
379
  title?: string | undefined;
380
+ /** Continue the chat WITH an AI Matrx agent ("Continue with agent"). */
381
+ agent?: AgentSelection | null | undefined;
361
382
  }
362
383
  interface SendResult {
363
384
  session: CodingSession;
@@ -434,6 +455,12 @@ interface CodingSessionsApi {
434
455
  agentCatalogRpc(request: AgentCatalogRpcRequest): Promise<AgentCatalogRpcResult>;
435
456
  /** An agent's variables and saved versions, for the New session form. */
436
457
  agentDetails(agentId: string): Promise<AgentDetails>;
458
+ /**
459
+ * Load a different agent into a session from its next message on (or
460
+ * remove it, with `null`). Claude reads it every turn; Codex is told in the
461
+ * next prompt.
462
+ */
463
+ setAgent(sessionId: string, agent: AgentSelection | null): Promise<CodingSession>;
437
464
  }
438
465
  type RawTranscript = {
439
466
  available: true;
@@ -522,15 +549,20 @@ declare function createAiMatrxRawBackupSink(options: {
522
549
  interface CodingSessionsAgentsOptions {
523
550
  /** The person's AI Matrx sign-in. Absent or `get() → null` = signed out. */
524
551
  credentials?: CredentialsPort | null | undefined;
525
- /** The organization agent work runs in (value or getter, read per call). */
526
- organizationId?: string | null | (() => string | null | undefined) | undefined;
552
+ /**
553
+ * The organization agent work runs in: a value, or a getter read per call —
554
+ * sync or async (Matrx 2's `codingSessionsCredentials()` hands an async one).
555
+ */
556
+ organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>) | undefined;
527
557
  /** AI Matrx server. Default `https://server.app.matrxserver.com`. */
528
558
  serverUrl?: string | undefined;
529
559
  /** The platform database URL. Default `https://db.matrxserver.com`. */
530
560
  supabaseUrl?: string | undefined;
531
- /** The database's publishable key — needed for the agent picker's reads. */
561
+ /** The database's publishable key (the picker's reads). Default: the platform's. */
532
562
  supabaseKey?: string | undefined;
533
563
  }
564
+ declare const DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
565
+ declare const DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
534
566
 
535
567
  /**
536
568
  * Reading the providers' OWN files — cheaply, and never copying them.
@@ -767,4 +799,4 @@ interface CodingSessionsEngine extends CodingSessionsApi {
767
799
  declare function describeProviderStatus(provider: CodingProvider, probe: Record<string, unknown> | undefined, lastProblem?: ProviderProblem | null, quota?: QuotaWindow[]): ProviderStatus;
768
800
  declare function createCodingSessionsEngine(options?: CodingSessionsEngineOptions): Promise<CodingSessionsEngine>;
769
801
 
770
- export { snippetAround as A, type CodingSessionsMethod as C, EFFORT_LEVELS as E, PreferenceStore as P, RAW_TRANSCRIPT_MAX_BYTES as R, type CodingSessionsApi as a, type CodingEvent as b, type CodingSessionsEngine as c, type CodingSessionsEngineOptions as d, type ProviderHomes as e, type RawBackupPolicy as f, type RawBackupSink as g, type RunStatus as h, buildCleanEnv as i, claudeDefaults as j, codexDefaults as k, createAiMatrxRawBackupSink as l, createCodingSessionsEngine as m, createRawBackupScheduler as n, defaultProviderHomes as o, describeActivity as p, describeProviderStatus as q, discoverLocalSessions as r, endsWithOpenToolCall as s, findDrivingProcess as t, findSessionFile as u, isAllowedEnvName as v, listProcesses as w, readSessionFile as x, searchLocalChats as y, sessionActivity as z };
802
+ export { searchLocalChats as A, sessionActivity as B, type CodingSessionsMethod as C, DEFAULT_SERVER_URL as D, EFFORT_LEVELS as E, snippetAround as F, PreferenceStore as P, RAW_TRANSCRIPT_MAX_BYTES as R, type CodingSessionsApi as a, type CodingEvent as b, type CodingSessionsEngine as c, type CodingSessionsEngineOptions as d, type CodingSessionsAgentsOptions as e, DEFAULT_SUPABASE_URL as f, type ProviderHomes as g, type RawBackupPolicy as h, type RawBackupSink as i, type RunStatus as j, buildCleanEnv as k, claudeDefaults as l, codexDefaults as m, createAiMatrxRawBackupSink as n, createCodingSessionsEngine as o, createRawBackupScheduler as p, defaultProviderHomes as q, describeActivity as r, describeProviderStatus as s, discoverLocalSessions as t, endsWithOpenToolCall as u, findDrivingProcess as v, findSessionFile as w, isAllowedEnvName as x, listProcesses as y, readSessionFile as z };
package/dist/index.cjs CHANGED
@@ -54,6 +54,20 @@ function isTerminalRunStatus(status) {
54
54
  return TERMINAL_RUN_STATUSES.includes(status);
55
55
  }
56
56
 
57
+ // src/normalize/agent-block.ts
58
+ var AGENT_BLOCK_MARKER = "<ai_matrx_agent ";
59
+ function stripAgentBlock(text) {
60
+ const removed = text.match(/^<ai_matrx_agent_removed>[\s\S]*?<\/ai_matrx_agent_removed>\s*/);
61
+ if (removed) return text.slice(removed[0].length);
62
+ if (!text.startsWith(AGENT_BLOCK_MARKER)) return text;
63
+ const end = text.indexOf("</ai_matrx_agent>");
64
+ if (end < 0) return text;
65
+ let rest = text.slice(end + "</ai_matrx_agent>".length);
66
+ const skills = rest.match(/^\s*<skills_instructions>[\s\S]*?<\/skills_instructions>/);
67
+ if (skills) rest = rest.slice(skills[0].length);
68
+ return rest.replace(/^\s+/, "");
69
+ }
70
+
57
71
  // src/normalize/claude.ts
58
72
  function isObject(value) {
59
73
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -143,7 +157,7 @@ function createClaudeNormalizer() {
143
157
  if (typeof content === "string") {
144
158
  if (isSyntheticClaudeUserText(content) || content.trim().length === 0) return [];
145
159
  userCounter += 1;
146
- return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: content, at }];
160
+ return [{ kind: "user", id: str(entry.uuid) ?? `user-${userCounter}`, text: stripAgentBlock(content), at }];
147
161
  }
148
162
  if (!Array.isArray(content)) return [];
149
163
  const items = [];
@@ -167,7 +181,7 @@ function createClaudeNormalizer() {
167
181
  items.unshift({
168
182
  kind: "user",
169
183
  id: str(entry.uuid) ?? `user-${userCounter}`,
170
- text: typed.join("\n\n"),
184
+ text: stripAgentBlock(typed.join("\n\n")),
171
185
  at
172
186
  });
173
187
  }
@@ -422,7 +436,7 @@ function rolloutItem(item, fallbackId, at) {
422
436
  const id = str2(item.id) ?? fallbackId;
423
437
  switch (item.type) {
424
438
  case "UserMessage": {
425
- const text = stripCodexInjectedContext(itemText(item.content));
439
+ const text = stripCodexInjectedContext(stripAgentBlock(itemText(item.content)));
426
440
  return text.trim() ? [{ kind: "user", id, text, at }] : [];
427
441
  }
428
442
  case "AgentMessage": {
@@ -505,7 +519,7 @@ function createCodexRolloutNormalizer() {
505
519
  return rolloutItem(payload.item, fallbackId, at);
506
520
  }
507
521
  if (payload.type === "user_message" && !appItems) {
508
- const text = stripCodexInjectedContext(str2(payload.message) ?? "");
522
+ const text = stripCodexInjectedContext(stripAgentBlock(str2(payload.message) ?? ""));
509
523
  return text.trim() ? [{ kind: "user", id: fallbackId, text, at }] : [];
510
524
  }
511
525
  return [];