dsh-plugin-prompt-tool 0.3.1 → 0.4.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,13 +1,13 @@
1
1
  # 提示词工具(dsh-plugin-prompt-tool)
2
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 源码。
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
7
  DeepSeek V4 会强烈依赖 API 中可见的**首轮工具目录与 persona** 选择执行轨迹。完整 Standard 目录与自动注入在场会破坏 Minimal 轨迹,而全程 Minimal 又会失去重型工具。本项目把「首次轨迹选择」与「后续完整工具能力」拆开:
8
8
 
9
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` 按需解锁。
10
+ 2. **晋升**:首次持久 `tool/call` 或 `assistant/message`(先到者为准)落地后,按 `usePtcMode` 开关选择 wire 形态——默认开启时切换为 Code Mode(PTC,单一 `run_code`),完整插件工具经生成 SDK 调用;关闭时恢复原生完整工具目录。
11
11
  3. **任务引导**:可选 `near-anchor` 在首条真实用户消息后追加一次近距离首句引导(we/let 按任务自动选择);Flash 主会话晋升后 `router-guide` 按任务复杂度追加每轮深度引导。
12
12
  4. **提示词注入**:we 锚定确认后把 `preset.md` 作为用户消息注入一次;we 未确认最多等一轮兜底,绝不卡死。
13
13
  5. **子代理**:目录直接全量放行(仍受调用方工具白名单过滤);可选 `subagentFlash` 固定 Flash 路由 + 任务分类人设 + 三锚。
@@ -21,37 +21,117 @@ DeepSeek V4 会强烈依赖 API 中可见的**首轮工具目录与 persona**
21
21
  - `bootstrapMaxTokens`:首轮输出封顶。`0` = 本项目默认不设封顶(Web 界面显示 256000,即不设上限);正整数 = 请求 #1 的 `maxTokens`,晋升后自动剥离,不会焊进后续请求。不锁定 1024,任意正整数均可。
22
22
  - `context-gate`:未晋升期间关闭两条统一注入路径,pre-step 只保留 claimed 批次 + `allowKinds`(放行用户技能手势与 `near-anchor` / `router-guide`);晋升后差分恢复一条 runtime-context 消息。
23
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`。
24
25
  - `router-first-turn`:只替换 persona 段,保留 plan-mode 段与第三方 section;首轮隐藏 `mnemon:*` 自动注入段,晋升后恢复。
25
26
  - `near-anchor`:行为引导放在真实用户消息之后(近距离零衰减),只要求首句一次;复杂规划任务放行 Let 深度路径,日常任务锚 We。
26
27
  - `router-guide`:简单任务快速收敛,复杂任务深度引导;子代理不注入,首轮不注入。
27
28
  - `prompt-injector`:注入状态以持久事件为准,跨进程重启 / 插件热重载不重复注入。
28
- - `injectAgentsPrompt`:开启时用 `AGENTS.md` 内容替换上游 instruction-hint 提示文本;关闭时保留上游原版 hint
29
+ - `injectAgentsPrompt`:开启时用 `AGENTS.md` 内容替换本地 instruction-hint 的默认提示文本;关闭时使用本地默认 hint(列出参考文件、按需读取)。
29
30
  - `skillSwitches`:扫描 `skills/*/SKILL.md` 注册可开关技能,未列出的技能默认开启。
30
31
  - `subagentFlash`:检测不到 DeepSeek 模型路由时 Web/TUI 禁用并强制降级为关闭。
31
32
 
32
33
  ## 安装
33
34
 
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
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
+ 两种安装源等价,后续初始化流程相同:
38
43
 
39
- # 2. 安装本插件(官方插件指令,自动加入 profile bundles)
44
+ ```sh
45
+ # 方式 A:npm / registry 安装
40
46
  dsh plugin --profile prompt-tool add dsh-plugin-prompt-tool
41
47
 
42
- # 3. 本地开发安装(任选其一,会覆盖 registry 依赖)
48
+ # 方式 B:本地源码安装(link 会覆盖 registry 依赖)
43
49
  dsh plugin --profile prompt-tool add link:<本仓库绝对路径>
50
+ ```
44
51
 
