The session-title snapshot exposed a real activation race: ui-trajectory and ui-question register into conversation-declared slots but only injected 'slots', so nothing ordered their applies after ui-conversation's — register() into the undeclared slot threw and the entry FAILED. Both now inject 'conversation' as an ordering edge (documented as such; specs stub the service where the bench declares the slot itself). Review-bot findings, all three applied: the module loader's load sink cross-checks the handoff id against the arriving row (a mis-stamped bundle can no longer register under another entry's identity); the default execute seam removes the inline script node right after its synchronous execution (repeated HMR rebuilds no longer accumulate dead nodes); a throwing onRebuilt subscriber is contained per-listener and routed to onError instead of escaping the fs.watchFile callback.
@deepseek-ai/dsh-host-webserver
Web-shape HTTP carrier: a node:http server routing /api/* to an injected fetch-shaped handler (node:http ↔ WHATWG bridge with SSE streamed out chunk by chunk) and everything else to static file serving with the step1-locked semantics — traversal outside the dist root is 403, any miss falls back to index.html with HTTP 200 (SPA routing), unknown extensions ship as octet-stream, non-GET/HEAD is 405.
The package has zero workspace dependencies on purpose: the handler arrives by structural typing ({ fetch: typeof fetch }), so webserver ← runtime is a runtime injection relationship, never a package dependency. Callers supply both the bind host and port; port 0 requests an OS-assigned port and the running handle reports the assigned value. dsh web defaults to 127.0.0.1 and accepts --host 0.0.0.0 for deliberate network access. Web (browser) shape only — Electron loads dist over file:// and carries fetch over an IPC bridge, not this server. This package never prints; the URL line belongs to the shell.
Client-disconnect detection hangs off the response close event, not the request: since Node 16, IncomingMessage close fires as soon as the request body is consumed (immediately for a bodyless GET), which would abort every SSE stream right after open. RunningWebServer.close() pairs close() with closeAllConnections() because SSE connections never end on their own.
A request whose handling throws (a malformed %-escape hitting decodeURIComponent, a client dropping mid-body) is answered 400 — or the socket destroyed when headers are already out — and reported to onError; it never becomes a process-killing unhandled rejection.
Model Experience
None, as the package is a pure HTTP carrier between the browser and the injected API handler; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- No TLS, auth, or origin policy — callers that bind a non-loopback address expose the server to that network; deployment hardening (or fronting it with a real reverse proxy) is deliberately out of scope for the dev-facing v1.
- The starter MIME table is minimal — extensions beyond the vite-emitted set fall back to
application/octet-stream; extend the table when an asset class actually ships. - Socket options are fixed — callers select the bind host and port, while backlog and other socket settings remain internal until a deployment needs them.