@ai-matrx/coding-sessions 0.1.81 → 0.1.83
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 +257 -81
- 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 +250 -74
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/worker.js +13 -1
- package/dist/engine/worker.js.map +1 -1
- package/dist/{index-RYOb-YZT.d.cts → index-DQ0teQvw.d.cts} +18 -0
- package/dist/{index-RYOb-YZT.d.ts → index-DQ0teQvw.d.ts} +18 -0
- package/dist/index.cjs +21 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +62 -8
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +18 -0
- package/dist/react/index.d.ts +18 -0
- package/dist/react/index.js +62 -8
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +266 -90
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +258 -82
- package/dist/server/index.js.map +1 -1
- package/dist/ui/assets/index-C-_1qjy9.css +2 -0
- package/dist/ui/assets/index-CmFgFvLa.js +69 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-B3Ic2ziA.js +0 -69
- package/dist/ui/assets/index-BAkpc01s.css +0 -2
package/dist/engine/worker.js
CHANGED
|
@@ -45,6 +45,7 @@ function installHistoryGuard() {
|
|
|
45
45
|
return originalRm(target, options);
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
+
var CODEX_SKIP_GIT_REPO_CHECK = "--skip-git-repo-check";
|
|
48
49
|
var CODEX_HOME_MODULE = /\/@paperclipai\/adapter-codex-local\/dist\/server\/codex-home\.js$/;
|
|
49
50
|
var ORIGINAL_SIGNATURE = "export async function writeManagedCodexMcpConfig(input) {";
|
|
50
51
|
var VENDORED_WRITE_MANAGED_CODEX_MCP_CONFIG = `export async function writeManagedCodexMcpConfig(input) {
|
|
@@ -129,7 +130,17 @@ async function run(request) {
|
|
|
129
130
|
dangerouslySkipPermissions: true,
|
|
130
131
|
// Token-level streaming for the transcript.
|
|
131
132
|
extraArgs: ["--include-partial-messages"]
|
|
132
|
-
} : {
|
|
133
|
+
} : {
|
|
134
|
+
engine: "cli",
|
|
135
|
+
// `codex exec` (start AND `exec … resume`) refuses any folder that
|
|
136
|
+
// is not a git repo or a project trusted in ~/.codex/config.toml:
|
|
137
|
+
// "Not inside a trusted directory and --skip-git-repo-check was not
|
|
138
|
+
// specified." People start chats anywhere (~/code, scratch folders),
|
|
139
|
+
// and we never write trust entries into their config. The driver
|
|
140
|
+
// places extraArgs before `resume <id> -` and dedupes this flag
|
|
141
|
+
// against its own sandbox injection.
|
|
142
|
+
extraArgs: [CODEX_SKIP_GIT_REPO_CHECK]
|
|
143
|
+
},
|
|
133
144
|
...request.model ? { model: request.model } : {},
|
|
134
145
|
...request.effort ? request.provider === "claude" ? { effort: request.effort } : { modelReasoningEffort: request.effort } : {}
|
|
135
146
|
};
|
|
@@ -286,6 +297,7 @@ if (process.env.MATRX_CODING_SESSIONS_WORKER === "1") process.on("message", (raw
|
|
|
286
297
|
});
|
|
287
298
|
});
|
|
288
299
|
export {
|
|
300
|
+
CODEX_SKIP_GIT_REPO_CHECK,
|
|
289
301
|
spliceCodexConfigGuard
|
|
290
302
|
};
|
|
291
303
|
//# sourceMappingURL=worker.js.map
|
|
@@ -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 */\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 : { engine: \"cli\" }),\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}\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":[]}
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
* platform term (chat.coding_session, the Coding Session Bridge). */
|
|
9
9
|
type CodingProvider = "claude" | "codex";
|
|
10
10
|
type Billing = "subscription" | "api" | "unknown";
|
|
11
|
+
/** Where the CLI a provider runs came from. */
|
|
12
|
+
type BinarySource = "desktop-app" | "path" | "npm-global" | "configured";
|
|
13
|
+
/** The exact CLI runs use: the newest copy on this computer (or the host's pick). */
|
|
14
|
+
interface ProviderBinary {
|
|
15
|
+
path: string;
|
|
16
|
+
version: string | null;
|
|
17
|
+
source: BinarySource;
|
|
18
|
+
/** "from ChatGPT app", "from PATH", … */
|
|
19
|
+
sourceLabel: string;
|
|
20
|
+
/** A newer copy than the one in use, when one exists. */
|
|
21
|
+
newer: {
|
|
22
|
+
path: string;
|
|
23
|
+
version: string;
|
|
24
|
+
sourceLabel: string;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
11
27
|
/** What `providers()` answers for one provider, in plain terms. */
|
|
12
28
|
interface ProviderStatus {
|
|
13
29
|
provider: CodingProvider;
|
|
@@ -45,6 +61,8 @@ interface ProviderStatus {
|
|
|
45
61
|
} | null | undefined;
|
|
46
62
|
/** The effort a run uses when none is picked (the person's own settings). */
|
|
47
63
|
defaultEffort?: string | null | undefined;
|
|
64
|
+
/** Which CLI runs use, and its version. */
|
|
65
|
+
binary?: ProviderBinary | null | undefined;
|
|
48
66
|
}
|
|
49
67
|
interface ModelOption {
|
|
50
68
|
id: string;
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
* platform term (chat.coding_session, the Coding Session Bridge). */
|
|
9
9
|
type CodingProvider = "claude" | "codex";
|
|
10
10
|
type Billing = "subscription" | "api" | "unknown";
|
|
11
|
+
/** Where the CLI a provider runs came from. */
|
|
12
|
+
type BinarySource = "desktop-app" | "path" | "npm-global" | "configured";
|
|
13
|
+
/** The exact CLI runs use: the newest copy on this computer (or the host's pick). */
|
|
14
|
+
interface ProviderBinary {
|
|
15
|
+
path: string;
|
|
16
|
+
version: string | null;
|
|
17
|
+
source: BinarySource;
|
|
18
|
+
/** "from ChatGPT app", "from PATH", … */
|
|
19
|
+
sourceLabel: string;
|
|
20
|
+
/** A newer copy than the one in use, when one exists. */
|
|
21
|
+
newer: {
|
|
22
|
+
path: string;
|
|
23
|
+
version: string;
|
|
24
|
+
sourceLabel: string;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
11
27
|
/** What `providers()` answers for one provider, in plain terms. */
|
|
12
28
|
interface ProviderStatus {
|
|
13
29
|
provider: CodingProvider;
|
|
@@ -45,6 +61,8 @@ interface ProviderStatus {
|
|
|
45
61
|
} | null | undefined;
|
|
46
62
|
/** The effort a run uses when none is picked (the person's own settings). */
|
|
47
63
|
defaultEffort?: string | null | undefined;
|
|
64
|
+
/** Which CLI runs use, and its version. */
|
|
65
|
+
binary?: ProviderBinary | null | undefined;
|
|
48
66
|
}
|
|
49
67
|
interface ModelOption {
|
|
50
68
|
id: string;
|
package/dist/index.cjs
CHANGED
|
@@ -633,6 +633,23 @@ function parseJsonl(text) {
|
|
|
633
633
|
// src/transcript.ts
|
|
634
634
|
var import_chat = require("@ai-matrx/chat");
|
|
635
635
|
|
|
636
|
+
// src/provider-health.ts
|
|
637
|
+
var MODEL_UNAVAILABLE = [
|
|
638
|
+
/model is not supported when using codex/i,
|
|
639
|
+
/\bmodel[\s:'"`][^.\n]{0,80}\b(?:not supported|does not exist|not available|not found)\b/i,
|
|
640
|
+
/\bmodel_not_found\b/i,
|
|
641
|
+
/issue with the selected model/i,
|
|
642
|
+
/may not exist or you may not have access to it/i
|
|
643
|
+
];
|
|
644
|
+
var MODEL_UNAVAILABLE_LABEL = "Model not available on this account";
|
|
645
|
+
function isModelUnavailable(text) {
|
|
646
|
+
const trimmed = (text ?? "").trim();
|
|
647
|
+
return trimmed !== "" && MODEL_UNAVAILABLE.some((re) => re.test(trimmed));
|
|
648
|
+
}
|
|
649
|
+
function runErrorLabel(text) {
|
|
650
|
+
return isModelUnavailable(text) ? MODEL_UNAVAILABLE_LABEL : text;
|
|
651
|
+
}
|
|
652
|
+
|
|
636
653
|
// src/tool-labels.ts
|
|
637
654
|
var BUILTIN = {
|
|
638
655
|
Bash: "Run command",
|
|
@@ -887,14 +904,15 @@ function createTranscriptBuilder() {
|
|
|
887
904
|
}));
|
|
888
905
|
return;
|
|
889
906
|
case "error": {
|
|
890
|
-
const
|
|
907
|
+
const text = runErrorLabel(item.text);
|
|
908
|
+
const said = current?.message.parts.some((p) => p.type === "error" && p.message.trim() === text.trim());
|
|
891
909
|
if (said) return;
|
|
892
910
|
upsertPart(item.id, () => ({
|
|
893
911
|
type: "error",
|
|
894
912
|
errorType: "provider",
|
|
895
913
|
code: null,
|
|
896
|
-
message:
|
|
897
|
-
userMessage:
|
|
914
|
+
message: text,
|
|
915
|
+
userMessage: text,
|
|
898
916
|
recoverable: true,
|
|
899
917
|
remedy: null
|
|
900
918
|
}));
|