dsh-plugin-prompt-tool 0.4.2 → 0.6.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.
Files changed (191) hide show
  1. package/README.md +131 -222
  2. package/engine/anchor-match.mjs +117 -0
  3. package/engine/compaction-epoch.mjs +139 -0
  4. package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
  5. package/engine/compositions/library/compaction.yml +52 -0
  6. package/engine/compositions/library/context-gate.yml +40 -0
  7. package/engine/compositions/library/custom-bash.yml +14 -0
  8. package/engine/compositions/library/delegation.yml +85 -0
  9. package/engine/compositions/library/official-agent-instructions.yml +17 -0
  10. package/engine/compositions/library/official-persistent-shell.yml +56 -0
  11. package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
  12. package/engine/compositions/library/official-tool-bash.yml +6 -0
  13. package/engine/compositions/library/official-tool-cordis.yml +13 -0
  14. package/engine/compositions/library/official-tool-presentation.yml +7 -0
  15. package/engine/compositions/library/official-tool-skill.yml +14 -0
  16. package/engine/compositions/library/persistent-shell.yml +36 -0
  17. package/engine/compositions/library/persona.yml +9 -0
  18. package/engine/compositions/library/planning.yml +41 -0
  19. package/engine/compositions/library/prompt-config-engine.yml +16 -0
  20. package/engine/compositions/library/run-code-env.yml +15 -0
  21. package/engine/compositions/library/skill-filesystem.yml +13 -0
  22. package/engine/compositions/library/skill-search.yml +14 -0
  23. package/engine/compositions/library/str-replace-editor.yml +10 -0
  24. package/engine/compositions/library/tool-ask-user.yml +8 -0
  25. package/engine/compositions/library/tool-bash.yml +16 -0
  26. package/engine/compositions/library/tool-bootstrap.yml +16 -0
  27. package/engine/compositions/library/tool-filter.yml +12 -0
  28. package/engine/compositions/library/tool-fs-search.yml +18 -0
  29. package/engine/compositions/library/tool-fs.yml +10 -0
  30. package/engine/compositions/library/tool-goal.yml +19 -0
  31. package/engine/compositions/library/tool-jobs.yml +23 -0
  32. package/engine/compositions/library/tool-pwsh.yml +12 -0
  33. package/engine/compositions/library/tool-todo.yml +11 -0
  34. package/engine/compositions/library/tool-web.yml +9 -0
  35. package/engine/compositions/source/local/context-gate.yml +37 -0
  36. package/engine/compositions/source/local/custom-bash.yml +11 -0
  37. package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
  38. package/engine/compositions/source/local/run-code-env.yml +12 -0
  39. package/engine/compositions/source/local/skill-search.yml +11 -0
  40. package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
  41. package/engine/context-gate.mjs +305 -0
  42. package/{preset → engine}/custom-bash.mjs +243 -243
  43. package/engine/executor.mjs +278 -0
  44. package/engine/fillers.mjs +273 -0
  45. package/engine/interpolate.mjs +66 -0
  46. package/engine/layers.mjs +224 -0
  47. package/engine/prompt-config-engine.mjs +49 -0
  48. package/engine/run-code-env.mjs +208 -0
  49. package/engine/schema.mjs +312 -0
  50. package/engine/session-vars.mjs +52 -0
  51. package/{preset → engine}/shared.mjs +35 -0
  52. package/engine/strategies.mjs +213 -0
  53. package/{preset → engine}/tool-bootstrap.mjs +350 -282
  54. package/engine/tool-filter.mjs +80 -0
  55. package/engine/vendor/yaml/LICENSE +13 -0
  56. package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
  57. package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
  58. package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
  59. package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
  60. package/engine/vendor/yaml/dist/compose/composer.js +219 -0
  61. package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
  62. package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
  63. package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
  64. package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
  65. package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
  66. package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
  67. package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
  68. package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
  69. package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
  70. package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
  71. package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
  72. package/engine/vendor/yaml/dist/doc/Document.js +335 -0
  73. package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
  74. package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
  75. package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
  76. package/engine/vendor/yaml/dist/doc/directives.js +176 -0
  77. package/engine/vendor/yaml/dist/errors.js +57 -0
  78. package/engine/vendor/yaml/dist/index.js +17 -0
  79. package/engine/vendor/yaml/dist/log.js +11 -0
  80. package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
  81. package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
  82. package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
  83. package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
  84. package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
  85. package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
  86. package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
  87. package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
  88. package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
  89. package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
  90. package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
  91. package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
  92. package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
  93. package/engine/vendor/yaml/dist/parse/cst.js +98 -0
  94. package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
  95. package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
  96. package/engine/vendor/yaml/dist/parse/parser.js +975 -0
  97. package/engine/vendor/yaml/dist/public-api.js +102 -0
  98. package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
  99. package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
  100. package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
  101. package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
  102. package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
  103. package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
  104. package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
  105. package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
  106. package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
  107. package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
  108. package/engine/vendor/yaml/dist/schema/tags.js +96 -0
  109. package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
  110. package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
  111. package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
  112. package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
  113. package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
  114. package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
  115. package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
  116. package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
  117. package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
  118. package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
  119. package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
  120. package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
  121. package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
  122. package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
  123. package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
  124. package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
  125. package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
  126. package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
  127. package/engine/vendor/yaml/dist/util.js +11 -0
  128. package/engine/vendor/yaml/dist/visit.js +233 -0
  129. package/engine/vendor/yaml/index.js +5 -0
  130. package/engine/vendor/yaml/package.json +11 -0
  131. package/lib/client.js +4724 -775
  132. package/lib/client.js.map +1 -1
  133. package/lib/index.d.mts +530 -51
  134. package/lib/index.mjs +4164 -740
  135. package/lib/preset-core.d.mts +7 -37
  136. package/lib/preset-core.mjs +43 -285
  137. package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
  138. package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
  139. package/package.json +36 -29
  140. package/preset/anchored/preset.yml +342 -0
  141. package/preset/creative/preset.yml +65 -0
  142. package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
  143. package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
  144. package/preset/custom/preset.yml +13 -0
  145. package/preset/liangshen/preset.yml +72 -0
  146. package/preset/minimal/preset.yml +44 -0
  147. package/preset/ptc/preset.yml +56 -0
  148. package/preset/standard/preset.yml +55 -0
  149. package/skills/manifest.json +7 -0
  150. package/skills/sandboxmod/SKILL.md +49 -49
  151. package/skills/web ui/SKILL.md +42 -0
  152. package/templates/10-pre-step.yml +28 -0
  153. package/templates/11-merged-a.yml +11 -0
  154. package/templates/13-anchor.yml +19 -0
  155. package/templates/14-first-turn-anchor.yml +27 -0
  156. package/templates/15-guide-auto.yml +25 -0
  157. package/templates/16-custom-fallback.yml +21 -0
  158. package/templates/17-instruction-hint.yml +23 -0
  159. package/templates/18-placeholder-env-facts.yml +11 -0
  160. package/templates/19-placeholder-skill-catalog.yml +19 -0
  161. package/templates/20-system-section.yml +19 -0
  162. package/templates/30-runtime-context.yml +10 -0
  163. package/templates/31-runtime-context-placeholder.yml +18 -0
  164. package/templates/40-agent-request.yml +11 -0
  165. package/templates/50-llm-stream.yml +9 -0
  166. package/templates/60-tool-pipeline.yml +12 -0
  167. package/AGENTS.md +0 -4
  168. package/plan.md +0 -312
  169. package/preset/agent.cordis.yml +0 -443
  170. package/preset/compaction-epoch.mjs +0 -81
  171. package/preset/context-gate.mjs +0 -165
  172. package/preset/instruction-hint.mjs +0 -217
  173. package/preset/near-anchor.mjs +0 -101
  174. package/preset/preset.yml +0 -3
  175. package/preset/prompt-injector.mjs +0 -112
  176. package/preset/router-first-turn.mjs +0 -73
  177. package/preset/router-guide.mjs +0 -79
  178. package/preset.md +0 -115
  179. package/upstream/dsh-anchored-standard/LICENSE +0 -22
  180. package/upstream/dsh-anchored-standard/NOTICE +0 -19
  181. package/upstream/dsh-anchored-standard/REVISION +0 -1
  182. package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
  183. package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
  184. package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
  185. package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
  186. package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
  187. package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
  188. package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
  189. package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
  190. package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
  191. /package/{preset → engine}/skill-search.mjs +0 -0
