dsh-plugin-prompt-tool 0.1.4 → 0.2.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 CHANGED
@@ -1,211 +1,115 @@
1
1
  # 提示词工具(dsh-plugin-prompt-tool)
2
2
 
3
- DSH 插件:把提示词规范注入三层(常驻层 + 按需技能层 + agent preset 锚定注入层),并提供 Web UI 在线编辑 `preset.md` 与 `AGENTS.md`。完整集成 [dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard) 的首轮锚定机制。
3
+ DSH 插件:为 DeepSeek Harness 提供三层提示词规范注入,并通过 Web 界面在线编辑。核心是“最优组合”首轮锚定:保持官方 Minimal(极简)训练分布的首轮条件,在模型轨迹稳定后再恢复完整能力。
4
4
 
5
- ## 三层注入
6
-
7
- - **常驻层(user 层)**:`AGENTS.md` 规则写入 `~/.dsh/AGENTS.md`。上游现以 `instruction-hint` 取代 dsh-agent-instructions 的大块注入:晋升后只提示一次"这些指令文件存在,先读",模型经文件工具自行读取;开启 `injectAgentsPrompt` 时,该提示文本改为直接注入 `AGENTS.md` 内容,注入位置不变,每会话一次(`preset.md` 不混入,改由 preset 层在锚定确认后注入,避免重复)。
8
- - **按需层(技能)**:扫描 `skills/*/SKILL.md` 注册全部技能;每个技能的开关以目录名为键、以 frontmatter 的 `name`(缺省用目录名)显示;加载时 content = `preset.md` 规范 + 技能正文,`resourceBase` 指向 `skills/<目录>`。
9
- - **独立 agent preset 层**:插件加载时直引 anchored-standard 上游文件生成 preset 到 `~/.dsh/.agent-presets/prompt-tool/`(首轮 = 官方 Minimal 真实 schema:持久 `bash` + `str_replace_editor` + 剥离自动注入上下文,无输出 cap);首轮 reasoning 稳定 "we" 轨迹,we 锚定确认后注入 `preset.md`;晋升后不放全量目录,改为 resident 集(bootstrap 对 + `dev_tool_search` / `skill_search` / `skill_load` + 已解锁工具)。
10
-
11
- > 提示词采用「we 锚定确认后注入」:首轮剥离自动注入(`agent-instructions` / `skill-catalog`),Minimal 真实工具 schema 下 reasoning 稳定走 "We need…" 轨迹;确认 we 锚定后(或不确认则最多等一轮兜底)把提示词规范作为 user 消息补进来(每会话一次)。工具目录晋升不依赖 we 确认(首个工具调用或助手回复即放开),锚定失败也不会卡死。
12
-
13
- ## 项目引用
14
-
15
- 本项目集成与参考的生态项目:
5
+ ## 来源与参考
16
6
 
17
7
  | 项目 | 关系 | 复用内容 |
18
8
  |---|---|---|
