@essentialai/cogent-bridge
Version:
Cogent Bridge — cross-agent comms for Claude Code, OpenAI Codex and Slack. Codex users: install with `curl -fsSL https://cogent.tools/install.sh | sh`, then start with `cogent-codex` for real-time peer wake.
129 lines • 6.8 kB
TypeScript
import type { Config } from "../config.js";
/**
* Codex CLI flag preflight (mock-hidden-gap audit, 2026-05-27, gap 5).
*
* Why this exists: `execCodex` (codex-cli.ts) drives auto-relay by spawning
* `codex exec resume <id> -c sandbox_mode=… --skip-git-repo-check -o <file> <msg>`.
* The unit tests mock `child_process`, so they only prove the args we *intend* to
* pass — NOT that the real `codex exec resume` subcommand accepts them. That gap
* already caused three production breaks (`--color`, `--sandbox`, `--full-auto`
* were each rejected by `exec resume` at exit 2 — see codex-cli.ts comments and
* memory `cli-flag-subcommand-verification`).
*
* This preflight, run at startup when COGENT_PLATFORM=codex, (1) verifies the
* `codex` binary resolves, and (2) runs `codex exec resume --help` and asserts
* every flag we emit is documented by the installed CLI. A drift (new codex
* version renames/drops a flag) surfaces as a loud startup warning instead of a
* silent exit-2 on the first auto-relay.
*/
/**
* The long-form flags `execCodex` emits on the `exec resume` subcommand. Keep
* this in sync with `execCodex`/`sandboxArgs` in codex-cli.ts. We assert the
* long forms (the short `-c`/`-o` always co-document with `--config`/
* `--output-last-message` in codex help output).
*/
export declare const EXPECTED_CODEX_RESUME_FLAGS: ReadonlyArray<{
flag: string;
usedFor: string;
}>;
export interface CodexFlagAnalysis {
missing: string[];
checked: string[];
}
/**
* Pure decision logic: given the text of `codex exec resume --help`, return which
* expected flags are absent. Unit-testable with no spawning.
*/
export declare function analyzeCodexHelp(helpText: string): CodexFlagAnalysis;
/**
* Verify the codex binary exists and that `codex exec resume` accepts the flags
* auto-relay emits. Never throws — a preflight failure must not stop startup; we
* only want the problem visible in the logs. Returns the analysis, or null if the
* help text could not be obtained (binary missing / call failed).
*/
export declare function preflightCodex(codexPath: string): Promise<CodexFlagAnalysis | null>;
/**
* Whether the host can initialize the Codex OS sandbox (an unprivileged Linux
* user namespace, via bubblewrap). `blocked` is the failure that makes every
* `codex exec resume` report "not finished" (bwrap: setting up uid map:
* Permission denied). `na` = not applicable (bypass mode, or non-Linux host).
*/
export type SandboxProbeStatus = "ok" | "blocked" | "unknown" | "na";
export interface SandboxProbeResult {
status: SandboxProbeStatus;
detail: string;
}
/**
* Pure decision logic: interpret the probe signals into a status. Unit-testable
* with no spawning. `unshareOk` undefined = the `unshare` binary was unavailable,
* so we fall back to the kernel sysctl signals.
*/
export declare function interpretSandboxProbe(s: {
platform: NodeJS.Platform;
mode: Config["COGENT_CODEX_SANDBOX"];
unshareOk?: boolean;
apparmorRestrict?: string | null;
usernsClone?: string | null;
}): SandboxProbeResult;
/**
* Pure: map an `unshare -Ur true` execFile rejection to a userns signal. Returns
* `false` (blocked) ONLY for a genuine permission-denied refusal — the process
* ran, exited non-zero, and stderr shows the userns op was denied (real
* Ubuntu-24.04/hardened case, e.g. "write failed /proc/self/uid_map: Operation
* not permitted"). Everything else is INCONCLUSIVE → `undefined`: a timeout/kill
* (`err.killed`, `code` null), a spawn error (ENOENT/EACCES/EAGAIN — `code` is a
* string errno), or a CLI usage error (a busybox `unshare` that lacks `-Ur`,
* which exits non-zero for an unrelated reason). An inconclusive result makes the
* caller fall through to the kernel sysctls and ultimately "unknown" (which never
* engages the bypass) — the line between failing SAFE and failing OPEN on a
* transient startup hiccup on a sandbox-capable host.
*/
export declare function classifyUnshareError(err: {
killed?: boolean;
code?: string | number | null;
stderr?: string;
} | null | undefined): boolean | undefined;
/**
* Gather sandbox-capability signals for the host and interpret them. Never throws.
* Probes `unshare -Ur true` (the exact op bubblewrap needs); if `unshare` is
* missing, falls back to reading the userns sysctls. Skips the probe (returns
* `na`) for `bypass` mode or non-Linux hosts.
*/
export declare function probeSandboxStatus(config: Config): Promise<SandboxProbeResult>;
/**
* Startup sandbox preflight — surface the Codex OS-sandbox capability LOUDLY so
* a blocked host never silently loops on "not finished". Never throws. Returns
* the probe result so startup can apply the COGENT_CODEX_SANDBOX_FALLBACK
* recovery. Fallback-aware: when auto-recovery is on (the default) a blocked host
* is a WARN (Cogent will bypass the dead sandbox so tasks run); when an operator
* has opted out it is an ERROR (tasks will fail until they fix the host).
*/
export declare function preflightCodexSandbox(config: Config): Promise<SandboxProbeResult>;
/**
* Pure: does this `~/.codex/config.toml` text declare a STANDALONE
* `[mcp_servers.cogent]` server? That name collides with the `cogent@cogent`
* plugin's OWN MCP server (both named `cogent`) — Codex collapses the two by name
* and MCP startup fails ("MCP client for cogent failed to start … connection
* closed: initialize response"). Latent until 3.19.2 made the plugin actually
* start. See docs/update-and-troubleshooting.md + memory
* `codex-app-server-wake-shipped-3.20.0`.
*
* Matches only the TOML TABLE HEADER (anchored to line start, optional leading
* whitespace) so a `#`-comment or prose mention never trips it, and requires the
* server name to be exactly `cogent` (so `[mcp_servers.cogent-cli]` and other
* names are ignored). A bare `[mcp_servers.cogent]` OR any subtable
* (`[mcp_servers.cogent.env]`, `[mcp_servers.cogent.tools.x]`) counts.
*/
export declare function detectCodexCogentServerCollision(configTomlText: string): boolean;
/** Absolute path to the codex config, honouring CODEX_HOME (falls back to ~/.codex). */
export declare function codexConfigPath(codexHomeDir?: string): string;
/**
* Startup preflight: warn LOUDLY if the codex config defines a standalone
* `[mcp_servers.cogent]` that collides with the plugin's own `cogent` server.
* Never throws (a missing/unreadable config is simply "no collision"). Runs for
* every codex bridge (plugin users included), so plugin-only users who never
* launch via `cogent-codex` still get flagged. Returns true iff a collision was
* found.
*/
export declare function preflightCodexServerCollision(codexHomeDir?: string): Promise<boolean>;
//# sourceMappingURL=codex-preflight.d.ts.map