-
Notifications
You must be signed in to change notification settings - Fork 0
meta dsh integration evidence
← 返回 Wiki 首页 | 元文档 · DSH 装载验证 | 上一篇:骨架走查记录 | 下一篇:功能全量清单 →
本文件记录 dsh-chat 被真实 DSH 装载的验证过程与结果。
为什么是文本而不是截图。 验证在开发者本机的 DSH 实例上进行,其界面包含 该开发者的会话标题、工作区路径等个人数据。截图会把这些带进公开仓库,因此 这里保留
--dump-config的输出 —— 它更精确(能证明条目进入了插件树的哪个位置), 且不含任何个人信息。截图留在本地build/evidence/,该目录已被 gitignore。
| 项 | 值 |
|---|---|
| DSH Desktop | v2.0.4 |
| DSH 运行时 | 0.1.2-alpha.1 |
| 装载方式 |
link: 到本仓库的 packages/chat/kernel
|
| profile | desktop |
dsh --profile desktop --dump-config 输出组合后的完整插件树。dsh-chat 的条目出现在其中:
# == @dsh-chat/kernel
- id: chat-host
name: '@dsh-chat/host'
config: {}整棵树共 149 个条目,dsh-chat 是其中之一。这证明:
-
@dsh-chat/kernel被 profile 的dsh.profile.bundles登记并识别为 bundle; - 它的
cordis.patch.yml被解析,条目被插入组合结果; -
@dsh-chat/host这个包名从 profile 的位置可解析。
$ node lib/bin.js
{"level":"info","msg":"config-manager 已挂载", ... "dshVersion":"0.1.2-alpha.1"}
启动日志中错误数为 0,窗口标题为 DeepSeek Harness Desktop(非 Recovery)。
对照:在装入 dsh-chat 之前,同一环境下曾因两个第三方插件的版本错配而进入
Recovery 模式,日志中分别是 failed to apply loader entry include 与
renderer boot failed。两者处置记录在实现记录。
host 插件注册的 /api/chat/health 在 DSH 的 web server 上存在,但无法用裸
curl 验证 —— DSH 的边缘层在路由匹配之前就对未认证请求返回 403:
$ curl -i http://127.0.0.1:43120/api/chat/health
HTTP 403 forbidden
$ curl -i http://127.0.0.1:43120/api/chat/health/不存在的路径
HTTP 403 forbidden
两个路径返回相同状态,说明 403 来自鉴权而非路由缺失。路由本身的行为由
packages/chat/host/src/index.host.spec.ts 在真实的 WebServer 服务上验证:
装载后返回 200、卸载后返回 404、连续装卸 3 轮不因路由冲突抛错。
上面三项验证做完之后,界面一直没有出现。追查下来发现客户端插件从未被装载, 而此前的记录把它写成了「组件尚未注册到 slot」—— 那个描述是错的,slot 注册的代码 早已写好并有单元测试。真正的原因有两层。
从 profile 目录复现 DSH 的模块解析:
$ cd ~/.dsh/profiles/desktop && node verify-client-entry.mjs
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]:
Package subpath './package.json' is not defined by "exports" in
.../node_modules/@dsh-chat/host/package.json
两个问题:./package.json 没有导出(DSH 工具链要读它,dshmarket 也显式导出了
这一条);更重要的是 bundle 的 cordis.patch.yml 里只有 @dsh-chat/host,
而客户端插件在独立的 @dsh-chat/client 包里,没有任何声明指向它。
DSH 的约定是一个包同时承载两侧:. 给 host 端,./client 给渲染进程,
并用 dsh.client 声明后者存在。profile 里的 dshmarket 就是这个形状。
已补上 ./client 子路径导出与转发模块。
补完导出后再解析,暴露出下一个问题:
Error [ERR_MODULE_NOT_FOUND]:
file:///.../packages/chat/client/dist/client/StatusSection.module.css
tsc 不会处理 .module.css。查上游的构建预设
(deepseek-harness/packages/client/tsdown.client.ts)后确认,DSH 的客户端 bundle 是
闭包工厂:调 window.__ModuleLoader__.load({id, factory}),外部依赖经注入的
require 从 loader 模块表解析(不走 import map);.module.css 由 lightningcss
在打包时编译成哈希类名映射,并生成一段运行时注入 <style> 的代码。
已发布的 dshmarket 的 client/client.js 印证了这一点 —— 487 KB 单文件,
CSS 以字符串内联在 NUL 前缀的 dsh-css: 区段里。
那个预设是 monorepo 内的相对导入,没有发布;dshmarket 是自己用
tsdown + lightningcss 重新实现了同一套约定。我们要做同样的事。
按上游预设的约定自己实现了打包(scripts/build-client-bundle.mjs),
只用 rolldown 与 lightningcss —— 两者本来就在依赖里(tsdown 是 rolldown
的薄封装),没有引入新的构建工具链。
约定的取值逐条抄自上游预设并在脚本里标了出处:
| 项 | 取值 |
|---|---|
| banner | window.__ModuleLoader__.load({ id: "@dsh-chat/host", factory: (require) => { |
| intro | var module = { exports: {} }; var exports = module.exports; |
| footer | return module.exports; } }); |
| 保持 external |
PLATFORM_MODULES 加本包 dsh.client.inject 声明的服务 |
| 其余依赖 | 一律打进 bundle |
打包脚本自带产物校验,不是形式主义:产物是给另一个进程加载的,构建成功不等于
它能跑,上一轮界面出不来正是因为没人检查过产物长什么样。校验四条:闭包工厂
形态、结尾的 return module.exports、无残留 .css require、以及每个
require() 的目标都在模块表里(模块表答不上来的 require 是必然的运行时抛错)。
打包接上后重启 DSH Desktop v2.0.4,设置侧栏出现 dsh-chat 分区,
能力表完整渲染:

这张图同时证明了几件事:
-
闭包工厂被 loader 接受 —— 分区出现即
__ModuleLoader__.load注册成功 -
slot 注册在真实宿主里生效 —— 不再只是单元测试里的
ctx.slots.register -
CSS Modules 内联可用 —— 卡片边框、三色徽标都来自
.module.css, 说明 lightningcss 编译出的类名映射与注入的<style>都到位了 - 能力表如实呈现三种状态 —— 已就绪(绿)、部分实现(橙)、未装载(灰)。 §6 要求可选能力必须显式显示为未安装、不得伪装为可用,这里能看到 「第二验证因素与恢复」「在线状态」「群聊与附件」都标着未装载
截图只保留设置弹窗:窗口左侧的会话列表含用户的会话标题与工作区名,
不进公开仓库。完整窗口截图留在 gitignore 的 build/evidence/。
补上 /api/chat/conversations 与 /api/chat/messages/history 之后,
界面显示的是数据库里真实的消息,不是占位内容。
会话列表带未读角标与「你:」前缀,消息视图分左右气泡,编辑过的那条标注 「已编辑」,撤回的显示为占位且原文不出现在响应里。当时的截图后来被验证七、八 的取代(面板换了位置),最终形态见下方。
这一步顺带暴露并修掉了两个真问题,都不是新代码的问题:
各端点都有端到端测试,但那些测试自己起 WebServer 并注册路由。插件的
apply() 里长期只有 /health —— 处理器写好了不等于插件把它挂上去了。
浏览器一调 /api/chat/conversations 就落到 web server 的兜底,界面白屏。
已把 18 条路由集中登记为 ROUTE_PATHS,并加了一条测试逐条访问确认它们真的在。
判据不是状态码 —— 空请求体会让多数端点合法地返回 NOT_FOUND_OR_FORBIDDEN,
那也是 404,与「路由不存在」的 404 撞了。真正区分两者的是响应头:已注册的路由由
commandHandler 应答,一定带 JSON content-type;未注册的路径 body 是空的。
原实现是 ERROR_CATALOGUE[code].retryability,目录里查不到就抛 TypeError,
整个分区渲染失败。而错误码是从网络上来的,服务端比客户端新时必然出现客户端
不认识的码 —— 一个未知错误码把界面整个搞没,比显示「操作未能完成」糟糕得多。
现在查不到就按 terminal 处理,且不给重试入口。
界面此前落在设置分区里 —— 那是将就,也偏离了 §5:文档明确把「会话列表」与 「消息视图」列为各自独立的 slot 贡献。而且它当时只能读不能写,没有输入框。
现在挂在 conversation.session.header.utilities,点会话头部右上角的「聊天」
从右侧滑出可调宽度的抽屉,带输入框:

不是乐观 UI 的假象。发送后直接查库:
最近 3 条消息:
01a0510b jia -> yi | 这条是从 DSH 界面发出去的
未读(未 ACK 的队列项):
jia -> 4
审计事件数: 2
三件事一次验证到:消息 ID 是 UUIDv7(01a0510b 前缀就是毫秒时间戳,§14
要求客户端生成 v7 而不是 crypto.randomUUID() 给的 v4);打开会话时的标记已读
生效;审计与领域写入同事务。
抽屉头部被标题栏盖住。 第一版写 top: 0,而 DSH 有自绘标题栏 —— 抽屉钻到
它下面,标题与关闭按钮整个看不见。改为量触发按钮的下边缘。
内容只占上面一小块。 ChatSection 原来写死 height: 420px。改成填满容器,
高度约束交给外层。
改默认宽度(380 → 520)后界面没变化:旧值存在 localStorage 里,在新区间内仍然
合法,于是被原样沿用。键名加 :v2 后缀让旧值失效一次。默认值变了而存储键没变,
等于改了个寂寞。
为解决「一个页面两个输入框」,把聊天做成了 conversation.view 的第三个标签,
指望切过去就只剩一个输入框。做出来了,然后退回了 —— 官方的「轨迹」标签同样
保留原生输入框:conversation.view 只替换正文区,输入框由 ConversationRoot
常驻渲染,没有 opt-out。结果是两个输入框上下紧挨着,比抽屉更糟。
抽屉至少把它们在空间上分开:我们的在右侧栏内,原生的在主区域左下。 这不是理想解,理想解见功能全量清单的「已确认做不到的事」。
-
多设备与跨主机的界面行为。当前验证是单机单账号:
P0-a还没有设备会话与 token,插件用配置里的本地身份充当已认证主体(authenticateFrom上标了边界)。 跨进程投递另有三进程验收, 但那一层没有界面。 - SSE 推送到界面。事件流端点已实现,界面目前是打开时拉一次,没有订阅。
-
经 DSH 的端到端聊天。host 的命令路由已接通并有 HTTP 端点测试
(发送、拉取、ACK、编辑、撤回、组织、工作项、通知、SSE),
但那些测试起的是独立的
WebServer实例,不是 DSH 进程内的那一个。 跨进程投递另有三进程验收。