mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Rebuild of the region machinery (PR3) on the post-#904 Typert projection: renderPageRegion/renderInheritedPage live in dsh-typert-generator beside the projection; scripts/gen-cordis-catalog.ts owns the curated SERVICE_PAGE / EVENT_SCOPE_PAGE / SERVICE_WALK_EXEMPTIONS / LINK_MAP partition (fail-loud in both directions, with the independent Context-merge scan backstopping the projection's blind spot), spliceRegion, and the guarded pair auto-record. docs/cordis-catalog/ is deleted: the flat events/services catalogs dissolve into per-page regions and docs/cordis-catalog/core moves to docs/cordis-api/ with the inherited tier as its own generated page. The partition absorbs the post-regrouping surface: ctx.typert → invariants.md, ctx.directoryPicker → workspace.md, skills/* events → skills.md, and the four launcher-provided tui accessor values join the named exemptions.
109 lines
6.6 KiB
Markdown
109 lines
6.6 KiB
Markdown
# HTTP Server
|
|
|
|
English | [中文](http-server.zh.md)
|
|
|
|
[dsh-host-webserver](../../packages/host/webserver) is the web-shape HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.httpServer`, a named-route registry, index.html transform taps, and a single claimable fallback seat. It is not part of the agent-loop spine and not a capability seam — it knows no harness concepts, and every feature surface (the `/api` bridge, plugin bundles, the HMR event stream) is a route some other plugin registers ([layering note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)). Web (browser) shape only: Electron loads dist over `file://` and carries fetch over an IPC bridge, not this server.
|
|
|
|
Source: [`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts)
|
|
|
|
## Routes
|
|
|
|
```ts type-equiv
|
|
/** Route match kind: 'exact' matches the pathname verbatim; 'prefix' p matches p and p/<anything>. */
|
|
type WebRouteKind = 'exact' | 'prefix'
|
|
```
|
|
|
|
```ts type-equiv
|
|
/** One named route registration. */
|
|
interface WebRoute {
|
|
kind: WebRouteKind
|
|
/** Absolute pathname, no trailing slash. */
|
|
path: string
|
|
/** Owns the full response lifecycle (may hold the response open, e.g. SSE). */
|
|
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>
|
|
}
|
|
```
|
|
|
|
Match order is fixed: exact table first, then longest matching prefix, then the registered fallback. Registration order carries no request-facing semantics — named routes are composed to be disjoint, and the fallback seat answers anything no named route claims; one owner only, a second registration throws. The shipped Web composition claims the seat with [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts), the SPA dist server with locked semantics: non-GET/HEAD is 405, traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), and unknown extensions ship as octet-stream.
|
|
|
|
## Config
|
|
|
|
```ts type-equiv
|
|
/** Gateway config: the listen address. */
|
|
interface Config {
|
|
/** Listen host; the two supported values are loopback and all-interfaces. */
|
|
host: '127.0.0.1' | '0.0.0.0'
|
|
/** Listen port; zero requests an OS-assigned port. */
|
|
port: number
|
|
}
|
|
```
|
|
|
|
`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. The dist location is an assembly fact of the frontend plugin that claims the seat.
|
|
|
|
## The service
|
|
|
|
`HttpServerService` (`ctx.httpServer`) listens immediately on activation; a listen failure (EADDRINUSE…) throws out of init — a FAILED fiber the boot's fail-loud sweep reports. `register(route)` adds one named route and returns its disposer; a duplicate `(kind, path)` throws, because route patterns are a composition-level contract and a collision is a misconfiguration. `tapIndex(transform)` adds a pure html-to-html transform applied to every index response — `/` and each SPA fallback — in registration order; [dsh-client-modules](../../packages/client/modules) uses it to inject the boot manifest. `port` reads the listening port, the OS-assigned value when `config.port` is 0.
|
|
|
|
A request whose handling throws (a malformed %-escape hitting `decodeURIComponent`, a client dropping mid-body) is logged as a warning and answered 400 — or the socket destroyed when headers are already out — never a process exit. Disposal pairs `close()` with `closeAllConnections()` because a handler may hold its response open (SSE) and such connections never end on their own; without the force-close, teardown would hang. The package never prints: the URL line belongs to the shell. Per-package operational detail, including the dev-mode bundle watch pipeline, stays in the [README](../../packages/host/webserver/README.md).
|
|
|
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
|
|
<a id="cordis-surface"></a>
|
|
|
|
## Cordis surface
|
|
|
|
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
|
|
|
|
<a id="ctxhttpserver--httpserverservice"></a>
|
|
|
|
### `ctx.httpServer` — `HttpServerService`
|
|
|
|
The web-shape HTTP carrier service. Activation listens immediately (route registration order carries no request-facing semantics: named routes are composed to be disjoint, and the fallback seat answers anything not yet claimed during the boot window — 404 until its owner registers). A listen failure throws out of init — a FAILED fiber the boot's fail-loud sweep reports.
|
|
|
|
```ts cordis-catalog
|
|
/**
|
|
* Register a named route. Duplicate (kind, path) throws — route patterns are
|
|
* a composition-level contract, so a collision is a misconfiguration.
|
|
* @param route - kind, path, and the owning handler.
|
|
* @returns the disposer removing the route.
|
|
*/
|
|
register(route: WebRoute): () => void
|
|
|
|
/**
|
|
* Register an exact-path HTTP upgrade route. Duplicate paths throw because
|
|
* one socket can have only one protocol owner.
|
|
* @param route - pathname and handler owning negotiation plus socket use.
|
|
* @returns the disposer removing the route.
|
|
*/
|
|
registerUpgrade(route: WebUpgradeRoute): () => void
|
|
|
|
/**
|
|
* Claim the fallback seat: the handler answering every request no named
|
|
* route matches (the SPA dist server in the shipped Web composition). One
|
|
* owner only — a second registration throws, because two fallbacks cannot
|
|
* compose.
|
|
* @param handler - owns the full response lifecycle of unmatched requests.
|
|
* @returns the disposer releasing the seat.
|
|
*/
|
|
registerFallback(handler: WebRoute['handler']): () => void
|
|
|
|
/**
|
|
* Register an index.html transform, applied by the fallback owner to every
|
|
* index response ({@link applyIndexTaps}) in registration order.
|
|
* @param transform - pure html-to-html function.
|
|
* @returns the disposer removing the transform.
|
|
*/
|
|
tapIndex(transform: (html: string) => string): () => void
|
|
|
|
/**
|
|
* Run an index.html body through the registered taps in registration order
|
|
* — called by the fallback owner on every index response it renders.
|
|
* @param html - the raw index.html body.
|
|
* @returns the transformed body.
|
|
*/
|
|
applyIndexTaps(html: string): string
|
|
```
|
|
|
|
Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts)
|
|
<!-- END GENERATED cordis-surface -->
|