dsh-plugin-t-expert 0.3.72 → 0.3.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,236 +0,0 @@
1
- ---
2
- name: dsh-harness-project
3
- description: Use when working in the deepseek-harness repository — the all-plugin Cordis agent harness — and you need the architecture, package map, profile/bundle boot model, extension points, repository conventions, quality gates, or the answer to "where does this change belong?". Load before writing, reviewing, or explaining code in this repo.
4
- ---
5
-
6
- # DeepSeek Harness:项目知识
7
-
8
- 本仓库 = **DeepSeek Harness(dsh)**:一个以 [Cordis](docs/cordis-primer.md) 为底座的「全插件」Agent Harness。版本 `0.1.5-rc.2`,MIT,pnpm workspace monorepo,发布包统一为 `@deepseek-ai/dsh-<name>`。
9
-
10
- **没有特权内核可打补丁。** 模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件;扩展方式是「在旁边挂一个插件」,而不是修改核心。所有注册都是 effect,插件卸载时自动回滚。
11
-
12
- ## 先读什么(权威顺序)
13
-
14
- 1. [AGENTS.md](AGENTS.md) — 常驻规约(root)与 [packages/AGENTS.md](packages/AGENTS.md) — 包级规约。
15
- 2. [docs/architecture.md](docs/architecture.md) — 改动 `packages/` 前必读:组合、核心包、loop、seam、扩展点。
16
- 3. [docs/glossary.md](docs/glossary.md) — 一个概念一个权威术语(seam / scope / turn / step / round / goal / human command)。
17
- 4. [packages/README.md](packages/README.md) — 包组地图;再进目标组的 README,最后进具体包的 README。
18
- 5. `docs/subsystems/<subsystem>.md` — 类型定义、语义、生成的 Cordis API。
19
- 6. `.agents/notes/` — 决策依据(active 决策记录;`archived/` 是冻结历史,**不是**当前权威)。
20
-
21
- 冲突时以代码为准:文档与代码不符是文档缺陷,应报告而不是照抄。
22
-
23
- ## 仓库布局
24
-
25
- | 路径 | 内容 |
26
- |---|---|
27
- | `vendor/` | Cordis 及其基础库的源码内联副本(重命名进 `@deepseek-ai` scope,见 [vendor/README.md](vendor/README.md))。改它要走 vendor 同步流程。 |
28
- | `packages/<group>/<pkg>/` | 全部 npm workspace 包,按能力族分组(见下)。 |
29
- | `apps/` | `cli`(`dsh` 可执行入口)、`web`(Vite 前端产物)、`desktop`(Electron)、`desktop-host`。 |
30
- | `python/` | Python SDK(`sdk/`)与运行时载体(`sdk-runtime/`)。 |
31
- | `native/` | `@deepseek-ai/node-addon-system`:Linux Landlock launcher + POSIX flock,Node-API 预编译。 |
32
- | `docs/` | 架构、子系统、生成的参考目录、cookbook、user 指南、i18n。 |
33
- | `.agents/` | `notes/`(Agent Notes)与 `skills/`(仓库级 skill)。 |
34
- | `scripts/` | 生成器与质量门禁(`run-gates.ts` 是聚合入口)。 |
35
- | `snapshots/`、`benchmarks/` | 录制会话快照与性能门禁。 |
36
- | `website/` | docs/ 的 VitePress 投影。 |
37
-
38
- 包组:`core/`(session、system-prompt、tools、agent、agent-loop、scope)、`llm/`、`subagent/`、`shell/`、`fs/`、`sandbox/`、`session/`、`session-query/`、`storage/`、`settings/`、`credentials/`、`interaction/`、`client/`(`ui-*`)、`host/`、`api/`、`typert/`、`preset/`、`bundle/`、`skill/`、`workflow/`、`webhook/`、`guard/`、`extensions/`、`util/` 等 —— 完整表见 [packages/README.md](packages/README.md),不要在这里复述第二份清单。
39
-
40
- ## 三个平面(改动落点判断的第一步)
41
-
42
- | 平面 | 拥有什么 | 判据 |
43
- |---|---|---|
44
- | **Host composition** | 注册表本体(tools/skills/subagents 注册表)、持久化、sandbox 与审批栈、模型路由、跨会话共享的服务 | 一个在 session 存在之前就完成注入的 host row;或 browser/其他 session 也要读的服务 |
45
- | **Agent preset**(`agent.cordis.yml`) | 单个 session 往那些注册表里**贡献**什么:工具、prompt section、persona、skill | 每 session 可不同;发布服务时**必须**待在带 `isolate` realm 的 group 里 |
46
- | **Session** | 该 session 自己的状态(日志、goal、plan、todo) | 按 Session/Agent 分键的状态 |
47
-
48
- - preset 里发布服务却不带 `isolate` realm → 落进 root realm 变成进程全局,`dsh-agent-presets` 在 mount 时直接拒绝。
49
- - `isolate: true` = entry-local realm(本次挂载私有);**同名 label 不会共享实例**,label 连接的是 realm。
50
- - 用户自建 preset 放在 `${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/`。**永远不要改内置 preset 安装目录**(升级会覆盖)。
51
-
52
- ## 启动模型:profile / bundle / patch
53
-
54
- 运行中的 `dsh` 是启动时按顺序分层组合出来的插件树。
55
-
56
- - **profile**:Harness home 里的具名组合,列出它叠加的 bundle、树外插件和用户自己的 `cordis.patch.yml`。内置模板:`web`、`headless`、`sdk`、`sdk-minimal`、`acp`。
57
- - **bundle**:Cordis config 行 + 其挂载代码的分发格式;在自身 `package.json` 的 `dsh.bundle` 指向 patch 文件,profile 用 `dsh.profile` 列 bundle。
58
- - **分层顺序**(从空 entry 列表开始):profile 里各 bundle 的列出顺序 → profile 的 `cordis.patch.yml` → home 级 patch → `--patch` overlay。
59
- - patch 按 **row id** 定位:替换整条 config,或插入新行。
60
- - 覆盖 `web` profile 默认是 live reload;`headless`/`sdk`/`sdk-minimal`/`acp` 只在启动时应用一次(一次性或 stdio 应用在已拥有工作后替换依赖会破坏生命周期)。
61
-
62
- ```sh
63
- dsh --profile web --dump-config # 看本机实际启动的树;打印出的任何一行都能被 patch 替换
64
- ```
65
-
66
- **应用启动只有一条路**:`dsh` CLI + 具名 profile(`dsh web` 是 `--profile web` 的别名)。package bin、demo、public SDK argv 直接拼 Cordis 树都是禁止的,`scripts/verify-application-entrypoints.ts` 会拒绝。
67
-
68
- ## 依赖语义:决定「等待」还是「挂起」
69
-
70
- `inject` 与 `ctx.get` 不是两种风格,而是**两种契约**(官方 `develop/framework/service` 有权威表述,本仓库同一套语义):
71
-
72
- - **`inject = ['x']` = 必需依赖**。框架保证 `apply` 执行时声明的服务**已全部就绪**;缺一个,插件就**等着、不执行**。
73
- → 这就是 **silent PENDING**:插件看起来"没加载",而且**它自己不产生任何日志**。排查时先怀疑这里,别先怀疑业务代码。
74
- - **省略 `inject`、用 `ctx.get('x')` = 可选依赖**,但**必须在使用点调用**,不能在 `apply` 里探一次:
75
- `apply` 是最早也最脆弱的时刻,提供方晚到的话那次探测已经返回 `undefined`,**没有任何机制会重跑它**。
76
- (真实事故:把可选服务的 `ctx.get` 写在 apply 期做一次性探测 → 服务晚到时永久判负 → 设置段不注册、面板空白。)
77
- - 运行期必需服务**消失** → 依赖它的插件自动 dispose;服务**回来** → 自动重新加载。所以「注册即 effect」是生命周期要求,不是代码风格。
78
- - 需要"等某个服务就绪再做事"时用 `ctx.inject(['x'], cb)`:它等的是"服务就绪"这件事本身,服务迟到也能补上。
79
-
80
- ## 官网文档:面向插件作者的同一份知识
81
-
82
- `https://deepseek-harness.github.io/deepseek-harness/` 是本仓库的**公开面**(`website/` 的 VitePress 投影),适合快速核对启动模型、依赖语义与钩子,再去读源码:
83
-
84
- | 页面 | 用途 |
85
- |---|---|
86
- | `guide/quickstart` | Web UI 起步:起服务、加工作区、配模型(新用户从这里开始) |
87
- | `develop/basic/` | 最小插件、`scratch-plugin/cordis.yml` 的 `- insert:` 行、**插件路径必须是绝对路径** |
88
- | `develop/basic/tool` · `config` · `publish` | 工具注册;受校验的 `Config`;`dsh.bundle` vs `dsh.profile` + `dsh plugin add` |
89
- | `develop/framework/` · `service` · `events` | 生命周期/effect、服务与依赖语义、事件系统 |
90
- | `develop/practice/` · `llm-adapter` · `dynamic-cordis` | 能力三角色分层、模型适配器、在运行中的智能体里改插件 |
91
- | `develop/cordis-tutorial/01…07` | Cordis 阶梯,终点是「进入 harness」 |
92
-
93
- **边界(重要)**:站点**不覆盖** `vitest` / `oxlint` / `run-gates.ts` / 覆盖率策略、`.agents/notes/` 约定、生成的子系统页 —— 所以**概念查站点、门禁与契约查检出**。冲突时以检出为准(站点由本仓库生成,冲突本身值得上报)。
94
-
95
- ## 核心包(spine)
96
-
97
- | 包 | 拥有 | ctx key |
98
- |---|---|---|
99
- | `core/session` | append-only `SessionEvent` 日志与内存存储 | `ctx.sessions` |
100
- | `core/system-prompt` | prompt section 与 tool schema 组装 | `ctx.systemPrompt` |
101
- | `core/tools` | 按 scope 的工具注册表 + 受控执行管线 | `ctx.tools` |
102
- | `core/agent` | `Agent` 接口、活体注册表、`agent/*` 事件 | `ctx.agents` |
103
- | `core/agent-loop` | 默认 driver(可替换) | `ctx.agentLoop` |
104
- | `core/scope` | 按 agent 的 scoped 注册原语 | 无 key |
105
- | `llm/llm` | 消息/流词汇 + adapter seam | `ctx.llm` |
106
-
107
- ## turn / step 与三类事件
108
-
109
- - **step** = 一次模型请求 + 它触发的工具调用;**turn** = 零或多个 step。
110
- - 事件分三个域,选对域是大多数改动的第一个决定:
111
- - **Session events**:追加进日志的持久事实(`turn/*`、`step/*`、`user/message`、`assistant/message`、`assistant/attempt`、`tool/*`、`system/message`)。要求 reload 后仍在,就用它。
112
- - **Agent events**(`agent/*`):携带活体 `Agent`(inbox、step、status、request、validation、continuation)。观察或拦截进行中的工作用它。
113
- - **Capability events**(`fs/*`、`tools/*`、`telemetry/*`):给 seam 挂策略和适配器,不必 import loop。
114
- - **waterfall 监听器必须调用 `next()`** 才算委派;不调用就是短路整条链。`agent/pre-step`、`agent/request`、`llm/stream`、三个 `tools/*` 是 waterfall;`agent/turn-stopping` 是串行、没有 `next()`。
115
-
116
- ## 能力 seam
117
-
118
- **seam = 可替换能力,三角色齐全**:Service Definition(拥有 `ctx.<key>` 和词汇类型的 Cordis `Service` —— 抽象类如 `ShellExecutor`,或具体注册表如 `WebRuntime`,**绝不是 TS `interface`**)、一个或多个 Service Provider、一个或多个 Consumer(通常是模型可见工具)。`packages/shell` 是范例:`dsh-shell` + `dsh-bash-local`/`dsh-bash-sandbox` + `dsh-tool-bash`。
119
-
120
- - 只做一个角色不叫 seam。角色独立演化时才分包。
121
- - **扩展插件依赖 Service Definition,绝不依赖具体 Provider**。
122
- - 设计 Service Definition 要对齐所有当前 Consumer;让某一个 Consumer 决定 service 契约是反向坏味道。
123
-
124
- ## 新行为放哪里
125
-
126
- | 目标 | 机制 |
127
- |---|---|
128
- | 加模型 provider | 在 `ctx.llm` 注册 adapter |
129
- | 加模型可见能力 | 注册到 `ctx.tools`,schema 进入 prompt 组装 |
130
- | 让某个 session 有不同能力集 | 组一个 agent preset(行内服务需 `isolate` realm) |
131
- | 加 shell 执行 | 注册 `ctx.shell` backend |
132
- | 加持久终端 | 注册 `ctx.terminals` backend + `dsh-tool-terminal` |
133
- | 加人类命令(斜杠) | 注册 `ctx.commands`,不产生模型 turn |
134
- | 加后台工作 | 注册 `ctx.jobs` |
135
- | 外部 webhook 起 Session | 在 `ctx.webhookRuntime` 注册可信规则 + provider adapter |
136
- | 文件系统访问或策略 | 注册 `ctx.fs` provider 或监听 `fs/*` |
137
- | 限制子进程 | 用 `ctx.sandbox` backend |
138
- | 拦截请求/工具/turn | 用对应 `agent/*` 或 `tools/*` 事件 |
139
- | 加模型可见上下文 | `agent.inject()`,在下一次被接受的请求中落地 |
140
- | 加 UI 或编辑器集成 | 驱动 `ctx.agents`,从 `session/event` 渲染 |
141
- | 加 Web Client Chat 节点 | 注册 `ConversationNodeDefinition` + keyed renderer |
142
- | 加持久 session 状态 | 扩展 `SessionEventMap`,从日志渲染与回放 |
143
- | 把注册限定到某个 agent | 用该 agent 的 `agent.ctx` |
144
-
145
- 分步指南在 [docs/cookbook/](docs/cookbook/extension-cookbook.md):加包、加工具、加 LLM adapter、加设置卡片、加 session 格式版本。
146
-
147
- ## 会被拒绝的约定(改动前自查)
148
-
149
- - **注册即 effect**:一切贡献走 `ctx.effect()` / `ctx.on()`;注册表 `register()` 返回 disposer;每个注册表都要有 HMR 安全测试(dispose fiber,断言清理)。
150
- - **Model-visible ⟺ logged**:任何进入模型请求的东西都必须能从 session 日志重建(有运行时 invariant 断言)。新增模型可见输入 = 新增 session event + 从日志渲染。
151
- - **插件,不是改 loop**:新行为挂到已文档化的扩展点;改 `agent-loop` 必须同步更新 `docs/architecture.md`。
152
- - **不硬编码可调项**:随部署变化的取值是受校验的 `Config` 字段(可从 cordis.yml 改)。`DEFAULT_*` 常量或测试钩子不算可配置性。协议常量、外部规范、安全不变量保持固定。
153
- - **显式 > 隐式(包边界)**:默认值解析是拥有方实现里显式的一步 `resolve(request): Spec`,不是 `run()` 里藏的 `?? default`。
154
- - **跨边界的不透明 id 要 brand**(`Branded<B>`,来自 `dsh-brand`),不能是裸 `string`。
155
- - **类型化边界信任 TypeScript**:不要为静态接口已保证的输入加运行时校验/兜底/敌意输入测试;只在 parser/config、排队、模型/工具 JSON、持久化/文件、worker、进程、wire 边界校验。
156
- - **源码面 vs 产物面,永不混用**:静态门禁与测试通过 tsconfig `paths` 解析到 `src`,在干净树上通过;消费 `lib/` 的门禁必须显式声明该依赖。
157
- - **失败要响**:自包含的错误在加载时失败,否则在最早已可解析点失败,绝不静默跳过缺失的引用。
158
- - **switch 判别式 tag**:封闭联合以 `assertNever` 收尾;可合并扩展的联合走有文档的 default 分支。
159
- - **测试描述行为而非正确性**:行为过时就连同测试一起改,并在 PR 里说明原因。
160
- - **非平凡改动必须在同一个 PR 里带一篇 Agent Note**(仅机械/局部编辑豁免)。归档 note 冻结,不得编辑或当作当前权威。
161
- - **客户端 UI 文案归 locale 所有**:产品文案走类型化字典 + `t`/本地化 props,`verify-client-ui-i18n` 会拒绝硬编码文案。
162
- - **空 `catch` 要写明吞掉什么**、为什么别的到不了;`try` 只包一条语句。
163
- - 文件以恰好一个换行结尾(pre-commit 的 `git diff --cached --check` 把关)。
164
-
165
- ## 命令
166
-
167
- ```sh
168
- pnpm install # pnpm workspace;Node ^22.19 || >=24,pnpm 11.7.0
169
- pnpm run typecheck # 先跑完 Host lib 阶段,再 tsc Client
170
- pnpm run lint # oxlint(先 build:lib:host)
171
- pnpm run test # vitest 单测
172
- pnpm run test:coverage # CI 覆盖率门禁:packages/*/*/src 逐文件 100%
173
- pnpm run test:e2e # 真 API 测试;无 DEEPSEEK_API_KEY 自行跳过
174
- pnpm run test:expected # owner 本地进程期望输出
175
- pnpm run test:snapshot # 无密钥录制会话回放(-t <name> 过滤)
176
- pnpm run test:web # 浏览器快照(先 build)
177
- pnpm run build # tsc 产出 lib/types,tsdown 打包 runtime
178
- pnpm run hygiene # publint + workspace/包/依赖检查 + NodeNext 消费者检查
179
- pnpm run doc-sync # 全量文档门禁
180
- pnpm run test:docs # 快速文档检查(doc-quick)
181
- pnpm run check:all # 聚合门禁
182
- pnpm run duplication # 跨文件 TS 克隆检测(jscpd)
183
- pnpm dsh --profile headless "task" # 从源码跑一次真实任务(需 key)
184
- ```
185
-
186
- 改了代码之后选**覆盖该改动面**的最小检查,不要反射性地跑全量:行为测试、model/user 输出快照、文档用 `doc-sync`、发布路径用 built smoke、provider 用真 API e2e。CI 负责穷尽覆盖与平台矩阵。
187
-
188
- ## 测试分级与「什么时候必须有快照」
189
-
190
- - **Unit**(`pnpm run test`):vitest,spec 与被测代码同区;每个注册表要有 HMR 安全测试;偏好边界、错误路径、事件顺序、并发竞争、契约回归的永久测试。
191
- - **覆盖率门禁**(`test:coverage`):逐文件 100%。未覆盖的行往往是该删的死代码,而不是该补的测试。行覆盖必要但绝不充分。
192
- - **真 API e2e**(`test:e2e`):无 key 自跳;**不要省真 API 测试**——无 key 只证明管道通,有 key 才证明 agent 能用。最高价值是启动 shipped profile 的 smoke。
193
- - **快照**(`test:snapshot`):顶层 scenario 的最高父代 generation 提供用户输入与模型回放,并作为期望的持久结果。改模型 transcript 用 `test:snapshot:record`,输入仍有效用 `refresh`。
194
- - **Web 浏览器快照**(`test:web`):Chromium 比对 `snapshots/web/`;CI 强制 `DSH_SNAPSHOT=replay` 只读。
195
- - **任何非平凡、模型/协议/用户可见的改动,都要在同一个 PR 里新增或更新一个无密钥录制会话 scenario。** 包测试、e2e、mock-only 证据都不能替代组装后的 transcript。
196
- - agent-loop / session 生命周期 / `SessionEventMap` 改动要同步更新 **TypeScript 与 Python 两套 SDK 期望输出**。
197
- - **优先真实现,少用 mock**:只 mock 昂贵或不确定的边界(LLM adapter、网络、时钟),下游全部保持真实。
198
- - **验证世界,不验证自述**:e2e 断言要重新执行命令或重新读文件;对 agent 自身输出的关键字探测会让作弊的 agent 通过。
199
- - **测试真入口路径**:产品可见插件必须有非 unit 的 REAL 组合测试(通过 Loader 与 app/process 启动测试专用 `cordis.yml`)。
200
- - spec 在 fork worker 里并发执行:端口、路径、子进程都要自己负责到 teardown;「单独跑才过」的 spec 是 spec 的缺陷。
201
-
202
- ## 文档分层(一个事实只有一个家)
203
-
204
- | 层 | 职责 |
205
- |---|---|
206
- | root `AGENTS.md` | 常驻命令:每个 session 都要在上下文里的规则,每条 1-3 行并链到它的家 |
207
- | 子树 `AGENTS.md` | 该子树特有规则 |
208
- | `docs/architecture.md` | 有序地图:组合、核心包、loop、seam、扩展点 |
209
- | `docs/subsystems/` | 每子系统一页参考:类型定义、语义、生成的 Cordis API |
210
- | `.agents/notes/` | 决策记录:为什么、放弃了什么、需要什么验证 |
211
- | `docs/postmortem/` | 事故叙事(唯一允许 war story 的层) |
212
- | `docs/cookbook/` | 带编号验证步骤的 how-to |
213
- | 包 README | 该包的契约:配置、语义、限制、扩展点、Model Experience |
214
- | 生成参考(subsystems 的 `cordis-surface` 区、cordis-api、tool-catalog、config-catalog、persistence-catalog、module-graph) | 从源码生成、有新鲜度门禁;**不要手改** |
215
- | `.agents/skills/` | 可复用工作流与专业判断标准 |
216
-
217
- 放置规则:bug → postmortem;why → Agent Note;how-to → cookbook;类型 → subsystems;包契约 → README;常驻规则 → root `AGENTS.md` + 依据链接。
218
-
219
- 写作规则:**只写当前状态,不写变更史**(不出现 previously/now/no longer/PR 编号);**每段一个物理行**(`verify-md-wrap`);`ts` 代码块必须能编译(`doc-typecheck`);改了被文档化的类型,同一改动里更新 owning subsystems 页面;跨引用用相对 Markdown 路径,`verify-md-links` 会拒绝死链;JSDoc 写完整契约,不写推理过程。
220
-
221
- ## 怎么找「谁拥有 X」
222
-
223
- 1. `grep` 服务 key(`ctx.<name>`)→ 命中 `Service` 声明所在包 = 拥有方。
224
- 2. `docs/subsystems/` 里找同名端点 → 拿到类型与语义。
225
- 3. `.agents/notes/` 里搜关键词 → 拿到为什么这么设计、放弃了什么。
226
- 4. `packages/<group>/README.md` → 确认它属于哪个能力族、同族还有谁。
227
-
228
- ## 常见坑
229
-
230
- - 把 `interface` 当成 Service Definition(seam 的 Service Definition 必须是 Cordis `Service`)。
231
- - 在扩展插件里直接依赖具体 Provider,而不是 Service Definition。
232
- - waterfall 监听器忘记 `next()`,静默短路后续策略。
233
- - 让一个新的模型可见输入绕过 session event —— 破坏「可重建」不变量。
234
- - 在 preset 里裸发布服务(缺 `isolate` realm),mount 时被拒。
235
- - 改行为却不同步包 README / JSDoc / Agent Note / 快照。
236
- - 直接改 `vendor/` 或生成参考文档(手改会被门禁打回)。