Skip to content

feat: add dsh (DeepSeek Harness) CLI backend - #3

Merged
dorokuma merged 2 commits into
dorokuma:mainfrom
KOP-XIAO:feat/dsh-backend
Aug 29, 2026
Merged

dorokuma merged 2 commits into
dorokuma:mainfrom
KOP-XIAO:feat/dsh-backend

Conversation

@KOP-XIAO

Copy link
Copy Markdown
Contributor

Summary

Adds dsh (DeepSeek Harness CLI) as a fourth CLI backend alongside agy / grok / codex, so WeChat messages can be routed to dsh --profile headless <prompt> via /backend dsh (or WECHATBRIDGE_BACKEND=dsh).

The headless profile answers one task, prints the final assistant message to stdout, writes dsh: <code>: <message> to stderr on error, and exits 0 only on a completed turn (verified against the published @deepseek-ai/dsh-headless bundle source).

What's included

  • wechatbridge/dsh.py — new backend: run_dsh() (single-turn subprocess with per-user workspace isolation), extract_artifacts() (markdown file:/// links + absolute/relative file links, deduped by path), handle_dsh_slash_command() (/help, /clear//new as single-turn no-ops, TUI/dangerous command guards, passthrough otherwise).
  • config.py — DSH_BIN_PATH / DSH_PROFILE (default headless) / DSH_TIMEOUT / WECHATBRIDGE_DSH_HOME; backend whitelist includes dsh.
  • runner_common.py — KNOWN_BACKENDS registers dsh (per-user prefs get a dsh slot); .dsh/logs|cache|sessions added to retention cleanup.
  • main.py — dispatch in _run_llm / _handle_slash / send_artifacts_back (dsh allowed root = per-user session dir); /backend usage text is now generated from KNOWN_BACKENDS.
  • Docs — README (en/zh) dsh backend notes, deploy/wechatbridge.env.example, CHANGELOG entry.

Design decisions

  • Single-turn only: dsh-headless always creates a fresh session-<uuid> per invocation — there is no resume. Every WeChat message starts a new dsh session; /clear / /new are accepted but are no-ops. /model, /fast, /planning, /persona, /add-dir return a short "not supported on dsh" notice for v1.
  • Machine-wide auth (same model as grok): the child runs with HOME pointed at the per-user session dir, so the bridge always passes DSH_HOME explicitly (default host ~/.dsh; override with WECHATBRIDGE_DSH_HOME).
  • Env hygiene: strips DSH_SESSION_ID / DSH_SESSION_JSONL / DSH_SHELL from the child env so a bridge process running inside a dsh session never leaks its own session vars.
  • Artifacts: output file links are extracted and resolved against the per-user session dir; link targets are stripped from the WeChat display text so server paths never leak.

Tests

  • New tests/test_dsh.py (35 tests) + tests/fake_dsh.py (fake headless CLI): command construction, artifact extraction (file:///, relative, dedup, URL-encoded), success/failure/timeout/empty paths, missing-credentials preflight, env assertions (DSH_HOME, HOME, DSH_SESSION_* stripping, cwd), slash commands, and /backend dsh preference switching. All pass.
  • Full suite: 325 tests; the 5 remaining failures are pre-existing on macOS (/var → /private/var symlink in codex/hardening path assertions) and reproduce on main without this change.

Acceptance needed

The dsh backend is implemented from the published headless bundle contract and a fake CLI in tests; final acceptance needs a real dsh login + dsh --profile headless run on hardware (same status as the codex backend).

KOP-XIAO and others added 2 commits August 29, 2026 12:26
Add dsh as a fourth CLI backend (agy/grok/codex/dsh). Runs
`dsh --profile headless <prompt>` in the per-user session directory,
extracts file:/// and markdown file links as send-back artifacts, and
maps CLI errors to the standard Chinese notice bubbles.

The headless profile always creates a fresh session per invocation, so
the dsh backend is single-turn: /clear and /new are accepted but are
no-ops, and model/effort/persona slash commands are not wired yet.

- config.py: DSH_BIN_PATH / DSH_PROFILE / DSH_TIMEOUT /
  WECHATBRIDGE_DSH_HOME + backend whitelist
- runner_common.py: register dsh in KNOWN_BACKENDS; .dsh cleanup paths
- main.py: dispatch in _run_llm/_handle_slash/send_artifacts_back;
  /backend usage text now lists dsh
- docs: README (en/zh), deploy env example, CHANGELOG
- tests: tests/fake_dsh.py + tests/test_dsh.py (35 tests, all pass)
Security and correctness hardening for the dsh CLI backend:

- Pass prompt as positional after -- to block option injection; fake_dsh stub now parses argv POSIX-style, with red/green tests

- Strip bare file:/// links from display text; path extraction uses a whitelist so adjacent CJK text is not swallowed

- Filter .dsh internal artifacts from send-back; clear .initialized.dsh on /clear

- Machine-level session cleanup under DSH_HOME/sessions gated on explicit WECHATBRIDGE_DSH_HOME; unified strip+abspath normalization shared by runtime and cleanup; warn-once when unset

- Replace @path mentions outside session_dir with [blocked-path]; token-based detection covers CJK, relative, symlink and adjacent-character bypass paths

- Docs: -- in command examples, DSH_HOME semantics, attachment limits for dsh

- Tests: 360 passing (baseline 325)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants