dushaobindoudou / dsh-acp

Listed

Agent Client Protocol (ACP) server plugin for the DeepSeek Harness (dsh) - drive dsh agents from Zed and any ACP v1 client

mainOther View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:dushaobindoudou/dsh-acp

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit 25331b6Synced Aug 17, 2026

dsh-acp

npm CI License: MIT

Agent Client Protocol (ACP) server for the DeepSeek Harness (dsh).

Drive a full dsh coding agent - streaming output, tool calls, permission prompts, durable sessions - from Zed, JetBrains, or any ACP v1 client, exactly like opencode or Gemini CLI.

English | 中文

What it does

dsh-acp is a dsh profile bundle: booting dsh --profile acp starts the entire DeepSeek Harness (agent loop, tools, sandbox, session persistence) with an ACP v1 JSON-RPC server on stdio instead of the web UI.

M1 (current release) implements:

ACP methodStatus
initialize✅ capabilities + agent info
session/new✅ creates a durable dsh agent (persisted under $DSH_HOME/sessions)
session/prompt✅ streaming agent_message_chunk / agent_thought_chunk (reasoning), plan updates, {stopReason}
tool calls✅ full lifecycle: tool_call (pending, with title/kind/locations/rawInput) → in_progresscompleted/failed with content
session/request_permission✅ dsh's approval seam bridged to the client (allow once/always, reject once/always)
session/cancel✅ aborts the turn, prompt resolves cancelled
session/close✅ cancels, flushes, disposes the agent

Roadmap: session/load replay, session/resume/list, modes (plan mode) + config options (model), slash commands, images, elicitation - see the design doc.

Install

Requires Node.js ≥ 22, pnpm, and the dsh CLI (npm i -g @deepseek-ai/dsh).

Install the bundle into an acp profile with the official plugin command (it initializes the profile, installs the package, and keeps dsh.profile.bundles in sync - see the harness docs, 打包与安装插件):

# prebuilt from npm (recommended — no build authorization needed)
dsh plugin --profile acp add dsh-acp-server

# or from a tarball
dsh plugin --profile acp add ./dsh-acp-server-0.1.0.tgz

# or from GitHub (source install; see note below)
dsh plugin --profile acp add github:dushaobindoudou/dsh-acp

# boots the ACP server on stdio
dsh --profile acp

node bin/setup-profile.mjs --pkg <spec> is a thin convenience wrapper over the same command.

GitHub installs pull source, not build output. The package's prepare script builds lib/ on install; pnpm ≥ 10 refuses to run it until you add the key it prints to the profile's pnpm-workspace.yaml:

allowBuilds:
  dsh-acp: true

