dsh-plugin-prompt-tool 0.6.2 → 0.7.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 (87) hide show
  1. package/README.md +11 -9
  2. package/engine/anchor-match.mjs +38 -12
  3. package/engine/anchor-turn.mjs +68 -0
  4. package/engine/classify-task.mjs +35 -0
  5. package/engine/code-presentation.mjs +103 -0
  6. package/engine/compositions/library/anchor-turn.yml +10 -0
  7. package/engine/compositions/library/bootstrap-filesystem.yml +1 -1
  8. package/engine/compositions/library/code-presentation.yml +8 -0
  9. package/engine/compositions/library/command-goal.yml +11 -0
  10. package/engine/compositions/library/compaction.yml +1 -1
  11. package/engine/compositions/library/context-gate.yml +1 -2
  12. package/engine/compositions/library/cot-drip.yml +11 -0
  13. package/engine/compositions/library/custom-bash.yml +1 -1
  14. package/engine/compositions/library/delegation.yml +11 -7
  15. package/engine/compositions/library/deliberation-gate.yml +11 -0
  16. package/engine/compositions/library/persistent-shell.yml +25 -2
  17. package/engine/compositions/library/persona.yml +6 -2
  18. package/engine/compositions/library/planning.yml +1 -1
  19. package/engine/compositions/library/prompt-config-engine.yml +1 -1
  20. package/engine/compositions/library/run-code-env.yml +1 -1
  21. package/engine/compositions/library/skill-filesystem.yml +1 -1
  22. package/engine/compositions/library/skill-search.yml +1 -1
  23. package/engine/compositions/library/str-replace-editor.yml +2 -2
  24. package/engine/compositions/library/tool-ask-user.yml +1 -1
  25. package/engine/compositions/library/tool-bash.yml +1 -1
  26. package/engine/compositions/library/tool-bootstrap.yml +1 -3
  27. package/engine/compositions/library/tool-config-engine.yml +9 -0
  28. package/engine/compositions/library/tool-filter.yml +3 -7
  29. package/engine/compositions/library/tool-fs-search.yml +1 -1
  30. package/engine/compositions/library/tool-fs.yml +1 -1
  31. package/engine/compositions/library/tool-goal.yml +1 -9
  32. package/engine/compositions/library/tool-jobs.yml +1 -1
  33. package/engine/compositions/library/tool-pwsh.yml +1 -1
  34. package/engine/compositions/library/tool-todo.yml +1 -1
  35. package/engine/compositions/library/tool-web.yml +2 -2
  36. package/engine/compositions/source/local/code-presentation.yml +8 -0
  37. package/engine/compositions/source/local/context-gate.yml +0 -1
  38. package/engine/compositions/source/local/str-replace-editor.yml +10 -0
  39. package/engine/compositions/source/local/tool-bootstrap.yml +1 -3
  40. package/engine/compositions/source/local/tool-config-engine.yml +9 -0
  41. package/engine/compositions/source/local/tool-filter.yml +8 -0
  42. package/engine/cot-drip.mjs +137 -0
  43. package/engine/custom-bash.mjs +73 -21
  44. package/engine/deliberation-gate.mjs +127 -0
  45. package/engine/layers.mjs +223 -198
  46. package/engine/run-code-env.mjs +8 -2
  47. package/engine/schema.mjs +1 -1
  48. package/engine/strategies.mjs +25 -14
  49. package/engine/tool-bootstrap.mjs +185 -59
  50. package/engine/tool-config-engine.mjs +390 -0
  51. package/lib/client.js +3840 -2311
  52. package/lib/client.js.map +1 -1
  53. package/lib/index.d.mts +185 -79
  54. package/lib/index.mjs +1000 -356
  55. package/lib/preset-core.d.mts +1 -1
  56. package/lib/preset-core.mjs +29 -3
  57. package/lib/{prompt-configs-BgFw_bFf.mjs → prompt-configs-BnQKRHVb.mjs} +211 -136
  58. package/lib/prompt-configs-CN0sJTUL.d.mts +193 -0
  59. package/package.json +121 -105
  60. package/preset/anchored/preset.yml +262 -308
  61. package/preset/creative/preset.yml +34 -32
  62. package/preset/custom/preset.yml +0 -1
  63. package/preset/liangshen/preset.yml +53 -50
  64. package/preset/minimal/preset.yml +18 -17
  65. package/preset/ptc/preset.yml +35 -33
  66. package/preset/standard/preset.yml +34 -32
  67. package/templates/10-pre-step.yml +18 -15
  68. package/templates/14-first-turn-anchor.yml +13 -13
  69. package/templates/15-guide-auto.yml +13 -13
  70. package/templates/16-custom-fallback.yml +19 -18
  71. package/templates/18-placeholder-env-facts.yml +13 -2
  72. package/templates/30-runtime-context.yml +3 -0
  73. package/templates/40-agent-request.yml +9 -9
  74. package/templates/60-tool-pipeline.yml +9 -8
  75. package/templates/tools/10-shell-command.yml +20 -0
  76. package/templates/tools/20-http-fetch.yml +24 -0
  77. package/templates/tools/30-fs-read.yml +20 -0
  78. package/templates/tools/40-delegate-character.yml +15 -0
  79. package/templates/tools/50-delegate-worldbook.yml +15 -0
  80. package/templates/tools/60-delegate-session-var.yml +15 -0
  81. package/templates/tools/70-delegate-worldbook-upsert.yml +36 -0
  82. package/lib/prompt-configs-B4vH09wx.d.mts +0 -100
  83. package/templates/11-merged-a.yml +0 -11
  84. package/templates/13-anchor.yml +0 -19
  85. package/templates/17-instruction-hint.yml +0 -23
  86. package/templates/19-placeholder-skill-catalog.yml +0 -19
  87. package/templates/31-runtime-context-placeholder.yml +0 -18
