dsh-plugin-prompt-tool 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +131 -222
  2. package/engine/anchor-match.mjs +117 -0
  3. package/engine/compaction-epoch.mjs +139 -0
  4. package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
  5. package/engine/compositions/library/compaction.yml +52 -0
  6. package/engine/compositions/library/context-gate.yml +40 -0
  7. package/engine/compositions/library/custom-bash.yml +14 -0
  8. package/engine/compositions/library/delegation.yml +85 -0
  9. package/engine/compositions/library/official-agent-instructions.yml +17 -0
  10. package/engine/compositions/library/official-persistent-shell.yml +56 -0
  11. package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
  12. package/engine/compositions/library/official-tool-bash.yml +6 -0
  13. package/engine/compositions/library/official-tool-cordis.yml +13 -0
  14. package/engine/compositions/library/official-tool-presentation.yml +7 -0
  15. package/engine/compositions/library/official-tool-skill.yml +14 -0
  16. package/engine/compositions/library/persistent-shell.yml +36 -0
  17. package/engine/compositions/library/persona.yml +9 -0
  18. package/engine/compositions/library/planning.yml +41 -0
  19. package/engine/compositions/library/prompt-config-engine.yml +16 -0
  20. package/engine/compositions/library/run-code-env.yml +15 -0
  21. package/engine/compositions/library/skill-filesystem.yml +13 -0
  22. package/engine/compositions/library/skill-search.yml +14 -0
  23. package/engine/compositions/library/str-replace-editor.yml +10 -0
  24. package/engine/compositions/library/tool-ask-user.yml +8 -0
  25. package/engine/compositions/library/tool-bash.yml +16 -0
  26. package/engine/compositions/library/tool-bootstrap.yml +16 -0
  27. package/engine/compositions/library/tool-filter.yml +12 -0
  28. package/engine/compositions/library/tool-fs-search.yml +18 -0
  29. package/engine/compositions/library/tool-fs.yml +10 -0
  30. package/engine/compositions/library/tool-goal.yml +19 -0
  31. package/engine/compositions/library/tool-jobs.yml +23 -0
  32. package/engine/compositions/library/tool-pwsh.yml +12 -0
  33. package/engine/compositions/library/tool-todo.yml +11 -0
  34. package/engine/compositions/library/tool-web.yml +9 -0
  35. package/engine/compositions/source/local/context-gate.yml +37 -0
  36. package/engine/compositions/source/local/custom-bash.yml +11 -0
  37. package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
  38. package/engine/compositions/source/local/run-code-env.yml +12 -0
  39. package/engine/compositions/source/local/skill-search.yml +11 -0
  40. package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
  41. package/engine/context-gate.mjs +305 -0
  42. package/{preset → engine}/custom-bash.mjs +243 -243
  43. package/engine/executor.mjs +278 -0
  44. package/engine/fillers.mjs +273 -0
  45. package/engine/interpolate.mjs +66 -0
  46. package/engine/layers.mjs +224 -0
  47. package/engine/prompt-config-engine.mjs +49 -0
  48. package/engine/run-code-env.mjs +208 -0
  49. package/engine/schema.mjs +312 -0
  50. package/engine/session-vars.mjs +52 -0
  51. package/{preset → engine}/shared.mjs +35 -0
  52. package/engine/strategies.mjs +213 -0
  53. package/{preset → engine}/tool-bootstrap.mjs +350 -282
  54. package/engine/tool-filter.mjs +80 -0
  55. package/engine/vendor/yaml/LICENSE +13 -0
  56. package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
  57. package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
  58. package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
  59. package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
  60. package/engine/vendor/yaml/dist/compose/composer.js +219 -0
  61. package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
  62. package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
  63. package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
  64. package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
  65. package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
  66. package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
  67. package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
  68. package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
  69. package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
  70. package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
  71. package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
  72. package/engine/vendor/yaml/dist/doc/Document.js +335 -0
  73. package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
  74. package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
  75. package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
  76. package/engine/vendor/yaml/dist/doc/directives.js +176 -0
  77. package/engine/vendor/yaml/dist/errors.js +57 -0
  78. package/engine/vendor/yaml/dist/index.js +17 -0
  79. package/engine/vendor/yaml/dist/log.js +11 -0
  80. package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
  81. package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
  82. package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
  83. package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
  84. package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
  85. package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
  86. package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
  87. package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
  88. package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
  89. package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
  90. package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
  91. package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
  92. package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
  93. package/engine/vendor/yaml/dist/parse/cst.js +98 -0
  94. package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
  95. package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
  96. package/engine/vendor/yaml/dist/parse/parser.js +975 -0
  97. package/engine/vendor/yaml/dist/public-api.js +102 -0
  98. package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
  99. package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
  100. package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
  101. package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
  102. package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
  103. package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
  104. package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
  105. package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
  106. package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
  107. package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
  108. package/engine/vendor/yaml/dist/schema/tags.js +96 -0
  109. package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
  110. package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
  111. package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
  112. package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
  113. package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
  114. package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
  115. package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
  116. package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
  117. package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
  118. package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
  119. package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
  120. package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
  121. package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
  122. package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
  123. package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
  124. package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
  125. package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
  126. package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
  127. package/engine/vendor/yaml/dist/util.js +11 -0
  128. package/engine/vendor/yaml/dist/visit.js +233 -0
  129. package/engine/vendor/yaml/index.js +5 -0
  130. package/engine/vendor/yaml/package.json +11 -0
  131. package/lib/client.js +4724 -775
  132. package/lib/client.js.map +1 -1
  133. package/lib/index.d.mts +530 -51
  134. package/lib/index.mjs +4164 -740
  135. package/lib/preset-core.d.mts +7 -37
  136. package/lib/preset-core.mjs +43 -285
  137. package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
  138. package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
  139. package/package.json +36 -29
  140. package/preset/anchored/preset.yml +342 -0
  141. package/preset/creative/preset.yml +65 -0
  142. package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
  143. package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
  144. package/preset/custom/preset.yml +13 -0
  145. package/preset/liangshen/preset.yml +72 -0
  146. package/preset/minimal/preset.yml +44 -0
  147. package/preset/ptc/preset.yml +56 -0
  148. package/preset/standard/preset.yml +55 -0
  149. package/skills/manifest.json +7 -0
  150. package/skills/sandboxmod/SKILL.md +49 -49
  151. package/skills/web ui/SKILL.md +42 -0
  152. package/templates/10-pre-step.yml +28 -0
  153. package/templates/11-merged-a.yml +11 -0
  154. package/templates/13-anchor.yml +19 -0
  155. package/templates/14-first-turn-anchor.yml +27 -0
  156. package/templates/15-guide-auto.yml +25 -0
  157. package/templates/16-custom-fallback.yml +21 -0
  158. package/templates/17-instruction-hint.yml +23 -0
  159. package/templates/18-placeholder-env-facts.yml +11 -0
  160. package/templates/19-placeholder-skill-catalog.yml +19 -0
  161. package/templates/20-system-section.yml +19 -0
  162. package/templates/30-runtime-context.yml +10 -0
  163. package/templates/31-runtime-context-placeholder.yml +18 -0
  164. package/templates/40-agent-request.yml +11 -0
  165. package/templates/50-llm-stream.yml +9 -0
  166. package/templates/60-tool-pipeline.yml +12 -0
  167. package/AGENTS.md +0 -4
  168. package/plan.md +0 -312
  169. package/preset/agent.cordis.yml +0 -443
  170. package/preset/compaction-epoch.mjs +0 -81
  171. package/preset/context-gate.mjs +0 -165
  172. package/preset/instruction-hint.mjs +0 -217
  173. package/preset/near-anchor.mjs +0 -101
  174. package/preset/preset.yml +0 -3
  175. package/preset/prompt-injector.mjs +0 -112
  176. package/preset/router-first-turn.mjs +0 -73
  177. package/preset/router-guide.mjs +0 -79
  178. package/preset.md +0 -115
  179. package/upstream/dsh-anchored-standard/LICENSE +0 -22
  180. package/upstream/dsh-anchored-standard/NOTICE +0 -19
  181. package/upstream/dsh-anchored-standard/REVISION +0 -1
  182. package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
  183. package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
  184. package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
  185. package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
  186. package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
  187. package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
  188. package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
  189. package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
  190. package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
  191. /package/{preset → engine}/skill-search.mjs +0 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * schema — prompt-config-engine 的提示词配置加载、归一化与权威校验。