19
- | [dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard) | **集成(上游,跟踪 main)** | 加载时直引子模块 `vendor/dsh-anchored-standard/preset/` `agent.cordis.yml` 与全部 `*.mjs` 模块(更新即生效),Minimal 真实 schema 首轮锚定机制 |
20
- | [dsh-router-standard](https://github.com/yjh051108/dsh-router-standard) | 参考 | 复杂度启发式正则、近距离注入原则、持久事件推导状态(resume 安全) |
21
- | [dsh-super-injector](https://github.com/yjh051108/dsh-super-injector) | 参考 | 缓存铁律(静态进 system 头、动态走消息尾)、首轮锚定铁律、开发工具链(dev_* 注入/热重载) |
22
- | [dsh 破限者(1449690477/dsh)](https://github.com/1449690477/dsh) | 姊妹项目 | `skills/` 技能目录(sandboxmod/SKILL.md)与之同源,常驻层 AGENTS.md 机制一致 |
9
+ | [xiaobright/dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard) | 上游,内联快照 | 两阶段首轮锚定、tool-bootstrap、context-gate、resident 目录与按需解锁 |
10
+ | [yjh051108/dsh-router-standard](https://github.com/yjh051108/dsh-router-standard) | 参考 | 首轮结构、追加任务引导、Flash 弱路由人设与三锚、子代理方案 |
11
+ | [yjh051108/dsh-super-injector](https://github.com/yjh051108/dsh-super-injector) | 参考 | 缓存铁律、工具面成本原则 |
23
12
 
24
- ### 上游直引(子模块)
13
+ 上游文件固化在 `upstream/dsh-anchored-standard/`(含 `LICENSE`、`NOTICE`、`REVISION`),
14
+ 用 `pnpm sync:anchored` 更新。
25
15
 
26
- 上游文件不复制、不锁版本:`.gitmodules` 已声明 `branch = main`,插件加载(写 preset)时
27
- 直接读子模块 `vendor/dsh-anchored-standard/preset/` 里的 `agent.cordis.yml`(运行时注入
28
- prompt-injector 块与 `preset.md`)以及全部上游 `*.mjs` 模块(tool-bootstrap /
29
- compaction-epoch / custom-bash / dev-tool-search / instruction-hint / skill-search),
30
- 并把它们完整复制进生成的 preset。同步上游:
16
+ ## 项目说明
31
17
 
32
- ```sh
33
- git submodule update --remote vendor/dsh-anchored-standard # 跟随上游 main
34
- pnpm build # 重建插件
35
- # 重启 dsh 即生效
36
- ```
18
+ - **常驻层**:把 `AGENTS.md` 作为受管块写入 `~/.dsh/AGENTS.md`;开启 `injectAgentsPrompt` 时,以 `instruction-hint` 的位置每会话注入一次。
19
+ - **按需层**:扫描 `skills/*/SKILL.md` 注册可开关技能,内容只来自技能自身。
20
+ - **锚定层**:生成独立的 `~/.dsh/.agent-presets/prompt-tool/` 预设,承载首轮工具引导、上下文闸门、任务引导注入与提示词注入。
21
+ - **Web 界面**:自建回环设置桥,在线编辑 `preset.md` 与 `AGENTS.md`,切换全部开关;不占用模型设置区。
22
+ - **TUI(终端界面)命令**:`/prompt-tool status` 查看状态,`/prompt-tool on|off|toggle <开关>` 切换。
23
+ - **上游管理**:上游 dsh-anchored-standard 以内联快照形式放在 `upstream/`,`pnpm sync:anchored` 刷新;不再使用 git 子模块。
37
24
 
38
- - 上游仓库以子模块形式固定在 `vendor/dsh-anchored-standard`,跟踪 main,不锁 commit
39
- - `preset.yml`、`prompt-injector.mjs`、`turn-anchor.mjs` 为本项目自有文件,固定走 `preset/`
40
- - vendor 缺失(git 安装未初始化子模块)或上游结构变化 → 插件加载时报错(fail loud)
41
- - npm 安装走发布包内置的 `vendor/dsh-anchored-standard/preset` 快照,无需子模块;
42
- git clone / `link:` 安装则需 `git submodule update --init` 后再使用
43
-
44
- ## 修改记录
45
-
46
- - **v2.3(2026-08-15)**:删除 `prompt/references` 档位参考目录(SKILL.md 已内联全部规则,不再需要);README 描述同步。
47
- - **v2.4(2026-08-15)**:新增 `injectSkill` 开关(按需层 `prompt/SKILL.md` 技能注入);UI 开关描述按实际生效层级修正,`injectPrompt` 明确为锚定层注入。
48
- - **v2.5(2026-08-15)**:技能目录更名 `prompt` → `skills`,`SKILL.md` 移至 `skills/sandboxmod/`;新增 AGENTS.md 在线编辑保存与 skills 子折叠栏逐个技能开关。
49
- - **v2.6(2026-08-15)**:移除 `injectSkill` 总开关(全部技能关闭时技能列表自动为空);UI 改为 preset.md / AGENTS.md / skills / 锚定轮与 preset 四个分区,每个分区默认折叠。
50
- - **v2.7(2026-08-15)**:`prompt.md` 文件更名为 `preset.md`;分区更名为 Preset预设 / AGENTS设置 / Skills设置 / 锚定轮与 preset。
51
- - **v2.2(2026-08-15)**:子模块同步上游 main(`ffb845c`,PR #20/#21/#23/#27/#29)。晋升后由全量目录改为 resident 集(bootstrap 对 + dev_tool_search / skill_search / skill_load + 解锁工具);AGENTS.md 由每轮注入改为 instruction-hint 一次性提示 + 模型自读;新增 compaction 回落与 Windows custom-bash;`writePreset` 复制上游全部 `preset/*.mjs`;`.gitmodules` 声明 `branch = main`。
52
- - **v2.1(2026-08-15)**:技能目录重命名 `dreammod` → `skill` → `prompt`;上游改为子模块直引,移除不再需要的同步脚本。
53
- - **v2.0(2026-08-15)**:跟随 anchored-standard PR #14,首轮工具 schema 从 `pwsh/read + 1024 cap` 改为官方 Minimal 真实 schema(持久 `bash` + `str_replace_editor`,无 cap);删除 zero 变体与锚定消息机制,回归原版 tool-bootstrap(字节一致)+ `prompt-injector.mjs` 附加件(we 确认后注入一次 preset.md,未确认最多等一轮兜底)。实测:复杂英文任务 ×5 并行,we 锚定 5/5、首请求纯净、注入恰好一次。
54
- - **v1(2026-08)**:初版——zero 工具锚定变体 + 固定锚定消息 + 三层注入(AGENTS.md 常驻层、skill 按需层、preset 层)。
55
-
56
- ## Web UI
57
-
58
- 在 Settings → 插件 → **插件配置**分区注册「提示词工具」可折叠卡片(`settings.plugin.item`,与其他插件卡片同款式),展开后提供:
59
-
60
- - **分区折叠**:Preset预设区(编辑器 + `injectPrompt`)、AGENTS设置区(编辑器 + `injectAgentsPrompt` + `writeAgents`)、Skills设置区(每个技能独立开关 `skillSwitches`)、锚定轮与 preset 区(`writePreset`、`anchorFirstTurn`、`anchorText`);每个分区默认折叠,文件分区带独立保存/还原/打开按钮,开关点击即时生效
61
- - **保存 / 还原**:`preset.md` 与 `AGENTS.md` 各自独立保存/还原/打开(未保存时头部显示"未保存"标记,可分别还原草稿);注入开关与技能开关点击后即时写入 settings 并生效,不需要保存按钮。Host 监听后写回 `preset.md` 与 `AGENTS.md`、按开关刷新 `~/.dsh/AGENTS.md` 与 preset、失效技能目录缓存,下一次请求即生效
62
- - **打开编辑**:用系统编辑器分别打开 `preset.md` 或 `AGENTS.md`
63
- - **在线编辑框**:直接编辑 `preset.md` 与 `AGENTS.md` 文本
64
-
65
- ## 工作原理
66
-
67
- 1. Host 启动读取 `preset.md` 作为提示词规范源,读取 `AGENTS.md` 作为常驻层源文件。
68
- 2. 常驻层:`writeAgents` 开启时把当前 `AGENTS.md` 写入 `~/.dsh/AGENTS.md`;`injectAgentsPrompt` 开启时,`instruction-hint` 位置注入的是 `AGENTS.md` 内容本身,而不是提示模型自行读取。
69
- 3. 按需层:扫描 `skills/*/SKILL.md`;每个技能的 name/description/whenToUse/metadata 来自自身 frontmatter,并按 `skillSwitches` 决定是否注册;全部技能关闭时技能列表自动为空。加载内容为 `preset.md` 规范 + 技能正文。
70
- 4. preset 层:直引 `vendor/` 上游 `agent.cordis.yml` + 全部 `*.mjs` 生成 `~/.dsh/.agent-presets/prompt-tool/`,并把 `preset.md` 注入 `prompt-injector` 的 `promptText`(we 锚定确认后注入)。晋升后目录为 resident 集,其余工具经 `dev_tool_search` 按需解锁。
71
- 5. UI 保存通过 settings API 写入 `promptText`、`agentsText` 与全部开关;Host 的 watch 回调写回 `preset.md` 与 `AGENTS.md`,并按开关刷新 `~/.dsh/AGENTS.md` 与 preset(含 turn-anchor 行的增删)、失效技能目录缓存,下一次请求即生效。
72
-
73
- ## 文件结构
74
-
75
- ```text
76
- dsh-plugin-prompt-tool/
77
- ├── package.json
78
- ├── LICENSE # MIT
79
- ├── preset.md # 提示词规范源文件(Web UI 可编辑)
80
- ├── AGENTS.md # 常驻层源文件(Web UI 可编辑)
81
- ├── plan.md # 设计与测试计划(含上游更新对照、实测数据)
82
- ├── tsconfig.json # Host 类型检查 program(排除 src/client)
83
- ├── tsconfig.client.json # Client 类型检查 program(jsx: react-jsx)
84
- ├── tsdown.config.ts # 构建配置(host lib + client bundle,自包含)
85
- ├── cordis.patch.yml # 挂载配置
86
- ├── preset/ # 本项目自有 preset 文件(上游文件直引 vendor 子模块)
87
- │ ├── preset.yml # preset 元数据
88
- │ ├── prompt-injector.mjs # 附加件:we 锚定确认后注入一次 preset.md
89
- │ └── turn-anchor.mjs # 可选附加件:首轮独立锚定轮(anchorFirstTurn 开关)
90
- ├── skills/ # 按需层技能目录
91
- │ └── sandboxmod/
92
- │ └── SKILL.md # 技能定义(frontmatter name: prompt,开关键 sandboxmod)
93
- ├── src/
94
- │ ├── index.ts # Host 入口
95
- │ ├── preset-core.ts # preset 生成纯函数(buildCordis / parseFrontmatter)
96
- │ ├── css-modules.d.ts
97
- │ └── client/
98
- │ ├── index.ts # Client 入口(注册 settings.plugin.item 卡片)
99
- │ ├── PromptEditor.tsx # 编辑框组件
100
- │ └── PromptEditor.module.css
101
- ├── test/
102
- │ └── preset-core.test.mjs # node:test 单元测试(buildCordis / parseFrontmatter)
103
- ├── vendor/ # git 子模块:dsh-anchored-standard(跟踪 main;agent.cordis.yml + 全部 preset/*.mjs 直引源)
104
- └── lib/ # 构建产物(pnpm build 生成,不提交)
105
- ├── index.mjs # Host 运行时(ESM)
106
- ├── index.d.mts # Host 类型声明
107
- ├── preset-core.mjs # preset 生成核心(测试导入)
108
- └── client.js # Client 运行时(浏览器模块加载器协议,经 exports["./client"] 扫描)
109
- ```
110
-
111
- ## 构建与检查
25
+ ## 构建 / 装载
112
26
 
113
27
  ```sh
114
28
  pnpm install
115
- pnpm build
116
- pnpm typecheck # Host 与 Client 两个 tsc program,均 --noEmit
117
- pnpm lint # oxlint
118
- pnpm test # pnpm build + node --test
29
+ pnpm build # 生成 lib/
30
+ pnpm typecheck # Host 与 Client 两个 tsc program
31
+ pnpm lint # oxlint
32
+ pnpm test # pnpm build + node --test
33
+ pnpm prepare # npm publish / git install 前自动触发
34
+ pnpm sync:anchored # 从上游 main 刷新内联快照(可加 ref 参数)
119
35
  ```
120
36
 
121
- 按官方发布规范,`prepare` 也指向同一份 tsdown 配置(自包含转译 `src/`,并产出 `.d.mts` 类型声明):
37
+ 本插件挂载在独立 profile(`prompt-tool`):
122
38
 
123
39
  ```sh
124
- pnpm prepare # npm publish / git install 前自动触发(构建 lib/ 与 vendor/ 直引文件的发布快照)
40
+ dsh plugin --profile prompt-tool add link:<本仓库绝对路径>
41
+ dsh --profile prompt-tool
42
+ dsh plugin --profile prompt-tool remove dsh-plugin-prompt-tool
125
43
  ```
126
44
 
127
- ## 装载(官方 bundle-in-profile 模式)
128
-
129
- 本插件挂载在**独立 profile**(`prompt-tool`)。
45
+ 临时调试可用官方 `--patch` 覆盖层(不落盘、不改 profile):
130
46
 
131
47
  ```sh
132
- dsh plugin --profile prompt-tool add link:<本仓库绝对路径> # 官方 link 安装
133
- dsh plugin --profile prompt-tool remove dsh-plugin-prompt-tool # 卸载
48
+ dsh --profile prompt-tool --patch <cordis.yml>
134
49
  ```
135
50
 
136
- profile 的 bundles:`@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app`(in-box,直接写进
137
- bundles 列表,pnpm 不管理)+ `dsh-plugin-prompt-tool`。本地仓库 link 后:`lib/` 为已构建
138
- 产物(`pnpm build` 生成),`vendor/` 子模块随仓库 checkout,插件加载时直引上游最新文件
139
- (`git submodule update --remote vendor/dsh-anchored-standard` 后重启 dsh 即生效)。
51
+ 完全重启 dsh 后,新建会话并选择 prompt-tool 预设。
140
52
 
141
- 启动(web app 随 `dsh-web-app` bundle 自动挂载):
53
+ ## 原理
142
54
 
143
- ```sh
144
- dsh --profile prompt-tool
145
- ```
55
+ 模型的首轮请求结构决定整条会话轨迹。上游实测:官方 Minimal 首轮(训练原句 + 持久 `bash` + `str_replace_editor`,无输出封顶)稳定锚定 we 轨迹;完整 Standard 目录与自动注入在场会破坏锚定。
146
56
 
147
- 注意:`dsh web` 是 `--profile web` 的保留别名,不可与 `--profile` 组合。临时调试可用官方
148
- `--patch` 覆盖层(不落盘、不改任何 profile):
57
+ 本插件把“首轮轨迹选择”和“后续完整能力”拆开:
149
58
 
150
- ```sh
151
- dsh --profile prompt-tool --patch <cordis.yml>
152
- ```
59
+ 1. **干净首轮**:`context-gate` 清空首轮自动上下文并剥离自动注入;`tool-bootstrap` 只暴露 Minimal 两个工具;`router-first-turn` 把 persona 替换为训练原句。
60
+ 2. **晋升**:首次工具调用或助手回复落地后,目录进入 resident(常驻)集,上下文与注入恢复;工具按需解锁。
61
+ 3. **追加任务引导**:可选 `near-anchor`,在首条真实用户消息之后追加一次引导——默认按任务自动选择 we/let 首句,也可固定使用自定义文本。
62
+ 4. **提示词注入**:we 锚定确认后,把 `preset.md` 作为用户消息注入一次;未确认最多等一轮兜底。
63
+ 5. **子代理**:目录直接全量放行(实际仍受调用方工具白名单过滤);开启 `subagentFlash` 时,采用 dsh-router-standard 的 Flash 方案——固定 Flash 路由、任务分类人设与三锚。
64
+
65
+ 主会话首轮始终只有两个工具;模型性能不受记忆、技能等自动注入影响,完整能力在轨迹稳定后恢复。
66
+
67
+ ## 配置参考
153
68
 
154
- ## 挂载
69
+ 挂载配置(cordis.patch.yml / profile patch):
155
70
 
156
71
  ```yaml
157
72
  - insert:
158
73
  - id: prompt-tool
159
74
  name: dsh-plugin-prompt-tool
160
75
  config:
161
- text: '' # 可选:覆盖 preset.md 文本(默认读文件)
162
- agentsText: '' # 可选:覆盖 AGENTS.md 文本(默认读文件)
163
- injectAgentsPrompt: false # 是否用 AGENTS.md 内容替换上游 instruction-hint 提示(默认关闭,本地安全测试用)
164
- writeAgents: true # 是否写 ~/.dsh/AGENTS.md(默认 true)
165
- writePreset: true # 是否生成锚定注入 preset(默认 true)
166
- injectPrompt: true # 是否注入 preset.md(默认 true;关闭后只停止 preset.md 注入,AGENTS 注入不受影响)
167
- skillSwitches: {} # 按 skills/* 目录名自动生成,未列出的目录默认 true
168
- anchorFirstTurn: false # 首轮独立锚定轮开关(默认关闭)
169
- anchorText: "You are a helpful software assistant.\n\nBegin every reasoning block with 'We need'." # 锚定句文本
170
- skillsDir: '' # 可选:技能目录(默认包内 skills/,可指向本地测试目录)
171
- skillRankBase: 250 # 技能候选排序基数,技能目录内按下标递增
172
- residentAgentsPath: '' # 可选:常驻规则文件目标路径(默认 ~/.dsh/AGENTS.md)
173
- presetDir: '' # 可选:生成的 agent preset 目录(默认 ~/.dsh/.agent-presets/prompt-tool/)
174
- presetOrder: 5 # 生成 preset 的显示顺序
175
- fallbackText: '' # 可选:preset.md 缺失或不可读时使用的文本(默认空文本)
76
+ text: '' # 覆盖 preset.md 文本;默认读项目文件
77
+ agentsText: '' # 覆盖 AGENTS.md 文本;默认读项目文件
78
+ injectAgentsPrompt: false # AGENTS.md 替换 instruction-hint 提示文本
79
+ writeAgents: true # ~/.dsh/AGENTS.md 受管块(默认 true)
80
+ writePreset: true # 启用锚定预设(总开关,默认 true)
81
+ injectPrompt: true # we 确认后注入 preset.md(默认 true
82
+ skillSwitches: {} # 按 skills/* 目录名开关,未列出默认开启
83
+ anchorFirstTurn: false # 追加任务引导(默认关闭)
84
+ anchorText: '' # 自定义引导文本
85
+ anchorCustom: false # 使用自定义引导(true=固定使用 anchorText;false=自动选择)
86
+ subagentFlash: false # 子代理固定 Flash 模型(默认关闭)
87
+ subagentFlashProvider: 'deepseek-official'
88
+ subagentFlashModel: 'deepseek-v4-flash'
89
+ customBashPath: 'bash.exe' # 自定义 bash 路径(默认 PATH 查找)
90
+ skillsDir: '' # 可选技能目录(默认包内 skills/)
91
+ skillRankBase: 250 # 技能候选排序基数
92
+ residentAgentsPath: '' # 默认 ~/.dsh/AGENTS.md
93
+ presetDir: '' # 默认 ~/.dsh/.agent-presets/prompt-tool/
94
+ presetOrder: 5 # preset 显示顺序
95
+ fallbackText: '' # preset.md 缺失或不可读时的回退文本
176
96
  ```
177
97
 
178
- config 字段:`text`(覆盖 `preset.md` 文本,默认读文件)、`agentsText`(覆盖 `AGENTS.md` 文本,默认读文件)、`injectAgentsPrompt`(是否用 `AGENTS.md` 内容替换上游 `instruction-hint` 提示,默认关闭,本地安全测试用)、`writeAgents`(是否写 `~/.dsh/AGENTS.md`,默认 true)、`writePreset`(是否生成 `~/.dsh/.agent-presets/prompt-tool/`,默认 true)、`injectPrompt`(锚定层:we 锚定确认后是否注入 `preset.md`,默认 true)、`skillSwitches`(以技能目录名为键的逐技能开关,缺省视为 true)。 `skillsDir`(技能目录)、`skillRankBase`(技能候选排序基数)、`residentAgentsPath`(常驻规则文件目标路径)、`presetDir`(生成的 preset 目录)、`presetOrder`(preset 显示顺序)、`fallbackText`(`preset.md` 缺失时的回退文本)均可通过 cordis config 覆盖,默认值与上方注释一致。`writeAgents`、`writePreset`、`injectPrompt`、`injectAgentsPrompt`、`skillSwitches` 相互独立;`preset.md` 不拼进技能正文,技能加载内容只来自 `skills/<目录>/SKILL.md`。关闭 `injectPrompt` 只停止注入 `preset.md`;`injectAgentsPrompt` 开启时 `AGENTS.md` 走 `instruction-hint` 同一位置,与 `prompt-injector` 无关。`anchorFirstTurn` 与 `injectPrompt` 均通过 `writePreset` 生成的 preset 生效。开启 `injectAgentsPrompt` 后,宿主生成 `agents-instruction.txt` 并修补生成目录中的 `instruction-hint.mjs`,让 `AGENTS.md` 内容在 `instruction-hint` 的同一位置注入;`prompt-injector` 的 `promptText` 只由 `injectPrompt` 控制,不再拼接 `AGENTS.md`。开启 `injectAgentsPrompt` 时,`AGENTS.md` 内容会以 `instruction-hint` 的同一位置(消息尾部、同一 source 语义)注入,代替上游“请先读取 instruction files”这段提示文本;不会拼接到 `prompt-injector` 的 `promptText` 头部。
179
-
180
- `anchorFirstTurn`(默认 false)开启后,preset 额外挂载 `turn-anchor.mjs`:会话首个真实用户消息先原样入 `agent.inbox` 的 `next-step`,首步只把 `anchorText` 作为独立输入发给模型;模型回应锚定句后,driver 在同一轮内自动消费任务继续执行。任务绝不丢失:inbox 入队失败时回退为原样直发。
181
-
182
- 锚定句实测(deepseek-v4-pro + reasoningEffort=max,简单任务):
183
-
184
- - 默认句(含 "Begin every reasoning block with 'We need'."):**12/12** 首轮 reasoning 以 "We need" 开头,preset.md 全部走 we 确认注入;
185
- - 裸句 "You are a helpful software assistant.":首词 "We need" 约 58-67%(12 会话 7-8 次),其余走兜底注入。
186
-
187
- ## 锚定机制实测
188
-
189
- 工具引导由 preset 层的 `tool-bootstrap.mjs`(anchored-standard 上游直引)承担(挂在 agent-plane 首行,`inject:[]` + `prepend: true`,保证 strip 是 waterfall 的最终 transform):首轮 = Minimal 真实 schema(持久 `bash` + `str_replace_editor`)+ 剥离自动注入 → reasoning 稳定 "we" 轨迹 → 首个工具调用/助手回复落库后进入 resident 目录(bootstrap 对 + `dev_tool_search` / `skill_search` / `skill_load` + 已解锁工具)→ we 确认后(`prompt-injector.mjs`,注册在 tool-bootstrap 之后)注入 `preset.md` 一次。prompt-tool 插件(host 层)只负责生成 preset,不直接注册工具引导事件,避免与 preset 层重复。
190
-
191
- 实测(deepseek-v4-pro + reasoningEffort=max,复杂英文任务 ×5 并行,dsh web HTTP API):
98
+ 字段说明:
192
99
 
193
- | 断言 | 结果 |
194
- |---|---|
195
- | turn1 reasoning 首词 we | **5/5** |
196
- | 首请求工具 | [bash, str_replace_editor](5/5) |
197
- | 首请求 maxTokens | 256000(无 cap,5/5) |
198
- | 首请求前注入消息 | 纯净(仅 user,5/5) |
199
- | preset.md 注入 | 恰好一次,we 确认后同 turn 注入(5/5) |
200
- | 晋升后目录 | resident 集:bash + str_replace_editor + dev_tool_search / skill_search / skill_load + 已解锁工具(上游 v2.2 起) |
100
+ - **启用锚定预设**:总开关。开启时生成并刷新 `~/.dsh/.agent-presets/prompt-tool/`;关闭时移除已生成目录,锚定相关开关失效。
101
+ - **追加任务引导**:开启时挂载 `near-anchor`,在首条真实用户消息后追加一次任务引导。
102
+ - **使用自定义引导**:开启时固定使用 `anchorText`;关闭时按任务自动选择 we/let 引导,Flash 模型附加三锚。开启且文本为空时不注入。
103
+ - **子代理固定 Flash 模型**:开启时采用 dsh-router-standard 的 Flash 子代理方案——通用子代理行加固定 Flash 路由、任务分类人设与三锚;宿主直派子代理(含 dsh-mnemon)自动补 Flash 路由,调用方显式模型优先。检测不到 DeepSeek 模型路由时 Web/TUI 禁用,且运行时强制降级为关闭。
104
+ - **自定义 bash 路径**:上游若带作者机器固定路径,生成 preset 时归一化为该值,默认 PATH 查找 `bash.exe`。
105
+ - 其他字段语义见上方配置注释;设置文件优先级高于挂载配置,首次安装时用项目文件内容初始化设置。
201
106
 
202
- 详细设计、上游更新对照与踩坑记录见 [plan.md](plan.md)。
107
+ ## 版权
203
108
 
204
- ## 已知限制
109
+ 本项目采用 MIT 许可证。
205
110
 
206
- - **模型设置页目录条目**:已不再经 `ctx.llm.registerConfigurableProviders` 暴露 settings 命名空间,改为自建 loopback-only `/api/prompt-tool/settings` bridge,模型设置页不会再出现「提示词工具」条目。
207
- - **AGENTS.md 覆盖**:在线保存会直接覆盖项目根 `AGENTS.md`;`writeAgents` 开启时还会覆盖 `~/.dsh/AGENTS.md`,失败仅记录日志,卸载插件不恢复原文件;请自行保留原内容。
208
- - **UI 刷新策略**:插件配置卡片在无未保存草稿时每次展开都会同步最新 settings;存在草稿时保留本地编辑,点击「还原」可重新拉取。
209
- - **技能目录扫描**:`skills/*/SKILL.md` 在插件加载时扫描;新增或删除技能目录后重启 dsh 即生效,无需改代码。每个目录的开关默认开启,按目录名写入 `skillSwitches`。
210
- - **MCP 工具**:本 preset 晋升后为 resident 目录,外部 MCP 工具(`mcp__*`)不会默认可见,需模型经 `dev_tool_search` 解锁。
211
- - **上游跟随**:`writePreset` 动态复制上游 `preset/*.mjs` 全集;上游结构变化导致生成 YAML 非法或锚点缺失时 fail loud,同步命令见上文。
111
+ - 首轮锚定机制源自 [xiaobright/dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard)(MIT),其 `preset/` 快照内联于本仓库 `upstream/`,对应上游提交记录见 `REVISION`。
112
+ - 任务引导与 Flash 子代理方案参考 [yjh051108/dsh-router-standard](https://github.com/yjh051108/dsh-router-standard)(MIT)。
113
+ - 缓存与工具面成本原则参考 [yjh051108/dsh-super-injector](https://github.com/yjh051108/dsh-super-injector)(MIT)。
114
+ - 上游 `agent.cordis.yml` 基于 DeepSeek Harness Standard 预设修改;原始 DeepSeek 版权与 MIT 声明保留在 `upstream/dsh-anchored-standard/NOTICE` `LICENSE`。
115
+ - 本项目 `LICENSE` 见仓库根目录;随上游快照发布的 `upstream/dsh-anchored-standard/LICENSE` `NOTICE` 继续保留。
package/lib/client.js CHANGED
@@ -96,6 +96,8 @@ window.__ModuleLoader__.load({
96
96
  injectAgentsPrompt: false,
97
97
  anchorFirstTurn: false,
98
98
  anchorText: "",
99
+ anchorCustom: false,
100
+ subagentFlash: false,
99
101
  injectPrompt: true,
100
102
  skillSwitches: {},
101
103
  skillCatalog: [],
@@ -106,6 +108,8 @@ window.__ModuleLoader__.load({
106
108
  injectAgentsPrompt: false,
107
109
  anchorFirstTurn: false,
108
110
  anchorText: "",
111
+ anchorCustom: false,
112
+ subagentFlash: false,
109
113
  injectPrompt: true,
110
114
  skillSwitches: {},
111
115
  writeAgents: true,
@@ -115,12 +119,14 @@ window.__ModuleLoader__.load({
115
119
  injectAgentsPrompt: fields.injectAgentsPrompt,
116
120
  anchorFirstTurn: fields.anchorFirstTurn,
117
121
  anchorText: fields.anchorText,
122
+ anchorCustom: fields.anchorCustom,
123
+ subagentFlash: fields.subagentFlash,
118
124
  injectPrompt: fields.injectPrompt,
119
125
  skillSwitches: { ...fields.skillSwitches },
120
126
  writeAgents: fields.writeAgents,
121
127
  writePreset: fields.writePreset
122
128
  });
123
- const switchesEqual = (a, b) => a.injectAgentsPrompt === b.injectAgentsPrompt && a.anchorFirstTurn === b.anchorFirstTurn && a.anchorText === b.anchorText && a.injectPrompt === b.injectPrompt && JSON.stringify(a.skillSwitches) === JSON.stringify(b.skillSwitches) && a.writeAgents === b.writeAgents && a.writePreset === b.writePreset;
129
+ const switchesEqual = (a, b) => a.injectAgentsPrompt === b.injectAgentsPrompt && a.anchorFirstTurn === b.anchorFirstTurn && a.anchorText === b.anchorText && a.anchorCustom === b.anchorCustom && a.subagentFlash === b.subagentFlash && a.injectPrompt === b.injectPrompt && JSON.stringify(a.skillSwitches) === JSON.stringify(b.skillSwitches) && a.writeAgents === b.writeAgents && a.writePreset === b.writePreset;
124
130
  const asRecord = (value) => value !== null && typeof value === "object" ? value : {};
125
131
  const readString = (source, key) => {
126
132
  const value = source[key];
@@ -187,6 +193,9 @@ window.__ModuleLoader__.load({
187
193
  const [agentsOpen, setAgentsOpen] = (0, react.useState)(false);
188
194
  const [skillsOpen, setSkillsOpen] = (0, react.useState)(false);
189
195
  const [presetOpen, setPresetOpen] = (0, react.useState)(false);
196
+ const [deepseekAvailable, setDeepseekAvailable] = (0, react.useState)(false);
197
+ const [deepseekProviders, setDeepseekProviders] = (0, react.useState)([]);
198
+ const [deepseekError, setDeepseekError] = (0, react.useState)("");
190
199
  const [loaded, setLoaded] = (0, react.useState)(false);
191
200
  const [fields, setFields] = (0, react.useState)(EMPTY);
192
201
  const [savedPromptText, setSavedPromptText] = (0, react.useState)("");
@@ -195,6 +204,8 @@ window.__ModuleLoader__.load({
195
204
  const [loading, setLoading] = (0, react.useState)(false);
196
205
  const [savingPrompt, setSavingPrompt] = (0, react.useState)(false);
197
206
  const [savingAgents, setSavingAgents] = (0, react.useState)(false);
207
+ const [restoringPrompt, setRestoringPrompt] = (0, react.useState)(false);
208
+ const [restoringAgents, setRestoringAgents] = (0, react.useState)(false);
198
209
  const [notice, setNotice] = (0, react.useState)("");
199
210
  const [noticeKind, setNoticeKind] = (0, react.useState)("ok");
200
211
  const fieldsRef = (0, react.useRef)(EMPTY);
@@ -212,6 +223,9 @@ window.__ModuleLoader__.load({
212
223
  showNotice("error", "读取配置失败:" + (res.message ?? ""));
213
224
  return;
214
225
  }
226
+ setDeepseekAvailable(res.deepseekAvailable === true);
227
+ setDeepseekProviders(res.deepseekProviders ?? []);
228
+ setDeepseekError(res.deepseekError ?? "");
215
229
  const ns = res.value;
216
230
  const value = asRecord(ns.value);
217
231
  const base = asRecord(ns.base);
@@ -223,6 +237,8 @@ window.__ModuleLoader__.load({
223
237
  injectAgentsPrompt: readBoolean(value, "injectAgentsPrompt", readBoolean(base, "injectAgentsPrompt", false)),
224
238
  anchorFirstTurn: readBoolean(value, "anchorFirstTurn", readBoolean(base, "anchorFirstTurn", false)),
225
239
  anchorText: readString(value, "anchorText") ?? readString(base, "anchorText") ?? "",
240
+ anchorCustom: readBoolean(value, "anchorCustom", readBoolean(base, "anchorCustom", false)),
241
+ subagentFlash: readBoolean(value, "subagentFlash", readBoolean(base, "subagentFlash", false)),
226
242
  injectPrompt: readBoolean(value, "injectPrompt", readBoolean(base, "injectPrompt", true)),
227
243
  skillSwitches: value.skillSwitches !== void 0 || base.skillSwitches !== void 0 ? {
228
244
  ...readSkillSwitches(base, "skillSwitches"),
@@ -252,6 +268,33 @@ window.__ModuleLoader__.load({
252
268
  revisionRef.current = res.value.revision;
253
269
  } catch {}
254
270
  }, []);
271
+ const restoreOriginal = (0, react.useCallback)(async (scope) => {
272
+ const setBusy = scope === "preset" ? setRestoringPrompt : setRestoringAgents;
273
+ setBusy(true);
274
+ try {
275
+ const res = await bridgePost("/restore-originals", {
276
+ scope,
277
+ expectedRevision: revisionRef.current
278
+ });
279
+ if (!res.ok) {
280
+ await refreshRevision();
281
+ showNotice("error", "从项目还原失败:" + (res.message ?? "") + "(已刷新配置版本,可重试)");
282
+ return;
283
+ }
284
+ revisionRef.current = res.value.revision;
285
+ await load();
286
+ showNotice("ok", scope === "preset" ? "preset.md 已从项目原文还原到设置" : "AGENTS.md 已从项目原文还原到设置");
287
+ } catch (error) {
288
+ await refreshRevision();
289
+ showNotice("error", "从项目还原失败:" + errorMessage(error) + "(已刷新配置版本,可重试)");
290
+ } finally {
291
+ setBusy(false);
292
+ }
293
+ }, [
294
+ load,
295
+ refreshRevision,
296
+ showNotice
297
+ ]);
255
298
  const patch = (partial) => {
256
299
  const next = {
257
300
  ...fieldsRef.current,
@@ -345,6 +388,16 @@ window.__ModuleLoader__.load({
345
388
  path: ["anchorText"],
346
389
  value: fieldsRef.current.anchorText
347
390
  },
391
+ {
392
+ op: "set",
393
+ path: ["anchorCustom"],
394
+ value: fieldsRef.current.anchorCustom
395
+ },
396
+ {
397
+ op: "set",
398
+ path: ["subagentFlash"],
399
+ value: fieldsRef.current.subagentFlash
400
+ },
348
401
  {
349
402
  op: "set",
350
403
  path: ["injectPrompt"],
@@ -367,6 +420,10 @@ window.__ModuleLoader__.load({
367
420
  }
368
421
  ], void 0, () => setSavedSwitches(snapshotSwitches(fieldsRef.current)));
369
422
  const toggle = (key) => {
423
+ if (key === "subagentFlash" && !deepseekAvailable) {
424
+ showNotice("error", "未检测到 DeepSeek 模型配置,子代理 Flash 开关不可用");
425
+ return;
426
+ }
370
427
  patch({ [key]: !fieldsRef.current[key] });
371
428
  persistSwitches();
372
429
  };
@@ -467,7 +524,14 @@ window.__ModuleLoader__.load({
467
524
  className: PromptEditor_module_css_default.secondary,
468
525
  disabled: !dirtyPrompt,
469
526
  onClick: discardPrompt,
470
- children: "还原"
527
+ children: "还原草稿"
528
+ }),
529
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
530
+ type: "button",
531
+ className: PromptEditor_module_css_default.secondary,
532
+ disabled: savingPrompt || savingAgents || restoringPrompt || restoringAgents,
533
+ onClick: () => void restoreOriginal("preset"),
534
+ children: restoringPrompt ? "还原中…" : "项目还原"
471
535
  }),
472
536
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
473
537
  type: "button",
@@ -542,7 +606,14 @@ window.__ModuleLoader__.load({
542
606
  className: PromptEditor_module_css_default.secondary,
543
607
  disabled: !dirtyAgents,
544
608
  onClick: discardAgents,
545
- children: "还原"
609
+ children: "还原草稿"
610
+ }),
611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
612
+ type: "button",
613
+ className: PromptEditor_module_css_default.secondary,
614
+ disabled: savingPrompt || savingAgents || restoringPrompt || restoringAgents,
615
+ onClick: () => void restoreOriginal("agents"),
616
+ children: restoringAgents ? "还原中…" : "项目还原"
546
617
  }),
547
618
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
548
619
  type: "button",
@@ -585,7 +656,7 @@ window.__ModuleLoader__.load({
585
656
  }, skill.folder))
586
657
  }),
587
658
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
588
- title: "锚定轮与 preset",
659
+ title: "锚定与 preset",
589
660
  hint: presetSwitchDirty ? "未保存" : void 0,
590
661
  open: presetOpen,
591
662
  onToggle: () => setPresetOpen(!presetOpen),
@@ -600,10 +671,28 @@ window.__ModuleLoader__.load({
600
671
  className: PromptEditor_module_css_default.rowText,
601
672
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
602
673
  className: PromptEditor_module_css_default.rowName,
603
- children: "生成锚定注入 preset"
674
+ children: "启用锚定预设"
604
675
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
605
676
  className: PromptEditor_module_css_default.rowDesc,
606
- children: "开启时生成并刷新 preset 目录,承载首轮工具引导与上述注入件;关闭时不生成,已有文件保持原样"
677
+ children: "总开关:开启时生成并刷新 ~/.dsh/.agent-presets/prompt-tool/,整套锚定预设才有载体;关闭时移除已生成的目录,下面锚定开关随之失效"
678
+ })]
679
+ })]
680
+ }),
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
682
+ className: PromptEditor_module_css_default.row,
683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
684
+ type: "checkbox",
685
+ checked: fields.subagentFlash,
686
+ disabled: !fields.writePreset || !deepseekAvailable,
687
+ onChange: () => toggle("subagentFlash")
688
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
689
+ className: PromptEditor_module_css_default.rowText,
690
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
691
+ className: PromptEditor_module_css_default.rowName,
692
+ children: "子代理固定 Flash 模型"
693
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
694
+ className: PromptEditor_module_css_default.rowDesc,
695
+ children: deepseekAvailable ? "开启时采用 dsh-router-standard 的 Flash 子代理方案:固定 Flash 路由 + 任务分类人设 + 三锚;宿主直派子代理(含 dsh-mnemon)也会自动补 Flash 路由。关闭时继承主会话模型,工具目录全量放行" : `未检测到 DeepSeek 模型配置,此开关不可用。providers=[${deepseekProviders.join(", ") || "空"}]${deepseekError ? " error=" + deepseekError : ""}`
607
696
  })]
608
697
  })]
609
698
  }),
@@ -618,10 +707,28 @@ window.__ModuleLoader__.load({
618
707
  className: PromptEditor_module_css_default.rowText,
619
708
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
620
709
  className: PromptEditor_module_css_default.rowName,
621
- children: "首轮独立锚定轮"
710
+ children: "追加任务引导"
711
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
712
+ className: PromptEditor_module_css_default.rowDesc,
713
+ children: "开启后在首条真实用户消息之后追加一句任务引导,不拆轮、不挪任务;内容按下方“使用自定义引导”开关选择"
714
+ })]
715
+ })]
716
+ }),
717
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
718
+ className: PromptEditor_module_css_default.row,
719
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
720
+ type: "checkbox",
721
+ checked: fields.anchorCustom,
722
+ disabled: !fields.writePreset || !fields.anchorFirstTurn,
723
+ onChange: () => toggle("anchorCustom")
724
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
725
+ className: PromptEditor_module_css_default.rowText,
726
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
727
+ className: PromptEditor_module_css_default.rowName,
728
+ children: "使用自定义引导"
622
729
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
623
730
  className: PromptEditor_module_css_default.rowDesc,