45
- # 4. 启动
52
+ ### 初始化:启动一次即可
53
+
54
+ ```sh
46
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,本插件已自动写入
47
85
  dsh web
86
+
87
+ # TUI:dsh-tui 的安装流程已经物化过其 profile 依赖
48
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。
49
105
 
50
- # 卸载
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
51
128
  dsh plugin --profile prompt-tool remove dsh-plugin-prompt-tool
52
129
  ```
53
130
 
54
- 装完**完整重启 `dsh web`**,新建空 session,预设选择 **prompt-tool**。插件会在启动时生成并刷新 `~/.dsh/.agent-presets/prompt-tool/`(升级插件后重启即自动更新)。
131
+ 首次启动还会把包内 `skills/` 增量复制到当前 profile 目录下的
132
+ `$DSH_HOME/profiles/<profile>/skills`,并优先使用这份副本:已有同名文件不覆盖,
133
+ 用户对副本的编辑会保留;包内新增技能文件会在下次启动补齐。想改回包内原始
134
+ skills,删除该 `skills` 目录后重启即可(或在配置里显式设置 `skillsDir`)。
55
135
 
56
136
  ## 验证
57
137
 
@@ -80,7 +160,7 @@ pnpm lint # oxlint
80
160
  - preset 与 shell 访问具有相同信任等级,安装前可自行审阅 `upstream/` 与生成目录。
81
161
  - 插件只监听本机回环设置桥,不发起网络请求,也不增加遥测。
82
162
  - 不要在已经产生内容的会话中途切换 preset。
83
- - 需要 DSH 0.1.0-rc.5+(preset 机制与 `system-prompt/assemble` 钩子)。
163
+ - 需要 DSH 0.1.0-rc.7+(preset 机制、`system-prompt/assemble` 钩子与 keyed `settings.plugin.item` 插槽)。
84
164
 
85
165
  ## 构建与上游管理
86
166
 
@@ -91,11 +171,11 @@ pnpm prepare # npm publish / git install 前自动触发
91
171
  pnpm sync:anchored # 从上游 main 刷新内联快照(可加 ref 参数)
92
172
  ```
93
173
 
94
- 上游 `dsh-anchored-standard` 以内联快照形式固化在 `upstream/dsh-anchored-standard/`(含 `LICENSE`、`NOTICE`、`REVISION`),对应提交见 `REVISION`。上游已转入维护期(2026-08-17);其 Project2 的 98/99/99 成绩来自当前实现之前的旧配置(issue #60),轨迹锚定有独立复现(#65),能力增益在小样本下未决(#51)。
174
+ 上游 `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)。
95
175
 
96
176
  ## 许可
97
177
 
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`。
178
+ 插件本体 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`。
99
179
 
100
180
  ## 配置参考
101
181
 
@@ -122,7 +202,8 @@ pnpm sync:anchored # 从上游 main 刷新内联快照(可加 ref 参数)
122
202
  subagentFlashProvider: 'deepseek-official'
123
203
  subagentFlashModel: 'deepseek-v4-flash'
124
204
  bootstrapMaxTokens: 0 # 首轮输出封顶:0=关闭;正整数=请求 #1 maxTokens
125
- skillsDir: '' # 可选技能目录(默认包内 skills/)
205
+ usePtcMode: true # 使用 PTC 模式:true=晋升后切换为 Code Mode(run_code);false=恢复原生完整目录
206
+ skillsDir: '' # 用户自定义技能目录;空 = 自动使用 profile 下 skills/ 副本
126
207
  skillRankBase: 250 # 技能候选排序基数
127
208
  residentAgentsPath: '' # 默认 ~/.dsh/AGENTS.md
