@anht3889/dsh-acp-zed
v0.5.0
Published
Interactive Agent Client Protocol server running DeepSeek Harness in Zed's Agent Panel
Readme
@anht3889/dsh-acp-zed
An Agent Client Protocol (ACP) server that runs a DeepSeek Harness agent inside Zed's Agent Panel. Zed spawns this process and talks to it over stdio JSON-RPC; the agent answers with the harness's tool loop (bash, filesystem, todos), streamed deltas, permission prompts, per-session model and reasoning-effort switches, persisted sessions you can resume, and DeepSeek models by default — with the whole composition swappable, so other providers are configuration, not code.
Install and configure — step by step
Step 1. Prove the prerequisites
- Zed (current stable); Agent Panel support ships with it.
- Node.js
^22.19 || >=24. Check:node --version. - A DeepSeek API key, or another provider.
Step 2. Decide how Zed launches the server
Recommended: no install at all — npx runs it on demand:
npx -y @anht3889/dsh-acp-zed --list-models # sanity check; prints the catalog to stderrAlternatives: a global install (npm i -g @anht3889/dsh-acp-zed, then the
command is just dsh-acp-zed), or a source checkout (node /path/to/dsh-acp-zed/lib/bin.js
after pnpm build).
Note the absolute-path gotcha now: a GUI-launched Zed (Dock/Spotlight) often
can't see npx from nvm/volta. If step 3 fails to spawn, use
which npx output as the absolute command — full detail in
Troubleshooting.
Step 3. Register the server in Zed
Command Palette → zed: open settings → add an agent_servers entry:
{
"agent_servers": {
"DeepSeek Harness": {
"type": "custom",
"command": "npx",
"args": ["-y", "@anht3889/dsh-acp-zed"]
}
}
}Step 4. Pick an authentication route
Either pass the key in Zed's env block:
"env": { "DEEPSEEK_API_KEY": "…" }or run the login flow once (silent prompt; saved owner-only to
~/.dsh/acp-zed/auth.env, mode 600) and omit the env block:
npx -y @anht3889/dsh-acp-zed loginThe env block always wins over the saved file when both exist.
Step 5. Verify outside Zed
npx -y @anht3889/dsh-acp-zed --list-modelsMust print the provider/model catalog to stderr and exit 0. If it fails here, the message names the cause (missing config, overlay parse error, auth file trouble) far louder than Zed's log does.
Step 6. Use it
Open the Agent Panel from Zed's status bar, pick DeepSeek Harness, start a
thread. The gear icon per thread exposes Model and Thought Level —
switch per session without touching settings. Threads persist, and
dsh-acp-zed exposes resume via Zed's recent-threads picker. Then:
- Using the agent — switching models/effort, resume, auth
- Custom providers — add an OpenAI-compatible gateway, Anthropic, or a self-hosted model
Using the agent
Enrolling MCP servers from the CLI
dsh-acp-zed mcp manages the shared catalog (~/.dsh/mcp) that BOTH this
bridge and the dsh web profile consume:
dsh-acp-zed mcp add http nvbug https://maas.prd.astra.nvidia.com/maas/nvbugs/mcp
# discovers OAuth endpoints (RFC 9728/8414), registers a loopback client,
# opens your browser (link also printed), exchanges the code, stores the
# tokens into ~/.dsh/mcp/secrets.yaml
dsh-acp-zed mcp add stdio tci tci-mcp
dsh-acp-zed mcp auth nvbug # re-run the browser dance when a refresh dies
dsh-acp-zed mcp list # catalog records + auth state
dsh-acp-zed mcp remove nvbug # record + tokens goneEvery enabled catalog record auto-mounts into every Zed thread — no Zed
context_servers entries needed at all; Zed-forwarded entries still work and
win on name collision (the catalog then contributes only auth).
Static-header (non-OAuth) catalog records are currently NOT attached by the
bridge (v1 boundary): put those in Zed's context_servers with literal
headers.
MCP servers (Zed context_servers)
Zed's context_servers settings flow into each session as ACP mcpServers;
OAuth entries work too: Zed authenticates OAuth servers only for its own
native agent and forwards a bare URL to external agents (its own source drops
oauth when building the ACP entry), so the bridge resolves auth itself from
the dsh web profile's MCP catalog (~/.dsh/mcp/servers.json +
secrets.yaml, managed by @anht3889/dsh-mcp-mgmt-bundle): matching entries
(same name or URL) inherit the catalog's OAuth token — refreshed through the
catalog's token endpoint when expired and written back for both profiles. If
the refresh token is dead, stderr names it and asks you to re-authenticate in
the web GUI. A mounted server that yields no tools is always named on stderr.
each entry mounts a @deepseek-ai/dsh-mcp-client connection shared across
all threads of the running agent (one instance per server name — MCP
namespaces and their tools are app-wide), exposing its tools as
mcp__<server>__<tool> to the model. Stdio and streamable-HTTP
(type: "http") entries work; the deprecated sse transport is rejected
with a clear error. An unreachable server soft-fails (session starts anyway;
details on stderr). If you change a server's settings in settings.json,
restart the agent — the live bridge keeps the config it first mounted and
rejects a diverging second thread with a restart-pointing error.
Skills (on by default)
Type / in a Zed thread: the slash menu lists every user-invocable skill
(advertised via ACP available_commands_update, refreshed live when a skill
file changes). Sending /name args as a message invokes that skill — the
harness claims the token and injects its body as instructions; unknown names
stay plain text. That's the same gesture semantics as the DeepSeek Harness
web GUI.
The underlying stack follows the harness default (on): the model-facing
skill tool is mounted, and skill docs placed in any of these roots are
discovered and shown to the agent — restart not required for content
changes:
<projectRoot>/.dsh/skills/and<projectRoot>/.agents/skills/— per-project~/.dsh/skills/and~/.agents/skills/— per-user- custom dirs via
skills.filesystem.customSkillDirson the acp-zed entry
The Superpowers pack drops in
as a plugin: dsh-acp-zed plugin add @anht3889/dsh-superpowers (published;
its skills inject is satisfied out of the box). To disable the stack
entirely, patch the acp-zed entry with skills: { enabled: false } in the
overlay — see docs/zed-setup.md.
Model and effort switching (per session)
Zed renders the server's session config options natively: in a thread, use the select menus for the agent's session options.
- Model — any model advertised by the current provider (the shipped
composition offers
deepseek-v4-flash,deepseek-v4-pro, anddeepseek-v4-flash-vision-exp). - Thought Level — the current model's reasoning effort (
off,low,high,maxfor the shipped DeepSeek models). Shown only for models that declare selectable efforts.
Changes are per-thread and take effect from the agent's next step — you can switch mid-generation and the running turn finishes on the old selection.
Resuming a thread
Threads persist to ~/.dsh/acp-zed/sessions (or $DSH_HOME/acp-zed/sessions)
as you work. Zed's recent-threads picker lists them (the server implements
session/list + session/load): pick one and the full transcript — your
messages, assistant replies, and completed tool calls — replays into the
thread before you continue. Resume applies within the workspace the session
was recorded in: the picker shows sessions belonging to the current project.
Authentication
Resolution order on every launch and every authenticate call:
- The live
DEEPSEEK_API_KEYenvironment variable (e.g. theenvblock in settings), then - the persisted login file
~/.dsh/acp-zed/auth.env.
If neither has a key, prompts report auth-required and Zed surfaces the
advertised sign-in methods — API key from environment (env) or Log in
from the terminal (terminal-login, which runs dsh-acp-zed login in a
terminal window). The key is never logged, echoed, or sent anywhere but the
DeepSeek API.
To sign out: npx @anht3889/dsh-acp-zed logout removes the persisted file
and clears the in-process key cache (Zed's own logout action clears the
running session's key).
Using a self-credentialed route (headers baked in, or a keyless local
gateway)? Add authRequired: false to the acp-zed overlay config — no auth
methods are advertised and prompts are ungated (see the worked example below).
Server settings and custom providers
The server boots a cordis
composition shipped inside the package (cordis.yml). Two layering surfaces
extend your installation:
- The settings document at
~/.dsh/acp-zed/settings.yaml, hot-reloaded: allm-pi-ai: providers:section declares custom provider routes (the multi-provider@deepseek-ai/dsh-llm-pi-aiadapter ships dormant in the default composition) and they register live on the next request — no restart. Credentials for them resolve through~/.dsh/acp-zed/credentials.yamlor the environment (which always wins). - The user overlay over the shipped composition at
~/.dsh/acp-zed/overlay.cordis.yml, when present. A top-level YAML array of patch entries:- id: <entry> config: {...}replaces a shipped plugin's config;- insert: [...]mounts new plugins.!!jsexpressions are allowed. Overlay changes are restart-to-apply and a broken overlay fails startup loudly with a named error. dsh-acp-zed plugin add <npm-name-or-dir>is the managed path for the second case: it installs into a private workspace under the harness home and appends the insert block for you (seeplugin list/plugin remove; the full guide indocs/zed-setup.md).
Worked example — point the bridge at an OpenAI-compatible gateway:
# ~/.dsh/acp-zed/settings.yaml — registers live (hot reload)
llm-pi-ai:
providers:
local-kimi:
displayName: Local Kimi
api: openai-completions
baseURL: http://10.78.3.34:8000/v1
compat: { supportsDeveloperRole: false, maxTokensField: max_tokens }
models:
- id: moonshotai/Kimi-K3
name: Kimi K3
contextWindow: 1048576
maxTokens: 32768# ~/.dsh/acp-zed/overlay.cordis.yml — restart-to-apply; config REPLACES the
# entry's whole config, so restate the shipped fields you keep (persona and
# workspaceContext from the package's cordis.yml)
- id: acp-zed
config:
provider: local-kimi
model: moonshotai/Kimi-K3
authRequired: false # keyless self-hosted route: no Authenticate gate
workspaceContext:
maxBytes: 65536
persona: |
You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
Verify your work by running the code or tests. Keep answers brief and factual.Restart the thread and verify the wiring outside Zed:
npx @anht3889/dsh-acp-zed --list-models # prints the provider/model catalog to stderr(On Windows, use a file:///C:/... URL for name; plain POSIX absolute paths
work on macOS/Linux. HOME can be relocated with $DSH_HOME.)
Troubleshooting
"No such file or directory" / the agent fails to spawn. Zed launched as a
GUI app (Dock, Finder, Spotlight) does not read your shell profile, so npx
from nvm/volta is often not on its PATH. Fixes:
- Find the absolute path (
which npxin a terminal) and use it ascommand:{ "command": "/Users/you/.nvm/versions/node/v24.15.0/bin/npx", "args": ["-y", "@anht3889/dsh-acp-zed"], ... } - Or launch Zed from your terminal (
zed .), which inherits the shell's PATH (thezedCLI installs from Zed's menu:cli: install).
Spawn errors and the server's stderr diagnostics surface in Zed's log
(zed: open log).
Every prompt demands authentication. Neither the env var nor the login
file has a key — run npx @anht3889/dsh-acp-zed login or add the env
block. The server self-checks outside Zed:
npx @anht3889/dsh-acp-zed --list-models must print the model catalog.
More: docs/zed-setup.md — deeper setup, permission modes, overlay mechanics, resume semantics, and troubleshooting.
Limitations (v1)
- No durable permission grants. Approval prompts offer one-shot choices only; there is no "always allow" that persists.
- No
mcpServers/additionalDirectories. A session requesting either is rejected with an invalid-params error. - No reasoning display. The Thought Level option changes the effort sent with requests, but reasoning traces are never streamed to the UI.
- No background tool jobs. The generic job tools are unmounted; every tool call completes in-band.
- Overlays are restart-to-apply. No HMR; restart the thread (or Zed)
after editing the overlay or config. Only the settings document
(
llm-pi-aiprovider routes, credentials) hot-reloads.
Developing
pnpm install
pnpm build # tsdown → lib/
pnpm test # vitest
pnpm typecheck # tsc --noEmit over src/ and tests/From a source checkout, point Zed straight at the built bin:
{ "command": "node", "args": ["/abs/path/to/dsh-acp-zed/lib/bin.js"],
"env": { "DEEPSEEK_API_KEY": "…" } }dsh-acp-zed --help prints the full CLI (usage goes to stderr; stdout is
reserved for ACP JSON-RPC).
License
MIT — see LICENSE.
