dsh-plugin-prompt-tool 2.0.1 → 3.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.
Files changed (231) hide show
  1. package/README.md +100 -233
  2. package/engine/actions/append-context.mjs +3 -24
  3. package/engine/actions/assembly.mjs +70 -94
  4. package/engine/actions/catalog.mjs +65 -28
  5. package/engine/actions/content.mjs +8 -152
  6. package/engine/actions/examples.mjs +8 -4
  7. package/engine/actions/inbox-prepend.mjs +5 -7
  8. package/engine/actions/index.mjs +17 -9
  9. package/engine/actions/inject-text.mjs +24 -4
  10. package/engine/actions/pre-step-filter.mjs +3 -4
  11. package/engine/actions/registration.mjs +20 -28
  12. package/engine/actions/request-params.mjs +21 -1
  13. package/engine/actions/shared.mjs +23 -15
  14. package/engine/actions/stream-output.mjs +26 -0
  15. package/engine/actions/tool-gate.mjs +31 -0
  16. package/engine/actions/tool-result.mjs +36 -0
  17. package/engine/actions/tool-visibility.mjs +248 -0
  18. package/engine/agent-capability.mjs +67 -0
  19. package/engine/anchor-match.mjs +14 -21
  20. package/engine/audience.d.mts +2 -0
  21. package/engine/audience.mjs +102 -0
  22. package/engine/branch.d.mts +4 -0
  23. package/engine/branch.mjs +62 -19
  24. package/engine/compaction-epoch.d.mts +25 -0
  25. package/engine/compaction-epoch.mjs +27 -4
  26. package/engine/compositions/source/local/filesystem-editor.yml +3 -3
  27. package/engine/compositions/source/local/instruction-hint.yml +4 -7
  28. package/engine/compositions/source/local/module-file-tools.yml +2 -0
  29. package/engine/compositions/source/local/module-import-tools.yml +4 -0
  30. package/engine/compositions/source/local/module-manifest-tools.yml +2 -0
  31. package/engine/compositions/source/local/module-rule-tools.yml +5 -0
  32. package/engine/compositions/source/local/module-variable-tools.yml +5 -0
  33. package/engine/compositions/source/local/present-as.yml +26 -0
  34. package/engine/compositions/source/local/tool-config-engine.yml +2 -3
  35. package/engine/compositions/source/local/tool-search.yml +11 -0
  36. package/engine/condition-facts.mjs +84 -0
  37. package/engine/conditions/anchor.mjs +63 -23
  38. package/engine/conditions/availability.mjs +1 -1
  39. package/engine/conditions/composite.mjs +4 -4
  40. package/engine/conditions/count.mjs +43 -13
  41. package/engine/conditions/evaluation.mjs +74 -19
  42. package/engine/conditions/examples.mjs +2 -1
  43. package/engine/conditions/index.d.mts +15 -0
  44. package/engine/conditions/index.mjs +23 -9
  45. package/engine/conditions/operators.mjs +4 -0
  46. package/engine/conditions/phase.mjs +10 -5
  47. package/engine/conditions/preset.mjs +1 -1
  48. package/engine/conditions/session.mjs +8 -2
  49. package/engine/conditions/subject.mjs +144 -65
  50. package/engine/conditions/text.mjs +26 -8
  51. package/engine/conditions/variable.mjs +83 -0
  52. package/engine/context-activation.d.mts +11 -0
  53. package/engine/{st-world-book.mjs → context-activation.mjs} +104 -64
  54. package/engine/delivery.d.mts +2 -0
  55. package/engine/delivery.mjs +93 -0
  56. package/engine/discovery.d.mts +38 -0
  57. package/engine/discovery.mjs +146 -0
  58. package/engine/env-safety.mjs +14 -0
  59. package/engine/executor.d.mts +28 -0
  60. package/engine/executor.mjs +290 -205
  61. package/engine/fillers.mjs +39 -8
  62. package/engine/fixed-registration.mjs +12 -0
  63. package/engine/history.mjs +71 -0
  64. package/engine/instruction-blocks.d.mts +13 -0
  65. package/engine/instruction-blocks.mjs +21 -0
  66. package/engine/instruction-files.d.mts +5 -0
  67. package/engine/instruction-files.mjs +97 -0
  68. package/engine/instruction-hint.mjs +69 -103
  69. package/engine/interpolate.d.mts +7 -0
  70. package/engine/interpolate.mjs +44 -13
  71. package/engine/layers.mjs +165 -230
  72. package/engine/machine-paths.mjs +40 -0
  73. package/engine/module-file-tools.mjs +8 -0
  74. package/engine/module-import-tools.mjs +10 -0
  75. package/engine/module-manifest-tools.mjs +8 -0
  76. package/engine/module-rule-tools.mjs +10 -0
  77. package/engine/module-variable-tools.mjs +10 -0
  78. package/engine/order.d.mts +14 -0
  79. package/engine/order.mjs +0 -3
  80. package/engine/path-safety.d.mts +2 -0
  81. package/engine/path-safety.mjs +58 -0
  82. package/engine/pre-step-source.d.mts +69 -0
  83. package/engine/pre-step-source.mjs +103 -0
  84. package/engine/present-as.mjs +77 -0
  85. package/engine/promotion-pool.d.mts +9 -0
  86. package/engine/promotion-pool.mjs +69 -0
  87. package/engine/record.mjs +2 -0
  88. package/engine/rule-engine.mjs +3 -2
  89. package/engine/rule-facts.d.mts +11 -0
  90. package/engine/rule-facts.mjs +60 -0
  91. package/engine/rule-runtime.d.mts +46 -0
  92. package/engine/rule-runtime.mjs +98 -13
  93. package/engine/rule-spec.d.mts +106 -0
  94. package/engine/rule-spec.mjs +302 -28
  95. package/engine/run-code-env.mjs +73 -19
  96. package/engine/schema.mjs +165 -243
  97. package/engine/sdk-strip.mjs +2 -2
  98. package/engine/service-names.d.mts +10 -0
  99. package/engine/service-names.mjs +10 -0
  100. package/engine/shared.d.mts +21 -0
  101. package/engine/shared.mjs +47 -26
  102. package/engine/skill-search.mjs +125 -53
  103. package/engine/source-kind.d.mts +9 -0
  104. package/engine/source-kind.mjs +16 -0
  105. package/engine/strategy-ids.mjs +5 -0
  106. package/engine/template-evaluation.d.mts +16 -0
  107. package/engine/template-evaluation.mjs +178 -0
  108. package/engine/template-frame.d.mts +16 -0
  109. package/engine/template-frame.mjs +69 -0
  110. package/engine/tool-config-engine.mjs +39 -35
  111. package/engine/tool-definition.d.mts +4 -0
  112. package/engine/tool-definition.mjs +5 -1
  113. package/engine/tool-git-bash.mjs +5 -1
  114. package/engine/tool-scope.mjs +22 -0
  115. package/engine/{dev-tool-search.mjs → tool-search.mjs} +63 -65
  116. package/engine/trigger.mjs +15 -270
  117. package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  118. package/engine/vendor/yaml/dist/nodes/Alias.js +25 -23
  119. package/engine/vendor/yaml/package.json +1 -1
  120. package/lib/client.js +8312 -6724
  121. package/lib/client.js.map +1 -1
  122. package/lib/index.d.mts +37 -762
  123. package/lib/index.mjs +26919 -5791
  124. package/modules/ponytail/module.yml +83 -203
  125. package/modules/ptc-mode/module.yml +45 -0
  126. package/modules/soul/assets/core-hard.md +6 -0
  127. package/modules/soul/assets/criteria.md +12 -0
  128. package/modules/soul/assets/echo.md +12 -0
  129. package/modules/soul/assets/layers.md +11 -0
  130. package/modules/soul/assets/ledger.md +9 -0
  131. package/modules/soul/assets/manifest.yml +20 -0
  132. package/modules/soul/assets/reference/00-layers.md +27 -0
  133. package/modules/soul/assets/reference/01-criteria.md +32 -0
  134. package/modules/soul/assets/reference/02-echo.md +158 -0
  135. package/modules/soul/assets/reference/03-vital.md +183 -0
  136. package/modules/soul/assets/reference/04-manifest.json +106 -0
  137. package/modules/soul/assets/reference/05-doctor.md +67 -0
  138. package/modules/soul/assets/reference/06-reference.md +189 -0
  139. package/modules/soul/assets/reference/07-bundle.json +431 -0
  140. package/modules/soul/assets/reference/08-/350/270/251/345/235/221/345/256/236/345/275/225.md +59 -0
  141. package/modules/soul/assets/reference/09-dev-workflow.md +109 -0
  142. package/modules/soul/assets/reference/10-echo-anatomy.md +100 -0
  143. package/modules/soul/assets/reference/11-self.md +80 -0
  144. package/modules/soul/assets/reference/12-ledger.md +159 -0
  145. package/modules/soul/assets/reference/README.md +10 -0
  146. package/modules/soul/assets/templates/agents-template.md +6 -0
  147. package/modules/soul/assets/vital.md +9 -0
  148. package/modules/soul/module.yml +311 -0
  149. package/modules/superpowers/module.yml +122 -0
  150. package/modules/tool-surface/module.yml +51 -47
  151. package/package.json +40 -41
  152. package/skills/dsh-module/SKILL.md +119 -0
  153. package/skills/dsh-module/reference.md +185 -0
  154. package/templates/10-pre-step.yml +2 -3
  155. package/templates/14-first-turn-anchor.yml +10 -14
  156. package/templates/15-guide-auto.yml +32 -28
  157. package/templates/16-custom-fallback.yml +33 -21
  158. package/templates/20-system-section.yml +1 -4
  159. package/templates/50-llm-stream.yml +4 -10
  160. package/templates/60-tool-pipeline.yml +12 -10
  161. package/templates/63-append-context.yml +20 -0
  162. package/templates/64-pre-step-filter.yml +23 -0
  163. package/templates/65-turn-stop.yml +2 -2
  164. package/templates/67-inbox-prepend.yml +22 -0
  165. package/templates/70-subagent-maintenance.yml +9 -10
  166. package/templates/75-subagent-end.yml +2 -2
  167. package/templates/80-tool-surface.yml +41 -57
  168. package/templates/tools/40-delegate-module-import.yml +22 -0
  169. package/templates/tools/50-delegate-module-rules.yml +14 -0
  170. package/templates/tools/60-delegate-module-variables.yml +17 -0
  171. package/templates/tools/70-delegate-module-rules-edit.yml +31 -0
  172. package/engine/actions/decision.mjs +0 -54
  173. package/engine/actions/guard.mjs +0 -80
  174. package/engine/actions/sdk-strip.mjs +0 -30
  175. package/engine/character-tools.mjs +0 -8
  176. package/engine/classify-task.mjs +0 -53
  177. package/engine/compositions/library/agent-instructions.yml +0 -9
  178. package/engine/compositions/library/command-goal.yml +0 -7
  179. package/engine/compositions/library/compaction.yml +0 -22
  180. package/engine/compositions/library/delegation-ptc.yml +0 -58
  181. package/engine/compositions/library/delegation.yml +0 -56
  182. package/engine/compositions/library/persistent-shell.yml +0 -50
  183. package/engine/compositions/library/planning.yml +0 -26
  184. package/engine/compositions/library/skill-filesystem-cordis.yml +0 -10
  185. package/engine/compositions/library/skill-filesystem.yml +0 -7
  186. package/engine/compositions/library/tool-ask-user.yml +0 -7
  187. package/engine/compositions/library/tool-bash.yml +0 -8
  188. package/engine/compositions/library/tool-cordis.yml +0 -7
  189. package/engine/compositions/library/tool-fs-search.yml +0 -9
  190. package/engine/compositions/library/tool-fs.yml +0 -7
  191. package/engine/compositions/library/tool-goal.yml +0 -7
  192. package/engine/compositions/library/tool-jobs.yml +0 -7
  193. package/engine/compositions/library/tool-plugin-manager-disabled.yml +0 -8
  194. package/engine/compositions/library/tool-plugin-manager.yml +0 -8
  195. package/engine/compositions/library/tool-present.yml +0 -7
  196. package/engine/compositions/library/tool-presentation.yml +0 -9
  197. package/engine/compositions/library/tool-pwsh.yml +0 -8
  198. package/engine/compositions/library/tool-skill.yml +0 -7
  199. package/engine/compositions/library/tool-todo.yml +0 -9
  200. package/engine/compositions/library/tool-web.yml +0 -10
  201. package/engine/compositions/source/local/character-tools.yml +0 -5
  202. package/engine/compositions/source/local/declared-triggers.yml +0 -12
  203. package/engine/compositions/source/local/dev-tool-search.yml +0 -11
  204. package/engine/compositions/source/local/session-var-tools.yml +0 -5
  205. package/engine/compositions/source/local/subagent-tool-policy.yml +0 -21
  206. package/engine/compositions/source/local/world-book-tools.yml +0 -5
  207. package/engine/condition.mjs +0 -96
  208. package/engine/declared-triggers.mjs +0 -93
  209. package/engine/session-var-tools.mjs +0 -8
  210. package/engine/session-vars.mjs +0 -54
  211. package/engine/st-macros.d.mts +0 -22
  212. package/engine/st-macros.mjs +0 -208
  213. package/engine/st-render.mjs +0 -109
  214. package/engine/st-world-book.d.mts +0 -10
  215. package/engine/subagent-tool-policy-core.mjs +0 -400
  216. package/engine/subagent-tool-policy.mjs +0 -352
  217. package/engine/trigger-spec.mjs +0 -173
  218. package/engine/world-book-tools.mjs +0 -8
  219. package/lib/migrate-rules.d.mts +0 -1
  220. package/lib/migrate-rules.mjs +0 -39
  221. package/lib/module-package-CHPFweqK.mjs +0 -2
  222. package/lib/module-package-Cnd1_mwx.mjs +0 -2872
  223. package/lib/paths-CyP0IkaM.d.mts +0 -1390
  224. package/lib/preset-transfer.d.mts +0 -2
  225. package/lib/preset-transfer.mjs +0 -3
  226. package/lib/rules-migration-DJ6vASB5.mjs +0 -15334
  227. package/templates/policies/legacy-defaults.yml +0 -34
  228. package/templates/tools/40-delegate-character.yml +0 -15
  229. package/templates/tools/50-delegate-worldbook.yml +0 -15
  230. package/templates/tools/60-delegate-session-var.yml +0 -15
  231. package/templates/tools/70-delegate-worldbook-upsert.yml +0 -36
