dsh-plugin-prompt-tool 0.2.0 → 0.3.1

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,68 +1,101 @@
1
1
  # 提示词工具(dsh-plugin-prompt-tool)
2
2
 
3
- DSH 插件:为 DeepSeek Harness 提供三层提示词规范注入,并通过 Web 界面在线编辑。核心是“最优组合”首轮锚定:保持官方 Minimal(极简)训练分布的首轮条件,在模型轨迹稳定后再恢复完整能力。
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
4
 
5
- ## 来源与参考
5
+ ## 原理
6
6
 
7
- | 项目 | 关系 | 复用内容 |
8
- |---|---|---|
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) | 参考 | 缓存铁律、工具面成本原则 |
7
+ DeepSeek V4 会强烈依赖 API 中可见的**首轮工具目录与 persona** 选择执行轨迹。完整 Standard 目录与自动注入在场会破坏 Minimal 轨迹,而全程 Minimal 又会失去重型工具。本项目把「首次轨迹选择」与「后续完整工具能力」拆开:
12
8
 
13
- 上游文件固化在 `upstream/dsh-anchored-standard/`(含 `LICENSE`、`NOTICE`、`REVISION`),
14
- `pnpm sync:anchored` 更新。
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`(先到者为准)落地后,目录进入 resident 集——bootstrap 双工具 + 发现工具 + 已解锁工具;上下文与注入恢复,重型工具经 `dev_tool_search` 按需解锁。
11
+ 3. **任务引导**:可选 `near-anchor` 在首条真实用户消息后追加一次近距离首句引导(we/let 按任务自动选择);Flash 主会话晋升后 `router-guide` 按任务复杂度追加每轮深度引导。
12
+ 4. **提示词注入**:we 锚定确认后把 `preset.md` 作为用户消息注入一次;we 未确认最多等一轮兜底,绝不卡死。
13
+ 5. **子代理**:目录直接全量放行(仍受调用方工具白名单过滤);可选 `subagentFlash` 固定 Flash 路由 + 任务分类人设 + 三锚。
15
14
 
16
- ## 项目说明
15
+ 阶段全部从持久 session events 推导,resume / reload 不丢失状态。
17
16
 
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 子模块。
17
+ ## 稳定化控制
24
18
 
25
- ## 构建 / 装载
19
+ 全部在生成 preset `agent.cordis.yml` 行中配置:
26
20
 
27
- ```sh
28
- pnpm install
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 参数)
35
- ```
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
+ - `router-first-turn`:只替换 persona 段,保留 plan-mode 段与第三方 section;首轮隐藏 `mnemon:*` 自动注入段,晋升后恢复。
25
+ - `near-anchor`:行为引导放在真实用户消息之后(近距离零衰减),只要求首句一次;复杂规划任务放行 Let 深度路径,日常任务锚 We。
26
+ - `router-guide`:简单任务快速收敛,复杂任务深度引导;子代理不注入,首轮不注入。
27
+ - `prompt-injector`:注入状态以持久事件为准,跨进程重启 / 插件热重载不重复注入。
28
+ - `injectAgentsPrompt`:开启时用 `AGENTS.md` 内容替换上游 instruction-hint 提示文本;关闭时保留上游原版 hint。
29
+ - `skillSwitches`:扫描 `skills/*/SKILL.md` 注册可开关技能,未列出的技能默认开启。
30
+ - `subagentFlash`:检测不到 DeepSeek 模型路由时 Web/TUI 禁用并强制降级为关闭。
36
31
 
37
- 本插件挂载在独立 profile(`prompt-tool`):
32
+ ## 安装
38
33
 
39
34
  ```sh
35
+ # 1. 先安装宿主服务(提供 webServer 等本插件依赖的服务)
36
+ dsh plugin --profile prompt-tool add @deepseek-ai/dsh-web-app
37
+ # 或使用 dsh-tui:dsh plugin --profile prompt-tool add @deepseek-harness-tui/dsh-tui
38
+
39
+ # 2. 安装本插件(官方插件指令,自动加入 profile bundles)
40
+ dsh plugin --profile prompt-tool add dsh-plugin-prompt-tool
41
+
42
+ # 3. 本地开发安装(任选其一,会覆盖 registry 依赖)
40
43
  dsh plugin --profile prompt-tool add link:<本仓库绝对路径>
44
+
45
+ # 4. 启动
41
46
  dsh --profile prompt-tool
47
+ dsh web
48
+ dsh-tui
49
+
50
+ # 卸载
42
51
  dsh plugin --profile prompt-tool remove dsh-plugin-prompt-tool
43
52
  ```
44
53
 
45
- 临时调试可用官方 `--patch` 覆盖层(不落盘、不改 profile):
54
+ 装完**完整重启 `dsh web`**,新建空 session,预设选择 **prompt-tool**。插件会在启动时生成并刷新 `~/.dsh/.agent-presets/prompt-tool/`(升级插件后重启即自动更新)。
55
+
56
+ ## 验证
57
+
58
+ 导出 session JSONL,检查 `request/header`:
59
+
60
+ - 第一份 header 的 `tools` 应恰好是 `["bash", "str_replace_editor"]`;
61
+ - 第一轮只包含用户消息与首句锚点:没有 workspace 指令 baseline、运行时快照、skill 目录消息;
62
+ - 首次工具调用或首次助手回复后,下一份变更 header 应包含 resident 目录:bootstrap 双工具 + `dev_tool_search` / `skill_search` / `skill_load` + 已解锁工具;
63
+ - 此后的请求保持 resident 集,只经 `dev_tool_search` 显式解锁增长;
64
+ - `we` 锚定确认后,事件流出现一次 `source.plugin === 'prompt-injector'` 的消息;未确认时最多等一轮兜底。
65
+
66
+ 本项目测试:
46
67
 
47
68
  ```sh
48
- dsh --profile prompt-tool --patch <cordis.yml>
69
+ pnpm test # pnpm build + node --test
70
+ pnpm typecheck # Host 与 Client 两个 tsc program
71
+ pnpm lint # oxlint
49
72
  ```
50
73
 
51
- 完全重启 dsh 后,新建会话并选择 prompt-tool 预设。
74
+ ## 行为与限制
52
75
 
53
- ## 原理
76
+ - Windows:DSH 的 PTY 后端仅支持 linux/darwin,持久 shell 组禁用;phase-1 的 `bash` 切换为 `custom-bash`——同名且 schema 与 Minimal 兼容,经普通跨平台子进程通道调起 Git Bash(运行时探测安装路径,不硬编码)。
77
+ - Linux/macOS:持久 shell 的 `shellPath` 自适应——`/bin/bash` 存在时保持默认,不存在(如 NixOS)回退 PATH 里的 `bash`。
78
+ - 首轮能力类提问可能基于被裁剪的双工具视图作答,晋升后由后续工具纠正;需要时可开启任务引导或直接首轮问任务类问题。
79
+ - 工具目录只变化一次(晋升点),因此第一、二次请求之间发生一次前缀缓存变化;之后每次 `dev_tool_search` 解锁再变化。
80
+ - preset 与 shell 访问具有相同信任等级,安装前可自行审阅 `upstream/` 与生成目录。
81
+ - 插件只监听本机回环设置桥,不发起网络请求,也不增加遥测。
82
+ - 不要在已经产生内容的会话中途切换 preset。
83
+ - 需要 DSH 0.1.0-rc.5+(preset 机制与 `system-prompt/assemble` 钩子)。
54
84
 
55
- 模型的首轮请求结构决定整条会话轨迹。上游实测:官方 Minimal 首轮(训练原句 + 持久 `bash` + `str_replace_editor`,无输出封顶)稳定锚定 we 轨迹;完整 Standard 目录与自动注入在场会破坏锚定。
85
+ ## 构建与上游管理
56
86
 
57
- 本插件把“首轮轨迹选择”和“后续完整能力”拆开:
87
+ ```sh
88
+ pnpm install
89
+ pnpm build # 生成 lib/
90
+ pnpm prepare # npm publish / git install 前自动触发
91
+ pnpm sync:anchored # 从上游 main 刷新内联快照(可加 ref 参数)
92
+ ```
93
+
94
+ 上游 `dsh-anchored-standard` 以内联快照形式固化在 `upstream/dsh-anchored-standard/`(含 `LICENSE`、`NOTICE`、`REVISION`),对应提交见 `REVISION`。上游已转入维护期(2026-08-17);其 Project2 的 98/99/99 成绩来自当前实现之前的旧配置(issue #60),轨迹锚定有独立复现(#65),能力增益在小样本下未决(#51)。
58
95
 
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 路由、任务分类人设与三锚。
96
+ ## 许可
64
97
 
65
- 主会话首轮始终只有两个工具;模型性能不受记忆、技能等自动注入影响,完整能力在轨迹稳定后恢复。
98
+ 插件本体 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)。上游 `agent.cordis.yml` 基于 DeepSeek Harness Standard 预设修改,原始 DeepSeek 版权与 MIT 声明保留在 `upstream/dsh-anchored-standard/NOTICE` 与 `LICENSE`。
66
99
 
67
100
  ## 配置参考
68
101
 
@@ -76,17 +109,19 @@ dsh --profile prompt-tool --patch <cordis.yml>
76
109
  text: '' # 覆盖 preset.md 文本;默认读项目文件
77
110
  agentsText: '' # 覆盖 AGENTS.md 文本;默认读项目文件
78
111
  injectAgentsPrompt: false # 用 AGENTS.md 替换 instruction-hint 提示文本
79
- writeAgents: true # 写 ~/.dsh/AGENTS.md 受管块(默认 true)
80
- writePreset: true # 启用锚定预设(总开关,默认 true)
81
- injectPrompt: true # we 确认后注入 preset.md(默认 true)
112
+ writeAgents: true # 写 ~/.dsh/AGENTS.md 受管块
113
+ writePreset: true # 启用锚定预设(总开关)
114
+ injectPrompt: true # we 确认后注入 preset.md
82
115
  skillSwitches: {} # 按 skills/* 目录名开关,未列出默认开启
83
- anchorFirstTurn: false # 追加任务引导(默认关闭)
84
- anchorText: '' # 自定义引导文本
85
- anchorCustom: false # 使用自定义引导(true=固定使用 anchorText;false=自动选择)
86
- subagentFlash: false # 子代理固定 Flash 模型(默认关闭)
116
+ anchorFirstTurn: false # 追加任务引导
117
+ anchorText: '' # 自定义引导文本(首句)
118
+ anchorCustom: false # 使用自定义引导(首句)
119
+ guideText: '' # 自定义引导文本(每轮)
120
+ guideCustom: false # 使用自定义引导(每轮)
121
+ subagentFlash: false # 子代理固定 Flash 模型
87
122
  subagentFlashProvider: 'deepseek-official'
88
123
  subagentFlashModel: 'deepseek-v4-flash'
89
- customBashPath: 'bash.exe' # 自定义 bash 路径(默认 PATH 查找)
124
+ bootstrapMaxTokens: 0 # 首轮输出封顶:0=关闭;正整数=请求 #1 maxTokens
90
125
  skillsDir: '' # 可选技能目录(默认包内 skills/)
91
126
  skillRankBase: 250 # 技能候选排序基数
92
127
  residentAgentsPath: '' # 默认 ~/.dsh/AGENTS.md
@@ -95,21 +130,11 @@ dsh --profile prompt-tool --patch <cordis.yml>
95
130
  fallbackText: '' # preset.md 缺失或不可读时的回退文本
96
131
  ```
97
132
 
98
- 字段说明:
99
-
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
- - 其他字段语义见上方配置注释;设置文件优先级高于挂载配置,首次安装时用项目文件内容初始化设置。
133
+ TUI 命令:
106
134
 
107
- ## 版权
108
-
109
- 本项目采用 MIT 许可证。
110
-
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` 继续保留。
135
+ ```text
136
+ /prompt-tool status
137
+ /prompt-tool on|off|toggle <开关>
138
+ /prompt-tool skill <技能目录名> on|off|toggle
139
+ /prompt-tool bootstrapMaxTokens <正整数|0>
140
+ ```
package/lib/client.js CHANGED
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
23
23
  }
24
24
  //#endregion
25
25
  //#region \0dsh-css:E:\Documents\GitHub\dsh-plugin-prompt-tool\src\client\PromptEditor.module.css.mjs
26
- const css = ".Ru2OGG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Ru2OGG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Ru2OGG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Ru2OGG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Ru2OGG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Ru2OGG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Ru2OGG_chevronOpen{transform:rotate(180deg)}.Ru2OGG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Ru2OGG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;margin:0 16px;padding:14px 0 16px;display:flex}.Ru2OGG_loading{color:var(--dsw-alias-label-tertiary);font-size:12px;display:inline-block}.Ru2OGG_group{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}.Ru2OGG_groupTitle{color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:600}.Ru2OGG_subSection{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;display:flex;overflow:hidden}.Ru2OGG_subHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:12px;padding:12px 14px;display:flex}.Ru2OGG_subHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_subTitle{color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:600;line-height:1.5}.Ru2OGG_subCount{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px}.Ru2OGG_subBody{flex-direction:column;gap:8px;padding:2px 14px 12px;display:flex}.Ru2OGG_subEmpty{color:var(--dsw-alias-label-tertiary);padding:4px 0 8px;font-size:12px}.Ru2OGG_row{cursor:pointer;align-items:flex-start;gap:10px;padding:6px 0;display:flex}.Ru2OGG_rowStack{flex-direction:column;align-items:stretch;gap:8px;padding:6px 0;display:flex}.Ru2OGG_row input{flex:none;margin-top:3px}.Ru2OGG_rowText{flex-direction:column;gap:2px;display:flex}.Ru2OGG_rowName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.Ru2OGG_rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.Ru2OGG_rowDisabled{opacity:.55}.Ru2OGG_anchorInput{box-sizing:border-box;width:100%;min-height:64px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6}.Ru2OGG_textarea{box-sizing:border-box;width:100%;min-height:420px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:10px;font-family:ui-monospace,monospace;font-size:13px;line-height:1.6}.Ru2OGG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.Ru2OGG_primary,.Ru2OGG_secondary{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Ru2OGG_primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Ru2OGG_secondary{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Ru2OGG_primary:disabled,.Ru2OGG_secondary:disabled{opacity:.55;cursor:default}.Ru2OGG_primary:focus-visible,.Ru2OGG_secondary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Ru2OGG_notice{color:var(--dsw-alias-state-success-primary);font-size:12px}.Ru2OGG_noticeError{color:var(--dsw-alias-state-error-primary,#d5484d)}";
26
+ const css = ".Ru2OGG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Ru2OGG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Ru2OGG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Ru2OGG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Ru2OGG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Ru2OGG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Ru2OGG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Ru2OGG_chevronOpen{transform:rotate(180deg)}.Ru2OGG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Ru2OGG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;margin:0 16px;padding:14px 0 16px;display:flex}.Ru2OGG_loading{color:var(--dsw-alias-label-tertiary);font-size:12px;display:inline-block}.Ru2OGG_group{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}.Ru2OGG_groupTitle{color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:600}.Ru2OGG_subSection{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;display:flex;overflow:hidden}.Ru2OGG_subHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:12px;padding:12px 14px;display:flex}.Ru2OGG_subHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Ru2OGG_subTitle{color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:600;line-height:1.5}.Ru2OGG_subCount{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px}.Ru2OGG_subBody{flex-direction:column;gap:8px;padding:2px 14px 12px;display:flex}.Ru2OGG_subEmpty{color:var(--dsw-alias-label-tertiary);padding:4px 0 8px;font-size:12px}.Ru2OGG_row{cursor:pointer;align-items:flex-start;gap:10px;padding:6px 0;display:flex}.Ru2OGG_rowStack{flex-direction:column;align-items:stretch;gap:8px;padding:6px 0;display:flex}.Ru2OGG_row input{flex:none;margin-top:3px}.Ru2OGG_rowText{flex-direction:column;gap:2px;display:flex}.Ru2OGG_rowName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.Ru2OGG_rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.Ru2OGG_rowDisabled{opacity:.55}.Ru2OGG_anchorInput{box-sizing:border-box;width:100%;min-height:64px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.6}.Ru2OGG_textarea{box-sizing:border-box;width:100%;min-height:420px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;padding:10px;font-family:ui-monospace,monospace;font-size:13px;line-height:1.6}.Ru2OGG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.Ru2OGG_primary,.Ru2OGG_secondary{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Ru2OGG_primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Ru2OGG_secondary{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Ru2OGG_primary:disabled,.Ru2OGG_secondary:disabled{opacity:.55;cursor:default}.Ru2OGG_primary:focus-visible,.Ru2OGG_secondary:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Ru2OGG_notice{color:var(--dsw-alias-state-success-primary);font-size:12px}.Ru2OGG_noticeError{color:var(--dsw-alias-state-error-primary,#d5484d)}.Ru2OGG_bootstrapTokensInput{box-sizing:border-box;width:96px;min-height:36px;max-height:48px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);text-align:right;border-radius:8px;flex:none;align-self:stretch;margin:0 0 0 auto;padding:4px 8px;font-size:12px;line-height:1.4}.Ru2OGG_bootstrapTokensInput:disabled{opacity:.55;cursor:default}";
27
27
  const tagId = "dsh-plugin-prompt-tool/PromptEditor.module.css";
28
28
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
29
  const tag = document.createElement("style");
@@ -36,6 +36,7 @@ window.__ModuleLoader__.load({
36
36
  "actions": "Ru2OGG_actions",
37
37
  "anchorInput": "Ru2OGG_anchorInput",
38
38
  "body": "Ru2OGG_body",
39
+ "bootstrapTokensInput": "Ru2OGG_bootstrapTokensInput",
39
40
  "card": "Ru2OGG_card",
40
41
  "cardOpen": "Ru2OGG_cardOpen",
41
42
  "chevron": "Ru2OGG_chevron",
@@ -97,7 +98,10 @@ window.__ModuleLoader__.load({
97
98
  anchorFirstTurn: false,
98
99
  anchorText: "",
99
100
  anchorCustom: false,
101
+ guideText: "",
102
+ guideCustom: false,
100
103
  subagentFlash: false,
104
+ bootstrapMaxTokens: 0,
101
105
  injectPrompt: true,
102
106
  skillSwitches: {},
103
107
  skillCatalog: [],
@@ -109,24 +113,32 @@ window.__ModuleLoader__.load({
109
113
  anchorFirstTurn: false,
110
114
  anchorText: "",
111
115
  anchorCustom: false,
116
+ guideText: "",
117
+ guideCustom: false,
112
118
  subagentFlash: false,
119
+ bootstrapMaxTokens: 0,
113
120
  injectPrompt: true,
114
121
  skillSwitches: {},
115
122
  writeAgents: true,
116
123
  writePreset: true
117
124
  };
125
+ /** 本项目默认不设上限时的显示值(adapter 默认 maxTokens)。 */
126
+ const DEFAULT_BOOTSTRAP_DISPLAY = "256000";
118
127
  const snapshotSwitches = (fields) => ({
119
128
  injectAgentsPrompt: fields.injectAgentsPrompt,
120
129
  anchorFirstTurn: fields.anchorFirstTurn,
121
130
  anchorText: fields.anchorText,
122
131
  anchorCustom: fields.anchorCustom,
132
+ guideText: fields.guideText,
133
+ guideCustom: fields.guideCustom,
123
134
  subagentFlash: fields.subagentFlash,
135
+ bootstrapMaxTokens: fields.bootstrapMaxTokens,
124
136
  injectPrompt: fields.injectPrompt,
125
137
  skillSwitches: { ...fields.skillSwitches },
126
138
  writeAgents: fields.writeAgents,
127
139
  writePreset: fields.writePreset
128
140
  });
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;
141
+ const switchesEqual = (a, b) => a.injectAgentsPrompt === b.injectAgentsPrompt && a.anchorFirstTurn === b.anchorFirstTurn && a.anchorText === b.anchorText && a.anchorCustom === b.anchorCustom && a.guideText === b.guideText && a.guideCustom === b.guideCustom && a.subagentFlash === b.subagentFlash && a.bootstrapMaxTokens === b.bootstrapMaxTokens && a.injectPrompt === b.injectPrompt && JSON.stringify(a.skillSwitches) === JSON.stringify(b.skillSwitches) && a.writeAgents === b.writeAgents && a.writePreset === b.writePreset;
130
142
  const asRecord = (value) => value !== null && typeof value === "object" ? value : {};
