Skip to content

design dsh client version gate

sinrimin edited this page Oct 13, 2026 · 1 revision

Design: dsh client version gate (issue #46)

Status: SHIPPED (branch feat/46-dsh-0.2, 2026-10-13). User directive (verbatim intent): dsh 本身迭代很快,我们不做兼容,只按最新的来;在合适的地方 对 Agent 客户端版本做限制,不在兼容范围内不连接,提示升级。 Context: design-phase-9-w7-native-sessions.md § #46 addendum (the v4 session-log switch this gate rides with), design-phase-9-w7.1-dsh-event- tap.md § #46 addendum (tap v2).

Problem

dsh's integration surfaces (session-log format, streaming buses, ACP dialect) move with the client, and #46 chose NO back-compat: the daemon reads one ground-truth version line (0.2.0-rc.2). Without a gate, an out-of-range client meets the integration as silent misbehavior — an empty session rail, dropped history, no token stream, or garbage tool output — with nothing telling the user their client is simply too old.

Decision

Refuse to CONNECT below a per-target floor, with an actionable upgrade hint. The floor lives on the ACP adapter table (one place; targets that iterate slowly carry none and are unaffected):

  • adapters.ts — AcpAdapterSpec.minVersion (deepseek: '0.2.0-rc.2').
  • acpMinVersion(target, env) — the floor to enforce, or null: no floor, or an HN_ACP_COMMAND_<TARGET> override owns the command line. An operator-pinned install is managed by hand and the daemon must not gate it (this also keeps test fixtures riding the override ungated — a fixture is not a dsh).

Check (acp/version-gate.ts): checkAcpVersion(target, env) probes <bin> --version through the W1 runtime probe (probeRuntime, 4s bound, pathEnv from the caller's env) and compares with shared's compareVersions (prerelease-aware: 0.2.0-rc.1 < 0.2.0-rc.2 ≤ 0.2.1-alpha.1 ≤ 0.2.0 non-prerelease). The first whitespace-separated token is parsed. Absent or unparseable versions never block — the spawn itself then fails with its own, more specific error; the gate only rules on versions it can actually read. Refusal message: dsh <v> is not supported (minimum <min>) — upgrade with: npm install -g @deepseek-ai/dsh@latest (HARNESS_PACKAGES supplies the package name).

Wiring (chat.ts):

  • Channel open (chat:session.start): the gate runs BEFORE any spawn — right after the adapter command resolves, before prewarm consumption. A refusal answers chat:session.ready {error: message} (the same channel the spawn failures use), so the web session page shows the upgrade hint as the open error. No adapter process is ever spawned, no ledger entry written.
  • Prewarm pool: spawn gates too — an out-of-range client is never warmed (the pool is a cache: a miss is silent; the channel open surfaces the message).
  • Injectable ChatHandlersOptions.versionGate for tests (the default is the real check).

Non-goals

  • Not a semver range engine: one floor per target, compared with the existing compareVersions. A ceiling is deliberately absent — newer dsh is expected to work until proven otherwise; if a release breaks the integration, the floor bumps and the gate carries the upgrade hint.
  • Not applied to targets without a floor (claude-code/codex/opencode/ hermes/pi today). Adding one later is a table row + tests.
  • Not an inventory-side warning (yet): the gate speaks at the point of failure, where the user is. The harness job's own hints (dshNodeWarning) keep their separate role.

Verification

  • test/version-gate.test.ts (7 cases): floor declaration + override skip; prerelease-aware refusals across the 0.1.x/0.2.0-rc.1 line and passes from 0.2.0-rc.2 up; decorated version strings; absent/unparseable pass-through; no probe for override/ungated targets.
  • test/chat.test.ts (#46 describe): an out-of-range client is refused at open AND never pre-warmed (injected gate + zero ledger entries), and the DEFAULT gate refuses a real (fake) old dsh on PATH end-to-end with the upgrade hint in the ready error.
  • Repo typecheck + all four package suites green; pnpm format.

Clone this wiki locally