package/engine/layers.mjs CHANGED
@@ -1,8 +1,8 @@
1
- /**
2
- * layers — 非 pre-step 的五个官方层级接线。
3
- * 所有注册都只作用于本插件提示词配置;单条失败 warnOnce 后继续。
4
- */
5
-
1
+ /**
2
+ * layers — 非 pre-step 的五个官方层级接线。
3
+ * 所有注册都只作用于本插件提示词配置;单条失败 warnOnce 后继续。
4
+ */
5
+
6
6
  import {
7
7
  getService,
8
8
  isDelegated,
@@ -11,60 +11,85 @@ import {
11
11
  parseToolNames,
12
12
  } from './shared.mjs'
13
13
  import { interpolateVariables, interpolateStatic } from './interpolate.mjs'
14
-
15
- const name = 'prompt-config-engine'
16
-
17
- /** 单条文本型配置的完整文本:texts 数组按空行拼接(单一文本字段)。 */
18
- function configText(config) {
19
- return config.texts
20
- .map((item) => interpolateStatic(item, config.variables))
21
- .filter((item) => item.length > 0)
22
- .join('\n\n')
23
- }
24
-
25
- /** agent/request 与 tools/* 层的作用域过滤。 */
26
- function matchesAgentScope(config, agent) {
27
- if (agent === undefined) return true
28
- const delegated = isDelegated(agent.session)
29
- if (config.audience === 'main' && delegated) return false
30
- if (config.audience === 'subagent' && !delegated) return false
31
- return matchesModel(config.modelScope, agent.options?.model)
32
- }
33
-
34
- /** 文本型层分组:merged 模式按位置分组,否则每条独立。 */
35
- function textLayerGroups(configs) {
36
- const sorted = [...configs].sort((a, b) => a.order - b.order)
37
- const groups = []
38
- const index = new Map()
39
- for (const config of sorted) {
40
- const key = config.mergeMode === 'merged' ? `merged:${config.position ?? ''}` : undefined
41
- if (key === undefined) {
42
- groups.push([config])
43
- continue
44
- }
45
- let at = index.get(key)
46
- if (at === undefined) {
47
- at = groups.length
48
- index.set(key, at)
49
- groups.push([])
50
- }
51
- groups[at].push(config)
52
- }
53
- return groups
54
- }
55
-
14
+
15
+ const name = 'prompt-config-engine'
16
+
17
+ /** 单条文本型配置的完整文本:texts 数组按空行拼接(单一文本字段)。 */
18
+ function configText(config) {
19
+ return config.texts
20
+ .map((item) => interpolateStatic(item, config.variables))
21
+ .filter((item) => item.length > 0)
22
+ .join('\n\n')
23
+ }
24
+
25
+ /** agent/request 与 tools/* 层的作用域过滤。 */
26
+ function matchesAgentScope(config, agent) {
27
+ if (agent === undefined) return true
28
+ const delegated = isDelegated(agent.session)
29
+ if (config.audience === 'main' && delegated) return false
30
+ if (config.audience === 'subagent' && !delegated) return false
31
+ return matchesModel(config.modelScope, agent.options?.model)
32
+ }
33
+
34
+ /** 文本型层分组:merged 模式按位置分组,否则每条独立。 */
35
+ function textLayerGroups(configs) {
36
+ const sorted = [...configs].sort((a, b) => a.order - b.order)
37
+ const groups = []
38
+ const index = new Map()
39
+ for (const config of sorted) {
40
+ const key = config.mergeMode === 'merged' ? `merged:${config.position ?? ''}` : undefined
41
+ if (key === undefined) {
42
+ groups.push([config])
43
+ continue
44
+ }
45
+ let at = index.get(key)
46
+ if (at === undefined) {
47
+ at = groups.length
48
+ index.set(key, at)
49
+ groups.push([])
50
+ }
51
+ groups[at].push(config)
52
+ }
53
+ return groups
54
+ }
55
+
56
+ /** persona 段标记(官方 deployment:persona + 旧名 persona)。 */
57
+ function isPersonaSection(config) {
58
+ const sectionName = config?.params?.sectionName
59
+ return sectionName === 'deployment:persona' || sectionName === 'persona'
60
+ }
61
+
56
62
  /** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
57
63
  function wireSystemSections(ctx, configs, warnOnce) {
58
- const systemPrompt = getService(ctx, 'systemPrompt')
59
- if (systemPrompt === undefined || typeof systemPrompt.section !== 'function') {
60
- if (configs.length > 0) warnOnce(`${name}: systemPrompt service unavailable — system-section configs skipped`)
61
- return
62
- }
63
- for (const group of textLayerGroups(configs)) {
64
- const base = group[0]
65
- try {
66
- const text = group.map((config) => configText(config)).filter((item) => item.length > 0).join('\n\n')
67
- if (text.length === 0) continue
64
+ const systemPrompt = getService(ctx, 'systemPrompt')
65
+ if (systemPrompt === undefined || typeof systemPrompt.section !== 'function') {
66
+ if (configs.length > 0) warnOnce(`${name}: systemPrompt service unavailable — system-section configs skipped`)
67
+ return
68
+ }
69
+ // 子代理 persona 分支卡(audience=subagent + 人设段标记):不独立注册(同名段冲突),
70
+ // 作为主 persona 段的子代理分支——装配时子代理使用子卡文本(替换主会话人设)。
71
+ const subPersona = configs
72
+ .filter((config) => config.audience === 'subagent' && isPersonaSection(config))
73
+ .sort((a, b) => a.order - b.order)[0]
74
+ const registerable = configs.filter((config) => !(config.audience === 'subagent' && isPersonaSection(config)))
75
+ for (const group of textLayerGroups(registerable)) {
76
+ const base = group[0]
77
+ try {
78
+ const groupText = group.map((config) => configText(config)).filter((item) => item.length > 0).join('\n\n')
79
+ if (groupText.length === 0) continue
80
+ const hasAudience = group.some((config) => config.audience != null)
81
+ // persona 主段 + 存在子代理分支:text 按 agent 分支(子代理 = 子卡文本,替换语义;
82
+ // 无子卡 = 继承主 persona)。官方 AssembleContext 运行时含 agent(assembleContextFor 注入)。
83
+ const isPersonaMain = base.audience == null && isPersonaSection(base)
84
+ const text = isPersonaMain && subPersona !== undefined
85
+ ? (context) => isDelegated(context?.agent?.session) ? configText(subPersona) : groupText
86
+ : hasAudience
87
+ ? (context) => group
88
+ .filter((config) => matchesAgentScope(config, context?.agent))
89
+ .map((config) => configText(config))
90
+ .filter((item) => item.length > 0)
91
+ .join('\n\n')
92
+ : groupText
68
93
  keepDisposer(ctx, systemPrompt.section({
69
94
  name: typeof base.params?.sectionName === 'string' && base.params.sectionName.length > 0 ? base.params.sectionName : base.id,
70
95
  order: base.order,
@@ -81,144 +106,144 @@ function wireSystemSections(ctx, configs, warnOnce) {
81
106
  }
82
107
  }
83
108
  }
84
-
85
- /** runtime-context:注册动态运行时上下文(晋升后由 context-gate 差分投影;支持 merged 拼接与 placeholder 函数 provider)。 */
86
- function wireRuntimeContexts(ctx, configs, warnOnce) {
87
- const systemPrompt = getService(ctx, 'systemPrompt')
88
- if (systemPrompt === undefined || typeof systemPrompt.context !== 'function') {
89
- if (configs.length > 0) warnOnce(`${name}: systemPrompt service unavailable — runtime-context configs skipped`)
90
- return
91
- }
92
- const staticConfigs = configs.filter((config) => config.strategy !== 'placeholder')
93
- for (const group of textLayerGroups(staticConfigs)) {
94
- const base = group[0]
95
- try {
96
- const text = group.map((config) => configText(config)).filter((item) => item.length > 0).join('\n\n')
97
- if (text.length === 0) continue
98
- keepDisposer(ctx, systemPrompt.context({
99
- name: typeof base.params?.contextName === 'string' && base.params.contextName.length > 0 ? base.params.contextName : base.id,
100
- order: base.order,
101
- text,
102
- }), `${name}: context ${base.id}`)
103
- } catch (error) {
104
- warnOnce(`${name}: runtime-context config ${base.id} failed: ${String(error?.message ?? error)}`)
105
- }
106
- }
107
- // placeholder:官方 context 接受函数 provider,在每次 assembly 时动态填充。
108
- const placeholders = configs.filter((config) => config.strategy === 'placeholder')
109
- .sort((a, b) => a.order - b.order)
110
- for (const config of placeholders) {
111
- try {
112
- const resolver = config.resolve
113
- keepDisposer(ctx, systemPrompt.context({
114
- name: typeof config.params?.contextName === 'string' && config.params.contextName.length > 0 ? config.params.contextName : config.id,
115
- order: config.order,
116
- text: async (assembly) => {
117
- const agent = assembly?.agent
118
- const session = agent?.session
119
- const resolved = await resolver({ ctx, agent, session, decision: { kind: 'ok', messages: [] }, messages: [] })
120
- if (resolved === null || resolved === undefined) return ''
121
- const variables = { ...config.variables, ...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}) }
122
- if (config.texts.length > 0) return interpolateVariables(config.texts.join('\n\n'), variables, session)
123
- return typeof resolved.text === 'string' ? interpolateVariables(resolved.text, variables, session) : ''
124
- },
125
- }), `${name}: context ${config.id}`)
126
- } catch (error) {
127
- warnOnce(`${name}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`)
128
- }
129
- }
130
- }
131
-
132
- /** agent-request:对冻结的 LlmCallConfig 做浅合并 / 整体替换。 */
133
- function wireAgentRequests(ctx, configs, warnOnce) {
134
- for (const config of configs) {
135
- ctx.on('agent/request', async (payload, next) => {
136
- const base = await next()
137
- try {
138
- if (!matchesAgentScope(config, payload?.agent)) return base
139
- const patch = config.params?.patch !== null && typeof config.params?.patch === 'object' && !Array.isArray(config.params.patch)
140
- ? config.params.patch
141
- : {}
142
- if (config.params?.replace === true) return { ...patch }
143
- return { ...base, ...patch }
144
- } catch (error) {
145
- warnOnce(`${name}: agent-request config ${config.id} failed: ${String(error?.message ?? error)}`)
146
- return base
147
- }
148
- })
149
- }
150
- }
151
-
152
- /** 把流替换为提示词配置文本的最小合法 chunk 序列。 */
153
- async function* replacedStream(text) {
154
- yield { type: 'block-start', index: 0, blockType: 'text' }
155
- yield { type: 'text-delta', index: 0, text }
156
- yield { type: 'block-end', index: 0, block: { type: 'text', text } }
157
- }
158
-
159
- /** llm-stream:pass 透传;replace 用提示词配置文本替代整个模型流。 */
160
- function wireLlmStreams(ctx, configs, warnOnce) {
161
- for (const config of configs) {
162
- ctx.on('llm/stream', (options, next) => {
163
- try {
164
- const mode = config.params?.mode ?? 'pass'
165
- if (mode === 'replace' && config.texts.length > 0 && matchesModel(config.modelScope, options?.model)) {
166
- return replacedStream(config.texts.join('\n\n'))
167
- }
168
- return next()
169
- } catch (error) {
170
- warnOnce(`${name}: llm-stream config ${config.id} failed: ${String(error?.message ?? error)}`)
171
- return next()
172
- }
173
- })
174
- }
175
- }
176
-
177
- /** tool-pipeline:pre-execute 判定、execute 包装、post-execute 结果替换/阻断。 */
178
- function wireToolPipelines(ctx, configs, warnOnce) {
179
- for (const config of configs) {
180
- const names = parseToolNames(config.params?.toolNames)
181
- const matchesTool = (exec) => names.length === 0 || names.includes(exec?.name)
182
- ctx.on('tools/pre-execute', async (exec, next) => {
183
- try {
184
- if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
185
- const decision = config.params?.preDecision ?? 'allow'
186
- if (decision === 'allow') return next()
187
- if (decision === 'deny') {
188
- return { kind: 'deny', reason: String(config.params?.denyReason ?? `${config.name}: denied by prompt config`) }
189
- }
190
- if (decision === 'ask') return { kind: 'ask' }
191
- return next()
192
- } catch (error) {
193
- warnOnce(`${name}: tool-pipeline(pre) config ${config.id} failed: ${String(error?.message ?? error)}`)
194
- return next()
195
- }
196
- })
197
- ctx.on('tools/post-execute', async (exec, result, next) => {
198
- try {
199
- if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
200
- const action = config.params?.postAction ?? 'accept'
201
- if (action === 'accept') return next()
202
- if (action === 'replace' && config.texts.length > 0) {
203
- return { kind: 'accept', content: [{ type: 'text', text: config.texts.join('\n\n') }] }
204
- }
205
- if (action === 'block') {
206
- return { kind: 'block', feedback: [{ type: 'text', text: config.texts.length > 0 ? config.texts.join('\n\n') : `${config.name}: blocked by prompt config` }] }
207
- }
208
- return next()
209
- } catch (error) {
210
- warnOnce(`${name}: tool-pipeline(post) config ${config.id} failed: ${String(error?.message ?? error)}`)
211
- return next()
212
- }
213
- })
214
- }
215
- }
216
-
217
- /** 把非 pre-step 提示词配置接入其声明的官方层级通道。 */
218
- export function wireLayers(ctx, configs, warnOnce) {
219
- wireSystemSections(ctx, configs.filter((config) => config.layer === 'system-section'), warnOnce)
220
- wireRuntimeContexts(ctx, configs.filter((config) => config.layer === 'runtime-context'), warnOnce)
221
- wireAgentRequests(ctx, configs.filter((config) => config.layer === 'agent-request'), warnOnce)
222
- wireLlmStreams(ctx, configs.filter((config) => config.layer === 'llm-stream'), warnOnce)
223
- wireToolPipelines(ctx, configs.filter((config) => config.layer === 'tool-pipeline'), warnOnce)
224
- }
109
+
110
+ /** runtime-context:注册动态运行时上下文(晋升后由 context-gate 差分投影;支持 merged 拼接与 placeholder 函数 provider)。 */
111
+ function wireRuntimeContexts(ctx, configs, warnOnce) {
112
+ const systemPrompt = getService(ctx, 'systemPrompt')
113
+ if (systemPrompt === undefined || typeof systemPrompt.context !== 'function') {
114
+ if (configs.length > 0) warnOnce(`${name}: systemPrompt service unavailable — runtime-context configs skipped`)
115
+ return
116
+ }
117
+ const staticConfigs = configs.filter((config) => config.strategy !== 'placeholder')
118
+ for (const group of textLayerGroups(staticConfigs)) {
119
+ const base = group[0]
120
+ try {
121
+ const text = group.map((config) => configText(config)).filter((item) => item.length > 0).join('\n\n')
122
+ if (text.length === 0) continue
123
+ keepDisposer(ctx, systemPrompt.context({
124
+ name: typeof base.params?.contextName === 'string' && base.params.contextName.length > 0 ? base.params.contextName : base.id,
125
+ order: base.order,
126
+ text,
127
+ }), `${name}: context ${base.id}`)
128
+ } catch (error) {
129
+ warnOnce(`${name}: runtime-context config ${base.id} failed: ${String(error?.message ?? error)}`)
130
+ }
131
+ }
132
+ // placeholder:官方 context 接受函数 provider,在每次 assembly 时动态填充。
133
+ const placeholders = configs.filter((config) => config.strategy === 'placeholder')
134
+ .sort((a, b) => a.order - b.order)
135
+ for (const config of placeholders) {
136
+ try {
137
+ const resolver = config.resolve
138
+ keepDisposer(ctx, systemPrompt.context({
139
+ name: typeof config.params?.contextName === 'string' && config.params.contextName.length > 0 ? config.params.contextName : config.id,
140
+ order: config.order,
141
+ text: async (assembly) => {
142
+ const agent = assembly?.agent
143
+ const session = agent?.session
144
+ const resolved = await resolver({ ctx, agent, session, decision: { kind: 'ok', messages: [] }, messages: [] })
145
+ if (resolved === null || resolved === undefined) return ''
146
+ const variables = { ...config.variables, ...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}) }
147
+ if (config.texts.length > 0) return interpolateVariables(config.texts.join('\n\n'), variables, session)
148
+ return typeof resolved.text === 'string' ? interpolateVariables(resolved.text, variables, session) : ''
149
+ },
150
+ }), `${name}: context ${config.id}`)
151
+ } catch (error) {
152
+ warnOnce(`${name}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`)
153
+ }
154
+ }
155
+ }
156
+
157
+ /** agent-request:对冻结的 LlmCallConfig 做浅合并 / 整体替换。 */
158
+ function wireAgentRequests(ctx, configs, warnOnce) {
159
+ for (const config of configs) {
160
+ ctx.on('agent/request', async (payload, next) => {
161
+ const base = await next()
162
+ try {
163
+ if (!matchesAgentScope(config, payload?.agent)) return base
164
+ const patch = config.params?.patch !== null && typeof config.params?.patch === 'object' && !Array.isArray(config.params.patch)
165
+ ? config.params.patch
166
+ : {}
167
+ if (config.params?.replace === true) return { ...patch }
168
+ return { ...base, ...patch }
169
+ } catch (error) {
170
+ warnOnce(`${name}: agent-request config ${config.id} failed: ${String(error?.message ?? error)}`)
171
+ return base
172
+ }
173
+ })
174
+ }
175
+ }
176
+
177
+ /** 把流替换为提示词配置文本的最小合法 chunk 序列。 */
178
+ async function* replacedStream(text) {
179
+ yield { type: 'block-start', index: 0, blockType: 'text' }
180
+ yield { type: 'text-delta', index: 0, text }
181
+ yield { type: 'block-end', index: 0, block: { type: 'text', text } }
182
+ }
183
+
184
+ /** llm-stream:pass 透传;replace 用提示词配置文本替代整个模型流。 */
185
+ function wireLlmStreams(ctx, configs, warnOnce) {
186
+ for (const config of configs) {
187
+ ctx.on('llm/stream', (options, next) => {
188
+ try {
189
+ const mode = config.params?.mode ?? 'pass'
190
+ if (mode === 'replace' && config.texts.length > 0 && matchesModel(config.modelScope, options?.model)) {
191
+ return replacedStream(config.texts.join('\n\n'))
192
+ }
193
+ return next()
194
+ } catch (error) {
195
+ warnOnce(`${name}: llm-stream config ${config.id} failed: ${String(error?.message ?? error)}`)
196
+ return next()
197
+ }
198
+ })
199
+ }
200
+ }
201
+
202
+ /** tool-pipeline:pre-execute 判定、execute 包装、post-execute 结果替换/阻断。 */
203
+ function wireToolPipelines(ctx, configs, warnOnce) {
204
+ for (const config of configs) {
205
+ const names = parseToolNames(config.params?.toolNames)
206
+ const matchesTool = (exec) => names.length === 0 || names.includes(exec?.name)
207
+ ctx.on('tools/pre-execute', async (exec, next) => {
208
+ try {
209
+ if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
210
+ const decision = config.params?.preDecision ?? 'allow'
211
+ if (decision === 'allow') return next()
212
+ if (decision === 'deny') {
213
+ return { kind: 'deny', reason: String(config.params?.denyReason ?? `${config.name}: denied by prompt config`) }
214
+ }
215
+ if (decision === 'ask') return { kind: 'ask' }
216
+ return next()
217
+ } catch (error) {
218
+ warnOnce(`${name}: tool-pipeline(pre) config ${config.id} failed: ${String(error?.message ?? error)}`)
219
+ return next()
220
+ }
221
+ })
222
+ ctx.on('tools/post-execute', async (exec, result, next) => {
223
+ try {
224
+ if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
225
+ const action = config.params?.postAction ?? 'accept'
226
+ if (action === 'accept') return next()
227
+ if (action === 'replace' && config.texts.length > 0) {
228
+ return { kind: 'accept', content: [{ type: 'text', text: config.texts.join('\n\n') }] }
229
+ }
230
+ if (action === 'block') {
231
+ return { kind: 'block', feedback: [{ type: 'text', text: config.texts.length > 0 ? config.texts.join('\n\n') : `${config.name}: blocked by prompt config` }] }
232
+ }
233
+ return next()
234
+ } catch (error) {
235
+ warnOnce(`${name}: tool-pipeline(post) config ${config.id} failed: ${String(error?.message ?? error)}`)
236
+ return next()
237
+ }
238
+ })
239
+ }
240
+ }
241
+
242
+ /** 把非 pre-step 提示词配置接入其声明的官方层级通道。 */
243
+ export function wireLayers(ctx, configs, warnOnce) {
244
+ wireSystemSections(ctx, configs.filter((config) => config.layer === 'system-section'), warnOnce)
245
+ wireRuntimeContexts(ctx, configs.filter((config) => config.layer === 'runtime-context'), warnOnce)
246
+ wireAgentRequests(ctx, configs.filter((config) => config.layer === 'agent-request'), warnOnce)
247
+ wireLlmStreams(ctx, configs.filter((config) => config.layer === 'llm-stream'), warnOnce)
248
+ wireToolPipelines(ctx, configs.filter((config) => config.layer === 'tool-pipeline'), warnOnce)
249
+ }
@@ -18,9 +18,14 @@
18
18
  * native 模式自动空转。
