# dsh-plugins

> 为 DeepSeek Harness 提供"伪 LLM"通道：用脚本让 agent 不调用真实模型，而是按 JSON/JSONL 剧本触发预设的工具调用，用于离线测试、教学演示与 CI 回归。

## Metadata

- Author: [@Ephemeral-AI-Lab](https://github.com/Ephemeral-AI-Lab)
- Repo: <https://github.com/Ephemeral-AI-Lab/dsh-plugins.git>
- GitHub: [Ephemeral-AI-Lab/dsh-plugins](https://github.com/Ephemeral-AI-Lab/dsh-plugins)
- Stars: 44
- Language: Python
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `dsh-plugin`, `dsh-plugin-market`, `dsh-plugins`
- Forks: 4
- Open Issues: 1
- Last push: 2026-08-20T20:04:56.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:Ephemeral-AI-Lab/dsh-plugins/mock
```

## Wiki

> 落地页对应的仓库子路径是 `mock`，npm 包名 `dsh-mock`（`mock/package.json:2-4`），是 `Ephemeral-AI-Lab/dsh-plugins` monorepo 下的子包之一。本百科聚焦 `mock` 路径对应的能力。

## 一句话定位
为 DeepSeek Harness 注册一个"伪 LLM"通道，让 agent 在不调用真实模型的前提下，按你写好的剧本触发预设的工具调用，用于离线复现、自动化测试与教学演示。

## 核心能力
- 注册内部专用的 provider/model `mock/mock`（`src/index.ts:132`）：被 AgentLoop 用来把"LLM 输出"换成预先编排的"工具调用流"，真实的模型选择器不显示它。
- 提供 `/mock` 斜杠命令（`src/index.ts:133-145`）：命令处理器只把整行原样塞回 session inbox 当作用户消息，让正常 AgentLoop 来执行——不绕过任何宿主逻辑。
- `run` 子命令：在输入框直接写 `tool({"k":"v"})` 触达单次工具调用，或写 `[a({...}),b({...})]` 触发一组并行调用（`src/parser.ts:71-108`）。
- `replay` 子命令：从本地脚本文件读取预先编排的剧本逐步重放（`src/parser.ts:111-132`），可选 `--overwrite-wait-time-ms N` 覆盖剧本里所有显式等待（`src/converter.ts:134-160`）。
- 同时支持两种 replay 源格式：canonical `dsh-mock-script` JSON 和原生的 DSH JSONL session 日志，自动识别转换（`src/converter.ts:172-251`、`src/index.ts:341-360`）。
- 实时把状态推给宿主 UI：在对话输入框上方渲染一个紧凑的 Mock status dock，展示当前第几步、总共几步、是否在等待工具回执（`src/client/index.ts:19-26`、`src/client/MockStatusRow.tsx:90-92`）。
- 状态经 `mockStatus` projection 暴露（`src/projection.ts:34-57`），失活或中止会回写为 `cancelled`，下次会话恢复时识别为过期。

## 技术实现
- **语言**: TypeScript（`tsconfig.json:3-21` 严格模式，目标 `ES2024` / `NodeNext`）
- **关键依赖**:
  - `@deepseek-ai/dsh-llm`（>=0.1.0-rc.5）：扩展 `LlmAdapter` 注册内部 `mock` 路由
  - `@deepseek-ai/commands`（>=0.1.0-rc.6）：用 `ctx.commands.register` 注册 `/mock`
  - `@deepseek-ai/cordis`（>=4.0.0）：宿主插件框架，监听 `agent/pre-step` / `agent/request` / `agent/error` / `agent/disposed` 等事件
  - `@deepseek-ai/dsh-session-projection`（>=0.1.0-rc.6）：注册 `mockStatus` projection 把状态折叠给客户端
  - `@deepseek-ai/dsh-client-ui-slots` + `@deepseek-ai/dsh-client-ui-conversation` + `@deepseek-ai/dsh-client-runtime`：把 MockStatusDock 注入到 `conversation.input.dock` 槽位
  - `react`（>=18.2.0）：状态行是 React 组件
  - `zod`（^4.4.3）：projection schema 强校验
- **架构模式**: Cordis 插件，`cordis.patch.yml` 同时禁用 `directory-picker` 并把 `mock` 注入到宿主插件目录（`cordis.patch.yml:1-9`）。`apply(ctx)` 注册一个 `MockAdapter` 给 `provider:'mock'`，再注册 `/mock` 命令并通过 `agent/pre-step` 拦截钩子把 `/mock …` 文本转成 plan 提交；adapter 内部按编译好的脚本逐步产出 `StreamChunk`，由宿主 AgentLoop 正常执行 tool 调用。
- **入口文件**: `mock/src/index.ts`（`export name = 'mock'`、`inject = ['llm','commands']`，`src/index.ts:29-30`），客户端入口为 `mock/src/client/index.ts`

## 适用场景
当你需要让 DSH agent 在不消耗真实模型配额的前提下，按一份固定剧本触发工具调用——比如把一段 DSH JSONL 录播回放成回归测试用例、给新人演示 agent 与工具的交互流程、给 CI 跑一段无网环境下的端到端验证——都可以装这个插件；只要能写出一份 canonical JSON 或现成的 session 日志，`/mock replay` 一行就能重跑。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| `@deepseek-ai/cordis` | `>=4.0.0` | peerDependencies，宿主插件框架 |
| `@deepseek-ai/dsh-llm` | `>=0.1.0-rc.5` | peerDependencies，注册 LLM adapter 必需 |
| `@deepseek-ai/dsh-commands` | `>=0.1.0-rc.6` | peerDependencies，注册 `/mock` 命令必需 |
| `@deepseek-ai/dsh-agent` | `>=0.1.0-rc.6` | devDependencies，运行时直接持有 `Agent` 实例 |
| `@deepseek-ai/dsh-session` + `@deepseek-ai/dsh-session-projection` | `>=0.1.0-rc.6` | peerDependencies，写 `mock/status` 事件并暴露 projection |
| `@deepseek-ai/dsh-client-runtime` + `@deepseek-ai/dsh-client-locale` + `@deepseek-ai/dsh-client-ui-conversation` + `@deepseek-ai/dsh-client-ui-slots` | 各 `>=0.1.0-rc.6` | peerDependencies，`package.json:27-35` 把这四个列在 `dsh.client.inject`，缺一 Web 端 dock 加载不到 |
| `react` | `>=18.2.0` | peerDependencies，状态行组件运行时 |
| `zod` | `^4.4.3` | devDependencies，projection schema 校验 |
| 平台 | DSH Web | `package.json:28` 显式声明 `dsh.client.platform: "web"`，命令行/桌面端未声明 |
| Node.js | 未声明 | `package.json` 无 `engines` 字段；devDependencies 中 `@types/node ^22.20.0` |
| 原生模块 | 无 | 仅依赖纯 JS 包，无 node-gyp 构建项 |

## 安装方式
```bash
dsh plugin --profile web add github:Ephemeral-AI-Lab/dsh-plugins/mock
```

## 配置项
本插件无需额外配置。它不读取任何环境变量或 `ctx.config`，也没有配置文件；唯一的"配置面"由 `/mock` 命令参数决定：

| 命令 / 参数 | 位置 | 类型 | 说明 | 默认值 |
|---|---|---|---|---|
| `/mock run <tool({...})>` 或 `/mock run [a({...}),b({...})]` | 输入框 | 一次或并行一组工具调用 | inline 触发；多工具必须放在 `[ ]` 里（`src/parser.ts:80-100`） | 无 |
| `/mock replay <path>` | 输入框 | 绝对路径或相对会话工作目录的脚本文件 | 读取脚本后按步重放；相对路径要求 session 当前 `header.cwd` 已设置（`src/index.ts:362-368`） | 无 |
| `--overwrite-wait-time-ms <N>` | replay 命令尾部 | 非负整数 | 覆盖剧本里所有显式 `wait` 步骤的毫秒数（`src/converter.ts:134-148`） | 不传则保留剧本里写的原始值 |

## 常见问题
**Q: 装完之后我能在模型下拉框里直接选 "mock" 吗？**

A: 不能。`mock` 路由被刻意从对外暴露的模型目录里隐藏（`README.md:21-28`），普通的模型选择器不会列出它；唯一激活方式是输入 `/mock run` 或 `/mock replay` 这类斜杠命令。

**Q: `/mock run` 和 `/mock replay` 的差别是什么？**

A: `run` 是 inline 触发——直接写一段 `tool({"k":"v"})` 或 `[a({...}),b({...})]` 让 agent 立即执行，session 内一次用完即弃；`replay` 是从本地脚本文件读一份预先编排的剧本逐步重放，适合固定场景的回归测试。两者内部都用同一个 `MockAdapter`，差别只在脚本来源（`src/index.ts:312-339`）。

**Q: replay 脚本支持哪些文件格式？**

A: 两种：(1) canonical `dsh-mock-script` JSON——`{"type":"dsh-mock-script","version":1,"steps":[…]}`，步骤可以是 `tool`、`parallel` 或 `wait`；(2) 原生 DSH JSONL session 日志——以 `{"type":"session"}` 开头，转换器会自动折叠出工具调用并组装成 canonical 脚本（`src/converter.ts:172-251`）。ZIP 压缩包直接传入会被显式拒绝，需要先解压拿出 `session.jsonl`（`src/replay-input.ts:9-17`）。

**Q: 用 mock 触发的工具调用，会被宿主正常记录吗？**

A: 会。MockAdapter 只负责产出 `StreamChunk`，工具查找、参数校验、授权、执行、结果回写和持久化全部走宿主 AgentLoop 与 ToolRuntime 流水线（`src/mock-adapter.ts:89-95`）；session 事件日志里出现的就是常规的 `tool/call`、`tool/result`、`turn` 等条目。

**Q: 一次 mock turn 结束后，下一会话会自动继续走真实模型吗？**

A: 会。`agent/request` 钩子会记录该 session 上一次用的真实 provider/model；mock turn 跑完后，下一次普通 turn 自动还原（`src/index.ts:217-247`），不需要手动切换。

**Q: 怎么卸载 mock？**

A: 用 `dsh plugin --profile web remove` 移除安装源即可。effect 回收时会调 `adapter.dispose()`、注销 `/mock` 命令与 llm adapter，并清空 session 维度的状态缓存（`src/index.ts:282-293`）。

## 上手难度
**进阶** — 入门用 `/mock run tool({...})` 触一次调用很简单，但要做有意义的 replay（写 canonical JSON 或导出 JSONL）需要先了解 DSH session 事件结构；并且需要理解 agent 内部 provider/model 切换的工作机制才能在出错时排查。

## 已知问题与限制
- v1 处于不稳定发布：`dsh-mock@0.1.1` 的命令、API 与 UI 都可能在稳定版前再变（`README.md:3-5`）。
- 直接把 ZIP 包当作 replay 源会被拒：`ReplayInputError(UNSUPPORTED_ARCHIVE)` 提示先解压出 `session.jsonl` 再传（`src/replay-input.ts:3-17`）。
- `replay` 模式显式拒绝嵌套/子 agent 工具：工具名匹配正则 `(?:^|[-_])(nested|subagent|agent[-_]spawn|spawn[-_]agent)(?:$|[-_])` 时直接抛 `UNSUPPORTED_NESTED_TOOL`（`src/index.ts:370-375`）。
- inline `/mock run` 不支持 `wait` 步骤——`wait` 语法只能在 canonical 脚本里使用（`src/parser.ts:92,103`）。
- 仅声明 `dsh.client.platform: "web"`（`package.json:28`），命令行 / 桌面端是否可用源码中未明确。
- canonical 脚本版本被钉死为 `1`，其它版本会被 `MockScriptError(INVALID_SCRIPT)` 拒绝（`src/converter.ts:84-85`）。
- MockAdapter 只产出 chunk、不做执行：如果宿主的 ToolRuntime 不认识 replay 脚本里写到的工具名，`tool/call` 事件虽然会出现但宿主侧会报"unknown tool"，mock 本身不会"凭空"伪造工具。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-plugins](https://deepseek-plugin.org/plugins/Ephemeral-AI-Lab/dsh-plugins/mock)
Wiki generated by AI (model: `MiniMax-M3`)