624
- children: "开启时 preset 挂载 turn-anchor:首个真实用户消息先入 next-step,首步只发锚定句"
731
+ children: "开启时使用下方文本作为引导;关闭时忽略下方文本,按任务自动选择 we/let 引导"
625
732
  })]
626
733
  })]
627
734
  }),
@@ -631,15 +738,15 @@ window.__ModuleLoader__.load({
631
738
  className: PromptEditor_module_css_default.rowText,
632
739
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
633
740
  className: PromptEditor_module_css_default.rowName,
634
- children: "锚定句文本(可自定义)"
741
+ children: "自定义引导文本"
635
742
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
636
743
  className: PromptEditor_module_css_default.rowDesc,
637
- children: "独立锚定轮发给模型的输入内容"
744
+ children: "仅在上方“使用自定义引导”开启时生效;关闭时自动文本优先"
638
745
  })]
639
746
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
640
747
  className: PromptEditor_module_css_default.anchorInput,
641
748
  value: fields.anchorText,
642
- disabled: !fields.writePreset || !fields.anchorFirstTurn,
749
+ disabled: !fields.writePreset || !fields.anchorFirstTurn || !fields.anchorCustom,
643
750
  onChange: (e) => patch({ anchorText: e.target.value }),
644
751
  onBlur: () => persistSwitches(),
645
752
  spellCheck: false