第 2 篇:整体组成与模块职责
本篇结论 :dsh 的 250 个包不是一盘散沙,而是五层金字塔 ------ 地基、脊梁、能力接缝、具体实现、消费者。你要改的东西几乎永远在顶层两层,底层几乎不用碰。读完你能得到:一张能定位「我要的东西在哪一层」的地图,以及看懂「能力接缝三件套」在真实代码里长什么样。
开篇:一家餐厅的岗位表
想象你要开一家餐厅。
打开 dsh 仓库看见 250 个包时的眩晕感,大概等于第一次拿到一家米其林餐厅的完整岗位表:主厨、副厨、切配、打荷、冷盘、热菜、面点、洗碗、传菜、领班、收银、采购、库管......两百多号人。
但如果你按层级看,其实就五层:
- 店面和基础设施(水电燃气、POS 机)------ 地基
- 后厨主线(接单 → 做菜 → 出餐这条主干)------ 脊梁
- 岗位标准(「切配岗」的定义,谁都能来应聘)------ 能力接缝
- 具体的人(张三做川菜,李四做粤菜)------ Provider
- 对客的岗位(服务员、外卖窗口、电话接单)------ Consumer
dsh 的包结构,就是这个结构。下面一层一层看。
2.1 先看仓库最上面一层
D:/dsh-master-probe
├── apps/ 可部署的应用成品:CLI / Web / 桌面端(Electron)
├── packages/ ★ 主体:约 250 个 @deepseek-ai/dsh-* 包,分 56 个分组
├── python/ Python SDK 和运行时
├── native/ Node 原生模块(Win32 进程/PTY 等底层系统调用)
├── vendor/ ★ Cordis 框架本体(被内置进来并改了作用域)
├── docs/ 官方文档(部分是脚本从源码生成的)
├── scripts/ 质量门禁和代码生成脚本
├── benchmarks/ 性能基准用例
├── snapshots/ 录制的会话快照,供无密钥回放测试
└── website/ VitePress 文档站
你要记住的两条:
- 真正的业务逻辑全在
packages/和vendor/。apps/只是「把某个组合打包成可部署形态」的容器。官方文档说得很硬:只有dshCLI + 命名 Profile 才是合法的应用启动方式,有个脚本专门拦截绕过dsh的启动路径。 vendor/是框架本体。 dsh 把 Cordis 的源码整包搬进仓库,改了 npm 作用域(cordis→@deepseek-ai/cordis)重新发布。所以你在vendor/README.md里能看到「19 处本地修改」的记录 ------ dsh 是按自己需要改造过 Cordis 的。
2.2 五层金字塔
#mermaid-svg-GTVSP953jTkSa0Uk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GTVSP953jTkSa0Uk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GTVSP953jTkSa0Uk .error-icon{fill:#552222;}#mermaid-svg-GTVSP953jTkSa0Uk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GTVSP953jTkSa0Uk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GTVSP953jTkSa0Uk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GTVSP953jTkSa0Uk .marker.cross{stroke:#333333;}#mermaid-svg-GTVSP953jTkSa0Uk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GTVSP953jTkSa0Uk p{margin:0;}#mermaid-svg-GTVSP953jTkSa0Uk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GTVSP953jTkSa0Uk .cluster-label text{fill:#333;}#mermaid-svg-GTVSP953jTkSa0Uk .cluster-label span{color:#333;}#mermaid-svg-GTVSP953jTkSa0Uk .cluster-label span p{background-color:transparent;}#mermaid-svg-GTVSP953jTkSa0Uk .label text,#mermaid-svg-GTVSP953jTkSa0Uk span{fill:#333;color:#333;}#mermaid-svg-GTVSP953jTkSa0Uk .node rect,#mermaid-svg-GTVSP953jTkSa0Uk .node circle,#mermaid-svg-GTVSP953jTkSa0Uk .node ellipse,#mermaid-svg-GTVSP953jTkSa0Uk .node polygon,#mermaid-svg-GTVSP953jTkSa0Uk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GTVSP953jTkSa0Uk .rough-node .label text,#mermaid-svg-GTVSP953jTkSa0Uk .node .label text,#mermaid-svg-GTVSP953jTkSa0Uk .image-shape .label,#mermaid-svg-GTVSP953jTkSa0Uk .icon-shape .label{text-anchor:middle;}#mermaid-svg-GTVSP953jTkSa0Uk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GTVSP953jTkSa0Uk .rough-node .label,#mermaid-svg-GTVSP953jTkSa0Uk .node .label,#mermaid-svg-GTVSP953jTkSa0Uk .image-shape .label,#mermaid-svg-GTVSP953jTkSa0Uk .icon-shape .label{text-align:center;}#mermaid-svg-GTVSP953jTkSa0Uk .node.clickable{cursor:pointer;}#mermaid-svg-GTVSP953jTkSa0Uk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GTVSP953jTkSa0Uk .arrowheadPath{fill:#333333;}#mermaid-svg-GTVSP953jTkSa0Uk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GTVSP953jTkSa0Uk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GTVSP953jTkSa0Uk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GTVSP953jTkSa0Uk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GTVSP953jTkSa0Uk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GTVSP953jTkSa0Uk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GTVSP953jTkSa0Uk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GTVSP953jTkSa0Uk .cluster text{fill:#333;}#mermaid-svg-GTVSP953jTkSa0Uk .cluster span{color:#333;}#mermaid-svg-GTVSP953jTkSa0Uk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GTVSP953jTkSa0Uk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GTVSP953jTkSa0Uk rect.text{fill:none;stroke-width:0;}#mermaid-svg-GTVSP953jTkSa0Uk .icon-shape,#mermaid-svg-GTVSP953jTkSa0Uk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GTVSP953jTkSa0Uk .icon-shape p,#mermaid-svg-GTVSP953jTkSa0Uk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GTVSP953jTkSa0Uk .icon-shape .label rect,#mermaid-svg-GTVSP953jTkSa0Uk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GTVSP953jTkSa0Uk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GTVSP953jTkSa0Uk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GTVSP953jTkSa0Uk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} L5 组合分发层
bundle/*(补丁层) · boot/*(启动胶水) · test-support/*
L4 消费者层
tool-*(面向模型的工具) · client/*(51 个 UI 包) · api/* · sdk/*
L3 具体实现层 Provider
fs-local · fs-sandbox · bash-local · llm-deepseek · session-persistence-jsonl
L2 能力接缝层 Service Definition
ctx.fs · ctx.shell · ctx.web · ctx.skill · ctx.subagents · ctx.workflow
L1 核心脊梁
session(147) · agent(93) · tools(58) · system-prompt(51) · agent-loop(40)
scope(32) · llm(115) · session-projection(70) · invariants(47)
L0 地基
vendor/cordis(插件框架本体) · util/*(15 个零依赖工具包)
括号里的数字是入度------有多少个包依赖它。数字越大越是「脊梁」,越底下越不能动。
一句话概括各层的性格:
| 层 | 性格 | 你会碰它吗 |
|---|---|---|
| L0 地基 | 零内部依赖,谁都用 | 几乎不碰 |
| L1 核心脊梁 | 不可约简的最小集合 | 很少碰,但要理解 |
| L2 能力接缝 | 薄薄一层接口标准 | 想加新能力时碰 |
| L3 Provider | 可热插拔的实现 | 经常碰(换个实现) |
| L4 Consumer | 面向模型/用户的工具 | 经常碰(加个工具) |
| L5 组合分发 | 决定最终装什么 | 最常碰(写配置) |
这张表是本篇最实用的一张。 你以后有一个需求,先问自己「这属于哪一层」,就能立刻知道该动什么、不该动什么。
2.3 L1:核心脊梁,六个包撑起全部
官方文档有一张「Core packages」表(docs/architecture.md:59-68),我把它翻成白话:
| 包 | 通俗职责 | ctx 上的 key |
|---|---|---|
core/session |
流水账本。只追加的事件日志,模型能看到的历史全从它派生 | ctx.sessions |
core/agent |
Agent 的花名册 。定义 Agent 接口、维护实时注册表、发 agent/* 事件 |
ctx.agents |
core/tools |
工具柜。工具的注册表 + 带守卫的执行管线 | ctx.tools |
core/system-prompt |
提示词装配线。把各插件贡献的段落和工具 schema 拼成系统提示词 | ctx.systemPrompt |
core/agent-loop |
那个主循环 。实现了 core/agent 定义的接口 ------ 就是第 1 篇说的「Agent 循环本身也是插件」 |
ctx.agentLoop |
core/scope |
隔离罩。让某个注册只对一个 Agent 生效 | 纯库,不占 key |
llm/llm |
模型调用的抽象。消息和流的词汇表 + 适配器接缝 | ctx.llm |
两个细节值得单独说:
**① core/agent 和 core/agent-loop 是分开的。**这不是多此一举。core/agent 定义「Agent 应该长什么样」(接口 + 注册表 + 事件词表),core/agent-loop 是「一个具体的实现」。
好处:你想换一种完全不同的 Agent 驱动方式(比如不做 React 循环,改做状态机),只要写一个新的实现挂在 ctx.agentLoop 上,所有依赖「Agent」这个概念的地方都不用改。
用餐厅的话说:core/agent 是「厨师岗」的岗位说明书,core/agent-loop 是现在上岗的这位张三。换李四来,岗位说明书不用改。
**② core/session 被 147 个包依赖,是全仓第一脊梁。**为什么?因为第 1 篇讲过的那条铁律 ------ 模型可见即已落日志。既然所有送给模型的东西都必须能从日志重建,那所有跟「给模型看什么」有关的包,都得依赖会话日志。
session 组的 19 个包就是围绕这本账本的全套服务:持久化(存成 JSONL)、投影(把事件折叠成可读状态)、标题生成、统计、遥测、格式迁移(v0→v1→v2→v3)......
2.4 L2/L3:能力接缝 ------ 三件套在真实代码里长什么样
第 1 篇讲过 Seam = Service Definition + Provider + Consumer。现在拿文件系统这个接缝走一遍,看它在仓库里具体是哪些包。
#mermaid-svg-w2LCgBXMRgU0dNNp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w2LCgBXMRgU0dNNp .error-icon{fill:#552222;}#mermaid-svg-w2LCgBXMRgU0dNNp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w2LCgBXMRgU0dNNp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w2LCgBXMRgU0dNNp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w2LCgBXMRgU0dNNp .marker.cross{stroke:#333333;}#mermaid-svg-w2LCgBXMRgU0dNNp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w2LCgBXMRgU0dNNp p{margin:0;}#mermaid-svg-w2LCgBXMRgU0dNNp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster-label text{fill:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster-label span{color:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster-label span p{background-color:transparent;}#mermaid-svg-w2LCgBXMRgU0dNNp .label text,#mermaid-svg-w2LCgBXMRgU0dNNp span{fill:#333;color:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp .node rect,#mermaid-svg-w2LCgBXMRgU0dNNp .node circle,#mermaid-svg-w2LCgBXMRgU0dNNp .node ellipse,#mermaid-svg-w2LCgBXMRgU0dNNp .node polygon,#mermaid-svg-w2LCgBXMRgU0dNNp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w2LCgBXMRgU0dNNp .rough-node .label text,#mermaid-svg-w2LCgBXMRgU0dNNp .node .label text,#mermaid-svg-w2LCgBXMRgU0dNNp .image-shape .label,#mermaid-svg-w2LCgBXMRgU0dNNp .icon-shape .label{text-anchor:middle;}#mermaid-svg-w2LCgBXMRgU0dNNp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w2LCgBXMRgU0dNNp .rough-node .label,#mermaid-svg-w2LCgBXMRgU0dNNp .node .label,#mermaid-svg-w2LCgBXMRgU0dNNp .image-shape .label,#mermaid-svg-w2LCgBXMRgU0dNNp .icon-shape .label{text-align:center;}#mermaid-svg-w2LCgBXMRgU0dNNp .node.clickable{cursor:pointer;}#mermaid-svg-w2LCgBXMRgU0dNNp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w2LCgBXMRgU0dNNp .arrowheadPath{fill:#333333;}#mermaid-svg-w2LCgBXMRgU0dNNp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w2LCgBXMRgU0dNNp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w2LCgBXMRgU0dNNp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w2LCgBXMRgU0dNNp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w2LCgBXMRgU0dNNp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w2LCgBXMRgU0dNNp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster text{fill:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp .cluster span{color:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w2LCgBXMRgU0dNNp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w2LCgBXMRgU0dNNp rect.text{fill:none;stroke-width:0;}#mermaid-svg-w2LCgBXMRgU0dNNp .icon-shape,#mermaid-svg-w2LCgBXMRgU0dNNp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w2LCgBXMRgU0dNNp .icon-shape p,#mermaid-svg-w2LCgBXMRgU0dNNp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w2LCgBXMRgU0dNNp .icon-shape .label rect,#mermaid-svg-w2LCgBXMRgU0dNNp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w2LCgBXMRgU0dNNp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w2LCgBXMRgU0dNNp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w2LCgBXMRgU0dNNp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Consumer(用电器)
Provider(插头厂商)
Service Definition(插座标准)
packages/fs/fs
定义 ctx.fs 该有哪些方法
声明 fs/write-intent 等事件
fs-local
直接读写本地磁盘
fs-sandbox
受沙箱策略约束的读写
fs-e2b
远程 E2B 沙箱里的文件系统
tool-fs
暴露给模型的文件工具
tool-fs-search
tool-str-replace-editor
关键观察:Consumer 那三个箭头,全部指向 DEF,没有一个指向某个具体的 P。
这就是接缝的全部秘密。三个文件工具只知道「有个 ctx.fs,它有 read/write/list」,完全不知道背后是本地磁盘还是远程沙箱。
于是威力出现了。 官方举的例子(docs/architecture.md:129):
文件系统和子进程共享同一个执行世界,所以把它们指向一个远程沙箱时,Bash、PTY 和 LSP 会跟着一起搬过去,不需要 fork 任何 Provider。
翻译成人话:你把「文件在哪」和「进程在哪」这两个 Provider 一起换成远程版,那么「执行 bash」这个功能就自动变成「在远程机器上执行 bash」了。你一行工具代码都没改。
用餐厅的话说:你把「食材仓库」和「灶台」都搬到了分店,那么「炒菜」这个动作自然就在分店发生了 ------ 厨师的菜谱一个字都不用改。
主要的接缝一览
| 接缝 | ctx key |
常见 Provider | 常见 Consumer |
|---|---|---|---|
| 模型调用 | ctx.llm |
llm-deepseek、llm-pi-ai |
agent-loop |
| 文件系统 | ctx.fs |
fs-local、fs-sandbox、fs-e2b |
tool-fs、tool-fs-search |
| Shell | ctx.shell |
bash-local、bash-sandbox、pwsh-local |
tool-bash、tool-pwsh |
| 联网 | ctx.web |
web-fetch-http、web-search-deepseek |
tool-web |
| 技能 | ctx.skills |
skill-filesystem、skill-badge |
tool-skill |
| 子智能体 | ctx.subagents |
subagent-acp、claude-code、codex、fork-in-process |
tool-subagent |
| 工作流 | ctx.workflowEngine |
workflow-worker-thread |
tool-workflow |
| 会话持久化 | ctx.sessionPersistence |
session-persistence-jsonl |
session 相关包 |
| 沙箱 | ctx.sandbox |
sandbox-local、sandbox-windows-acl |
shell/fs 的沙箱版 |
这张表是你以后「想加能力」的索引。 想加一个新模型?在 ctx.llm 上注册适配器。想加一个新的搜索源?在 ctx.web 上注册 Provider。
2.5 L4:消费者层 ------ 工具、UI、远程接口
这一层是「把能力暴露出去」的地方,分三拨:
① tool-* ------ 暴露给模型的工具。 每个工具在 ctx.tools 上注册自己,同时把 schema 贡献给提示词装配,这样模型才知道有这么个东西可用。
② client/* ------ 51 个 UI 包。 整个 Web / 桌面界面。ui-chat、ui-session、ui-settings、ui-skill、ui-subagent、ui-workflow-run......这是 Cordis 的「客户端编译面」,和 Host 端是分开编译的。
③ api/* + sdk/* ------ 跨进程边界。 api 是远程 BFF 装配 + Typert RPC 网关;sdk 是 stdio JSON-RPC 协议 + 客户端 + 服务端,让 Python 或别的进程能驱动 dsh。
术语小卡片:Typert dsh 的类型图生成器 + 运行时注册表 + RPC 网关协议。作用一句话:让 Host 端的能力能跨进程暴露给 Client 端,而且类型是打通的。你不用深究,只要知道 Web 界面和后端就是靠它通信的。
2.6 L5:组合分发层 ------ 决定最终装什么
只有三组,但极其重要(第 4 篇整篇都在讲它):
| 组 | 职责 |
|---|---|
bundle/* |
6 个可安装的补丁层包:base、headless、web-app、sdk-app、acp-app、sdk-minimal |
boot/* |
启动胶水:app-boot(Profile/应用启动)和 cmdline(CLI 参数解析) |
test-support/* |
测试基础设施:假模型服务器、会话快照、Loader 冒烟测试等 |
2.7 一个必须记住的坑
fs-local 和 fs-sandbox 不能同时挂。
原因回到原理二:两个 Provider 都去注册 ctx.fs 这个 key,就冲突了。dsh 会让整个 Profile 加载失败,而不是随便挑一个。
官方原话(
packages/bundle/base/README.md):挂载dsh-fs-local会和fs-sandbox双重注册ctx.fs,导致加载失败。二选一。
这个坑的通用形式 :任何两个注册了同一个 ctx key 的插件都不共存。以后遇到「Profile 拒绝加载」,第一反应就是查是不是撞 key 了。
顺带一个相关规则:Windows 上默认用 PowerShell 工具,Mac/Linux 上默认用 bash 工具 ,同一台机器只有一套。你非要在 Windows 上换回 bash,就必须同时禁用两个 pwsh 行、再启用两个 bash 行 ------ 只改一半会加载失败。
动手试试
练习 1 回到 --dump-config 的输出,试着给每一行标注它属于哪一层(L0--L5)。你会发现绝大多数行是 L3 的 Provider 和 L4 的 tool-*,L1 脊梁只有寥寥几行 ------ 但那几行是整棵树真正的承重墙。
练习 2 挑一个你感兴趣的接缝(比如 ctx.web),在 packages/web/ 目录下看一眼它的包结构。你会发现它天然分成三类:web(定义)、web-search-*(Provider)、tool-web(Consumer)。这个「三件套」的形状在 dsh 里到处都是,认一次就终身受用。
本篇小结
- 仓库主体在
packages/(250 包 / 56 分组),框架本体在vendor/cordis。apps/只是部署容器。 - 五层金字塔 :L0 地基(Cordis + util)→ L1 核心脊梁(7 个包,
session被 147 包依赖)→ L2 能力接缝(薄接口)→ L3 Provider(可换实现)→ L4 Consumer(工具/UI/远程)→ L5 组合分发(bundle/boot)。 - 你要改的东西几乎都在 L3/L4/L5,底层很少碰,但 L1 必须理解。
- 能力接缝三件套:Consumer 只依赖 Definition,从不 import Provider。换 Provider 就换掉整条产品线(fs + subprocess 一起换 → bash/PTY/LSP 全跟着搬)。
- 坑 :同一
ctxkey 不能双重注册(fs-local和fs-sandbox二选一)。
下篇预告
你现在手里有了一张静态地图:dsh 由什么组成、谁负责什么。
但地图是死的。从你敲下回车那一刻,到 Agent 把答案吐出来,中间到底发生了什么?那些插件是怎么被一个个装配起来的?一个「任务」是怎么变成一次模型调用、一次工具执行的?