then re-run the add. Prefer locking a commit (github:…/dsh-acp#<sha>), or avoid the authorization entirely with a prebuilt npm package or tarball. Verify any time with dsh --profile acp --dump-config (a # == dsh-acp layer should appear).

The two final forms

dsh-acp-server                    # 1) standalone: ACP on stdio (editors) or `serve` for remote
dsh web                           # 2) together: the GUI and ACP on one port
  1. dsh-acp-server (after npm i -g dsh-acp-server, or via npx) boots dsh --profile acp with the current stdio; every launcher flag passes through (serve --port 7800, --patch extra.yml). First use in a DSH home bootstraps the acp profile with the official dsh plugin command - bootstrap output goes to stderr, so an editor's stdout only ever sees ACP frames.
  2. dsh web - install once into the web profile (below) and every web boot serves the GUI and /acp on one port.

Why a bin instead of the literal dsh acp-server: the dsh launcher hardcodes its app subcommands (web, plugin) and parses argv before any plugin loads, so a bundle cannot register one; the wrapper is the same single-command shape.

Share one port with the Web GUI (dsh web + ACP)

Install into the web profile once, and every plain dsh web boot serves the GUI and ACP in one process on one port:

node bin/setup-webacp.mjs        # official `dsh plugin --profile web add` + the web-mounted row
dsh web                          # http://127.0.0.1:3080 = GUI, /acp = ACP
node bin/acp-chat.mjs --url http://127.0.0.1:3080

Prefer to keep web untouched? node bin/setup-webacp.mjs --clone webacp gives the same on dsh --profile webacp.

The script writes a row-level inject: [agents, agentDefaultModel, webServer] so Cordis starts the acp-server fiber only after the shared webServer service exists - the stdio transport can never race a web boot. A hand install without that row is safe too: the plugin detects a web composition late via service provisioning (terminal boots skip stdio entirely; daemon boots get an EOF grace window before exiting). Set a token in the acp-server config to require bearer auth.

Remote access (serve, like opencode serve)

Editors get ACP over stdio, but you can also run a long-lived HTTP+SSE endpoint - for remote machines, shared agents, or curl - following the shape of the ACP streamable-HTTP RFD (POST sends client->server messages, a long-lived SSE GET stream carries all server->client messages, Acp-Connection-Id binds them):

dsh --profile acp serve --port 7800            # bind 127.0.0.1 by default
dsh --profile acp serve --host 0.0.0.0 --port 7800 --token s3cret
EndpointPurpose
POST /acpone JSON-RPC message per body; initialize -> 200 + Acp-Connection-Id; everything else -> 202 (response arrives on SSE)
GET /acp/streamSSE stream for the connection (header or ?connection=)
DELETE /acpclose the connection
GET /healthzliveness (no auth)

Several clients can be attached to one dsh process (one SDK connection each). Connect from anywhere with the bundled client:

node bin/acp-chat.mjs --url http://127.0.0.1:7800 --token s3cret

Without serve the profile still boots stdio-first, so Zed keeps working unchanged. Default bind is loopback; use --token whenever you bind beyond it.

Try it without an editor

acp-chat is a zero-install interactive terminal client bundled in this repo (REPL with streaming, tool-call display, plan rendering, and inline permission prompts):

node bin/acp-chat.mjs            # spawns `dsh --profile acp` and drops you into a chat

Third-party ACP clients that work today:

ClientTypeTry it
Zededitor (reference client)agent_servers custom entry below
acpxCLInpx acpx@latest --agent 'dsh --profile acp' "hello"
ghost.nvim / acpear.nvimNeovimplugin config → command dsh-acp
acp.elEmacs(setq acp-agent-command '("dsh" "--profile" "acp"))
obsidian-agent-clientObsidianplugin settings
ACP-inspectorconformance/debugvalidates wire traffic

Configuration

All knobs have schema defaults, so the shipped bundle row carries no config; override only the keys you want in your own profile layer ($DSH_HOME/profiles/acp/cordis.patch.yml):

- id: acp-server
  config:
    agentName: my-dsh            # initialize.agentInfo.name shown to clients
    provider: deepseek           # pin the model for ACP sessions
    model: reasoner              # (provider and model must be set together)
    offerAlwaysPermissions: false # hide allow_always/reject_always (M1 maps
                                  # "always" grants to one-shot decisions)
    flushOnTurnEnd: true         # flush session persistence after each turn

Behavior follows the harness config conventions: the schema validates at plugin load (wrong types or a half-set provider/model pin fail loudly with the exact key), missing keys fall back to defaults (a patch layer replaces the whole config value, the schema refills the rest), and unset provider/model follows the profile's agent-default-model. Config is read when the process boots - editors spawn one per session.

Use with Zed

Zed → Settings → agent_servers:

{
  "dsh": {
    "type": "custom",
    "command": "dsh-acp-server",
    "args": []
  }
}

The npm package is dsh-acp-server (the plain dsh-acp name on npm belongs to another project); its dsh-acp-server bin is a thin launcher for dsh --profile acp. Point DSH_BIN at a non-PATH dsh. Model credentials come from the usual dsh places ($DSH_HOME settings / DEEPSEEK_API_KEY), shared with the web UI - no second setup.

Development

pnpm install
pnpm run build     # tsc -> lib/
pnpm test          # unit tests (pure translation layer)

# end-to-end: boots the REAL dsh --profile acp in a throwaway $DSH_HOME
# with a deterministic mock LLM, and asserts the full M1 wire behavior
node test/e2e/e2e.test.mjs

The e2e harness is also the fastest way to iterate on protocol behavior: it drives initialize → new → prompt (text) → prompt (tool call) → close over real stdio.

How it maps

ACP client (Zed) ⇄ NDJSON JSON-RPC ⇄ dsh-acp plugin ⇄ dsh services
                                          ├─ ctx.agents.create/resume   (session/new, load)
                                          ├─ agent.followup/cancel      (session/prompt, cancel)
                                          ├─ 'session/event'            (streaming session/update)
                                          ├─ 'approval/request'         (session/request_permission)
                                          └─ dsh-base: tools, sandbox, persistence, settings

Full research behind every mapping: research/ - ACP protocol, DSH architecture, prior art, design blueprint.

Contributing

PRs welcome - see CONTRIBUTING.md. Milestone plan lives in research/acp-dsh-design.md §4.

License

MIT

Project files and signals

Shown items are public repository signals detected in the directory snapshot.

TestsDetected
Contributing guideDetected

Repository information

Language
JavaScript
License
MIT
Last updated
Aug 17, 2026, 9:01 AM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.