@huanlin/dsh-plugin-better-plan 0.4.0
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.
- package/README.md +141 -0
- package/cordis.patch.yml +27 -0
- package/lib/client.js +869 -0
- package/lib/index.js +1095 -0
- package/lib/types/client/PlanView.d.ts +35 -0
- package/lib/types/client/execution-launch.d.ts +65 -0
- package/lib/types/client/icons.d.ts +16 -0
- package/lib/types/client/index.d.ts +56 -0
- package/lib/types/client/locales.d.ts +94 -0
- package/lib/types/client/markdown-props.d.ts +44 -0
- package/lib/types/client/review-store.d.ts +76 -0
- package/lib/types/config.d.ts +53 -0
- package/lib/types/context.d.ts +80 -0
- package/lib/types/delivery-registry.d.ts +59 -0
- package/lib/types/first-heading.d.ts +25 -0
- package/lib/types/index.d.ts +74 -0
- package/lib/types/locale.d.ts +112 -0
- package/lib/types/plan-fold.d.ts +17 -0
- package/lib/types/prompt-override.d.ts +62 -0
- package/lib/types/resolve-cwd.d.ts +41 -0
- package/lib/types/review-gate.d.ts +107 -0
- package/lib/types/review-route.d.ts +90 -0
- package/lib/types/shadow-tool.d.ts +93 -0
- package/lib/types/trust-fence.d.ts +25 -0
- package/lib/types/ws-route.d.ts +58 -0
- package/package.json +116 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://dshfind.com/zh/plugins/huanlinoto/dsh-plugin-better-plan"><img src="https://dshfind.com/api/card/huanlinoto/dsh-plugin-better-plan?lang=zh" alt="dsh-plugin-better-plan card"></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# dsh-plugin-better-plan
|
|
6
|
+
|
|
7
|
+
一个取代 DSH 内置 plan mode「计划交付体验」的插件:计划交付不再把全文渲染进聊天悬浮卡片,而是**先写成 markdown 文件,再经 better-sidebar 的专属「计划」面板展示,并在侧边栏完成审批**——计划送达侧边栏后对话直接停下,聊天里不弹审批卡;用户在 Plan 面板审阅全文后点 Approve / Execute in new chat / Keep planning 按钮。
|
|
8
|
+
|
|
9
|
+
> Replaces the built-in plan mode's plan delivery: plans are written to a markdown file first and shown in the better-sidebar **Plan panel**, where the approval happens — a delivered plan parks the conversation with no popup in chat; the user reviews the full plan in the panel and clicks Approve / Execute in new chat / Keep planning there.
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
- **文件先行**:内置 `exit_plan_mode` 的参数是完整计划正文;本插件以**同名工具逐 agent 遮蔽替换**(`agent/session-start` → `agent.ctx.tools.register`,跨 scope 层遮蔽 preset 挂载的原版),新契约只有一个 `path` 参数——模型先用 `write` 工具把完整计划写成 markdown,再传路径。
|
|
14
|
+
- **侧边栏展示**:工具读取计划文件,经自有 `/better-plan/ws/delivery` WebSocket 推送到会话的 Plan tab(`order: 15`,single 单实例);正文用 DSH `MarkdownText` 渲染(双代 chrome labels prop 通吃 0.1.1-rc.x / 0.1.2-alpha.1+)。
|
|
15
|
+
- **侧边栏审批(送达时无聊天弹窗、对话直接停止)**:推送送达已连接的侧边栏视图后,工具**立即返回**「计划已呈现,请结束回合」——模型收尾结束对话,聊天里既没有审批卡也没有挂起的调用卡片。用户在 Plan 面板的动作栏审阅全文后点 **Approve** / **新开对话执行** / **Keep planning**(可附反馈文本),决定经 `POST /better-plan/api/review` 回到宿主端:批准 → 立即落 `plan/mode: false` 并以 `agent.steer` 开启新回合让模型开始执行;保留 → 反馈经 steer 开新回合回给模型修改。审批语义与模式切换语义与原版一致。
|
|
16
|
+
- **新开对话执行(delegation)**:第三个决定 `approve_new_session` 结算为 `delegated`——规划会话照常退出计划模式,但 steer 文案改为「计划移交新对话执行,本会话收尾即可」;同时面板经 DSH 公开 `ctx.sessions` 契约在**同一工作目录**新建空白会话,把 kickoff(指向计划文件的绝对路径 + 执行指令)作为首条用户消息排队发出并导航过去。新对话与用户手点「新建会话」完全同构(默认 preset),失败时状态行下就地报错、计划路径仍在面板可见,可手动重试。`ctx.sessions` 服务缺席时按钮不渲染;无侧边栏的聊天弹窗降级路径保持原版两选项。
|
|
17
|
+
- **提示词面对齐**:preset 的 `plan:policy` 提示段仍是原版措辞(内联传正文 + 禁止写文件,且宣称压过工具描述),与本插件的工具契约直接冲突。本插件注册 `system-prompt/assemble` waterfall 监听器(逐 agent,挂在 agent scope 上——assemble 派发 key 就是 agent),把该段四处原版契约句子就地改写为文件先行契约:交付句改写为**同回合两步强制交付**(先写 `docs/plans/YYYY-MM-DD-<topic>.md`——日期 + 主题短横线命名,再立即调 `exit_plan_mode`);原版「exit_plan_mode 是该回合唯一且最后的工具调用」句同步改写(否则模型把已发生的 write 视作违反该句,写完文件就停);写禁句与规则压治句各带豁免。锚点句子只在计划模式激活时出现,无需自管状态。
|
|
18
|
+
- **无侧边栏降级**:推送未送达(better-sidebar 未安装或面板视图未连接)时,回退为**原版阻塞审批卡**——`userQuestions.ask` + `plan-review` 意图在聊天里渲染(detail 是完整计划全文),批准后原样在回合内继续。
|
|
19
|
+
- **审批状态双通道**:WS `review` 帧实时广播(多窗口同步、attach 回放恢复刷新);Plan 面板挂载时再经 `GET /better-plan/api/review` 引导拉取一次(防丢帧;store 已有活动状态时让位,不会误清)。
|
|
20
|
+
- **i18n(跟随 DSH 语言,zh/en)**:面向用户的文案双语——侧边栏 Plan 面板与 tab 标题注册进 DSH 共享 locale 注册表(命名空间 `betterPlan`),跟随 Host-backed 语言偏好实时切换;宿主端文案(交付结果的 render 文本、steer 消息、无侧边栏审批弹窗)按会话解析 locale——config `locale` 覆盖 → 连接视图上报的 locale(WS connect 查询参数 + 审批请求携带浏览器当前 DSH 语言)→ 英文。**模型契约文本保持英文**(工具 description、`plan:policy` 改写、execute 错误指引)。审批路由的错误响应带稳定 `code`,面板按 code 映射本地化文案,未知 code 回退原文。
|
|
21
|
+
- **刷新可恢复**:计划路径随 tab `meta` 进 better-sidebar 的 localStorage 持久化,刷新后 Plan 面板按 `meta.path` 重读文件。
|
|
22
|
+
|
|
23
|
+
## 开发
|
|
24
|
+
|
|
25
|
+
### 仓库结构
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
src/
|
|
29
|
+
├── index.ts # host 入口: name/inject/Config/apply + 遮蔽挂接 + pre-step flush + WS/HTTP 路由
|
|
30
|
+
├── config.ts # Config schema (Schemastery, strict) + resolveBetterPlanConfig
|
|
31
|
+
├── context.ts # 插件视角的 Context face(cordis Context ∩ 结构化服务面)
|
|
32
|
+
├── shadow-tool.ts # 同名 exit_plan_mode 工具定义(execute / render / presentCall / presentResult)
|
|
33
|
+
├── review-gate.ts # 审批门:per-session 停靠 + decide(approve/keep/approve_new_session → delegated)+ attach 回放/abort/dispose 结算
|
|
34
|
+
├── review-route.ts # POST /better-plan/api/review(信任栅栏 + 体校验 + 陈旧 id 守卫)
|
|
35
|
+
├── delivery-registry.ts # per-session 推送队列 + 视图 attach(consume-on-send,队列上限 8)
|
|
36
|
+
├── ws-route.ts # /better-plan/ws/delivery 升级路由 + tagged 帧(deliver/review)+ socket attach
|
|
37
|
+
├── trust-fence.ts # Host 回环 / trustedHosts 浏览器信任栅栏(对齐 /api 网关语义)
|
|
38
|
+
├── locale.ts # locale 词表 + 会话级解析链(config → 视图上报 → en)+ 宿主端双语文案
|
|
39
|
+
├── resolve-cwd.ts # 会话 cwd 解析链(header → persistence → process.cwd)
|
|
40
|
+
├── first-heading.ts # 计划首 heading 提取(原版同款正则)+ basename
|
|
41
|
+
└── client/
|
|
42
|
+
├── index.tsx # client 入口: Plan tab 注册 + locale 词典注册 + 交付 WS 订阅 + tagged 帧分发
|
|
43
|
+
├── PlanView.tsx # 面板组件:状态头 + 审批动作栏(三决定,delegation 按能力渲染)+ MarkdownText 正文 + 加载/错误/重试
|
|
44
|
+
├── locales.ts # 面板双语词典(ctx.locale 注册 + 模块级 t())+ 路由错误 code 映射 + delegation kickoff 文案
|
|
45
|
+
├── review-store.ts # 审批状态外部 store + submitReviewDecision(POST 回宿主端,携带 locale)
|
|
46
|
+
├── execution-launch.ts # delegation 流:ctx.sessions 结构化 face → 新建会话 + kickoff 排队 + 导航
|
|
47
|
+
├── markdown-props.ts # 双形状 MarkdownText props(内联,不得 value-import better-sidebar 内部)
|
|
48
|
+
└── icons.tsx # 内联 SVG 图标
|
|
49
|
+
tests/
|
|
50
|
+
├── composition.spec.ts # 组合层:真实 ToolRuntime/AgentRegistry/UserQuestionService 驱动 execute 全分支(含侧边栏审批流)
|
|
51
|
+
├── locale.spec.ts # locale 目录/解析链/双语文案
|
|
52
|
+
├── shadow-tool.spec.ts # 工具契约:description / 紧凑卡投影 / render 双分支 / finalizeContent 本地化
|
|
53
|
+
├── review-gate.spec.ts / review-route.spec.ts
|
|
54
|
+
├── delivery-registry.spec.ts / ws-route.spec.ts / trust-fence.spec.ts / resolve-cwd.spec.ts / first-heading.spec.ts
|
|
55
|
+
└── client/
|
|
56
|
+
├── delivery.spec.ts # WS tagged 帧 → openTab/updateTab/activateTab + review store 喂给
|
|
57
|
+
├── plan-view.spec.tsx # PlanView 加载/错误/重试 + 审批动作栏 + delegation 流 (jsdom)
|
|
58
|
+
└── markdown-props.spec.ts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 前置依赖
|
|
62
|
+
|
|
63
|
+
- Node.js >= 22、pnpm
|
|
64
|
+
- 本机 `~/.dsh/source/current` 指向 DSH 源码 checkout(0.1.2-alpha.1+,已构建)
|
|
65
|
+
|
|
66
|
+
### 三件套
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pnpm install
|
|
70
|
+
# 建链 @deepseek-ai/* 与 dsh-better-sidebar 的 node_modules junction(见 AGENTS.md「环境搭建」)
|
|
71
|
+
pnpm run typecheck # tsc --noEmit (host 面 + client 面)
|
|
72
|
+
pnpm test # vitest run
|
|
73
|
+
pnpm run build # tsdown 双 bundle + tsc 类型产物 → lib/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 构建策略
|
|
77
|
+
|
|
78
|
+
**预构建 `lib/` 入库**(不含 `prepare` 脚本)。client 半部依赖 `@deepseek-ai/dsh-client-ui-primitives` 等 private 包,pnpm 在 git install 的 `prepare` 阶段会在临时目录拉不到这些包,所以 `lib/` 必须预构建并提交。改源码后需 `pnpm run build` + commit `lib/`。
|
|
79
|
+
|
|
80
|
+
## 运行
|
|
81
|
+
|
|
82
|
+
### 安装到 profile
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
# 本地开发(热更新)
|
|
86
|
+
dsh plugin --profile web add "link:D:/Projects/deepseek-harness/dsh-plugin-better-plan"
|
|
87
|
+
|
|
88
|
+
# 从 GitHub 安装(预构建 lib/,开箱即用)
|
|
89
|
+
dsh plugin --profile web add "github:huanlinoto/dsh-plugin-better-plan"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
安装后重启 `dsh web`,浏览器硬刷新(`Ctrl+Shift+R`)。前置:profile 内安装 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(v0.12.0+;未安装时插件照常工作,走全文降级路径)。
|
|
93
|
+
|
|
94
|
+
### 配置(cordis.patch.yml 插件行 config)
|
|
95
|
+
|
|
96
|
+
| 字段 | 类型/默认 | 说明 |
|
|
97
|
+
|------|-----------|------|
|
|
98
|
+
| `planDir` | `string = 'docs/plans'` | 工具 description 中建议的计划目录 |
|
|
99
|
+
| `maxPlanBytes` | `number = 262144` | 计划文件读取上限;超限拒绝并提示精简 |
|
|
100
|
+
| `locale` | `'auto' \| 'zh' \| 'en' = 'auto'` | 宿主端用户可见文案的语言:`auto` 跟随连接视图上报的 locale(浏览器当前 DSH 语言),`zh`/`en` 强制指定;模型契约文本不受影响 |
|
|
101
|
+
|
|
102
|
+
不注入额外系统提示段——新契约全部由工具 description 携带(`plan:policy` 段保持原版原文,`exit_plan_mode` 名称未变故其陈述依然为真)。
|
|
103
|
+
|
|
104
|
+
## 检查
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
pnpm run typecheck # 类型门禁(host + client 两个 tsc 面)
|
|
108
|
+
pnpm test # 132 个单元/组合/组件测试
|
|
109
|
+
pnpm run build # 产物: lib/index.js, lib/client.js (+ lib/types/*.d.ts)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 合规自检
|
|
113
|
+
|
|
114
|
+
- [x] 零源码 patch:未修改 DSH checkout 任何文件(内置 plan-mode 行未触碰)
|
|
115
|
+
- [x] B1: `package.json` 声明 `dsh.bundle.patch`
|
|
116
|
+
- [x] B2: 自带 `cordis.patch.yml`(insert 行 id `better-plan` / name 用包名 / config 默认值齐全)
|
|
117
|
+
- [x] B3: patch 行 `name` 用包名(Loader 从 profile node_modules 解析)
|
|
118
|
+
- [x] F1: `files` 含 `lib/` + `cordis.patch.yml`
|
|
119
|
+
- [x] F2: `peerDependencies` 含 `@deepseek-ai/cordis` + 用到的 `@deepseek-ai/*`(全部 optional;市场约束的裸名 `cordis` 未出现)
|
|
120
|
+
- [x] F3: typecheck/test/build 三 script 齐全(无 `prepare`,预构建策略)
|
|
121
|
+
- [x] A4: Config 用 Schemastery `z.object`(strict,未知 key 拒绝)
|
|
122
|
+
- [x] A5: 部署可变值只有 `planDir` / `maxPlanBytes` 两个配置字段
|
|
123
|
+
- [x] A6: 不导出 default
|
|
124
|
+
- [x] C4/C9: 工具返回规范 JSON 值 + render 分离;presentCall/presentResult 纯函数(不读文件系统)
|
|
125
|
+
- [x] G: Unit + 组合(真实服务组合,临时目录真实文件 I/O)+ Client 组件三层
|
|
126
|
+
- [x] README 中文,含开发/运行/检查三节 + 顶部 dshfind card
|
|
127
|
+
|
|
128
|
+
### 与设计文档(docs/plans/2026-08-29-better-plan-plugin-design.md)的实施偏差
|
|
129
|
+
|
|
130
|
+
1. **规范值带 `delivered`**:canonical value 为 `{ approved: true, delivered: boolean }`(原版仅 `{ approved: true }`)。render 的「已展示于侧边栏计划面板」分支需要它,且 `sidebar_open` 先例(`delivered` 进规范值)支持该形状;`approved` 的 const 语义不变。
|
|
131
|
+
2. **presentCall 标题用 basename**:设计写「title = 计划首 heading ?? basename」,但 presentCall 是 args-only 纯函数(C9 回放约束),拿不到文件内容。首 heading 改为在 execute 内计算,用于 WS 推送的 tab 标题;聊天卡片标题 = 文件 basename。
|
|
132
|
+
3. **批准后的模式切换由本插件承载**:preset isolate realm 里的 `planMode` 服务对 agent ctx 不可见,无法直接驱动原版控制器的 `pendingIntents`。本插件以同机制补齐——`plan/mode: false` 延迟到下一个被接受的 `agent/pre-step` 边界 append(WeakSet 记账、append 失败保留重试、工具结果即叙述不额外注入)。语义与原版 execute 完全一致。
|
|
133
|
+
4. **WS 路由加了信任栅栏**:设计未提及;比照 better-sidebar 的路由防护补齐(Host 回环 / trustedHosts / sec-fetch-site / Origin hostname),防 DNS-rebinding 与跨站页面收割推送载荷。
|
|
134
|
+
5. **提示词面覆盖(真机走查发现)**:工具遮蔽生效后首测仍失败——preset 的 `plan:policy` 提示段教的是原版契约(「call exit_plan_mode with the complete plan markdown」+「Do not edit or write files」+「规则压过工具描述」),模型被两份矛盾指令夹住后写完文件直接收尾,从未调用交付工具。修复:`system-prompt/assemble` waterfall 监听器把该段三处句子就地改写为文件先行契约(`rewritePlanPolicySection` 锚点替换、幂等、缺锚跳过以兼容 preset 变体)。监听器必须逐 agent 注册在 `agent.ctx` 上——`assembleContextFor` 以 agent 为派发 key,scope 链准入只向上流,插件 fiber 上的全局注册会被过滤(测试固化了这一约束)。
|
|
135
|
+
6. **审批面从聊天卡迁到侧边栏 + 交付不再阻塞(真机走查反馈)**:设计 §1/§11 曾把「Plan tab 内 Approve/Refuse 按钮」列为 v1 非目标(审批留在聊天审批条)。真机走查后按用户要求推翻并迭代两步:(a) 首版把工具调用停靠在审批门上等决定——被走查否决:聊天里留下一个永远「运行中」的卡片,观感即卡死,且用户此刻输入无处落地。(b) 终版:**送达后工具立即返回** `decision: 'pending'`,render 指示模型结束回合(对话自然停止);用户在 Plan 面板动作栏决定后,宿主端**立即落 `plan/mode: false`(回合间 append,与原版控制器空闲时行为一致;失败退回 pendingExits 边界重试)并 `agent.steer` 开启新回合**把批准/反馈告诉模型(`steer` 契约:空闲 driver 直接开回合)。未送达时仍回退原版阻塞弹窗(detail=完整计划全文)。关键机制事实:阻塞工具 ≠ 停止对话——停止对话的唯一自然形态是模型停止调用工具,工具结果文本就是那个指令。
|
|
136
|
+
7. **交付提示词二次加硬(真机走查反馈:模型光写文件不调工具)**:首轮提示词覆盖后模型仍写完计划文件就收尾、从不调用 `exit_plan_mode`。诊断出两处措辞缺陷:(a) 改写后的交付句以「When ready」开头——太软,模型自行判断「还没准备好」;(b) 致命的是原版**紧随其后的「Make exit_plan_mode the only and final tool call in that assistant response」句未被改写**——文件先行契约下 write 必然发生在 exit 调用之前,模型把已发生的 write 读成违反该句,于是直接停笔。修复(v0.2.1):交付句改写为无条件**同回合两步强制契约**(写 `docs/plans/YYYY-MM-DD-<topic>.md`,日期+kebab-case 主题命名,紧跟 exit 调用;「写文件只是准备不是交付,写完不调工具的回合等于什么都没呈现」),final-call 句改写为「write 在前不失格,exit 是交付回合最后一个调用、其后无物」;工具 description 与 ENOENT 错误文本同步加硬(MANDATORY two-step + 命名示例 `2026-08-09-dsh-pet-rust-impl-spec.md`)。
|
|
137
|
+
8. **第三个决定:新开对话执行(v0.4.0)**:审批动作栏增加 `approve_new_session`。宿主侧结算为新的 `delegated` 状态(`approved` 会谎称「模型正在本对话执行」),照常翻 `plan/mode: false` 但 steer 换成移交文案;新建会话放在 **client 侧**经公开 `ctx.sessions` 契约(`create({cwd})` → `binding().session.prompt(kickoff,'queue')` → `open`)完成,而非宿主 `agents.create`——与用户手点「新建会话」同构(默认 preset,无 provider/model 继承),无会话列表竞态,且零新增 peer 依赖(结构化 face,`ctx.get('sessions')` 缺席时按钮不渲染)。kickoff 是首条用户消息(指向计划文件绝对路径),cwd 取自 sessions 列表里规划会话的 summary。
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
AGPL-3.0
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# dsh-plugin-better-plan bundle layer: inserts the plugin row.
|
|
2
|
+
# Loaded by `dsh plugin --profile <name> add @huanlin/dsh-plugin-better-plan`.
|
|
3
|
+
# The `name` is the package entry; the Loader resolves it from profile node_modules.
|
|
4
|
+
#
|
|
5
|
+
# The plugin SHADOWS the built-in `exit_plan_mode` per agent (same tool name,
|
|
6
|
+
# per-agent scoped registration via `agent/session-start` → `agent.ctx`), so the
|
|
7
|
+
# built-in plan-mode row in every agent preset stays mounted and untouched — its
|
|
8
|
+
# `plan:policy` section, `/plan` command, projection and composer badge keep
|
|
9
|
+
# working; only the delivery tool is replaced.
|
|
10
|
+
#
|
|
11
|
+
# Config (defaults shown; all fields optional):
|
|
12
|
+
# planDir — plan-directory suggestion interpolated into the tool
|
|
13
|
+
# description (e.g. `docs/plans`).
|
|
14
|
+
# maxPlanBytes — read cap of one plan file; larger files are refused with
|
|
15
|
+
# guidance to trim the plan.
|
|
16
|
+
# locale — locale of the host's user-facing copy (delivery render
|
|
17
|
+
# text, steer messages, no-sidebar review question): `auto`
|
|
18
|
+
# follows the connected sidebar view's reported locale
|
|
19
|
+
# (the browser's active DSH language), `zh`/`en` force one.
|
|
20
|
+
# Model-contract text stays English regardless.
|
|
21
|
+
- insert:
|
|
22
|
+
- id: better-plan
|
|
23
|
+
name: '@huanlin/dsh-plugin-better-plan'
|
|
24
|
+
config:
|
|
25
|
+
planDir: docs/plans
|
|
26
|
+
maxPlanBytes: 262144
|
|
27
|
+
locale: auto
|