mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs(web): retire tap-era prose across READMEs, subsystems, and notes
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md
|
||||
2026-07-24-web-config-tree-boot-and-transport-layering.md: f397ca6a25cffa0ba709d0a8e84717a1c228b951
|
||||
2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: e1cde78bad16d618913e52894eff1271dac74be1
|
||||
2026-07-24-web-config-tree-boot-and-transport-layering.md: eb30ba84ef293a169931ef6519a9d6d2ea98af7f
|
||||
2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 7a175b44fa8806b4b73eca538f32f16aa778fdee
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ English | [中文](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md)
|
||||
|
||||
**Config sources have one declaration place each.** Bundle yml values are engineering defaults, Settings sections are writable user preferences, CLI flags address their owning launcher rows, and env values enter through yml `!!js` expressions. Patches replace a row's config wholesale. The resolved frontend `distIndex` uses that patch channel as an assembly fact. The transport-independent provider/model default belongs to `ctx.agentDefaultModel`; the [direct headless entry point](2026-08-09-headless-direct-core-entry-point.md) and the Web gateway consume the same state.
|
||||
|
||||
**The transport splits five ways.** `dsh-host-apiproxy` is the gateway plugin (`api-gateway` row): it default-exports `ApiProxyService`, configures only `{nativeOpen?}`, consumes the base layer's entry-point-neutral `ctx.agentDefaultModel`, provides `ctx.apiProxy`, remains transport-agnostic, and registers no routes. `dsh-host-webserver` is a plain route-registration plugin: `WebServer` provides `ctx.webServer` (`register(route) → disposer` with duplicate-pattern throw, `tapIndex` transforms applied in registration order, `port`), listens on activation, answers per-request failures with 400 and logging, and knows no harness concepts. The connection node half owns the `/api` binding from `ctx.apiProxy` through `toFetchHandler`. The modules node half (`ClientModuleRegistry`, providing `ctx.clientModules`) owns incremental package scanning, the bundle route, the index tap, and `onRebuilt`/`onGraphChanged` notification. The hmr node half owns dev reload through `fs.watchFile` membership and the `/plugins/events` SSE route.
|
||||
**The transport splits five ways.** `dsh-host-apiproxy` is the gateway plugin (`api-gateway` row): it default-exports `ApiProxyService`, configures only `{nativeOpen?}`, consumes the base layer's entry-point-neutral `ctx.agentDefaultModel`, provides `ctx.apiProxy`, remains transport-agnostic, and registers no routes. `dsh-host-webserver` is a plain route-registration plugin: `WebServer` provides `ctx.webServer` (`register(route) → disposer` with duplicate-pattern throw, `renderIndex` rendering — structured `webserver/index-inject` rows, then raw `tapIndex` transforms in registration order — and `port`), listens on activation, answers per-request failures with 400 and logging, and knows no harness concepts. The connection node half owns the `/api` binding from `ctx.apiProxy` through `toFetchHandler`. The modules node half (`ClientModuleRegistry`, providing `ctx.clientModules`) owns incremental package scanning, the bundle route, the boot injection rows, and `onRebuilt`/`onGraphChanged` notification. The hmr node half owns dev reload through `fs.watchFile` membership and the `/plugins/events` SSE route.
|
||||
|
||||
**Package export discipline.** The modules package exposes exactly `.` (node half) and `./client` (the complete browser half: `ClientModuleSystem`, `parseBootManifest`, the adoption plugin face) — no bespoke subpaths; wire types re-export through the root for host-side consumers. The adoption handshake: the kernel writes the constructed instance to `window.__DSH_MODULES__` before cordis exists; the `./client` apply reads the slot (missing = loud throw) and provides `ctx.modules`.
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
**每个配置源有唯一声明位置。** 组合包 yml 值是工程默认,Settings 分节是可写的用户偏好,CLI(命令行界面)flags 面向其归属的启动器配置行,env 值则通过 yml `!!js` 表达式进入。patch 会整体替换一行的 config。解析后的前端 `distIndex` 通过同一条 patch 通道作为组装事实传递。与传输无关的提供方/模型默认值归 `ctx.agentDefaultModel` 所有;[直接 headless 入口](2026-08-09-headless-direct-core-entry-point.md)与 Web 网关消费同一份状态。
|
||||
|
||||
**传输五分。** `dsh-host-apiproxy` 是网关插件(`api-gateway` 行):默认导出 `ApiProxyService`,只配置 `{nativeOpen?}`,消费 base 层不偏向特定入口的 `ctx.agentDefaultModel`,provide `ctx.apiProxy`,保持传输无关且不注册路由。`dsh-host-webserver` 是朴素的路由注册插件:`WebServer` provide `ctx.webServer`(`register(route) → disposer`、重复 pattern 即抛、`tapIndex` 按注册序应用、`port`),激活即 listen,单请求失败时答 400 并记日志,且不认识任何 harness 概念。connection node 半拥有从 `ctx.apiProxy` 经 `toFetchHandler` 绑定到 `/api` 的逻辑。modules node 半(`ClientModuleRegistry`,provide `ctx.clientModules`)拥有单包增量扫描、bundle 路由、index tap 与 `onRebuilt`/`onGraphChanged` 通知。HMR(热模块替换) node 半通过 `fs.watchFile` membership 与 `/plugins/events` SSE 路由拥有开发期重载。
|
||||
**传输五分。** `dsh-host-apiproxy` 是网关插件(`api-gateway` 行):默认导出 `ApiProxyService`,只配置 `{nativeOpen?}`,消费 base 层不偏向特定入口的 `ctx.agentDefaultModel`,provide `ctx.apiProxy`,保持传输无关且不注册路由。`dsh-host-webserver` 是朴素的路由注册插件:`WebServer` provide `ctx.webServer`(`register(route) → disposer`、重复 pattern 即抛、`renderIndex` 渲染——先结构化 `webserver/index-inject` 行、后原始 `tapIndex` 按注册序应用——与 `port`),激活即 listen,单请求失败时答 400 并记日志,且不认识任何 harness 概念。connection node 半拥有从 `ctx.apiProxy` 经 `toFetchHandler` 绑定到 `/api` 的逻辑。modules node 半(`ClientModuleRegistry`,provide `ctx.clientModules`)拥有单包增量扫描、bundle 路由、启动注入行与 `onRebuilt`/`onGraphChanged` 通知。HMR(热模块替换) node 半通过 `fs.watchFile` membership 与 `/plugins/events` SSE 路由拥有开发期重载。
|
||||
|
||||
**包出口纪律。** modules 包只暴露 `.`(node 半)与 `./client`(完整浏览器半:`ClientModuleSystem`、`parseBootManifest`、收编插件面)——不设专用子路径;wire 类型经根出口 re-export 给 host 侧消费方。收编握手:内核在 cordis 之前把建好的实例写入 `window.__DSH_MODULES__`;`./client` 的 apply 读取该槽位(缺少时显式抛错)并 provide `ctx.modules`。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-10-pre-plugin-theme-bootstrap.md
|
||||
2026-08-10-pre-plugin-theme-bootstrap.md: 7f5c57316d7dd022b08918459282a6f6128eb93b
|
||||
2026-08-10-pre-plugin-theme-bootstrap.zh.md: 7152bf8dc2bbfae2a671c4da899cbebda0bd4b39
|
||||
2026-08-10-pre-plugin-theme-bootstrap.md: cf3b2d5b6c8e8b102df6a37bab0fc63d81d8736b
|
||||
2026-08-10-pre-plugin-theme-bootstrap.zh.md: 2836f7b6d32528796bf64bf708229d5677866364
|
||||
|
||||
@@ -12,7 +12,7 @@ The web shell renders `Loading plugins…` before the browser-side plugin tree a
|
||||
|
||||
## Decision
|
||||
|
||||
ui-theme's host half transforms each index HTML document through `ctx.webServer.tapIndex()`, inserting a synchronous inline script immediately after the opening `<body>` tag. The transform registers under an optional `httpServer` injection, so compositions without that service still activate ui-theme and install no transform. When the HTML parser executes the script, the body exists, but the shell's module script and framework-free boot page have not yet run.
|
||||
ui-theme's host half answers every `webserver/index-inject` collection with one body-placed script row (`bootThemeInjection`), which `renderIndex` renders as a synchronous inline script immediately after the opening `<body>` tag. The subscription is unconditional — a composition without a web server never emits the event, so ui-theme still activates and contributes nothing. When the HTML parser executes the script, the body exists, but the shell's module script and framework-free boot page have not yet run.
|
||||
|
||||
The host half registers the [`ui-theme.preference` settings section](2026-08-06-host-backed-web-preferences.md) when a settings provider exists. For each index response, it embeds that schema-validated built-in preference in the inline script; without a settings provider or active registration, it embeds the `system` default. The browser resolves `system` through `prefers-color-scheme`, falling back to light when `matchMedia` is unavailable. It writes only the two pieces of DOM state that ThemePresenter later owns: `document.documentElement.style.colorScheme` and `body[data-ds-dark-theme]`.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Web 壳在浏览器侧插件树激活前呈现 `Loading plugins…`。ui-theme
|
||||
|
||||
## 决策
|
||||
|
||||
ui-theme 的主机侧通过 `ctx.webServer.tapIndex()` 转换每份 index HTML,在 `<body>` 起始标签后紧接一段同步内联脚本。该转换通过可选的 `httpServer` 注入注册,因此不含该服务的组合仍会激活 ui-theme,但不会安装转换。HTML 解析器执行该脚本时,body 已存在,而壳的模块脚本与不依赖框架的启动页尚未运行。
|
||||
ui-theme 的主机侧以一条 body 定位的 script 行(`bootThemeInjection`)回应每次 `webserver/index-inject` 收集,`renderIndex` 把它渲染为 `<body>` 起始标签后紧接的一段同步内联脚本。订阅是无条件的——没有 web server 的组合根本不会 emit 该事件,ui-theme 照常激活且不贡献任何行。HTML 解析器执行该脚本时,body 已存在,而壳的模块脚本与不依赖框架的启动页尚未运行。
|
||||
|
||||
settings provider 存在时,主机侧会注册 [`ui-theme.preference` settings 分节](2026-08-06-host-backed-web-preferences.md)。它为每份 index 响应把经过 schema 校验的内建偏好嵌入内联脚本;不存在 settings provider 或有效注册时则嵌入默认值 `system`。浏览器通过 `prefers-color-scheme` 解析 `system`,不支持 `matchMedia` 时回退为浅色。脚本只写 ThemePresenter 后续拥有的两项 DOM 状态:`document.documentElement.style.colorScheme` 与 `body[data-ds-dark-theme]`。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
|
||||
client-modules.md: 81c85e2f0eb1d7248459f6ca092ed89f602e8415
|
||||
client-modules.zh.md: 37b8acc0d6d335802333100b5e55e709153911da
|
||||
client-modules.md: d939cbbafa186b82d9110a6ccc41e9892b53eb95
|
||||
client-modules.zh.md: 8f407de0bd44ea2cc95b6d29ca7e149648785c0f
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
English | [中文](client-modules.zh.md)
|
||||
|
||||
The web plugin table: the Node half of the client module system in [dsh-client-modules](../../packages/client/modules), provided as `ctx.clientModules` (`ClientModuleRegistry`). It scans the host Loader's entries for packages declaring `dsh.client`, composes the `window.__DSH_BOOT__` entry graph, serves each bundle at `/plugins/<id>/client.js`, and taps the index render to inject the boot manifest — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](../../packages/host/webserver): the carrier described in [web-server.md](web-server.md) supplies the prefix route and index tap this service registers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](../../packages/client/modules/README.md), not here.
|
||||
The web plugin table: the Node half of the client module system in [dsh-client-modules](../../packages/client/modules), provided as `ctx.clientModules` (`ClientModuleRegistry`). It scans the host Loader's entries for packages declaring `dsh.client`, composes the `window.__DSH_BOOT__` entry graph, serves each bundle at `/plugins/<id>/client.js`, and answers every index-injection collection with the boot manifest rows — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](../../packages/host/webserver): the carrier described in [web-server.md](web-server.md) supplies the prefix route and the `webserver/index-inject` event this service answers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](../../packages/client/modules/README.md), not here.
|
||||
|
||||
Source: [`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts)
|
||||
|
||||
## The wire
|
||||
|
||||
The graph is the wire single source between the Node and browser halves: the host composes `WebBootEntry` rows from scanned packages, injects the graph as the first script in `<head>` (`window.__DSH_BOOT__`, with `<` escaped so plugin-controlled strings cannot break out of the script element), and the shell parses it before booting anything. A page without a valid manifest cannot boot — the browser-side parser throws loud on a missing or malformed graph.
|
||||
The graph is the wire single source between the Node and browser halves: the host composes `WebBootEntry` rows from scanned packages, publishes the graph as a `global` injection row rendered ahead of later script rows (`globalThis["__DSH_BOOT__"]`, with `<` escaped so plugin-controlled strings cannot break out of the script element), and the shell parses it before booting anything. A page without a valid manifest cannot boot — the browser-side parser throws loud on a missing or malformed graph.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -60,9 +60,9 @@ Scanning is incremental per package; there is no full-rescan code path. Every co
|
||||
|
||||
Package metadata — including the negative "not a client package" verdict — is cached per name and never expires: plugin-set changes take effect on restart. A fiber restart reuses its row and rev untouched; bundle content changes reach the graph only through `rebuilt()`.
|
||||
|
||||
## The bundle route and index tap
|
||||
## The bundle route and index injection
|
||||
|
||||
`GET`/`HEAD /plugins/<id>/client.js` serves the registered bundle from disk with `no-cache` (the rev query, not HTTP caching, anchors consistency); other methods are 405. An unknown id — or a registered row whose bundle is unreadable because it has not been built yet — answers a loud 404 rather than letting the carrier's SPA fallback ship HTML as JavaScript. The index tap injects the current graph on every index render, so a reload always boots against the live composition.
|
||||
`GET`/`HEAD /plugins/<id>/client.js` serves the registered bundle from disk with `no-cache` (the rev query, not HTTP caching, anchors consistency); other methods are 405. An unknown id — or a registered row whose bundle is unreadable because it has not been built yet — answers a loud 404 rather than letting the carrier's SPA fallback ship HTML as JavaScript. The injection rows carry the current graph on every index render, so a reload always boots against the live composition.
|
||||
|
||||
## The service
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
[English](client-modules.md) | 中文
|
||||
|
||||
Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModules`(`ClientModuleRegistry`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包,组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins/<id>/client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[web-server.md](web-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。
|
||||
Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModules`(`ClientModuleRegistry`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包,组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins/<id>/client.js` 提供各个 bundle,并以启动 manifest(元数据清单)行回应每次 index 注入收集——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[web-server.md](web-server.md) 所述的载体提供本服务注册的前缀路由与其回应的 `webserver/index-inject` 事件。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。
|
||||
|
||||
源码:[`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts)
|
||||
|
||||
## wire
|
||||
|
||||
图是 Node 半与浏览器半之间协议层的唯一真源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `<head>` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。
|
||||
图是 Node 半与浏览器半之间协议层的唯一真源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图发布为一条 `global` 注入行、渲染在后续 script 行之前(`globalThis["__DSH_BOOT__"]`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -60,9 +60,9 @@ interface WebBootGraph {
|
||||
|
||||
包元数据——包括「非 client 包」这一否定结论——按名缓存且永不过期:插件集合的变更在重启后生效。fiber 重启原样复用其行与 rev;bundle 内容变更只经 `rebuilt()` 到达图。
|
||||
|
||||
## bundle 路由与 index 转换
|
||||
## bundle 路由与 index 注入
|
||||
|
||||
`GET`/`HEAD /plugins/<id>/client.js` 以 `no-cache` 从磁盘提供已注册的 bundle(锚定一致性的是 rev 查询参数,而非 HTTP 缓存);其他方法返回 405。未知 id——或已注册、但 bundle 因尚未构建而不可读的行——回应一个大声的 404,而不是让载体的 SPA 回退把 HTML 当作 JavaScript 发出。index 转换在每次 index 渲染时注入当前图,因此刷新页面总是针对实时组合启动。
|
||||
`GET`/`HEAD /plugins/<id>/client.js` 以 `no-cache` 从磁盘提供已注册的 bundle(锚定一致性的是 rev 查询参数,而非 HTTP 缓存);其他方法返回 405。未知 id——或已注册、但 bundle 因尚未构建而不可读的行——回应一个大声的 404,而不是让载体的 SPA 回退把 HTML 当作 JavaScript 发出。注入行在每次 index 渲染时携带当前图,因此刷新页面总是针对实时组合启动。
|
||||
|
||||
## 服务
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/web-server.md
|
||||
web-server.md: 70bc0a2631418c102bdb79c1df29a73df5edf30a
|
||||
web-server.zh.md: 870c9209dcfacd191da408be2e195edc047ab25d
|
||||
web-server.md: 6a33eb2bacb7dd9e85a13d7f2ef9cf870b5c44d9
|
||||
web-server.zh.md: 5dcc4b6e002c4bdeb0d839e67a750f67f40eb0a6
|
||||
|
||||
@@ -42,7 +42,7 @@ interface Config {
|
||||
|
||||
## The service
|
||||
|
||||
`WebServer` (`ctx.webServer`) listens immediately on activation; a listen failure (EADDRINUSE…) rejects initialization, and the boot process reports the failed fiber. `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, including the port assigned by the OS when `config.port` is 0.
|
||||
`WebServer` (`ctx.webServer`) listens immediately on activation; a listen failure (EADDRINUSE…) rejects initialization, and the boot process reports the failed fiber. `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. `collectIndexInjections()` gathers structured `IndexInjection` rows over one `webserver/index-inject` emit, and `renderIndex(html)` renders them into every index response — `/` and each SPA fallback — before applying the raw `tapIndex(transform)` escape-hatch transforms in registration order; [dsh-client-modules](../../packages/client/modules) answers the event with the boot manifest rows. `port` reads the listening port, including the port assigned by the OS 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).
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ interface Config {
|
||||
|
||||
## 服务
|
||||
|
||||
`WebServer`(`ctx.webServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会使初始化被拒绝,启动进程会报告失败的 fiber。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层约定,冲突即配置错误。`tapIndex(transform)` 添加一个纯 HTML 到 HTML 转换函数,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,包括 `config.port` 为 0 时操作系统分配的端口。
|
||||
`WebServer`(`ctx.webServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会使初始化被拒绝,启动进程会报告失败的 fiber。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层约定,冲突即配置错误。`collectIndexInjections()` 经一次 `webserver/index-inject` emit 收集结构化 `IndexInjection` 行,`renderIndex(html)` 把它们渲染进每个 index 响应(`/` 和每次 SPA 回退),随后再按注册顺序应用原始的 `tapIndex(transform)` 逃生口转换;[dsh-client-modules](../../packages/client/modules) 以启动 manifest(元数据清单)行回应该事件。`port` 读取监听端口,包括 `config.port` 为 0 时操作系统分配的端口。
|
||||
|
||||
处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/connection/README.md
|
||||
README.md: f0a707cc5f3a962c7852f7323c727d0a39a57b10
|
||||
README.zh.md: b529eebf9af93e36b6b92c19964678b7f5a04ea1
|
||||
README.md: a7562b9dac57930b1abc0b76b9079a6865a38b35
|
||||
README.zh.md: c18740e98721a9aa7ec95037da45c739c58a06f7
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The Host half owns the single `/api` route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
|
||||
Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The exported `ClientTransportHooks` names the page global `__DSH_TRANSPORT__` that replaces the browser carrier wholesale: the served web app leaves it unset and gets HTTP + WebSocket, while a shell owning a different physical transport (the worker preview's postMessage tunnel) provides `createApiClient` and `fetch` — plus `loadBundle` when it also owns bundle bytes — instead of forking the plugin. The Host half owns the single `/api` route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
|
||||
|
||||
## /api browser-trust fence
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket;进程内载体满足同一双流抽象。Host half 持有唯一 `/api` route 及其 Fetch bridge;已注册的 Typert interceptor 会先认领自己的 Remote endpoint,未认领请求再回退 API Proxy。Loopback hostname 判定逻辑留在包内部:`/api` Host fence 与 WebSocket upgrade 会直接使用它,其他客户端插件则消费派生的 `ctx.connection.isLoopback` 状态。node 半侧的 `/api` 路由让特权方法集(`host.pickDirectory`、`host.openPath`,以及整个配置面——`settings.describe`/`openDocument`/`update`/`replace`/`mutate` 与 `credentials.describe`/`set`/`unset`;读取与原生操作也在内,因为 describe 会返回已暴露的配置、打开操作会作用于 Host 桌面,而探测任意引用会报出某条凭据来自何处——以及 agent(智能体) preset 的创作面 `agentPreset.read`/`copy`/`openDocument`/`remove`,因为组装指明了一个会话所运行的插件,读取它是侦察,而 copy/remove/openDocument 管理名单并驱动宿主桌面(创作只有复制一种写入,因此这些方法都不接收组装文本或路径);`agentPreset.list` 与 `agentPreset.select` 不在其中——名单只携带 id 与信任级别,而选择一个 preset 并不比 `session.create` 自带的 `agentPreset` 多给任何能力,何况默认 preset 本就带着 bash)以空信任表过信任 fence,从而钉在回环——已声明的 `trustedHosts` 授权可达其余全部方法,而这些方法在真正的认证层出现之前仍只限回环本机。平台载体与 ConnectionController 循环属于包内部;apply 负责选择并驱动它们。下行边界见 [WebSocket 下行载体 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md)。
|
||||
协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket;进程内载体满足同一双流抽象。导出的 `ClientTransportHooks` 命名了整体替换浏览器载体的页面全局量 `__DSH_TRANSPORT__`:served web app 不设置它、走 HTTP + WebSocket;拥有另一种物理传输的壳(worker 预览的 postMessage 隧道)则在此提供 `createApiClient` 与 `fetch`——当它同时持有 bundle 字节时再加 `loadBundle`——而不必 fork 本插件。Host half 持有唯一 `/api` route 及其 Fetch bridge;已注册的 Typert interceptor 会先认领自己的 Remote endpoint,未认领请求再回退 API Proxy。Loopback hostname 判定逻辑留在包内部:`/api` Host fence 与 WebSocket upgrade 会直接使用它,其他客户端插件则消费派生的 `ctx.connection.isLoopback` 状态。node 半侧的 `/api` 路由让特权方法集(`host.pickDirectory`、`host.openPath`,以及整个配置面——`settings.describe`/`openDocument`/`update`/`replace`/`mutate` 与 `credentials.describe`/`set`/`unset`;读取与原生操作也在内,因为 describe 会返回已暴露的配置、打开操作会作用于 Host 桌面,而探测任意引用会报出某条凭据来自何处——以及 agent(智能体) preset 的创作面 `agentPreset.read`/`copy`/`openDocument`/`remove`,因为组装指明了一个会话所运行的插件,读取它是侦察,而 copy/remove/openDocument 管理名单并驱动宿主桌面(创作只有复制一种写入,因此这些方法都不接收组装文本或路径);`agentPreset.list` 与 `agentPreset.select` 不在其中——名单只携带 id 与信任级别,而选择一个 preset 并不比 `session.create` 自带的 `agentPreset` 多给任何能力,何况默认 preset 本就带着 bash)以空信任表过信任 fence,从而钉在回环——已声明的 `trustedHosts` 授权可达其余全部方法,而这些方法在真正的认证层出现之前仍只限回环本机。平台载体与 ConnectionController 循环属于包内部;apply 负责选择并驱动它们。下行边界见 [WebSocket 下行载体 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md)。
|
||||
|
||||
## /api 浏览器信任栅栏
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/web/README.md
|
||||
README.md: c7b77d3eba8cf56017c816362e9ffc4fcb3be403
|
||||
README.zh.md: 2a52bcdfe7395466866b80160f0646cf3b7fe111
|
||||
README.md: b4304ddc3f7d8fc0f8bfc5cb8373196a97e0273f
|
||||
README.zh.md: aebaccf819f02ed49afdf4f20027738c19c6cde0
|
||||
|
||||
@@ -8,7 +8,7 @@ The boot page uses plain DOM and local CSS, so client-bundle and plugin-activati
|
||||
|
||||
`PLATFORM_MODULES` (src/platform.ts) is the single source of truth for shell-seeded shared modules. Together with `PRELOADED_CLIENT_EXTERNALS`, it defines the implicit external baseline for every dynamic bundle; `dsh.client.external` adds only exact non-baseline requests.
|
||||
|
||||
The optional override parameter `seams` forwards the module system's `loadBundle` transport override (`BootSeams`) for environments where external `<script>` execution cannot reach the page context; ordinary browser callers omit it.
|
||||
The optional override parameter `seams` forwards the module system's `loadBundle` transport override (`BootSeams`) for environments where external `<script>` execution cannot reach the page context; ordinary browser callers omit it. A pre-injected page transport is the default ahead of it: when `globalThis.__DSH_TRANSPORT__` (the connection package's `ClientTransportHooks`) carries `loadBundle`, the module stage adopts it as the bundle transport and skips the immediate-tier HTTP prefetch — explicit `seams` still win.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Web 启动内核:`new AppWebEntry(el, seams?).run()` 分两个阶段挂载客
|
||||
|
||||
`PLATFORM_MODULES`(src/platform.ts)是外壳播种共享模块的唯一事实来源。它与 `PRELOADED_CLIENT_EXTERNALS` 一起定义全部动态 bundle 的隐式 external 基座;`dsh.client.external` 只添加基座之外的精确请求。
|
||||
|
||||
可选的覆盖参数 `seams` 会为外部 `<script>` 执行无法到达页面上下文的环境转发模块系统的 `loadBundle` 传输覆盖(`BootSeams`);普通浏览器调用方省略此参数。
|
||||
可选的覆盖参数 `seams` 会为外部 `<script>` 执行无法到达页面上下文的环境转发模块系统的 `loadBundle` 传输覆盖(`BootSeams`);普通浏览器调用方省略此参数。预注入的页面传输是位于其前的默认值:当 `globalThis.__DSH_TRANSPORT__`(connection 包的 `ClientTransportHooks`)携带 `loadBundle` 时,模块阶段将其采纳为 bundle 传输并跳过 immediately 层级的 HTTP 预取——显式 `seams` 仍然优先。
|
||||
|
||||
## 模型体验
|
||||
|
||||
|
||||
Reference in New Issue
Block a user