128
209
  presetDir: '' # 默认 ~/.dsh/.agent-presets/prompt-tool/
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)}.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}";
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_skillsBar{flex-direction:column;gap:8px;padding:2px 0 8px;display:flex}.Ru2OGG_skillsDirRow{align-items:center;gap:8px;min-width:0;display:flex}.Ru2OGG_skillsDirRow .Ru2OGG_secondary{flex:none}.Ru2OGG_skillsDirInput{box-sizing:border-box;min-width:0;min-height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex:1;padding:6px 10px;font-size:12px;line-height:1.4}.Ru2OGG_skillsDirInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Ru2OGG_skillsPath{min-width:0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;flex:1;font-size:12px;line-height:1.5}.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");
@@ -59,6 +59,10 @@ window.__ModuleLoader__.load({
59
59
  "rowStack": "Ru2OGG_rowStack",
60
60
  "rowText": "Ru2OGG_rowText",
61
61
  "secondary": "Ru2OGG_secondary",
62
+ "skillsBar": "Ru2OGG_skillsBar",
63
+ "skillsDirInput": "Ru2OGG_skillsDirInput",
64
+ "skillsDirRow": "Ru2OGG_skillsDirRow",
65
+ "skillsPath": "Ru2OGG_skillsPath",
62
66
  "subBody": "Ru2OGG_subBody",
63
67
  "subCount": "Ru2OGG_subCount",
64
68
  "subEmpty": "Ru2OGG_subEmpty",
@@ -102,9 +106,12 @@ window.__ModuleLoader__.load({
102
106
  guideCustom: false,
103
107
  subagentFlash: false,
104
108
  bootstrapMaxTokens: 0,
109
+ usePtcMode: true,
105
110
  injectPrompt: true,
106
111
  skillSwitches: {},
107
112
  skillCatalog: [],
113
+ skillsDir: "",
114
+ activeSkillsDir: "",
108
115
  writeAgents: true,
109
116
  writePreset: true
110
117
  };
@@ -117,8 +124,10 @@ window.__ModuleLoader__.load({
117
124
  guideCustom: false,
118
125
  subagentFlash: false,
119
126
  bootstrapMaxTokens: 0,
127
+ usePtcMode: true,
120
128
  injectPrompt: true,
121
129
  skillSwitches: {},
130
+ skillsDir: "",
122
131
  writeAgents: true,
123
132
  writePreset: true
124
133
  };
@@ -133,12 +142,14 @@ window.__ModuleLoader__.load({
133
142
  guideCustom: fields.guideCustom,
134
143
  subagentFlash: fields.subagentFlash,
135
144
  bootstrapMaxTokens: fields.bootstrapMaxTokens,
145
+ usePtcMode: fields.usePtcMode,
136
146
  injectPrompt: fields.injectPrompt,
137
147
  skillSwitches: { ...fields.skillSwitches },
148
+ skillsDir: fields.skillsDir,
138
149
  writeAgents: fields.writeAgents,
139
150
  writePreset: fields.writePreset
140
151
  });
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;
152
+ 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.usePtcMode === b.usePtcMode && a.injectPrompt === b.injectPrompt && JSON.stringify(a.skillSwitches) === JSON.stringify(b.skillSwitches) && a.skillsDir === b.skillsDir && a.writeAgents === b.writeAgents && a.writePreset === b.writePreset;
142
153
  const asRecord = (value) => value !== null && typeof value === "object" ? value : {};
143
154
  const readString = (source, key) => {
144
155
  const value = source[key];
@@ -215,12 +226,14 @@ window.__ModuleLoader__.load({
215
226
  const [loaded, setLoaded] = (0, react.useState)(false);
216
227
  const [fields, setFields] = (0, react.useState)(EMPTY);
217
228
  const [bootstrapTokensDraft, setBootstrapTokensDraft] = (0, react.useState)(DEFAULT_BOOTSTRAP_DISPLAY);
229
+ const [skillsDirDraft, setSkillsDirDraft] = (0, react.useState)("");
218
230
  const [savedPromptText, setSavedPromptText] = (0, react.useState)("");
219
231
  const [savedAgentsText, setSavedAgentsText] = (0, react.useState)("");
220
232
  const [savedSwitches, setSavedSwitches] = (0, react.useState)(EMPTY_SWITCHES);
221
233
  const [loading, setLoading] = (0, react.useState)(false);
222
234
  const [savingPrompt, setSavingPrompt] = (0, react.useState)(false);
223
235
  const [savingAgents, setSavingAgents] = (0, react.useState)(false);
236
+ const [savingSkillsDir, setSavingSkillsDir] = (0, react.useState)(false);
224
237
  const [restoringPrompt, setRestoringPrompt] = (0, react.useState)(false);
225
238
  const [restoringAgents, setRestoringAgents] = (0, react.useState)(false);
226
239
  const [notice, setNotice] = (0, react.useState)("");
@@ -259,18 +272,22 @@ window.__ModuleLoader__.load({
259
272
  guideCustom: readBoolean(value, "guideCustom", readBoolean(base, "guideCustom", false)),
260
273
  subagentFlash: readBoolean(value, "subagentFlash", readBoolean(base, "subagentFlash", false)),
261
274
  bootstrapMaxTokens: readNumber(value, "bootstrapMaxTokens", readNumber(base, "bootstrapMaxTokens", 0)),
275
+ usePtcMode: readBoolean(value, "usePtcMode", readBoolean(base, "usePtcMode", true)),
262
276
  injectPrompt: readBoolean(value, "injectPrompt", readBoolean(base, "injectPrompt", true)),
263
277
  skillSwitches: value.skillSwitches !== void 0 || base.skillSwitches !== void 0 ? {
264
278
  ...readSkillSwitches(base, "skillSwitches"),
265
279
  ...readSkillSwitches(value, "skillSwitches")
266
280
  } : {},
267
- skillCatalog: readSkillCatalog(value, "skillCatalog").length > 0 ? readSkillCatalog(value, "skillCatalog") : readSkillCatalog(base, "skillCatalog"),
281
+ skillCatalog: res.skillCatalog ?? (readSkillCatalog(value, "skillCatalog").length > 0 ? readSkillCatalog(value, "skillCatalog") : readSkillCatalog(base, "skillCatalog")),
282
+ skillsDir: readString(value, "skillsDir") ?? readString(base, "skillsDir") ?? "",
283
+ activeSkillsDir: res.activeSkillsDir ?? readString(value, "activeSkillsDir") ?? readString(base, "activeSkillsDir") ?? "",
268
284
  writeAgents: readBoolean(value, "writeAgents", readBoolean(base, "writeAgents", true)),
269
285
  writePreset: readBoolean(value, "writePreset", readBoolean(base, "writePreset", true))
270
286
  };
271
287
  fieldsRef.current = next;
272
288
  setFields(next);
273
289
  setBootstrapTokensDraft(next.bootstrapMaxTokens > 0 ? String(next.bootstrapMaxTokens) : DEFAULT_BOOTSTRAP_DISPLAY);
290
+ setSkillsDirDraft(next.skillsDir);
274
291
  setSavedPromptText(next.promptText);
275
292
  setSavedAgentsText(next.agentsText);
276
293
  setSavedSwitches(snapshotSwitches(next));
@@ -359,6 +376,20 @@ window.__ModuleLoader__.load({
359
376
  showNotice("error", "打开失败:" + errorMessage(error) + ";可直接在下方编辑框保存");
360
377
  }
361
378
  };
379
+ const openSkillsDir = async () => {
380
+ const path = fields.activeSkillsDir || fields.skillsDir;
381
+ if (!path) {
382
+ showNotice("error", "技能目录路径未知,请先重新展开面板读取配置");
383
+ return;
384
+ }
385
+ try {
386
+ const res = await api.host.openPath({ path });
387
+ if (res.result.ok) showNotice("ok", "已打开技能目录:" + path);
388
+ else showNotice("error", "打开失败:" + (res.result.error?.message ?? ""));
389
+ } catch (error) {
390
+ showNotice("error", "打开失败:" + errorMessage(error));
391
+ }
392
+ };
362
393
  const enqueueSave = (0, react.useCallback)((ops, okMessage, onSaved, setBusy) => {
363
394
  setBusy?.(true);
364
395
  saveQueueRef.current = saveQueueRef.current.then(async () => {
@@ -434,6 +465,11 @@ window.__ModuleLoader__.load({
434
465
  path: ["bootstrapMaxTokens"],
435
466
  value: fieldsRef.current.bootstrapMaxTokens
436
467
  },
468
+ {
469
+ op: "set",
470
+ path: ["usePtcMode"],
471
+ value: fieldsRef.current.usePtcMode
472
+ },
437
473
  {
438
474
  op: "set",
439
475
  path: ["injectPrompt"],
@@ -444,6 +480,11 @@ window.__ModuleLoader__.load({
444
480
  path: ["skillSwitches"],
445
481
  value: fieldsRef.current.skillSwitches
446
482
  },
483
+ {
484
+ op: "set",
485
+ path: ["skillsDir"],
486
+ value: fieldsRef.current.skillsDir
487
+ },
447
488
  {
448
489
  op: "set",
449
490
  path: ["writeAgents"],
@@ -477,6 +518,18 @@ window.__ModuleLoader__.load({
477
518
  } else patch({ bootstrapMaxTokens: parsed });
478
519
  persistSwitches();
479
520
  };
521
+ const applySkillsDir = () => {
522
+ const next = skillsDirDraft.trim();
523
+ patch({ skillsDir: next });
524
+ enqueueSave([{
525
+ op: "set",
526
+ path: ["skillsDir"],
527
+ value: next
528
+ }], next.length > 0 ? `自定义技能目录已保存并生效:${next}` : "已切回自动技能目录(当前 profile 下 skills/)", () => {
529
+ setSavedSwitches(snapshotSwitches(fieldsRef.current));
530
+ load();
531
+ }, setSavingSkillsDir);
532
+ };
480
533
  const skillEnabled = (folder) => fields.skillSwitches[folder] !== false;
481
534
  const toggleSkill = (folder) => {
482
535
  const enabled = fieldsRef.current.skillSwitches[folder] !== false;
@@ -494,7 +547,8 @@ window.__ModuleLoader__.load({
494
547
  const promptSwitchDirty = fields.injectPrompt !== savedSwitches.injectPrompt;
495
548
  const agentsSwitchDirty = fields.injectAgentsPrompt !== savedSwitches.injectAgentsPrompt || fields.writeAgents !== savedSwitches.writeAgents;
496
549
  const skillsSwitchDirty = JSON.stringify(fields.skillSwitches) !== JSON.stringify(savedSwitches.skillSwitches);
497
- const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText || fields.bootstrapMaxTokens !== savedSwitches.bootstrapMaxTokens;
550
+ const skillsDirDirty = fields.skillsDir !== savedSwitches.skillsDir || skillsDirDraft.trim() !== fields.skillsDir;
551
+ const presetSwitchDirty = fields.writePreset !== savedSwitches.writePreset || fields.anchorFirstTurn !== savedSwitches.anchorFirstTurn || fields.anchorText !== savedSwitches.anchorText || fields.bootstrapMaxTokens !== savedSwitches.bootstrapMaxTokens || fields.usePtcMode !== savedSwitches.usePtcMode;
498
552
  const discardPrompt = () => patch({ promptText: savedPromptText });
499
553
  const discardAgents = () => patch({ agentsText: savedAgentsText });
500
554
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
@@ -675,12 +729,50 @@ window.__ModuleLoader__.load({
675
729
  })
676
730
  ]
677
731
  }),
678
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
679
733
  title: "Skills设置",
680
- hint: `${fields.skillCatalog.length} 个${skillsSwitchDirty ? " · 未保存" : ""}`,
734
+ hint: `${fields.skillCatalog.length} 个${skillsSwitchDirty || skillsDirDirty ? " · 未保存" : ""}`,
681
735
  open: skillsOpen,
682
736
  onToggle: () => setSkillsOpen(!skillsOpen),
683
- children: fields.skillCatalog.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
738
+ className: PromptEditor_module_css_default.skillsBar,
739
+ children: [
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
741
+ className: PromptEditor_module_css_default.skillsDirRow,
742
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
743
+ type: "button",
744
+ className: PromptEditor_module_css_default.secondary,
745
+ disabled: !fields.activeSkillsDir && !fields.skillsDir,
746
+ onClick: () => void openSkillsDir(),
747
+ children: "打开技能目录"
748
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
749
+ className: PromptEditor_module_css_default.skillsPath,
750
+ children: fields.activeSkillsDir || fields.skillsDir || "(技能目录路径未知,请重新展开面板)"
751
+ })]
752
+ }),
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
754
+ className: PromptEditor_module_css_default.skillsDirRow,
755
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
756
+ className: PromptEditor_module_css_default.skillsDirInput,
757
+ "aria-label": "用户自定义技能目录",
758
+ value: skillsDirDraft,
759
+ placeholder: fields.activeSkillsDir || "留空 = 自动使用当前 profile 下的 skills/ 副本",
760
+ spellCheck: false,
761
+ onChange: (e) => setSkillsDirDraft(e.target.value)
762
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
763
+ type: "button",
764
+ className: PromptEditor_module_css_default.primary,
765
+ disabled: savingSkillsDir || skillsDirDraft.trim() === fields.skillsDir,
766
+ onClick: applySkillsDir,
767
+ children: savingSkillsDir ? "设置中…" : "设置自定义目录"
768
+ })]
769
+ }),
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
771
+ className: PromptEditor_module_css_default.rowDesc,
772
+ children: "留空并点“设置自定义目录”会切回自动目录(当前 profile 下的 skills/ 副本)。修改后技能目录立即重新扫描。"
773
+ })
774
+ ]
775
+ }), fields.skillCatalog.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
684
776
  className: PromptEditor_module_css_default.subEmpty,
685
777
  children: "skills 目录下没有技能"
686
778
  }) : fields.skillCatalog.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -703,7 +795,7 @@ window.__ModuleLoader__.load({
703
795
  ]
704
796
  })]
