# Dsh Ops

> Use when 运维/扩展 dsh (DeepSeek Harness)：安装配置、自定义插件、排障。

- Skill: `yyyyyhhhhh0639/dsh-ops` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add yyyyyhhhhh0639/dsh-ops`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yyyyyhhhhh0639/dsh-ops/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: yyyyyhhhhh0639 (https://skillmd.com/u/yyyyyhhhhh0639)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/yyyyyhhhhh0639/dsh-ops

---


# DeepSeek Harness (dsh) 运维与扩展

## When to Use

- 安装/启动/重启 dsh（DeepSeek Harness）服务，或排查其故障
- 配置 dsh 的模型 provider、搜索 provider、credentials
- 为 dsh 写自定义插件（尤其 web seam 搜索 provider）
- 在慢速/不稳定代理下 clone 大仓库
- 对照 dsh 与 Hermes 的 agent harness 架构差异

DeepSeek AI 的开源 agent harness（`npx @deepseek-ai/dsh web`，默认 http://127.0.0.1:3080）。架构信条：**everything is a plugin**，基于 vendored 的 Cordis 框架（`vendor/cordis/`，仅 9 个 TS 文件 2693 行）。本机部署于 `F:\AI\npm\dsh`（**版本锚定 0.1.1-rc.2**，2026-08-16 从 rc.6 升级；升级后以源码/README 重新核对），profile 在 `~/.dsh/profiles/web/`，源码 clone 于 `F:\AI\Hermes\Sessions\GitHub\deepseek-harness`。**rc.6→rc.2 已知差异**：`web_search` 参数 `query:string`→`queries:string[]`、credentials 事件改名 `credentials/reference-updated`、client UI 新增 `dsh.client.external` 字段（typert/patch/loader/pnpm link: 均无变化）。

## 安装与启动

- `npm install -g @deepseek-ai/dsh`；启动 `dsh web`（后台常驻），验证 `curl http://127.0.0.1:3080` 返回 200。
- **npm 语义边界**：`npm install -g` 只用于 **dsh 本体**安装/更新（官方路径，见 `dsh-install-update` 技能）；**profile 内（`~/.dsh/profiles/web`）严禁 npm install**——npm 会在 profile 树复制 @deepseek-ai 副本导致双实例崩溃、删 junction 时跟随清空全局树。profile 插件安装一律 `pnpm install` / `dsh plugin`（官方唯一入口，转发 pnpm）。
- **npm script-shell 坑**：本机 `.npmrc` 配置了 `script-shell = D:\Git\usr\bin\bash.exe`，npm 用 bash.exe 执行 lifecycle scripts 时崩溃（exit 0xC0000142，DLL 初始化失败）。对策：安装时临时覆盖 `--script-shell "C:\Windows\System32\cmd.exe"`，不要改全局配置。
- **端口残留**：kill 托管进程后 node 子进程常占 3080。`netstat -ano | grep ":3080" | grep LISTEN` 找 pid，git-bash 的 taskkill 参数转换有问题，用 `powershell.exe -NoProfile -Command "Stop-Process -Id <pid> -Force"`。

## 配置（模型与搜索）

- `~/.dsh/settings.yaml`：模型 provider 配置（`llm-pi-ai.providers.<name>` + `agent-default-model`）。**会话级模型选择优先于默认值**——改了 `agent-default-model` 后必须新建会话才生效。
- `~/.dsh/.credentials.yaml`：密钥存储（YAML 键值）。credentials-local 用 **chokidar watcher 观察文件**——外部编辑自动热重载整快照（支持轮转）；`resolve(ref)` 是同步内存查询（values Map + env 继承 + dotenv fallback），不是每次重读文件。key 解析走 `ctx.get('credentials')` seam。
- 本机模型：opencode-go provider（`OPENCODE_GO_API_KEY`），模型含 deepseek-v4-flash 等；搜索 key 已迁入：EXA/FIRECRAWL/TAVILY/PARALLEL。

## 架构速览（源码级事实）

> **技能机制差异（dsh vs Hermes）**：dsh 技能目录是**会话内动态替换**——技能文件变更 → Chokidar 失效目录 → `skills/change` → 下轮注入 `update: true` 完整新目录（"replaces every earlier available-skills list in this session"），**无需新会话即生效**；Hermes 的技能目录在系统提示中固化，需新会话。改 dsh 技能后当前会话即可验证。

