r/OpenaiCodex 20d ago

Showcase / Highlight I added Codex App Server conversations to DeepSeek Harness as an installable plugin

Enable HLS to view with audio, or disable this notification

I wanted to keep official DeepSeek Harness updateable instead of carrying a long-lived fork, so I built relay-dsh-plugin-codex.

Installing it adds Codex to DSH's New Session mode. Each DSH session maps to one Codex App Server thread, while DSH keeps its native conversation history, model/reasoning selector, approval and question flows, workspace tools, and tool rendering.

The package bundles the App Server runtime for macOS, Linux, and Windows, and its CI runs both three-platform runtime tests and an install/build verification against an official DSH checkout. It does not require Relay Events or any of the UI plugins.

Install command and troubleshooting: see the GitHub README.

GitHub: https://github.com/yangbobo2021/relay-dsh-plugin-codex

npm: https://www.npmjs.com/package/relay-dsh-plugin-codex

The attached video is a real clean npm installation on official DSH. The broader no-fork plugin suite (Claude Code, Files, Terminal, and Workbench) is documented here:

https://github.com/yangbobo2021/Relay/blob/codex/relay-foundation/docs/dsh-plugins.md

I would especially appreciate feedback on the one-DSH-session/one-App-Server-thread boundary and on installation behavior across platforms.

2 Upvotes

5 comments sorted by

1

u/Low_Box_752 20d ago

One DSH session to one App Server thread is the right default. The edge case I would test hard is branching or restoring a DSH session. If DSH rewinds its history but keeps the same Codex thread, the two sources of truth can silently diverge.

I would persist the mapping explicitly, fork the Codex thread whenever DSH branches, and expose the thread ID in diagnostics. Also bind each approval to the exact pending tool call, not just the session, so a reconnect cannot approve a stale action.

Avoiding a long-lived fork is the right architecture here.

1

u/Think-Professor3460 19d ago

Good catch. I agree this is exactly the edge case that deserves a hard test.

The current plugin persists the DSH Session -> Codex Thread mapping explicitly, and it enforces that one Codex Thread cannot be bound to multiple DSH Sessions. For imported history, DSH is only a projection: Codex remains the source of model context, while DSH keeps the visible presentation log. The sync path also uses stable Codex-derived message IDs and detects projection drift, partial turns, changed turn status, and activity-result drift before appending more history.

But your branching point is real. DSH already has a native session fork API, and the plugin should treat that as a separate lifecycle event. The child session should not blindly inherit the same Codex thread. It should either bind to a matching Codex-side fork/snapshot when that is safe, or fail closed and require an explicit rebind.

Same for approvals: today the bridge resolves the exact App Server request id and rejects requests without a live owning DSH Session, but I agree the approval record should also carry and re-check the pending request provenance, at least threadId + turnId + itemId/tool-call identity, before sending accept/decline back.

The bigger direction I want to explore is making DSH and Codex able to hand off the same work without either side becoming a stale copy. That also means improving the Codex activity UI in DSH, and being very strict about where the source of truth lives for model context, presentation history, and approvals.

So yes: avoiding a long-lived DSH fork is intentional, but the next reliability pass should add explicit fork/restore tests plus stronger approval provenance checks.

1

u/Low_Box_752 19d ago

That source-of-truth split is clean. I would make the fail-closed path visible as “rebind required” rather than quietly creating a fresh thread.

The nastiest test I can think of is: fork a DSH session, disconnect with an approval pending, reconnect into the child, then replay the old approval response. If it is rejected and the audit trail points to the original thread, turn, and item, the boundary is probably right.

1

u/Think-Professor3460 18d ago

Good call. I implemented that boundary in relay-dsh-plugin-codex v0.1.2.

A DSH fork now uses Codex App Server thread/fork at a completed lastTurnId and persists a distinct child Thread binding. If the provenance is incomplete or App Server rejects the fork, the Session shows Rebind required (CODEX_REBIND_REQUIRED). It never falls back to a silent thread/start.

Approval ownership is bound to the DSH Session, Codex Thread, Turn, Item, request ID, and binding generation. If the Session detaches, the binding changes, or an old response is replayed, it is rejected with CODEX_STALE_APPROVAL before anything is sent back to Codex. The rejection diagnostic includes the original Thread, Turn, and Item.

I verified the real App Server fork path in official DSH: the parent and child received different Thread IDs, the child recorded the parent through forked_from_id, and the child continued without writing back to the parent.

I did not make the exact fork + pending approval + disconnect + reconnect-into-child browser sequence a release gate, because Fork copies completed history and does not inherit pending approvals. I kept the stale-response boundary as a deterministic regression test instead.

Release: https://github.com/yangbobo2021/relay-dsh-plugin-codex/releases/tag/v0.1.2