# Conflicts: # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md # .agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.i18n.yaml # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.zh.md # .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md # .agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml # .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.zh.md # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md # .agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml # .agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md # .agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml # .agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md # .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml # README.i18n.yaml # README.zh.md # docs/architecture.i18n.yaml # docs/architecture.zh.md # docs/development.i18n.yaml # docs/development.zh.md # docs/persistence-catalog.i18n.yaml # docs/persistence-catalog.zh.md # docs/subsystems/README.i18n.yaml # docs/subsystems/README.zh.md # docs/subsystems/agent-team.i18n.yaml # docs/subsystems/agent-team.zh.md # docs/subsystems/client-modules.i18n.yaml # docs/subsystems/client-modules.zh.md # docs/subsystems/commands.i18n.yaml # docs/subsystems/commands.zh.md # docs/subsystems/persistence.i18n.yaml # docs/subsystems/persistence.zh.md # docs/subsystems/session-reference.i18n.yaml # docs/tool-catalog.i18n.yaml # docs/tool-catalog.zh.md # docs/user/guide/providers.i18n.yaml # docs/user/guide/providers.zh.md # packages/README.i18n.yaml # packages/README.zh.md # packages/bundle/web-app/README.i18n.yaml # packages/bundle/web-app/README.zh.md # packages/client/README.i18n.yaml # packages/client/README.zh.md # packages/client/connection/README.i18n.yaml # packages/client/connection/README.zh.md # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.zh.md # packages/client/ui-primitives/README.i18n.yaml # packages/client/ui-primitives/README.zh.md # packages/client/ui-sidebar/README.i18n.yaml # packages/client/ui-sidebar/README.zh.md # packages/client/ui-workspace/README.i18n.yaml # packages/client/ui-workspace/README.zh.md # packages/context/README.i18n.yaml # packages/context/README.zh.md # packages/core/agent-loop/README.i18n.yaml # packages/credentials/README.i18n.yaml # packages/credentials/README.zh.md # packages/experimental/agent-team/README.i18n.yaml # packages/experimental/agent-team/README.zh.md # packages/experimental/tool-agent-team/README.i18n.yaml # packages/experimental/tool-agent-team/README.zh.md # packages/host/frontend-static/README.i18n.yaml # packages/host/frontend-static/README.zh.md # packages/host/webserver/README.i18n.yaml # packages/host/webserver/README.zh.md # packages/interaction/commands/README.i18n.yaml # packages/interaction/commands/README.zh.md # packages/plan/plan-mode/README.i18n.yaml # packages/plan/plan-mode/README.zh.md # packages/sandbox/sandbox-local/README.i18n.yaml # packages/sandbox/sandbox-local/README.zh.md # packages/session/README.i18n.yaml # packages/session/README.zh.md # packages/session/session-persistence-sqlite/README.i18n.yaml # packages/session/session-persistence-sqlite/README.zh.md # packages/session/session-projection-cache/README.i18n.yaml # packages/session/session-projection-cache/README.zh.md # packages/shell/tool-pwsh/README.i18n.yaml # packages/shell/tool-pwsh/README.zh.md # packages/subagent/subagent-codex/README.i18n.yaml # packages/subagent/subagent-codex/README.zh.md # packages/subagent/subagent/README.i18n.yaml # packages/subagent/subagent/README.zh.md # packages/web/tool-web/README.i18n.yaml # packages/web/tool-web/README.zh.md # scripts/snapshots/translation-prompt-v4/request-response.expected.json
7.5 KiB
Agent Note: dsh web 的 config-tree boot 与 web 传输分层
Status: implemented
English | 中文
范围:
dsh web如何组合(cordis.yml + cordis 之前的 boot 类 + 配置源),以及 web 传输如何跨包分层(网关 / 载体 / 绑定 / 图 / 开发期重载)。浏览器侧装载链归 client 插件装载 note 所有,本组合只是它的供给方。
问题
dsh web 曾是仅剩的手工装配面:bootHost 逐个挂 32 个插件、config 钉死在代码里(违反 no-hardcoded-tunables),client roster 是 web.ts 常量,而 TUI/headless 早已是 yml 组合。传输层的职责错位与之配套:webserver 自称哑载体却认识 __DSH_BOOT__ 图、拥有 SSE(Server-Sent Events)通道、硬编码 /api/* 前缀;dev 的 bundle watch 寄居在 prod 注册表里靠 watch? 参数开关、生命周期无主;图注册表对每次 internal/plugin 全量重扫;单请求失败与致命 server 错误共用一个一律退出进程的 sink。还有一个用户可见缺陷:web 路径从不加载 $DSH_HOME/.env,DSH_HOME=… dsh web 读不到自定义 home 下的 API key。
决策
组合结果是一棵平铺配置树。 apps/cli/config/base.cordis.yml 与 apps/cli/config/web.cordis.yml 共同持有全部行——host 运行时(32 行)、api-gateway 行、webserver 行、dsh.client 行(浏览器 roster;modules 行同时是 host 行)。不做主干 bundle:每插件一行、每个 config 字段 yml 可改。这一立场后来推广到全仓:两个 surface 共享的配置项被抽取进 apps/cli/config/base.cordis.yml,各 surface 则收敛为一份 overlay(共享 base overlay)。dsh-client-hmr 行是普通的始终启用的 bundle 行(最初由 --dev 在代码中追加;该旗标已废除)。行序无装载语义;激活由服务可用性驱动。共享 audit 会拒绝没有 fiber 的 import、仅等待失败的 fiber 以恢复原始激活错误,并报告让 fiber 停在 PENDING 的服务;抛出错误前,审计会通过一个进程级检查点标记这些 rejection 的确切原因,从而让 installFailLoud 将 Loader 的重复通知合并为一次,而无关的未处理 rejection 仍然致命。Node app-boot 产物内嵌 @cordisjs/plugin-include,但将 @cordisjs/plugin-loader 保持为外部依赖,因此 include 的 EntryTree 与 host 会绑定到同一个 Loader peer,而不会让一棵配置树横跨两个 Loader 实现。
boot 胶水由两个类组成。 AppCLIEntry(apps/cli)与 AppWebEntry(壳内核)只持有那些必须独立于 cordis、提前存在的东西:argv 事实、合成的 patch 集、解析出的 boot manifest(元数据清单)、模块系统实例、loading 页句柄——其余一律进插件。AppCLIEntry.run() 三段:分层 env(ambient > cwd .env > $DSH_HOME/.env,顺手关掉上述缺陷)→ patch 合成 → Loader include boot 加 activation audit。AppWebEntry.run() 在浏览器侧镜像它:把 window.__DSH_BOOT__ 解析成 BootManifest(双视角:npm 包行给模块表、cordis 插件行给 entry 组合;畸形 wire 大声抛)、建模块系统、渲染 loading 页、immediately 层预取与 Context/Loader 准备并行、create entry 之前等预取齐(物化是 tree.import 的同步 require,不受 fiber inject 等待保护;i18n → runtime/client 这类跨包 require 边要求 immediately 层工厂全部注册完——否则有实测 10–25% 的 boot 竞态)、收编 modules entry、逐一创建图行、settle、sweep。
每个配置源有唯一声明位置。 组合包 yml 值是工程默认,Settings 分节是可写的用户偏好,CLI(命令行界面)flags 面向其归属的启动器配置行,env 值则通过 yml !!js 表达式进入。patch 会整体替换一行的 config。解析后的前端 distIndex 通过同一条 patch 通道作为组装事实传递。与传输无关的提供方/模型默认值归 ctx.agentDefaultModel 所有;直接 headless 入口与 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 即抛、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。
后果
- 重组一个 web 部署 = 改 yml/patch;退役件(
mountWebPlugins、CLIENT_PACKAGES、createHostWebPluginRegistry、startWebServer、webserver 的图/SSE/api 知识)全部删除。 - Headless 是直接 core 入口:其随附 profile 包含共享的 base Agent 能力,并省去 Host、HTTP、Web 与浏览器层。本笔记的传输划分是浏览器 surface 的约定。
- 一个值得记住的 TypeScript 坑:
declare module 'cordis'augmentation 所在文件若没有任何 cordis import,会被降级成独立模块声明,无声打散全程序的Contextmerge(ctx.on/ctx.effect全程序消失)。用import type {} from 'cordis'锚定。
考虑过的替代方案
| 弃案 | 一行理由 |
|---|---|
专门的 dsh-host-profile 受体包 |
用户模型状态归 Settings 支撑的 ctx.agentDefaultModel 所有;额外的 Host 受体会重复归属,并排除直接入口 |
运行时里的 assembly 垫层插件(provide apiHandler) |
它的存在只因 createApiProxy 住运行时;本体迁入 apiproxy 后网关可自承载,且 toFetchHandler 是绑定方自己调的纯函数 |
| 全量重扫与增量扫描并存 | 两条实现两份语义;单包路径足以覆盖激活初扫 |
modules 包特设 ./impl 出口 |
出口不统一;标准 ./client 承载完整浏览器半 |
dev overlay / cordis.dev.yml |
一套 yml;!!js 无法条件化行存在性,--dev 追加一行就是全部差异 |
| env 进映射表 | 同一字段将出现 env/json 双源,需再发明优先级 |
create 不等预取(以 arrive() 去重为安全依据) |
被 10–25% boot 竞态证伪:在途去重只覆盖同包双拉,不覆盖跨包同步 require 边 |
| json 直接当 loader patches 文件 | json 键名将耦合 yml 行结构,profile 编写者要懂 cordis |