mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
core.md claimed to be the packages/core reference but opened on repo-wide type patterns and never documented the ownership vocabulary: AgentHandle, CreateAgentOptions, ResumeAgentOptions, and AgentFactory were TYPE_LINK_EXEMPTIONS pointing at a package README, invisible to the folder that calls itself the type reference. The page now reads spine map -> creation and ownership (AgentHandle pasted; the options and factory summarized with links into the generated registry section) -> the Agent handle (AgentStatus, AgentOptions, SteeringOutcome, SteeringReceipt, and SettleReason now pasted; the one settlement prose wall split by topic; delivery vocabulary ordered as a message travels) -> initiator -> interception -> a Sessions summary -> the ToolDefinition pointer -> an explicitly framed repo-wide patterns tail (the ...Map pattern, branded ids). The duplicate SessionEvent paste is gone -- session.md owns it and LINK_MAP follows -- the four ownership types moved from TYPE_LINK_EXEMPTIONS into LINK_MAP -> core.md, and three dead LINK_MAP entries (ContinuationDecision, ContinuationStop, HookContext) no longer name types absent from the source tree. The "what this page owns" meta-section folds into the intro. The subsystems README index silently lost tasks.md and session-reference.md on both language sides during a base absorption; the rows are restored and scripts/project-doc-site.spec.ts now fails when any page misses either side of the index (proven red on a removed row). tools.md links ToolSchema to its llm-streaming.md declaration instead of calling it core; subagent.md links AgentHandle and CreateAgentOptions.seed to the new section. A new Agent Note records the package-anchored page-scoping decision; the 2026-06-20 catalog note marks its spine-vs-seam rule superseded as the page-scoping rule while keeping the type-equiv mechanism current, and docs/AGENTS.md cites the new note.
109 lines
6.7 KiB
Markdown
109 lines
6.7 KiB
Markdown
# HTTP 服务器
|
||
|
||
[English](http-server.md) | 中文
|
||
|
||
[dsh-host-webserver](../../packages/host/webserver) 是 GUI 宿主 web 形态的 HTTP 载体:单个提供 `ctx.httpServer` 的 `node:http` 插件,由具名路由注册表、index.html 转换挂点与单一可认领的回退席位组成。它不属于 agent loop(智能体循环)主干,也不是能力 seam:它不了解任何 harness 概念,每个功能表面(`/api` 桥接、插件 bundle、HMR(热模块替换)事件流)都是由其他插件注册的一条路由([分层说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md))。仅限 web(浏览器)形态:Electron 通过 `file://` 加载 dist,并经 IPC 桥接承载 fetch,不经过本服务器。
|
||
|
||
源码:[`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts)
|
||
|
||
## 路由
|
||
|
||
```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>
|
||
}
|
||
```
|
||
|
||
匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到已注册的回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,任何未被具名路由认领的请求都由回退席位应答;席位只有一个所有者,第二次注册会抛出异常。发布的 Web 组合用 [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts) 认领席位,即遵循固定语义的 SPA dist 服务器:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 发送。
|
||
|
||
## 配置
|
||
|
||
```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` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。dist 位置是认领席位的前端插件的组装事实。
|
||
|
||
## 服务
|
||
|
||
`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会从 init 抛出,形成一个 FAILED fiber,由启动的大声失败 sweep 上报。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层契约,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,`config.port` 为 0 时读到的是操作系统分配的值。
|
||
|
||
处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包(package)从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [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 -->
|