package/engine/schema.mjs CHANGED
@@ -3,14 +3,15 @@
3
3
  * 只负责"配置长什么样";执行语义见 executor.mjs,内容策略见 strategies.mjs。
4
4
  */
5
5
 
6
- import { readFileSync, readdirSync } from 'node:fs'
6
+ import { readFileSync } from 'node:fs'
7
7
  import { fileURLToPath } from 'node:url'
8
8
  import { sep } from 'node:path'
9
9
  import { parse as parseYaml } from './vendor/yaml/index.js'
10
10
  import { bindResolver } from './strategies.mjs'
11
- import { attachStRenderers } from './st-render.mjs'
12
- import { FILE_SEQUENCE, compareConfigSequence } from './order.mjs'
13
- import { MATCH_LOGIC, createAnchorMatcher } from './anchor-match.mjs'
11
+ import { compareConfigSequence } from './order.mjs'
12
+ import { SUBJECT_FIELDS } from './condition-facts.mjs'
13
+ import { pluginKind, pluginOwner } from './source-kind.mjs'
14
+ import { CONTEXT_ACTIVATION_STRATEGY } from './strategy-ids.mjs'
14
15
 
15
16
  const name = 'rule-engine'
16
17
 
@@ -70,83 +71,7 @@ function loadTemplate(file, baseUrl = import.meta.url, presetRootUrl) {
70
71
  return { text: raw }
71
72
  }
