@ai-matrx/coding-sessions 0.1.83 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/engine/index.cjs +656 -126
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -1
- package/dist/engine/index.d.ts +4 -1
- package/dist/engine/index.js +654 -126
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +7 -5
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-DQ0teQvw.d.cts → index-aavbdzSo.d.cts} +161 -1
- package/dist/{index-DQ0teQvw.d.ts → index-aavbdzSo.d.ts} +161 -1
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -1
- package/dist/index.d.ts +112 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +821 -412
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +113 -8
- package/dist/react/index.d.ts +113 -8
- package/dist/react/index.js +851 -418
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +720 -141
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +5 -1
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.js +719 -140
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/index-XIcCZr8A.js +71 -0
- package/dist/ui/assets/index-eqe0sxR4.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +3 -1
- package/dist/ui/assets/index-C-_1qjy9.css +0 -2
- package/dist/ui/assets/index-CmFgFvLa.js +0 -69
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,
|
|
@@ -129,7 +131,7 @@ async function run(request) {
|
|
|
129
131
|
engine: "cli",
|
|
130
132
|
dangerouslySkipPermissions: true,
|
|
131
133
|
// Token-level streaming for the transcript.
|
|
132
|
-
extraArgs: ["--include-partial-messages"]
|
|
134
|
+
extraArgs: ["--include-partial-messages", ...request.agentArgs ?? []]
|
|
133
135
|
} : {
|
|
134
136
|
engine: "cli",
|
|
135
137
|
// `codex exec` (start AND `exec … resume`) refuses any folder that
|
|
@@ -139,7 +141,7 @@ async function run(request) {
|
|
|
139
141
|
// and we never write trust entries into their config. The driver
|
|
140
142
|
// places extraArgs before `resume <id> -` and dedupes this flag
|
|
141
143
|
// against its own sandbox injection.
|
|
142
|
-
extraArgs: [CODEX_SKIP_GIT_REPO_CHECK]
|
|
144
|
+
extraArgs: [CODEX_SKIP_GIT_REPO_CHECK, ...request.agentArgs ?? []]
|
|
143
145
|
},
|
|
144
146
|
...request.model ? { model: request.model } : {},
|
|
145
147
|
...request.effort ? request.provider === "claude" ? { effort: request.effort } : { modelReasoningEffort: request.effort } : {}
|
|
@@ -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}\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\"],\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],\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":[]}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { JsonValue } from '@ai-matrx/agents/portable';
|
|
2
|
+
import { CredentialsPort } from '@ai-matrx/data';
|
|
3
|
+
import { McpHttpReply } from '@ai-matrx/agents/portable/mcp';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* The public contract of `@ai-matrx/coding-sessions`, shared by the engine
|
|
3
7
|
* (Node), the local host (Node) and the UI (browser). Pure types — nothing here
|
|
4
8
|
* touches Node or the DOM, and every value is JSON-serializable because it
|
|
5
9
|
* crosses a transport (WebSocket today, desktop IPC later).
|
|
6
10
|
*/
|
|
11
|
+
|
|
7
12
|
/** The coding agents this package drives. "coding session" is the canonical
|
|
8
13
|
* platform term (chat.coding_session, the Coding Session Bridge). */
|
|
9
14
|
type CodingProvider = "claude" | "codex";
|
|
@@ -189,6 +194,10 @@ interface CodingSession {
|
|
|
189
194
|
lastRunStatus: RunStatus | null;
|
|
190
195
|
/** Prompts waiting for the active run to finish, oldest first. */
|
|
191
196
|
queued: string[];
|
|
197
|
+
/** The AI Matrx agent loaded into every run of this session, when one was picked. */
|
|
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;
|
|
192
201
|
}
|
|
193
202
|
/**
|
|
194
203
|
* One normalized transcript item — the same shape from Claude and Codex, from
|
|
@@ -269,12 +278,105 @@ interface StartInput {
|
|
|
269
278
|
prompt: string;
|
|
270
279
|
model?: string | null | undefined;
|
|
271
280
|
effort?: string | null | undefined;
|
|
281
|
+
/** An AI Matrx agent to load into the CLI. Needs the host's `agents` credentials. */
|
|
282
|
+
agent?: AgentSelection | null | undefined;
|
|
283
|
+
}
|
|
284
|
+
/** Which AI Matrx agent a new session loads, and the person's variable values. */
|
|
285
|
+
interface AgentSelection {
|
|
286
|
+
agentId: string;
|
|
287
|
+
/** Pin one saved version; omitted = the latest, pinned at start. */
|
|
288
|
+
versionId?: string | null | undefined;
|
|
289
|
+
variables?: Record<string, JsonValue> | undefined;
|
|
290
|
+
}
|
|
291
|
+
/** One part of an agent and whether this session's CLI carries it. */
|
|
292
|
+
interface SessionAgentPart {
|
|
293
|
+
part: "instructions" | "skills" | "tools";
|
|
294
|
+
loaded: number;
|
|
295
|
+
total: number;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The agent a session runs with. Recorded at start and never drifts: every
|
|
299
|
+
* later turn reloads the SAME bundle (same version, same hash).
|
|
300
|
+
*/
|
|
301
|
+
interface SessionAgent {
|
|
302
|
+
agentId: string;
|
|
303
|
+
versionId: string | null;
|
|
304
|
+
versionNumber: number | null;
|
|
305
|
+
name: string;
|
|
306
|
+
definitionHash: string;
|
|
307
|
+
loaded: SessionAgentPart[];
|
|
308
|
+
/** What could not be carried, with the reason. */
|
|
309
|
+
unavailable: {
|
|
310
|
+
kind: string;
|
|
311
|
+
name: string;
|
|
312
|
+
reason: string;
|
|
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
|
+
}
|
|
328
|
+
/** Whether this host can reach AI Matrx as a signed-in person. */
|
|
329
|
+
interface AgentAccess {
|
|
330
|
+
signedIn: boolean;
|
|
331
|
+
userId: string | null;
|
|
332
|
+
organizationId: string | null;
|
|
333
|
+
/** True when the host was started with no AI Matrx connection at all. */
|
|
334
|
+
configured: boolean;
|
|
335
|
+
}
|
|
336
|
+
/** A catalogue read the picker asks the host to make (PostgREST RPC shape). */
|
|
337
|
+
interface AgentCatalogRpcRequest {
|
|
338
|
+
fn: string;
|
|
339
|
+
args?: Record<string, unknown> | undefined;
|
|
340
|
+
count?: "exact" | "planned" | "estimated" | undefined;
|
|
341
|
+
range?: [number, number] | undefined;
|
|
342
|
+
order?: {
|
|
343
|
+
column: string;
|
|
344
|
+
ascending?: boolean | undefined;
|
|
345
|
+
} | undefined;
|
|
346
|
+
}
|
|
347
|
+
interface AgentCatalogRpcResult {
|
|
348
|
+
data: unknown;
|
|
349
|
+
error: {
|
|
350
|
+
message: string;
|
|
351
|
+
code?: string | undefined;
|
|
352
|
+
details?: string | null | undefined;
|
|
353
|
+
hint?: string | null | undefined;
|
|
354
|
+
} | null;
|
|
355
|
+
count?: number | null;
|
|
356
|
+
}
|
|
357
|
+
/** What a picker needs about one agent — the LIGHT read (no skills, no tools). */
|
|
358
|
+
interface AgentDetails {
|
|
359
|
+
agentId: string;
|
|
360
|
+
name: string;
|
|
361
|
+
/** Variable declarations as authored (camelCase). */
|
|
362
|
+
variables: Record<string, JsonValue>[];
|
|
363
|
+
/** Newest first. */
|
|
364
|
+
versions: {
|
|
365
|
+
id: string;
|
|
366
|
+
versionNumber: number;
|
|
367
|
+
changedAt: string | null;
|
|
368
|
+
}[];
|
|
369
|
+
/** The version "Latest" resolves to right now. */
|
|
370
|
+
latestVersionNumber: number | null;
|
|
371
|
+
latestVersionId: string | null;
|
|
272
372
|
}
|
|
273
373
|
interface TakeoverInput {
|
|
274
374
|
provider: CodingProvider;
|
|
275
375
|
providerSessionId: string;
|
|
276
376
|
cwd: string;
|
|
277
377
|
title?: string | undefined;
|
|
378
|
+
/** Continue the chat WITH an AI Matrx agent ("Continue with agent"). */
|
|
379
|
+
agent?: AgentSelection | null | undefined;
|
|
278
380
|
}
|
|
279
381
|
interface SendResult {
|
|
280
382
|
session: CodingSession;
|
|
@@ -345,6 +447,18 @@ interface CodingSessionsApi {
|
|
|
345
447
|
model?: string | null;
|
|
346
448
|
effort?: string | null;
|
|
347
449
|
}): Promise<CodingSession>;
|
|
450
|
+
/** Is this host signed in to AI Matrx (so agents can load)? */
|
|
451
|
+
agentAccess(): Promise<AgentAccess>;
|
|
452
|
+
/** One allowlisted agent-catalogue read, made by the host as the signed-in person. */
|
|
453
|
+
agentCatalogRpc(request: AgentCatalogRpcRequest): Promise<AgentCatalogRpcResult>;
|
|
454
|
+
/** An agent's variables and saved versions, for the New session form. */
|
|
455
|
+
agentDetails(agentId: string): Promise<AgentDetails>;
|
|
456
|
+
/**
|
|
457
|
+
* Load a different agent into a session from its next message on (or
|
|
458
|
+
* remove it, with `null`). Claude reads it every turn; Codex is told in the
|
|
459
|
+
* next prompt.
|
|
460
|
+
*/
|
|
461
|
+
setAgent(sessionId: string, agent: AgentSelection | null): Promise<CodingSession>;
|
|
348
462
|
}
|
|
349
463
|
type RawTranscript = {
|
|
350
464
|
available: true;
|
|
@@ -413,6 +527,41 @@ declare function createAiMatrxRawBackupSink(options: {
|
|
|
413
527
|
fetch?: typeof fetch;
|
|
414
528
|
}): RawBackupSink;
|
|
415
529
|
|
|
530
|
+
/**
|
|
531
|
+
* THE AI MATRX CONNECTION — what a host needs to load the person's agents into
|
|
532
|
+
* their coding sessions: who they are (a `CredentialsPort`, read fresh on every
|
|
533
|
+
* call), which organization the work runs in, the agent picker's catalogue
|
|
534
|
+
* reads, the portable bundle, and one MCP endpoint per session for the agent's
|
|
535
|
+
* server tools.
|
|
536
|
+
*
|
|
537
|
+
* The token lives only in this process. The browser page asks the host to make
|
|
538
|
+
* each catalogue read (an allowlist of RPCs); the CLI only ever sees a
|
|
539
|
+
* 127.0.0.1 URL carrying a per-session random secret.
|
|
540
|
+
*
|
|
541
|
+
* A host with no credentials (the standalone launchd page today) is a STATE,
|
|
542
|
+
* not an error: `access()` says signed out, the page shows one line, and plain
|
|
543
|
+
* sessions work exactly as before.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
/** The host option that connects coding sessions to AI Matrx. */
|
|
547
|
+
interface CodingSessionsAgentsOptions {
|
|
548
|
+
/** The person's AI Matrx sign-in. Absent or `get() → null` = signed out. */
|
|
549
|
+
credentials?: CredentialsPort | null | undefined;
|
|
550
|
+
/**
|
|
551
|
+
* The organization agent work runs in: a value, or a getter read per call —
|
|
552
|
+
* sync or async (Matrx 2's `codingSessionsCredentials()` hands an async one).
|
|
553
|
+
*/
|
|
554
|
+
organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>) | undefined;
|
|
555
|
+
/** AI Matrx server. Default `https://server.app.matrxserver.com`. */
|
|
556
|
+
serverUrl?: string | undefined;
|
|
557
|
+
/** The platform database URL. Default `https://db.matrxserver.com`. */
|
|
558
|
+
supabaseUrl?: string | undefined;
|
|
559
|
+
/** The database's publishable key (the picker's reads). Default: the platform's. */
|
|
560
|
+
supabaseKey?: string | undefined;
|
|
561
|
+
}
|
|
562
|
+
declare const DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
563
|
+
declare const DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
564
|
+
|
|
416
565
|
/**
|
|
417
566
|
* Reading the providers' OWN files — cheaply, and never copying them.
|
|
418
567
|
*
|
|
@@ -622,12 +771,23 @@ interface CodingSessionsEngineOptions {
|
|
|
622
771
|
rawBackup?: RawBackupSink | undefined;
|
|
623
772
|
/** Receives raw provider stdout per run — used to capture test fixtures. */
|
|
624
773
|
onRawOutput?: ((runId: string, provider: CodingProvider, chunk: string) => void) | undefined;
|
|
774
|
+
/**
|
|
775
|
+
* The AI Matrx connection that loads the person's agents into sessions:
|
|
776
|
+
* `credentials` (a `CredentialsPort`, read fresh per call) plus the
|
|
777
|
+
* organization. Absent → the agent picker shows signed out; plain sessions
|
|
778
|
+
* are unaffected.
|
|
779
|
+
*/
|
|
780
|
+
agents?: CodingSessionsAgentsOptions | undefined;
|
|
625
781
|
}
|
|
626
782
|
interface CodingSessionsEngine extends CodingSessionsApi {
|
|
627
783
|
subscribe(listener: (event: CodingEvent) => void): () => void;
|
|
628
784
|
/** Stop every live run and wait for the index to be written. */
|
|
629
785
|
dispose(): Promise<void>;
|
|
630
786
|
readonly dataDir: string;
|
|
787
|
+
/** The host serves agent tools at `<baseUrl>/mcp/<secret>`; null stops serving. */
|
|
788
|
+
setMcpBaseUrl(baseUrl: string | null): void;
|
|
789
|
+
/** One MCP POST for a session's agent tools; null = no session owns `secret`. */
|
|
790
|
+
handleMcp(secret: string, body: unknown): Promise<McpHttpReply | null>;
|
|
631
791
|
}
|
|
632
792
|
/**
|
|
633
793
|
* The status the screen shows: what the provider's own auth check says, then
|
|
@@ -637,4 +797,4 @@ interface CodingSessionsEngine extends CodingSessionsApi {
|
|
|
637
797
|
declare function describeProviderStatus(provider: CodingProvider, probe: Record<string, unknown> | undefined, lastProblem?: ProviderProblem | null, quota?: QuotaWindow[]): ProviderStatus;
|
|
638
798
|
declare function createCodingSessionsEngine(options?: CodingSessionsEngineOptions): Promise<CodingSessionsEngine>;
|
|
639
799
|
|
|
640
|
-
export {
|
|
800
|
+
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 };
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { JsonValue } from '@ai-matrx/agents/portable';
|
|
2
|
+
import { CredentialsPort } from '@ai-matrx/data';
|
|
3
|
+
import { McpHttpReply } from '@ai-matrx/agents/portable/mcp';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* The public contract of `@ai-matrx/coding-sessions`, shared by the engine
|
|
3
7
|
* (Node), the local host (Node) and the UI (browser). Pure types — nothing here
|
|
4
8
|
* touches Node or the DOM, and every value is JSON-serializable because it
|
|
5
9
|
* crosses a transport (WebSocket today, desktop IPC later).
|
|
6
10
|
*/
|
|
11
|
+
|
|
7
12
|
/** The coding agents this package drives. "coding session" is the canonical
|
|
8
13
|
* platform term (chat.coding_session, the Coding Session Bridge). */
|
|
9
14
|
type CodingProvider = "claude" | "codex";
|
|
@@ -189,6 +194,10 @@ interface CodingSession {
|
|
|
189
194
|
lastRunStatus: RunStatus | null;
|
|
190
195
|
/** Prompts waiting for the active run to finish, oldest first. */
|
|
191
196
|
queued: string[];
|
|
197
|
+
/** The AI Matrx agent loaded into every run of this session, when one was picked. */
|
|
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;
|
|
192
201
|
}
|
|
193
202
|
/**
|
|
194
203
|
* One normalized transcript item — the same shape from Claude and Codex, from
|
|
@@ -269,12 +278,105 @@ interface StartInput {
|
|
|
269
278
|
prompt: string;
|
|
270
279
|
model?: string | null | undefined;
|
|
271
280
|
effort?: string | null | undefined;
|
|
281
|
+
/** An AI Matrx agent to load into the CLI. Needs the host's `agents` credentials. */
|
|
282
|
+
agent?: AgentSelection | null | undefined;
|
|
283
|
+
}
|
|
284
|
+
/** Which AI Matrx agent a new session loads, and the person's variable values. */
|
|
285
|
+
interface AgentSelection {
|
|
286
|
+
agentId: string;
|
|
287
|
+
/** Pin one saved version; omitted = the latest, pinned at start. */
|
|
288
|
+
versionId?: string | null | undefined;
|
|
289
|
+
variables?: Record<string, JsonValue> | undefined;
|
|
290
|
+
}
|
|
291
|
+
/** One part of an agent and whether this session's CLI carries it. */
|
|
292
|
+
interface SessionAgentPart {
|
|
293
|
+
part: "instructions" | "skills" | "tools";
|
|
294
|
+
loaded: number;
|
|
295
|
+
total: number;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The agent a session runs with. Recorded at start and never drifts: every
|
|
299
|
+
* later turn reloads the SAME bundle (same version, same hash).
|
|
300
|
+
*/
|
|
301
|
+
interface SessionAgent {
|
|
302
|
+
agentId: string;
|
|
303
|
+
versionId: string | null;
|
|
304
|
+
versionNumber: number | null;
|
|
305
|
+
name: string;
|
|
306
|
+
definitionHash: string;
|
|
307
|
+
loaded: SessionAgentPart[];
|
|
308
|
+
/** What could not be carried, with the reason. */
|
|
309
|
+
unavailable: {
|
|
310
|
+
kind: string;
|
|
311
|
+
name: string;
|
|
312
|
+
reason: string;
|
|
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
|
+
}
|
|
328
|
+
/** Whether this host can reach AI Matrx as a signed-in person. */
|
|
329
|
+
interface AgentAccess {
|
|
330
|
+
signedIn: boolean;
|
|
331
|
+
userId: string | null;
|
|
332
|
+
organizationId: string | null;
|
|
333
|
+
/** True when the host was started with no AI Matrx connection at all. */
|
|
334
|
+
configured: boolean;
|
|
335
|
+
}
|
|
336
|
+
/** A catalogue read the picker asks the host to make (PostgREST RPC shape). */
|
|
337
|
+
interface AgentCatalogRpcRequest {
|
|
338
|
+
fn: string;
|
|
339
|
+
args?: Record<string, unknown> | undefined;
|
|
340
|
+
count?: "exact" | "planned" | "estimated" | undefined;
|
|
341
|
+
range?: [number, number] | undefined;
|
|
342
|
+
order?: {
|
|
343
|
+
column: string;
|
|
344
|
+
ascending?: boolean | undefined;
|
|
345
|
+
} | undefined;
|
|
346
|
+
}
|
|
347
|
+
interface AgentCatalogRpcResult {
|
|
348
|
+
data: unknown;
|
|
349
|
+
error: {
|
|
350
|
+
message: string;
|
|
351
|
+
code?: string | undefined;
|
|
352
|
+
details?: string | null | undefined;
|
|
353
|
+
hint?: string | null | undefined;
|
|
354
|
+
} | null;
|
|
355
|
+
count?: number | null;
|
|
356
|
+
}
|
|
357
|
+
/** What a picker needs about one agent — the LIGHT read (no skills, no tools). */
|
|
358
|
+
interface AgentDetails {
|
|
359
|
+
agentId: string;
|
|
360
|
+
name: string;
|
|
361
|
+
/** Variable declarations as authored (camelCase). */
|
|
362
|
+
variables: Record<string, JsonValue>[];
|
|
363
|
+
/** Newest first. */
|
|
364
|
+
versions: {
|
|
365
|
+
id: string;
|
|
366
|
+
versionNumber: number;
|
|
367
|
+
changedAt: string | null;
|
|
368
|
+
}[];
|
|
369
|
+
/** The version "Latest" resolves to right now. */
|
|
370
|
+
latestVersionNumber: number | null;
|
|
371
|
+
latestVersionId: string | null;
|
|
272
372
|
}
|
|
273
373
|
interface TakeoverInput {
|
|
274
374
|
provider: CodingProvider;
|
|
275
375
|
providerSessionId: string;
|
|
276
376
|
cwd: string;
|
|
277
377
|
title?: string | undefined;
|
|
378
|
+
/** Continue the chat WITH an AI Matrx agent ("Continue with agent"). */
|
|
379
|
+
agent?: AgentSelection | null | undefined;
|
|
278
380
|
}
|
|
279
381
|
interface SendResult {
|
|
280
382
|
session: CodingSession;
|
|
@@ -345,6 +447,18 @@ interface CodingSessionsApi {
|
|
|
345
447
|
model?: string | null;
|
|
346
448
|
effort?: string | null;
|
|
347
449
|
}): Promise<CodingSession>;
|
|
450
|
+
/** Is this host signed in to AI Matrx (so agents can load)? */
|
|
451
|
+
agentAccess(): Promise<AgentAccess>;
|
|
452
|
+
/** One allowlisted agent-catalogue read, made by the host as the signed-in person. */
|
|
453
|
+
agentCatalogRpc(request: AgentCatalogRpcRequest): Promise<AgentCatalogRpcResult>;
|
|
454
|
+
/** An agent's variables and saved versions, for the New session form. */
|
|
455
|
+
agentDetails(agentId: string): Promise<AgentDetails>;
|
|
456
|
+
/**
|
|
457
|
+
* Load a different agent into a session from its next message on (or
|
|
458
|
+
* remove it, with `null`). Claude reads it every turn; Codex is told in the
|
|
459
|
+
* next prompt.
|
|
460
|
+
*/
|
|
461
|
+
setAgent(sessionId: string, agent: AgentSelection | null): Promise<CodingSession>;
|
|
348
462
|
}
|
|
349
463
|
type RawTranscript = {
|
|
350
464
|
available: true;
|
|
@@ -413,6 +527,41 @@ declare function createAiMatrxRawBackupSink(options: {
|
|
|
413
527
|
fetch?: typeof fetch;
|
|
414
528
|
}): RawBackupSink;
|
|
415
529
|
|
|
530
|
+
/**
|
|
531
|
+
* THE AI MATRX CONNECTION — what a host needs to load the person's agents into
|
|
532
|
+
* their coding sessions: who they are (a `CredentialsPort`, read fresh on every
|
|
533
|
+
* call), which organization the work runs in, the agent picker's catalogue
|
|
534
|
+
* reads, the portable bundle, and one MCP endpoint per session for the agent's
|
|
535
|
+
* server tools.
|
|
536
|
+
*
|
|
537
|
+
* The token lives only in this process. The browser page asks the host to make
|
|
538
|
+
* each catalogue read (an allowlist of RPCs); the CLI only ever sees a
|
|
539
|
+
* 127.0.0.1 URL carrying a per-session random secret.
|
|
540
|
+
*
|
|
541
|
+
* A host with no credentials (the standalone launchd page today) is a STATE,
|
|
542
|
+
* not an error: `access()` says signed out, the page shows one line, and plain
|
|
543
|
+
* sessions work exactly as before.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
/** The host option that connects coding sessions to AI Matrx. */
|
|
547
|
+
interface CodingSessionsAgentsOptions {
|
|
548
|
+
/** The person's AI Matrx sign-in. Absent or `get() → null` = signed out. */
|
|
549
|
+
credentials?: CredentialsPort | null | undefined;
|
|
550
|
+
/**
|
|
551
|
+
* The organization agent work runs in: a value, or a getter read per call —
|
|
552
|
+
* sync or async (Matrx 2's `codingSessionsCredentials()` hands an async one).
|
|
553
|
+
*/
|
|
554
|
+
organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>) | undefined;
|
|
555
|
+
/** AI Matrx server. Default `https://server.app.matrxserver.com`. */
|
|
556
|
+
serverUrl?: string | undefined;
|
|
557
|
+
/** The platform database URL. Default `https://db.matrxserver.com`. */
|
|
558
|
+
supabaseUrl?: string | undefined;
|
|
559
|
+
/** The database's publishable key (the picker's reads). Default: the platform's. */
|
|
560
|
+
supabaseKey?: string | undefined;
|
|
561
|
+
}
|
|
562
|
+
declare const DEFAULT_SERVER_URL = "https://server.app.matrxserver.com";
|
|
563
|
+
declare const DEFAULT_SUPABASE_URL = "https://db.matrxserver.com";
|
|
564
|
+
|
|
416
565
|
/**
|
|
417
566
|
* Reading the providers' OWN files — cheaply, and never copying them.
|
|
418
567
|
*
|
|
@@ -622,12 +771,23 @@ interface CodingSessionsEngineOptions {
|
|
|
622
771
|
rawBackup?: RawBackupSink | undefined;
|
|
623
772
|
/** Receives raw provider stdout per run — used to capture test fixtures. */
|
|
624
773
|
onRawOutput?: ((runId: string, provider: CodingProvider, chunk: string) => void) | undefined;
|
|
774
|
+
/**
|
|
775
|
+
* The AI Matrx connection that loads the person's agents into sessions:
|
|
776
|
+
* `credentials` (a `CredentialsPort`, read fresh per call) plus the
|
|
777
|
+
* organization. Absent → the agent picker shows signed out; plain sessions
|
|
778
|
+
* are unaffected.
|
|
779
|
+
*/
|
|
780
|
+
agents?: CodingSessionsAgentsOptions | undefined;
|
|
625
781
|
}
|
|
626
782
|
interface CodingSessionsEngine extends CodingSessionsApi {
|
|
627
783
|
subscribe(listener: (event: CodingEvent) => void): () => void;
|
|
628
784
|
/** Stop every live run and wait for the index to be written. */
|
|
629
785
|
dispose(): Promise<void>;
|
|
630
786
|
readonly dataDir: string;
|
|
787
|
+
/** The host serves agent tools at `<baseUrl>/mcp/<secret>`; null stops serving. */
|
|
788
|
+
setMcpBaseUrl(baseUrl: string | null): void;
|
|
789
|
+
/** One MCP POST for a session's agent tools; null = no session owns `secret`. */
|
|
790
|
+
handleMcp(secret: string, body: unknown): Promise<McpHttpReply | null>;
|
|
631
791
|
}
|
|
632
792
|
/**
|
|
633
793
|
* The status the screen shows: what the provider's own auth check says, then
|
|
@@ -637,4 +797,4 @@ interface CodingSessionsEngine extends CodingSessionsApi {
|
|
|
637
797
|
declare function describeProviderStatus(provider: CodingProvider, probe: Record<string, unknown> | undefined, lastProblem?: ProviderProblem | null, quota?: QuotaWindow[]): ProviderStatus;
|
|
638
798
|
declare function createCodingSessionsEngine(options?: CodingSessionsEngineOptions): Promise<CodingSessionsEngine>;
|
|
639
799
|
|
|
640
|
-
export {
|
|
800
|
+
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 };
|