131
143
  const readString = (source, key) => {
132
144
  const value = source[key];
@@ -136,6 +148,10 @@ window.__ModuleLoader__.load({
136
148
  const value = source[key];
137
149
  return typeof value === "boolean" ? value : fallback;
138
150
  };
151
+ const readNumber = (source, key, fallback) => {
152
+ const value = source[key];
153
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
154
+ };
139
155
  const readSkillSwitches = (source, key) => {
140
156
  const value = source[key];
141
157
  if (value === null || typeof value !== "object") return {};
@@ -198,6 +214,7 @@ window.__ModuleLoader__.load({
198
214
  const [deepseekError, setDeepseekError] = (0, react.useState)("");
199
215
  const [loaded, setLoaded] = (0, react.useState)(false);
200
216
  const [fields, setFields] = (0, react.useState)(EMPTY);
217
+ const [bootstrapTokensDraft, setBootstrapTokensDraft] = (0, react.useState)(DEFAULT_BOOTSTRAP_DISPLAY);
201
218
  const [savedPromptText, setSavedPromptText] = (0, react.useState)("");
202
219
  const [savedAgentsText, setSavedAgentsText] = (0, react.useState)("");
203
220
  const [savedSwitches, setSavedSwitches] = (0, react.useState)(EMPTY_SWITCHES);
@@ -238,7 +255,10 @@ window.__ModuleLoader__.load({
238
255
  anchorFirstTurn: readBoolean(value, "anchorFirstTurn", readBoolean(base, "anchorFirstTurn", false)),
239
256
  anchorText: readString(value, "anchorText") ?? readString(base, "anchorText") ?? "",
240
257
  anchorCustom: readBoolean(value, "anchorCustom", readBoolean(base, "anchorCustom", false)),
258
+ guideText: readString(value, "guideText") ?? readString(base, "guideText") ?? "",
259
+ guideCustom: readBoolean(value, "guideCustom", readBoolean(base, "guideCustom", false)),
241
260
  subagentFlash: readBoolean(value, "subagentFlash", readBoolean(base, "subagentFlash", false)),
261
+ bootstrapMaxTokens: readNumber(value, "bootstrapMaxTokens", readNumber(base, "bootstrapMaxTokens", 0)),
242
262
  injectPrompt: readBoolean(value, "injectPrompt", readBoolean(base, "injectPrompt", true)),
243
263
  skillSwitches: value.skillSwitches !== void 0 || base.skillSwitches !== void 0 ? {
244
264
  ...readSkillSwitches(base, "skillSwitches"),
@@ -250,6 +270,7 @@ window.__ModuleLoader__.load({
250
270
  };
251
271
  fieldsRef.current = next;
252
272
  setFields(next);
273
+ setBootstrapTokensDraft(next.bootstrapMaxTokens > 0 ? String(next.bootstrapMaxTokens) : DEFAULT_BOOTSTRAP_DISPLAY);
253
274
  setSavedPromptText(next.promptText);
254
275
  setSavedAgentsText(next.agentsText);
255
276
  setSavedSwitches(snapshotSwitches(next));
@@ -393,11 +414,26 @@ window.__ModuleLoader__.load({
393
414
  path: ["anchorCustom"],
394
415
  value: fieldsRef.current.anchorCustom
395
416
  },
417
+ {
418
+ op: "set",
419
+ path: ["guideText"],
420
+ value: fieldsRef.current.guideText
421
+ },
422
+ {
423
+ op: "set",
424
+ path: ["guideCustom"],
425
+ value: fieldsRef.current.guideCustom
426
+ },
396
427
  {
397
428
  op: "set",
398
429
  path: ["subagentFlash"],
399
430
  value: fieldsRef.current.subagentFlash
400
431
  },
432
+ {
433
+ op: "set",
434
+ path: ["bootstrapMaxTokens"],
435
+ value: fieldsRef.current.bootstrapMaxTokens
436
+ },
401
437
  {
402
438
  op: "set",
403
439
  path: ["injectPrompt"],
@@ -427,6 +463,20 @@ window.__ModuleLoader__.load({
427
463
  patch({ [key]: !fieldsRef.current[key] });
428
464
  persistSwitches();
429
465
  };
466
+ const toggleBootstrapMaxTokens = () => {
467
+ const next = fieldsRef.current.bootstrapMaxTokens > 0 ? 0 : 256e3;
468
+ patch({ bootstrapMaxTokens: next });
469
+ setBootstrapTokensDraft(DEFAULT_BOOTSTRAP_DISPLAY);
470
+ persistSwitches();
471
+ };
472
+ const commitBootstrapTokensDraft = () => {
473
+ const parsed = Number(bootstrapTokensDraft);
474
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
475
+ setBootstrapTokensDraft(DEFAULT_BOOTSTRAP_DISPLAY);
476
+ patch({ bootstrapMaxTokens: 256e3 });
477
+ } else patch({ bootstrapMaxTokens: parsed });
478
+ persistSwitches();
479
+ };
430
480
  const skillEnabled = (folder) => fields.skillSwitches[folder] !== false;
431
481
  const toggleSkill = (folder) => {
432
482
  const enabled = fieldsRef.current.skillSwitches[folder] !== false;
@@ -444,7 +494,7 @@ window.__ModuleLoader__.load({
444
494
  const promptSwitchDirty = fields.injectPrompt !== savedSwitches.injectPrompt;
445
495
  const agentsSwitchDirty = fields.injectAgentsPrompt !== savedSwitches.injectAgentsPrompt || fields.writeAgents !== savedSwitches.writeAgents;
446
496
  const skillsSwitchDirty = JSON.stringify(fields.skillSwitches) !== JSON.stringify(savedSwitches.skillSwitches);
447
- const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText;
497
+ const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText || fields.bootstrapMaxTokens !== savedSwitches.bootstrapMaxTokens;
448
498
  const discardPrompt = () => patch({ promptText: savedPromptText });
449
499
  const discardAgents = () => patch({ agentsText: savedAgentsText });
450
500
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
@@ -678,6 +728,39 @@ window.__ModuleLoader__.load({
678
728
  })]
679
729
  })]
680
730
  }),
731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
732
+ className: PromptEditor_module_css_default.row,
733
+ children: [
734
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
735
+ id: "prompt-tool-bootstrap-max-tokens",
736
+ type: "checkbox",
737
+ checked: fields.bootstrapMaxTokens > 0,
738
+ disabled: !fields.writePreset,
739
+ onChange: toggleBootstrapMaxTokens
740
+ }),
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
742
+ htmlFor: "prompt-tool-bootstrap-max-tokens",
743
+ className: PromptEditor_module_css_default.rowText,
744
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
745
+ className: PromptEditor_module_css_default.rowName,
746
+ children: "首轮输出封顶"
747
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
748
+ className: PromptEditor_module_css_default.rowDesc,
749
+ children: "关闭时显示项目默认 256000(不设上限);开启后右侧数值生效,晋升后自动剥离。清空或输入 0 会恢复 256000,不关闭开关"
750
+ })]
751
+ }),
752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
753
+ className: PromptEditor_module_css_default.bootstrapTokensInput,
754
+ type: "number",
755
+ min: 1,
756
+ step: 1,
757
+ value: bootstrapTokensDraft,
758
+ disabled: !fields.writePreset || fields.bootstrapMaxTokens === 0,
759
+ onChange: (e) => setBootstrapTokensDraft(e.target.value),
760
+ onBlur: commitBootstrapTokensDraft
761
+ })
762
+ ]
763
+ }),
681
764
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
682
765
  className: PromptEditor_module_css_default.row,
