Files
deepseek-harness/docs/rfc/implemented/process/2026-07-06-node-engine-floor.md
imccyu 6f77da4c8c refactor: relocate demo app bundles to packages/examples/*-demo
Move the agent-spine bundle and the stdio/ACP/JSON-RPC app packages out of
core/ and ui/ into a new packages/examples/ group, renamed with a -demo
suffix so the npm name marks them as non-product surface:

  core/agent-core   -> examples/agent-spine-demo  (dsh-agent-spine-demo)
  ui/stdio-agent    -> examples/stdio-demo        (dsh-stdio-demo)
  ui/acp-agent      -> examples/acp-demo          (dsh-acp-demo)
  ui/jsonrpc-agent  -> examples/jsonrpc-demo      (dsh-jsonrpc-demo)

Update every code/config/test reference and reference-only doc mentions, and
regenerate module-graph, config-catalog, and doc-graphs. The jsonrpc bin
(dsh-jsonrpc-agent) and single-file exe (dsh-jsonrpc-agent-pkg) keep their
names; the SDK runtime-startup surface is reconciled separately.
2026-07-15 16:46:05 +08:00

4.7 KiB
Raw Blame History

RFC: Raise the Node LTS engine floor to 22.19

Status: implemented

Problem

The Node 22 branch of the root engines.node range is a contract for the installed workspace, not only for the runtime APIs the harness source calls directly. It must be no lower than package engines.node declarations for dependencies the workspace installs on that branch; otherwise pnpm install --engine-strict fails at an advertised LTS version, and non-strict installs run outside a dependency's supported runtime.

Decision

Set engines.node to ^22.19.0 || >=24.0.0 and test the keyless CI compatibility matrix on ['22.19', 24, 26]. Every matrix leg runs the TypeScript typecheck plus a keyless source-mode worker smoke, so the floor is exercised through both a complete source typecheck and a real unbuilt runtime path. The real-API e2e workflow stays on Node 24 because it exercises API integration rather than the runtime floor.

Two Node features gate the source runtime:

  • node:sqlitepackages/session-persistence/session-persistence-sqlite does a top-level import { DatabaseSync } from 'node:sqlite'. The module dropped its --experimental-sqlite flag requirement at 22.13 (LTS) and 23.4 (Current); before those, importing it throws at load.
  • Native TypeScript type-stripping — the packages/examples/stdio-demo/tests/built-bin.e2e.ts smoke boots the published lib/bin.js under plain node (no tsx) and loads the example's .ts plugins (mock-llm.ts, echo-tool.ts). Type-stripping is the default from 22.18 (LTS) and 23.6 (Current); before those it needs --experimental-strip-types.

Those source features clear on the 22.x line at 22.18, but the installed Pi adapter dependency raises the advertised LTS floor. @deepseek-ai/dsh-llm-pi-ai depends on @earendil-works/pi-ai@0.79.3, whose package declares engines.node >=22.19.0, so the LTS floor is 22.19. The 24.x branch remains >=24.0.0. The disjoint range excludes Node 23 entirely: Node 23.023.5 still has at least one flagged source feature, and the 23 line is non-LTS/EOL, so advertising >=23.6 would add a dead release line and a CI leg no deployment should use.

@types/node remains pinned to the 22.x line (^22.20.0) to match the LTS support line: reaching for a Node 23+/24+/25+ API fails tsc on every machine and in the typecheck gate, rather than compiling clean and surviving to a runtime failure only a floor matrix leg could catch. The whole tree typechecks clean against the Node 22 type surface today, so the pin costs nothing.

Consequences

  • The advertised LTS branch no longer undercuts the Pi adapter dependency floor.
  • CI proves the Node 22 LTS floor directly with Node 22.19, keeps the Node 24 branch on node: 24, and keeps Node 26 for the next even line; each leg typechecks the source graph and launches the unbuilt workflow worker for real.
  • The built-bin smoke needs no version-conditional flag: at 22.19 type-stripping is already the default, so the test stays the plain node lib/bin.js path it documents.
  • A future dependency or source API that raises the runtime floor must move engines.node, the compatibility matrix, and this RFC in the same change.

Alternatives considered

  • Keep ^22.18.0 || >=24.0.0. Rejected: it advertises an LTS version lower than the Pi adapter dependency floor. @earendil-works/pi-ai@0.79.3 requires >=22.19.0.
  • Downgrade or pin @earendil-works/pi-ai to preserve the 22.18 advertised range. Rejected: the current Pi adapter dependency is part of the intended workspace, and 22.19 is still inside the Node 22 LTS line.
  • Floor >=22.13 (the node:sqlite boundary) plus --experimental-strip-types in the built-bin smoke on 22.1322.17. Rejected: it adds a version-conditional test flag for one narrow range and dresses up an experimental-flag dependency as first-class support. The Pi adapter dependency already requires a higher LTS floor.
  • Open-ended >=22.19. Rejected: it advertises support for Node 23.023.5, where node:sqlite (until 23.4) or type-stripping (until 23.6) is still flagged.
  • Include Node 23.6+ (^22.19.0 || >=23.6.0). Rejected: 23.6+ does run both source features unflagged, but Node 23 is end-of-life; advertising a dead release line adds a range term and a CI leg for a runtime no deployment should use.
  • Matrix [22, 24, 26] instead of pinning 22.19. Rejected: floating major-version entries drift upward over time and silently stop exercising the declared LTS floor.
  • Keep @types/node ahead of the floor (^25). Rejected: types ahead of the runtime floor let a Node 24/25-only API compile clean and fail only at runtime on 22.x. Pinning @types/node to the 22.x line turns that into a compile error everywhere.