Files
deepseek-harness/examples/acp-agent/cordis.snapshot.yml
Tianyi Cui 09c8e549b0 fix(hooks): run hooks in the session cwd; honest process-level config + best-effort session-start; surface systemMessage drop
Address review on the bridges:

- Hook cwd (blocking): the bridges never passed a workdir to runHook, so hooks
  ran in the executor default (the ACP server launch dir), not the session
  cwd — a hook doing `pwd`/relative reads/marker writes operated in the wrong
  tree. Both bridges now thread the agent's session `header.cwd` (the
  session/new.cwd) as the hook workdir for agent-scoped points. Regression per
  bridge: server cwd ≠ session cwd, a `pwd` hook proves it ran in the session
  workspace (proven red without the workdir).
- Example config honesty (blocking): `configPath: ./hooks.json` is read ONCE at
  load against the PROCESS cwd, not per-session — the comment/README now say so
  explicitly (a project-local per-session hooks.json is not discovered;
  TODO(per-session-hook-config)). The hooks-run-in-session-cwd fix above is the
  distinct, separately-documented half.
- Session-start timing (blocking): agent/session-start is a synchronous emit and
  the hook runs on a detached .then, so injected context is BEST-EFFORT — not
  guaranteed before the first request. Downgrade the contract in code comments +
  README + RFC (TODO(session-start-gating)) rather than implying "first request
  sees it", and add a no-wait regression that asserts the safe properties
  without pre-waiting for the inject.
- systemMessage (non-blocking): the merge collects merged.systemMessages but no
  bridge surfaced it. Warn per hook (like updatedInput) and document it as
  deferred in both READMEs + the RFC; tests assert the warn + non-surfacing.
2026-07-01 16:34:28 +08:00

101 lines
4.3 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. Use
subagent_fork instead when the subtask needs THIS conversation's
context: the child inherits the log so far.
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. `configPath` is read ONCE at load and resolves
# `./hooks.json` against the PROCESS cwd (not per-session) — in these snapshot
# runs the harness launches the subprocess with process cwd = the scenario's temp
# workspace, so a scenario that ships `workspace/hooks.json` (copied into that cwd
# before the run) exercises the hooks path end-to-end; every other scenario has no
# such file, so the parse fails-soft and the bridge registers nothing (a silent
# no-op — the ACP app loads no logger exporter, so the warning never reaches
# stdout). Hooks themselves run in the session cwd (the bridge passes it as workdir).
- id: hooks-claude
name: '@deepseek-ai/dsh-hooks-claude'
config:
configPath: ./hooks.json