| 概念 | 要点 |
|---|---|
| Cordis | 插件=Service 对象；Context 是 Proxy；`extend()/isolate()/intercept()` 创建作用域子上下文；事件 5 种派发（emit/parallel/serial/bail/waterfall）；注册是可逆 effect，卸载自动 unwind |
| Fiber | 插件实例；状态机 PENDING→LOADING→ACTIVE→UNLOADING→DISPOSED；`_refresh()` 把 inject 服务的 fiber uid 拼成 epoch，epoch 变化即重载——加载顺序完全由依赖声明驱动 |
| Session log | 追加式 `SessionEvent[]`，`seq = log.length` 连续；"Model-visible means logged" 运行时不变式；`deriveMessages()` 从日志派生模型历史（带缓存、deep-frozen） |
| Capability seam | 三角色：Service Definition + Provider + Consumer（模型面对的工具）。换 provider 即换整个行为 |
| web seam | `ctx.web`：search/fetch 两个命名空间，多 provider 注册但**单选路由**（`resolveProvider`：配置 id → 唯一可用 → AMBIGUOUS 抛错），**原生无 fallback** |

## 插件开发（核心机制）

详见 `references/plugin-dev.md`。关键点：

1. **loader 按行 import**：`cordis.patch.yml` 的一行 = 一个插件。`name` 支持裸包名或**相对路径**（`./plugins/xxx/index.js`，按 `ctx.baseUrl`=profile 目录解析）——自写插件无需 pnpm/node_modules，放 profile 目录下即可。
2. **patch 语法**：改已有行 = 同 `id` 匹配后**逐键覆盖**（config 整体替换、无深合并；未提及的键保留原值；name 若给出必须与现有行一致，否则 warn 跳过）；**插新行必须用 `- insert: [...]`**（直接加行会 `warn: patch: entry not found` 并静默跳过——插件不生效、无报错）。
3. **ESM 目录 import 坑**：`name` 写目录会 `ERR_UNSUPPORTED_DIR_IMPORT`——必须写完整文件路径 `./plugins/xxx/index.js`。
4. **typert 贡献者导出坑**（实证）：`exports["./typert"]` 指向的模块必须**具名导出 `TYPERT`**（`export const TYPERT = {...}` 或 `export { TYPERT }`）——typert-loader 用 `mod.TYPERT` 读取；只有 `export default TYPERT` 时 `mod.TYPERT === undefined` → `plugin tree failed to load: typert-loader ... has no TYPERT manifest object` → **整棵插件树加载失败、dsh web 起不来**（桌面端表现为启动报错）。manifest 要求：`package`=包名、`face:"host"`、`schemas` 数组（zod v4 实例）、`model.services/events/objects` 数组（均可空）。修复后必须重启 dsh 进程。
5. **WebSearchProvider 接口**：`{ id, available(), search(request, signal) }`。`available()` 是**同步无网络**检查，语义=配置完备性（官方 DeepSeek provider 检查 `apiKey` 或 `resolveApiKey` 存在），**不是值存在性**——key 在 credentials 里时同步查不到，误判 unavailable 会导致 `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`。
6. **credentials 获取**：用 `ctx.get('credentials')`（未注入返回 undefined，不抛错），然后 `await credentials.resolve(ref)` 取 `.value`。不要直接属性访问 `ctx.credentials`。
7. **UI 呈现链路**：工具结果 content → tool-web `searchMetaFromValue()` 映射为 `meta.answer` → `presentResult` 组装 `card: 'web'` 卡片 → client `WebBlock` 渲染（answer 在引用列表上方）。**自定义 provider 的 content 会自动出现在聊天页工具卡片里**（模型同见）。
8. **运行时 client UI 插件可行**：npm 安装版可挂载 client 插件——必须用包名挂载（profile package.json `file:` 依赖 + **`pnpm install`**；**严禁 npm install**：npm 会在 profile 树复制 @deepseek-ai 副本导致双实例崩溃，且删除 junction 时跟随清空全局树，见排障 Pitfalls 双实例条目），手写 `__ModuleLoader__` bundle（`dsh.client` 声明 + `exports["./client"]` + **`exports.inject` 声明用到的 ctx 服务**）。四个坑：目录名 → `ERR_UNSUPPORTED_DIR_IMPORT` 整站黑屏；exports 缺 `./package.json` → client-modules 静默跳过；**`__ModuleLoader__.load` 的 id 必须等于包名**（写成别名如加 `-ui` 后缀 → bundle 加载成功但注册失败：`loaded without registering via __ModuleLoader__.load`——mock 执行 factory 才能抓到）；**`dsh.client.inject` 声明决定模块表内容**——`require("react")`/primitives 能否解析由 inject 的包决定，缺声明 → factory 抛异常 → 同样静默不注册。**细节以项目技能 `dsh-client-ui-plugin-dev`（F:\AI\DeepSeek Harness\dsh-plugins-yyhuang\.dsh\skills\）为准**。验证：`__DSH_BOOT__.entries` 含包名 → `GET /plugins/<包名>/client.js` 200（不是 patch id，会 404 假阴性）。
9. **host 侧 userQuestions.ask() 在 web 模式要求 agent-owned session**（rc.2 实测）：从 `session/event` 回调触发 ask 时**必须传 `{ agent: ctx.agents.get(sessionId) }`**，否则报 `web user interaction requires an agent-owned session`；早期"不传 agent 可绕过 DELEGATED_CALLER 校验"的说法在 rc.2 已失效。
10. **agent.inject() 的 idle-pending 陷阱**：inject 只排队不唤醒——idle agent 的注入消息静默滞留直到下次 followup()/steer()。需要"立即执行"的场景用 **`agent.followup(msg)`**（唤醒并开新轮）；inject 适合"下一轮自然生效"的场景。
11. **消息级按钮挂点 = `conversation.chat.assistant-actions` 槽**：复制/分叉/点赞所在的 IconActions 行（官方 message-feedback 插件即此模式：Tooltip + primitives 图标 + 无边框 action 按钮）；chain/list 槽的 `select()` 返回 **match 数据或 null（decline），不要返回布尔 true**（不可靠）。turnTail 槽是另一挂点但渲染在 IconActions 之前、仅完结 turn。