683
766
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -710,7 +793,7 @@ window.__ModuleLoader__.load({
710
793
  children: "追加任务引导"
711
794
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
712
795
  className: PromptEditor_module_css_default.rowDesc,
713
- children: "开启后在首条真实用户消息之后追加一句任务引导,不拆轮、不挪任务;内容按下方“使用自定义引导”开关选择"
796
+ children: "总开关:开启后在首条真实用户消息之后追加任务引导;关闭时下方首句与每轮的自定义开关、编辑框全部禁用"
714
797
  })]
715
798
  })]
716
799
  }),
@@ -725,7 +808,7 @@ window.__ModuleLoader__.load({
725
808
  className: PromptEditor_module_css_default.rowText,
726
809
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
727
810
  className: PromptEditor_module_css_default.rowName,
728
- children: "使用自定义引导"
811
+ children: "使用自定义引导(首句)"
729
812
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
730
813
  className: PromptEditor_module_css_default.rowDesc,
731
814
  children: "开启时使用下方文本作为引导;关闭时忽略下方文本,按任务自动选择 we/let 引导"
@@ -733,15 +816,15 @@ window.__ModuleLoader__.load({
733
816
  })]
734
817
  }),
735
818
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
736
- className: clsx(PromptEditor_module_css_default.rowStack, !fields.anchorFirstTurn && PromptEditor_module_css_default.rowDisabled),
819
+ className: clsx(PromptEditor_module_css_default.rowStack, (!fields.anchorFirstTurn || !fields.anchorCustom) && PromptEditor_module_css_default.rowDisabled),
737
820
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
738
821
  className: PromptEditor_module_css_default.rowText,