72
73
 
73
- /**
74
- * 完整 YAML 解析(vendored yaml 包):提示词配置文件直接使用标准 YAML。
75
- * 支持缩进 map、列表、block scalar、行尾注释、引号转义等全部语法。
76
- */
77
- export function parsePromptConfigYaml(raw) {
78
- const parsed = parseYaml(raw)
79
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
80
- throw new TypeError(`${name}: prompt config yaml must contain a single object`)
81
- }
82
- return parsed
83
- }
84
-
85
- /**
86
- * 枚举提示词配置候选文件:**两条加载路径共用同一份枚举规则**。
87
- *
88
- * 扩展名、排序与跳过名单必须两侧一致——否则 host(编辑/列举)与引擎(注入)会看到不同的
89
- * 文件集:host 让人编辑 A 文件、引擎却加载 B 文件。`variables.yml` 是模板变量源而非配置,
90
- * 两侧都跳过(引擎另行读取它做合并,见 `loadPromptConfigFiles`)。
91
- *
92
- * 只共享**枚举**这一层:解析、结构校验、错误类型与包装都留在各自边界——两边的入参类型
93
- * (URL vs 字符串路径)、空路径短路与异常消息本来就不同,强行合并会改变其中一侧的行为。
94
- *
95
- * @param entries `readdirSync(dir, { withFileTypes: true })` 的结果
96
- * @returns 按名排序的文件名(含扩展名),已剔除 `variables.yml`
97
- */
98
- export function promptConfigFileNames(entries) {
99
- return entries
100
- .filter((entry) => entry.isFile() && /\.(ya?ml|json)$/i.test(entry.name))
101
- .sort((a, b) => a.name.localeCompare(b.name))
102
- .map((entry) => entry.name)
103
- .filter((fileName) => fileName !== 'variables.yml')
104
- }
105
-
106
- /**
107
- * 从提示词配置模块目录加载全部提示词配置描述:按文件名排序扫描 *.yml / *.yaml / *.json。
108
- * 文件名用数字前缀表达引擎执行顺序(00-…、10-…)。
109
- */
110
- export function loadPromptConfigFiles(dirUrl) {
111
- let entries
112
- try {
113
- entries = readdirSync(dirUrl, { withFileTypes: true })
114
- } catch (error) {
115
- throw new TypeError(`${name}: configsDir ${String(dirUrl)} is not readable: ${String(error?.message ?? error)}`)
116
- }
117
- // 预设级模板变量(writePreset 生成 variables.yml):读入后合并进每条配置
118
- // variables(配置自身优先);variables.yml 本身不当作配置解析。缺失或损坏
119
- // 时为空变量源,不阻断加载。
120
- let presetVariables = {}
121
- try {
122
- const parsed = parseYaml(readFileSync(new URL('variables.yml', dirUrl), 'utf8'))
123
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) presetVariables = parsed
124
- } catch {
125
- // 无 variables.yml(旧产物/手写目录)或解析失败:保持空变量源。
126
- }
127
- const specs = []
128
- for (const fileName of promptConfigFileNames(entries)) {
129
- const raw = readFileSync(new URL(fileName, dirUrl), 'utf8')
130
- const spec = /\.json$/i.test(fileName) ? JSON.parse(raw) : parsePromptConfigYaml(raw)
131
- const prefix = /^(\d+)-/.exec(fileName)
132
- if (prefix !== null && spec !== null && typeof spec === 'object' && !Array.isArray(spec)) {
133
- const sequence = Number(prefix[1])
134
- if (!Number.isSafeInteger(sequence)) throw new TypeError(`${name}: unsafe config sequence in ${fileName}`)
135
- Object.defineProperty(spec, FILE_SEQUENCE, { value: sequence })
136
- }
137
- specs.push(spec)
138
- }
139
- for (const spec of specs) {
140
- if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) continue
141
- const own = spec.variables !== null && typeof spec.variables === 'object' && !Array.isArray(spec.variables)
142
- ? spec.variables
143
- : {}
144
- spec.variables = { ...presetVariables, ...own }
145
- }
146
- return specs
147
- }
148
-
149
- export const KNOWN_STRATEGIES = new Set(['static', 'placeholder', 'first-turn-anchor', 'guide-auto', 'anchor-notice', 'world-book'])
74
+ export const KNOWN_STRATEGIES = new Set(['static', 'placeholder', CONTEXT_ACTIVATION_STRATEGY])
150
75
  /**
151
76
  * 策略 × 层支持矩阵:`config.resolve` 只在 pre-step(executor)与 runtime-context 的
152
77
  * placeholder(layers)被消费,其他层声明非 static 策略会绑定 resolver 却无人调用,
@@ -156,123 +81,111 @@ export const KNOWN_STRATEGIES = new Set(['static', 'placeholder', 'first-turn-an
156
81
  export const STRATEGY_LAYER_SUPPORT = {
157
82
  static: null,
158
83
  placeholder: ['pre-step', 'runtime-context'],
159
- 'first-turn-anchor': ['pre-step'],
160
- 'guide-auto': ['pre-step'],
161
- 'anchor-notice': ['pre-step'],
162
- 'world-book': ['pre-step'],
84
+ [CONTEXT_ACTIVATION_STRATEGY]: ['pre-step'],
163
85
  }
164
86
  /** 模板专属策略允许的层:resolver 同样只在 pre-step / runtime-context 被调用。 */
165
87
  const TEMPLATE_STRATEGY_LAYERS = ['pre-step', 'runtime-context']
166
88
  export const KNOWN_SLOT_KINDS = new Set(['ordered', 'anchor'])
167
89
  /**
168
90
  * 九个官方注入层的**唯一权威定义**(B5 T1:原 `LAYER_ORDER` / `LAYER_FIELD_POLICIES` /
169
- * `LAYER_EDITING` / `LAYER_LABELS` / `LAYER_DEFAULT_SUBJECT` 五处事实合一)。
91
+ * `LAYER_EDITING` / `LAYER_LABELS` 四处事实合一)。
170
92
  *
171
93
  * 新增或改一层只动这里一处:
172
94
  * - 数组顺序即 `layerOrder`(**产品定义**,改顺序等于改 UI 组织,不要只为排序调整);
173
- * - `fields` 是层能力矩阵(11 项,客户端表单据此动态渲染);
174
- * - `editing` 是字段能力表(subjects / content / variables / messageMetadata / params);
175
- * - `label` 是由引擎统一下发给客户端的显示名与说明;
176
- * - `defaultSubject` 是该条件层的缺省匹配对象(不声明 = 该层没有匹配对象)。
95
+ * - `channel` / `phase` 是该层 `inject-text` 的**唯一注册点**(缺 `channel` 的层不可注入:
96
+ * `tool-pipeline` 的工具链裁决改用 `tool-gate` / `append-context` 动作);
97
+ * - `fields` 是层能力矩阵(9 项,客户端表单据此动态渲染);
98
+ * - `editing` 是字段能力表(content / variables / messageMetadata / params);
99
+ * - `label` 是由引擎统一下发给客户端的显示名与说明。
177
100
  *
178
101
  * 下面所有导出都由本表**派生**,并保持既有名字与形状,外部消费方不受影响。
179
- * `defaultSubject` 必须落在本层 `editing.subjects` 内——就近声明 + 加载期断言,
180
- * 避免「缺省值不在允许集合里」这种只能靠运行期才发现的错配。
181
102
  */
