@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.
- package/CHANGELOG.md +12 -0
- package/dist/engine/index.cjs +184 -49
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +1 -1
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +183 -50
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +5 -3
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-DEPwyr2j.d.cts → index-CDeDKQHe.d.cts} +37 -5
- package/dist/{index-DEPwyr2j.d.ts → index-CDeDKQHe.d.ts} +37 -5
- package/dist/index.cjs +18 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +176 -25
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +29 -2
- package/dist/react/index.d.ts +29 -2
- package/dist/react/index.js +224 -62
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +182 -50
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +2 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +183 -51
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/{index-DSWoryQZ.js → index-crORYlzR.js} +1 -2
- package/dist/ui/assets/index-eqe0sxR4.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +3 -3
- package/dist/ui/assets/index-Gp2bMDtx.css +0 -2
package/dist/engine/worker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
/**
|
|
526
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
/**
|
|
526
|
-
|
|
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
|
|
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 {
|
|
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 [];
|