dsh-compat-adapter
Make Claude Code / Codex / WorkBuddy / OpenCode / Copilot hooks and rules actually run inside DSH.
A DSH pre-adapter plugin: declare explicitly โ grade capability โ route to a real seam.
No guessing, no pretending it works, no foreign plugin runtime inside the Host.
๐ I want to use it โ how another plugin calls this adapter (consumer guide)
English ยท
็ฎไฝไธญๆ
---
## What this is
DSH (DeepSeek Harness) is a new agent runtime. Meanwhile you already have a working set of hooks and rules in Claude Code, Codex, WorkBuddy, OpenCode and GitHub Copilot โ the "block this before it writes a file", "add context after that tool call", "run a script when the session ends" machinery.
The cheap way to move them to DSH is to wire them up by name. That breaks silently:
> Claude's `updatedInput` gets parsed but never honored;
> Codex's `permissionDecision: "ask"` has no approval path and degrades into an allow;
> Copilot writes its denial at the **top level**, while DSH's hook parser only reads `hookSpecificOutput` โ so reusing it directly means **every Copilot deny is silently read as "no decision" and let through**;
> OpenCode plugins are TypeScript callbacks: the *names* line up, the *callback bodies* cannot cross.
**This plugin exists so that every adaptation loss becomes a readable output instead of a working-looking illusion.**
It does exactly three things:
| | |
|---|---|
| **Declare** | The preset author names the vendor and event explicitly. No filename sniffing, no ownership guessing. |
| **Grade** | Every asset returns a frozen four-level result with its losses listed one by one. |
| **Route** | Reuse the official bridge where one exists, really translate where a seam can intercept, say "observe-only" where it cannot, and refuse otherwise. |
### Two things it does not do
- **It does not re-implement the official bridges.** Every event they cover is handed straight to `@deepseek-ai/dsh-hooks-claude-code` / `-codex`. This plugin only turns declarations into bridge config, and decides what happens to the events no bridge covers.
- **It does not execute foreign code.** JS function handlers, OpenCode's TS callback runtime, Claude's `http` / `mcp_tool` / `prompt` / `agent` handlers โ all `UNSUPPORTED`. Other agents' plugin runtimes do not get embedded in the DSH Host.
---
## Core design: grades, not booleans
Every registration returns a **frozen** capability result. There is no `supported: true` field flattening the risk away.
| Grade | Meaning | In this version |
| :--- | :--- | :--- |
| `DIRECT` | A 1:1 DSH seam exists; semantics preserved verbatim. | **None.** See below. |
| `ADAPTED` | A DSH seam exists but semantics are lossy; `degradations[]` states each loss. | **All 112 events** |
| `CARRY` | No general mapping; carried byte-for-byte and injected at session start. Never becomes executable policy. | **All 5 vendors' rules** |
| `UNSUPPORTED` | No faithful mapping; rejected with a `reason`. | Procedural boundaries (below) |
### Why nothing claims `DIRECT`
Both official bridges are themselves best-effort dialect adaptations. Claiming byte-faithfulness would be a false capability.
So this plugin marks **not a single row `DIRECT`** โ all 112 rows are `ADAPTED`, and every row must state its own losses. That is not pessimism, it is honesty: `test/matrix-readme.test.js` asserts `DIRECT.length === 0` and asserts that every `ADAPTED` row carries at least one degradation.
> **Full coverage is not equivalence.** All 112 events are executable (0 refusals), but that is coverage where *every row states its losses* โ not a claim that they all behave identically. **10 of them say `Approximate` in their own `degradations`** (Claude `MessageDisplay`, `StopFailure`; Codex `Interrupt`; WorkBuddy `Elicitation`, `ElicitationResult`; OpenCode `installation.updated`, `server.connected`, `session.deleted`, `session.updated`, `tui.toast.show`); the other **102 are real/precise mappings**. `test/doc-counts.test.js` compares these numbers against the code, so the prose can never claim more capability than the adapter has.
### Four hard invariants (enforced by tests, not by documentation)
| Invariant | Enforced by |
| :--- | :--- |
| `ADAPTED` **must** list at least one `degradations` entry, or construction throws | `capabilityResult()` + `test/capability.test.js` |
| `UNSUPPORTED` **must** carry a `reason`, or construction throws | `capabilityResult()` + `test/capability.test.js` |
| An unknown vendor / event **throws** (`UNKNOWN_VENDOR` / `UNKNOWN_EVENT`); never a silent fallback | `test/service.test.js` |
| An `UNSUPPORTED` asset is **never mounted**: no config written, no bridge mounted, absent from `status()`'s executable list | `test/service.test.js` + `verify:live` |
So what are the real `UNSUPPORTED` paths left, now that every event is executable?
- **JS function handlers** โ foreign code is not executed in the Host.
- **Missing `ctx.shell`** โ no shell executor in the composition, so command hooks cannot run; rules still work (they only read files and inject context).
- **Missing official bridge package** โ if `dsh-hooks-*` does not resolve from the profile, the registration is refused rather than falling back to a homegrown implementation.
- **Non-command handlers** โ `http` / `mcp_tool` / `prompt` / `agent` throw `INVALID_HANDLER`.
---
## Supported vendors
Five independent adapters. Each is a pure data module (`src/vendors/