全栈 Web 与 Typert RPC¶
端到端强类型 Typert RPC、React 前端架构、Client 插件与 Vite HMR 热重载机制。
很多 Coding Agent 仅提供命令行 CLI,而 DeepSeek Harness 提供了一套极其现代、体验流畅的 Web GUI 应用(apps/web 与 packages/client/*)。
核心文件速查¶
| 路径 | 职责 | 重要度 |
|---|---|---|
packages/typert/protocol/ |
Typert 类型图定义与 RPC 序列化 | ⭐⭐⭐ |
packages/api/gateway/src/index.ts |
后端 BFF 网关与 Typert 路由聚合 | ⭐⭐⭐ |
apps/web/src/ |
React 19 + Tailwind + Vite Web 主入口 | ⭐⭐⭐ |
packages/client/hmr/ |
Client 插件热模块重载(HMR)接收器 | ⭐⭐ |
Typert:编译期到运行时的强类型 RPC 网关¶
DSH 没有采用传统的 GraphQL 或手写 REST API,而是自研了 Typert 强类型 RPC 体系:
Backend Service (Node.js) Frontend Client (Browser)
┌───────────────────────────┐ ┌───────────────────────────┐
│ declare module typert { │ │ import type { ClientApi } │
│ export interface Api { │ ──(Typert)──► │ │
│ 'session.history': ...│ │ client.request( │
│ } │ │ 'session.history', │
│ } │ │ { sessionId } │
└───────────────────────────┘ │ ) -> Fully Typed Return! │
└───────────────────────────┘
- 类型即契约:后端定义的所有服务接口,前端在调用时拥有 100% 完整的 TypeScript 类型推导与自动补全;
- 双向流式通信:基于 WebSocket 实时广播
session/event,前端通过订阅精确刷新打字流与工具执行卡片。
前端组件架构:一切皆 Client 插件¶
正如后端万物皆插件,DSH 的 Web 前端(packages/client/*)同样采用了极致的模块化架构:
┌────────────────────────────────────────────────────────────┐
│ packages/client/web │
│ ├── ui-layout (主窗口与分栏布局) │
│ ├── ui-sidebar (会话列表与管理) │
│ ├── ui-conversation (核心对话流与气泡渲染) │
│ ├── ui-tool (折叠式工具调用卡片与 Diff 查看) │
│ ├── ui-user-questions (多选/确认交互表单) │
│ ├── ui-workflow-run (Workflow 执行流水线可视化) │
│ ├── ui-goal (Goal 目标进度条与状态跟踪) │
│ └── ui-settings (模型、Provider 与插件管理面板) │
└────────────────────────────────────────────────────────────┘
每个 UI 模块都是一个独立的客户端插件,通过挂载到前端 Context 上注入自定义视图(View Slots)。
Client Plugin HMR:开发期即时热重载¶
当开发者在本地扩展或修改某个前端 UI 插件时,packages/client/hmr 会与 Vite Dev Server 联动:
- 重新打包的 Client 插件包通过 WebSocket 动态注入到正在运行的 Web 页面中;
- 页面无需整页刷新,即可原地替换组件渲染逻辑,保留当前的会话状态与滚动位置。
本章思考与自测¶
- 思考题:当后端推送一个 8MB 的大历史记录时,DSH 在 Caddy 和 BFF 网关层采取了哪些网络优化手段?(提示:参考 zstd 压缩与 flush_interval 设置)
- 自测题:在 Web 界面中,当用户点击"打断(Stop)"按钮时,信号是如何通过 WebSocket 一路传递到底层的
AgentHandle.interrupt()的?