8adcbceeed
The two bridge plugins that run a user's existing Claude Code / Codex hook
config on the harness's typed interception seams, built on the shared
dsh-hook-protocol library. A bridge is a faithfulness adapter, not a power
tool: anything it does a native cordis plugin does more powerfully — the
bridge exists only to run UNMODIFIED external hooks.
- dsh-hooks-claude: CC dialect. Seven hook points (SessionStart,
UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart,
SubagentStop), CC per-event stdin payloads, env + ${CLAUDE_PLUGIN_ROOT}/
${CLAUDE_PROJECT_DIR} substitution, literal-or-regex matcher.
- dsh-hooks-codex: Codex dialect — a deliberate subset. Five hook points,
always-regex matcher, snake_case payloads (turn_id/model, no trailing
newline), no env/substitution, block-only decisions.
Both map the neutral merged outcome onto the seam's typed Decision and stamp
an explicit {kind:'plugin'} source on injected context (so it is never
mislabeled as a user prompt). Config parse-failure is contained; only command
hooks run. updatedInput is logged+warned (input rewrite deferred); the Stop
loop-guard is deferred (TODO).
Tests: per-file 100% — config-parse unit branches + per-seam mappings
end-to-end through the REAL loop + REAL bash + REAL shell scripts (scripted
mock model only) + a real-Loader export-shape guard. A keyless ACP snapshot
scenario (hook-prompt-block) proves a UserPromptSubmit hook blocks a prompt
end-to-end (rejected turn -> ACP cancelled, hook/* events in the log); a
with-key e2e (hooks.e2e.ts) proves a PreToolUse hook blocks real bash
(verified on disk). The snapshot normalizer now scrubs hook/result.durationMs.
RFC: docs/rfc/implemented/feature/2026-06-30-hook-bridges.md
96 lines
3.9 KiB
YAML
96 lines
3.9 KiB
YAML
# Snapshot-test REPLAY config: the acp-agent plugin tree with the model backend
|
|
# swapped to llm-replay (serves a recorded session JSONL — no API key, no
|
|
# network). The dsh-acp-agent bin selects this file for DSH_SNAPSHOT=replay.
|
|
#
|
|
# Same app as cordis.yml (@deepseek-ai/dsh-acp-agent: the agent-core spine +
|
|
# JSONL persistence + the ACP bridge) — only the LLM backend differs: llm-replay
|
|
# here, llm-deepseek there. It can't reuse the real adapter because llm-deepseek's
|
|
# apply() throws without DEEPSEEK_API_KEY, killing a keyless replay run at boot.
|
|
#
|
|
# stdout is reserved for the ACP JSON-RPC protocol — no stdout logger (the app
|
|
# package omits it). The replay fixture path comes from $DSH_SNAPSHOT_FILE (and
|
|
# an optional $DSH_SNAPSHOT_OVERRIDE sidecar), set by the snapshot harness.
|
|
|
|
# The replay adapter: short-circuits llm/stream with the recorded log's chunks,
|
|
# in place of llm-deepseek.
|
|
- id: llm-replay
|
|
name: '@deepseek-ai/dsh-llm-replay'
|
|
|
|
# Local bash executor for agent-core's tool-bash schema.
|
|
# FIXME(config-comments): keep this executor note from implying bash is the
|
|
# whole tool set; subagent and todo_write are loaded below.
|
|
- id: bash
|
|
name: '@deepseek-ai/dsh-bash-local'
|
|
config:
|
|
timeoutMs: 60000
|
|
|
|
# The ACP server app — identical to cordis.yml's entry.
|
|
- id: acp-agent
|
|
name: '@deepseek-ai/dsh-acp-agent'
|
|
config:
|
|
model: deepseek-v4-flash
|
|
persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
|
|
systemPrompt: |
|
|
You are a coding assistant driven over the Agent Client Protocol.
|
|
|
|
Your tools are bash (plus bash_output/bash_kill for background tasks)
|
|
and subagent. Do ALL file operations through bash: read with
|
|
cat/sed/head, search with grep, write with heredocs (cat <<'EOF' >
|
|
file), edit with sed or a rewrite. Each bash call runs in a fresh
|
|
shell — pass workdir instead of cd. Check the [exit code: N] marker;
|
|
verify your work. Keep answers brief and factual.
|
|
|
|
Use the subagent tool to delegate a focused, self-contained subtask to
|
|
a fresh child agent (it works in its own context and returns only its
|
|
final result) — give it a complete, standalone instruction.
|
|
|
|
For multi-step work, use the todo_write tool to track a task list:
|
|
send the WHOLE list each call (it replaces the previous one), keep at
|
|
most one task in_progress (exactly one while work remains), and mark a
|
|
task completed as soon as it is done. Skip it for trivial single-step
|
|
tasks.
|
|
|
|
# The subagent seam + both in-process backends + two model-facing tools —
|
|
# identical to cordis.yml's wiring (only the LLM backend differs above): spawn
|
|
# and fork are each reachable via a dsh-tool-subagent bound to it with a distinct
|
|
# toolName (subagent → spawn, subagent_fork → fork).
|
|
- id: subagent
|
|
name: '@deepseek-ai/dsh-subagent'
|
|
|
|
- id: subagent-spawn
|
|
name: '@deepseek-ai/dsh-subagent-spawn'
|
|
config:
|
|
providerName: spawn
|
|
|
|
- id: subagent-fork
|
|
name: '@deepseek-ai/dsh-subagent-fork'
|
|
config:
|
|
providerName: fork
|
|
|
|
- id: tool-subagent
|
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
config:
|
|
provider: spawn
|
|
toolName: subagent
|
|
|
|
- id: tool-subagent-fork
|
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
config:
|
|
provider: fork
|
|
toolName: subagent_fork
|
|
|
|
# The model-facing todo_write tool — identical to cordis.yml's wiring, so a
|
|
# replayed todo_write tool call resolves to a real tool during snapshot replay.
|
|
- id: tool-todo
|
|
name: '@deepseek-ai/dsh-tool-todo'
|
|
|
|
# The Claude Code hook bridge, pointed at a `hooks.json` in the session cwd. A
|
|
# scenario that ships `workspace/hooks.json` (copied into the cwd before the run)
|
|
# exercises the hooks path end-to-end; every other scenario has no such file, so
|
|
# the bridge's parse fails-soft and it registers nothing (a silent no-op — the
|
|
# ACP app loads no logger exporter, so the warning never reaches stdout).
|
|
- id: hooks-claude
|
|
name: '@deepseek-ai/dsh-hooks-claude'
|
|
config:
|
|
configPath: ./hooks.json
|