Bundle
dsh-better-codex-subagent
Drop-in replacement for the Codex subagent provider that projects the app-server stream into a harness child session, so Codex runs render as ordinary subagents in the Web UI
- Source
- ivwumupy
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 14 days ago
Readme
# dsh-better-codex-subagent
English | [中文](README.zh.md)
A drop-in replacement for the fixed `codex` subagent provider
([`@deepseek-ai/dsh-subagent-codex`](../../packages/subagent/subagent-codex/README.md))
that additionally mirrors the Codex app-server stream into a harness child
session. Every Codex run therefore renders as an ordinary one-shot subagent in
the session tree and Web UI: a discoverable child node under the delegating
session, with the delegated task, the assistant answers, and the tool
executions (commands and file changes) as a normal conversation transcript.
The provider registers under the same `codex` name, so the
`tool-subagent-codex` tool row (`provider: codex`) keeps working unchanged.
Loading this plugin together with `@deepseek-ai/dsh-subagent-codex` fails loud
on the duplicate provider name — compose one or the other.
## Start and ownership
`start(request)` derives the child cwd from the parent Session, spawns the
fixed `codex app-server --stdio` command through `dsh-subprocess`, and performs
`initialize` → `initialized` → `thread/start { cwd, ephemeral }`. Only after
the app-server returns a thread matching the requested lifetime does the
provider create the projection child session, seed it (descriptor, turn start,
user task), and publish the run. A failure before publication closes the wire,
terminates the managed process tree, waits for it to exit, and rejects
`start()` — no orphan child session is left behind.
The published run's result contract is identical to the base provider: one
turn, the authoritative `turn/completed` terminal notification, and the
latest `agentMessage` with `phase: "final_answer"` (or the `phase: null`
compatibility fallback) as the returned answer. Local cancellation maps to
`aborted`, `contextWindowExceeded` to `max-tokens`, and any other remote
failure to `error`. `dispose()` is idempotent and reaches whole-tree
quiescence. The projection session stays in the store after the run settles
as a terminal, read-only one-shot child; the Web UI already renders one-shot
subagent conversations read-only.
## Projection
The child session mirrors the app-server notification stream through a
stateful transcript:
| App-server notification | Projection-session events |
|---|---|
| `item/agentMessage/delta` | accumulated per item id |
| `item/completed` (`agentMessage`, `final_answer` or `null` phase) | `assistant/message` with the accumulated or completed text |
| `item/started` / `item/completed` (`commandExecution`) | `tool/call` (`exec_command` with `command`/`cwd`) + `tool/result` (`aggregatedOutput`, else exit code, else status) |
| `item/started` / `item/completed` (`fileChange`) | `tool/call` (`fileChange` with path/kind changes) + `tool/result` (path summary) |
| run settlement | `turn/end` (`completed` / `aborted` / `max-tokens` / `error`) |
The transcript is deliberately tolerant: unknown notification methods, item
kinds, and agent-message phases are ignored, so an app-server version that
introduces new item types cannot fail the run. Malformed payloads of known
kinds degrade to a skipped write. The base provider's fail-closed posture is
kept where it matters: unknown server *requests* (which demand an answer) and
mismatched thread/turn scoping in terminal notifications still fail the run.
## Configuration
| Key | Default | Meaning |
|---|---|---|
| `env` | `{}` | Explicit child environment layered over the subprocess seam's credential-scrubbed parent environment. |
| `disposeGraceMs` | `3000` | Positive finite grace in milliseconds, no greater than [`MAX_TIMER_DELAY_MS`](../../packages/util/timeout/README.md), between the shared process-tree owner's termination tiers. |
| `ephemeral` | `true` | Thread lifetime passed to `thread/start`. `false` persists the Codex session under `CODEX_HOME/sessions` for a later `codex resume`; `true` keeps the base provider's ephemeral contract. |
```yaml
- id: better-codex-subagent
name: 'dsh-better-codex-subagent'
config:
env:
OPENAI_API_KEY: !!js process.env.OPENAI_API_KEY
ephemeral: false
- id: tool-subagent-codex
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: codex
toolName: subagent_codex
maxDepth: provider-managed
```
## Background execution
One-shot background execution works for the `codex` provider: the tool row
just needs `enableRunInBackground` unset or `true` (the default). The model
can then pass `run_in_background: true`, which starts the Codex run as a
background job that returns a job id immediately; `job_output` collects the
final answer and `job_kill` cancels. The projection session is the job's
child session, so the Web UI still renders the running and settled subagent
transcript. `backgroundMode` must stay `one-shot` (the default): the `codex`
provider has no `prepareContinuable` capability, and a `continuable` tool row
fails at mount.
## Installation
The package is a dsh **bundle**: its manifest declares
`dsh.bundle` with a patch layer (`cordis.patch.yml`) that mounts the provider,
so `dsh plugin` installs it as a bundle into a profile. The package ships
prebuilt (`lib/types/*.js` ESM + declarations) plus the source and declares
its harness packages as peer dependencies, so it loads wherever the harness
packages are already present — a dsh profile, for example, resolves them from
the installed dsh runtime.
```sh
# From this package directory: build and produce the publishable tarball.
pnpm pack # → dsh-better-codex-subagent-0.1.0.tgz
```
Install into a profile (`dsh plugin` forwards to pnpm and appends the bundle
to the profile's `dsh.profile.bundles` because of the `dsh.bundle` manifest):
```sh
dsh plugin --profile web add ./dsh-better-codex-subagent-0.1.0.tgz
```
Other distribution forms work the same way: `dsh plugin --profile web add
dsh-better-codex-subagent` after publishing to npm, or
`dsh plugin --profile web add github:you/deepseek-harness#<sha>` from git — a
git install fetches sources only, so the package then needs a `prepare`
script (none is shipped yet) and an `allowBuilds` entry in the profile's
`pnpm-workspace.yaml`.
If the profile already mounts the base
`@deepseek-ai/dsh-subagent-codex` provider (for example a manually inserted
`- id: subagent-codex` row in the profile's `cordis.patch.yml`), remove that
row and its dependency before restarting — both packages register the `codex`
provider name and the duplicate fails loud:
```sh
dsh plugin --profile web remove @deepseek-ai/dsh-subagent-codex
# and delete the `- id: subagent-codex` insert row from
# ~/.dsh/profiles/web/cordis.patch.yml
```
The `tool-subagent-codex` tool row (`provider: codex`) in the standard-codex
agent preset needs no change. Then restart `dsh web`.
## Product compatibility and evidence
The wire implements the same 0.147.0 app-server surface as the base provider
(adapted from its `wire.ts`), with the ephemeral lifetime parameterized and a
validated notification sink added. Development evidence is pinned to
`@openai/codex@0.147.0`; the npm package is a test-only dependency, and
deployments still supply `codex` on `PATH`. The keyless real-product test
boots the real app-server against a loopback Responses fixture and asserts
that the published run is discoverable through `ctx.subagents.listChildren`
and that its transcript carries the delegated task, the answer, and the
closing `turn/end`.
## Known Limitations and Deferred Work
- **Message granularity is per completed item, not per delta** — the
`item/agentMessage/delta` stream is accumulated but written as one
`assistant/message` on `item/completed`; the Web UI does not stream the
answer live. Streaming `assistant/chunk` projection is deferred.
- **Tool projection covers `commandExecution` and `fileChange` items only** —
`mcpToolCall`, `webSearch`, `collabToolCall`, `plan`, and `reasoning` items
are ignored; the transcript is the extension point for them.
- **No continuation** — the child is a terminal one-shot projection session;
follow-up messages, cold resume, and `codex resume` through the harness are
out of scope (persisting the thread with `ephemeral: false` makes the Codex
session resumable in the Codex CLI itself).
- **Host-managed product installation and account state** — a missing or
incompatible `codex`, configuration error, or authentication failure is
surfaced as a startup or run error; the plugin provides no installer, login
flow, or runtime version gate.
- **No wall-clock timeout or side-effect rollback** — the caller cancels long
work, and files or external systems changed before cancellation are not
restored.
Install
dsh plugin --profile web add github:ivwumupy/dsh-better-codex-subagent
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-better-codex-subagent 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.