dsh-plugin-prompt-tool 0.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -67
- package/engine/actions/append-context.mjs +49 -0
- package/engine/actions/assembly.mjs +150 -0
- package/engine/actions/catalog.mjs +92 -0
- package/engine/actions/content.mjs +205 -0
- package/engine/actions/decision.mjs +54 -0
- package/engine/actions/examples.mjs +11 -0
- package/engine/actions/guard.mjs +80 -0
- package/engine/actions/inbox-prepend.mjs +37 -0
- package/engine/actions/index.mjs +100 -0
- package/engine/actions/inject-text.mjs +22 -0
- package/engine/actions/pre-step-filter.mjs +55 -0
- package/engine/actions/registration.mjs +168 -0
- package/engine/actions/request-params.mjs +32 -0
- package/engine/actions/sdk-strip.mjs +30 -0
- package/engine/actions/shared.mjs +71 -0
- package/engine/actions.mjs +2 -931
- package/engine/branch.mjs +44 -0
- package/engine/character-tools.mjs +3 -6
- package/engine/compaction-epoch.mjs +31 -13
- package/engine/compositions/library/agent-instructions.yml +1 -1
- package/engine/compositions/library/command-goal.yml +1 -1
- package/engine/compositions/library/compaction.yml +1 -1
- package/engine/compositions/library/delegation-ptc.yml +1 -1
- package/engine/compositions/library/delegation.yml +1 -1
- package/engine/compositions/library/persistent-shell.yml +1 -1
- package/engine/compositions/library/planning.yml +1 -1
- package/engine/compositions/library/skill-filesystem-cordis.yml +1 -1
- package/engine/compositions/library/skill-filesystem.yml +1 -1
- package/engine/compositions/library/tool-ask-user.yml +1 -1
- package/engine/compositions/library/tool-bash.yml +1 -1
- package/engine/compositions/library/tool-cordis.yml +1 -1
- package/engine/compositions/library/tool-fs-search.yml +1 -1
- package/engine/compositions/library/tool-fs.yml +1 -1
- package/engine/compositions/library/tool-goal.yml +1 -1
- package/engine/compositions/library/tool-jobs.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +1 -1
- package/engine/compositions/library/tool-plugin-manager.yml +1 -1
- package/engine/compositions/library/tool-present.yml +1 -1
- package/engine/compositions/library/tool-presentation.yml +1 -1
- package/engine/compositions/library/tool-pwsh.yml +1 -1
- package/engine/compositions/library/tool-skill.yml +1 -1
- package/engine/compositions/library/tool-todo.yml +1 -1
- package/engine/compositions/library/tool-web.yml +1 -1
- package/engine/compositions/source/local/dev-tool-search.yml +11 -0
- package/engine/compositions/source/local/instruction-hint.yml +22 -1
- package/engine/compositions/source/local/rule-engine.yml +6 -0
- package/engine/condition.mjs +16 -3
- package/engine/conditions/anchor.mjs +32 -0
- package/engine/conditions/availability.mjs +47 -0
- package/engine/conditions/composite.mjs +83 -0
- package/engine/conditions/count.mjs +140 -0
- package/engine/conditions/evaluation.mjs +41 -0
- package/engine/conditions/examples.mjs +11 -0
- package/engine/conditions/index.mjs +85 -0
- package/engine/conditions/names.mjs +21 -0
- package/engine/conditions/phase.mjs +54 -0
- package/engine/conditions/preset.mjs +94 -0
- package/engine/conditions/scope.mjs +18 -0
- package/engine/conditions/session.mjs +22 -0
- package/engine/conditions/source.mjs +63 -0
- package/engine/conditions/subject.mjs +121 -0
- package/engine/conditions/text.mjs +44 -0
- package/engine/conditions/values.mjs +32 -0
- package/engine/dev-tool-search.mjs +213 -0
- package/engine/executor.mjs +93 -31
- package/engine/fillers.mjs +23 -16
- package/engine/instruction-hint.mjs +39 -23
- package/engine/layers.mjs +171 -77
- package/engine/order.mjs +20 -0
- package/engine/predicates.mjs +2 -753
- package/engine/rule-engine.mjs +40 -0
- package/engine/rule-runtime.mjs +110 -0
- package/engine/rule-spec.mjs +187 -0
- package/engine/schema.mjs +32 -11
- package/engine/session-var-tools.mjs +3 -6
- package/engine/shared.mjs +1 -1
- package/engine/st-render.mjs +8 -4
- package/engine/strategies.mjs +2 -235
- package/engine/subagent-tool-policy-core.mjs +7 -7
- package/engine/subagent-tool-policy.mjs +45 -20
- package/engine/tool-config-engine.mjs +53 -32
- package/engine/trigger-editor-meta.mjs +2 -21
- package/engine/trigger-spec.mjs +49 -133
- package/engine/world-book-tools.mjs +3 -6
- package/lib/client.js +10444 -10886
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +265 -361
- package/lib/index.mjs +1559 -2026
- package/lib/migrate-rules.d.mts +1 -0
- package/lib/migrate-rules.mjs +39 -0
- package/lib/module-package-CHPFweqK.mjs +2 -0
- package/lib/module-package-Cnd1_mwx.mjs +2872 -0
- package/lib/{paths-CY5psBqE.d.mts → paths-CyP0IkaM.d.mts} +388 -231
- package/lib/preset-transfer.d.mts +2 -2
- package/lib/preset-transfer.mjs +3 -2
- package/lib/{preset-package-BnJqrU4R.mjs → rules-migration-DJ6vASB5.mjs} +13825 -15283
- package/modules/ponytail/module.yml +406 -0
- package/modules/skill-surface/module.yml +74 -0
- package/modules/tool-surface/module.yml +67 -0
- package/package.json +50 -80
- package/templates/10-pre-step.yml +26 -22
- package/templates/14-first-turn-anchor.yml +25 -21
- package/templates/15-guide-auto.yml +34 -19
- package/templates/16-custom-fallback.yml +27 -15
- package/templates/18-placeholder-env-facts.yml +23 -14
- package/templates/20-system-section.yml +20 -11
- package/templates/30-runtime-context.yml +13 -7
- package/templates/40-agent-request.yml +11 -8
- package/templates/50-llm-stream.yml +14 -6
- package/templates/60-tool-pipeline.yml +15 -9
- package/templates/65-turn-stop.yml +19 -12
- package/templates/66-subagent-start.yml +12 -7
- package/templates/70-subagent-maintenance.yml +35 -29
- package/templates/75-subagent-end.yml +10 -5
- package/templates/80-tool-surface.yml +66 -0
- package/templates/policies/legacy-defaults.yml +34 -0
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/engine/compositions/source/local/prompt-config-engine.yml +0 -7
- package/engine/prompt-config-engine.mjs +0 -104
- package/preset/pt-cordis/preset.yml +0 -43
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +0 -29
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +0 -501
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +0 -38
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +0 -60
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +0 -13
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +0 -5
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +0 -22
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +0 -3
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +0 -2
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +0 -15
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +0 -8
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +0 -7
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +0 -84
- package/preset/pt-custom/preset.yml +0 -15
- package/preset/pt-minimal/preset.yml +0 -27
- package/preset/pt-ptc/preset.yml +0 -45
- package/preset/pt-standard/preset.yml +0 -43
package/engine/layers.mjs
CHANGED
|
@@ -18,6 +18,8 @@ import { KNOWN_STRATEGIES } from './schema.mjs'
|
|
|
18
18
|
import { interpolateVariables, stripUnresolvedRefs, RUNTIME_FACTS, runtimeFactValue } from './interpolate.mjs'
|
|
19
19
|
import { getSessionVar, sessionVarsSnapshot } from './session-vars.mjs'
|
|
20
20
|
import { conditionHit, lastAssistantText, subagentTextOf, toolArgsText } from './condition.mjs'
|
|
21
|
+
import { compareConfigSequence, compareTextPlacement } from './order.mjs'
|
|
22
|
+
import { ruleFrame, ruleMatches } from './conditions/evaluation.mjs'
|
|
21
23
|
|
|
22
24
|
const name = 'prompt-config-engine'
|
|
23
25
|
// 同一宿主会话共享投递账本,避免预设作用域重挂后重投同一次结束通知。
|
|
@@ -66,7 +68,7 @@ function officialAliasOf(name) {
|
|
|
66
68
|
* 非法官方名(中文/大写/连字符)改写成确定性别名并改写文本引用;注册失败的名字退回静态解析。
|
|
67
69
|
* @returns 各配置独立的引用白名单与别名;同一作用域内等价绑定只注册一次。
|
|
68
70
|
*/
|
|
69
|
-
function registerOfficialVariables(ctx, configs, warnOnce) {
|
|
71
|
+
function registerOfficialVariables(ctx, configs, warnOnce, keep) {
|
|
70
72
|
const registries = new Map(configs.map((config) => [config, { protect: new Set(), alias: new Map(), registered: new Set() }]))
|
|
71
73
|
const systemPrompt = getService(ctx, 'systemPrompt')
|
|
72
74
|
if (systemPrompt === undefined || typeof systemPrompt.variable !== 'function') return registries
|
|
@@ -109,7 +111,7 @@ function registerOfficialVariables(ctx, configs, warnOnce) {
|
|
|
109
111
|
usedNames.add(official)
|
|
110
112
|
const source = isFact ? name.toLowerCase() : name
|
|
111
113
|
const declaredValue = declared ? String(config.variables[name] ?? '') : undefined
|
|
112
|
-
|
|
114
|
+
keep(systemPrompt.variable(official, (context) => {
|
|
113
115
|
const session = context?.agent?.session
|
|
114
116
|
const override = getSessionVar(session, source)
|
|
115
117
|
const value = override !== undefined ? String(override) : declaredValue ?? runtimeFactValue(source, session) ?? ''
|
|
@@ -160,30 +162,33 @@ function officialChannelText(text, label, registry, warnOnce) {
|
|
|
160
162
|
return result.text
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
/**
|
|
165
|
+
/** 只合并排序后同来源、同官方档位和位置的连续 merged 卡,不跨过穿插段。 */
|
|
164
166
|
function textLayerGroups(configs) {
|
|
165
|
-
const sorted = [...configs].sort(
|
|
167
|
+
const sorted = [...configs].sort(compareTextPlacement)
|
|
166
168
|
const groups = []
|
|
167
|
-
const index = new Map()
|
|
168
169
|
for (const config of sorted) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (at === undefined) {
|
|
176
|
-
at = groups.length
|
|
177
|
-
index.set(key, at)
|
|
178
|
-
groups.push([])
|
|
179
|
-
}
|
|
180
|
-
groups[at].push(config)
|
|
170
|
+
const previous = groups.at(-1)
|
|
171
|
+
const base = previous?.[0]
|
|
172
|
+
if (config.mergeMode === 'merged' && base?.mergeMode === 'merged'
|
|
173
|
+
&& config.sourceModuleId === base.sourceModuleId
|
|
174
|
+
&& config.position === base.position && config.order === base.order) previous.push(config)
|
|
175
|
+
else groups.push([config])
|
|
181
176
|
}
|
|
182
177
|
return groups
|
|
183
178
|
}
|
|
184
179
|
|
|
180
|
+
/** 官方同 order 按 name 排序:默认名编码安全整数序号,显式名仍由作者拥有。 */
|
|
181
|
+
function textRegistrationName(config, field) {
|
|
182
|
+
const explicit = config.params?.[field]
|
|
183
|
+
if (typeof explicit === 'string' && explicit.length > 0) return explicit
|
|
184
|
+
if (config.sourceModuleId === undefined) return config.id
|
|
185
|
+
// 非负安全整数最多 16 位,跨过四位文件前缀后仍保持数值顺序。
|
|
186
|
+
const sequence = String(config.sequence).padStart(16, '0')
|
|
187
|
+
return `prompt-tool:${sequence}:${config.sourceModuleId}:${config.id}`
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
/** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
|
|
186
|
-
function wireSystemSections(ctx, configs, registry, warnOnce) {
|
|
191
|
+
function wireSystemSections(ctx, configs, registry, warnOnce, keep) {
|
|
187
192
|
const disposers = []
|
|
188
193
|
const systemPrompt = getService(ctx, 'systemPrompt')
|
|
189
194
|
if (systemPrompt === undefined || typeof systemPrompt.section !== 'function') {
|
|
@@ -206,15 +211,15 @@ function wireSystemSections(ctx, configs, registry, warnOnce) {
|
|
|
206
211
|
.filter((item) => item.length > 0)
|
|
207
212
|
.join('\n\n')
|
|
208
213
|
: groupText
|
|
209
|
-
|
|
210
|
-
name:
|
|
214
|
+
keep(systemPrompt.section({
|
|
215
|
+
name: textRegistrationName(base, 'sectionName'),
|
|
211
216
|
order: base.order,
|
|
212
217
|
text,
|
|
213
218
|
...(base.params?.complete === true ? { complete: true } : {}),
|
|
214
219
|
}), `${name}: section ${base.id}`)
|
|
215
220
|
// suppressRuntimeContext 抑制该 scope 的动态 runtime-context 快照(多段重复调用幂等)。
|
|
216
221
|
if (base.params?.suppressRuntimeContext === true) {
|
|
217
|
-
|
|
222
|
+
keep(systemPrompt.suppressRuntimeContext(), `${name}: suppressRuntimeContext ${base.id}`)
|
|
218
223
|
}
|
|
219
224
|
} catch (error) {
|
|
220
225
|
warnOnce(`${name}: system-section config ${base.id} failed: ${String(error?.message ?? error)}`)
|
|
@@ -223,8 +228,60 @@ function wireSystemSections(ctx, configs, registry, warnOnce) {
|
|
|
223
228
|
return disposers
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
async function resolvedContextText(ctx, config, context, registry, warnOnce) {
|
|
232
|
+
const agent = context.agent
|
|
233
|
+
const session = agent?.session
|
|
234
|
+
const resolved = await config.resolve({ ctx, agent, session, signal: context.signal, decision: { kind: 'ok', messages: [] }, messages: [] })
|
|
235
|
+
if (context.signal?.aborted || resolved == null) return ''
|
|
236
|
+
const variables = { ...config.variables, ...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}) }
|
|
237
|
+
const rendered = config.texts.length > 0 ? interpolateVariables(config.texts.join('\n\n'), variables, session)
|
|
238
|
+
: typeof resolved.text === 'string' ? interpolateVariables(resolved.text, variables, session) : ''
|
|
239
|
+
return officialChannelText(rendered, `runtime-context ${config.id}`, registry, warnOnce)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 条件化文本保留官方排序占位;仅在本次真实 assembly 填充,不缓存 context 身份。 */
|
|
243
|
+
function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on) {
|
|
244
|
+
if (!configs.length) return []
|
|
245
|
+
const systemPrompt = getService(ctx, 'systemPrompt')
|
|
246
|
+
if (systemPrompt === undefined) { warnOnce(`${name}: systemPrompt unavailable`); return [] }
|
|
247
|
+
const variable = `pt_rule_${newMessageId('text').replace(/[^a-z0-9_]/gi, '_').toLowerCase()}`
|
|
248
|
+
const marker = `{{${variable}}}`
|
|
249
|
+
keep(systemPrompt.variable(variable, () => ''), `${name}: conditional text marker`)
|
|
250
|
+
const bindings = []
|
|
251
|
+
for (const layer of ['system-section', 'runtime-context']) for (const group of textLayerGroups(configs.filter(config => config.layer === layer))) {
|
|
252
|
+
const section = layer === 'system-section'
|
|
253
|
+
const base = group[0]
|
|
254
|
+
const entryName = textRegistrationName(base, section ? 'sectionName' : 'contextName')
|
|
255
|
+
keep(systemPrompt[section ? 'section' : 'context']({ name: entryName, order: base.order, text: marker }), `${name}: conditional ${entryName}`)
|
|
256
|
+
bindings.push({ group, name: entryName, collection: section ? 'sections' : 'contexts' })
|
|
257
|
+
}
|
|
258
|
+
let active = true
|
|
259
|
+
keep(() => { active = false }, `${name}: conditional text lifecycle`)
|
|
260
|
+
return [on('system-prompt/assemble', async (assembly, context, next, invocation) => {
|
|
261
|
+
const frame = invocation ?? ruleFrame('system-prompt/assemble', [assembly, context], warnOnce)
|
|
262
|
+
for (const binding of bindings) {
|
|
263
|
+
const entry = assembly[binding.collection].find(item => item.name === binding.name && item.text === marker)
|
|
264
|
+
if (entry === undefined) continue
|
|
265
|
+
entry.text = ''
|
|
266
|
+
const blocks = []
|
|
267
|
+
for (const config of binding.group) {
|
|
268
|
+
if (!active || context.signal?.aborted || !ruleMatches(config.rule, frame) || !matchesAgentScope(config, context.agent)) continue
|
|
269
|
+
try {
|
|
270
|
+
const text = config.layer === 'runtime-context' && (config.strategy === 'placeholder' || !KNOWN_STRATEGIES.has(config.strategy))
|
|
271
|
+
? await resolvedContextText(ctx, config, context, registry.get(config), warnOnce)
|
|
272
|
+
: configText(config, registry, warnOnce, context)
|
|
273
|
+
if (text.length) blocks.push(text)
|
|
274
|
+
} catch (error) { warnOnce(`${name}: rule text ${config.id} failed: ${String(error?.message ?? error)}`) }
|
|
275
|
+
}
|
|
276
|
+
if (active && !context.signal?.aborted) entry.text = blocks.join('\n\n')
|
|
277
|
+
}
|
|
278
|
+
return next()
|
|
279
|
+
})]
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
226
283
|
/** runtime-context:注册运行时上下文;静态文本支持 merged,动态策略在官方 assembly waterfall 中填充。 */
|
|
227
|
-
function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
284
|
+
function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on) {
|
|
228
285
|
const disposers = []
|
|
229
286
|
const systemPrompt = getService(ctx, 'systemPrompt')
|
|
230
287
|
if (systemPrompt === undefined || typeof systemPrompt.context !== 'function') {
|
|
@@ -243,8 +300,8 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
243
300
|
const render = context => group.map(config => configText(config, registry, warnOnce, context)).filter(item => item.length > 0).join('\n\n')
|
|
244
301
|
const text = dynamic ? render : render()
|
|
245
302
|
if (!dynamic && text.length === 0) continue
|
|
246
|
-
|
|
247
|
-
name:
|
|
303
|
+
keep(systemPrompt.context({
|
|
304
|
+
name: textRegistrationName(base, 'contextName'),
|
|
248
305
|
order: base.order,
|
|
249
306
|
text,
|
|
250
307
|
}), `${name}: context ${base.id}`)
|
|
@@ -255,18 +312,18 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
255
312
|
// 官方 provider 必须同步:先注册可渲染为空的私有变量占位,再在 waterfall 中填充。
|
|
256
313
|
// 标记随官方排序/遮蔽进入本次 assembly,无会话缓存,也不依赖调用方 context 的对象身份。
|
|
257
314
|
const placeholders = configs.filter(needsResolver)
|
|
258
|
-
.sort(
|
|
315
|
+
.sort(compareTextPlacement)
|
|
259
316
|
if (placeholders.length === 0) return disposers
|
|
260
317
|
const slotVariable = `pt_runtime_${newMessageId('context').replace(/[^a-z0-9_]/gi, '_').toLowerCase()}`
|
|
261
318
|
const slotText = `{{${slotVariable}}}`
|
|
262
319
|
const registered = new Map()
|
|
263
320
|
let active = true
|
|
264
|
-
|
|
265
|
-
|
|
321
|
+
keep(() => { active = false }, `${name}: runtime-context lifecycle`)
|
|
322
|
+
keep(systemPrompt.variable(slotVariable, () => ''), `${name}: runtime-context placeholder`)
|
|
266
323
|
for (const config of placeholders) {
|
|
267
324
|
try {
|
|
268
|
-
const contextName =
|
|
269
|
-
|
|
325
|
+
const contextName = textRegistrationName(config, 'contextName')
|
|
326
|
+
keep(systemPrompt.context({
|
|
270
327
|
name: contextName,
|
|
271
328
|
order: config.order,
|
|
272
329
|
text: slotText,
|
|
@@ -276,22 +333,15 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce) {
|
|
|
276
333
|
warnOnce(`${name}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`)
|
|
277
334
|
}
|
|
278
335
|
}
|
|
279
|
-
disposers.push(
|
|
336
|
+
disposers.push(on('system-prompt/assemble', async (assembly, context, next) => {
|
|
280
337
|
const entries = assembly.contexts.filter(entry => registered.has(entry.name) && entry.text === slotText)
|
|
281
338
|
for (const entry of entries) {
|
|
282
339
|
const config = registered.get(entry.name)
|
|
283
340
|
entry.text = ''
|
|
284
341
|
if (!active || context.signal?.aborted) continue
|
|
285
342
|
try {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const resolved = await config.resolve({ ctx, agent, session, signal: context.signal, decision: { kind: 'ok', messages: [] }, messages: [] })
|
|
289
|
-
if (!active || context.signal?.aborted || resolved === null || resolved === undefined) continue
|
|
290
|
-
const variables = { ...config.variables, ...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}) }
|
|
291
|
-
const rendered = config.texts.length > 0
|
|
292
|
-
? interpolateVariables(config.texts.join('\n\n'), variables, session)
|
|
293
|
-
: typeof resolved.text === 'string' ? interpolateVariables(resolved.text, variables, session) : ''
|
|
294
|
-
entry.text = officialChannelText(rendered, `runtime-context ${config.id}`, registry.get(config), warnOnce)
|
|
343
|
+
const text = await resolvedContextText(ctx, config, context, registry.get(config), warnOnce)
|
|
344
|
+
if (active && !context.signal?.aborted) entry.text = text
|
|
295
345
|
} catch (error) {
|
|
296
346
|
warnOnce(`${name}: runtime-context ${config.id} resolve failed: ${String(error?.message ?? error)}`)
|
|
297
347
|
}
|
|
@@ -336,21 +386,22 @@ export function applyAgentRequestParams(params, base) {
|
|
|
336
386
|
}
|
|
337
387
|
|
|
338
388
|
/** agent-request:对冻结的 LlmCallConfig 做浅合并 / 整体替换 / 按值条件删键。 */
|
|
339
|
-
function wireAgentRequests(ctx, configs, warnOnce) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
389
|
+
function wireAgentRequests(ctx, configs, warnOnce, on) {
|
|
390
|
+
if (configs.length === 0) return []
|
|
391
|
+
const ordered = configs.some(config => config.sequence !== undefined) ? configs : [...configs].reverse()
|
|
392
|
+
return [on('agent/request', async (payload, next, invocation) => {
|
|
393
|
+
let result = await next()
|
|
394
|
+
const frame = invocation ?? ruleFrame('agent/request', [payload], warnOnce)
|
|
395
|
+
// 模块请求在 next 返回后按序合并;无文件来源的独立调用保持原回栈顺序。
|
|
396
|
+
for (const config of ordered) {
|
|
344
397
|
try {
|
|
345
|
-
if (
|
|
346
|
-
return applyAgentRequestParams(config.params, base)
|
|
398
|
+
if (ruleMatches(config.rule, frame) && matchesAgentScope(config, payload?.agent)) result = applyAgentRequestParams(config.params, result)
|
|
347
399
|
} catch (error) {
|
|
348
400
|
warnOnce(`${name}: agent-request config ${config.id} failed: ${String(error?.message ?? error)}`)
|
|
349
|
-
return base
|
|
350
401
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
402
|
+
}
|
|
403
|
+
return result
|
|
404
|
+
})]
|
|
354
405
|
}
|
|
355
406
|
|
|
356
407
|
/** 把流替换为提示词配置文本的最小合法 chunk 序列。 */
|
|
@@ -361,11 +412,15 @@ async function* replacedStream(text) {
|
|
|
361
412
|
}
|
|
362
413
|
|
|
363
414
|
/** llm-stream:pass 透传;replace 用提示词配置文本替代整个模型流。 */
|
|
364
|
-
function wireLlmStreams(ctx, configs, warnOnce) {
|
|
415
|
+
function wireLlmStreams(ctx, configs, warnOnce, on) {
|
|
365
416
|
const disposers = []
|
|
366
417
|
for (const config of configs) {
|
|
367
|
-
disposers.push(
|
|
418
|
+
disposers.push(on('llm/stream', (options, next, invocation) => {
|
|
368
419
|
try {
|
|
420
|
+
const frame = invocation ?? ruleFrame('llm/stream', [options], warnOnce)
|
|
421
|
+
const agent = options?.sessionId === undefined ? undefined : getService(ctx, 'agents')?.get?.(options.sessionId)
|
|
422
|
+
frame.subject.agent = agent
|
|
423
|
+
if (!ruleMatches(config.rule, frame)) return next()
|
|
369
424
|
const mode = config.params?.mode ?? 'pass'
|
|
370
425
|
if (mode === 'replace' && config.texts.length > 0 && matchesModel(config.modelScope, options?.model)) {
|
|
371
426
|
return replacedStream(config.texts.join('\n\n'))
|
|
@@ -382,13 +437,21 @@ function wireLlmStreams(ctx, configs, warnOnce) {
|
|
|
382
437
|
|
|
383
438
|
/** 条件判定的匹配器与取文本逻辑由 condition.mjs 承载(pre-step 与其他层共用)。 */
|
|
384
439
|
|
|
385
|
-
/**
|
|
440
|
+
/**
|
|
441
|
+
* 插件来源的 user 消息:与 anchor-turn / progress-reminder 同一形状。
|
|
442
|
+
*
|
|
443
|
+
* `kind` 必须是**生产者名**而不是裸 `plugin`:会话格式 v4 把 `{ kind: 'plugin', plugin }`
|
|
444
|
+
* 列为退役包装并直接拒绝(`dsh-session-format-v3-to-v4` 的 `assertV4MessageSources` /
|
|
445
|
+
* `assertV4SourceRowAdmission` 一律抛 `format v4 message requires a producer-owned source kind`),
|
|
446
|
+
* 迁移只在读旧日志时把 `plugin` 折进 kind。写 `kind: 'plugin'` 会让本次 splice 被 codec
|
|
447
|
+
* 拒绝、连 `agent/inbox/spliced` 都落不了盘——子代理启动因此整段失败。
|
|
448
|
+
*/
|
|
386
449
|
export function pluginMessage(prefix, text, summary) {
|
|
387
450
|
return {
|
|
388
451
|
id: newMessageId(prefix),
|
|
389
452
|
role: 'user',
|
|
390
453
|
content: [{ type: 'text', text }],
|
|
391
|
-
source: { kind:
|
|
454
|
+
source: { kind: `plugin:${name}`, plugin: name, form: 'notice', summary },
|
|
392
455
|
}
|
|
393
456
|
}
|
|
394
457
|
|
|
@@ -406,13 +469,14 @@ function layerText(config, agent, warnOnce) {
|
|
|
406
469
|
}
|
|
407
470
|
|
|
408
471
|
/** tool-pipeline:pre-execute 判定、execute 包装、post-execute 结果替换/阻断。 */
|
|
409
|
-
function wireToolPipelines(ctx, configs, warnOnce) {
|
|
472
|
+
function wireToolPipelines(ctx, configs, warnOnce, on) {
|
|
410
473
|
const disposers = []
|
|
411
474
|
for (const config of configs) {
|
|
412
475
|
const names = parseToolNames(config.params?.toolNames)
|
|
413
476
|
const matchesTool = (exec) => names.length === 0 || names.includes(exec?.name)
|
|
414
|
-
disposers.push(
|
|
477
|
+
disposers.push(on('tools/pre-execute', async (exec, next, invocation) => {
|
|
415
478
|
try {
|
|
479
|
+
if (!ruleMatches(config.rule, invocation ?? ruleFrame('tools/pre-execute', [exec], warnOnce))) return next()
|
|
416
480
|
if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
|
|
417
481
|
if (!conditionHit(config, { argsText: toolArgsText(exec?.arguments) })) return next()
|
|
418
482
|
const decision = config.params?.preDecision ?? 'allow'
|
|
@@ -427,8 +491,9 @@ function wireToolPipelines(ctx, configs, warnOnce) {
|
|
|
427
491
|
return next()
|
|
428
492
|
}
|
|
429
493
|
}))
|
|
430
|
-
disposers.push(
|
|
494
|
+
disposers.push(on('tools/post-execute', async (exec, result, next, invocation) => {
|
|
431
495
|
try {
|
|
496
|
+
if (!ruleMatches(config.rule, invocation ?? ruleFrame('tools/post-execute', [exec, result], warnOnce))) return next()
|
|
432
497
|
if (!matchesTool(exec) || !matchesAgentScope(config, exec?.agent)) return next()
|
|
433
498
|
if (!conditionHit(config, { argsText: toolArgsText(exec?.arguments), resultText: extractText(result) })) return next()
|
|
434
499
|
const action = config.params?.postAction ?? 'accept'
|
|
@@ -515,14 +580,19 @@ export function createTurnStopBudget() {
|
|
|
515
580
|
}
|
|
516
581
|
|
|
517
582
|
/** turn-stop:命中条件时阻止本轮停止并强制续跑一步,上限在引擎内。 */
|
|
518
|
-
function wireTurnStops(ctx, configs, warnOnce) {
|
|
583
|
+
function wireTurnStops(ctx, configs, warnOnce, on) {
|
|
519
584
|
const disposers = []
|
|
520
585
|
if (configs.length === 0) return disposers
|
|
521
|
-
const
|
|
586
|
+
const budgets = new Map()
|
|
522
587
|
|
|
523
588
|
for (const config of configs) {
|
|
524
|
-
|
|
589
|
+
const source = config.sourceModuleId ?? ''
|
|
590
|
+
if (!budgets.has(source)) budgets.set(source, createTurnStopBudget())
|
|
591
|
+
const budget = budgets.get(source)
|
|
592
|
+
disposers.push(on('agent/turn-stopping', (payload = {}, _next, invocation) => {
|
|
593
|
+
const { agent, turn } = payload
|
|
525
594
|
try {
|
|
595
|
+
if (!ruleMatches(config.rule, invocation ?? ruleFrame('agent/turn-stopping', [payload], warnOnce))) return
|
|
526
596
|
const session = agent?.session
|
|
527
597
|
if (session?.id === undefined || typeof agent.steer !== 'function') return
|
|
528
598
|
if (!matchesAgentScope(config, agent)) return
|
|
@@ -560,33 +630,39 @@ function mainSessionForChild(ctx, id) {
|
|
|
560
630
|
}
|
|
561
631
|
|
|
562
632
|
/** 子代理生命周期:启动注入子代理;结束默认观察,可显式给所属主会话投递上下文。 */
|
|
563
|
-
function wireSubagentEvents(ctx, configs, warnOnce) {
|
|
633
|
+
function wireSubagentEvents(ctx, configs, warnOnce, on) {
|
|
564
634
|
const disposers = []
|
|
565
635
|
const startConfigs = configs.filter((config) => config.layer === 'subagent-start')
|
|
566
636
|
const endConfigs = configs.filter((config) => config.layer === 'subagent-end')
|
|
567
|
-
const injectMain = endConfigs.some((config) => config.params?.action === 'inject-main')
|
|
568
637
|
// B4 保留原状(不迁 `sessionState`):键是 `runId`(运行标识)而不是 session,不属于
|
|
569
638
|
// 「按会话索引」的会话态,套统一接口会把语义挪到错误的键上;它的 `delete(最旧)` 也是与
|
|
570
639
|
// `clear()` 不同的另一档淘汰策略,无等价证明前保留。
|
|
571
640
|
const runs = new Map()
|
|
572
|
-
if (
|
|
641
|
+
if (endConfigs.length > 0) {
|
|
573
642
|
disposers.push(ctx.on('subagent/start', (info) => {
|
|
574
643
|
if (typeof info?.runId !== 'string' || typeof info?.id !== 'string') return
|
|
644
|
+
const child = getService(ctx, 'agents')?.get?.(info.id)
|
|
645
|
+
const session = child?.session ?? getService(ctx, 'sessions')?.get?.(info.id)
|
|
646
|
+
if (session === undefined) return
|
|
575
647
|
const mainId = mainSessionForChild(ctx, info.id)
|
|
576
|
-
if (mainId === undefined) return
|
|
577
648
|
// ponytail: 有界运行记录;极端并发超过上限时,结束事件仍可通过存活会话血缘定位。
|
|
578
649
|
if (runs.size >= MAX_TRACKED_SESSIONS) runs.delete(runs.keys().next().value)
|
|
579
|
-
runs.set(info.runId, { id: info.id, mainId,
|
|
650
|
+
runs.set(info.runId, { id: info.id, mainId, agent: child, session, model: child?.options?.model })
|
|
580
651
|
}))
|
|
581
|
-
|
|
652
|
+
const clearRuns = () => runs.clear()
|
|
653
|
+
disposers.push(clearRuns)
|
|
654
|
+
ctx.effect?.(() => clearRuns)
|
|
582
655
|
}
|
|
583
656
|
if (startConfigs.length > 0) {
|
|
584
|
-
disposers.push(
|
|
657
|
+
disposers.push(on('subagent/start', (info, _next, invocation) => {
|
|
585
658
|
try {
|
|
586
659
|
const child = getService(ctx, 'agents')?.get?.(info?.id)
|
|
587
660
|
if (child === undefined) return
|
|
661
|
+
const frame = invocation ?? ruleFrame('subagent/start', [info], warnOnce)
|
|
662
|
+
frame.subject.agent = child
|
|
588
663
|
const subagentText = subagentTextOf(info)
|
|
589
664
|
for (const config of startConfigs) {
|
|
665
|
+
if (!ruleMatches(config.rule, frame)) continue
|
|
590
666
|
if (!matchesAgentScope(config, child)) continue
|
|
591
667
|
if (!conditionHit(config, { subagentText })) continue
|
|
592
668
|
if (typeof child.inject !== 'function') continue
|
|
@@ -600,14 +676,19 @@ function wireSubagentEvents(ctx, configs, warnOnce) {
|
|
|
600
676
|
}))
|
|
601
677
|
}
|
|
602
678
|
if (endConfigs.length > 0) {
|
|
603
|
-
disposers.push(
|
|
679
|
+
disposers.push(on('subagent/end', (info, _next, invocation) => {
|
|
604
680
|
try {
|
|
605
681
|
const child = getService(ctx, 'agents')?.get?.(info?.id)
|
|
606
682
|
const recorded = runs.get(info?.runId)
|
|
607
683
|
runs.delete(info?.runId)
|
|
608
684
|
if (recorded !== undefined && recorded.id !== info?.id) return
|
|
609
685
|
const subagentText = subagentTextOf(info)
|
|
686
|
+
const frame = invocation ?? ruleFrame('subagent/end', [info], warnOnce)
|
|
687
|
+
frame.subject.agent = child ?? recorded?.agent
|
|
688
|
+
frame.subject.session = child?.session ?? recorded?.session ?? getService(ctx, 'sessions')?.get?.(info?.id)
|
|
689
|
+
frame.subject.model = child?.options?.model ?? recorded?.model
|
|
610
690
|
for (const config of endConfigs) {
|
|
691
|
+
if (!ruleMatches(config.rule, frame)) continue
|
|
611
692
|
if (!matchesModel(config.modelScope, child?.options?.model ?? recorded?.model)) continue
|
|
612
693
|
if (!conditionHit(config, { subagentText })) continue
|
|
613
694
|
if (config.params?.action !== 'inject-main') {
|
|
@@ -645,17 +726,30 @@ function wireSubagentEvents(ctx, configs, warnOnce) {
|
|
|
645
726
|
* @returns 聚合 disposer:回收本次接线显式创建的 waterfall 监听器;段/上下文/
|
|
646
727
|
* 变量注册走 keepDisposer(随 ctx fiber 释放),不在本函数的回收面内。
|
|
647
728
|
*/
|
|
648
|
-
export function wireLayers(ctx, configs, warnOnce) {
|
|
729
|
+
export function wireLayers(ctx, configs, warnOnce, options = {}) {
|
|
730
|
+
configs = [...configs].sort(compareConfigSequence)
|
|
731
|
+
const owned = []
|
|
732
|
+
const keep = (dispose, label) => {
|
|
733
|
+
if (typeof dispose !== 'function') return
|
|
734
|
+
let active = true
|
|
735
|
+
const release = () => { if (active) { active = false; dispose() } }
|
|
736
|
+
owned.push(release); keepDisposer(ctx, release, label)
|
|
737
|
+
}
|
|
738
|
+
const on = options.on ?? ((...args) => ctx.on(...args))
|
|
649
739
|
// 官方插值两层共享一份变量注册:运行时事实按 assembly 求值,非法名走别名改写。
|
|
650
|
-
const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === 'system-section' || config.layer === 'runtime-context'), warnOnce)
|
|
740
|
+
const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === 'system-section' || config.layer === 'runtime-context'), warnOnce, keep)
|
|
741
|
+
const conditional = configs.filter(config => typeof config.rule?.when === 'function' && ['system-section', 'runtime-context'].includes(config.layer))
|
|
742
|
+
const regular = configs.filter(config => !conditional.includes(config))
|
|
651
743
|
const registered = [
|
|
652
|
-
wireSystemSections(ctx,
|
|
653
|
-
wireRuntimeContexts(ctx,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
744
|
+
wireSystemSections(ctx, regular.filter((config) => config.layer === 'system-section'), registry, warnOnce, keep),
|
|
745
|
+
wireRuntimeContexts(ctx, regular.filter((config) => config.layer === 'runtime-context'), registry, warnOnce, keep, on),
|
|
746
|
+
wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on),
|
|
747
|
+
wireAgentRequests(ctx, configs.filter((config) => config.layer === 'agent-request'), warnOnce, on),
|
|
748
|
+
wireLlmStreams(ctx, configs.filter((config) => config.layer === 'llm-stream'), warnOnce, on),
|
|
749
|
+
wireToolPipelines(ctx, configs.filter((config) => config.layer === 'tool-pipeline'), warnOnce, on),
|
|
750
|
+
wireTurnStops(ctx, configs.filter((config) => config.layer === 'turn-stop'), warnOnce, on),
|
|
751
|
+
wireSubagentEvents(ctx, configs.filter((config) => config.layer === 'subagent-start' || config.layer === 'subagent-end'), warnOnce, on),
|
|
752
|
+
owned,
|
|
659
753
|
].flat().filter((disposer) => typeof disposer === 'function')
|
|
660
754
|
return () => {
|
|
661
755
|
for (const dispose of registered.splice(0)) {
|
package/engine/order.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 加载器私有元数据:不进入 YAML、公共配置或客户端载荷。 */
|
|
2
|
+
export const FILE_SEQUENCE = Symbol('prompt-tool:file-sequence')
|
|
3
|
+
|
|
4
|
+
const compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0
|
|
5
|
+
|
|
6
|
+
/** 模块配置按持久序号执行;无物化来源的独立调用保留原 order 语义。 */
|
|
7
|
+
export function compareConfigSequence(a, b) {
|
|
8
|
+
const difference = (a.sequence ?? a.order ?? 0) - (b.sequence ?? b.order ?? 0)
|
|
9
|
+
if (difference !== 0) return difference
|
|
10
|
+
if (a.sourceModuleId === undefined && b.sourceModuleId === undefined) return 0
|
|
11
|
+
return compareText(a.sourceModuleId ?? '', b.sourceModuleId ?? '')
|
|
12
|
+
|| compareText(a.ruleId ?? a.id, b.ruleId ?? b.id)
|
|
13
|
+
|| (a.ruleActionIndex ?? 0) - (b.ruleActionIndex ?? 0)
|
|
14
|
+
|| compareText(a.id, b.id)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 官方文本段的定位仍由 order 决定;序号只提供同位置的稳定顺序。 */
|
|
18
|
+
export function compareTextPlacement(a, b) {
|
|
19
|
+
return a.order - b.order || compareConfigSequence(a, b)
|
|
20
|
+
}
|