705
797
  })]
706
- }, skill.folder))
798
+ }, skill.folder))]
707
799
  }),
708
800
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
709
801
  title: "锚定与 preset",
@@ -728,6 +820,24 @@ window.__ModuleLoader__.load({
728
820
  })]
729
821
  })]
730
822
  }),
823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
824
+ className: PromptEditor_module_css_default.row,
825
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
826
+ type: "checkbox",
827
+ checked: fields.usePtcMode,
828
+ disabled: !fields.writePreset,
829
+ onChange: () => toggle("usePtcMode")
830
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
831
+ className: PromptEditor_module_css_default.rowText,
832
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
833
+ className: PromptEditor_module_css_default.rowName,
834
+ children: "使用 PTC 模式"
835
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
836
+ className: PromptEditor_module_css_default.rowDesc,
837
+ children: "默认开启:晋升后把 wire 切换为 Code Mode(PTC,单一 run_code),完整插件工具通过生成 SDK 调用。关闭后恢复原生完整工具目录"
838
+ })]
839
+ })]
840
+ }),
731
841
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
732
842
  className: PromptEditor_module_css_default.row,
733
843
  children: [
@@ -891,8 +1001,7 @@ window.__ModuleLoader__.load({
891
1001
  const injected = () => ({ api: connection.api });
892
1002
  ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
893
1003
  name: "settings.plugin.item",
894
- id: "prompt-tool",
895
- order: 30,
1004
+ key: "prompt-tool",
896
1005
  inject: injected
897
1006
  }, PromptEditor));
898
1007
  }