19
19
  */
20
20
 
21
+ import { validateConfig } from './shared.mjs'
22
+
21
23
  /** Cordis 插件名,供 loader 诊断使用。 */
22
24
  export const name = 'run-code-env'
23
25
 
26
+ /** Every config key this plugin accepts — anything else is a typo. */
27
+ const ALLOWED_KEYS = new Set(['enabled', 'envKeys'])
28
+
24
29
  /** 需要 tools 视图与 systemPrompt 段。shellEnv 为机会型读取,不写进 inject。 */
25
30
  export const inject = ['tools', 'systemPrompt']
26
31
 
@@ -166,7 +171,7 @@ export function patchRunCodeTool(tool, ctx, keys) {
166
171
 
167
172
  /** 注册 PTC env 注入:只在目标 scope 出现 run_code 时生效。 */
168
173
  export function apply(ctx, config) {
169
- const source = config === undefined ? {} : config
174
+ const source = validateConfig(name, config, ALLOWED_KEYS)
170
175
  const enabled = source.enabled !== false
171
176
  const keys = normalizeEnvKeys(source.envKeys)
172
177
 
@@ -197,7 +202,8 @@ export function apply(ctx, config) {
197
202
  if (enabled) {
198
203
  ctx.systemPrompt.section({
199
204
  name: 'tools:run-code-env',
200
- order: 160,
205
+ // alpha.1 官方 SDK 提示段固定在 5000;对齐保留在 per-tool 指导之后。
206
+ order: 5000,
201
207
  text: (context) => {
202
208
  const schemas = ctx.tools.schemas(context?.scope)
203
209
  if (!Array.isArray(schemas) || !schemas.some((schema) => schema?.name === 'run_code')) return ''
package/engine/schema.mjs CHANGED
@@ -120,7 +120,7 @@ export const KNOWN_FILLS = new Set(['instruction-hint', 'env-facts', 'skill-cata
120
120
  /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
121
121
  export const LAYER_FIELD_POLICIES = {
122
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 },
123
+ 'system-section': { position: false, dedupe: false, promotion: false, audience: true, modelScope: false, merge: true, order: true, role: false, placeholder: false },
124
124
  'runtime-context': { position: false, dedupe: false, promotion: false, audience: false, modelScope: false, merge: true, order: true, role: false, placeholder: true },
125
125
  'agent-request': { position: false, dedupe: false, promotion: false, audience: true, modelScope: true, merge: false, order: true, role: false, placeholder: false },
126
126
  'llm-stream': { position: false, dedupe: false, promotion: false, audience: false, modelScope: true, merge: false, order: true, role: false, placeholder: false },
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { extractText } from './shared.mjs'
9
9
  import { MATCH_LOGIC, createAnchorMatcher } from './anchor-match.mjs'
10
+ import { createTaskClassifier } from './classify-task.mjs'
10
11
  import { createInstructionHintResolver, createPlaceholderResolver } from './fillers.mjs'
11
12
 
12
13
  const name = 'prompt-config-engine'
@@ -17,30 +18,30 @@ const name = 'prompt-config-engine'
17
18
  */
18
19
  function createFirstTurnAnchorResolver(config) {
19
20
  const useCustom = config.params?.useCustom === true
20
- const customText = typeof config.params?.firstTurnText === 'string' ? config.params.firstTurnText : ''
21
+ // 自定义文本统一读 text(与 guide-auto 同契约);firstTurnText 旧键兼容。
22
+ const customText = typeof config.params?.text === 'string'
23
+ ? config.params.text
24
+ : (typeof config.params?.firstTurnText === 'string' ? config.params.firstTurnText : '')
21
25
  const buildPattern = typeof config.params?.buildPattern === 'string' ? config.params.buildPattern : ''
22
26
  const complexPattern = typeof config.params?.complexPattern === 'string' ? config.params.complexPattern : ''
23
27
  const firstTurnBuild = typeof config.params?.firstTurnBuild === 'string' ? config.params.firstTurnBuild : ''
24
28
  const firstTurnInspect = typeof config.params?.firstTurnInspect === 'string' ? config.params.firstTurnInspect : ''
25
29
  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
30
+ const classifier = createTaskClassifier({ buildPattern, complexPattern })
28
31
  return ({ messages }) => {
29
32
  if (useCustom) {
30
33
  const text = customText.trim()
31
34
  return text.length > 0 ? { text } : null
32
35
  }
33
- if (buildRe === undefined || complexRe === undefined || (firstTurnBuild.length === 0 && firstTurnInspect.length === 0 && firstTurnDeep.length === 0)) {
36
+ if (!classifier.ready || (firstTurnBuild.length === 0 && firstTurnInspect.length === 0 && firstTurnDeep.length === 0)) {
34
37
  return null
35
38
  }
36
39
  const userIndex = messages.findIndex((message) => message?.source?.kind === 'user')
37
40
  if (userIndex < 0) return null
38
41
  const taskText = extractText(messages[userIndex])
39
42
  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
43
+ const kind = classifier.classify(taskText)
44
+ const anchor = kind === 'complex' ? firstTurnDeep : kind === 'build' ? firstTurnBuild : firstTurnInspect
44
45
  return anchor.length > 0 ? { text: anchor } : null
45
46
  }
46
47
  }
@@ -48,14 +49,17 @@ function createFirstTurnAnchorResolver(config) {
48
49
  /**
49
50
  * guide-auto:晋升后每轮用户消息后的弱/深度引导。
50
51
  * 正则与引导文本全部来自 config.params(由 preset.yml 单一配置源下发)。
52
+ * 复杂任务判定 fallback 复用锚定功能的 complexPattern(guideComplexPattern
53
+ * 冗余副本已移除)——锚定与引导是独立功能,仅分类器共用。
51
54
  */
52
55
  function createGuideAutoResolver(config) {
53
56
  const useCustom = config.params?.useCustom === true
54
57
  const customText = typeof config.params?.text === 'string' ? config.params.text : ''
55
- const guideComplexPattern = typeof config.params?.guideComplexPattern === 'string' ? config.params.guideComplexPattern : ''
58
+ const complexPattern = typeof config.params?.complexPattern === 'string' ? config.params.complexPattern : ''
56
59
  const guideWeak = typeof config.params?.guideWeak === 'string' ? config.params.guideWeak : ''
57
60
  const guideDeep = typeof config.params?.guideDeep === 'string' ? config.params.guideDeep : ''
58
- const guideComplexRe = guideComplexPattern.length > 0 ? new RegExp(guideComplexPattern, 'i') : undefined
61
+ // 复杂判定 fallback 复用锚定分类器(buildPattern 不参与引导判定)。
62
+ const classifier = createTaskClassifier({ complexPattern })
59
63
  return ({ messages }) => {
60
64
  const userIndex = messages.findIndex((message) => message?.source?.kind === 'user')
61
65
  if (userIndex < 0) return null
@@ -66,7 +70,7 @@ function createGuideAutoResolver(config) {
66
70
  return guide.length > 0 ? { text: guide } : null
67
71
  }
68
72
  if (guideWeak.length === 0 && guideDeep.length === 0) return null
69
- return { text: (text.length > 120 || (guideComplexRe !== undefined && guideComplexRe.test(text))) ? guideDeep : guideWeak }
73
+ return { text: (text.length > 120 || classifier.isComplex(text)) ? guideDeep : guideWeak }
70
74
  }
71
75
  }
72
76
 
@@ -81,8 +85,13 @@ function createCustomFallbackResolver(config) {
81
85
  const firstTurnWord = typeof config.params?.firstTurnWord === 'string' && config.params.firstTurnWord.length > 0
82
86
  ? config.params.firstTurnWord
83
87
  : 'we'
84
- // 锚定匹配经 anchor-match 引擎(prefix 模式:首轮 reasoning 开头命中锚定词)。
85
- const anchor = createAnchorMatcher({ keys: [firstTurnWord], mode: 'prefix' })
88
+ // 确认词集合:writePreset 派生的 anchorWords 优先(锚句信号词多词 prefix,任一命中即
89
+ // 确认——deep Let…/自定义锚句首词都覆盖);旧产物无 anchorWords 时回退 firstTurnWord 单词。
90
+ const anchorWords = Array.isArray(config.params?.anchorWords) && config.params.anchorWords.length > 0
91
+ ? config.params.anchorWords.map(String).filter((word) => word.length > 0)
92
+ : [firstTurnWord]
93
+ // 锚定匹配经 anchor-match 引擎(prefix 模式:首轮 reasoning 开头命中任一确认词)。
94
+ const anchor = createAnchorMatcher({ keys: anchorWords, mode: 'prefix' })
86
95
 
87
96
  const anchorScanned = new Map()
88
97
 
@@ -140,7 +149,9 @@ function createWorldBookResolver(config) {
140
149
  secondaryKeys: Array.isArray(config.params?.secondaryKeys) ? config.params.secondaryKeys : [],
141
150
  caseSensitive: config.params?.caseSensitive === true,
142
151
  wholeWords: config.params?.wholeWords === true,
143
- useRegex: config.params?.useRegex === true,
152
+ // 三态透传:undefined=ST 语义自动检测(/regex/ 或含特殊字符键按正则);
153
+ // 存量配置显式 true/false 仍强制对应行为。
154
+ useRegex: config.params?.useRegex,
144
155
  logic,
145
156
  })
146
157
  // ST 语义:constant 或无任何键的条目恒注入(always active)。