182
103
  const LAYER_DEFINITIONS = [
183
104
  {
184
105
  layer: 'pre-step',
185
- fields: { position: true, dedupe: true, promotion: true, audience: true, modelScope: true, merge: true, order: true, role: true, placeholder: true, subject: true, match: true },
186
- editing: { subjects: ['userMessage'], content: 'text', variables: true, messageMetadata: true, params: {} },
106
+ channel: 'agent/pre-step', phase: 'after-next',
107
+ fields: { position: true, dedupe: true, promotion: true, audience: true, modelScope: true, merge: true, order: true, role: true, placeholder: true },
108
+ editing: { content: 'text', variables: true, messageMetadata: true, params: {} },
187
109
  label: { title: '消息批层', detail: '官方默认层:agent/pre-step 消息批。支持 position / dedupe / promotion / audience / mergeMode 与文本插值。' },
188
- defaultSubject: 'userMessage',
189
110
  },
190
111
  {
191
112
  layer: 'system-section',
192
- fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: false, merge: true, order: true, role: false, placeholder: false, subject: false, match: false },
193
- editing: { subjects: [], content: 'text', variables: true, messageMetadata: false,
113
+ channel: 'system-prompt/assemble', phase: 'before-next',
114
+ fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: false, merge: true, order: true, role: false, placeholder: false },
115
+ editing: { content: 'text', variables: true, messageMetadata: false,
194
116
  params: { sectionName: { type: 'string' }, complete: { type: 'boolean' }, suppressRuntimeContext: { type: 'boolean' } } },
195
117
  label: { title: '系统段层', detail: 'system-section 静态层:注册即全局,由 order 与 params.complete / sectionName 控制。' },
196
118
  },
197
119
  {
198
120
  layer: 'runtime-context',
199
- fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: false, merge: true, order: true, role: false, placeholder: true, subject: false, match: false },
200
- editing: { subjects: [], content: 'text', variables: true, messageMetadata: false, params: { contextName: { type: 'string' } } },
121
+ channel: 'system-prompt/assemble', phase: 'before-next',
122
+ fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: false, merge: true, order: true, role: false, placeholder: true },
123
+ editing: { content: 'text', variables: true, messageMetadata: false, params: { contextName: { type: 'string' } } },
201
124
  label: { title: '运行上下文', detail: 'runtime-context 层:static 按 order 注册,placeholder 单条生效,由 params.contextName 控制。' },
202
125
  },
203
126
  {
204
127
  layer: 'agent-request',
205
- fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: false, match: false },
206
- editing: { subjects: [], content: 'request', variables: false, messageMetadata: false,
207
- params: { patch: { type: 'object' }, replace: { type: 'boolean' } } },
208
- label: { title: '调用配置层', detail: 'agent-request 层:按 order 注册,params.patch 改写请求配置。' },
128
+ fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false },
129
+ editing: { content: 'request', variables: false, messageMetadata: false, params: {} },
130
+ label: { title: '调用配置层', detail: 'agent/request 的请求参数由 request-params 动作修改。' },
209
131
  },
210
132
  {
211
133
  layer: 'llm-stream',
212
- fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: false, match: false },
213
- editing: { subjects: [], content: 'stream', variables: false, messageMetadata: false,
214
- params: { mode: { type: 'enum', values: ['pass', 'replace'] } } },
215
- label: { title: '模型流层', detail: 'llm/stream 层:按 order 注册,params.mode = pass | replace。' },
134
+ fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
135
+ editing: { content: 'stream', variables: false, messageMetadata: false, params: {} },
136
+ label: { title: '模型流层', detail: 'llm/stream 的透传与替换由 stream-output 动作处理。' },
216
137
  },
217
138
  {
218
139
  layer: 'tool-pipeline',
219
- fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: true, match: true },
220
- editing: { subjects: ['toolArgs', 'toolResult'], content: 'tool-result', variables: false, messageMetadata: false,
221
- params: { toolNames: { type: 'string' }, preDecision: { type: 'enum', values: ['allow', 'deny', 'ask'] },
222
- denyReason: { type: 'string' }, postAction: { type: 'enum', values: ['accept', 'replace', 'block'] } } },
223
- label: { title: '工具管线层', detail: 'tools/* 层:按 order 注册,params.toolNames 与 preDecision / postAction 控制;subject / match 可选,命中才裁决。' },
224
- defaultSubject: 'toolArgs',
140
+ fields: { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false },
141
+ editing: { content: 'tool-result', variables: false, messageMetadata: false,
142
+ // 该层没有注入通道:旧的四项工具链参数(toolNames / preDecision / denyReason / postAction)
143
+ // 没有任何消费者,已删除;工具链行为由 tool-gate / tool-result 动作承担。
144
+ params: {} },
145
+ label: { title: '工具管线层', detail: '工具链层:只作规则级展示归属(无 inject-text 通道)——工具链的裁决与追加上下文由 tool-gate / tool-result / append-context 动作承担。' },
225
146
  },
226
147
  {
227
148
  layer: 'turn-stop',
228
- fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: true, match: true },
229
- editing: { subjects: ['assistantText'], content: 'text', variables: true, messageMetadata: false, params: {} },
149
+ channel: 'agent/turn-stopping', phase: 'before-next',
150
+ fields: { position: false, dedupe: true, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
151
+ editing: { content: 'text', variables: true, messageMetadata: false, params: {} },
230
152
  label: { title: '轮次停止层', detail: 'agent/turn-stopping 层:命中条件时强制续跑一步;引擎内置续跑上限,不可用配置关闭。' },
231
- defaultSubject: 'assistantText',
232
153
  },
233
154
  {
234
155
  layer: 'subagent-start',
235
- fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: true, match: true },
236
- editing: { subjects: ['subagentInfo'], content: 'text', variables: true, messageMetadata: false, params: {} },
156
+ channel: 'subagent/start', phase: 'before-next',
157
+ fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
158
+ editing: { content: 'text', variables: true, messageMetadata: false, params: {} },
237
159
  label: { title: '子代理启动层', detail: 'subagent/start 层:命中条件时向该子代理注入上下文。' },
238
- defaultSubject: 'subagentInfo',
239
160
  },
240
161
  {
241
162
  layer: 'subagent-end',
242
- fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false, subject: true, match: true },
243
- editing: { subjects: ['subagentInfo'], content: 'subagent-result', variables: true, messageMetadata: false,
163
+ channel: 'subagent/end', phase: 'before-next',
164
+ fields: { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
165
+ editing: { content: 'subagent-result', variables: true, messageMetadata: false,
244
166
  params: { action: { type: 'enum', values: ['observe', 'inject-main'] } } },
245
167
  label: { title: '子代理结束层', detail: 'subagent/end 层:默认记录;action=inject-main 时向所属主会话注入配置文本,不改写子代理返回结果。' },
246
- defaultSubject: 'subagentInfo',
247
168
  },
248
169
  ]
249
170
 