## 官方文档锚点（插件管理权威依据）

> 以下结论来自官方文档全库检索（npm install 在插件场景零出现）与逐条核对，是本技能 npm/pnpm 纪律的官方依据。文档路径相对源码克隆 `F:\AI\Hermes\Sessions\GitHub\deepseek-harness\`。

- **`dsh plugin` 是官方唯一插件管理入口**：`apps/cli/README.md:14` —— "Manage a profile's plugins by **forwarding to pnpm** in the profile directory"（转发 pnpm，非 npm）
- **bundles 解析顺序**：`apps/cli/README.md:39` —— bundles 先从 dsh 安装本体解析（`@deepseek-ai/dsh-base` 等），再查 profile 的 node_modules（pnpm 装的 out-of-tree 插件）。两棵树职责分离，@deepseek-ai 永远只有本体一份
- **官方 manifest 示例用 `link:` 协议**：`docs/user/develop/basic/publish.md:85-101` —— "dsh-hello-plugin": "link:/path/to/hello-plugin"（本地插件官方示例即 link:，与双实例修复的 link: 指向全局同构）
- **无 `dsh.bundle` 声明 = 普通依赖**：`publish.md:64` —— 安装但 "activates no layer"（只作库，不作插件）
- **profile manifest 由 `dsh plugin` 维护**：`publish.md:73` —— "You never write a profile manifest by hand"
- **npm install 官方定位**：全库检索零出现于插件安装场景（仅构建/工具描述/sandbox 文档）——profile 内用它即脱离官方轨道

## 搜索 provider 生态与 fallback

- 官方包：`web-search-deepseek`（默认，需 DEEPSEEK_API_KEY，一次搜索=一次完整模型调用）、`web-search-exa`、`web-search-perplexity`。base bundle 默认 `searchProvider: deepseek-official` 且 `tool-web.fetch: false`。
- **fallback 聚合插件**（本机已部署，挂载为 `@yyhuang/web-search-fallback` 包名——源码在 `~/.dsh/profiles/web/plugins/web-search-fallback/index.js`，package.json `file:` 依赖 + patch 引用包名）：自写 WebSearchProvider，内部按优先级持有多个子 provider（exa→firecrawl→tavily→parallel），逐个尝试、失败记录并切换、全部失败抛 `WEB_PROVIDER_ERROR`；调用链格式化为多行 markdown 写进 result.content（显示为卡片 answer 区）。完整设计、4 个 API 格式、3 个实测修复见 `references/fallback-search-plugin.md`。
- Hermes 侧搜索（对照参考）：`web/ddgs` 插件（DuckDuckGo，免费无 key）是 Hermes 生效链路；原生 `web_search`/`web_extract` 未禁用（config.yaml 无 `disabled_toolsets` 键）；`.env` 里 4 个搜索 key（EXA/FIRECRAWL/TAVILY/PARALLEL）已配但 `web.search_backend` 为空。

## 代理下大仓 clone 恢复

本机 7898 socks5 代理连 GitHub **极不稳定**（TLS 频繁 `SSL_read: unexpected eof`），7890 仅主页可达（codeload/API 403）。12K commits 大仓策略：

1. `git clone --filter=blob:none`（blobless：元数据完整、blob 按需）**可能会 checkout 失败但元数据已完整**（`Clone succeeded, but checkout failed`）——不要重删重 clone！
2. 恢复：`git read-tree HEAD` 重建 index（纯本地，不触发网络）→ 循环 `git checkout-index --stdin`（每轮取缺失文件前 250 个 + `timeout 100` 防挂起）直到文件齐全。脚本模式见 `references/proxy-clone-recovery.md`。
3. 代理配置写**仓库局部** config（`git config --local http.proxy`），加 `http.version=HTTP/1.1` + `http.postBuffer=524288000` 降低断连率。

## 排障 Pitfalls

- **双实例 bug（两次事故后根治，详见 provenance）**：profile 装第三方插件（如 dsh-better-sidebar，peerDependencies 声明 16 个 @deepseek-ai 包）时，任何包管理器都会在 profile 树引入 @deepseek-ai 副本 → dsh 本体（全局树 F:\AI\npm）与插件（profile 树）加载**两个不同物理实例** → `kScope = Symbol("dsh.scope")` 不匹配 → 所有会话报 `agent-presets: refusing to compose an unscoped context`。**正确配置**：
  1. profile `package.json` 的 dependencies 显式声明 better-sidebar 的全部 peer 为 **`link:` 协议指向全局树**：`"@deepseek-ai/cordis": "link:F:/AI/npm/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/cordis"`（16 个 @deepseek-ai + cordis）
  2. `pnpm-workspace.yaml` **不要用 `nodeLinker: hoisted`**（会复制 file: 依赖）；用 pnpm 默认 isolated 模式（symlink）
  3. 只用 **pnpm** 管理 profile（`dsh plugin` 同源）；**严禁 npm install**（npm 的扁平复制 + junction 跟随删除会破坏全局树）
  4. **不要用手工 junction 代替 link:**（npm install 删除 junction 时会跟随链接删除全局树目录——实证：全局树 62 个包目录被清空）
  5. 验证三件套：`node -e "fs.realpathSync('.../node_modules/@deepseek-ai/cordis')"` 应指向 F:\AI\npm → 从 dshmarket 与全局 loader 视角 require cordis，`Context === Context` 应为 true → 重启 dsh 后新建+resume 会话
- **zod 版本分裂（双实例家族变体 3，2026-08-16 实测：typert 插件集体注册失败）**：新装插件（如 dsh-vision-router）带进传递依赖 zod@3.x，pnpm 将其 hoist 到 `.pnpm/node_modules/zod`；typert 插件的 `import('zod')` 从 store 位置向上解析时**先命中 hoisted 根**（任意版本），而非 profile 顶层 link:（4.4.3）→ typert-loader 报 `not backed by a zod v4 schema` → **整棵插件树加载失败、dsh 起不来**。**修复铁律**：所有 typert 插件的 package.json **必须显式声明** `"zod": "link:F:/AI/npm/node_modules/@deepseek-ai/dsh/node_modules/zod"`——让 pnpm 在插件自己 node_modules 建 4.4.3 链接，先于 hoisted 根命中；仅 profile 顶层 link: 不够（Node 向上解析先命中 hoisted 根）。**通用模式：新装插件可能改变已有插件的共享依赖解析路径——"插件没改却突然报错"时先查依赖树变化（`.pnpm/` 新条目 + package.json 最近修改时间）**。
- **改 host 插件代码后必须重启 dsh 进程**（web profile 的 `hmr` 插件行 `disabled: true`，Node ESM 缓存——实证）；**client.js 例外**（`dsh-client-hmr` 500ms 轮询自动热更新，无需重启）。
- **流程纪律**：涉及写代码/多文件改动时，先向用户给出方案 + 决策点再动手——用户会在"需求陈述被直接当成执行授权"时叫停（"先讨论方案啊"）。需求陈述 ≠ 执行授权。
- 搜索验证三件套：Node 直测 provider（key 有效性+响应解析）→ `dsh --profile web --dump-config`（确认插件行进树）→ 浏览器端到端（真实搜索看卡片）。
- `WebSearchResult.sources` 有上限（tool-web `searchMaxResults` 默认 8）；provider 超限由 seam 截断。
- **pnpm supply-chain 策略**：`minimumReleaseAgeExclude` 只接受**精确版本**（不支持 `1.3.x` 通配，会报 `ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE`）；新装包发布时间太新会拦 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`——加精确版本豁免即可。

## 参考文件

- `references/plugin-dev.md` — loader/patch/接口/呈现机制细节 + 官方源码引用点
- `references/fallback-search-plugin.md` — fallback 插件完整设计、4 API 格式、3 bug 修复记录、配置示例
- `references/proxy-clone-recovery.md` — 代理不稳定下 blobless clone 恢复脚本模式

