fix(dsh): default ACP entrypoint to the official dsh CLI (#2031)

* fix(dsh): default ACP entrypoint to the official dsh CLI

The default spawn command was `dsh-acp-demo` — the standalone legacy
demo package. Since `@deepseek-ai/dsh` 0.2.0 the ACP profile ships with
the main CLI, so `dsh --profile acp` is the maintained entrypoint and
the one a plain `npm i -g @deepseek-ai/dsh` install provides. The old
default required every self-hoster to also discover and pin the demo
package (whose npm `latest` tag is stale) before `hapi dsh` worked at
all.

- resolveDshAcpCommand defaults to `dsh --profile acp` when no DSH env
  is set; any explicit `HAPI_DSH_ACP_COMMAND` keeps bare `[]` args so
  custom wrappers never silently receive the official CLI's flags
- DEFAULT_COMMANDS/availability follow, so the PATH probe now checks
  `dsh` itself
- the demo package remains fully supported via explicit
  `HAPI_DSH_ACP_COMMAND=dsh-acp-demo`; docs updated accordingly

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Apply config-only DSH overrides through the official CLI's patch overlay

HAPI_DSH_ACP_CONFIG without HAPI_DSH_ACP_COMMAND fell through to the
default official CLI but passed `--config <path>`, a flag the official
dsh CLI does not have — and dropped the required `--profile acp`, so the
boot would run the wrong entrypoint with an unknown flag. A config-only
override now composes the path as a `--patch` overlay on top of the ACP
profile (verified against dsh 0.2.0-rc.2 --dump-config). The `--config`
form remains available for legacy community servers pinned via
HAPI_DSH_ACP_COMMAND, and both READMEs now document the split.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
admin
2026-10-26 10:02:36 +08:00
committed by GitHub
co-authored by Claude Code
parent ad13695678
commit cc1a09be0c
6 changed files with 80 additions and 17 deletions
+10 -4
View File
@@ -163,8 +163,9 @@ environment automatically.
See `src/configuration.ts` for all options.
DeepSeek Harness ACP uses `dsh-acp-demo` by default. Override the executable or
its arguments without shell parsing:
DeepSeek Harness ACP uses the official `dsh` CLI with `--profile acp` by default
(the ACP profile ships with `@deepseek-ai/dsh` since 0.2.0). Override the
executable or its arguments without shell parsing:
```bash
export HAPI_DSH_ACP_COMMAND=dsh-acp-demo
@@ -172,6 +173,11 @@ export HAPI_DSH_ACP_CONFIG=/path/to/deepseek-harness/examples/acp-agent/cordis.y
hapi dsh
```
`HAPI_DSH_ACP_CONFIG` maps to `--config` for the configured server. Without
`HAPI_DSH_ACP_COMMAND` it instead applies to the official CLI, which has no
`--config` flag — the path is passed as a `--patch` overlay on top of
`--profile acp`.
For a source checkout, use JSON arguments:
```bash
@@ -196,8 +202,8 @@ controls for DSH.
- `HAPI_EXPERIMENTAL` - Enable experimental features (true/1/yes).
- `HAPI_EXTRA_HEADERS_JSON` - JSON object of extra headers to send on CLI → hub requests, e.g. `{"Cookie":"CF_Authorization=..."}`. Can also be set as the `extraHeaders` object in `~/.hapi/settings.json` (environment variable wins).
- `HAPI_CLAUDE_PATH` - Path to a specific `claude` executable.
- `HAPI_DSH_ACP_COMMAND` - ACP server executable for `hapi dsh` (default: `dsh-acp-demo`).
- `HAPI_DSH_ACP_CONFIG` - Optional `dsh-acp-demo --config` path.
- `HAPI_DSH_ACP_COMMAND` - ACP server executable for `hapi dsh` (default: `dsh` with `--profile acp`).
- `HAPI_DSH_ACP_CONFIG` - Optional config path for the configured DSH ACP server (`--config` with `HAPI_DSH_ACP_COMMAND`, `--patch` overlay on the default official CLI).
- `HAPI_DSH_ACP_ARGS_JSON` - Optional JSON array of ACP server arguments.
- `HAPI_HTTP_MCP_URL` - Default MCP target for `hapi mcp`.
+6
View File
@@ -91,6 +91,7 @@ describe('agent executable resolution', () => {
it('rejects malformed or missing DSH static configuration', async () => {
const directory = await mkdtemp(join(tmpdir(), 'hapi-agent-path-'))
await makeExecutable(directory, 'dsh')
await makeExecutable(directory, 'dsh-acp-demo')
expect(getAgentAvailability('dsh', {
PATH: directory,
@@ -107,6 +108,11 @@ describe('agent executable resolution', () => {
PATH: directory,
HAPI_DSH_ACP_CONFIG: config,
})).toEqual({ agent: 'dsh', available: true })
expect(getAgentAvailability('dsh', {
PATH: directory,
HAPI_DSH_ACP_COMMAND: 'dsh-acp-demo',
HAPI_DSH_ACP_CONFIG: config,
})).toEqual({ agent: 'dsh', available: true })
})
it('accepts a macOS Codex app executable when the CLI is absent', async () => {
+1 -1
View File
@@ -11,7 +11,7 @@ const DEFAULT_COMMANDS: Record<AgentFlavor, string> = {
codex: 'codex',
copilot: 'copilot',
cursor: 'agent',
dsh: 'dsh-acp-demo',
dsh: 'dsh',
gemini: 'gemini',
grok: 'grok',
kimi: 'kimi',
+30 -5
View File
@@ -2,20 +2,45 @@ import { describe, expect, it } from 'vitest'
import { resolveDshAcpCommand } from './dshBackend'
describe('resolveDshAcpCommand', () => {
it('uses the official ACP demo executable by default', () => {
it('defaults to the official dsh CLI with the ACP profile', () => {
expect(resolveDshAcpCommand({})).toEqual({
command: 'dsh-acp-demo',
args: []
command: 'dsh',
args: ['--profile', 'acp']
})
})
it('supports a config path for dsh-acp-demo', () => {
expect(resolveDshAcpCommand({ HAPI_DSH_ACP_CONFIG: '/tmp/cordis.yml' })).toEqual({
it('supports a config path override', () => {
expect(resolveDshAcpCommand({
HAPI_DSH_ACP_COMMAND: 'dsh-acp-demo',
HAPI_DSH_ACP_CONFIG: '/tmp/cordis.yml'
})).toEqual({
command: 'dsh-acp-demo',
args: ['--config', '/tmp/cordis.yml']
})
})
it('maps a config-only override onto the official CLI profile and patch overlay', () => {
// The official CLI has no `--config` flag: config overlays go through
// `--patch` on top of a profile, and the ACP server still needs
// `--profile acp` to boot. `--config` stays a legacy-server escape
// hatch behind HAPI_DSH_ACP_COMMAND.
expect(resolveDshAcpCommand({
HAPI_DSH_ACP_CONFIG: '/tmp/cordis.yml'
})).toEqual({
command: 'dsh',
args: ['--profile', 'acp', '--patch', '/tmp/cordis.yml']
})
})
it('keeps bare arguments for an explicitly configured server', () => {
// Custom wrappers must not silently receive the official CLI's
// `--profile acp` flags; they opt into args via ARGS_JSON or CONFIG.
expect(resolveDshAcpCommand({ HAPI_DSH_ACP_COMMAND: 'dsh-acp-demo' })).toEqual({
command: 'dsh-acp-demo',
args: []
})
})
it('supports source checkouts and community servers through JSON args', () => {
expect(resolveDshAcpCommand({
HAPI_DSH_ACP_COMMAND: 'pnpm',
+18 -2
View File
@@ -21,16 +21,32 @@ function parseArgsJson(raw: string): string[] {
return value
}
/** Default ACP entrypoint: the official `dsh` CLI ships the ACP profile since 0.2.x. */
export const DSH_DEFAULT_ACP_COMMAND = 'dsh'
export const DSH_DEFAULT_ACP_ARGS = ['--profile', 'acp']
export function resolveDshAcpCommand(env: DshAcpEnvironment = process.env): {
command: string
args: string[]
} {
const command = env.HAPI_DSH_ACP_COMMAND?.trim() || 'dsh-acp-demo'
const override = env.HAPI_DSH_ACP_COMMAND?.trim()
const command = override || DSH_DEFAULT_ACP_COMMAND
if (env.HAPI_DSH_ACP_ARGS_JSON?.trim()) {
return { command, args: parseArgsJson(env.HAPI_DSH_ACP_ARGS_JSON.trim()) }
}
const config = env.HAPI_DSH_ACP_CONFIG?.trim()
return { command, args: config ? ['--config', config] : [] }
if (config) {
// The official CLI has no `--config` flag: it composes config overlays
// via `--patch` on top of a profile, so a config-only override still
// needs `--profile acp`. Legacy community servers that speak
// `--config` must be pinned with HAPI_DSH_ACP_COMMAND.
if (override) return { command, args: ['--config', config] }
return { command, args: [...DSH_DEFAULT_ACP_ARGS, '--patch', config] }
}
// An explicitly configured server keeps bare `[]` args: custom wrappers
// must not silently receive the official CLI's `--profile acp` flags.
if (override) return { command, args: [] }
return { command, args: DSH_DEFAULT_ACP_ARGS }
}
export function createDshBackend(env: NodeJS.ProcessEnv = process.env): AcpSdkBackend {
+15 -5
View File
@@ -19,7 +19,7 @@ agent's integration; their supported syntax varies by agent.
| GitHub Copilot | `hapi copilot` | ACP (`copilot --acp --stdio`) | ✓ | ✓ | `default` `read-only` `safe-yolo` `yolo` | ✓ |
| Kimi | `hapi kimi` | ACP (`kimi acp`) | ✓ | ✓ | `default` `read-only` `safe-yolo` `yolo` | ✓ |
| OpenCode | `hapi opencode` | ACP (`opencode acp`) | ✓ | ✓ | `default` `plan` `yolo` | ✓ |
| DeepSeek Harness | `hapi dsh` | ACP (`dsh-acp-demo` or configured server) | — | ✓ | Managed by DSH ACP composition | — |
| DeepSeek Harness | `hapi dsh` | ACP (`dsh --profile acp` or configured server) | — | ✓ | Managed by DSH ACP composition | — |
| Antigravity (agy) | `hapi agy` | Headless print mode (per-turn `agy -p` + NDJSON) | — | ✓ | `request-review` `always-proceed` | ✓ |
| Pi | `hapi pi` | `pi --mode rpc` (JSON-line RPC over stdio) | — | ✓ | none (always auto-approve) | ✓ |
| Gemini CLI | — | **Removed** — Google sunset the consumer Gemini CLI (2026-06-18) | — | — | — | — |
@@ -256,12 +256,22 @@ If a remote session reports authentication failure, run `grok login --device-aut
## DeepSeek Harness
`hapi dsh` uses the shared ACP transport and keeps DSH's runtime outside HAPI. The
default executable is `dsh-acp-demo`; configure a different ACP server or a
default entrypoint is the official `dsh` CLI with `--profile acp` (the ACP profile
ships with `@deepseek-ai/dsh` since 0.2.0). Configure a different ACP server or a
source checkout with `HAPI_DSH_ACP_COMMAND`, `HAPI_DSH_ACP_CONFIG`, or the JSON
argument array `HAPI_DSH_ACP_ARGS_JSON`.
argument array `HAPI_DSH_ACP_ARGS_JSON`. `HAPI_DSH_ACP_CONFIG` passes a `--config`
path to the server named by `HAPI_DSH_ACP_COMMAND`; on the default official CLI
(which has no `--config` flag) it is applied as a `--patch` overlay on top of
`--profile acp`.
The official demo is published as `@deepseek-ai/dsh-acp-demo`; use an exact
version such as `0.1.0-rc.7` rather than npm's stale `latest` tag:
```bash
npm install -g @deepseek-ai/dsh@0.2.0-rc.2
hapi dsh
```
The standalone legacy demo is still published as `@deepseek-ai/dsh-acp-demo`; use
it through an explicit override and an exact version such as `0.1.0-rc.7` rather
than npm's stale `latest` tag:
```bash
npm install -g @deepseek-ai/dsh-acp-demo@0.1.0-rc.7