739
822
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
740
823
  className: PromptEditor_module_css_default.rowName,
741
- children: "自定义引导文本"
824
+ children: "自定义引导文本(首句)"
742
825
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
743
826
  className: PromptEditor_module_css_default.rowDesc,
744
- children: "仅在上方“使用自定义引导”开启时生效;关闭时自动文本优先"
827
+ children: "仅在上方“使用自定义引导(首句)”开启时生效;关闭时自动文本优先"
745
828
  })]
746
829
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
747
830
  className: PromptEditor_module_css_default.anchorInput,
@@ -751,6 +834,44 @@ window.__ModuleLoader__.load({
751
834
  onBlur: () => persistSwitches(),
752
835
  spellCheck: false
753
836
  })]
837
+ }),
838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
839
+ className: PromptEditor_module_css_default.row,
840
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
841
+ type: "checkbox",
842
+ checked: fields.guideCustom,
843
+ disabled: !fields.writePreset || !fields.anchorFirstTurn,
844
+ onChange: () => toggle("guideCustom")
845
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
846
+ className: PromptEditor_module_css_default.rowText,
847
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
848
+ className: PromptEditor_module_css_default.rowName,
849
+ children: "使用自定义引导(每轮)"
850
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
851
+ className: PromptEditor_module_css_default.rowDesc,
852
+ children: "开启时每轮固定使用下方文本作为引导;关闭时忽略下方文本,按任务自动选择"
853
+ })]
854
+ })]
855
+ }),
856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
857
+ className: clsx(PromptEditor_module_css_default.rowStack, (!fields.anchorFirstTurn || !fields.guideCustom) && PromptEditor_module_css_default.rowDisabled),
858
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
859
+ className: PromptEditor_module_css_default.rowText,
860
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
861
+ className: PromptEditor_module_css_default.rowName,
862
+ children: "自定义引导文本(每轮)"
863
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
864
+ className: PromptEditor_module_css_default.rowDesc,
865
+ children: "仅在上方“使用自定义引导(每轮)”开启时生效;留空则不注入"
866
+ })]
867
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
868
+ className: PromptEditor_module_css_default.anchorInput,
869
+ value: fields.guideText,
870
+ disabled: !fields.writePreset || !fields.anchorFirstTurn || !fields.guideCustom,
871
+ onChange: (e) => patch({ guideText: e.target.value }),
872
+ onBlur: () => persistSwitches(),
873
+ spellCheck: false
874
+ })]
754
875
  })
755
876
  ]
756
877
  }),