Bundle
dsh-codex-bridge
Codex bridge for DeepSeek Harness: call_codex / codex_status / codex_abort tools plus a WebUI Codex tab observing the codex sessions of the current session.
- Source
- pandashere
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-codex-bridge
English | [中文](README.zh.md)
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:
```bash
npm install
npm run check
npm pack
```
Install 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:
```bash
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 web
```
The 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:
```bash
curl -s http://127.0.0.1:3080/plugins/dsh-codex-bridge/client.js | head
```
To update, build a tarball with a newer package version, remove the installed
bundle, add the new tarball, and restart. To uninstall:
```bash
npx @deepseek-ai/dsh@0.1.0-rc.6 plugin --profile web remove dsh-codex-bridge
```
## Config
| 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_codex` starts 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.** `defaultSandbox` maps to `codex -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
```bash
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
Install
dsh plugin --profile web add github:pandashere/dsh-codex-bridge
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-codex-bridge from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.