Files
deepseek-harness/packages/ui/acp-agent
Tianyi Cui 0642a1d666 Merge current master into PR #219 fixes
Origin/master advanced after the reviewed fixes were first pushed, leaving PR #219 conflicted. Merge the new vendor Cordis/loader update into the PR branch rather than rewriting pushed history, so the task fixes remain based on the exact current landing target.

Preserve the upstream package graph, lockfile, app-boot, and built-bin adjustments as landed on master; resolve only genuine overlaps with the task fixes and regenerate derived artifacts where required.

# Conflicts:
#	packages/subagent/tool-subagent/package.json
2026-07-15 12:45:47 +08:00
..
2026-07-15 11:28:45 +08:00

@deepseek-ai/dsh-acp-agent

The ACP server app: a Cordis app plugin that composes the default agent spine (@deepseek-ai/dsh-agent-core) with the front-door cluster an Agent Client Protocol server needs, and a bin that boots a leaf cordis.yml speaking ACP JSON-RPC on stdio.

It is the structured counterpart to @deepseek-ai/dsh-stdio-agent: both consume the same spine, but this one bakes in the OPPOSITE front-door cluster.

What it bakes in — and what it deliberately omits

stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it LEAVES OUT as what it includes:

Plugin Why
@deepseek-ai/dsh-agent-core the spine, pre-creating no agents (ACP session/new creates them on demand)
@deepseek-ai/dsh-user-interaction the human question/answer seam used by clients that can complete ACP elicitation requests
@deepseek-ai/dsh-session-persistence-jsonl durable JSONL session log (the bridge advertises loadSession)
@deepseek-ai/dsh-acp the bridge that owns stdout for JSON-RPC and provides ACP-backed user answers when a leaf explicitly exposes a user-question tool
@deepseek-ai/dsh-tool-ask-user omitted by default — ACP elicitation support is still client-dependent, so leaves must opt in deliberately
@deepseek-ai/dsh-user-approval omitted by default — permission policy is deployment-specific; sandbox/approval leaves opt in and the ACP bridge then supplies the answerer
console logger omitted — it writes to stdout and would corrupt the protocol frames (the stdout-purity footgun)
hmr omitted — the editor owns the subprocess

Because the package wires no logger entry, an ACP leaf has nothing to get wrong by default: it only picks backends, so the common mistake — copying a console-logger entry from the stdio config — has no place here. (A leaf author technically can still add @cordisjs/plugin-logger-console as a sibling entry; the package can't forbid that. So the rule stands: never add a stdout logger to an ACP leaf — stdout is the JSON-RPC channel. Use a stderr exporter if you need logs.)

Config

Key Default Routed to
model (required) the per-session agent template the bridge creates agents from
persona the deployment persona template (may reference {{model}}/{{cwd}}), routed to dsh-system-prompt
toolOrder explicit model-facing tool order (a name list with one '<unlisted-tools>' rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to dsh-system-prompt
tools { mode: 'native' } tool-registry presentation config through agent-core
skills owner defaults skill registry, local provider, and model-facing skill-tool config through agent-core
toolBash owner defaults model-facing bash config through agent-core, including bash's producer-local enableRunInBackground
toolTasks owner defaults generic task_output wait bounds through agent-core
persistenceRoot ./.sessions the JSONL backend's root directory

The leaf supplies the swappable backends: an LLM adapter (llm-deepseek for the real model, llm-replay for keyless snapshot replay) and a bash executor.

The bin

dsh-acp-agent [--config path-to-cordis.yml] (short form -c; default ./cordis.yml):

  • loads a gitignored .env from the cwd — skipped in snapshot REPLAY so a stray key can never trigger a live call;
  • honors DSH_SNAPSHOT=replay by booting the sibling cordis.snapshot.yml (the keyless replay tree, llm-replay in place of llm-deepseek);
  • in a snapshot run, disposes the context on stdin EOF so the session log is fully flushed before exit.

Run it under node --expose-internals, or Loader's optional node-addon-require-builtin fallback is required, so the cordis Loader can resolve the config's bare plugin specifiers through its internal module loader. (demo:acp runs under tsx, whose tsconfig paths map resolves them instead.)

All diagnostics go to stderr — stdout is the protocol.

Model Experience

Indirectly, through dsh-agent-core and dsh-acp, which compose each ACP agent's prompt, tools, and message history; this app bundle adds no model-bound content itself.

Known Limitations and Deferred Work

  • JSONL persistence is baked in — config chooses its root but cannot select a different backend; that requires a sibling entry or differently composed app package.
  • User-question and approval mechanisms are omitted by default — the bridge can answer both when their services/tools are composed, but this front door does not enable those deployment policies itself.
  • A leaf can still corrupt stdout — the app mounts no console logger, but it cannot prevent a sibling leaf entry from writing non-protocol bytes to the ACP channel.