Review follow-up (#196): a listed name with no registered tool was silently ignored; misconfiguration must block work instead. The check lives in the assembly — the earliest moment the registered tool set exists (tool plugins register after the service constructs) and the only universal one (cordis has no "all plugins loaded" event; registrations change at any time). assemble() is now async so the throw surfaces as a rejection rather than a synchronous escape from a Promise-returning method. Blast radius, pinned by a loop-level test: the rejection reaches the turn's outer catch — the turn closes balanced with an `error` reason, agent/error mirrors it, no step opens, no request/header is logged, no request reaches the adapter, and the agent returns to idle; every turn fails identically until the config is fixed. A boot-time validation pass was considered and rejected (recorded in the RFC). The general principle — misconfiguration fails loud, never a silent skip — is added to AGENTS.md.
43 lines
3.1 KiB
Markdown
43 lines
3.1 KiB
Markdown
# @deepseek-ai/dsh-acp-agent
|
|
|
|
The **ACP server app**: a Cordis app plugin that composes the providerless agent spine ([`@deepseek-ai/dsh-agent-core`](../../core/agent-core/README.md)) with the front-door cluster an [Agent Client Protocol](../acp/README.md) 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`](../stdio-agent/README.md): 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-session-persistence-jsonl` | durable JSONL session log (the bridge advertises `loadSession`) |
|
|
| `@deepseek-ai/dsh-acp` | the bridge that owns stdout for JSON-RPC |
|
|
| ~~console logger~~ | **omitted** — it writes to stdout and would corrupt the protocol frames ([the stdout-purity footgun](../acp/README.md)) |
|
|
| ~~`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` |
|
|
| `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 (`bash-local`).
|
|
|
|
## The bin
|
|
|
|
`dsh-acp-agent [path-to-cordis.yml]` (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`: the cordis Loader resolves the config's bare plugin specifiers through its internal module loader, active only under that flag. (`demo:acp` runs under tsx, whose tsconfig `paths` map resolves them instead.)
|
|
|
|
All diagnostics go to **stderr** — stdout is the protocol.
|