dsh-codex-bridge
English | 中文
A dual-face (host + browser) plugin for DeepSeek Harness (dsh) that bridges the Codex CLI into the harness.
Why
A dsh agent often wants a second opinion or a parallel coding pass from an external coding agent (OpenAI Codex). Doing that by hand — spawning codex, capturing JSONL, polling, wiring the output back — is exactly the kind of scaffolding a harness plugin exists to remove. This plugin makes Codex a first-class dsh citizen:
1. Calls Codex as a tool — call_codex starts a Codex session (codex -a never exec --json) in the session's working directory, with async (returns immediately; multiple calls run in parallel) and block (waits for the final answer) modes, plus codex_status to poll and codex_abort to cancel. 2. Continues the same thread — codex_steer resumes a settled Codex session on the SAME thread (codex exec resume <thread_id>), so you can redirect, ask follow-ups, or correct direction with the full history retained. The thread is linear: the parent must be the latest record, and one thread can have only one active continuation. 3. Shows the whole agent loop — the Codex tab in the conversation pane (on par with Chat and Trajectory) observes each session live: status, prompt, an Agent Loop waterfall (messages, tool calls with command/args, collapsible tool output with exit code, turn separators), the transcript, and the final answer — pushed through the session projection channel.
Design stance: this is a UX channel, not a security boundary. Codex runs with the calling user's privileges under its own sandbox policy (read-only/workspace-write are offered to the model; danger-full-access is deploy-config only). The model-facing surface is deliberately tight: Codex always runs in the session working directory (never the host cwd — fail closed), only top-level agents may call by default, and maxParallel/maxSessionsPerSession/maxLoopSteps/maxLoopBytes bound resource and write amplification.
Installation
Requirements: Node.js 22 or newer, @deepseek-ai/dsh@0.1.0-rc.6, and an authenticated Codex CLI available as codex (or set codexPath). The plugin does not read or store API keys; authentication remains owned by the Codex CLI.
Build, validate, and pack the standalone bundle from the plugin directory:
npm install
npm run check
npm packInstall the generated tarball into a DSH profile, then restart dsh web. Installing the source directory as a link is not supported because host peers are supplied by the DSH profile:
npx @deepseek-ai/dsh@0.1.0-rc.6 plugin --profile web add ./dsh-codex-bridge-0.1.0.tgz
npx @deepseek-ai/dsh@0.1.0-rc.6 webThe browser half is served at /plugins/dsh-codex-bridge/client.js and appears in the conversation pane. Verify it against a running default Web profile:
curl -s http://127.0.0.1:3080/plugins/dsh-codex-bridge/client.js | headTo update, build a tarball with a newer package version, remove the installed bundle, add the new tarball, and restart. To uninstall:
npx @deepseek-ai/dsh@0.1.0-rc.6 plugin --profile web remove dsh-codex-bridgeConfig
| Key | Default | Meaning | |-----|---------|---------| | codexPath | codex | codex executable (absolute path or PATH lookup) | | defaultSandbox | read-only | codex sandbox policy for its shell commands (deployment may raise it) | | defaultTimeoutMs | 0 | lifetime limit per codex session (0 = unlimited) | | maxParallel | 3 | global cap on concurrent codex processes | | maxSessionsPerSession | 8 | cap on live codex sessions per dsh session | | maxRetained | 16 | retained (settled) records per dsh session (oldest evicted) | | maxPromptChars | 16384 | prompt length cap (longer prompts are rejected) | | maxTranscriptChars | 16384 | transcript cap recorded in events/projections | | maxLoopSteps | 32 | bounded agent-loop window (steps kept in the record/projection) | | maxLoopBytes | 16384 | serialized-byte cap for the loop window (UTF-8; oldest completed steps evicted) | | allowedAgents | roots | who may call call_codex: roots \| all | | killGraceMs | 2000 | SIGTERM → SIGKILL grace on abort |
Tools
call_codex— `{ prompt, mode?: async|block, sandbox?: read-only|workspace-write,
model?, timeout_ms?, codex_session_id? }. async starts and returns immediately (parallel); block waits for the answer (or, with codex_session_id`, waits on a previously started session). A cancelled blocking wait aborts the codex session.
codex_status— list the current session's codex sessions (status,
prompt preview, progress).
codex_abort—{ codex_session_id }; SIGTERM the process group, then
SIGKILL after killGraceMs.
codex_steer— `{ codex_session_id, prompt, mode?: async|block, model?,
timeout_ms? }. Continue a settled parent session on the SAME thread (codex exec resume <thread_id>); the new record links back via parent` (lineage shows in the Codex tab). A post-restart parent works as long as its record carries the codex thread id.
Model Experience
The Codex tab (conversation pane, after Chat/Trajectory):
- Left column — every codex session of the current dsh session, with
status dot, prompt preview, and relative time. Click to select.
- Right column — status badge, meta (id/codexId/cwd/sandbox/model/
duration/exit/error), the prompt, and an Activity | Text toggle:
- Activity — the Agent Loop waterfall: messages, tool rows (tool name, running/done/failed, duration, exit code, truncated marker; output auto-expands on failure), turn separators, and a "N steps dropped" marker when the bounded window evicted older steps. - Text — the streamed transcript and the final answer.
State changes ride the session projection channel (codex/session events, codex/sessions projection), so the tab updates live and survives page refresh (history replay).
Known Limitations and Deferred Work
- One shot per call, then continuation.
call_codexstarts a fresh
codex exec run; live mid-run steering (injecting a message while Codex is still working) is not available in the standard CLI — that would require the experimental codex app-server/remote-control daemon path.
- The loop window is recent-activity, not an audit trail. Older steps are
physically evicted under maxLoopSteps/maxLoopBytes; the canonical dsh session log still holds the whole-value snapshots, but the tab only shows the retained window.
- Sandbox is Codex's own.
defaultSandboxmaps tocodex -s; it bounds
what Codex's shell commands may touch, not the harness's sandbox. Never call it a harness security boundary.
- POSIX-only process groups. Abort uses
detached+ negative-pid
kill; a Windows port needs Job Object / taskkill /T tree termination.
- Telemetry redaction covers dsh exports only. Codex's own telemetry is
out of scope.
Development
npm run check # typecheck + tests + compliance
npm run build # host (tsc) + client bundle (esbuild, __ModuleLoader__ ABI)The client bundle speaks the harness __ModuleLoader__.load({id, factory}) protocol with the platform module table as externals; the host half follows the bundle format from create-dsh-plugin.
License
MIT