250
- /** 就近断言:缺省匹配对象必须在本层允许的 subjects 内(新增层时最先撞到这里)。 */
251
- for (const definition of LAYER_DEFINITIONS) {
252
- if (definition.defaultSubject === undefined) continue
253
- if (!definition.editing.subjects.includes(definition.defaultSubject)) {
254
- throw new TypeError(
255
- `schema: layer ${definition.layer} defaultSubject ${definition.defaultSubject} must be one of its subjects`,
256
- )
257
- }
258
- }
259
-
260
171
  /** 九个官方注入层的固定顺序:/meta 的 layerOrder、UI 层序与模板菜单共用这一份。 */
261
172
  export const LAYER_ORDER = LAYER_DEFINITIONS.map((definition) => definition.layer)
262
173
  export const KNOWN_LAYERS = new Set(LAYER_ORDER)
263
174
  /**
264
- * 条件判定的匹配对象:决定把哪段文本交给 anchor-match 匹配器。
265
- * 取值与 DSH 扩展点一一对应,各层缺省值见 LAYER_DEFAULT_SUBJECT。
175
+ * `inject-text` 可绑定的层与注册点(由 {@link LAYER_DEFINITIONS} 的 `channel` 派生,不另写层表):
176
+ * 没有通道的 `tool-pipeline` 不可注入——工具链的裁决与结果处理走 `tool-gate` / `tool-result` / `append-context` 动作。
177
+ * `LAYER_ORDER` 九层供展示;六个注入层供 `config.layer`,其余由独立动作承担。
266
178
  */
