UNPKG

@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.

122 lines • 5.5 kB
import { getConfig } from "../config.js"; import { execClaude, detectCurrentSession, validateSession, listCandidates, projectDirsFor, recordedCandidate } from "./cc-cli.js"; import { execCodex, detectCurrentCodexSession, validateCodexSession, listCodexCandidates, codexSessionsRoot } from "./codex-cli.js"; import { appServerDaemonRunning, ensureDaemon, execCodexViaAppServer, runningUnderAppServerDaemon } from "./codex-app-server.js"; /** * Codex wake dispatch, honoring COGENT_CODEX_WAKE: * - `auto` (DEFAULT): use the app-server rail IFF this bridge is running under a * `codex … app-server` daemon (i.e. codex is `--remote`, e.g. launched via * cogent-codex). That's the ONLY reliable signal — in `--remote` mode the daemon, * not the codex TUI, spawns the plugin's MCP, so an env flag never reaches the * bridge. Detected structurally via process ancestry → zero config, works no * matter how codex was started. * - `app-server`: force the rail (explicit). * - `exec-resume`: never use it (explicit opt-out; original behavior byte-for-byte). * In every case it GRACEFULLY FALLS BACK to `exec-resume` when the daemon can't be * reached — a plain codex still degrades to the honest "queued" message. */ async function execCodexWake(sessionId, message, cwd, timeoutMs, idempotencyKey) { const wake = getConfig().COGENT_CODEX_WAKE; // `auto` takes the app-server rail when EITHER we were spawned under the daemon OR a daemon is // already listening. Ancestry alone missed every bridge that runs outside a cogent-codex-launched // codex (a monitor script, a systemd unit) — those fell back to `exec-resume`, which takes the // thread's EXCLUSIVE WRITER and locks the human out of their own live session with // `-32600 already has an active writer`. The daemon is a machine-wide singleton, so being its // descendant was never the right question. Never STARTS a daemon: no daemon → unchanged. const useAppServer = wake === "app-server" || (wake === "auto" && (runningUnderAppServerDaemon() || appServerDaemonRunning())); if (useAppServer) { try { const sock = await ensureDaemon(); const result = await execCodexViaAppServer(sessionId, message, cwd, timeoutMs, { socketPath: sock, idempotencyKey }); if (!result.appServerUnavailable) return result; // daemon unreachable at RPC layer → fall through to exec-resume } catch { // ensureDaemon failed (no daemon / socket never appeared) → fall through } } return execCodex(sessionId, message, cwd, timeoutMs); } /** * Dispatch a non-interactive "resume + send" to the local agent CLI based on * the local peer's platform. Only "codex" diverges; everything else (and the * non-CLI platforms, which never reach auto-relay) uses the Claude path. */ export function execRemote(platform, sessionId, message, cwd, timeoutMs, opts) { switch (platform) { case "codex": return execCodexWake(sessionId, message, cwd, timeoutMs, opts?.idempotencyKey); case "cc": default: return execClaude(sessionId, message, cwd, timeoutMs); } } /** Platform-aware "find the current session id for this cwd". */ export function detectCurrentSessionFor(platform, cwd) { switch (platform) { case "codex": return detectCurrentCodexSession(cwd); case "cc": default: return detectCurrentSession(cwd); } } /** Platform-aware "list all candidate sessions for this cwd" (name + mtime). */ export function listCandidatesFor(platform, cwd) { switch (platform) { case "codex": return listCodexCandidates(cwd); case "cc": default: return listCandidates(cwd); } } /** * Platform-aware "which session did this cwd's agent report for ITSELF". * * Layer 1 of session discovery: a fact published by the agent (via the plugin * hook payload) rather than a path we derived. Only Claude Code publishes a * transcript path today; Codex already matches its rollouts by recorded cwd, so * it has no derivation to bypass. Returns null when nothing was recorded. */ export async function reportedSessionFor(platform, cwd) { if (platform !== "cc") return null; try { return (await recordedCandidate(cwd))?.sessionId ?? null; } catch { return null; } } /** * Platform-aware "which directories would I scan for this cwd's sessions". * * Diagnostics only. A zero-candidate resolve used to be a dead end: the log said * "no candidate sessions found for cwd" without ever naming the directory it * looked in, so a cwd-representation mismatch and a genuinely empty machine were * indistinguishable. Surfacing the probed path turns that into a one-glance fix. */ export function probePathsFor(platform, cwd) { switch (platform) { case "codex": return Promise.resolve([codexSessionsRoot()]); case "cc": default: return projectDirsFor(cwd); } } /** Platform-aware "does this session id still exist on disk". */ export function validateSessionFor(platform, sessionId, cwd) { switch (platform) { case "codex": return validateCodexSession(sessionId); case "cc": default: return validateSession(sessionId, cwd); } } //# sourceMappingURL=exec-remote.js.map