Repository navigation
design dsh client version gate
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).
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.
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 anHN_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 answerschat: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:
spawngates 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.versionGatefor tests (the default is the real check).
-
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.
-
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) olddshon PATH end-to-end with the upgrade hint in the ready error. - Repo typecheck + all four package suites green;
pnpm format.