Bundle
dsh-plugin-debug-mode
DSH 插件的 Cursor-Agent 式 Debug 模式:以固定探针日志协议驱动 Agent 在代码库中主动埋点,把日志落到用户临时目录(不可写时回退 .dsh/debug-mode/),调试结束后由用户在弹出的面板中选择「问题已解决」或「问题复现,需要进一步修复」。
- Source
- rh666666
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 4 days ago
Readme
# dsh-plugin-debug-mode
`github:rh666666/dsh-plugin-debug-mode` · <https://github.com/rh666666/dsh-plugin-debug-mode>
> DSH(DeepSeek Harness)插件:**Cursor Agent 式的 Debug 模式**。
> 让 Agent 别猜——它自己在你的代码里埋探针,把现场取回来;你复现一次,然后二选一。
## 它解决什么问题
「点了保存之后列表偶尔不刷新。」
传统流程是 Agent 读代码 → 猜一个原因 → 改 → 你试 → 没好 → 再猜一轮。
Debug 模式把这件事反过来:**先在代码里装仪表,再让现象自己说话。**
```
你:现象描述
└─ Agent 分析 → 在可疑路径埋探针(每处都带可摘除标记)
└─ 探针把记录交给宿主,落进 <用户临时目录>/dsh-debug-mode/tasks/<hash>/<hash>.log
(临时目录不可写时回退到 <你的仓库>/.dsh/debug-mode/,回退会在面板上明说)
└─ 你复现一次
└─ Agent 通过「读取日志」拿到摘要(异常全量、分组计数、省略量显式)
└─ 面板弹出,你选:
[ 问题已解决 ] → 删日志 + 删掉本次全部探针代码
[ 问题复现,需要进一步修复 ] → 日志**保留**(Agent 还没读它)
```
**整个流程不需要 Agent 运行任何命令。** 它的动作只有两种:给调试服务发信号
(`dsh_debug_*` 工具)+ 在你的源代码里埋点/改代码。**"跑起来复现"是你的事。**
这不是洁癖——有一次真实调试里,工作区所在的 `D:\` 归 `SYSTEM` 所有、当前账号缺
「更改权限」,于是**所有 shell 命令**在执行前的授权阶段就被系统拒绝了;
而同一时刻编辑文件完全正常。**能挡住整个流程的,恰恰是流程本来就不需要的那一步**,
所以现在这条被写成了硬约束。
## 怎么进入
**只有一个入口**:斜杠命令。
```
/debug 保存之后列表偶尔不刷新,十几分钟出现一次
```
**敲完回车就开始了**:这一条命令会连同你的描述**直接开一轮对话**,Agent 立刻开始分析,
不需要你再补一句「开始」。描述没写全也没关系——Agent 会先问你现象、期望与复现路径。
不识别对话意图、不做工具栏按钮。埋探针是在改你的代码库,这个动作必须由你显式发起,
不能由模型推断出来。`dsh_debug_start` 工具**必须持有 `/debug` 签发的票据**才放行——
无票调用一律被宿主拒绝(这是硬校验,不是提示词里的一句叮嘱)。
## 四个信号
| 信号 | 谁触发 | 做什么 |
| --- | --- | --- |
| **启动调试** | `/debug` 之后由 Agent 调用 | 首次进入调试模式;生成唯一哈希,创建日志、探针桩与现成代码片段。同一任务的哈希此后不变 |
| **汇报埋点** | Agent | 交卷:记录分析结论与两份清单,然后把球交回给你复现 |
| **进一步调试** | Agent | 开下一轮:**这时才清空**日志(哈希不变)。注意顺序——用户点「复现」时日志是**保留**的,Agent 必须先将 `dsh_debug_read` 读它,再 `continue` |
| **读取日志** | Agent | 把日志内容以摘要形式输出给 Agent,无需 Agent 自己去翻文件 |
| **结束调试** | Agent(由用户「已解决」驱动) | 删除任务目录,并移除本次任务埋下的**全部**探针与临时块 |
## 调试期间的代码改动
调试不只是「埋点 → 读日志」——**改代码本身也是调试手段**,但轮次有讲究:
- **第 1 轮默认不改代码**,只取证。没看到数据之前猜的修法一旦改掉原始行为,
后面的日志就再也证不出真相了。(闸门只在出现 `dsh-debug:change` 标记时拒绝——
埋探针本身不算改行为。)
- **第 2 轮起可以「先按假设改,再埋点验证」**,但改完必须再埋点,用日志证明
「改之前复现、改之后不复现」。
- 探针和修复**分开记账**:探针用 `dsh-debug:probe <hash>` 包裹,临时代码用
`dsh-debug:temp`,改动用 `dsh-debug:change <hash>` 标记。收尾时**只删探针与临时块,
绝不回滚你的修复**——整体 `git checkout` 回滚是被明令禁止的收尾方式。
## 产物放在哪
调试产物默认落**用户临时目录**(`os.tmpdir()/dsh-debug-mode/tasks/<hash>/`),
和你的工作区分开——所以它不会跟着 `git status`、不会被编辑器索引、也不会被当成项目文件;
**每次任务结束都随任务一起删除**。
- 临时目录不可写时(只读沙箱、受限账号等)自动回退到 `<你的仓库>/.dsh/debug-mode/`,
并且**会显示提示**——回退同时写进任务的备注、待办清单与 `dsh_debug_start` 的返回正文,
不会让你事后发现"日志不见了"。
- 环境变量 `DSH_DEBUG_MODE_TMP` 可以把临时根指到别处(工具参数 `tempDir` 优先级更高)。
- 代价要说清楚:因为用的是**系统临时目录**,系统清理临时文件时,**在途任务**的产物可能
被删掉。那会让这个任务没法继续(日志没了、桩也没了),需要重新 `/debug` 开一个。
## 埋点入口:任何能发 HTTP 的语言都一样
**写日志这件事只有宿主一份实现。** 探针不需要自己格式化时间戳、自己截断、自己数上限、
自己保证字段序——它只要把一条 JSON `POST` 过去:
```bash
# GET 是自检:先确认通道活着,再让用户去复现
curl -s 'http://127.0.0.1:<DSH 端口>/dsh-debug-mode/probe?h=<hash>'
# POST 写一条(只有 probe 是必填)
curl -s -X POST 'http://127.0.0.1:<DSH 端口>/dsh-debug-mode/probe?h=<hash>' \
-H 'content-type: text/plain' \
--data-binary '{"probe":"app.py:42:enter","data":{"order_id":"A1"}}'
```
**路径是固定的**:`/dsh-debug-mode/probe`,任务哈希走查询串 `?h=`。
宿主地址与端口由插件自己算出来(`DSH_WEB_URL`,回退到 `webServer.port`),
所以**你不需要告诉 Agent 地址**——它拿到的就是一条可以直接用的完整 URL。
只有目标不在本机时(容器里、另一台机器上)才需要把 `127.0.0.1` 换成可达地址,
**路径与哈希一个字都不要动**。
`dsh_debug_start` 还会把**端点与哈希已经填好**的现成代码片段生成到任务目录下的
`runtime/probe.http.md`(绝对路径在 `dsh_debug_start` 的返回里给出),覆盖 curl / Python /
Node / Ruby / Go / PHP / PowerShell / Java —— 复制粘贴就能用。
这条通道带来的两件事:
- **语言无关**:字段序、带时区的时间戳、8 KiB 单行截断、16 MiB 总量上限、并发追加,
全部由宿主强制。所以不存在"某个语言的桩把协议实现错了"这种失败模式。
- **可自检**:`GET` 一眼看出通道通不通。投递是尽力而为的(探针不许拖慢被调试的程序),
所以"通道是死的"与"埋点没执行到"的现象本来一模一样——现在它们可区分。
两条桩还会在**加载时**自己写一条 `debug-mode:ready`:
- 看得到 `ready` ⇒ 桩是通的 ⇒ 日志里没有你的探针 = **那段代码没走到**;
- 看不到 `ready` ⇒ 桩根本没被加载 ⇒ 去查 import 路径 / `<script src>` / 进程重启过没有。
### 什么时候不走 HTTP
| 场合 | 用什么 |
| --- | --- |
| 热路径(每帧 / 每请求上千次) | HTTP + 调用方聚合(每 N 次或每 X ms 一条) |
| 进程可能**硬崩**,要求"崩溃前最后几条必须落盘" | Node 文件桩(同步追加,不缓冲) |
| 运行时没有网络栈 | 按指南 §六 自写文件桩 |
### 浏览器页面
如果被调试的是**在浏览器里打开的 HTML 页面**(尤其是 `file://` 直接打开的静态页),
用宿主给的**经典脚本桩**,把记录 POST 回宿主(走 DSH 自己的 HTTP 载体,
所以你**不需要**为了调试去起一个本地服务器):
```html
<script src="http://127.0.0.1:<DSH 端口>/dsh-debug-mode/stub.js?h=<hash>"></script>
<script>
dshProbe('buggy-demo.html:42:onclick', { list: todos.length })
</script>
```
桩由**宿主即时伺服**(`GET /dsh-debug-mode/stub.js?h=<hash>`),所以页面里没有一行指向
某个本地文件——**经典 `<script src>` 不受 CORS 限制**,这条通道对 `file://` 打开的页面同样
能用,你**不需要**为了调试去起一个本地服务器。若页面自己的 CSP 只允许 `'self'`,把宿主
origin 加进 `script-src`,或者把落盘副本(`dsh_debug_start` 返回的
`runtime/probe.browser.js`)的内容整段粘进页面里。
记录会按**同一份行协议**落进任务日志(`task` 由宿主写死,字段序只有一处真相)。
## 面板:输入框上方那张可折叠的卡片
从任务创建到任务结束,**输入框正上方**常驻一张小卡片。它在文档流里(不是浮层),
所以**不会挤动输入框、也不可能挡住它**;而且默认是**收起来**的——
只有轮到你做决定时才自动展开:
```
▸ 调试模式 · #deadbeef · 第 2 轮 · Agent 正在埋点/排查 312 行 · 48 KB
```
轮到你了,它自己展开,并且只说两件事——**结论一句话** + **你要做什么**:
```
▾ 调试模式 · #deadbeef · 第 2 轮 · 轮到你复现并选择 312 行 · 48 KB
total 在 items 为空时算成了 null
请按这个顺序复现:
1. 打开保存对话框
2. 清空标题后点保存
3. 回到列表页
也可以不用按钮:直接在输入框里描述现象,同样算「还在」。
[ 问题已解决 ] [ 问题复现,需要进一步修复 ]
```
- 点头部那一行即可折叠/展开;**折叠之后那一行仍然说清现在轮到谁**。
- 那份复现清单是 Agent 交卷时给出来的——过去它不存在,你面对"请复现"只能凭记忆
猜一遍当初怎么触发的。
- **只有等你在面板上做过选择,Agent 才被允许收尾**(`dsh_debug_end` 会校验这一点)。
- **输入框始终可用**:想补充「只在切换标签页之后才复现」这种信息,直接打字就行。
输入框里有没发出去的内容时,两个按钮会自动禁用——免得同一条描述被送两遍。
- 宿主扫不动你的工作区时(只读沙箱等),面板会直接说**「残留检测未生效」**,
而不是假装还在保护你。
## 收尾会发生什么
选择「问题已解决」之后,宿主会:
1. 按 `dsh-debug:probe <hash>` 与 `dsh-debug:temp <hash>` 标记**逐块摘除**探针与
临时探索代码(摘之前先备份到任务目录的 `backups/`,但**绝不自动还原**);
2. 删除任务目录(日志、状态、探针桩一起消失);
3. 在回复里给出三类账:**修复(保留)/ 探针(已删)/ 临时改动(已还原)**。
两件它**不会**做的事:
- **不会回滚你的修复。** `git checkout` / `git stash` 整体回滚是明令禁止的收尾方式。
- **不会静默保留。** 如果扫到"既不是本次探针、也不能确认是修复"的残留
(陌生哈希、残缺标记、配对有歧义),收尾会**暂停一次**,把清单交回来,
Agent 会逐条问你三选一:保留 / 删除(先看 diff)/ 我自己处理。
## 另外:Agent 可以不进调试
`/debug` 是你发起的,但它不是契约。如果 Agent 读完代码就看出根因(空指针、
明显拼写错误、缺一次 `await`……),它应当**直接告诉你根因并改代码**,
而不是为了走流程先埋一圈探针。票据会自己过期,你下次想调试时重新 `/debug` 即可。
## 状态
**v0.19 · M0–M6 + R1–R6 + HTTP 探针通道全部落地,全部测试全绿**:
`/debug` 与票据闸门、5 个信号工具、日志管道与摘要、唤醒闭环、交互面板、收尾对账、
防堆屎山机制(R1 工作区索引 + 窗口 diff、残留扫描与隔离区、停止规则、欠账看板)
全部就位;浏览器/远程运行时的 HTTP 探针通道也已补齐,**整条流程不依赖任何命令**。
唯一还没做的是**人在真实 GUI 里点一次面板**——那一步只有你能做。
先读 [CHANGELOG.md](./CHANGELOG.md)(只追加的变更记录)与
[AGENTS.md](./AGENTS.md)(唯一权威入口:契约、纪律、被推翻的机制都在那儿)。
## 安装
**从 GitHub 直装**(仓库已推送;npm 上目前还没有这个包名):
```bash
dsh plugin --profile web add github:rh666666/dsh-plugin-debug-mode
```
另一种写法(标准 git 规范,不依赖 `github:` 简写):
```bash
dsh plugin --profile web add git+https://github.com/rh666666/dsh-plugin-debug-mode.git
```
GitHub 拉不动时,克隆到本地再挂载:
```bash
git clone https://github.com/rh666666/dsh-plugin-debug-mode.git
dsh plugin --profile web add link:<克隆下来的目录绝对路径>
```
**安装期不需要构建,也不需要 `node_modules`**:本插件零运行时依赖,而且
`lib/client.js` 是**提交进 git 的生成物**——仓库里已经带着它,所以 git 直装与
`link:` 本地挂载拿到的东西是一样的,不必先跑构建脚本(只有你改了 `src/client.js`
才需要重跑 `scripts/build-client.mjs`,见下面的「开发」)。
装完在任意会话里敲 `/debug <现象描述>` 即可——命令会自动出现在宿主的命令面板里。
## 开发
```bash
node scripts/build-client.mjs # 生成 lib/client.js(改了 src/client.js 必须重跑)
node --test test/*.test.mjs # 全部单测(零依赖,会先自动重建客户端产物)
```
零运行时依赖:插件本体只用 Node 内置模块,因为探针桩会被复制进**你的**项目里执行。
浏览器半也只有一份手写的 `src/client.js`,由一个 30 行的构建脚本包成 DSH 要求的
`window.__ModuleLoader__.load({ id, factory })` 形式——**没有打包器,也没有 node_modules**。
## 设计要点
- 日志是 **JSONL**,字段序固定(`ts, task, probe, level, msg, data`)——
追加写天然多进程安全,崩溃最多丢半行,`grep` 依然好使。
- 探针桩**永不抛异常、不持有 fd**:可观测性代码不许把被调试的程序搞挂。
单任务日志到 16 MiB 就**停止追加**(并写一条 `debug-mode:overflow`),
免得一个跑飞的循环把磁盘写满。
- 探针代码用 `dsh-debug:probe <hash> start/end` 包裹,**可被机器整块摘除**;
临时探索用 `dsh-debug:temp`,修复用 `dsh-debug:change`。收尾时宿主会拿
「工作区 diff 扫到的东西」和「你自报的清单」对账并点名漏网项——
**以扫描为准,不靠 Agent 自诚实**。
- 调试日志**永不长期保存**:任务关闭即删除,`.dsh/` 自动 gitignore。
- 面板常驻在**输入框上方**(`conversation.input.dock`,文档流兄弟节点,
不会被浮层遮住输入框),按 `sessionId` 归属,不会串到别的会话去;
默认折叠,只在轮到你复现时自动展开。
- **调试期间输入框始终可用**:按钮只能表达「还在 / 好了」,而「只在切换标签页之后才复现」
这种信息只有打字能说清。输入框里有没发出去的内容时,面板按钮会自动禁用,
免得同一条描述被送两遍。
- **闸门不会把人卡死**:任何硬闸门连续拒绝同一原因 3 次后自动降级为警告并把降级
写进面板;闸门依赖的扫描能力不可用时,面板直接显示「残留检测未生效」。
## License
MIT
Install
dsh plugin --profile web add github:rh666666/dsh-plugin-debug-mode
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-plugin-debug-mode from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.