DSH plugin WebRoute: matching order and the fallback seat
The DSH plugin HTTP carrier deliberately knows nothing about DeepSeek Harness: it offers named routes, optional gzip, index transform callbacks and one claimable fallback handler. Every functional route — the /api bridge, plugin bundles, HMR event streams — is registered by other plugins. That positioning yields three rules to remember: matching order is fixed as exact → longest prefix → fallback, registration order carries no semantics; the fallback has exactly one owner; and it serves browsers only (Electron loads file:// and talks over IPC, never through this server).
DSH plugin HTTP carrier: browser transport only
Bottom line: the carrier provides transport; plugins register functional routes; it is neither the agent loop nor a capability seam.
- What it is: a
node:httpplugin providingctx.webServer. - What it includes: a named route registry, optional gzip, index transform callbacks and a claimable fallback.
- What it excludes: TLS, authentication and Origin policy.
- Who it serves: browsers only; the desktop app uses
file://plus IPC. - Output ownership: the package prints nothing; the startup URL line belongs to the shell.
DSH plugin WebRoute and matching order: exact, prefix, fallback
Bottom line: matching order is fixed and registration order does not participate.
interface WebRoute {
kind: 'exact' | 'prefix'
path: string // absolute pathname, no trailing slash
handler: (req, res) => void | Promise<void>
}
- exact matches the pathname verbatim.
- prefix matches the prefix and any subpath.
- Order: the exact table, then the longest matching prefix, then the fallback.
- Disjoint routes: named routes must not overlap; a duplicate
(kind, path)throws. - Upgrade routes: one protocol owner per path; duplicates throw as well.
- Handler duties: own the full response lifecycle, including keeping a connection open for SSE.
DSH plugin fallback seat: exactly one owner
Bottom line: the fallback handler is unique, a second registration throws, and unclaimed requests return 404 during startup.
- Semantics: answers every request no named route matches.
- Uniqueness: two fallbacks cannot compose, so only one owner exists.
- Shipped claimant: the frontend static plugin claims the seat with fixed SPA dist semantics.
- Static details: authenticate before reading dist; non-index assets stay public; non-GET/HEAD returns 405; traversal outside the root returns 403; misses return an empty 404; unknown extensions go out as octet-stream.
- Startup window: while the seat is unclaimed, unmatched requests answer 404 until the owner registers.
DSH plugin index injection: structured rows then raw taps
Bottom line: render structured injection rows first, then apply raw html transforms in registration order.
- Structured rows:
collectIndexInjections()gathers onewebserver/index-injectemit where subscribers push their current rows. - Fresh per call: subscribers can read live state such as the module graph and theme preference.
- Raw escape hatch:
tapIndex(transform)registers a pure html-to-html function. - Application order:
renderIndex(html)renders structured rows first, then taps in registration order. - Who calls it: the fallback owner on every index response it renders.
- Real example: the client modules package responds with startup manifest rows.
DSH plugin configuration and lifecycle: listen, compress and tear down
Bottom line: listen on activation and fail initialization on error; teardown must force-close connections.
| Config | Values | Default |
|---|---|---|
host | 127.0.0.1 or 0.0.0.0 | loopback |
port | number; 0 asks the OS | — |
compression | none / gzip | none |
compressionLevel | 0–9 | 1 |
compressionThresholdBytes | minimum size to compress | 1024 |
- Binding is exposure: the carrier owns no TLS, authentication or Origin policy, so a non-loopback bind is deliberate exposure.
- The shipped command is stricter:
dsh webpicks loopback and rejects--host 0.0.0.0; its Connection plugin handles Host/Origin checks and browser authentication. - Compression scope: eligible socket responses only; existing encodings,
no-transform, ranges, SSE, ZIP and pre-compressed files stay identity. - Listen failure: initialization is rejected and boot reports the failed fiber.
- Handler errors: logged as warnings and answered with 400 (or a destroyed socket once headers were sent), never crashing the process.
- Teardown:
close()paired withcloseAllConnections(), because SSE-style handlers keep responses open and would otherwise hang the shutdown.
DSH plugin HTTP troubleshooting
Bottom line: separate unmatched routes, a claimed seat and missing index injection.
- 404s — no named route matched and no fallback is claimed, or the path differs from registration.
- Route conflict errors — duplicate
(kind, path)or upgrade paths, both configuration errors. - Fallback registration throws — an owner already exists; check for double claiming.
- Injection missing — confirm the fallback owner calls
renderIndexand rows are pushed during the emit. - No gzip — the response is in the identity allowlist (ranges, SSE, pre-compressed).
- Repair status, stated honestly: matching order, seat uniqueness, index injection and configuration semantics follow the official HTTP server page; confirm static claimant details against the package source.
Sources: HTTP server, dsh-host-webserver source, dsh-host-frontend-static.
FAQ
DSH plugin route matching is fixed: the exact table first, then the longest matching prefix, then the fallback. Registration order carries no request-facing semantics, and named routes must be disjoint at the composition level.
No: the DSH plugin fallback seat has exactly one owner and a second registration throws, because two fallbacks cannot compose. The shipped Web composition has the frontend static plugin claim the seat.
It throws: a duplicate (kind, path) is a configuration error in DSH plugin routing because route patterns are a composition-level contract. HTTP upgrade routes behave the same way, since one socket has one protocol owner.
DSH plugin provides two paths: structured IndexInjection rows and raw html-to-html tapIndex transforms. Rendering applies the structured rows first, then the registered taps in registration order.
The DSH plugin WebServer listens immediately on activation, and a listen failure such as EADDRINUSE rejects initialization. The boot process reports the failed fiber instead of degrading silently.
Related Terms
- WebRoute
- WebRoute is one named route on the DSH plugin HTTP carrier, combining an exact or prefix match kind, an absolute path and a handler that owns the full response lifecycle.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- WebRouteKind
- WebRouteKind decides how a path matches: exact matches the pathname verbatim while prefix matches that prefix and any subpath. Matching order is always exact, then longest prefix, then fallback.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- fallback seat
- The fallback seat is the single fallback handler on the DSH plugin HTTP carrier, answering every request no named route matches. It has exactly one owner and a second registration throws.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- IndexInjection
- IndexInjection is a structured index.html injection row collected through one webserver/index-inject emit, fresh on every call so subscribers read live state.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
Sources
- DeepSeek Harness docs — HTTP server (routing, config, service and index injection)· deepseek-harness.github.io
- dsh-host-webserver source (route registration and fallback)· deepseek-ai (GitHub)
- dsh-host-frontend-static (the SPA dist server that claims the fallback seat)· deepseek-ai (GitHub)