From a282cba946cc8db0cc667664fd5cdb345b91d0b2 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Thu, 13 Aug 2026 11:18:53 -0700 Subject: [PATCH] feat(acp): add DeepSeek Harness client preset --- .../acp/src/client/builtin_clients.rs | 30 +++++++++++++++++++ .../interfaces/acp/src/client/manager.rs | 11 +++++++ .../components/AcpAgentsConfig.test.tsx | 24 +++++++++++++++ .../config/components/AcpAgentsConfig.tsx | 9 +++++- 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/crates/interfaces/acp/src/client/builtin_clients.rs b/src/crates/interfaces/acp/src/client/builtin_clients.rs index e1aafbb8e..3c6f2af33 100644 --- a/src/crates/interfaces/acp/src/client/builtin_clients.rs +++ b/src/crates/interfaces/acp/src/client/builtin_clients.rs @@ -8,6 +8,7 @@ const LEGACY_CLAUDE_ACP_ARGS: &[&str] = &["--yes", "@zed-industries/claude-code- const CODEX_ACP_PACKAGE: &str = "@agentclientprotocol/codex-acp"; const CODEX_ACP_ARGS: &[&str] = &["--yes", "@agentclientprotocol/codex-acp@latest"]; const LEGACY_CODEX_ACP_ARGS: &[&str] = &["--yes", "@zed-industries/codex-acp@latest"]; +const DSH_ACP_PACKAGE: &str = "@deepseek-ai/dsh-acp-demo@next"; pub(crate) struct BuiltinAcpClientPreset { pub(crate) id: &'static str, @@ -32,6 +33,19 @@ const BUILTIN_ACP_CLIENT_PRESETS: &[BuiltinAcpClientPreset] = &[ adapter_package: None, adapter_bin: None, }, + // DeepSeek Harness exposes its automation surface as a native ACP server + // through the `dsh-acp-demo` bin. The server reads `./cordis.yml` by + // default; users can override that path in the preset arguments. The ACP + // package follows DSH's developer-preview `next` release channel. + BuiltinAcpClientPreset { + id: "dsh", + command: "dsh-acp-demo", + args: &[], + tool_command: "dsh-acp-demo", + install_package: Some(DSH_ACP_PACKAGE), + adapter_package: None, + adapter_bin: None, + }, // Oh My Pi (omp) — a terminal coding agent that speaks ACP natively via // `omp acp` (no adapter needed, like opencode). User-managed: omp targets // the bun runtime (installed via `bun install -g @oh-my-pi/pi-coding-agent` @@ -152,6 +166,22 @@ mod tests { assert_eq!(config.args, vec!["acp"]); } + #[test] + fn dsh_is_a_native_acp_preset() { + let preset = builtin_acp_client_preset("dsh").expect("DSH preset registered"); + assert_eq!(preset.command, "dsh-acp-demo"); + assert!(preset.args.is_empty()); + assert_eq!(preset.tool_command, "dsh-acp-demo"); + assert_eq!(preset.install_package, Some(DSH_ACP_PACKAGE)); + assert!(preset.adapter_package.is_none()); + assert!(preset.adapter_bin.is_none()); + + let config = default_config_for_builtin_client("dsh").expect("DSH config"); + assert!(config.enabled); + assert_eq!(config.command, "dsh-acp-demo"); + assert!(config.args.is_empty()); + } + #[test] fn migrates_only_exact_legacy_builtin_commands() { let mut config_file = AcpClientConfigFile { diff --git a/src/crates/interfaces/acp/src/client/manager.rs b/src/crates/interfaces/acp/src/client/manager.rs index 8e6563ec3..45452c944 100644 --- a/src/crates/interfaces/acp/src/client/manager.rs +++ b/src/crates/interfaces/acp/src/client/manager.rs @@ -2721,4 +2721,15 @@ mod tests { assert_eq!(resolved.env.get("BASE").map(String::as_str), Some("1")); assert!(resolved.enabled); } + + #[test] + fn resolves_builtin_dsh_config_for_remote_workspace() { + let resolved = + resolve_config_for_client(&AcpClientConfigFile::default(), "dsh", Some("remote-host")) + .expect("built-in DSH config"); + + assert_eq!(resolved.command, "dsh-acp-demo"); + assert!(resolved.args.is_empty()); + assert!(resolved.enabled); + } } diff --git a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.test.tsx b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.test.tsx index 64bb96d2e..ecb5b39de 100644 --- a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.test.tsx +++ b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.test.tsx @@ -548,6 +548,30 @@ describe('AcpAgentsConfig', () => { expect(notifySuccessMock).toHaveBeenCalledWith('notifications.configAddedManualCliRequired'); }); + it('offers DeepSeek Harness as a native ACP preset', async () => { + probeClientRequirementsMock.mockResolvedValue([ + { + id: 'dsh', + tool: { name: 'dsh-acp-demo', installed: true, version: '0.1.0-rc.6' }, + runnable: true, + notes: [], + }, + ]); + + await act(async () => { + root.render(); + }); + + await act(async () => { + await Promise.resolve(); + await Promise.resolve(); + }); + + expect(container.textContent).toContain('DeepSeek Harness'); + expect(container.textContent).toContain('dsh-acp-demo'); + expect(container.textContent).not.toContain('registry.acpMissing'); + }); + it('does not downgrade enabled agents on transient probe timeouts during refresh', async () => { probeClientRequirementsMock .mockResolvedValueOnce([ diff --git a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx index 6f291eed7..fa2d307a2 100644 --- a/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AcpAgentsConfig.tsx @@ -90,7 +90,7 @@ interface AcpClientPreset { // Presets that speak ACP natively and therefore need no separate adapter // package (their CLI binary is launched directly). -const NATIVE_ACP_PRESET_IDS = new Set(['opencode', 'omp']); +const NATIVE_ACP_PRESET_IDS = new Set(['opencode', 'dsh', 'omp']); // Presets BitFun cannot install on the user's behalf — the agent must be // installed manually (e.g. omp targets bun and ships via its own installer). @@ -105,6 +105,13 @@ const PRESETS: AcpClientPreset[] = [ command: 'opencode', args: ['acp'], }, + { + id: 'dsh', + name: 'DeepSeek Harness', + description: 'Native ACP server (dsh-acp-demo); add --config with an absolute DSH cordis.yml path.', + command: 'dsh-acp-demo', + args: [], + }, { id: 'omp', name: 'Oh My Pi',