267
- export const KNOWN_SUBJECTS = new Set(['toolArgs', 'toolResult', 'userMessage', 'assistantText', 'subagentInfo'])
268
- /** 各条件层的缺省匹配对象;不在此表的层没有匹配对象。 */
269
- export const LAYER_DEFAULT_SUBJECT = Object.fromEntries(
270
- LAYER_DEFINITIONS
271
- .filter((definition) => definition.defaultSubject !== undefined)
272
- .map((definition) => [definition.layer, definition.defaultSubject]),
179
+ const INJECTION_DEFINITIONS = LAYER_DEFINITIONS.filter((definition) => definition.channel !== undefined)
180
+ export const INJECTION_LAYERS = INJECTION_DEFINITIONS.map((definition) => definition.layer)
181
+ export const INJECTION_CHANNELS = Object.fromEntries(
182
+ INJECTION_DEFINITIONS.map((definition) => [definition.layer, { channel: definition.channel, phase: definition.phase }]),
273
183
  )
274
- /** 支持 subject / match 的层;其余层声明这两个字段即 fail loud(避免误以为是门)。 */
275
- export const CONDITIONAL_LAYERS = new Set(Object.keys(LAYER_DEFAULT_SUBJECT))
184
+ /**
185
+ * live 规则条件的匹配对象(if.text.subject / if.anchor.subject):
186
+ * 由 condition-facts 的 SUBJECT_FIELDS 派生,单一来源;不再用于旧 config.subject。
187
+ */
188
+ export const KNOWN_SUBJECTS = new Set(Object.keys(SUBJECT_FIELDS))
276
189
  export const KNOWN_POSITIONS = new Set(['after-user', 'before-all', 'after-all'])
277
190
  export const KNOWN_DEDUPES = new Set(['session', 'batch', 'none'])
278
191
  export const KNOWN_PROMOTIONS = new Set(['none', 'main', 'include-subagents'])
@@ -285,7 +198,7 @@ export const KNOWN_MODEL_SCOPES = new Set(['all', 'pro', 'flash'])
285
198
  * 自定义策略或模板 patch 的角色仍由 executor 在出口校验,避免写出非法事件。
286
199
  */
287
200
  export const KNOWN_ROLES = new Set(['user'])
288
- export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-catalog'])
201
+ export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-catalog', 'tool-catalog'])
289
202
 
290
203
  /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
291
204
  export const LAYER_FIELD_POLICIES = Object.fromEntries(
@@ -310,9 +223,11 @@ export const LAYER_LABELS = Object.fromEntries(
310
223
  /** 引擎能力矩阵:作为 /meta 的唯一数据源,客户端表单据此动态渲染。 */
311
224
  export function getEngineMeta() {
312
225
  return {
313
- // layerOrder = 固定九层顺序(UI 组织用);layers = 合法层集合(校验与旧消费方用)。
226
+ // layerOrder = 固定九层顺序(UI 组织用);layers = 合法层集合(校验与旧消费方用);
227
+ // injectionLayers 只含实际文本注入层;请求、流与工具管线使用独立动作。
314
228
  layerOrder: [...LAYER_ORDER],
315
229
  layers: [...KNOWN_LAYERS].sort(),
230
+ injectionLayers: [...INJECTION_LAYERS],
316
231
  strategies: [...KNOWN_STRATEGIES].sort(),
317
232
  slotKinds: [...KNOWN_SLOT_KINDS].sort(),
318
233
  positions: [...KNOWN_POSITIONS].sort(),
@@ -324,7 +239,6 @@ export function getEngineMeta() {
324
239
  mergeModes: [...KNOWN_MERGE_MODES].sort(),
325
240
  fills: [...KNOWN_FILLS].sort(),
326
241
  subjects: [...KNOWN_SUBJECTS].sort(),
327
- layerDefaultSubjects: { ...LAYER_DEFAULT_SUBJECT },
328
242
  layerFieldPolicies: LAYER_FIELD_POLICIES,
329
243
  layerLabels: LAYER_LABELS,
330
244
  layerContracts: structuredClone(LAYER_CONTRACTS),
@@ -332,62 +246,47 @@ export function getEngineMeta() {
332
246
  }
333
247
 
334
248
  /**
335
- * 归一化并预编译条件判定的 match 段:键集合直接交给 anchor-match 的匹配器,
336
- * 非法 logic、非法正则与空键集合都在挂载期 fail loud——运行时静默不命中会让
337
- * "配了门却没拦住"变成不可见的失效。
338
- * @returns 归一化后的匹配参数;未声明 match 时返回 undefined(= 无条件)。
249
+ * 官方 `LlmCallConfig` 允许被改写的键:不能把消息、工具或 system 塞进请求配置。
250
+ * `request-params` 动作的 `patch`/`unset` 共用这一份。
251
+ *
252
+ * 核对到 `@deepseek-ai/dsh-llm@0.2.1-alpha.2`(上游 `packages/llm/llm/src/call-config.ts`):
253
+ * `provider`/`model` 属 `LlmCallConfig`,其余四个自 0.2.1 起拆在 `LlmCallControls`,字段集未变。
254
+ * 上游同处留了 `TODO(call-config-shape)`(讨论 epoch 级字段与 provider 专属选项的归属),
255
+ * 所以这张表将来可能扩——它漂了本项目不会变红,升级 `dsh-llm` 时要手动再对一次。
339
256
  */
340
- function normalizeMatch(raw, label) {
341
- if (raw === undefined || raw === null) return undefined
342
- if (typeof raw !== 'object' || Array.isArray(raw)) {
343
- throw new TypeError(`${label}.match must be an object when present`)
344
- }
345
- const stringList = (value, field) => {
346
- if (value === undefined || value === null) return []
347
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
348
- throw new TypeError(`${label}.match.${field} must be an array of strings when present`)
349
- }
350
- return value.map((item) => item.trim()).filter((item) => item.length > 0)
351
- }
352
- const keys = stringList(raw.keys, 'keys')
353
- const secondaryKeys = stringList(raw.secondaryKeys, 'secondaryKeys')
354
- if (keys.length === 0 && secondaryKeys.length === 0) {
355
- throw new TypeError(`${label}.match needs at least one non-empty key`)
356
- }
357
- const logic = raw.logic ?? MATCH_LOGIC.ANY
358
- if (!Object.values(MATCH_LOGIC).includes(logic)) {
359
- throw new TypeError(`${label}.match.logic must be one of ${Object.values(MATCH_LOGIC).join(', ')}`)
360
- }
361
- for (const field of ['caseSensitive', 'wholeWords', 'useRegex']) {
362
- if (raw[field] !== undefined && typeof raw[field] !== 'boolean') {
363
- throw new TypeError(`${label}.match.${field} must be a boolean when present`)
364
- }
365
- }
366
- const match = {
367
- keys,
368
- secondaryKeys,
369
- logic,
370
- caseSensitive: raw.caseSensitive === true,
371
- wholeWords: raw.wholeWords === true,
372
- useRegex: raw.useRegex,
257
+ const LLM_CALL_FIELDS = new Set(['provider', 'model', 'reasoningEffort', 'temperature', 'maxTokens', 'stop'])
258
+
259
+ /**
260
+ * 校验请求配置补丁的子键与值;拼错的键与非法值在保存/挂载前拒绝。
261
+ * @param label 已拼好的报错前缀(`<config label>.params.patch` 或 `action <id>.patch`)
262
+ */
263
+ export function assertLlmCallPatch(patch, label) {
264
+ if (patch === null || typeof patch !== 'object' || Array.isArray(patch)) throw new TypeError(`${label} must be an object`)
265
+ for (const [key, value] of Object.entries(patch)) {
266
+ if (!LLM_CALL_FIELDS.has(key)) throw new TypeError(`${label}.${key} is not a LlmCallConfig field`)
267
+ const valid = key === 'temperature' ? typeof value === 'number' && Number.isFinite(value)
268
+ : key === 'maxTokens' ? Number.isSafeInteger(value) && value > 0
269
+ : key === 'stop' ? Array.isArray(value) && value.every(item => typeof item === 'string')
270
+ : typeof value === 'string' && value.trim().length > 0
271
+ if (!valid) throw new TypeError(`${label}.${key} has an invalid value`)
373
272
  }
374
- // 预编译:useRegex、/pattern/flags 形态与整词包装里的正则错误在此暴露。
375
- try {
376
- createAnchorMatcher(match)
377
- } catch (error) {
378
- throw new TypeError(`${label}.match is not compilable: ${error instanceof Error ? error.message : String(error)}`)
273
+ }
274
+
275
+ /**
276
+ * `replace=true` 的不变量:整体替换会丢掉下游 provider/model,缺一即不可用。
277
+ * 由 request-params 动作消费;报错前缀由 label 承载。
278
+ * @param label 已拼好的报错前缀(`<config label>.params.patch` 或 `action <id>.patch`)
279
+ */
280
+ export function assertReplacePatch(patch, replace, label) {
281
+ if (replace === true && (!patch?.provider || !patch?.model)) {
282
+ throw new TypeError(`${label} requires provider and model when replace=true`)
379
283
  }
380
- return match
381
284
  }
382
285
 
383
286
  function normalizeLayerParams(raw, layer, label) {
384
287
  if (raw === undefined || raw === null) return {}
385
288
  if (typeof raw !== 'object' || Array.isArray(raw)) throw new TypeError(`${label}.params must be an object`)
386
- let params = raw
387
- if (layer === 'tool-pipeline' && Array.isArray(raw.toolNames)) {
388
- if (raw.toolNames.some(item => typeof item !== 'string')) throw new TypeError(`${label}.params.toolNames must contain only strings`)
389
- params = { ...raw, toolNames: raw.toolNames.join(',') }
390
- }
289
+ const params = raw
391
290
  for (const [key, rule] of Object.entries(LAYER_CONTRACTS[layer].params)) {
392
291
  const value = params[key]
393
292
  if (value === undefined) continue
@@ -396,22 +295,6 @@ function normalizeLayerParams(raw, layer, label) {
396
295
  : typeof value === rule.type
397
296
  if (!valid) throw new TypeError(`${label}.params.${key} must be ${rule.type === 'enum' ? rule.values.join(' | ') : rule.type}`)
398
297
  }
399
- if (layer === 'agent-request') {
400
- const patch = params.patch ?? {}
401
- // 官方 0.1.6 LlmCallConfig;不能把消息、工具或 system 塞进请求配置。
402
- const keys = new Set(['provider', 'model', 'reasoningEffort', 'temperature', 'maxTokens', 'stop'])
403
- for (const [key, value] of Object.entries(patch)) {
404
- if (!keys.has(key)) throw new TypeError(`${label}.params.patch.${key} is not a LlmCallConfig field`)
405
- const valid = key === 'temperature' ? typeof value === 'number' && Number.isFinite(value)
406
- : key === 'maxTokens' ? Number.isSafeInteger(value) && value > 0
407
- : key === 'stop' ? Array.isArray(value) && value.every(item => typeof item === 'string')
408
- : typeof value === 'string' && value.trim().length > 0
409
- if (!valid) throw new TypeError(`${label}.params.patch.${key} has an invalid value`)
410
- }
411
- if (params.replace === true && (!patch.provider || !patch.model)) {
412
- throw new TypeError(`${label}.params.patch requires provider and model when replace=true`)
413
- }
414
- }
415
298
  return params
416
299
  }
417
300
 
@@ -430,12 +313,11 @@ const isNonEmptyString = (value) => typeof value === 'string' && value.length >
430
313
  * undefined 不是同一档);
431
314
  * - `optional`:缺失即不设置;一旦存在(**含 null**)就按 `validate` 校验形态。
432
315
  *
433
- * 表里需要额外上下文的项用 `resolve`(subject 有「全局集合 + 层内允许集」两条语义与层缺省
434
- * 回退)。**表顺序 = 报错顺序**:与手写版逐个检查的先后完全一致,同一组非法输入必须先报同一处。
316
+ * 表里需要额外上下文的项用 `resolve`。**表顺序 = 报错顺序**:与手写版逐个检查的先后完全一致,
317
+ * 同一组非法输入必须先报同一处。
435
318
  *
436
- * 不在此表(各自领域的权威,留在原处):`params`(`normalizeLayerParams`)、`match`
437
- * (`normalizeMatch`)、`text`/`texts`(多来源合并)、`strategy`/`configKind`/`layer`
438
- * (各自带额外上下文校验)。
319
+ * 不在此表(各自领域的权威,留在原处):`params`(`normalizeLayerParams`)、
320
+ * `text`/`texts`(多来源合并)、`strategy`/`configKind`/`layer`(各自带额外上下文校验)。
439
321
  */
440
322
  export const CONFIG_FIELDS = [
441
323
  { field: 'position', known: KNOWN_POSITIONS, default: 'after-user' },
@@ -444,19 +326,6 @@ export const CONFIG_FIELDS = [
444
326
  { field: 'audience', known: KNOWN_AUDIENCES, keepNull: true, keepMissing: true },
445
327
  { field: 'modelScope', known: KNOWN_MODEL_SCOPES, default: 'all' },
446
328
  { field: 'role', known: KNOWN_ROLES, default: 'user' },
447
- {
448
- field: 'subject',
449
- resolve: (spec, { label, layer }) => {
450
- // 条件判定:subject 决定把哪段文本交给匹配器(缺省由层决定),match 缺省 = 无条件。
451
- if (spec.subject !== undefined && !KNOWN_SUBJECTS.has(spec.subject)) {
452
- throw new TypeError(`${name}: ${label} unknown subject ${JSON.stringify(spec.subject)} — known subjects: ${[...KNOWN_SUBJECTS].sort().join(', ')}`)
453
- }
454
- if (spec.subject !== undefined && !LAYER_CONTRACTS[layer].subjects.includes(spec.subject)) {
455
- throw new TypeError(`${name}: ${label}.subject ${JSON.stringify(spec.subject)} is unavailable on layer ${JSON.stringify(layer)}`)
456
- }
457
- return spec.subject ?? LAYER_DEFAULT_SUBJECT[layer]
458
- },
459
- },
460
329
  {
461
330
  field: 'identity',
462
331
  resolve: (spec, { label }) => {
@@ -494,6 +363,26 @@ export const CONFIG_FIELDS = [
494
363
  { field: 'mergeMode', known: KNOWN_MERGE_MODES, default: 'separate' },
495
364
  ]
496
365
 
366
+ /**
367
+ * 「本模块停用了模板变量」的引擎内部标记:`rule-spec.mjs#compileRules` 在配置**通过校验之后**打上它,
368
+ * 执行期据此跳过会话变量合并(见 `executor.mjs#runPromptConfigBatch`)。它**不在**
369
+ * {@link INJECT_CONFIG_FIELDS} 里——作者写同名键会按未知键 fail loud,键名只在这里定义一次。
370
+ */
371
+ export const SESSION_VARIABLES_DISABLED = 'variablesDisabled'
372
+
373
+ /**
374
+ * `inject-text.config` 的允许键:{@link CONFIG_FIELDS} 的声明键 + {@link createPromptConfigs}
375
+ * 直读键。规则动作的配置是同一份提示词配置,因此白名单只能从这里派生——另写一份会漂移。
376
+ * `enabled` 不在表里(`group`/`exclusive` 由 CONFIG_FIELDS 带进来):这三个键在
377
+ * `rule-spec.mjs#normalizeActionGates` 一律点名拒绝,它们属于规则层,运行期
378
+ * `rule-runtime.mjs` 会覆盖它们。
379
+ */
380
+ export const INJECT_CONFIG_FIELDS = new Set([
381
+ ...CONFIG_FIELDS.map((rule) => rule.field),
382
+ 'id', 'layer', 'strategy', 'configKind', 'text', 'templateFile', 'fill',
383
+ 'sourceKind', 'form', 'summary', 'params',
384
+ ])
385
+
497
386
  /** 按 {@link CONFIG_FIELDS} 遍历一次,产出全部标量字段的归一值(或按表顺序 fail loud)。 */
498
387
  function resolveConfigFields(spec, label, layer) {
499
388
  const resolved = {}
@@ -526,6 +415,45 @@ function resolveConfigFields(spec, label, layer) {
526
415
  return resolved
527
416
  }
528
417
 
418
+ const ACTIVATION_FIELDS = new Set(['scan', 'scanDepth', 'scanVariables', 'selective', 'probability', 'useProbability', 'selectionGroup', 'groupOverride', 'groupWeight', 'useGroupScoring', 'sticky', 'cooldown', 'delay', 'excludeRecursion', 'preventRecursion', 'delayUntilRecursion'])
419
+ const ACTIVATION_LOGIC = new Set(['AND_ANY', 'NOT_ALL', 'NOT_ANY', 'AND_ALL'])
420
+ function validateActivation(params, strategy, label) {
421
+ for (const key of ['stWorldBook', 'stMacros', 'stSource', 'stWords']) {
422
+ if (Object.hasOwn(params, key)) throw new TypeError(`${label}.params.${key} is retired; reimport or explicitly convert to native context/template fields`)
423
+ }
424
+ if (params.templateEvaluation !== undefined && typeof params.templateEvaluation !== 'boolean') throw new TypeError(`${label}.params.templateEvaluation must be boolean`)
425
+ if (strategy !== CONTEXT_ACTIVATION_STRATEGY) {
426
+ if (params.activation !== undefined) throw new TypeError(`${label}.params.activation requires strategy ${CONTEXT_ACTIVATION_STRATEGY}`)
427
+ return
428
+ }
429
+ const activation = params.activation
430
+ if (activation === null || typeof activation !== 'object' || Array.isArray(activation)) throw new TypeError(`${label}.params.activation must be an object`)
431
+ const invalid = Object.keys(activation).filter(key => !ACTIVATION_FIELDS.has(key))
432
+ if (invalid.length) throw new TypeError(`${label}.params.activation unknown fields: ${invalid.join(', ')}`)
433
+ if (!['batch', 'history'].includes(activation.scan)) throw new TypeError(`${label}.params.activation.scan must be batch or history`)
434
+ for (const key of ['scanDepth', 'sticky', 'cooldown', 'delay']) {
435
+ if (activation[key] !== undefined && (!Number.isSafeInteger(activation[key]) || activation[key] < 0)) throw new TypeError(`${label}.params.activation.${key} must be a non-negative integer`)
436
+ }
437
+ if (activation.delayUntilRecursion !== undefined && typeof activation.delayUntilRecursion !== 'boolean'
438
+ && (!Number.isSafeInteger(activation.delayUntilRecursion) || activation.delayUntilRecursion < 0)) throw new TypeError(`${label}.params.activation.delayUntilRecursion must be boolean or a non-negative integer`)
439
+ for (const key of ['selective', 'useProbability', 'groupOverride', 'useGroupScoring', 'excludeRecursion', 'preventRecursion']) {
440
+ if (activation[key] !== undefined && typeof activation[key] !== 'boolean') throw new TypeError(`${label}.params.activation.${key} must be boolean`)
441
+ }
442
+ for (const key of ['probability', 'groupWeight']) {
443
+ if (activation[key] !== undefined && (!Number.isFinite(activation[key]) || activation[key] < 0 || (key === 'probability' && activation[key] > 100))) throw new TypeError(`${label}.params.activation.${key} is out of range`)
444
+ }
445
+ if (activation.selectionGroup !== undefined && typeof activation.selectionGroup !== 'string') throw new TypeError(`${label}.params.activation.selectionGroup must be string`)
446
+ if (activation.scanVariables !== undefined && (!Array.isArray(activation.scanVariables) || activation.scanVariables.some(key => typeof key !== 'string' || key.length === 0))) throw new TypeError(`${label}.params.activation.scanVariables must contain variable names`)
447
+ for (const key of ['keys', 'secondaryKeys']) {
448
+ if (params[key] !== undefined && (!Array.isArray(params[key]) || params[key].some(value => typeof value !== 'string'))) throw new TypeError(`${label}.params.${key} must contain strings`)
449
+ }
450
+ for (const key of ['constant', 'caseSensitive', 'wholeWords', 'useRegex']) {
451
+ if (params[key] !== undefined && typeof params[key] !== 'boolean') throw new TypeError(`${label}.params.${key} must be boolean`)
452
+ }
453
+ if (params.selectiveLogic !== undefined && !ACTIVATION_LOGIC.has(params.selectiveLogic)) throw new TypeError(`${label}.params.selectiveLogic must use a named logic`)
454
+ if (params.wordBoundary !== undefined && !['ascii-token', 'unicode'].includes(params.wordBoundary)) throw new TypeError(`${label}.params.wordBoundary must be ascii-token or unicode`)
455
+ }
456
+
529
457
  /** 从 YAML 提示词配置描述构造运行时提示词配置。配置错误必须在挂载时暴露(fail loud)。 */
530
458
  export function createPromptConfigs(specs, options = {}) {
531
459
  if (options.templateModuleRoot !== undefined && options.templatePresetRoot !== undefined
@@ -553,7 +481,8 @@ export function createPromptConfigs(specs, options = {}) {
553
481
  }
554
482
  const rawStrategy = spec.strategy ?? 'static'
555
483
  const strategy = rawStrategy
556
- if (strategy === 'custom-fallback') throw new TypeError(`${name}: migrate custom-fallback to an anchor condition and anchor-notice content`)
484
+ if (strategy === 'world-book') throw new TypeError(`${name}: world-book is retired; reimport or explicitly convert to context-activation`)
485
+ if (['custom-fallback', 'first-turn-anchor', 'anchor-notice', 'guide-auto'].includes(strategy)) throw new TypeError(`${name}: retired strategy ${strategy}; use rule conditions and static content`)
557
486
  if (!KNOWN_STRATEGIES.has(strategy)) {
558
487
  // 模板专属策略:声明了 strategyDir 时由 strategies.bindResolver 懒加载,
559
488
  // 否则视为未知策略 fail loud。
@@ -566,6 +495,7 @@ export function createPromptConfigs(specs, options = {}) {
566
495
  throw new TypeError(`${name}: ${label} unknown configKind ${JSON.stringify(configKind)}`)
567
496
  }
568
497
  const layer = spec.layer ?? 'pre-step'
498
+ if (layer === 'agent-request' || layer === 'llm-stream') throw new TypeError(`${name}: layer ${layer} is not an inject-text channel; use ${layer === 'agent-request' ? 'request-params' : 'stream-output'} action`)
569
499
  if (!KNOWN_LAYERS.has(layer)) {
570
500
  throw new TypeError(`${name}: ${label} unknown layer ${JSON.stringify(layer)} — known layers: ${[...KNOWN_LAYERS].sort().join(', ')}`)
571
501
  }
@@ -576,7 +506,7 @@ export function createPromptConfigs(specs, options = {}) {
576
506
  }
577
507
  // 层能力矩阵同时是引擎校验源:矩阵标记 false 的字段在对应层不生效,
578
508
  // 显式提供时 fail loud(UI 表单已按矩阵隐藏,此处兜底手写配置)。
579
- const restricted = ['position', 'dedupe', 'promotion', 'audience', 'modelScope', 'merge', 'role', 'subject', 'match']
509
+ const restricted = ['position', 'dedupe', 'promotion', 'audience', 'modelScope', 'merge', 'role']
580
510
  .filter((field) => LAYER_FIELD_POLICIES[layer][field] === false && spec[field === 'merge' ? 'mergeMode' : field] != null)
581
511
  if (restricted.length > 0) {
582
512
  throw new TypeError(`${name}: ${label} layer ${JSON.stringify(layer)} does not support field(s): ${restricted.join(', ')}`)
@@ -584,8 +514,6 @@ export function createPromptConfigs(specs, options = {}) {
584
514
  // 标量字段统一校验与归一(B5 T2:原 15 段同型校验收敛为一次遍历,表顺序即报错顺序)。
585
515
  // 传**裸 label**:前缀 `${name}: ` 由 resolveConfigFields 与各 resolve 自行补齐。
586
516
  const fields = resolveConfigFields(spec, label, layer)
587
- // subject 已由 fields 归一(含「全局集合 + 层内允许集」两条语义与层缺省回退)。
588
- const match = normalizeMatch(spec.match, `${name}: ${label}`)
589
517
  // placeholder 的层限制由上面的 STRATEGY_LAYER_SUPPORT 统一校验(此处不再重复)。
590
518
  let fill
591
519
  if (strategy === 'placeholder') {
@@ -599,10 +527,9 @@ export function createPromptConfigs(specs, options = {}) {
599
527
  const templatePatch = template !== null && typeof template === 'object'
600
528
  ? { id: template.id, role: template.role, content: template.content, source: template.source }
601
529
  : undefined
602
- // tool-pipeline 的 toolNames 是逗号分隔字符串(parseToolNames 只认字符串):
603
- // 数组写法会被静默解析为空 = 匹配所有工具,把一条定向门扩大成全工具门,
604
- // 与条件判定叠加后危害更大,这里归一化而不是留给运行时。
530
+ // 内容和层参数保留各自验证,条件只由规则树负责。
605
531
  const params = normalizeLayerParams(spec.params, layer, `${name}: ${label}`)
532
+ validateActivation(params, strategy, `${name}: ${label}`)
606
533
  const config = {
607
534
  id: spec.id,
608
535
  name: fields.name ?? spec.id,
@@ -620,15 +547,13 @@ export function createPromptConfigs(specs, options = {}) {
620
547
  promotion: fields.promotion,
621
548
  audience: fields.audience,
622
549
  modelScope: fields.modelScope,
623
- subject: fields.subject,
624
- match,
625
550
  // sourceKind 进的是消息 source.kind,必须是 **v4 的生产者身份**(`plugin:<owner>`):
626
551
  // 裸规则 id 会被 `userMessagesText` 的注入过滤漏掉(它只认 `source.plugin` 与
627
552
  // `plugin:` 前缀),于是本引擎注入过的规则正文会回来参与下一轮 when 判定——实测
628
553
  // 后果是只读档正文里的「只读」二字让写档的 notAny 在第二轮翻转,同一子代理两档并注。
629
554
  sourceKind: (() => {
630
555
  const declared = typeof spec.sourceKind === 'string' && spec.sourceKind.length > 0 ? spec.sourceKind : spec.id
631
- return declared.startsWith('plugin:') ? declared : `plugin:${declared}`
556
+ return pluginOwner(declared) !== undefined ? declared : pluginKind(declared)
632
557
  })(),
633
558
  form: typeof spec.form === 'string' ? spec.form : 'notice',
634
559
  summary: typeof spec.summary === 'string' ? spec.summary : '',
@@ -647,18 +572,15 @@ export function createPromptConfigs(specs, options = {}) {
647
572
  templatePatch,
648
573
  params,
649
574
  }
650
- const sequence = (Object.hasOwn(options.configOrder ?? {}, spec.id) ? options.configOrder[spec.id] : undefined) ?? spec[FILE_SEQUENCE]
575
+ const sequence = (Object.hasOwn(options.configOrder ?? {}, spec.id) ? options.configOrder[spec.id] : undefined)
651
576
  ?? (typeof options.sourceModuleId === 'string' ? index * 10 : undefined)
652
577
  if (sequence !== undefined) config.sequence = sequence
653
578
  if (typeof options.sourceModuleId === 'string') config.sourceModuleId = options.sourceModuleId
654
- // 条件判定的匹配器在挂载期编译一次,执行侧(condition.mjs)直接复用:
655
- // normalizeMatch 已用同一份参数试编译过,这里是同源的第二句(失败会抛出)。
656
- config.matchScan = match === undefined ? undefined : createAnchorMatcher(match).scan
657
579
  config.resolve = bindResolver(config, options.strategyDir)
658
580
  return config
659
581
  })
660
582
  // 模块/物化来源按持久序号;没有文件来源的独立数组继续采用 anchor + order 契约。
661
- return attachStRenderers(configs
583
+ return configs
662
584
  .map((config, fileOrder) => ({ config, fileOrder }))
663
585
  .sort((a, b) => {
664
586
  if (a.config.sequence !== undefined || b.config.sequence !== undefined) {
@@ -670,5 +592,5 @@ export function createPromptConfigs(specs, options = {}) {
670
592
  if (aAnchor === 1 && a.config.order !== b.config.order) return a.config.order - b.config.order
671
593
  return a.fileOrder - b.fileOrder
672
594
  })
673
- .map(({ config }) => config))
595
+ .map(({ config }) => config)
674
596
  }