3
+ * 只负责"配置长什么样";执行语义见 executor.mjs,内容策略见 strategies.mjs。
4
+ */
5
+
6
+ import { readFileSync, readdirSync } from 'node:fs'
7
+ import { parse as parseYaml } from './vendor/yaml/index.js'
8
+ import { bindResolver } from './strategies.mjs'
9
+
10
+ const name = 'prompt-config-engine'
11
+
12
+ /**
13
+ * 内容模板加载:提示词配置可声明 templateFile,由外部 yml / json / 纯文本模板提供内容,
14
+ * 引擎只负责读取与注入(内容与执行分离)。
15
+ * - .json:解析为 { text, id?, role?, content?, source? },或纯字符串;
16
+ * - .yml/.yaml:用 vendored yaml 完整解析(顶层对象取 text 等字段);
17
+ * - 其他扩展名:整个文件内容作为 text。
18
+ */
19
+ function readTextFile(url) {
20
+ return readFileSync(url, 'utf8')
21
+ }
22
+
23
+ function loadTemplate(file) {
24
+ if (typeof file !== 'string' || file.length === 0) return undefined
25
+ let raw
26
+ try {
27
+ raw = readTextFile(new URL(file, import.meta.url))
28
+ } catch {
29
+ throw new TypeError(`${name}: templateFile ${JSON.stringify(file)} is not readable`)
30
+ }
31
+ if (/\.json$/i.test(file)) {
32
+ try {
33
+ const parsed = JSON.parse(raw)
34
+ return typeof parsed === 'string' ? { text: parsed } : parsed
35
+ } catch (error) {
36
+ throw new TypeError(`${name}: templateFile ${JSON.stringify(file)} is not valid JSON: ${String(error?.message ?? error)}`)
37
+ }
38
+ }
39
+ if (/\.ya?ml$/i.test(file)) {
40
+ try {
41
+ const parsed = parseYaml(raw)
42
+ return typeof parsed === 'string' ? { text: parsed } : parsed
43
+ } catch (error) {
44
+ throw new TypeError(`${name}: templateFile ${JSON.stringify(file)} is not valid YAML: ${String(error?.message ?? error)}`)
45
+ }
46
+ }
47
+ return { text: raw }
48
+ }
49
+
50
+ /**
51
+ * 完整 YAML 解析(vendored yaml 包):提示词配置文件直接使用标准 YAML。
52
+ * 支持缩进 map、列表、block scalar、行尾注释、引号转义等全部语法。
53
+ */
54
+ export function parsePromptConfigYaml(raw) {
55
+ const parsed = parseYaml(raw)
56
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
57
+ throw new TypeError(`${name}: prompt config yaml must contain a single object`)
58
+ }
59
+ return parsed
60
+ }
61
+
62
+ /**
63
+ * 从提示词配置模块目录加载全部提示词配置描述:按文件名排序扫描 *.yml / *.yaml / *.json。
64
+ * 文件名用数字前缀表达引擎执行顺序(00-…、10-…)。
65
+ */
66
+ export function loadPromptConfigFiles(dirUrl) {
67
+ let entries
68
+ try {
69
+ entries = readdirSync(dirUrl, { withFileTypes: true })
70
+ } catch (error) {
71
+ throw new TypeError(`${name}: configsDir ${String(dirUrl)} is not readable: ${String(error?.message ?? error)}`)
72
+ }
73
+ // 预设级模板变量(writePreset 生成 variables.yml):读入后合并进每条配置
74
+ // variables(配置自身优先);variables.yml 本身不当作配置解析。缺失或损坏
75
+ // 时为空变量源,不阻断加载。
76
+ let presetVariables = {}
77
+ try {
78
+ const parsed = parseYaml(readFileSync(new URL('variables.yml', dirUrl), 'utf8'))
79
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) presetVariables = parsed
80
+ } catch {
81
+ // 无 variables.yml(旧产物/手写目录)或解析失败:保持空变量源。
82
+ }
83
+ const specs = []
84
+ const files = entries
85
+ .filter((entry) => entry.isFile() && /\.(ya?ml|json)$/i.test(entry.name))
86
+ .sort((a, b) => a.name.localeCompare(b.name))
87
+ for (const entry of files) {
88
+ if (entry.name === 'variables.yml') continue
89
+ const url = new URL(entry.name, dirUrl)
90
+ const raw = readFileSync(url, 'utf8')
91
+ if (/\.json$/i.test(entry.name)) {
92
+ specs.push(JSON.parse(raw))
93
+ } else {
94
+ specs.push(parsePromptConfigYaml(raw))
95
+ }
96
+ }
97
+ for (const spec of specs) {
98
+ if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) continue
99
+ const own = spec.variables !== null && typeof spec.variables === 'object' && !Array.isArray(spec.variables)
100
+ ? spec.variables
101
+ : {}
102
+ spec.variables = { ...presetVariables, ...own }
103
+ }
104
+ return specs
105
+ }
106
+
107
+ export const KNOWN_STRATEGIES = new Set(['static', 'placeholder', 'instruction-hint', 'first-turn-anchor', 'guide-auto', 'custom-fallback', 'world-book'])
108
+ export const KNOWN_SLOT_KINDS = new Set(['ordered', 'anchor'])
109
+ export const KNOWN_LAYERS = new Set(['pre-step', 'system-section', 'runtime-context', 'agent-request', 'llm-stream', 'tool-pipeline'])
110
+ export const KNOWN_POSITIONS = new Set(['after-user', 'before-all', 'after-all'])
111
+ export const KNOWN_DEDUPES = new Set(['session', 'batch', 'none'])
112
+ export const KNOWN_PROMOTIONS = new Set(['none', 'main', 'include-subagents'])
113
+ /** audience 专用标记:缺省(null/省略)= 公用(主会话+子代理通用);main=仅主会话;subagent=仅子代理。 */
114
+ export const KNOWN_AUDIENCES = new Set(['main', 'subagent'])
115
+ export const KNOWN_MERGE_MODES = new Set(['separate', 'merged'])
116
+ export const KNOWN_MODEL_SCOPES = new Set(['all', 'pro', 'flash'])
117
+ export const KNOWN_ROLES = new Set(['user', 'assistant'])
118
+ export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-catalog'])
119
+
120
+ /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
121
+ export const LAYER_FIELD_POLICIES = {
122
+ 'pre-step': { position: true, dedupe: true, promotion: true, audience: true, modelScope: true, merge: true, order: true, role: true, placeholder: true },
123
+ 'system-section': { position: false, dedupe: false, promotion: false, audience: false, modelScope: false, merge: true, order: true, role: false, placeholder: false },
124
+ 'runtime-context': { position: false, dedupe: false, promotion: false, audience: false, modelScope: false, merge: true, order: true, role: false, placeholder: true },
125
+ 'agent-request': { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false },
126
+ 'llm-stream': { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
127
+ 'tool-pipeline': { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false },
128
+ }
129
+
130
+ /** 层显示名与说明:由引擎统一下发,客户端不再各自维护。 */
131
+ export const LAYER_LABELS = {
132
+ 'pre-step': { title: '消息批层', detail: '官方默认层:agent/pre-step 消息批。支持 position / dedupe / promotion / audience / mergeMode 与文本插值。' },
133
+ 'system-section': { title: '系统段层', detail: 'system-section 静态层:注册即全局,由 order 与 params.complete / sectionName 控制。' },
134
+ 'runtime-context': { title: '运行上下文', detail: 'runtime-context 层:static 按 order 注册,placeholder 单条生效,由 params.contextName 控制。' },
135
+ 'agent-request': { title: '调用配置层', detail: 'agent-request 层:按 order 注册,params.patch 改写请求配置。' },
136
+ 'llm-stream': { title: '模型流层', detail: 'llm/stream 层:按 order 注册,params.mode = pass | replace。' },
137
+ 'tool-pipeline': { title: '工具管线层', detail: 'tools/* 层:按 order 注册,params.toolNames 与 preDecision / postAction 控制。' },
138
+ }
139
+
140
+ /** 引擎能力矩阵:作为 /meta 的唯一数据源,客户端表单据此动态渲染。 */
141
+ export function getEngineMeta() {
142
+ return {
143
+ layers: [...KNOWN_LAYERS].sort(),
144
+ strategies: [...KNOWN_STRATEGIES].sort(),
145
+ slotKinds: [...KNOWN_SLOT_KINDS].sort(),
146
+ positions: [...KNOWN_POSITIONS].sort(),
147
+ dedupes: [...KNOWN_DEDUPES].sort(),
148
+ promotions: [...KNOWN_PROMOTIONS].sort(),
149
+ audienceModes: [...KNOWN_AUDIENCES].sort(),
150
+ modelScopes: [...KNOWN_MODEL_SCOPES].sort(),
151
+ roles: [...KNOWN_ROLES].sort(),
152
+ mergeModes: [...KNOWN_MERGE_MODES].sort(),
153
+ fills: [...KNOWN_FILLS].sort(),
154
+ layerFieldPolicies: LAYER_FIELD_POLICIES,
155
+ layerLabels: LAYER_LABELS,
156
+ }
157
+ }
158
+
159
+ /** 从 YAML 提示词配置描述构造运行时提示词配置。配置错误必须在挂载时暴露(fail loud)。 */
160
+ export function createPromptConfigs(specs, options = {}) {
161
+ if (specs === undefined) return []
162
+ if (!Array.isArray(specs)) throw new TypeError(`${name}: config.configs must be an array`)
163
+ const configs = specs.map((spec, index) => {
164
+ const label = `configs[${index}]`
165
+ if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
166
+ throw new TypeError(`${name}: ${label} must be an object`)
167
+ }
168
+ if (typeof spec.id !== 'string' || spec.id.length === 0) {
169
+ throw new TypeError(`${name}: ${label}.id must be a non-empty string`)
170
+ }
171
+ const rawStrategy = spec.strategy ?? 'static'
172
+ const strategy = rawStrategy
173
+ if (!KNOWN_STRATEGIES.has(strategy)) {
174
+ // 模板专属策略:声明了 strategyDir 时由 strategies.bindResolver 懒加载,
175
+ // 否则视为未知策略 fail loud。
176
+ if (typeof options.strategyDir !== 'string' || options.strategyDir.length === 0) {
177
+ throw new TypeError(`${name}: ${label} unknown strategy ${JSON.stringify(strategy)}`)
178
+ }
179
+ }
180
+ const configKind = spec.configKind ?? 'ordered'
181
+ if (!KNOWN_SLOT_KINDS.has(configKind)) {
182
+ throw new TypeError(`${name}: ${label} unknown configKind ${JSON.stringify(configKind)}`)
183
+ }
184
+ const layer = spec.layer ?? 'pre-step'
185
+ if (!KNOWN_LAYERS.has(layer)) {
186
+ throw new TypeError(`${name}: ${label} unknown layer ${JSON.stringify(layer)} — known layers: ${[...KNOWN_LAYERS].sort().join(', ')}`)
187
+ }
188
+ // 层能力矩阵同时是引擎校验源:矩阵标记 false 的字段在对应层不生效,
189
+ // 显式提供时 fail loud(UI 表单已按矩阵隐藏,此处兜底手写配置)。
190
+ const restricted = ['position', 'dedupe', 'promotion', 'audience', 'modelScope', 'merge', 'role']
191
+ .filter((field) => LAYER_FIELD_POLICIES[layer][field] === false && spec[field === 'merge' ? 'mergeMode' : field] != null)
192
+ if (restricted.length > 0) {
193
+ throw new TypeError(`${name}: ${label} layer ${JSON.stringify(layer)} does not support field(s): ${restricted.join(', ')}`)
194
+ }
195
+ const position = spec.position ?? 'after-user'
196
+ if (!KNOWN_POSITIONS.has(position)) {
197
+ throw new TypeError(`${name}: ${label} unknown position ${JSON.stringify(position)}`)
198
+ }
199
+ const dedupe = spec.dedupe ?? 'none'
200
+ if (!KNOWN_DEDUPES.has(dedupe)) {
201
+ throw new TypeError(`${name}: ${label} unknown dedupe ${JSON.stringify(dedupe)}`)
202
+ }
203
+ const promotion = spec.promotion ?? 'none'
204
+ if (!KNOWN_PROMOTIONS.has(promotion)) {
205
+ throw new TypeError(`${name}: ${label} unknown promotion ${JSON.stringify(promotion)}`)
206
+ }
207
+ const audience = spec.audience
208
+ if (audience !== undefined && audience !== null && !KNOWN_AUDIENCES.has(audience)) {
209
+ throw new TypeError(`${name}: ${label} unknown audience ${JSON.stringify(audience)}`)
210
+ }
211
+ const modelScope = spec.modelScope ?? 'all'
212
+ if (!KNOWN_MODEL_SCOPES.has(modelScope)) {
213
+ throw new TypeError(`${name}: ${label} unknown modelScope ${JSON.stringify(modelScope)}`)
214
+ }
215
+ const role = spec.role ?? 'user'
216
+ if (!KNOWN_ROLES.has(role)) {
217
+ throw new TypeError(`${name}: ${label} unknown role ${JSON.stringify(role)}`)
218
+ }
219
+ // identity 仅支持 plugin 命名空间(kind 模式与 sourceKind 重复,已归一)。
220
+ const identity = spec.identity ?? { field: 'plugin', value: spec.id }
221
+ if (identity === null || typeof identity !== 'object' || Array.isArray(identity)
222
+ || identity.field !== 'plugin' || typeof identity.value !== 'string' || identity.value.length === 0) {
223
+ throw new TypeError(`${name}: ${label}.identity must be { field: 'plugin', value: string }`)
224
+ }
225
+ const order = spec.order ?? 0
226
+ if (typeof order !== 'number' || !Number.isFinite(order)) {
227
+ throw new TypeError(`${name}: ${label}.order must be a finite number`)
228
+ }
229
+ if (spec.group !== undefined && (typeof spec.group !== 'string' || spec.group.length === 0)) {
230
+ throw new TypeError(`${name}: ${label}.group must be a non-empty string when present`)
231
+ }
232
+ if (spec.exclusive !== undefined && typeof spec.exclusive !== 'boolean') {
233
+ throw new TypeError(`${name}: ${label}.exclusive must be a boolean when present`)
234
+ }
235
+ if (spec.name !== undefined && (typeof spec.name !== 'string' || spec.name.length === 0)) {
236
+ throw new TypeError(`${name}: ${label}.name must be a non-empty string when present`)
237
+ }
238
+ if (spec.variables !== undefined && (spec.variables === null || typeof spec.variables !== 'object' || Array.isArray(spec.variables))) {
239
+ throw new TypeError(`${name}: ${label}.variables must be an object when present`)
240
+ }
241
+ if (spec.texts !== undefined && (!Array.isArray(spec.texts) || spec.texts.some((item) => typeof item !== 'string'))) {
242
+ throw new TypeError(`${name}: ${label}.texts must be an array of strings when present`)
243
+ }
244
+ const mergeMode = spec.mergeMode ?? 'separate'
245
+ if (!KNOWN_MERGE_MODES.has(mergeMode)) {
246
+ throw new TypeError(`${name}: ${label} unknown mergeMode ${JSON.stringify(mergeMode)}`)
247
+ }
248
+ if (strategy === 'placeholder' && layer !== 'pre-step' && layer !== 'runtime-context') {
249
+ throw new TypeError(`${name}: ${label} strategy=placeholder supports layer pre-step or runtime-context only, got ${JSON.stringify(layer)}`)
250
+ }
251
+ let fill
252
+ if (strategy === 'placeholder') {
253
+ fill = typeof spec.fill === 'string' && spec.fill.length > 0 ? spec.fill : undefined
254
+ if (fill === undefined || !KNOWN_FILLS.has(fill)) {
255
+ throw new TypeError(`${name}: ${label} strategy=placeholder requires fill in [${[...KNOWN_FILLS].sort().join(', ')}]`)
256
+ }
257
+ }
258
+ const template = loadTemplate(spec.templateFile)
259
+ const templatePatch = template !== null && typeof template === 'object'
260
+ ? { id: template.id, role: template.role, content: template.content, source: template.source }
261
+ : undefined
262
+ const config = {
263
+ id: spec.id,
264
+ name: typeof spec.name === 'string' ? spec.name : spec.id,
265
+ enabled: spec.enabled !== false,
266
+ strategy,
267
+ configKind,
268
+ layer,
269
+ group: typeof spec.group === 'string' ? spec.group : undefined,
270
+ exclusive: spec.exclusive === true,
271
+ order,
272
+ role,
273
+ fill,
274
+ position,
275
+ dedupe,
276
+ promotion,
277
+ audience,
278
+ modelScope,
279
+ sourceKind: typeof spec.sourceKind === 'string' && spec.sourceKind.length > 0 ? spec.sourceKind : spec.id,
280
+ form: typeof spec.form === 'string' ? spec.form : 'notice',
281
+ summary: typeof spec.summary === 'string' ? spec.summary : '',
282
+ identity,
283
+ // text/texts 统一:text 为单块便捷写法,运行时与渲染只消费 texts。
284
+ texts: (() => {
285
+ const specText = typeof spec.text === 'string' && spec.text.length > 0 ? spec.text : undefined
286
+ const specTexts = Array.isArray(spec.texts) ? spec.texts.filter((item) => typeof item === 'string' && item.length > 0) : []
287
+ const templateText = typeof template?.text === 'string' ? template.text : ''
288
+ return specText !== undefined || specTexts.length > 0
289
+ ? [...(specText !== undefined ? [specText] : []), ...specTexts]
290
+ : (templateText.length > 0 ? [templateText] : [])
291
+ })(),
292
+ mergeMode,
293
+ variables: spec.variables !== null && typeof spec.variables === 'object' && !Array.isArray(spec.variables) ? spec.variables : {},
294
+ templatePatch,
295
+ params: spec.params !== null && typeof spec.params === 'object' && !Array.isArray(spec.params) ? spec.params : {},
296
+ }
297
+ config.resolve = bindResolver(config, options.strategyDir)
298
+ return config
299
+ })
300
+ // 排序契约:anchor 提示词配置保持模块文件相对顺序(固定锚点),ordered 提示词配置按 order
301
+ // 稳定升序排在其后。默认 order=0 时等价于文件顺序。
302
+ return configs
303
+ .map((config, fileOrder) => ({ config, fileOrder }))
304
+ .sort((a, b) => {
305
+ const aAnchor = a.config.configKind === 'anchor' ? 0 : 1
306
+ const bAnchor = b.config.configKind === 'anchor' ? 0 : 1
307
+ if (aAnchor !== bAnchor) return aAnchor - bAnchor
308
+ if (aAnchor === 1 && a.config.order !== b.config.order) return a.config.order - b.config.order
309
+ return a.fileOrder - b.fileOrder
310
+ })
311
+ .map(({ config }) => config)
312
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * session-vars — 会话变量(ST setvar/getvar 运行时语义)。
3
+ *
4
+ * 变量挂在 session 对象上(SESSION_VARS_KEY 属性),因此 .engine 引擎实例与
5
+ * 插件进程工具实例(不同模块副本)操作同一 session 即共享同一份数据——
6
+ * 无跨实例状态同步问题。WeakMap 不需要:session 释放后属性随对象回收。
7
+ *
8
+ * 插值优先级:resolved 运行时 > 会话变量 > 配置 params > 配置 variables(含预设)。
9
+ */
10
+
11
+ /** session 对象上的变量属性键(字符串常量,跨模块实例一致)。 */
12
+ export const SESSION_VARS_KEY = '__pt_session_vars__'
13
+
14
+ function varsOf(session) {
15
+ if (session === null || typeof session !== 'object') return undefined
16
+ let vars = session[SESSION_VARS_KEY]
17
+ if (vars === undefined) {
18
+ vars = {}
19
+ session[SESSION_VARS_KEY] = vars
20
+ }
21
+ return vars
22
+ }
23
+
24
+ /** 会话变量快照(无会话/未设置 → 空对象)。 */
25
+ export function sessionVarsSnapshot(session) {
26
+ const vars = varsOf(session)
27
+ return vars === undefined ? {} : { ...vars }
28
+ }
29
+
30
+ /** 读取单个会话变量(未设置 → undefined)。 */
31
+ export function getSessionVar(session, key) {
32
+ const vars = varsOf(session)
33
+ return vars === undefined ? undefined : vars[String(key)]
34
+ }
35
+
36
+ /** 设置会话变量(值转字符串;空值仍记录)。 */
37
+ export function setSessionVar(session, key, value) {
38
+ const vars = varsOf(session)
39
+ if (vars === undefined) return
40
+ vars[String(key)] = String(value ?? '')
41
+ }
42
+
43
+ /** 清除会话变量;key 缺省时清空全部。 */
44
+ export function clearSessionVars(session, key) {
45
+ const vars = varsOf(session)
46
+ if (vars === undefined) return
47
+ if (key === undefined || key === '') {
48
+ for (const name of Object.keys(vars)) delete vars[name]
49
+ } else {
50
+ delete vars[String(key)]
51
+ }
52
+ }
@@ -81,3 +81,38 @@ export function isDelegated(session) {
81
81
  export function isFlashModel(modelId) {
82
82
  return typeof modelId === 'string' && /flash/i.test(modelId)
83
83
  }
84
+
85
+ /** 模型范围过滤:flash=仅 Flash 家族模型;pro=仅非 Flash;all=全部。 */
86
+ export function matchesModel(scope, model) {
87
+ if (scope === 'all') return true
88
+ const isFlash = isFlashModel(model)
89
+ return scope === 'flash' ? isFlash : !isFlash
90
+ }
91
+
92
+ /** 逗号分隔的 token 列表;空 = 空数组(调用方自行决定"全部"语义)。 */
93
+ export function parseToolNames(value) {
94
+ if (typeof value !== 'string' || value.trim() === '') return []
95
+ return value.split(',').map((item) => item.trim()).filter((item) => item.length > 0)
96
+ }
97
+
98
+ /** 读取可选服务;测试桩 / 极简组合里缺失时返回 undefined,由调用方降级。 */
99
+ export function getService(ctx, name) {
100
+ try {
101
+ return typeof ctx.get === 'function' ? ctx.get(name) : undefined
102
+ } catch {
103
+ return undefined
104
+ }
105
+ }
106
+
107
+ /** 把服务注册返回的 disposer 挂到 fiber(资源注册契约)。 */
108
+ export function keepDisposer(ctx, disposer, label) {
109
+ if (typeof disposer !== 'function') return
110
+ try {
111
+ if (typeof ctx.effect === 'function') {
112
+ ctx.effect(() => disposer, label)
113
+ return
114
+ }
115
+ } catch {
116
+ // 极简测试桩没有 effect:保持注册随进程,测试自行隔离。
117
+ }
118
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * strategies — 引擎内容策略绑定(config.resolve)。
3
+ * 内置策略: static / placeholder / instruction-hint / first-turn-anchor / guide-auto / custom-fallback。
4
+ * 策略参数全部来自 config.params(由 preset.yml 单一配置源下发),引擎只负责组装。
5
+ * 仍支持 strategyDir 懒加载自定义模板策略。
6
+ */
7
+
8
+ import { extractText } from './shared.mjs'
9
+ import { MATCH_LOGIC, createAnchorMatcher } from './anchor-match.mjs'
10
+ import { createInstructionHintResolver, createPlaceholderResolver } from './fillers.mjs'
11
+
12
+ const name = 'prompt-config-engine'
13
+
14
+ /**
15
+ * first-turn-anchor:首条真实用户消息后的一次性任务锚点。
16
+ * 正则与锚句文本全部来自 config.params(由 preset.yml 单一配置源下发)。
17
+ */
18
+ function createFirstTurnAnchorResolver(config) {
19
+ const useCustom = config.params?.useCustom === true
20
+ const customText = typeof config.params?.firstTurnText === 'string' ? config.params.firstTurnText : ''
21
+ const buildPattern = typeof config.params?.buildPattern === 'string' ? config.params.buildPattern : ''
22
+ const complexPattern = typeof config.params?.complexPattern === 'string' ? config.params.complexPattern : ''
23
+ const firstTurnBuild = typeof config.params?.firstTurnBuild === 'string' ? config.params.firstTurnBuild : ''
24
+ const firstTurnInspect = typeof config.params?.firstTurnInspect === 'string' ? config.params.firstTurnInspect : ''
25
+ const firstTurnDeep = typeof config.params?.firstTurnDeep === 'string' ? config.params.firstTurnDeep : ''
26
+ const buildRe = buildPattern.length > 0 ? new RegExp(buildPattern, 'i') : undefined
27
+ const complexRe = complexPattern.length > 0 ? new RegExp(complexPattern, 'i') : undefined
28
+ return ({ messages }) => {
29
+ if (useCustom) {
30
+ const text = customText.trim()
31
+ return text.length > 0 ? { text } : null
32
+ }
33
+ if (buildRe === undefined || complexRe === undefined || (firstTurnBuild.length === 0 && firstTurnInspect.length === 0 && firstTurnDeep.length === 0)) {
34
+ return null
35
+ }
36
+ const userIndex = messages.findIndex((message) => message?.source?.kind === 'user')
37
+ if (userIndex < 0) return null
38
+ const taskText = extractText(messages[userIndex])
39
+ if (taskText.length === 0) return null
40
+ let anchor
41
+ if (complexRe.test(taskText)) anchor = firstTurnDeep
42
+ else if (buildRe.test(taskText)) anchor = firstTurnBuild
43
+ else anchor = firstTurnInspect
44
+ return anchor.length > 0 ? { text: anchor } : null
45
+ }
46
+ }
47
+
48
+ /**
49
+ * guide-auto:晋升后每轮用户消息后的弱/深度引导。
50
+ * 正则与引导文本全部来自 config.params(由 preset.yml 单一配置源下发)。
51
+ */
52
+ function createGuideAutoResolver(config) {
53
+ const useCustom = config.params?.useCustom === true
54
+ const customText = typeof config.params?.text === 'string' ? config.params.text : ''
55
+ const guideComplexPattern = typeof config.params?.guideComplexPattern === 'string' ? config.params.guideComplexPattern : ''
56
+ const guideWeak = typeof config.params?.guideWeak === 'string' ? config.params.guideWeak : ''
57
+ const guideDeep = typeof config.params?.guideDeep === 'string' ? config.params.guideDeep : ''
58
+ const guideComplexRe = guideComplexPattern.length > 0 ? new RegExp(guideComplexPattern, 'i') : undefined
59
+ return ({ messages }) => {
60
+ const userIndex = messages.findIndex((message) => message?.source?.kind === 'user')
61
+ if (userIndex < 0) return null
62
+ const text = extractText(messages[userIndex])
63
+ if (text.length === 0) return null
64
+ if (useCustom) {
65
+ const guide = customText.trim()
66
+ return guide.length > 0 ? { text: guide } : null
67
+ }
68
+ if (guideWeak.length === 0 && guideDeep.length === 0) return null
69
+ return { text: (text.length > 120 || (guideComplexRe !== undefined && guideComplexRe.test(text))) ? guideDeep : guideWeak }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * custom-fallback:自定义锚定词命中后注入一次,未命中最多两轮兜底。
75
+ * 参数全部来自 config.params(由 preset.yml 单一配置源下发)。
76
+ */
77
+ function createCustomFallbackResolver(config) {
78
+ const promptText = config.texts.length > 0
79
+ ? config.texts.join('\n\n')
80
+ : (typeof config.params?.text === 'string' && config.params.text.length > 0 ? config.params.text : undefined)
81
+ const firstTurnWord = typeof config.params?.firstTurnWord === 'string' && config.params.firstTurnWord.length > 0
82
+ ? config.params.firstTurnWord
83
+ : 'we'
84
+ // 锚定匹配经 anchor-match 引擎(prefix 模式:首轮 reasoning 开头命中锚定词)。
85
+ const anchor = createAnchorMatcher({ keys: [firstTurnWord], mode: 'prefix' })
86
+
87
+ const anchorScanned = new Map()
88
+
89
+ const isAnchorConfirmed = (agent) => {
90
+ const session = agent.session
91
+ const cached = anchorScanned.get(session.id)
92
+ if (cached !== undefined) return cached
93
+ const first = session.events.find((event) => event.type === 'assistant/message')
94
+ if (first === undefined) return false
95
+ const content = first.data?.message?.content ?? []
96
+ const reasoning = content.find((block) => block.type === 'reasoning')
97
+ const confirmed = reasoning !== undefined && anchor.scan(String(reasoning.text ?? '')).active
98
+ anchorScanned.set(session.id, confirmed)
99
+ return confirmed
100
+ }
101
+
102
+ const assistantRounds = (agent) =>
103
+ agent.session.events.filter((event) => event.type === 'assistant/message').length
104
+
105
+ return ({ agent }) => {
106
+ if (promptText === undefined) return null
107
+ const session = agent.session
108
+ const confirmed = isAnchorConfirmed(agent)
109
+ if (!confirmed && assistantRounds(agent) <= 1) return null
110
+ return {
111
+ text: promptText,
112
+ source: {
113
+ kind: 'plugin',
114
+ plugin: config.id,
115
+ form: 'notice',
116
+ summary: confirmed
117
+ ? `prompt-tool 提示词(「${firstTurnWord}」锚定确认后注入)`
118
+ : `prompt-tool 提示词(「${firstTurnWord}」未确认,兜底注入)`,
119
+ },
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * world-book:世界书条目(角色卡 lorebook)。constant=true 恒注入(不扫 keys);
126
+ * selective 条目扫描当前消息批文本,keys/secondaryKeys 按 selectiveLogic 组合
127
+ * (any=任一命中 / all=副键全中 / not=副键全不中)。匹配经 anchor-match 引擎。
128
+ */
129
+ function createWorldBookResolver(config) {
130
+ const constant = config.params?.constant === true
131
+ // selectiveLogic:ST world_info_logic 0=AND_ANY 1=NOT_ALL 2=NOT_ANY 3=AND_ALL。
132
+ const rawLogic = config.params?.selectiveLogic
133
+ const logic = rawLogic === 3
134
+ ? MATCH_LOGIC.ALL
135
+ : (rawLogic === 1
136
+ ? MATCH_LOGIC.NOT
137
+ : (rawLogic === 2 ? MATCH_LOGIC.NOT_ANY : MATCH_LOGIC.ANY))
138
+ const matcher = createAnchorMatcher({
139
+ keys: Array.isArray(config.params?.keys) ? config.params.keys : [],
140
+ secondaryKeys: Array.isArray(config.params?.secondaryKeys) ? config.params.secondaryKeys : [],
141
+ caseSensitive: config.params?.caseSensitive === true,
142
+ wholeWords: config.params?.wholeWords === true,
143
+ useRegex: config.params?.useRegex === true,
144
+ logic,
145
+ })
146
+ // ST 语义:constant 或无任何键的条目恒注入(always active)。
147
+ const hasAnyKey = (Array.isArray(config.params?.keys) ? config.params.keys : [])
148
+ .some((key) => String(key).trim().length > 0)
149
+ || (Array.isArray(config.params?.secondaryKeys) ? config.params.secondaryKeys : [])
150
+ .some((key) => String(key).trim().length > 0)
151
+ const promptText = config.texts.length > 0
152
+ ? config.texts.join('\n\n')
153
+ : (typeof config.params?.text === 'string' && config.params.text.length > 0 ? config.params.text : undefined)
154
+ return ({ messages }) => {
155
+ if (promptText === undefined) return null
156
+ if (constant || !hasAnyKey) return { text: promptText }
157
+ const haystack = (Array.isArray(messages) ? messages : [])
158
+ .map((message) => extractText(message))
159
+ .filter((text) => text.length > 0)
160
+ .join('\n')
161
+ if (haystack.length === 0) return null
162
+ return matcher.scan(haystack).active ? { text: promptText } : null
163
+ }
164
+ }
165
+
166
+ /**
167
+ * 为归一化后的提示词配置绑定策略 resolve(策略状态随提示词配置对象,不随 apply)。
168
+ * strategyDir:外部策略模块目录(相对本文件 URL)。未声明时只允许内置策略。
169
+ */
170
+ export function bindResolver(config, strategyDir) {
171
+ switch (config.strategy) {
172
+ case 'placeholder': return createPlaceholderResolver(config)
173
+ case 'instruction-hint': return createInstructionHintResolver(config)
174
+ case 'first-turn-anchor': return createFirstTurnAnchorResolver(config)
175
+ case 'guide-auto': return createGuideAutoResolver(config)
176
+ case 'custom-fallback':
177
+ return createCustomFallbackResolver(config)
178
+ case 'world-book':
179
+ return createWorldBookResolver(config)
180
+ case 'static': {
181
+ const texts = config.texts
182
+ const patch = config.templatePatch ?? {}
183
+ return () => {
184
+ if (texts.length > 0) return { ...patch, content: texts.map((item) => ({ type: 'text', text: item })) }
185
+ return null
186
+ }
187
+ }
188
+ default: {
189
+ // 模板专属策略:懒加载 <strategyDir>/<strategy>.mjs,模块约定导出
190
+ // `createResolver(config)` 或默认导出同名工厂。加载失败按单配置失败语义
191
+ // 抛给调用方(executor 会跳过该配置并 warnOnce)。
192
+ if (typeof strategyDir !== 'string' || strategyDir.length === 0) {
193
+ throw new TypeError(`${name}: unknown config strategy ${JSON.stringify(config.strategy)}`)
194
+ }
195
+ const moduleUrl = new URL(`${config.strategy}.mjs`, strategyDir.endsWith('/') ? strategyDir : `${strategyDir}/`)
196
+ let loaded
197
+ return async (args) => {
198
+ if (loaded === undefined) {
199
+ loaded = await import(moduleUrl.href)
200
+ }
201
+ const make = typeof loaded.createResolver === 'function'
202
+ ? loaded.createResolver
203
+ : typeof loaded.default === 'function'
204
+ ? loaded.default
205
+ : undefined
206
+ if (typeof make !== 'function') {
207
+ throw new TypeError(`${name}: strategy module ${moduleUrl.href} must export createResolver(config)`)
208
+ }
209
+ return make(config)(args)
210
+ }
211
+ }
212
+ }
213
+ }