dsh-plugin-prompt-tool 0.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -67
- package/engine/actions/append-context.mjs +49 -0
- package/engine/actions/assembly.mjs +150 -0
- package/engine/actions/catalog.mjs +92 -0
- package/engine/actions/content.mjs +205 -0
- package/engine/actions/decision.mjs +54 -0
- package/engine/actions/examples.mjs +11 -0
- package/engine/actions/guard.mjs +80 -0
- package/engine/actions/inbox-prepend.mjs +37 -0
- package/engine/actions/index.mjs +100 -0
- package/engine/actions/inject-text.mjs +22 -0
- package/engine/actions/pre-step-filter.mjs +55 -0
- package/engine/actions/registration.mjs +168 -0
- package/engine/actions/request-params.mjs +32 -0
- package/engine/actions/sdk-strip.mjs +30 -0
- package/engine/actions/shared.mjs +71 -0
- package/engine/actions.mjs +2 -931
- package/engine/branch.mjs +44 -0
- package/engine/character-tools.mjs +3 -6
- package/engine/compaction-epoch.mjs +31 -13
- package/engine/compositions/library/agent-instructions.yml +1 -1
- package/engine/compositions/library/command-goal.yml +1 -1
- package/engine/compositions/library/compaction.yml +1 -1
- package/engine/compositions/library/delegation-ptc.yml +1 -1
- package/engine/compositions/library/delegation.yml +1 -1
- package/engine/compositions/library/persistent-shell.yml +1 -1
- package/engine/compositions/library/planning.yml +1 -1
- package/engine/compositions/library/skill-filesystem-cordis.yml +1 -1
- package/engine/compositions/library/skill-filesystem.yml +1 -1
- package/engine/compositions/library/tool-ask-user.yml +1 -1
- package/engine/compositions/library/tool-bash.yml +1 -1
- package/engine/compositions/library/tool-cordis.yml +1 -1
- package/engine/compositions/library/tool-fs-search.yml +1 -1
- package/engine/compositions/library/tool-fs.yml +1 -1
- package/engine/compositions/library/tool-goal.yml +1 -1
- package/engine/compositions/library/tool-jobs.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager.yml +1 -1
- package/engine/compositions/library/tool-present.yml +1 -1
- package/engine/compositions/library/tool-presentation.yml +1 -1
- package/engine/compositions/library/tool-pwsh.yml +1 -1
- package/engine/compositions/library/tool-skill.yml +1 -1
- package/engine/compositions/library/tool-todo.yml +1 -1
- package/engine/compositions/library/tool-web.yml +1 -1
- package/engine/compositions/source/local/dev-tool-search.yml +11 -0
- package/engine/compositions/source/local/instruction-hint.yml +22 -1
- package/engine/compositions/source/local/rule-engine.yml +6 -0
- package/engine/condition.mjs +16 -3
- package/engine/conditions/anchor.mjs +32 -0
- package/engine/conditions/availability.mjs +47 -0
- package/engine/conditions/composite.mjs +83 -0
- package/engine/conditions/count.mjs +140 -0
- package/engine/conditions/evaluation.mjs +41 -0
- package/engine/conditions/examples.mjs +11 -0
- package/engine/conditions/index.mjs +85 -0
- package/engine/conditions/names.mjs +21 -0
- package/engine/conditions/phase.mjs +54 -0
- package/engine/conditions/preset.mjs +94 -0
- package/engine/conditions/scope.mjs +18 -0
- package/engine/conditions/session.mjs +22 -0
- package/engine/conditions/source.mjs +63 -0
- package/engine/conditions/subject.mjs +121 -0
- package/engine/conditions/text.mjs +44 -0
- package/engine/conditions/values.mjs +32 -0
- package/engine/dev-tool-search.mjs +213 -0
- package/engine/executor.mjs +93 -31
- package/engine/fillers.mjs +23 -16
- package/engine/instruction-hint.mjs +39 -23
- package/engine/layers.mjs +171 -77
- package/engine/order.mjs +20 -0
- package/engine/predicates.mjs +2 -753
- package/engine/rule-engine.mjs +40 -0
- package/engine/rule-runtime.mjs +110 -0
- package/engine/rule-spec.mjs +187 -0
- package/engine/schema.mjs +32 -11
- package/engine/session-var-tools.mjs +3 -6
- package/engine/shared.mjs +1 -1
- package/engine/st-render.mjs +8 -4
- package/engine/strategies.mjs +2 -235
- package/engine/subagent-tool-policy-core.mjs +7 -7
- package/engine/subagent-tool-policy.mjs +45 -20
- package/engine/tool-config-engine.mjs +53 -32
- package/engine/trigger-editor-meta.mjs +2 -21
- package/engine/trigger-spec.mjs +49 -133
- package/engine/world-book-tools.mjs +3 -6
- package/lib/client.js +10444 -10886
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +265 -361
- package/lib/index.mjs +1559 -2026
- package/lib/migrate-rules.d.mts +1 -0
- package/lib/migrate-rules.mjs +39 -0
- package/lib/module-package-CHPFweqK.mjs +2 -0
- package/lib/module-package-Cnd1_mwx.mjs +2872 -0
- package/lib/{paths-CY5psBqE.d.mts → paths-CyP0IkaM.d.mts} +388 -231
- package/lib/preset-transfer.d.mts +2 -2
- package/lib/preset-transfer.mjs +3 -2
- package/lib/{preset-package-BnJqrU4R.mjs → rules-migration-DJ6vASB5.mjs} +13825 -15283
- package/modules/ponytail/module.yml +406 -0
- package/modules/skill-surface/module.yml +74 -0
- package/modules/tool-surface/module.yml +67 -0
- package/package.json +50 -80
- package/templates/10-pre-step.yml +26 -22
- package/templates/14-first-turn-anchor.yml +25 -21
- package/templates/15-guide-auto.yml +34 -19
- package/templates/16-custom-fallback.yml +27 -15
- package/templates/18-placeholder-env-facts.yml +23 -14
- package/templates/20-system-section.yml +20 -11
- package/templates/30-runtime-context.yml +13 -7
- package/templates/40-agent-request.yml +11 -8
- package/templates/50-llm-stream.yml +14 -6
- package/templates/60-tool-pipeline.yml +15 -9
- package/templates/65-turn-stop.yml +19 -12
- package/templates/66-subagent-start.yml +12 -7
- package/templates/70-subagent-maintenance.yml +35 -29
- package/templates/75-subagent-end.yml +10 -5
- package/templates/80-tool-surface.yml +66 -0
- package/templates/policies/legacy-defaults.yml +34 -0
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/engine/compositions/source/local/prompt-config-engine.yml +0 -7
- package/engine/prompt-config-engine.mjs +0 -104
- package/preset/pt-cordis/preset.yml +0 -43
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +0 -29
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +0 -501
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +0 -38
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +0 -60
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +0 -13
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +0 -22
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +0 -3
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +0 -2
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +0 -15
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +0 -8
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +0 -7
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +0 -84
- package/preset/pt-custom/preset.yml +0 -15
- package/preset/pt-minimal/preset.yml +0 -27
- package/preset/pt-ptc/preset.yml +0 -45
- package/preset/pt-standard/preset.yml +0 -43
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> 一切皆可注入:把 DSH 官方开放的全部注入层级收敛为一个可配置提示词注入引擎——注入什么、注入到哪一层、何时注入,全由提示词配置决定。
|
|
4
4
|
|
|
5
|
-
DSH
|
|
5
|
+
DSH 生态的提示词注入层:`rule-engine.mjs` 按「条件触发 → 执行动作」对接九个官方插入点。每张配置卡对应一条独立规则,可组合多条件与多动作;提示词注入也是动作。包内模块库提供复制起点;模块不注册为官方会话预设。
|
|
6
6
|
|
|
7
|
-
> 能力来源:工具目录锚定与晋升门控移植自 [dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard)(MIT,上游已于 2026-09-10 冻结),近距离引导参考 [dsh-router-standard](https://github.com/yjh051108/dsh-router-standard),缓存铁律参考 [dsh-super-injector](https://github.com/yjh051108/dsh-super-injector)
|
|
7
|
+
> 能力来源:工具目录锚定与晋升门控移植自 [dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard)(MIT,上游已于 2026-09-10 冻结),近距离引导参考 [dsh-router-standard](https://github.com/yjh051108/dsh-router-standard),缓存铁律参考 [dsh-super-injector](https://github.com/yjh051108/dsh-super-injector)。**本项目只移植引擎能力,不再分发上游预设**:锚定/深思链路由模块与规则按需声明。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
@@ -28,68 +28,83 @@ dsh --profile prompt-tool
|
|
|
28
28
|
|
|
29
29
|
### 当前格式与宿主要求
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
module.yml 保存模块完整定义,支持 UI 自动保存和直接手改完整文件;rules/ 是校验后供 UI 与运行时使用的切片,手改切片或校验失配会从完整定义恢复。旧 promptConfigs、triggers、模型路由与锚定快捷参数须先离线迁移;运行时不自动双读。共享能力参数归 layerSettings,指令正文保持独立权限通道。
|
|
32
|
+
|
|
33
|
+
升级旧模块前,由用户停止相关 DSH 服务,再使用安装包提供的 `prompt-tool-migrate-rules`,或在已构建仓库中执行:
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
$Repo = 'D:/AI/GitHub/dsh-plugin-prompt-tool'
|
|
37
|
+
pnpm --dir $Repo migrate:rules -- --root '<DSH_HOME>/.prompt-tool/modules' --characters-root '<DSH_HOME>/.prompt-tool/.characters' --check
|
|
38
|
+
pnpm --dir $Repo migrate:rules -- --root '<DSH_HOME>/.prompt-tool/modules' --characters-root '<DSH_HOME>/.prompt-tool/.characters' --apply
|
|
39
|
+
# 需要恢复时,保持服务停止,并用相同目录执行 --rollback
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
目录必须显式指定为存在的绝对路径;没有角色库时省略 `--characters-root`。先全量预检,再生成完整候选、校验来源版本并原子替换;原文件保留在各根的 `.rules-migration-backup`。重复应用无改动,回滚只覆盖仍与迁移产物一致的目录。无法证明等价的手写组合、互斥多启用冲突、失效旧参数或自定义策略目录会拒绝迁移,要求先整理。迁移完成后由用户重新启动 DSH。
|
|
32
43
|
|
|
33
44
|
技能调用策略只接受官方 frontmatter 键,状态文件只接受 v4;不提供旧布局迁移、回滚或备份脚本。
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
只有 `dsh-base` 的 profile 缺少 Web 能力时,插件仅报告诊断;所需 bundle 由官方插件管理流程装配,插件不改写 profile 或重启服务。
|
|
36
47
|
|
|
37
|
-
需要 DSH `0.
|
|
48
|
+
需要 DSH `0.2.0-rc.1+`(Cordis `4.0.4`):设置接入 ConfigForms,模块定义与物化文件保存在插件自己的存储根 `$DSH_HOME/.prompt-tool/`(宿主从不扫描该目录),运行时装配由插件的配装通道在每个 Agent 的 scope 里完成,**按存储根 `config.yml` 的启用表逐模块装配**。官方组合模块快照随包分发(`engine/compositions/library/`),不再从官方预设同步——那套生成器已随内置预设目录退场。Node 需要 `^22.19.0 || >=24.0.0`,与官方宿主一致。升级后需要用户重启 DSH 服务。
|
|
49
|
+
|
|
50
|
+
`peerDependencies` 里的 `@deepseek-ai/dsh-*` 声明为 `>=0.2.0-rc.1`(无上界):宿主在加载插件时会逐条校验这些 peer,范围写死到某个次版本会让每次宿主升版都判为不兼容;这个范围覆盖 0.2.0 及以上,同时把已知不兼容的更早版本挡在外面。实际锁定的版本以 `pnpm-lock.yaml` 为准。`minimumReleaseAgeExclude` 由 pnpm 维护——安装时 pnpm 会把 lockfile 拿去比对供应链策略,因此升级 DSH 后跑一次 `pnpm install` 即可,不要手工逐条增删。
|
|
38
51
|
|
|
39
52
|
## 特性
|
|
40
53
|
|
|
41
|
-
- 🔌
|
|
54
|
+
- 🔌 **九个官方插入点按需接线**:一个引擎接入声明所需的插入点,共享同一套过滤与降级语义
|
|
42
55
|
- ✍️ **一切皆可配置**:`layer / strategy / position / promotion / audience / modelScope / mergeMode / order / text / texts / fill / variables / params` 全开放
|
|
43
56
|
- 🧑🤝🧑 **消息受众三态**:`audience: main / subagent`,省略 `audience` 表示公用;身份类提示词可只注入子代理
|
|
44
|
-
- 🗂️
|
|
45
|
-
- 🧩
|
|
46
|
-
- 🖥️ **可拖动悬浮工作台入口**:工作台经官方 `shell.overlay` 渲染悬浮触发器与 body portal 抽屉;按钮可拖动、位置存插件自己的 localStorage、窗口变化自动夹回可见区(不读宿主布局树,已移除 `sidebar.footer.action`
|
|
47
|
-
- 🧪
|
|
57
|
+
- 🗂️ **完整定义与校验切片**:module.yml 保存完整行为,rules/<id>.yml 保存正文,_settings.yml 保存顺序和启停,variables.yml 保存模板变量;失配单向恢复,不生成旧宿主装配产物。
|
|
58
|
+
- 🧩 **显式互斥**:同模块同组中任一卡声明互斥时,启用目标卡会原子关闭同组其他卡的总开关;重排不改变启用状态。
|
|
59
|
+
- 🖥️ **可拖动悬浮工作台入口**:工作台经官方 `shell.overlay` 渲染悬浮触发器与 body portal 抽屉;按钮可拖动、位置存插件自己的 localStorage、窗口变化自动夹回可见区(不读宿主布局树,已移除 `sidebar.footer.action` 几何探针);五页(主会话/子代理/工具预览/技能设置/模块)在抽屉内渲染,抽屉用 fixed + z-index 置顶,不被宿主导航栏遮挡
|
|
60
|
+
- 🧪 **条件与内容分离**:`when` 支持组合判断;注入动作支持 `static / first-turn-anchor / guide-auto / anchor-notice / placeholder / world-book`。旧 `custom-fallback` 拆为锚点条件与通知内容。
|
|
48
61
|
- 🛡️ **失败不伤会话**:单条失败跳过 + `warnOnce`;配置错误挂载时 fail loud;`dedupe: session` 持久幂等
|
|
49
|
-
- 🧭 **通用 instruction-hint
|
|
62
|
+
- 🧭 **通用 instruction-hint 引擎**:模块可通过 `strategy: placeholder` 与 `fill: instruction-hint` 提示指令文件存在;实现位于 `engine/instruction-hint.mjs`。`layerSettings.pre-step.instructionHint` 可启用该能力,按模型可见 surface 去重,重挂不重复,被压缩遮蔽后才再次提示
|
|
50
63
|
- 📦 **Bridge 载荷**:JSON 请求统一 32 MiB 硬上限并明确返回 413;角色卡原始图片走 64 MiB 流式通道,按 PNG 魔数识别。
|
|
51
64
|
- 📂 **技能管理**:官方发现与会话快照统一技能来源、生效和遮蔽状态;单端开关写回技能文件。支持目录包与直属 Markdown 技能、创建、两种复制导入,以及用户根和引用根的可恢复删除;技能局部刷新保留其他页面草稿。
|
|
52
65
|
- 🎭 **SillyTavern 导入**:JSON 预设、角色卡和独立世界书转换为本地预设——按官方顺序表保留启停,赋值模板运行时求值;不等价能力明确报告,采样参数由宿主管理
|
|
53
|
-
- 🎴
|
|
66
|
+
- 🎴 **角色卡导入**:PNG/JSON/YAML 角色来源经统一预览后成为普通模块;PNG 保留原图,更新保留模块记忆与用户资产。
|
|
54
67
|
- 📦 **预设交换**:文件夹、ZIP、原生 JSON/YAML 与 ST 来源共用识别、预览和完整候选安装;导出可选完整 ZIP 或仅定义 YAML。资源、覆盖及分享边界见 [资产交换文档](docs/asset-transfer.md)。
|
|
55
68
|
- 📚 **世界书**:`character_book` 转 world-book 策略配置(`keys` 命中触发 / `constant` 常驻 / 正则键自动检测 / `selectiveLogic` 组合逻辑),与模块卡片同一存储与编辑(模块列表「世界书」过滤 + 批量启用/禁用)
|
|
56
|
-
- 🛠️ **自定义工具**:
|
|
57
|
-
- 🛡️ **子代理工具策略**:
|
|
69
|
+
- 🛠️ **自定义工具**:module.yml `customTools` 段声明式定义模型工具(执行器 shell/http/delegate/fs/ask-user,`{{args.x}}` 参数插值);参数与输出经官方 `dsh-tools` 转换器物化为标准 JSON Schema,非法参数产生标准工具错误,delegate 经 `ctx.tools.execute` 嵌套调度走完整官方工具管线;`customTools.scope` 暂不支持(显式拒绝)
|
|
70
|
+
- 🛡️ **子代理工具策略**:module.yml 顶层 `subagentToolPolicy` 段(opt-in)声明 ceiling、profiles、角色卡绑定、有序任务规则与受控模型扩权;`subagent` 固定走 spawn、`subagent_fork` 固定走 fork,按官方 `SubagentRun`/continuable 契约创建并在窗口内冻结 toolFilter;模型选择器和扩权参数在工具 body 前校验,模型路由经过 LLM preflight;UI 从官方 sessions snapshot 读取当前会话,并可编辑、停用、预览策略及查询存活 Agent 工具面
|
|
58
71
|
- ♻️ **Session 日志读取**:引擎冷启动与幂等扫描统一走官方 `session.snapshotEvents()`(DSH `0.1.2-alpha.4+`),不再读取已移除的 `session.events` 数组。
|
|
59
|
-
- 🧩
|
|
72
|
+
- 🧩 **模板变量**:顶层 `variables` 提供 `{{key}}` 插值,注入动作的 `config.variables` 可局部覆盖。正文、匹配词、分类阈值与模型偏好由规则或模板显式提供;业务配置为空就不生成对应内容,引擎不内藏默认锚句。
|
|
60
73
|
- 💬 **会话变量工具**:`session_var`(list/get/set/clear)——模型维护角色状态(`{{心情}}` 等),会话级覆盖预设默认;ST 运行时宏(`{{lastusermessage}}` / `{{lastcharmessage}}`)从会话事件提取
|
|
61
74
|
- 🧩 **工具按模块装配**:角色卡、世界书、会话变量、自定义工具分别由 `character-tools` / `world-book-tools` / `session-var-tools` / `tool-config-engine` 模块提供;不再维护重复的顶层工具开关
|
|
62
|
-
- 📐
|
|
75
|
+
- 📐 **显式按需装配**:空模块不自动附加增强能力;模块人设来自顶层 `persona`,官方工具与普通委派仍由会话原有预设提供。首轮门控、来源过滤、工具名单、深思门与进度节拍统一由 `rules` 声明;总入口负责编译调度,`engine/conditions/` 判断,`engine/actions/` 执行。
|
|
63
76
|
|
|
64
77
|
## Web 客户端结构
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
每条配置保留一张卡,复用原卡壳、折叠箭头和拖拽样式,指令文件卡置顶。卡内通过“条件 / 动作 / JSON / 设置”平级侧边导航编辑,窄屏改为横向页签,正文归对应注入动作。上移、下移与拖拽按钮位于卡头总开关旁。Linear 风格统一桌面控件为 28px、按钮与下拉为胶囊;短输入按字符宽度收紧,下拉按最长选项文案限宽,数字 96px,正文与 JSON 保留完整编辑宽度。粗指针的 44px 触控目标扩大控件本体,不覆盖外部空白。
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
规则编辑器通过 `/rules` 保存局部修改与完整文件版本,保留在途新输入、未知字段和未完成 JSON 草稿。动态注入支持 `when`;固定注册的独占、抑制及安全守卫拒绝不支持的动态条件。具体支持范围见 [引擎复用指南](docs/engine-reuse.md#声明的条件与动作边界)。
|
|
69
82
|
|
|
70
83
|
Web 客户端按四层组织:
|
|
71
84
|
|
|
72
85
|
```text
|
|
73
86
|
src/client/
|
|
74
|
-
├─ app/ # SlotRegistry owner
|
|
87
|
+
├─ app/ # SlotRegistry owner、工作台壳与五页组合
|
|
75
88
|
├─ data/ # typed bridge、Fields、状态 facade、保存与脏检测纯逻辑
|
|
76
89
|
├─ features/ # prompts / tools / subagents / skills / presets / characters
|
|
77
90
|
└─ ui/ # 仅 props/callback 的共享交互与 CSS Modules
|
|
78
91
|
```
|
|
79
92
|
|
|
80
|
-
依赖方向固定为 `app → features → data/ui → shared contract`:跨领域组合只在 `app/workspace/pages/`,feature 不导入其他 feature
|
|
93
|
+
依赖方向固定为 `app → features → data/ui → shared contract`:跨领域组合只在 `app/workspace/pages/`,feature 不导入其他 feature 内部实现。控件由插件持有,不运行时加载 Harness Client 包;官方能力通过 Cordis 服务与 slot 接入,类型依赖保留。Client bridge 使用 `src/shared/bridge-contract.ts` 的 endpoint key 与 request/value map。样式按 owner 拆分,只共享 `--dsw-alias-*` 主题颜色,安装和释放随插件生命周期。
|
|
94
|
+
|
|
95
|
+
角色卡与世界书模型工具写入**该执行 Agent 实际配装的提示词层**(启用表 ∩ 磁盘,多个时取启用表第一个),不查询会话绑定的官方预设。保存等待物化完成;已保存但未生效会明确返回失败。失败定义保留诊断,方便修复。
|
|
81
96
|
完整的当前目录、slot 生命周期、状态边界、可访问性和维护约束见 [Web 客户端 UI 结构框架](docs/ui-architecture.md)。
|
|
82
97
|
|
|
83
98
|
### 配置卡与工具预览
|
|
84
99
|
|
|
85
|
-
-
|
|
100
|
+
- 五页工作台保留搜索、展开和滚动位置;工具、人设、策略以及非法 JSON/数字输入可跨页继续编辑。存在未保存草稿时,模块切换会提示先处理,不会无声覆盖。
|
|
86
101
|
- 配置保存与校验位于长列表操作区,失败就近显示;低频卡片操作收进菜单,删除和丢弃文件草稿需明确确认。窄容器自动换行,键盘操作、明暗主题和减弱动效均沿用宿主规范。
|
|
87
102
|
|
|
88
|
-
-
|
|
103
|
+
- 参数在所属规则的动作中编辑,主/子代理模型参数使用 `request-params` 动作与受众条件。共享能力仍按实际 `modules` 装配显示,当前会话模型继续使用官方选择控件。
|
|
89
104
|
- 「指令提示」统一通过「前置步骤 → 内容策略:动态填充 → 填充来源:指令提示」编辑,不再作为独立策略入口。指令文件正文和授权保存仍归独立文件通道,不复制进预设。
|
|
90
|
-
- AGENTS.md / CLAUDE.md
|
|
105
|
+
- AGENTS.md / CLAUDE.md 这类指令文件复用普通配置卡组件,保留名称、路径、正文和后续官方注入的启停;位置、顺序、晋升、受众与模型范围由官方负责,不提供文件卡控制项。名称和启停落独立指令策略,正文就地编辑、**焦点离开卡片时自动写回原文件**(版本冲突保留草稿并提供「重新读取」,没有单独的保存按钮)。
|
|
91
106
|
- 创建菜单始终提供全部层级模板、工具和可添加能力,不受当前列表筛选限制。创建后只展开目标卡片(当它落在当前筛选视野内),不改动层级筛选与搜索词;同一提示词模板可重复创建,自动分配不重复标识。空内容不等于删除,保存和后台刷新保留未完成草稿;工具草稿在层级/世界书筛选往返时不丢失。
|
|
92
|
-
-
|
|
107
|
+
- 插件装配自身工具能力,官方文件系统与编辑工具仍由宿主预设提供。自定义模型工具经「添加能力 / 工具模块 → 添加工具模板 / 新建空白工具」配置名称、描述、参数、输出与执行器,保存前完整校验;不安装、连接或管理外部 MCP/DSH 插件。
|
|
93
108
|
- 「工具预览」是独立顶层页,参照官方插件目录:顶部统一搜索、可折叠分组、右侧预设选择、双列展开详情卡,窄屏单列。卡片显示「模型可见」,不伪造插件运行状态。当前会话与所选预设分别读取:既有会话仍使用冻结 generation,修改预设只影响后续 generation;不会隐藏同名自定义工具或自动恢复会话。
|
|
94
109
|
- 在工具链的 `tool-config-engine` 能力卡中配置哪些自定义执行器需要用户批准;缺少批准服务时拒绝执行。生成目录保持只读,仍由预设重建产生。
|
|
95
110
|
|
|
@@ -104,40 +119,43 @@ src/client/
|
|
|
104
119
|
`v2` 已按当前 `app / data / features / ui` 客户端结构更新,并通过 showcase 9/9 校验;无 `v2` 后缀的文件保留为首轮构图记录。本轮已使用 Microsoft Edge 完成 visual-check:containment/captures 均通过,保留 1440×900 与 2048×1320 的明暗截图及联系页;自动收据的 `visualReview` 仍为 `pending`,仅表示需要人工查看截图,不代表渲染失败。
|
|
105
120
|
|
|
106
121
|
|
|
107
|
-
##
|
|
122
|
+
## 模块参数体系
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
初始化直接按同名复制:缺哪个目录只补哪个,已有目录不覆盖,也不运行时探测官方名称或重命名。
|
|
111
|
-
用户保存时以当前预设自身的 `preset.yml` 生成运行产物;现有用户预设不自动改名。
|
|
124
|
+
初始化从包内 modules/ 补建缺失模块,已有目录不覆盖。包内内置模块三个:`ponytail`(懒惰资深工程师行为规则)、`tool-surface`(工具面收窄到常驻核心集,其余按需解锁)、`skill-surface`(拦掉全量技能目录注入,改按需发现)——后两个默认不启用,需在「模块」页启用并重启。materializeModule 原地校验恢复 rules/,只清理已知旧产物,保留记忆、技能、正文与用户资产。工具和子代理策略从完整定义内联装配。
|
|
112
125
|
|
|
113
|
-
|
|
126
|
+
部署 Config 使用 modulesEnabled 作为模块运行总闸:关闭仅撤回贡献,不清空文件。旧 writePreset 仅作输入兼容,双键冲突拒绝。编辑目标通过 x-module-id 传递,不切换或跟随官方会话预设。
|
|
127
|
+
|
|
128
|
+
模块行为由一份 `module.yml` 下发,参数所有者各自独立:
|
|
114
129
|
|
|
115
130
|
| 层 | 职责 |
|
|
116
131
|
|---|---|
|
|
117
|
-
| `layerSettings.<层名>` |
|
|
132
|
+
| `layerSettings.<层名>` | 模块共享参数;磁盘归属由参数目录的 `storageLayer` 固定,不随 UI 分组改变 |
|
|
118
133
|
| `moduleConfigs` | 行级 config 直写通道(参数桥未覆盖的键:超时/环境白名单/ST 导入等),不锁定覆盖 UI 可管理参数 |
|
|
119
|
-
| `
|
|
134
|
+
| `rules` | 独立身份、条件树 `when` 与动作数组 `do`;注入正文与参数归相应动作 |
|
|
135
|
+
| `configOrder` | 规则 ID 到序号的完整定义映射;运行切片中为 _settings.yml.rules[id].order,文件名不带序号 |
|
|
120
136
|
|
|
121
137
|
### 共享参数一览(全部可选,缺省按对应模块解释)
|
|
122
138
|
|
|
123
139
|
| 分类 | 键 |
|
|
124
140
|
|---|---|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
| 策略深度 | `maxDepth`,只约束已启用的插件子代理工具策略;普通官方委派由宿主管理 |
|
|
142
|
+
| 指令提示 | `instructionHint`,默认关闭 |
|
|
143
|
+
| 工具 | `toolGitBashEnabled` `customToolRequireApproval` |
|
|
144
|
+
|
|
145
|
+
以上共 4 个公开共享参数。模型参数属于 `request-params` 动作,主/子代理由条件区分;旧模型键与 15 个锚定/引导快捷键只供离线迁移读取,不再提供在线保存入口。无法等价承接的旧值明确拒迁,不丢弃也不静默激活。编辑器输出上限 `strReplaceEditorMaxOutputChars` 由宿主工具配置负责。
|
|
130
146
|
|
|
131
|
-
>
|
|
147
|
+
> 首轮工具面与输出封顶、pre-step 来源名单、常驻工具白/黑名单、锚句、深思门与进度节拍通过 `rules` 声明(示例见 [engine 复用指南](docs/engine-reuse.md));子代理工具面仍由 `subagentToolPolicy` 实例策略授权。
|
|
132
148
|
|
|
133
|
-
|
|
149
|
+
AGENTS.md 走「文件即真相」:文件集合、正文与版本不物化进生成目录,而是由宿主按本会话工作区现场解析(`$DSH_HOME/AGENTS.md` + 工作区 cwd→项目根链的 AGENTS.md/CLAUDE.md/AGENTS.local.md/CLAUDE.local.md);工作台中的文件卡指向原文件,卡片定义与正文都不进 `module.yml`。插件不写常驻受管块。
|
|
134
150
|
|
|
135
|
-
|
|
151
|
+
官方指令注入与逐文件开关:
|
|
136
152
|
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
153
|
+
- 官方 `@deepseek-ai/dsh-agent-instructions` 负责发现、读取、预算、更新与压缩恢复,预设须保留官方指令行;插件只在消息进入会话前过滤,不再自行注入文件正文。文件可读不代表已经注入。
|
|
154
|
+
- 指令文件卡保留名称、正文编辑和启停;关闭只拦截该文件**后续**的官方注入,历史正文不撤回,重新开启不强制重放。首次请求前关闭即可阻止可识别文件进入新历史;开关不阻止官方读取文件。
|
|
155
|
+
- `$DSH_HOME/.prompt-tool/instructions.yml` 只保存 `files[fileId].enabled` 与 `name`,跨预设共享,**默认放行**。不再提供「独立指令文件来源」总开关或文件级位置、顺序、晋升、受众、模型范围;自定义消息仍用普通前置步骤配置。
|
|
156
|
+
- 旧策略顶层 `enabled` / `defaults` 及文件级注入参数读取时忽略、下一次成功保存时清理;旧顶层关闭不会转换为逐文件关闭。`module.yml#agentsHints` 仍不生效,策略缺失不因读取自动创建。
|
|
157
|
+
- 过滤覆盖官方基线、附加、更新和移除消息,并同步对应 `source.changes`。无法可靠分段的官方内容原样放行并诊断;不识别的文件身份也保持官方内容,不新增远程来源管理。已被官方预算省略的内容不由插件补回。
|
|
158
|
+
- 预设级 `instructionHint` 保持可选、默认关闭;启用后先过滤,再把剩余符合条件的官方全文转换为路径提示。它不替换已经进入历史的全文。决策边界见 [ADR-0004](docs/adr/0004-official-instruction-filter.md)。
|
|
141
159
|
|
|
142
160
|
指令文件正文属于用户自己的文件,不受预设生命周期管辖。工作台按**打开时解析出的会话工作区**读取文件快照(正文 + 文件身份 + 字节版本 + 读取状态一次取回),正文在**焦点离开卡片**(或列表「保存」)时写回原文件:
|
|
143
161
|
|
|
@@ -146,14 +164,14 @@ src/client/
|
|
|
146
164
|
- 读取失败(不可读/超限/文件消失)与「读取成功的空文件」严格区分:前者不可编辑、不可保存,不用空正文掩盖错误。
|
|
147
165
|
- 原子写入(tmp + rename,保留原权限),失败保留原文件并清理临时文件;正文不受预设变量插值影响。
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
模型参数按模块独立保存,不自动回写宿主全局默认。主模型参数作用于主会话请求;子模型 provider/name 与采样参数作用于本地子代理的实际请求,不改写普通官方委派的 spawn 预检。
|
|
150
168
|
|
|
151
169
|
| 段 | 键 |
|
|
152
170
|
|---|---|
|
|
153
171
|
| `layerSettings.agent-request`(主对话) | `modelProvider` `modelName` `modelReasoningEffort` `modelTemperature` `modelMaxTokens` |
|
|
154
172
|
| `layerSettings.subagent-start`(子代理) | `subagentModelProvider` `subagentModelName` `subagentReasoningEffort` `subagentTemperature` `subagentMaxTokens` `maxDepth` |
|
|
155
173
|
|
|
156
|
-
读取当前结构后展平到内部 EngineParams
|
|
174
|
+
读取当前结构后展平到内部 EngineParams,保存只更新 `storageLayer` 指定的层。旧顶层 `params` / 模型段不参与运行参数,规则快捷键的兼容范围见[参数框架](docs/architecture-params.md)。人设独立写在顶层 `persona` 段,由模块配装通道在 Agent scope 注册。示例:
|
|
157
175
|
|
|
158
176
|
```yaml
|
|
159
177
|
persona:
|
|
@@ -165,9 +183,9 @@ persona:
|
|
|
165
183
|
|
|
166
184
|
工作台「模型路由」卡顶部另有**当前会话**区(仅主对话作用域):显示活动会话的模型/思维程度(会话 `modelSelection` 投影,缺省回退宿主默认),模型下拉展示全部可用模型并按服务商分组,选择模型时自动回写对应服务商;切换走官方 `session.selectModel`——对当前会话立即生效并被宿主持久化为新会话默认,与官方模型选择器双向同源;子代理会话与宿主默认场景不支持会话级切换。预设参数非空时按请求覆盖会话选择(参数桥优先级不变)。
|
|
167
185
|
|
|
168
|
-
|
|
186
|
+
模块编辑选择与官方会话预设独立。切换编辑模块只改变请求目标;未指定目标时由服务端解析默认目录,bootstrap 的模块身份、参数、变量与配置卡来自同一目录。启用哪些模块由 `config.yml.enabled` 决定。
|
|
169
187
|
|
|
170
|
-
> 根目录 [
|
|
188
|
+
> 根目录 [module.yml](module.yml) 覆盖 4 个公开共享参数与九层规则。`pnpm rebuild:preset-template` 从权威契约重建;规则默认关闭,共享参数按需取消注释。
|
|
171
189
|
|
|
172
190
|
## 提示词配置(九个官方插入点)
|
|
173
191
|
|
|
@@ -183,44 +201,44 @@ persona:
|
|
|
183
201
|
| `subagent-start` | `subagent/start` + `Agent.inject` | 子代理事件匹配与注入文本;模型/深度在卡内共享设置 |
|
|
184
202
|
| `subagent-end` | `subagent/end` + 可选 `Agent.inject` | `params.action=observe\|inject-main`,后者投递到所属主会话 |
|
|
185
203
|
|
|
186
|
-
|
|
204
|
+
九个插入点彼此独立,没有跨层全局运行顺序。模块配置在同一插入点、位置内按 `configOrder` 序号排列;`system-section` 与 `runtime-context` 的 `order` 保留官方定位语义。
|
|
187
205
|
UI / 写盘按上表分组;这是展示顺序,不是模型提示词优先级。详细支持字段、限制与官方依据见[九层对照](docs/injection-point-contracts.md)。
|
|
188
206
|
模型实际收到的提示词文本顺序更接近 `system-section → runtime-context → pre-step`;`agent-request` / `llm-stream` / `tool-pipeline` 是控制通道。
|
|
189
207
|
|
|
190
|
-
|
|
208
|
+
主会话与子代理的配置列表沿用原模块页的拖拽按钮样式,也可聚焦按钮后用上下方向键移动。“当前模块”范围保留配置编辑与本模块排序;点击“跨模块排序”后,同一列表显示已启用模块中符合当前受众和筛选的配置摘要,支持跨模块拖拽和上下移,隐藏配置保留原有槽位。进入前保存当前模块配置草稿,字段等草稿未完成时拒绝切换;返回当前模块时重新读取。模块页不再另设排序区。服务端按模块 ID+配置 ID 写回轻量序号,拒绝未知、重复身份和过期版本,不接收正文或任意序号。新启用模块的未编号或冲突配置接在尾部,重复启用保序,详见 [排序决策](docs/adr/0006-module-config-order.md)。
|
|
191
209
|
|
|
192
210
|
- `mergeMode`:`separate`(默认)同位置多条为独立消息;`merged` 同位置拼接为一条
|
|
193
|
-
- `order
|
|
211
|
+
- `order`:官方 system/context 位置、无模块来源的独立引擎与触发器、ST 世界书预算仍保留各自语义,不由排序界面改写
|
|
194
212
|
- 文本插值:`{{key}}` 全层支持——配置/预设 `variables` 优先,ST 运行时宏(lastusermessage 等)次之,内置 `{{DSH_HOME}}/{{WORKSPACE}}/{{CWD}}` 兜底,未注册保留字面(system-section 注册期无会话时运行时宏替换为空,不残留)
|
|
195
213
|
|
|
196
214
|
|
|
197
215
|
## SillyTavern 导入
|
|
198
216
|
|
|
199
|
-
|
|
217
|
+
工作台「模块」页按内容识别原生模块与 SillyTavern 来源;ST JSON/YAML 预设卡片经预览确认后,按注入层级映射为本地模块:
|
|
200
218
|
|
|
201
|
-
- `prompts[]` → `
|
|
219
|
+
- `prompts[]` → `rules[].do` 中的注入动作:system 角色进入 `system-section`,其余进入 `pre-step`;官方 `prompt_order[].order[]` 决定启停与相对顺序,深度位置保留来源并报告降级
|
|
202
220
|
- 采样参数(`temperature` / `openai_max_tokens` / `reasoning_effort`)**剥离**——模型参数统一由「模型设置」UI / 宿主默认管理
|
|
203
221
|
- ST 变量:保留可启停的赋值模板,在运行时顺序求值;声明变量在合并和角色卡应用时保持局部绑定
|
|
204
222
|
- ST 管理工具:始终装配 `character-tools`、`session-var-tools` 与 `tool-config-engine`
|
|
205
|
-
- `enable_web_search`:`true` → 额外组装 `tool-web`(fetch 启用);`false` →
|
|
223
|
+
- `enable_web_search`:`true` → 额外组装 `tool-web`(fetch 启用);`false` → 产出三条规则(`assembly` 呈现剔除 + `sdk-strip` 裁 `tools:sdk` 正文 + `guard` 执行层拒绝,共用同一份 `deny: [web_search, web_fetch]`),PTC 下同样生效
|
|
206
224
|
- 含有效 `character_book` 条目时自动追加 `world-book-tools` 模块,使导入预设可直接调用世界书管理工具
|
|
207
225
|
- 世界书条目级条件:`delayUntilRecursion`(延迟到递归扫描的层级池)、`useGroupScoring`(组内评分淘汰)、`matchCreatorNotes` / `matchCharacterDepthPrompt`(按需扫描卡片备注与深度提示词)按 ST 语义求值;`characterFilter`(角色/标签过滤)、`automationId`(STscript 自动化)、`outletName` 与向量检索**不实现**,只保留来源事实并在预览卡里逐条告警
|
|
208
226
|
- 触发键里的 ST 宏(例如只存在于 ST 全局 persona 的 `{{user}}`)登记为「模板变量」空占位并产出诊断:未赋值时该键不参与匹配(不会退化成字面量误判),在模板变量里赋值后按既有匹配路径生效
|
|
209
227
|
- 角色卡 `extensions.depth_prompt` 保留为一条**默认禁用**的 pre-step 配置(ST 只在群聊自动注入),可在工作台手动启用
|
|
210
|
-
- `modules` 按需装配:`
|
|
228
|
+
- `modules` 按需装配:`rule-engine` 与上述 ST 管理工具承接转换产物;含 system-section 时补 `persona`(`complete: false` 允许 system 段生效);含有效世界书条目时补 `world-book-tools`
|
|
211
229
|
|
|
212
|
-
|
|
230
|
+
转换结果是一个普通模块(id 由文件名生成),可在工作台「模块」页的模块列表中直接启用。字段级参数对照与完整示例见 [SillyTavern.md](docs/SillyTavern.md)。
|
|
213
231
|
|
|
214
|
-
### 角色卡(PNG / JSON
|
|
232
|
+
### 角色卡(PNG / JSON)导入
|
|
215
233
|
|
|
216
|
-
|
|
234
|
+
工作台「模块」页将角色卡作为来源导入普通 `.prompt-tool/modules/<id>/`:
|
|
217
235
|
|
|
218
236
|
- **PNG**:`ccv3` 优先 / `chara` 兜底;按魔数识别并受限解码,原始文件先暂存、预览确认后入库,保留原图。
|
|
219
237
|
- **JSON/YAML**:支持 ST 角色数据和自包含原生角色片段。批次逐张预览,不把多份来源合并成一张角色卡;所有大小的文件都先预览再提交。
|
|
220
238
|
- 正文映射:`first_mes` → 开场白(`dedupe: session`)、`alternate_greetings` → 备用开场白、
|
|
221
239
|
`description/personality/scenario` → 角色设定;采样参数剥离(模型设置 UI 管理)
|
|
222
|
-
-
|
|
223
|
-
-
|
|
240
|
+
- **统一模块身份**:人设、开场白和世界书写完整 module.yml,规则切片与其他模块一致;没有角色专属目录、前缀或登记
|
|
241
|
+
- **模块记忆**:memory.md 属于模块,更新保留本地记忆和用户资产;world_book_read_memory 按需读取,不自动注入
|
|
224
242
|
|
|
225
243
|
### 世界书(world-book 策略)
|
|
226
244
|
|
|
@@ -229,11 +247,11 @@ UI / 写盘按上表分组;这是展示顺序,不是模型提示词优先级
|
|
|
229
247
|
- **ST 注入语义**:常驻候选或主键命中,再按副键逻辑、概率、分组与时序筛选;非常驻无主键不注入;原生手写 world-book 约定保持不变
|
|
230
248
|
- **匹配选项**:`caseSensitive` / `wholeWords`;只有 `/pattern/flags` 形式识别为正则,其余为字面键
|
|
231
249
|
- **管理**:模块列表顶部下拉选「世界书」过滤(完整模块卡片编辑 + 批量启用/禁用);
|
|
232
|
-
模型工具
|
|
250
|
+
模型工具 world_book_list/upsert/delete(note 追加模块记忆)与 world_book_read_memory(按需读取)
|
|
233
251
|
- **ST 变量**:赋值不在导入时执行;local/global 分表但只在会话内有效,嵌套宏有循环与大小保护。
|
|
234
252
|
深度历史位置、system 角色、token 预算和 ST 扩展脚本不具备完整等价性,详见兼容边界
|
|
235
253
|
- **会话变量**:`session_var` 工具(list/get/set/clear)维护角色状态(会话级覆盖预设默认,
|
|
236
|
-
|
|
254
|
+
结束即失);跨会话文本通过 note 保存在模块 memory.md,读取失败会明确报错
|
|
237
255
|
|
|
238
256
|
详细转换规则见 [SillyTavern.md](docs/SillyTavern.md)。
|
|
239
257
|
|
|
@@ -243,20 +261,19 @@ UI / 写盘按上表分组;这是展示顺序,不是模型提示词优先级
|
|
|
243
261
|
pnpm install && pnpm build
|
|
244
262
|
pnpm test # 全量契约与行为测试(隔离 cwd 运行):参数契约/注入装配/六插入点/生成链路/引擎语义/组合重建/模型路由/UI 契约/安全边界
|
|
245
263
|
pnpm typecheck && pnpm lint
|
|
246
|
-
pnpm verify:host #
|
|
264
|
+
pnpm verify:host # 官方包范围、安装版本、解析目标、类型/运行时依赖与 Client 模块边界
|
|
247
265
|
pnpm sync:yaml # 刷新 engine/vendor/yaml(生成目录运行时 YAML 解析器)
|
|
248
|
-
pnpm rebuild:composition # 只生成官方切块/变体;source/local 本地源不复制(失败安全)
|
|
249
266
|
```
|
|
250
267
|
|
|
251
268
|
测试由 `scripts/run-tests.mjs` 启动:先跑 build,再以独立临时 cwd 与 TEMP/TMP 启动 Node 内置 test runner,用例路径为绝对路径,避免相对 cwd 的测试污染仓库。
|
|
252
269
|
|
|
253
270
|
依赖升级后的验证顺序:`pnpm install` → `pnpm verify:host` → `pnpm typecheck && pnpm lint` → `pnpm test`。官方源码联调请使用不入库的显式本地 override,不要恢复 `pnpm-workspace.yaml` 里的 `link:` 默认配置。
|
|
254
271
|
|
|
255
|
-
发布类型声明通过 `deps.dts.neverBundle` 引用官方 SDK,不内联其品牌类型与相对模块扩充;公开类型引用的包须声明为生产或 peer 依赖,不能仅存在于 devDependencies。`deps.onlyBundle` 显式约束内联依赖(服务端为空,客户端仅 `clsx
|
|
272
|
+
发布类型声明通过 `deps.dts.neverBundle` 引用官方 SDK,不内联其品牌类型与相对模块扩充;公开类型引用的包须声明为生产或 peer 依赖,不能仅存在于 devDependencies。`deps.onlyBundle` 显式约束内联依赖(服务端为空,客户端仅 `clsx`),新增依赖需重新核对打包边界。客户端仍保留宿主 loader 要求的 CJS 协议,不为消除通用 ESM 建议而切换格式。
|
|
256
273
|
|
|
257
274
|
## 排障:插件未加载时
|
|
258
275
|
|
|
259
|
-
启动日志出现 `dsh: skipping profile bundle "dsh-plugin-prompt-tool"`
|
|
276
|
+
启动日志出现 `dsh: skipping profile bundle "dsh-plugin-prompt-tool"` 时,插件整体未加载,预设种子补建也不会运行。插件启动只诊断缺失 Web 能力,不写 profile manifest 或修复包链接。按顺序检查:
|
|
260
277
|
|
|
261
278
|
1. **看 bundles 列表**:`<DSH_HOME>/profiles/<name>/package.json` 的 `dsh.profile.bundles` 是否含 `dsh-plugin-prompt-tool`。
|
|
262
279
|
2. **体检依赖链接**(只读、零写入):
|
|
@@ -266,13 +283,13 @@ pnpm rebuild:composition # 只生成官方切块/变体;source/local 本地源
|
|
|
266
283
|
```
|
|
267
284
|
|
|
268
285
|
它按 dsh 同一口径(`createRequire(<profile>/package.json).resolve.paths`)逐项判定,并额外报出**链接目标是否存在**——「链接在、目标不在」的悬空链接正是最常见的失败形态(例如相对深度算错一层)。
|
|
269
|
-
3.
|
|
286
|
+
3. **修复**:体检命令始终只读。缺失依赖或链接交给官方插件管理器;在 Harness 中使用 `plugin_manager` 安装或管理 bundle,CLI 的依赖修复入口为:
|
|
270
287
|
|
|
271
288
|
```powershell
|
|
272
289
|
dsh plugin --profile web install
|
|
273
290
|
```
|
|
274
291
|
|
|
275
|
-
|
|
292
|
+
替换已安装包后需要用户重启 DSH;插件不自动重启服务。
|
|
276
293
|
|
|
277
294
|
## 许可
|
|
278
295
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { labelOf, requireString } from './shared.mjs'
|
|
2
|
+
import { createTurnStopBudget, pluginMessage } from '../layers.mjs'
|
|
3
|
+
|
|
4
|
+
export function prepareAppendContext(action, plugin) {
|
|
5
|
+
const label = labelOf(action)
|
|
6
|
+
const text = requireString(action.text, `${label}.text`, plugin)
|
|
7
|
+
if (text.length === 0) return
|
|
8
|
+
if (action.mode === 'continue') {
|
|
9
|
+
return (_ctx, { warnOnce, on, collect, take }) => {
|
|
10
|
+
const budget = createTurnStopBudget()
|
|
11
|
+
collect(on('agent/turn-stopping', ({ agent, turn } = {}) => {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof action.match === 'function' && action.match(agent, turn) !== true) return
|
|
14
|
+
const session = agent?.session
|
|
15
|
+
if (session?.id === undefined || typeof agent.steer !== 'function') return
|
|
16
|
+
const entry = budget.entry(session.id, turn)
|
|
17
|
+
if (!budget.available(entry, turn) || !take({ agent })) return
|
|
18
|
+
// 计数在 steer 之前落账:steer 抛错也不允许下一步重试越过预算。
|
|
19
|
+
budget.claim(entry, turn)
|
|
20
|
+
// 身份归本动作:复用 pluginMessage 的形状,但 source.plugin 必须是动作命名空间
|
|
21
|
+
// (层模块的 pluginMessage 用 layers 自己的身份,那不是本动作的名字)。
|
|
22
|
+
const message = pluginMessage(`action-${label}`, text, `${label} continue`)
|
|
23
|
+
agent.steer({ ...message, source: { ...message.source, plugin: label } })
|
|
24
|
+
} catch (error) {
|
|
25
|
+
warnOnce(`${plugin}: append-context(continue) action ${label} failed: ${String(error?.message ?? error)}`)
|
|
26
|
+
}
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// 追加上下文:只回 user 角色的 durable 通知(宿主把 additionalContexts 落成 user 消息)。
|
|
31
|
+
return (_ctx, { warnOnce, on, collect, take }) => collect(on('tools/post-execute', async (exec, result, next) => {
|
|
32
|
+
const decision = await next()
|
|
33
|
+
try {
|
|
34
|
+
if (typeof action.match === 'function' && action.match(exec, result, decision) !== true) return decision
|
|
35
|
+
if (decision?.kind !== 'accept') return decision
|
|
36
|
+
// source 形状与 pluginMessage 同源:v4 要求 kind 是生产者名(`plugin:<name>`),
|
|
37
|
+
// 裸 `plugin` 会被会话格式校验拒绝,这条 durable user 通知就落不了盘。
|
|
38
|
+
const notice = {
|
|
39
|
+
...pluginMessage(`action-${label}`, text, `${label} context`),
|
|
40
|
+
source: { kind: `plugin:${label}`, plugin: label, form: 'notice', summary: `${label} context` },
|
|
41
|
+
}
|
|
42
|
+
const updated = { ...decision, additionalContexts: [...(decision.additionalContexts ?? []), notice] }
|
|
43
|
+
return take(exec) ? updated : decision
|
|
44
|
+
} catch (error) {
|
|
45
|
+
warnOnce(`${plugin}: append-context action ${label} failed, keeping the plain result: ${String(error?.message ?? error)}`)
|
|
46
|
+
return decision
|
|
47
|
+
}
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { NAME_LIST, createMask, labelOf, requireString } from './shared.mjs'
|
|
2
|
+
import { sessionEvents } from '../shared.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 动态白名单:从**本会话已持久化的** `tool/call` 事件里回收解锁名单,填入 `into`。
|
|
6
|
+
*
|
|
7
|
+
* 语义对齐原 `tool-bootstrap.mjs` 的 `unlockedFor(session)`:某个发现工具(如
|
|
8
|
+
* `dev_tool_search`)上一次调用时把要解锁的工具名写进了自己的调用参数,本函数把它读回来
|
|
9
|
+
* 填入 allow,于是**解锁能跨请求保留**——没有它,解锁是一次性的(当次请求用完即被裁掉)。
|
|
10
|
+
*
|
|
11
|
+
* 判据刻意保守,坏数据只影响该条、绝不上抛:
|
|
12
|
+
* - 只认 `event.type === 'tool/call'` 且 `event.data.name` **精确等于**声明里的 `tool`;
|
|
13
|
+
* - `arguments` 是模型产出的任意字符串,`JSON.parse` 必须 try/catch;解析出的必须是对象;
|
|
14
|
+
* - 只取 `key` 指向的**字符串数组**里的字符串项,非字符串项逐项丢弃,不展开对象、不 eval;
|
|
15
|
+
* - 任一步不成立就跳过该条事件,其余事件照常累积;全程无异常(读事件本身除外,由调用方兜)。
|
|
16
|
+
*
|
|
17
|
+
* `tool` / `key` **按参数传入**而不是向外层读取——这个模块会被多个 assembly 动作各挂一次,
|
|
18
|
+
* 任何模块级可变状态都会让它们互相覆盖。
|
|
19
|
+
*/
|
|
20
|
+
function fillUnlocked(into, session, tool, key, warnOnce, plugin, label) {
|
|
21
|
+
into.clear()
|
|
22
|
+
let events
|
|
23
|
+
try {
|
|
24
|
+
events = sessionEvents(session)
|
|
25
|
+
} catch (error) {
|
|
26
|
+
warnOnce(`${plugin}: assembly action ${label}: reading session events for allowFrom failed: ${String(error?.message ?? error)}`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
for (const event of events) {
|
|
30
|
+
if (event?.type !== 'tool/call' || event?.data?.name !== tool) continue
|
|
31
|
+
const raw = event.data.arguments
|
|
32
|
+
const parsed = typeof raw === 'string' ? safeParse(raw) : raw
|
|
33
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) continue
|
|
34
|
+
const value = parsed[key]
|
|
35
|
+
if (!Array.isArray(value)) continue
|
|
36
|
+
for (const item of value) if (typeof item === 'string' && item.length > 0) into.add(item)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function safeParse(text) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(text)
|
|
43
|
+
} catch {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function prepareAssembly(action, plugin) {
|
|
49
|
+
const target = action.target
|
|
50
|
+
if (target === null || typeof target !== 'object' || Array.isArray(target)) {
|
|
51
|
+
throw new TypeError(`${plugin}: assembly requires a target object — { tools } / { sections } / { contexts }`)
|
|
52
|
+
}
|
|
53
|
+
const toolsSource = target.tools
|
|
54
|
+
const allowFrom = toolsSource?.allowFrom
|
|
55
|
+
let allowExtra
|
|
56
|
+
if (allowFrom !== undefined) {
|
|
57
|
+
if (allowFrom === null || typeof allowFrom !== 'object' || Array.isArray(allowFrom)) {
|
|
58
|
+
throw new TypeError(`${plugin}: assembly.tools.allowFrom must be an object — { tool, key }`)
|
|
59
|
+
}
|
|
60
|
+
requireString(allowFrom.tool, 'assembly.tools.allowFrom.tool', plugin)
|
|
61
|
+
requireString(allowFrom.key, 'assembly.tools.allowFrom.key', plugin)
|
|
62
|
+
// deny 是黑名单、动态项只做加法,同声明的语义不可解释(且 allowFrom 已经覆盖不了 deny
|
|
63
|
+
// 的名字到底该不该出现),挂载期直接拒,而不是运行期猜。
|
|
64
|
+
if (Array.isArray(toolsSource.deny) && toolsSource.deny.length > 0) {
|
|
65
|
+
throw new TypeError(`${plugin}: assembly.tools cannot combine deny with allowFrom — 动态项只做加法,与黑名单语义冲突`)
|
|
66
|
+
}
|
|
67
|
+
allowExtra = new Set()
|
|
68
|
+
}
|
|
69
|
+
const tools = toolsSource === undefined ? undefined : createMask(toolsSource, 'assembly.tools', plugin, allowExtra)
|
|
70
|
+
const sectionsAdd = target.sections?.add
|
|
71
|
+
const sectionsRemove = NAME_LIST.parse(target.sections?.remove, plugin, 'assembly.sections.remove')
|
|
72
|
+
// 白名单(2026-09-22 用户拍板「扩动作库」):`personaSectionsOnly` 一类的语义是
|
|
73
|
+
// `filter(白名单)`,而 remove 是黑名单——未列名的第三方/晚到段不会被它删掉。
|
|
74
|
+
// 两种语义**互斥**:同时声明即挂载期 fail loud,而不是静默二选一。
|
|
75
|
+
const sectionsKeep = NAME_LIST.parse(target.sections?.keep, plugin, 'assembly.sections.keep')
|
|
76
|
+
if (sectionsKeep !== undefined && sectionsRemove !== undefined) {
|
|
77
|
+
throw new TypeError(`${plugin}: assembly target.sections cannot combine keep with remove — 一种是白名单、一种是黑名单,同时声明语义不明`)
|
|
78
|
+
}
|
|
79
|
+
const contextsAdd = target.contexts?.add
|
|
80
|
+
const contextsRemove = NAME_LIST.parse(target.contexts?.remove, plugin, 'assembly.contexts.remove')
|
|
81
|
+
const contextsClear = target.contexts?.clear === true
|
|
82
|
+
for (const [key, list] of [['sections.add', sectionsAdd], ['contexts.add', contextsAdd]]) {
|
|
83
|
+
if (list === undefined) continue
|
|
84
|
+
if (!Array.isArray(list)) throw new TypeError(`${plugin}: assembly.${key} must be an array of { name, text }`)
|
|
85
|
+
for (const entry of list) {
|
|
86
|
+
if (entry === null || typeof entry !== 'object') throw new TypeError(`${plugin}: assembly.${key} entries must be objects`)
|
|
87
|
+
requireString(entry.name, `assembly.${key}[].name`, plugin)
|
|
88
|
+
requireString(entry.text, `assembly.${key}[].text`, plugin)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return (_ctx, { warnOnce, on, collect, take }) => collect(on('system-prompt/assemble', async (assembly, context, next) => {
|
|
92
|
+
// Downstream errors propagate untouched; only this action's own logic is guarded.
|
|
93
|
+
const assembled = await next()
|
|
94
|
+
try {
|
|
95
|
+
if (typeof action.match === 'function' && action.match(context, assembled) !== true) return assembled
|
|
96
|
+
let result = assembled
|
|
97
|
+
if (tools !== undefined && Array.isArray(result.tools)) {
|
|
98
|
+
// 每轮重算解锁名单:本轮装配要反映**本会话至今**的全部解锁调用。
|
|
99
|
+
if (allowExtra !== undefined) {
|
|
100
|
+
fillUnlocked(allowExtra, context?.session ?? context?.agent?.session, allowFrom.tool, allowFrom.key, warnOnce, plugin, labelOf(action))
|
|
101
|
+
}
|
|
102
|
+
// keep 名单兜底(2026-09-22 用户拍板,**可选**):判据与原 `tool-bootstrap` 逐条对齐——
|
|
103
|
+
// 原模块是 `missing = required.filter(t => !available.has(t))`
|
|
104
|
+
// 且 `missing.length > 0` 即放弃裁剪,也就是「**任意一个** 名单工具缺失」而不是
|
|
105
|
+
// 「一个都不存在」(对拍实测:目录 `[bash,read]` + 名单 `[bash,str_replace_editor]` 时
|
|
106
|
+
// 原模块给完整目录,只判"全缺"会裁成 `[bash]`)。
|
|
107
|
+
// 声明写 `requireMatch: true` 才启用;**不写就照名单裁**(与 `tool-filter.mjs` 一致)。
|
|
108
|
+
const available = new Set(result.tools.map((tool) => tool?.name).filter((name) => typeof name === 'string'))
|
|
109
|
+
// 必需名单 = 静态 allow ∪ 本轮解锁项:解锁项同样是「本该存在」的工具,缺了就 fail-open。
|
|
110
|
+
const required = tools.allow === undefined && allowExtra === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: new Set([...(tools.allow ?? []), ...(allowExtra ?? [])])
|
|
113
|
+
const missingRequired = tools.requireMatch && required !== undefined
|
|
114
|
+
&& [...required].some((name) => !available.has(name))
|
|
115
|
+
if (missingRequired) {
|
|
116
|
+
warnOnce(`${plugin}: assembly action ${labelOf(action)}: a requireMatch tool is absent — exposing the full tool set (fail-open)`)
|
|
117
|
+
} else {
|
|
118
|
+
const kept = result.tools.filter((tool) => !tools.blocks(tool?.name))
|
|
119
|
+
if (kept.length !== result.tools.length) result = { ...result, tools: kept }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(result.sections) && (sectionsAdd !== undefined || sectionsRemove !== undefined || sectionsKeep !== undefined)) {
|
|
123
|
+
const kept = sectionsKeep !== undefined
|
|
124
|
+
? result.sections.filter((section) => sectionsKeep.has(section?.name))
|
|
125
|
+
: sectionsRemove === undefined
|
|
126
|
+
? result.sections
|
|
127
|
+
: result.sections.filter((section) => !sectionsRemove.has(section?.name))
|
|
128
|
+
if (kept.length !== result.sections.length || sectionsAdd?.length > 0) {
|
|
129
|
+
result = { ...result, sections: sectionsAdd === undefined ? kept : [...kept, ...sectionsAdd] }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (Array.isArray(result.contexts)) {
|
|
133
|
+
if (contextsClear) {
|
|
134
|
+
if (result.contexts.length > 0) result = { ...result, contexts: [] }
|
|
135
|
+
} else if (contextsAdd !== undefined || contextsRemove !== undefined) {
|
|
136
|
+
const kept = contextsRemove === undefined
|
|
137
|
+
? result.contexts
|
|
138
|
+
: result.contexts.filter((entry) => !contextsRemove.has(entry?.name))
|
|
139
|
+
if (kept.length !== result.contexts.length || contextsAdd?.length > 0) {
|
|
140
|
+
result = { ...result, contexts: contextsAdd === undefined ? kept : [...kept, ...contextsAdd] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result !== assembled && take(context) ? result : assembled
|
|
145
|
+
} catch (error) {
|
|
146
|
+
warnOnce(`${plugin}: assembly action ${labelOf(action)} failed, exposing the full assembly: ${String(error?.message ?? error)}`)
|
|
147
|
+
return assembled
|
|
148
|
+
}
|
|
149
|
+
}))
|
|
150
|
+
}
|