package/README.md CHANGED
@@ -1,222 +1,131 @@
1
- # 提示词工具(dsh-plugin-prompt-tool
2
-
3
- 把「Anchored Standard + dsh-router-standard 最优组合」做成 DSH 全家桶里的一键安装插件:插件启动时生成并维护 `~/.dsh/.agent-presets/prompt-tool/` 预设,首轮模型请求只看到官方 Minimal 精确双工具——持久 `bash` 与 `str_replace_editor`——和正确的 persona,没有运行时上下文与指令注入;锚定建立后进入 resident 目录,恢复常规注入,并在确认轨迹后注入 `preset.md`。同时提供 Web 界面在线编辑 `preset.md` / `AGENTS.md`、切换全部开关,以及可开关的 skills 技能层(含一键打开技能目录、设置自定义技能目录)。全部通过官方插件接口实现,不修改 DSH 源码。
4
-
5
- ## 原理
6
-
7
- DeepSeek V4 会强烈依赖 API 中可见的**首轮工具目录与 persona** 选择执行轨迹。完整 Standard 目录与自动注入在场会破坏 Minimal 轨迹,而全程 Minimal 又会失去重型工具。本项目把「首次轨迹选择」与「后续完整工具能力」拆开:
8
-
9
- 1. **干净首轮**:`context-gate` 清空首轮 runtime-context 并剥离自动注入;`tool-bootstrap` 只暴露 Minimal 双工具;`router-first-turn` 按主会话模型替换 persona——Pro 使用训练原句,Flash 自动采用 dsh-router-standard 的 Flash 弱路由人设(build/fix 分类 + 回顾锚 + 反跑题锚 + 先深想再产出)。
10
- 2. **晋升**:首次持久 `tool/call` 或 `assistant/message`(先到者为准)落地后,按 `usePtcMode` 开关选择 wire 形态——默认开启时切换为 Code Mode(PTC,单一 `run_code`),完整插件工具经生成 SDK 调用;关闭时恢复原生完整工具目录。
11
- 3. **任务引导**:可选 `near-anchor` 在首条真实用户消息后追加一次近距离首句引导(we/let 按任务自动选择);Flash 主会话晋升后 `router-guide` 按任务复杂度追加每轮深度引导。
12
- 4. **提示词注入**:we 锚定确认后把 `preset.md` 作为用户消息注入一次;we 未确认最多等一轮兜底,绝不卡死。
13
- 5. **子代理**:目录直接全量放行(仍受调用方工具白名单过滤);可选 `subagentFlash` 固定 Flash 路由 + 任务分类人设 + 三锚。
14
-
15
- 阶段全部从持久 session events 推导,resume / reload 不丢失状态。
16
-
17
- ## 稳定化控制
18
-
19
- 全部在生成 preset 的 `agent.cordis.yml` 行中配置:
20
-
21
- - `bootstrapMaxTokens`:首轮输出封顶。`0` = 本项目默认不设封顶(Web 界面显示 256000,即不设上限);正整数 = 请求 #1 的 `maxTokens`,晋升后自动剥离,不会焊进后续请求。不锁定 1024,任意正整数均可。
22
- - `context-gate`:未晋升期间关闭两条统一注入路径,pre-step 只保留 claimed 批次 + `allowKinds`(放行用户技能手势与 `near-anchor` / `router-guide`);晋升后差分恢复一条 runtime-context 消息。
23
- - `tool-bootstrap`:`promoteOn: either` 避免纯文字首答永久困在双工具;compaction 后回到 bootstrap + `compactionTools` 受控阶段。
24
- - `usePtcMode`(默认开启):晋升后把 wire 切换为 Code Mode(PTC,单一 `run_code`),完整插件工具经生成 SDK 调用;关闭时恢复原生完整工具目录。两种模式都不再依赖 `dev_tool_search`,生成 preset 时直接移除该行且不复制 `dev-tool-search.mjs`。
25
- - `router-first-turn`:只替换 persona 段,保留 plan-mode 段与第三方 section;首轮隐藏 `mnemon:*` 自动注入段,晋升后恢复。
26
- - `near-anchor`:行为引导放在真实用户消息之后(近距离零衰减),只要求首句一次;复杂规划任务放行 Let 深度路径,日常任务锚 We。
27
- - `router-guide`:简单任务快速收敛,复杂任务深度引导;子代理不注入,首轮不注入。
28
- - `prompt-injector`:注入状态以持久事件为准,跨进程重启 / 插件热重载不重复注入。
29
- - `injectAgentsPrompt`:开启时用 `AGENTS.md` 内容替换本地 instruction-hint 的默认提示文本;关闭时使用本地默认 hint(列出参考文件、按需读取)。
30
- - `skillSwitches`:扫描 `skills/*/SKILL.md` 注册可开关技能,未列出的技能默认开启。
31
- - `subagentFlash`:检测不到 DeepSeek 模型路由时 Web/TUI 禁用并强制降级为关闭。
32
-
33
- ## 安装
34
-
35
- 依据官方[《打包与安装插件》](https://deepseek-harness.github.io/deepseek-harness/develop/basic/publish)准则:
36
- `dsh plugin add` 只把**声明了 `dsh.bundle` 的直接依赖**追加进
37
- `dsh.profile.bundles`;`@deepseek-ai/dsh-web-app` 是随 DSH 安装自带的 in-box
38
- bundle(从 dsh 安装目录解析,不需要也不应写进 `dependencies`)。
39
-
40
- ### npm 安装与源码安装
41
-
42
- 两种安装源等价,后续初始化流程相同:
43
-
44
- ```sh
45
- # 方式 A:npm / registry 安装
46
- dsh plugin --profile prompt-tool add dsh-plugin-prompt-tool
47
-
48
- # 方式 B:本地源码安装(link 会覆盖 registry 依赖)
49
- dsh plugin --profile prompt-tool add link:<本仓库绝对路径>
50
- ```
51
-
52
- ### 初始化:启动一次即可
53
-
54
- ```sh
55
- dsh --profile prompt-tool
56
- ```
57
-
58
- 第一次启动只负责完成初始化。首次进程尚未挂载 Web 表面,这是预期行为;
59
- 自愈完成后**插件会自动退出(exit 0)**,无需手动停止:
60
-
61
- ```text
62
- prompt-tool: auto-added @deepseek-ai/dsh-web-app to dsh.profile.bundles for profile "prompt-tool"; next launch will mount the Web surface ...
63
- prompt-tool: initialization complete exiting so the repaired profile can be launched
64
- ```
65
-
66
- 这一次启动会自动完成:
67
-
68
- 1. **prompt-tool profile**:把 `@deepseek-ai/dsh-web-app` 补进
69
- `dsh.profile.bundles`,最终为
70
- `base web-app dsh-plugin-prompt-tool`;
71
- 2. **web profile**:若存在,把 `dsh-plugin-prompt-tool` 写进它的
72
- `dependencies` + `bundles`(dependency 写法复用当前 profile 的安装
73
- spec)。**只写 package.json,不手工创建 node_modules 链接**;
74
- 3. **dsh-tui profile**:若存在且确实装有
75
- `@deepseek-harness-tui/dsh-tui`,同样写进 `dsh-plugin-prompt-tool`;
76
- **没有 dsh-tui 则整体跳过,不写入本插件**。dsh-tui 不需要
77
- `@deepseek-ai/dsh-web-app`,因此不会给它补 web-app。
78
-
79
- 所有写入幂等:文件已正确时不做任何改动。
80
-
81
- ### 初始化之后直接使用
82
-
83
- ```sh
84
- # Web:官方内置 profile,本插件已自动写入
85
- dsh web
86
-
87
- # TUI:dsh-tui 的安装流程已经物化过其 profile 依赖
88
- dsh-tui
89
- ```
90
-
91
- `dsh web` dsh 安装必然存在;`dsh-tui` 则在其自身安装说明中已经执行过
92
- profile 依赖物化。因此初始化只需写入 package.json,不需要额外 `install`。
93
-
94
- 如果还想直接使用 `prompt-tool` 这个 profile 本身,再运行一次即可
95
- (第二次启动时 web-app 已由官方装配路径加载):
96
-
97
- ```sh
98
- dsh --profile prompt-tool
99
- ```
100
-
101
- > TUI 前提:先安装/初始化过 `@deepseek-harness-tui/dsh-tui` 并已生成
102
- > `dsh-tui` profile,再执行上面的初始化启动;否则按设计会跳过 dsh-tui
103
- > profile。之后补装 dsh-tui 时,重新执行一次
104
- > `dsh --profile prompt-tool` 即可把本插件补进 dsh-tui profile。
105
-
106
- ### 备选:直接把本插件装进官方 `web` 模板 profile
107
-
108
- `web` 是官方内置模板,初始 bundles 已含
109
- `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app`:
110
-
111
- ```sh
112
- # registry 安装
113
- dsh plugin --profile web add dsh-plugin-prompt-tool
114
- # 本地开发安装
115
- dsh plugin --profile web add link:<本仓库绝对路径>
116
-
117
- # 启动
118
- dsh --profile web
119
- ```
120
-
121
- 完成初始化后,用 `dsh web`、`dsh-tui` `dsh --profile prompt-tool` 启动,
122
- 新建空 session,预设选择 **prompt-tool**。插件会在启动时生成并刷新
123
- `~/.dsh/.agent-presets/prompt-tool/`(升级插件后重启即自动更新)。
124
-
125
- ### 卸载
126
-
127
- ```sh
128
- dsh plugin --profile prompt-tool remove dsh-plugin-prompt-tool
129
- ```
130
-
131
- 首次启动还会把包内 `skills/` 增量复制到**本插件自己的 profile** 目录下的
132
- `$DSH_HOME/profiles/prompt-tool/skills`,并优先使用这份副本(从 `dsh web`
133
- 或 `dsh-tui` 启动也一样写这里,不会写到 web/dsh-tui profile):已有同名文件不覆盖,
134
- 用户对副本的编辑会保留;包内新增技能文件会在下次启动补齐。想改回包内原始
135
- skills,删除该 `skills` 目录后重启即可(或在配置里显式设置 `skillsDir`)。
136
-
137
- ## 验证
138
-
139
- 导出 session JSONL,检查 `request/header`:
140
-
141
- - 第一份 header 的 `tools` 应恰好是 `["bash", "str_replace_editor"]`;
142
- - 第一轮只包含用户消息与首句锚点:没有 workspace 指令 baseline、运行时快照、skill 目录消息;
143
- - 首次工具调用或首次助手回复后,下一份变更 header 应包含 resident 目录:bootstrap 双工具 + `dev_tool_search` / `skill_search` / `skill_load` + 已解锁工具;
144
- - 此后的请求保持 resident 集,只经 `dev_tool_search` 显式解锁增长;
145
- - `we` 锚定确认后,事件流出现一次 `source.plugin === 'prompt-injector'` 的消息;未确认时最多等一轮兜底。
146
-
147
- 本项目测试:
148
-
149
- ```sh
150
- pnpm test # pnpm build + node --test
151
- pnpm typecheck # Host 与 Client 两个 tsc program
152
- pnpm lint # oxlint
153
- ```
154
-
155
- ## 行为与限制
156
-
157
- - Windows:DSH 的 PTY 后端仅支持 linux/darwin,持久 shell 组禁用;phase-1 的 `bash` 切换为 `custom-bash`——同名且 schema 与 Minimal 兼容,经普通跨平台子进程通道调起 Git Bash(运行时探测安装路径,不硬编码)。
158
- - Linux/macOS:持久 shell 的 `shellPath` 自适应——`/bin/bash` 存在时保持默认,不存在(如 NixOS)回退 PATH 里的 `bash`。
159
- - 首轮能力类提问可能基于被裁剪的双工具视图作答,晋升后由后续工具纠正;需要时可开启任务引导或直接首轮问任务类问题。
160
- - 工具目录只变化一次(晋升点),因此第一、二次请求之间发生一次前缀缓存变化;之后每次 `dev_tool_search` 解锁再变化。
161
- - preset 与 shell 访问具有相同信任等级,安装前可自行审阅 `upstream/` 与生成目录。
162
- - 插件只监听本机回环设置桥,不发起网络请求,也不增加遥测。
163
- - 不要在已经产生内容的会话中途切换 preset。
164
- - 需要 DSH 0.1.0-rc.7+(preset 机制、`system-prompt/assemble` 钩子与 keyed `settings.plugin.item` 插槽)。
165
-
166
- ## 构建与上游管理
167
-
168
- ```sh
169
- pnpm install
170
- pnpm build # 生成 lib/
171
- pnpm prepare # npm publish / git install 前自动触发
172
- pnpm sync:anchored # 从上游 main 刷新内联快照(可加 ref 参数)
173
- ```
174
-
175
- 上游 `dsh-anchored-standard` 以内联快照形式固化在 `upstream/dsh-anchored-standard/`(含 `LICENSE`、`NOTICE`、`REVISION`),对应提交见 `REVISION`。本项目运行时不直读上游任何文件:`preset/` 已自有化全部 `agent.cordis.yml` 与预设 JS 脚本(shared / context-gate / compaction-epoch / custom-bash / instruction-hint / skill-search / tool-bootstrap),生成时只注入 `usePtcMode` / `bootstrapMaxTokens` / `subagentFlash` 动态项;`upstream/` 仅用于溯源与 sync 对照。上游已转入维护期(2026-08-17);其 Project2 的 98/99/99 成绩来自当前实现之前的旧配置(issue #60),轨迹锚定有独立复现(#65),能力增益在小样本下未决(#51)。
176
-
177
- ## 许可
178
-
179
- 插件本体 MIT(Czerror)。首轮锚定机制源自 [xiaobright/dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard)(MIT),任务引导与 Flash 方案参考 [yjh051108/dsh-router-standard](https://github.com/yjh051108/dsh-router-standard)(MIT),缓存与工具面成本原则参考 [yjh051108/dsh-super-injector](https://github.com/yjh051108/dsh-super-injector)(MIT)。本地 `preset/` 下的 cordis 模板与 JS 脚本基于 DeepSeek Harness Standard 预设与上游 anchored-standard 修改,原始 DeepSeek 版权与 MIT 声明保留在 `upstream/dsh-anchored-standard/NOTICE` 与 `LICENSE`。
180
-
181
- ## 配置参考
182
-
183
- 挂载配置(cordis.patch.yml / profile patch):
184
-
185
- ```yaml
186
- - insert:
187
- - id: prompt-tool
188
- name: dsh-plugin-prompt-tool
189
- config:
190
- text: '' # 覆盖 preset.md 文本;默认读项目文件
191
- agentsText: '' # 覆盖 AGENTS.md 文本;默认读项目文件
192
- injectAgentsPrompt: false # 用 AGENTS.md 替换 instruction-hint 提示文本
193
- writeAgents: true # 写 ~/.dsh/AGENTS.md 受管块
194
- writePreset: true # 启用锚定预设(总开关)
195
- injectPrompt: true # we 确认后注入 preset.md
196
- skillSwitches: {} # 按 skills/* 目录名开关,未列出默认开启
197
- anchorFirstTurn: false # 追加任务引导
198
- anchorText: '' # 自定义引导文本(首句)
199
- anchorCustom: false # 使用自定义引导(首句)
200
- guideText: '' # 自定义引导文本(每轮)
201
- guideCustom: false # 使用自定义引导(每轮)
202
- subagentFlash: false # 子代理固定 Flash 模型
203
- subagentFlashProvider: 'deepseek-official'
204
- subagentFlashModel: 'deepseek-v4-flash'
205
- bootstrapMaxTokens: 0 # 首轮输出封顶:0=关闭;正整数=请求 #1 maxTokens
206
- usePtcMode: true # 使用 PTC 模式:true=晋升后切换为 Code Mode(run_code);false=恢复原生完整目录
207
- skillsDir: '' # 用户自定义技能目录;空 = 自动使用 prompt-tool profile 下 skills/ 副本
208
- skillRankBase: 250 # 技能候选排序基数
209
- residentAgentsPath: '' # 默认 ~/.dsh/AGENTS.md
210
- presetDir: '' # 默认 ~/.dsh/.agent-presets/prompt-tool/
211
- presetOrder: 5 # preset 显示顺序
212
- fallbackText: '' # preset.md 缺失或不可读时的回退文本
213
- ```
214
-
215
- TUI 命令:
216
-
217
- ```text
218
- /prompt-tool status
219
- /prompt-tool on|off|toggle <开关>
220
- /prompt-tool skill <技能目录名> on|off|toggle
221
- /prompt-tool bootstrapMaxTokens <正整数|0>
222
- ```
1
+ # dsh-plugin-prompt-tool — 层级提示词注入器
2
+
3
+ > 一切皆可注入:把 DSH 官方开放的全部注入层级收敛为一个可配置提示词注入引擎——注入什么、注入到哪一层、何时注入,全由提示词配置决定。
4
+
5
+ DSH 生态的提示词注入标准层:一个 `prompt-config-engine.mjs` 接线官方六个注入层级(`agent/pre-step`、`systemPrompt.section`、`systemPrompt.context`、`agent/request`、`llm/stream`、`tools/*`),内置 anchored 默认预设,开箱即用。
6
+
7
+ > 策略来源:工具目录锚定 [dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard)、近距离引导 [dsh-router-standard](https://github.com/yjh051108/dsh-router-standard)、缓存铁律 [dsh-super-injector](https://github.com/yjh051108/dsh-super-injector)。
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ dsh plugin --profile prompt-tool add dsh-plugin-prompt-tool # npm 安装
13
+ dsh plugin --profile prompt-tool add link:<本仓库绝对路径> # 本地源码(link 覆盖 registry)
14
+ dsh --profile prompt-tool # 首次启动自动补 dsh-web-app,二次启动生效
15
+ ```
16
+
17
+ 需要 DSH `0.1.1-rc.1+`。
18
+
19
+ ## 特性
20
+
21
+ - 🔌 **六层一次接线**:一个引擎注册全部可注入层级,共享同一套过滤与降级语义
22
+ - ✍️ **一切皆可配置**:`layer / strategy / position / promotion / subagents / modelScope / mergeMode / order / text / texts / fill / variables / params` 全开放
23
+ - 🧑‍🤝‍🧑 **子代理三态**:`subagents: none / inherit / only`,身份类提示词可只注入子代理
24
+ - 🗂️ **内容与执行分离**:每条提示词配置渲染为 `~/.dsh/.agent-presets/<预设>/prompt-configs/` 下的 yml,引擎按文件名数字前缀顺序扫描
25
+ - 🧩 **三层合并**:引擎默认(按 params 生成)< 模板默认 promptConfigs < 预设 promptConfigs,同名 `id` 覆盖
26
+ - 🖥️ **独立工作台**:侧边栏「提示词工具」入口(主会话/子代理/注入层/技能设置/预设配置五页)+ 设置面板提示词配置页(列表/表单/模板插入/保存前校验)
27
+ - 🧪 **七种内容策略**:`static / first-turn-anchor / guide-auto / custom-fallback / instruction-hint / placeholder / world-book`(world-book 支持 ST selectiveLogic 选择性触发:任一/副键全中/排除)
28
+ - 🛡️ **失败不伤会话**:单条失败跳过 + `warnOnce`;配置错误挂载时 fail loud;`dedupe: session` 持久幂等
29
+ - 🎭 **SillyTavern 导入**:JSON 预设卡片一键转换为本地预设——`prompts[]` 映射提示词配置、setvar/getvar 收集进顶层 `variables`(未定义自定义宏自动登记空值占位)、`enable_web_search` 按开关装配工具;采样参数剥离(模型设置 UI 管理)
30
+ - 🎴 **角色卡库**:SillyTavern 角色卡(PNG tEXt chunk `ccv3`/`chara`,或 chara_card JSON)导入独立库(`.characters/<id>/`,含原图/转换参数/角色记忆),按需「导入到当前预设」(`chara-<卡>-` 前缀合并、幂等可移除),多文件自动合并
31
+ - 📚 **世界书**:`character_book` world-book 策略配置(`keys` 命中触发 / `constant` 常驻 / `useRegex` 正则 / `selectiveLogic` 组合逻辑),与模块卡片同一存储与编辑(模块列表「世界书」过滤 + 批量启用/禁用)
32
+ - 🧩 **模板变量**:预设级 `variables` 段(`{{key}}` 插值源)——模块列表顶部「模板变量」卡片统一编辑(可折叠/清空/停用/失焦自动保存);锚定匹配引擎(anchor-match)统一 custom-fallback 与 world-book 的匹配语义
33
+ - 💬 **会话变量工具**:`session_var`(list/get/set/clear)——模型维护角色状态(`{{心情}}` 等),会话级覆盖预设默认;ST 运行时宏(`{{lastusermessage}}` / `{{lastcharmessage}}`)从会话事件提取
34
+
35
+ ## 预设参数体系
36
+
37
+ 预设行为由一份 `preset.yml` 单一配置源下发,共四层默认值,各层职责不重叠:
38
+
39
+ | 层 | 职责 |
40
+ |---|---|
41
+ | `hostDefaults` | settings 层默认值(开关/路径),被用户 settings 覆盖 |
42
+ | `params` | 引擎行为默认(锚定/引导/PTC/门控/模型/工具),被运行时 settings 覆盖 |
43
+ | `moduleConfigs` | 引擎组合行级 config(persona 文本/超时/白名单) |
44
+ | `promptConfigs` | 注入提示词配置(策略/层/位置/时机),与目录、settings 三源合并 |
45
+
46
+ ### params 一览(全部可选,缺省 = 官方默认)
47
+
48
+ | 分类 | |
49
+ |---|---|
50
+ | 锚定 | `firstTurnAnchor` `firstTurnCustom` `firstTurnText` `firstTurnWord` `firstTurnBuild` `firstTurnInspect` `firstTurnDeep` |
51
+ | 引导 | `guideCustom` `guideText` `guideComplexPattern` `guideWeak` `guideDeep` `buildPattern` `complexPattern` |
52
+ | PTC/门控 | `usePtcMode` `bootstrapMaxTokens` `injectPrompt` `allowKinds` |
53
+ | 主对话模型 | `modelProvider` `modelName` `modelReasoningEffort` `modelTemperature` `modelMaxTokens` |
54
+ | 子代理模型 | `subagentModelProvider` `subagentModelName` `subagentReasoningEffort` `subagentTemperature` `subagentMaxTokens` |
55
+ | 人设 | 模块列表的 `persona-main`(system-section + `deployment:persona`,complete 互斥 + suppressRuntimeContext);`subagentPersona`(子代理显式,缺省经 scope 链继承主会话) |
56
+ | 工具集 | `toolFilterAllow` `toolFilterDeny`(子代理 toolFilter;主对话 tool-filter 模块共用) |
57
+ | 深度 | `maxDepth`(0 禁止委派 / `provider-managed` / 正整数) |
58
+
59
+ > 根目录 **`preset.yml`** 是配置参数齐全、逐项注释的完整模板,复制即得自定义预设起点。
60
+
61
+ ## 提示词配置(六层全家桶)
62
+
63
+ | `layer` | 官方通道 | 关键参数 |
64
+ |---|---|---|
65
+ | `pre-step` | `agent/pre-step` 消息批(默认层) | `position / dedupe / promotion / subagents / modelScope / strategy` |
66
+ | `system-section` | `ctx.systemPrompt.section` 静态段 | `order / text / templateFile / variables / params.complete / params.sectionName` |
67
+ | `runtime-context` | `ctx.systemPrompt.context` 动态快照 | `order / text / variables / params.contextName` |
68
+ | `agent-request` | `agent/request`(LlmCallConfig) | `params.patch`(浅合并)/ `params.replace`(整体替换) |
69
+ | `llm-stream` | `llm/stream`(流包装) | `params.mode=pass\|replace` |
70
+ | `tool-pipeline` | `tools/*`(pre/execute/post) | `params.toolNames`、`preDecision=allow\|deny\|ask`、`postAction=accept\|replace\|block` |
71
+
72
+ 默认四条:`00-near-anchor`(首句锚点)、`10-router-guide`(每轮引导)、`20-prompt-injector`(we 确认后注入 preset.md 一次)、`30-instruction-hint`(指令文件提示)。
73
+
74
+ - `mergeMode`:`separate`(默认)同位置多条为独立消息;`merged` 同位置拼接为一条
75
+ - `order`:数值小者更靠近插入锚点,同时决定 `merged` 组内拼接顺序
76
+ - 文本插值:`{{key}}` 全层支持——配置/预设 `variables` 优先,ST 运行时宏(lastusermessage 等)次之,内置 `{{DSH_HOME}}/{{WORKSPACE}}/{{CWD}}` 兜底,未注册保留字面(system-section 注册期无会话时运行时宏替换为空,不残留)
77
+
78
+
79
+ ## SillyTavern 导入
80
+
81
+ 工作台「预设配置」页导入 SillyTavern JSON 预设卡片(导入包无定义文件、仅含单个 `.json` 时自动识别转换),按注入层级映射为本地预设:
82
+
83
+ - `prompts[]` → `promptConfigs`:`system_prompt + role=system` → `system-section`(多条可 `mergeMode: merged` 拼接);其余 → `pre-step`(`injection_position=0` → `before-all`,否则 `after-user`);OFF 状态与 `injection_order` 原样保留
84
+ - 采样参数(`temperature` / `openai_max_tokens` / `reasoning_effort`)**剥离**——模型参数统一由「模型设置」UI / 宿主默认管理
85
+ - ST 变量:`setvar`/`getvar`(含默认值)收集进顶层 `variables`;未定义自定义宏自动登记空值占位(不留字面)
86
+ - `enable_web_search`:`true` → 组装 `tool-web`(fetch 启用);`false` → 不组装,改加 `tool-filter` 黑名单 `web_search / web_fetch`
87
+ - `modules` 按需装配:`prompt-config-engine` 始终;含 system-section 时补 `persona`(`complete: false` 允许 system 段生效)
88
+
89
+ 转换结果是一个普通预设(id 由文件名生成),可在工作台预设切换器中直接使用。字段级参数对照与完整示例见 [SillyTavern.md](SillyTavern.md)。
90
+
91
+ ### 角色卡(PNG / JSON)与角色卡库
92
+
93
+ 工作台「角色管理」页导入角色卡到**角色卡库**(`~/.dsh/.agent-presets/.characters/<id>/`):
94
+
95
+ - **PNG**:tEXt chunk(`ccv3` 优先 / `chara` 兜底)base64 解析,原图存 `avatar.png`(字节无损)
96
+ - **JSON**:chara_card_v2/v3 直接转换;多文件(角色卡 × 响应预设)自动合并
97
+ - 正文映射:`first_mes` → 开场白(`dedupe: session`)、`alternate_greetings` → 备用开场白、
98
+ `description/personality/scenario` 角色设定;采样参数剥离(模型设置 UI 管理)
99
+ - **导入到当前预设**:参数合并进当前预设 promptConfigs(`chara-<卡>-` 前缀、幂等);可一键移除
100
+ - **角色记忆**:`memory.md` 跟随角色卡跨预设,应用时合并为 world-book constant 配置注入
101
+
102
+ ### 世界书(world-book 策略)
103
+
104
+ `character_book` 条目转 world-book 策略配置(与普通模块同一存储/编辑):
105
+
106
+ - **注入语义**:`constant` 常驻注入;有 `keys` 命中聊天内容才注入;无 keys 全局每次注入
107
+ - **匹配选项**:`caseSensitive` / `wholeWords` / `useRegex`(keys 按正则)
108
+ - **管理**:模块列表顶部下拉选「世界书」过滤(完整模块卡片编辑 + 批量启用/禁用);
109
+ 模型工具 `world_book_list/upsert/delete`(`note` 写入角色卡记忆)
110
+ - **ST 变量**:`setvar`/`getvar` 收集进顶层 `variables`、未定义自定义宏自动登记空值占位;
111
+ `trim`/注释/ERA 剥离,`{{user}}`/`{{char}}` 替换;运行时宏(lastusermessage/lastcharmessage)
112
+ 从会话事件提取;TavernHelper 扩展注入物自动剥离
113
+ - **会话变量**:`session_var` 工具(list/get/set/clear)维护角色状态(会话级覆盖预设默认,
114
+ 结束即失);跨会话长期记忆用 `world_book` note(持久 memory.md 跟随角色卡)
115
+
116
+ 详细转换规则见 [SillyTavern.md](SillyTavern.md)。
117
+
118
+ ## 开发与验证
119
+
120
+ ```sh
121
+ pnpm install && pnpm build
122
+ pnpm test # 287 单测:渲染/合并/六层接线/preset 生成/锚定匹配/插值/会话变量
123
+ pnpm typecheck && pnpm lint
124
+ pnpm sync:anchored # 刷新 upstream/dsh-anchored-standard 内联快照
125
+ pnpm sync:yaml # 刷新 engine/vendor/yaml(生成目录运行时 YAML 解析器)
126
+ pnpm rebuild:composition # 从官方内置预设源码重建组合模块
127
+ ```
128
+
129
+ ## 许可
130
+
131
+ 插件本体 MIT(Czerror)。默认预设策略来源见顶部引用;`preset/` cordis 模板与脚本基于 DeepSeek Harness 官方 Standard 预设修改,版权声明见 `upstream/dsh-anchored-standard/`。
@@ -0,0 +1,117 @@
1
+ /**
2
+ * anchor-match — 关键词锚定匹配引擎(纯函数,无状态,无依赖)。
3
+ *
4
+ * 从 strategies 拆解:custom-fallback(自定义锚定词)与 world-book(关键词触发)
5
+ * 共用同一套匹配语义——主键/副键、大小写、整词、正则、组合逻辑。
6
+ *
7
+ * 逻辑对齐 ST world_info_logic:
8
+ * any = AND_ANY(0):主键或副键任一命中即激活(world-book 默认 / custom-fallback 单锚);
9
+ * all = AND_ALL(3):主键命中且副键全部命中;
10
+ * not = NOT_ALL(1):主键命中且副键全部未命中(排除);
11
+ * notAny = NOT_ANY(2):主键命中且至少一个副键未命中(部分排除)。
12
+ *
13
+ * 模式:
14
+ * scan = 全文扫描(world-book 消息批匹配);
15
+ * prefix = 文本开头匹配(custom-fallback 首轮 reasoning 锚定确认:ASCII 词边界前缀,
16
+ * 非 ASCII 直 prefix)。
17
+ */
18
+
19
+ /** 组合逻辑(ST world_info_logic 映射)。 */
20
+ export const MATCH_LOGIC = {
21
+ ANY: 'any',
22
+ ALL: 'all',
23
+ NOT: 'not',
24
+ NOT_ANY: 'notAny',
25
+ }
26
+
27
+ /** 正则转义(关键词原样匹配时用)。 */
28
+ export function escapeRegExp(text) {
29
+ return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
30
+ }
31
+
32
+ /** 单键正则编译(逐键匹配:any/all/not 需要精确的命中键数,捕获组会干扰 match 计数)。 */
33
+ function compileSingle(key, { caseSensitive, wholeWords, useRegex }) {
34
+ const flags = caseSensitive ? '' : 'i'
35
+ if (useRegex) {
36
+ // ST use_regex=true:键原样作为正则(作者负责合法性)。
37
+ return new RegExp(key, flags)
38
+ }
39
+ if (wholeWords) {
40
+ return new RegExp(`(^|[^\\p{L}\\p{N}])(${escapeRegExp(key)})(?![\\p{L}\\p{N}])`, `${flags}u`)
41
+ }
42
+ return new RegExp(escapeRegExp(key), flags)
43
+ }
44
+
45
+ /** 编译键列表 → [{ key, re }](剔除空键)。 */
46
+ function compileKeyList(list, options) {
47
+ return (Array.isArray(list) ? list : [])
48
+ .map((key) => String(key).trim())
49
+ .filter((key) => key.length > 0)
50
+ .map((key) => ({ key, re: compileSingle(key, options) }))
51
+ }
52
+
53
+ /**
54
+ * 创建锚定匹配器。
55
+ * @param {object} options
56
+ * @param {string[]} options.keys 主键
57
+ * @param {string[]} [options.secondaryKeys] 副键
58
+ * @param {boolean} [options.caseSensitive]
59
+ * @param {boolean} [options.wholeWords]
60
+ * @param {boolean} [options.useRegex]
61
+ * @param {'any'|'all'|'not'|'notAny'} [options.logic] 组合逻辑(缺省 any)
62
+ * @param {'scan'|'prefix'} [options.mode] 匹配模式(缺省 scan)
63
+ * @returns {{ scan: (text: string) => { primary: number, secondary: number, active: boolean } }}
64
+ */
65
+ export function createAnchorMatcher(options = {}) {
66
+ const {
67
+ keys = [],
68
+ secondaryKeys = [],
69
+ caseSensitive = false,
70
+ wholeWords = false,
71
+ useRegex = false,
72
+ logic = MATCH_LOGIC.ANY,
73
+ mode = 'scan',
74
+ } = options
75
+ const primaryList = compileKeyList(keys, { caseSensitive, wholeWords, useRegex })
76
+ const secondaryList = compileKeyList(secondaryKeys, { caseSensitive, wholeWords, useRegex })
77
+
78
+ const scan = (raw) => {
79
+ const text = String(raw ?? '')
80
+ if (mode === 'prefix') {
81
+ // custom-fallback 锚定确认:仅主键首词,文本开头匹配。
82
+ const word = (Array.isArray(keys) ? keys : []).map((key) => String(key).trim())
83
+ .find((key) => key.length > 0)
84
+ if (word === undefined || text.length === 0) return { primary: 0, secondary: 0, active: false }
85
+ const hit = /^[\x20-\x7E]+$/.test(word)
86
+ ? new RegExp(`^${escapeRegExp(word.toLowerCase())}\\b`, 'i').test(text)
87
+ : text.startsWith(word)
88
+ return { primary: hit ? 1 : 0, secondary: 0, active: hit }
89
+ }
90
+ if (primaryList.length === 0 && secondaryList.length === 0) {
91
+ return { primary: 0, secondary: 0, active: false }
92
+ }
93
+ const countHits = (list) => {
94
+ let hits = 0
95
+ for (const { re } of list) {
96
+ re.lastIndex = 0
97
+ if (re.test(text)) hits += 1
98
+ }
99
+ return hits
100
+ }
101
+ const primary = countHits(primaryList)
102
+ const secondary = countHits(secondaryList)
103
+ let active = false
104
+ if (logic === MATCH_LOGIC.ALL) {
105
+ active = primary > 0 && (secondaryList.length === 0 || secondary === secondaryList.length)
106
+ } else if (logic === MATCH_LOGIC.NOT) {
107
+ active = primary > 0 && secondary === 0
108
+ } else if (logic === MATCH_LOGIC.NOT_ANY) {
109
+ active = primary > 0 && secondaryList.length > 0 && secondary < secondaryList.length
110
+ } else {
111
+ active = primary > 0 || secondary > 0
112
+ }
113
+ return { primary, secondary, active }
114
+ }
115
+
116
+ return { scan }
117
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Epoch-aware promotion tracker shared by the bootstrap and baseline-gate
3
+ * plugins of the anchored presets.
4
+ *
5
+ * A compaction rewrites the model-visible surface: the pre-compaction
6
+ * conversation collapses into one synthetic summary message, and the
7
+ * workspace-instruction baseline is re-injected from scratch. The first
8
+ * post-compaction request is therefore a "second first request" — the same
9
+ * first-token conditions the anchored presets exist to control. Promotion is
10
+ * epoch-aware: only a durable promotion signal (`tool/call` and/or
11
+ * `assistant/message`, per the caller's `promoteEvents`) recorded AFTER the
12
+ * last `compaction/end` boundary counts as promoted. Before any compaction
13
+ * the boundary is -1, which preserves the original one-shot semantics.
14
+ *
15
+ * State is memoized per session id and maintained incrementally through
16
+ * `observe()`; a cold session scans its durable log once (so resume and
17
+ * reload reconstruct the same phase), then O(1).
18
+ *
19
+ * By default subagents (`delegationDepth > 0`) are treated as already
20
+ * promoted so their first request can use tools. Set `includeSubagents: true`
21
+ * to make subagents follow the same bootstrap/anchor phase as top-level
22
+ * sessions.
23
+ *
24
+ * GATE MODE (liangshen 稳定化扩展, source: xiaobright/dsh-anchored-standard
25
+ * MIT + phase-1 quarantine): `promoteGate: true` gates the promotion on the
26
+ * first reasoning block classifying minimal-like (`we` present, no `let me`),
27
+ * with a `maxPromoteSteps` (default 4) fallback; `promoteAfterFirstResponse:
28
+ * true` promotes a tool-less first response once it has responded, and also
29
+ * releases an anchor-gated session when its first turn ends. Gate mode uses
30
+ * the durable-event state machine below and ignores `promoteEvents` (fixed
31
+ * either semantics: `tool/call` and `assistant/message` are both tracked).
32
+ * Non-gate mode keeps the original event-set semantics byte-for-byte.
33
+ */
34
+
35
+ /** 首段 reasoning 块分类(liangshen 移植):we 且无 let me = minimal-like。 */
36
+ export function classifyReasoning(text) {
37
+ const trimmed = String(text ?? '').trim()
38
+ const we = [...trimmed.matchAll(/\bwe\b/gi)].length
39
+ const letMe = [...trimmed.matchAll(/\blet me\b/gi)].length
40
+ const metrics = { we, letMe }
41
+ if (we > 0 && letMe === 0) return { label: 'minimal-like', score: 4, metrics }
42
+ if (letMe > 0) return { label: 'standard-like', score: -4, metrics }
43
+ return { label: 'ambiguous', score: 0, metrics }
44
+ }
45
+
46
+ /** 首段 reasoning 块是否为 minimal-like(后续块不覆盖首个标准样块)。 */
47
+ export function hasAnchoredReasoning(content) {
48
+ if (!Array.isArray(content)) return false
49
+ const first = content.find((block) => block?.type === 'reasoning')
50
+ return first !== undefined && classifyReasoning(first.text).label === 'minimal-like'
51
+ }
52
+
53
+ /** Build one epoch-aware promotion tracker. */
54
+ export function createEpochPromotion(promoteEvents, options = {}) {
55
+ const includeSubagents = options.includeSubagents === true
56
+ const promoteGate = options.promoteGate === true
57
+ const promoteAfterFirstResponse = options.promoteAfterFirstResponse === true
58
+ const maxPromoteSteps = Number.isSafeInteger(options.maxPromoteSteps) && options.maxPromoteSteps > 0
59
+ ? options.maxPromoteSteps
60
+ : 4
61
+ const promote = new Set(promoteEvents)
62
+ const gated = promoteGate || promoteAfterFirstResponse
63
+ /** sessionId -> entry(boundary/promoted + 门控字段) */
64
+ const state = new Map()
65
+
66
+ const freshEntry = (boundary) => ({
67
+ boundary,
68
+ promoted: false,
69
+ toolCalled: false,
70
+ responded: false,
71
+ anchored: false,
72
+ turnEnded: false,
73
+ steps: 0,
74
+ })
75
+
76
+ /** 门控晋升判定(liangshen decidePromotion 移植)。 */
77
+ const decideGate = (entry) => {
78
+ if (entry.promoted) return true
79
+ if (entry.toolCalled && !promoteGate) return true
80
+ if (entry.toolCalled && promoteGate && (entry.anchored || entry.steps >= maxPromoteSteps)) return true
81
+ if (entry.toolCalled && promoteGate && promoteAfterFirstResponse && entry.turnEnded) return true
82
+ if (!entry.toolCalled && entry.responded && promoteAfterFirstResponse) return true
83
+ return false
84
+ }
85
+
86
+ /** 应用一个事件;compaction/end 返回新 entry(旧状态清零、boundary 前推)。 */
87
+ const applyEvent = (entry, event) => {
88
+ const seq = event.seq ?? 0
89
+ if (event.type === 'compaction/end') return freshEntry(seq)
90
+ if (seq <= entry.boundary) return entry
91
+ if (gated) {
92
+ if (event.type === 'tool/call') entry.toolCalled = true
93
+ else if (event.type === 'step/start') entry.steps += 1
94
+ else if (event.type === 'turn/end') entry.turnEnded = true
95
+ else if (event.type === 'assistant/message') {
96
+ entry.responded = true
97
+ if (!entry.anchored) entry.anchored = hasAnchoredReasoning(event.data?.message?.content)
98
+ }
99
+ if (decideGate(entry)) entry.promoted = true
100
+ return entry
101
+ }
102
+ if (promote.has(event.type)) entry.promoted = true
103
+ return entry
104
+ }
105
+
106
+ /** Scan a session's durable log from scratch (cold start / resume). */
107
+ const scan = (session) => {
108
+ let entry = freshEntry(-1)
109
+ for (const event of session.events) entry = applyEvent(entry, event)
110
+ state.set(session.id, entry)
111
+ return entry
112
+ }
113
+
114
+ return {
115
+ /**
116
+ * Current phase of the agent's session.
117
+ * @param agent - the assembly/pre-step agent, or undefined outside an agent.
118
+ * @returns { boundary, promoted } — `boundary` is the last compaction/end
119
+ * seq (-1 before any compaction); `promoted` is true when a durable
120
+ * promotion signal exists after that boundary.
121
+ */
122
+ status(agent) {
123
+ if (agent === undefined) return { boundary: -1, promoted: true }
124
+ const session = agent.session
125
+ if (session === undefined) return { boundary: -1, promoted: true }
126
+ // By default subagents keep the full catalog from their very first
127
+ // request; includeSubagents makes them follow the normal bootstrap phase.
128
+ if (!includeSubagents && (session.header?.delegationDepth ?? 0) > 0) return { boundary: -1, promoted: true }
129
+ return state.get(session.id) ?? scan(session)
130
+ },
131
+ /** Incremental feed: call on every `session/event`. */
132
+ observe(session, event) {
133
+ const entry = state.get(session.id)
134
+ if (entry === undefined) return
135
+ const next = applyEvent(entry, event)
136
+ if (next !== entry) state.set(session.id, next)
137
+ },
138
+ }
139
+ }
@@ -0,0 +1,19 @@
1
+ # module: bootstrap-filesystem
2
+ # source: E:\Documents\GitHub\deepseek-harness/apps/cli/config/agent-presets/minimal/agent.cordis.yml
3
+ # local patches: 1
4
+
5
+ - id: bootstrap-filesystem
6
+ name: cordis:group
7
+ group: true
8
+ isolate:
9
+ fs: true
10
+ config:
11
+ - id: fs-local
12
+ name: '@deepseek-ai/dsh-fs-local'
13
+ config:
14
+ cwd: !!js process.env.DSH_CWD ?? process.cwd()
15
+
16
+ - id: str-replace-editor
17
+ name: '@deepseek-ai/dsh-tool-str-replace-editor'
18
+ config:
19
+ maxOutputChars: 16000