diff --git a/codex-rs/shell-escalation/src/unix/mod.rs b/codex-rs/shell-escalation/src/unix/mod.rs index 0ae7941da2..b61af9aee7 100644 --- a/codex-rs/shell-escalation/src/unix/mod.rs +++ b/codex-rs/shell-escalation/src/unix/mod.rs @@ -1,3 +1,51 @@ +//! Unix shell-escalation protocol implementation. +//! +//! A patched shell invokes an exec wrapper on every `exec()` attempt. The wrapper sends an +//! `EscalateRequest` over the inherited `CODEX_ESCALATE_SOCKET`, and the server decides whether to +//! run the command directly (`Run`) or execute it on the server side (`Escalate`). +//! +//! ### Escalation flow +//! +//! Command Server Shell Execve Wrapper +//! | +//! o----->o +//! | | +//! | o--(exec)-->o +//! | | | +//! |o<-(EscalateReq)--o +//! || | | +//! |o--(Escalate)---->o +//! || | | +//! |o<---------(fds)--o +//! || | | +//! o<------o | | +//! | || | | +//! x------>o | | +//! || | | +//! |x--(exit code)--->o +//! | | | +//! | o<--(exit)--x +//! | | +//! o<-----x +//! +//! ### Non-escalation flow +//! +//! Server Shell Execve Wrapper Command +//! | +//! o----->o +//! | | +//! | o--(exec)-->o +//! | | | +//! |o<-(EscalateReq)--o +//! || | | +//! |o-(Run)---------->o +//! | | | +//! | | x--(exec)-->o +//! | | | +//! | o<--------------(exit)--x +//! | | +//! o<-----x +//! pub mod escalate_client; pub mod escalate_protocol; pub mod escalate_server; diff --git a/shell-tool-mcp/src/index.ts b/shell-tool-mcp/src/index.ts index 9199a5a276..6003040365 100644 --- a/shell-tool-mcp/src/index.ts +++ b/shell-tool-mcp/src/index.ts @@ -1,7 +1,5 @@ -// Launches the codex-exec-mcp-server binary bundled in this package. +// Reports the path to the appropriate Bash binary bundled in this package. -import { spawn } from "node:child_process"; -import { accessSync, constants } from "node:fs"; import os from "node:os"; import path from "node:path"; import { resolveBashPath } from "./bashSelection"; @@ -12,8 +10,6 @@ async function main(): Promise { const targetTriple = resolveTargetTriple(process.platform, process.arch); const vendorRoot = path.resolve(__dirname, "..", "vendor"); const targetRoot = path.join(vendorRoot, targetTriple); - const execveWrapperPath = path.join(targetRoot, "codex-execve-wrapper"); - const serverPath = path.join(targetRoot, "codex-exec-mcp-server"); const osInfo = process.platform === "linux" ? readOsRelease() : null; const { path: bashPath } = resolveBashPath( @@ -23,70 +19,7 @@ async function main(): Promise { osInfo, ); - [execveWrapperPath, serverPath, bashPath].forEach((checkPath) => { - try { - accessSync(checkPath, constants.F_OK); - } catch { - throw new Error(`Required binary missing: ${checkPath}`); - } - }); - - const args = [ - "--execve", - execveWrapperPath, - "--bash", - bashPath, - ...process.argv.slice(2), - ]; - const child = spawn(serverPath, args, { - stdio: "inherit", - }); - - const forwardSignal = (signal: NodeJS.Signals) => { - if (child.killed) { - return; - } - try { - child.kill(signal); - } catch { - /* ignore */ - } - }; - - (["SIGINT", "SIGTERM", "SIGHUP"] as const).forEach((sig) => { - process.on(sig, () => forwardSignal(sig)); - }); - - child.on("error", (err) => { - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - }); - - const childResult = await new Promise< - | { type: "signal"; signal: NodeJS.Signals } - | { type: "code"; exitCode: number } - >((resolve) => { - child.on("exit", (code, signal) => { - if (signal) { - resolve({ type: "signal", signal }); - } else { - resolve({ type: "code", exitCode: code ?? 1 }); - } - }); - }); - - if (childResult.type === "signal") { - // This environment running under `node --test` may not allow rethrowing a signal. - // Wrap in a try to avoid masking the original termination reason. - try { - process.kill(process.pid, childResult.signal); - } catch { - process.exit(1); - } - } else { - process.exit(childResult.exitCode); - } + console.log(`Platform Bash is: ${bashPath}`); } void main().catch((err) => {