dsh-plugin-prompt-tool 0.7.2 → 0.8.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 +185 -46
- package/engine/THIRD_PARTY_LICENSES +25 -0
- package/engine/actions.mjs +931 -0
- package/engine/anchor-match.mjs +6 -3
- package/engine/character-tools.mjs +11 -0
- package/engine/classify-task.mjs +18 -0
- package/engine/compaction-epoch.mjs +41 -20
- package/engine/compositions/library/agent-instructions.yml +9 -0
- package/engine/compositions/library/command-goal.yml +2 -6
- package/engine/compositions/library/compaction.yml +2 -32
- package/engine/compositions/library/delegation-ptc.yml +58 -0
- package/engine/compositions/library/delegation.yml +5 -38
- package/engine/compositions/library/persistent-shell.yml +4 -13
- package/engine/compositions/library/planning.yml +8 -23
- package/engine/compositions/library/skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/skill-filesystem.yml +2 -8
- package/engine/compositions/library/tool-ask-user.yml +2 -3
- package/engine/compositions/library/tool-bash.yml +4 -12
- package/engine/compositions/library/tool-cordis.yml +7 -0
- package/engine/compositions/library/tool-fs-search.yml +2 -11
- package/engine/compositions/library/tool-fs.yml +2 -5
- package/engine/compositions/library/tool-goal.yml +2 -6
- package/engine/compositions/library/tool-jobs.yml +2 -18
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +8 -0
- package/engine/compositions/library/tool-plugin-manager.yml +8 -0
- package/engine/compositions/library/tool-present.yml +7 -0
- package/engine/compositions/library/tool-presentation.yml +9 -0
- package/engine/compositions/library/tool-pwsh.yml +2 -6
- package/engine/compositions/library/tool-skill.yml +7 -0
- package/engine/compositions/library/tool-todo.yml +2 -4
- package/engine/compositions/library/tool-web.yml +2 -1
- package/engine/compositions/source/local/character-tools.yml +5 -0
- package/engine/compositions/source/local/declared-triggers.yml +12 -0
- package/engine/compositions/source/local/filesystem-editor.yml +30 -0
- package/engine/compositions/source/local/instruction-hint.yml +15 -0
- package/engine/compositions/{library/official-persistent-shell.yml → source/local/persistent-shell-posix.yml} +10 -8
- package/engine/compositions/source/local/prompt-config-engine.yml +3 -9
- package/engine/compositions/source/local/run-code-env.yml +4 -7
- package/engine/compositions/source/local/session-var-tools.yml +5 -0
- package/engine/compositions/source/local/skill-search.yml +3 -9
- package/engine/compositions/source/local/subagent-tool-policy.yml +21 -0
- package/engine/compositions/source/local/tool-bash-disabled.yml +9 -0
- package/engine/compositions/source/local/tool-config-engine.yml +8 -0
- package/engine/compositions/source/local/tool-git-bash.yml +15 -0
- package/engine/compositions/source/local/world-book-tools.yml +5 -0
- package/engine/condition.mjs +83 -0
- package/engine/declared-triggers.mjs +93 -0
- package/engine/executor.mjs +364 -185
- package/engine/fields.mjs +219 -0
- package/engine/fillers.mjs +12 -141
- package/engine/host-package.mjs +51 -0
- package/engine/instruction-hint.mjs +285 -0
- package/engine/interpolate.mjs +157 -29
- package/engine/layers.mjs +495 -75
- package/engine/predicates.mjs +753 -0
- package/engine/prompt-config-engine.mjs +63 -8
- package/engine/run-code-env.mjs +48 -31
- package/engine/schema.mjs +444 -103
- package/engine/sdk-strip.mjs +365 -0
- package/engine/session-var-tools.mjs +11 -0
- package/engine/session-vars.mjs +8 -6
- package/engine/shared.mjs +137 -0
- package/engine/skill-search.mjs +34 -21
- package/engine/st-macros.d.mts +22 -0
- package/engine/st-macros.mjs +208 -0
- package/engine/st-render.mjs +105 -0
- package/engine/st-world-book.d.mts +10 -0
- package/engine/st-world-book.mjs +259 -0
- package/engine/strategies.mjs +31 -20
- package/engine/subagent-tool-policy-core.mjs +400 -0
- package/engine/subagent-tool-policy.mjs +327 -0
- package/engine/tool-config-engine.mjs +207 -145
- package/engine/tool-definition.mjs +143 -0
- package/engine/{custom-bash.mjs → tool-git-bash.mjs} +38 -13
- package/engine/trigger-editor-meta.mjs +48 -0
- package/engine/trigger-spec.mjs +257 -0
- package/engine/trigger.mjs +304 -0
- package/engine/world-book-tools.mjs +11 -0
- package/lib/client.js +16495 -5456
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +628 -382
- package/lib/index.mjs +5391 -2942
- package/lib/paths-CY5psBqE.d.mts +1233 -0
- package/lib/preset-package-BnJqrU4R.mjs +16792 -0
- package/lib/preset-transfer.d.mts +2 -0
- package/lib/preset-transfer.mjs +2 -0
- package/package.json +91 -49
- package/preset/pt-cordis/preset.yml +43 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +29 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +501 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +38 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +60 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +13 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +22 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +3 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +2 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +15 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +8 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +7 -0
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +84 -0
- package/preset/pt-custom/preset.yml +15 -0
- package/preset/pt-minimal/preset.yml +27 -0
- package/preset/pt-ptc/preset.yml +45 -0
- package/preset/pt-standard/preset.yml +43 -0
- package/templates/14-first-turn-anchor.yml +4 -4
- package/templates/15-guide-auto.yml +3 -3
- package/templates/16-custom-fallback.yml +1 -0
- package/templates/18-placeholder-env-facts.yml +3 -3
- package/templates/20-system-section.yml +3 -2
- package/templates/65-turn-stop.yml +18 -0
- package/templates/66-subagent-start.yml +14 -0
- package/templates/75-subagent-end.yml +15 -0
- package/templates/tools/10-shell-command.yml +1 -1
- package/templates/tools/40-delegate-character.yml +1 -1
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/templates/tools/60-delegate-session-var.yml +1 -1
- package/templates/tools/70-delegate-worldbook-upsert.yml +1 -1
- package/engine/anchor-turn.mjs +0 -68
- package/engine/code-presentation.mjs +0 -103
- package/engine/compositions/library/anchor-turn.yml +0 -10
- package/engine/compositions/library/bootstrap-filesystem.yml +0 -19
- package/engine/compositions/library/code-presentation.yml +0 -8
- package/engine/compositions/library/context-gate.yml +0 -39
- package/engine/compositions/library/cot-drip.yml +0 -11
- package/engine/compositions/library/custom-bash.yml +0 -14
- package/engine/compositions/library/deliberation-gate.yml +0 -11
- package/engine/compositions/library/official-agent-instructions.yml +0 -17
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +0 -10
- package/engine/compositions/library/official-tool-bash.yml +0 -6
- package/engine/compositions/library/official-tool-cordis.yml +0 -13
- package/engine/compositions/library/official-tool-presentation.yml +0 -7
- package/engine/compositions/library/official-tool-skill.yml +0 -14
- package/engine/compositions/library/persona.yml +0 -13
- package/engine/compositions/library/prompt-config-engine.yml +0 -16
- package/engine/compositions/library/run-code-env.yml +0 -15
- package/engine/compositions/library/skill-search.yml +0 -14
- package/engine/compositions/library/str-replace-editor.yml +0 -10
- package/engine/compositions/library/tool-bootstrap.yml +0 -14
- package/engine/compositions/library/tool-config-engine.yml +0 -9
- package/engine/compositions/library/tool-filter.yml +0 -8
- package/engine/compositions/source/local/code-presentation.yml +0 -8
- package/engine/compositions/source/local/context-gate.yml +0 -36
- package/engine/compositions/source/local/custom-bash.yml +0 -11
- package/engine/compositions/source/local/str-replace-editor.yml +0 -10
- package/engine/compositions/source/local/tool-bootstrap.yml +0 -11
- package/engine/compositions/source/local/tool-filter.yml +0 -8
- package/engine/context-gate.mjs +0 -305
- package/engine/cot-drip.mjs +0 -137
- package/engine/deliberation-gate.mjs +0 -127
- package/engine/tool-bootstrap.mjs +0 -476
- package/engine/tool-filter.mjs +0 -80
- package/lib/preset-core.d.mts +0 -10
- package/lib/preset-core.mjs +0 -77
- package/lib/prompt-configs-BnQKRHVb.mjs +0 -884
- package/lib/prompt-configs-CN0sJTUL.d.mts +0 -193
- package/preset/anchored/preset.yml +0 -296
- package/preset/creative/preset.yml +0 -67
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +0 -420
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +0 -165
- package/preset/custom/preset.yml +0 -12
- package/preset/liangshen/preset.yml +0 -75
- package/preset/minimal/preset.yml +0 -45
- package/preset/ptc/preset.yml +0 -58
- package/preset/standard/preset.yml +0 -57
- package/skills/manifest.json +0 -7
- package/skills/sandboxmod/SKILL.md +0 -49
- package/skills/web ui/SKILL.md +0 -42
package/engine/executor.mjs
CHANGED
|
@@ -2,22 +2,50 @@
|
|
|
2
2
|
* executor — pre-step 消息批执行器。
|
|
3
3
|
* 职责:过滤(层/子代理/模型/晋升)→ resolve → 插值 → 去重/合并 → 落位插入。
|
|
4
4
|
* 任一提示词配置失败只跳过该提示词配置并 warnOnce,绝不让注入 bug 卡死会话。
|
|
5
|
+
*
|
|
6
|
+
* 装配路径有两条,批执行算法只有一份(runPreStepBatch):
|
|
7
|
+
* - 管理路径:宿主 prompt-tool 插件提供 `promptToolPreStep` 协调服务时,本来源
|
|
8
|
+
* (预设 mount)把配置注册给协调器,由协调器统一执行预设来源与独立指令文件来源;
|
|
9
|
+
* - 独立路径:没有协调服务(引擎被复制到无宿主的目录复用)时,本行 ctx 注册本地
|
|
10
|
+
* pre-step 监听器,只执行自身预设配置。
|
|
11
|
+
* 协调服务迟到或消失时按「先撤旧再启新」切换,任一时刻每 scope 只有一条执行路径。
|
|
5
12
|
*/
|
|
6
13
|
|
|
7
14
|
import {
|
|
15
|
+
MAX_TRACKED_SESSIONS,
|
|
8
16
|
PROMOTE_EVENTS,
|
|
9
17
|
createWarnOnce,
|
|
18
|
+
getService,
|
|
10
19
|
isDelegated,
|
|
20
|
+
keepDisposer,
|
|
11
21
|
matchesModel,
|
|
12
22
|
newMessageId,
|
|
23
|
+
sessionEvents,
|
|
13
24
|
} from './shared.mjs'
|
|
14
25
|
import { interpolateVariables } from './interpolate.mjs'
|
|
26
|
+
import { conditionHit, userMessagesText } from './condition.mjs'
|
|
15
27
|
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
16
28
|
import { wireLayers } from './layers.mjs'
|
|
17
29
|
import { sessionVarsSnapshot } from './session-vars.mjs'
|
|
30
|
+
import { selectStWorldBook } from './st-world-book.mjs'
|
|
18
31
|
|
|
19
32
|
const name = 'prompt-config-engine'
|
|
20
33
|
|
|
34
|
+
/** 已确认投递身份的进程内快路径上限;真相在持久事件流。 */
|
|
35
|
+
// ponytail: 身份条数上限防无界增长(Map 按投递身份累积);若需精确淘汰改 LRU。
|
|
36
|
+
const MAX_MEMO_CONFIGS = 4096
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 查询宿主 prompt-tool 插件的 pre-step 协调服务(ctx.get 跨 fiber 解析)。
|
|
40
|
+
* 缺失/形状不符时返回 undefined:本行回退独立执行路径。
|
|
41
|
+
*/
|
|
42
|
+
function coordinatorOf(ctx) {
|
|
43
|
+
const service = getService(ctx, 'promptToolPreStep')
|
|
44
|
+
return service !== null && typeof service === 'object' && typeof service.registerPreset === 'function'
|
|
45
|
+
? service
|
|
46
|
+
: undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
/** 事件数据兼容两层形状:event.data 本身是消息,或 event.data.message 是消息。 */
|
|
22
50
|
function eventMessage(event) {
|
|
23
51
|
const data = event?.data
|
|
@@ -32,9 +60,14 @@ function mergedIdentity(config) {
|
|
|
32
60
|
return `merged:${config.position}`
|
|
33
61
|
}
|
|
34
62
|
|
|
63
|
+
/** 去重身份:merged 组用位置命名空间,独立配置用自身身份。 */
|
|
64
|
+
function identityOf(config) {
|
|
65
|
+
return config.mergeMode === 'merged' ? mergedIdentity(config) : config.identity.value
|
|
66
|
+
}
|
|
67
|
+
|
|
35
68
|
function hasInjected(config, session) {
|
|
36
|
-
const value =
|
|
37
|
-
return (
|
|
69
|
+
const value = identityOf(config)
|
|
70
|
+
return sessionEvents(session).some((event) => {
|
|
38
71
|
const message = eventMessage(event)
|
|
39
72
|
// 双通道去重:kind(外来/第三方消息,如 context-gate 的 instruction-hint)或
|
|
40
73
|
// plugin(本引擎注入的命名空间,merged 组用 merged:<position>)。
|
|
@@ -44,57 +77,280 @@ function hasInjected(config, session) {
|
|
|
44
77
|
|
|
45
78
|
/** 当前消息批内是否已有该提示词配置注入(每轮去重)。 */
|
|
46
79
|
function hasInBatch(config, messages) {
|
|
47
|
-
const value =
|
|
80
|
+
const value = identityOf(config)
|
|
48
81
|
return messages.some((message) =>
|
|
49
82
|
message?.source?.kind === config.sourceKind || message?.source?.plugin === value)
|
|
50
83
|
}
|
|
51
84
|
|
|
52
|
-
/**
|
|
53
|
-
|
|
85
|
+
/**
|
|
86
|
+
* 按投递身份取「已确认投递」的会话集合。记账只发生在宿主真正接纳并持久化该消息之后
|
|
87
|
+
* (见 confirmDelivered):候选在瀑布外层被门控/策略剥离时不算已投递,否则晋升后
|
|
88
|
+
* (门控放行时)正文会永久缺失。集合上限按会话数截断,真相仍在持久事件流。
|
|
89
|
+
*/
|
|
90
|
+
function deliveredSessions(memo, key) {
|
|
91
|
+
let set = memo.get(key)
|
|
92
|
+
if (set === undefined) {
|
|
93
|
+
if (memo.size >= MAX_MEMO_CONFIGS) memo.clear()
|
|
94
|
+
set = new Set()
|
|
95
|
+
memo.set(key, set)
|
|
96
|
+
}
|
|
97
|
+
if (set.size >= MAX_TRACKED_SESSIONS) set.clear()
|
|
98
|
+
return set
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 记录一次宿主已接纳的注入消息(由调用方在 session/event 里转发)。
|
|
103
|
+
* plugin(本引擎身份,merged 组用 merged:<position>)与 kind(外来通道,如
|
|
104
|
+
* context-gate 的 instruction-hint)都以会话为界记账,与 hasInjected 的双通道语义一致。
|
|
105
|
+
*/
|
|
106
|
+
export function confirmDelivered(memo, session, event) {
|
|
107
|
+
if (memo === null || memo === undefined || session === null || session === undefined) return
|
|
108
|
+
const source = eventMessage(event)?.source
|
|
109
|
+
if (source === null || typeof source !== 'object') return
|
|
110
|
+
for (const field of ['plugin', 'kind']) {
|
|
111
|
+
const value = source[field]
|
|
112
|
+
if (typeof value !== 'string' || value.length === 0) continue
|
|
113
|
+
deliveredSessions(memo, `${field}:${value}`).add(session.id)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** dedupe=session:本会话是否已有该身份的已确认投递(快路径 + 持久事件真相)。 */
|
|
118
|
+
function alreadyDelivered(config, session, memo) {
|
|
119
|
+
const confirmed = (field, value) => memo.get(`${field}:${value}`)?.has(session.id) === true
|
|
120
|
+
if (confirmed('plugin', identityOf(config))) return true
|
|
121
|
+
if (typeof config.sourceKind === 'string' && confirmed('kind', config.sourceKind)) return true
|
|
122
|
+
return hasInjected(config, session)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* pre-step 唯一可发出角色:宿主把本批消息逐条写成 `user/message` 事件,事件校验要求
|
|
127
|
+
* `role === 'user'`(assistant 只能由模型侧 assistant/message 事件产生)。
|
|
128
|
+
*/
|
|
129
|
+
const PRE_STEP_ROLE = 'user'
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 记录一次角色降级:保留原角色、明确告警、不改正文,也不把非法值传给宿主。
|
|
133
|
+
* 同一配置只告警一次(warnOnce),且不记录正文或用户内容。
|
|
134
|
+
*/
|
|
135
|
+
function downgradeRole(config, requested, warnOnce) {
|
|
136
|
+
if (typeof requested !== 'string' || requested === PRE_STEP_ROLE) return undefined
|
|
137
|
+
warnOnce(`${name}: config ${String(config?.id ?? '<unknown>')} role ${JSON.stringify(requested)} cannot be injected in pre-step, message downgraded to "${PRE_STEP_ROLE}"`)
|
|
138
|
+
return requested
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 构造默认 user 消息;策略返回完整 patch 时覆盖对应字段,但非法角色在出口兜底降级。 */
|
|
142
|
+
function buildMessage(config, resolved, warnOnce) {
|
|
54
143
|
const text = typeof resolved.text === 'string' ? resolved.text : ''
|
|
55
144
|
const defaultContent = config.texts.length > 0
|
|
56
145
|
? config.texts.map((item) => ({ type: 'text', text: item }))
|
|
57
146
|
: [{ type: 'text', text }]
|
|
58
147
|
const sourceValue = mergedIdentity(config)
|
|
148
|
+
// 策略 patch(templateFile 的 role 等)与配置声明都必须经过同一出口判定。
|
|
149
|
+
const requested = downgradeRole(config, typeof resolved.role === 'string' ? resolved.role : config.role, warnOnce)
|
|
150
|
+
const base = resolved.source !== null && typeof resolved.source === 'object'
|
|
151
|
+
? resolved.source
|
|
152
|
+
: {
|
|
153
|
+
kind: config.sourceKind,
|
|
154
|
+
plugin: sourceValue,
|
|
155
|
+
...(typeof config.form === 'string' ? { form: config.form } : {}),
|
|
156
|
+
...(typeof config.summary === 'string' && config.summary.length > 0 ? { summary: config.summary } : {}),
|
|
157
|
+
}
|
|
59
158
|
return {
|
|
60
159
|
id: typeof resolved.id === 'string' && resolved.id.length > 0 ? resolved.id : newMessageId(config.id),
|
|
61
|
-
role:
|
|
160
|
+
role: PRE_STEP_ROLE,
|
|
62
161
|
content: Array.isArray(resolved.content) ? resolved.content : defaultContent,
|
|
63
|
-
source:
|
|
64
|
-
? resolved.source
|
|
65
|
-
: {
|
|
66
|
-
kind: config.sourceKind,
|
|
67
|
-
plugin: sourceValue,
|
|
68
|
-
...(typeof config.form === 'string' ? { form: config.form } : {}),
|
|
69
|
-
...(typeof config.summary === 'string' && config.summary.length > 0 ? { summary: config.summary } : {}),
|
|
70
|
-
},
|
|
162
|
+
source: requested === undefined ? base : { ...base, requestedRole: requested },
|
|
71
163
|
}
|
|
72
164
|
}
|
|
73
165
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
166
|
+
/**
|
|
167
|
+
* 批执行算法(单一实现):过滤(层/子代理/模型/晋升)→ resolve → 插值 →
|
|
168
|
+
* 去重/合并 → 落位插入。
|
|
169
|
+
*
|
|
170
|
+
* 预设卡与独立指令文件卡共用这条路径,身份空间互不覆盖;文件来源的候选资格由
|
|
171
|
+
* 调用方(协调器)按可见状态先行判定,这里只按各卡自身声明的策略执行。文件正文以
|
|
172
|
+
* content 块返回(不填 config.texts),因此不经过预设变量插值。
|
|
173
|
+
*
|
|
174
|
+
* @param options.configs 已过滤 enabled 与互斥组的最终提示词配置列表。
|
|
175
|
+
* @param options.promotion { main, withSubagents } 晋升追踪器(真相在持久事件流)。
|
|
176
|
+
* @param options.memo 已确认投递身份的进程内 session 去重快路径(见 confirmDelivered)。
|
|
177
|
+
* @returns 注入后的 decision;reject、缺 agent/session、全部跳过或异常时原样返回。
|
|
178
|
+
*/
|
|
179
|
+
export async function runPreStepBatch(options) {
|
|
180
|
+
const { ctx, agent, decision, configs, promotion, memo, warnOnce } = options
|
|
181
|
+
if (decision === null || typeof decision !== 'object') return decision
|
|
182
|
+
if (decision.kind === 'reject') return decision
|
|
183
|
+
if (agent === undefined) return decision
|
|
184
|
+
const session = agent.session
|
|
185
|
+
if (session === undefined) return decision
|
|
186
|
+
const { main, withSubagents } = promotion
|
|
187
|
+
try {
|
|
188
|
+
const messages = Array.isArray(decision.messages) ? [...decision.messages] : []
|
|
189
|
+
// 条件判定的匹配对象在本批进入时取定:批次内后续注入不改变本批的判定依据。
|
|
190
|
+
const userText = userMessagesText(messages)
|
|
191
|
+
let changed = false
|
|
192
|
+
|
|
193
|
+
const due = []
|
|
194
|
+
// 本批资格判定(唯一实现):层通道、受众、模型、晋升与声明式条件。
|
|
195
|
+
// 条件判定放在去重之前:未命中的配置不算「已注入」,条件恢复后仍应能注入。
|
|
196
|
+
// ST 模板的跨配置变量帧按 order 预求值,但只有这里的获准集合才允许产生副作用
|
|
197
|
+
// (未命中的 setter 提前 setvar 会污染同批 reader)——渲染器不再复制判定。
|
|
198
|
+
const delegated = isDelegated(session)
|
|
199
|
+
const qualified = (config) => config.layer === 'pre-step'
|
|
200
|
+
&& !(config.audience === 'main' && delegated)
|
|
201
|
+
&& !(config.audience === 'subagent' && !delegated)
|
|
202
|
+
&& matchesModel(config.modelScope, agent.options?.model)
|
|
203
|
+
&& (config.promotion !== 'main' || main.status(agent).promoted)
|
|
204
|
+
&& (config.promotion !== 'include-subagents' || withSubagents.status(agent).promoted)
|
|
205
|
+
&& conditionHit(config, { userText })
|
|
206
|
+
const qualifiedConfigs = configs.filter(qualified)
|
|
207
|
+
// 协调器为绑定来源 ctx 会复制 config;renderSt 函数身份在副本间保持不变。
|
|
208
|
+
const eligible = new Set(qualifiedConfigs.map(config => config.renderSt))
|
|
209
|
+
const stWorldBook = selectStWorldBook(qualifiedConfigs, session, messages, warnOnce)
|
|
210
|
+
for (const config of configs) {
|
|
211
|
+
try {
|
|
212
|
+
if (!qualified(config)) continue
|
|
213
|
+
|
|
214
|
+
if (config.dedupe === 'session') {
|
|
215
|
+
if (alreadyDelivered(config, session, memo)) continue
|
|
216
|
+
} else if (config.dedupe === 'batch' && hasInBatch(config, messages)) {
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const resolved = await config.resolve({ ctx, agent, session, decision, messages, stWorldBookSelected: stWorldBook.has(config) })
|
|
221
|
+
if (resolved === null || resolved === undefined) continue
|
|
222
|
+
const patched = { ...resolved }
|
|
223
|
+
// params 并入插值变量:ST 变量(setvar/getvar 收集 + 预设参数)顶层 key 直接可插值
|
|
224
|
+
//(含中文 key 如 {{接受值}}——引擎正则已支持 Unicode 字母)。
|
|
225
|
+
// 会话变量(session_var 工具维护)覆盖配置/预设默认:resolved > 会话 > params > 配置。
|
|
226
|
+
const mergedVars = {
|
|
227
|
+
...config.variables,
|
|
228
|
+
...config.params,
|
|
229
|
+
...sessionVarsSnapshot(session),
|
|
230
|
+
...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}),
|
|
231
|
+
}
|
|
232
|
+
if (typeof config.renderSt === 'function') {
|
|
233
|
+
patched.text = config.renderSt(agent, messages, warnOnce, options, eligible)
|
|
234
|
+
patched.content = patched.text.length > 0 ? [{ type: 'text', text: patched.text }] : []
|
|
235
|
+
} else if (typeof patched.text === 'string') {
|
|
236
|
+
// 提示词配置级模板变量 + filler 变量 + 内置环境变量插值。
|
|
237
|
+
patched.text = interpolateVariables(patched.text, mergedVars, session)
|
|
238
|
+
}
|
|
239
|
+
if (config.texts.length > 0 && typeof config.renderSt !== 'function') {
|
|
240
|
+
const blocks = config.texts
|
|
241
|
+
.map((item) => interpolateVariables(item, mergedVars, session))
|
|
242
|
+
.filter((item) => item.length > 0)
|
|
243
|
+
.map((item) => ({ type: 'text', text: item }))
|
|
244
|
+
if (blocks.length > 0) patched.content = blocks
|
|
245
|
+
}
|
|
246
|
+
const hasText = typeof patched.text === 'string' && patched.text.length > 0
|
|
247
|
+
const hasContent = Array.isArray(patched.content) && patched.content.length > 0
|
|
248
|
+
if (!hasText && !hasContent) continue
|
|
249
|
+
due.push({ config, resolved: patched })
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// 单个提示词配置失败不得伤及会话:跳过该提示词配置,只告警一次。
|
|
252
|
+
warnOnce(`${name}: config ${String(config?.id ?? '<unknown>')} failed, skipping: ${String((error && error.message) || error)}`)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// order 升序(同值保持声明顺序):决定拼接顺序与同位置插入顺序。
|
|
257
|
+
due.sort((a, b) => a.config.order - b.config.order)
|
|
258
|
+
|
|
259
|
+
// merged 模式的多条提示词配置在首条配置的位置合并为一条消息;
|
|
260
|
+
// 文本按内容块拼接,source 身份改用 merged:<position> 以保持持久幂等。
|
|
261
|
+
const orderedGroups = []
|
|
262
|
+
const groupIndex = new Map()
|
|
263
|
+
for (const entry of due) {
|
|
264
|
+
const group = entry.config.mergeMode === 'merged'
|
|
265
|
+
? mergedIdentity(entry.config)
|
|
266
|
+
: undefined
|
|
267
|
+
if (group === undefined) {
|
|
268
|
+
orderedGroups.push([entry])
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
let index = groupIndex.get(group)
|
|
272
|
+
if (index === undefined) {
|
|
273
|
+
index = orderedGroups.length
|
|
274
|
+
groupIndex.set(group, index)
|
|
275
|
+
orderedGroups.push([])
|
|
276
|
+
}
|
|
277
|
+
orderedGroups[index].push(entry)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const planned = []
|
|
281
|
+
for (const group of orderedGroups) {
|
|
282
|
+
const base = group[0]
|
|
283
|
+
const message = buildMessage(base.config, base.resolved, warnOnce)
|
|
284
|
+
if (group.length > 1) {
|
|
285
|
+
message.content = group.flatMap((entry) => {
|
|
286
|
+
if (Array.isArray(entry.resolved.content)) return entry.resolved.content
|
|
287
|
+
return typeof entry.resolved.text === 'string' && entry.resolved.text.length > 0
|
|
288
|
+
? [{ type: 'text', text: entry.resolved.text }]
|
|
289
|
+
: []
|
|
290
|
+
})
|
|
291
|
+
if (message.source !== null && typeof message.source === 'object') {
|
|
292
|
+
message.source = { ...message.source, plugin: mergedIdentity(base.config) }
|
|
293
|
+
}
|
|
294
|
+
// 合并组的角色由首条配置决定;其余成员声明的非法角色同样要告警并留下降级事实。
|
|
295
|
+
for (const entry of group.slice(1)) {
|
|
296
|
+
const requested = downgradeRole(entry.config, typeof entry.resolved.role === 'string' ? entry.resolved.role : entry.config.role, warnOnce)
|
|
297
|
+
if (requested !== undefined && message.source.requestedRole === undefined) {
|
|
298
|
+
message.source = { ...message.source, requestedRole: requested }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (new Set(group.map((entry) => entry.config.position)).size > 1) {
|
|
302
|
+
warnOnce(`${name}: merged group mixes positions — using ${String(base.config.position)} from the first config`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
planned.push({ position: base.config.position, message, group })
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 同位置批量插入:planned 已按 order 升序,多元素 splice/unshift/push 保持该顺序。
|
|
309
|
+
// 去重记账不在这里:候选被外层门控剥离时也必须保持「未投递」,由 confirmDelivered 确认。
|
|
310
|
+
const markGroup = (group) => {
|
|
311
|
+
for (const entry of group) stWorldBook.commit?.(entry.config)
|
|
312
|
+
}
|
|
313
|
+
const beforeAll = planned.filter((item) => item.position === 'before-all')
|
|
314
|
+
const afterUser = planned.filter((item) => item.position !== 'before-all' && item.position !== 'after-all')
|
|
315
|
+
const afterAll = planned.filter((item) => item.position === 'after-all')
|
|
316
|
+
if (beforeAll.length > 0) {
|
|
317
|
+
messages.unshift(...beforeAll.map((item) => item.message))
|
|
318
|
+
for (const item of beforeAll) markGroup(item.group)
|
|
319
|
+
changed = true
|
|
320
|
+
}
|
|
321
|
+
const userIndex = messages.findIndex((item) => item?.source?.kind === 'user')
|
|
322
|
+
if (afterUser.length > 0 && userIndex >= 0) {
|
|
323
|
+
messages.splice(userIndex + 1, 0, ...afterUser.map((item) => item.message))
|
|
324
|
+
for (const item of afterUser) markGroup(item.group)
|
|
325
|
+
changed = true
|
|
326
|
+
}
|
|
327
|
+
if (afterAll.length > 0) {
|
|
328
|
+
messages.push(...afterAll.map((item) => item.message))
|
|
329
|
+
for (const item of afterAll) markGroup(item.group)
|
|
330
|
+
changed = true
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return changed ? { ...decision, messages } : decision
|
|
334
|
+
} catch (error) {
|
|
335
|
+
warnOnce(`${name}: prompt config failed, skipping: ${String((error && error.message) || error)}`)
|
|
336
|
+
return decision
|
|
83
337
|
}
|
|
84
|
-
// 默认 after-user:只锚真实用户消息,插件消息不算。
|
|
85
|
-
const userIndex = messages.findIndex((item) => item?.source?.kind === 'user')
|
|
86
|
-
if (userIndex < 0) return false
|
|
87
|
-
messages.splice(userIndex + 1, 0, message)
|
|
88
|
-
return true
|
|
89
338
|
}
|
|
90
339
|
|
|
91
340
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
341
|
+
* 把一组运行时提示词配置装配为注入执行器(独立模式 / 协调器管理模式的接线口)。
|
|
342
|
+
*
|
|
343
|
+
* @param options.prepend 是否以 prepend 注册本地 pre-step(合并行恒 true;由参数决定)。
|
|
344
|
+
* @param options.sourceId 协调器里的来源 id(默认取首条配置 id 派生)。
|
|
345
|
+
* @param options.officialInstructions 本 mount 的组合是否仍挂着官方指令加载行
|
|
346
|
+
* (负责人冲突事实;协调器据此拒绝同时注入文件正文)。
|
|
94
347
|
*/
|
|
95
348
|
export function applyPromptConfigs(ctx, configs, options = {}) {
|
|
96
349
|
const list = configs.filter((config) => config !== undefined && config !== null)
|
|
97
|
-
|
|
350
|
+
const sourceId = typeof options.sourceId === 'string' && options.sourceId.length > 0
|
|
351
|
+
? options.sourceId
|
|
352
|
+
: `preset:${String(list[0]?.id ?? 'unknown')}`
|
|
353
|
+
// 空 mount 同样保留接管监听:装配事实与协调器迟到/HMR 不依赖是否有预设卡。
|
|
98
354
|
// 互斥组:同一 group 且 exclusive=true 时,只保留排序后第一个 enabled 提示词配置。
|
|
99
355
|
const claimedGroups = new Set()
|
|
100
356
|
const effectiveList = list.filter((config) => {
|
|
@@ -105,174 +361,97 @@ export function applyPromptConfigs(ctx, configs, options = {}) {
|
|
|
105
361
|
}
|
|
106
362
|
return true
|
|
107
363
|
})
|
|
364
|
+
const injectedMemo = new Map()
|
|
108
365
|
const main = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: false })
|
|
109
366
|
const withSubagents = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: true })
|
|
110
|
-
ctx.on('session/event', (session, event) => {
|
|
367
|
+
const observerDisposer = ctx.on('session/event', (session, event) => {
|
|
111
368
|
main.observe(session, event)
|
|
112
369
|
withSubagents.observe(session, event)
|
|
370
|
+
// 独立路径的去重记账:只有宿主真正持久化的注入消息才确认投递(门控剥离的候选不算)。
|
|
371
|
+
confirmDelivered(injectedMemo, session, event)
|
|
113
372
|
})
|
|
114
373
|
|
|
115
|
-
/** 每提示词配置每会话的进程内快路径;真相在持久事件流。 */
|
|
116
|
-
// ponytail: 会话数上限防无界增长(Set 按 session.id 累积);若需精确淘汰改 LRU。
|
|
117
|
-
const MAX_MEMO_SESSIONS = 4096
|
|
118
|
-
const injectedMemo = new Map()
|
|
119
|
-
const configMemo = (config) => {
|
|
120
|
-
let memo = injectedMemo.get(config.id)
|
|
121
|
-
if (memo === undefined) {
|
|
122
|
-
if (injectedMemo.size >= MAX_MEMO_SESSIONS) injectedMemo.clear()
|
|
123
|
-
memo = new Set()
|
|
124
|
-
injectedMemo.set(config.id, memo)
|
|
125
|
-
}
|
|
126
|
-
return memo
|
|
127
|
-
}
|
|
128
|
-
|
|
129
374
|
const warnOnce = createWarnOnce(ctx, name)
|
|
130
375
|
const prepend = options.prepend === true || list.some((config) => config.prepend === true)
|
|
376
|
+
const promotion = { main, withSubagents }
|
|
377
|
+
const source = {
|
|
378
|
+
configs: effectiveList,
|
|
379
|
+
officialInstructions: options.officialInstructions === true,
|
|
380
|
+
}
|
|
381
|
+
/** 已交给协调器的注册(仅管理路径非空)。 */
|
|
382
|
+
let registration = null
|
|
383
|
+
const releaseRegistration = () => {
|
|
384
|
+
if (registration === null) return
|
|
385
|
+
const dispose = registration
|
|
386
|
+
registration = null
|
|
387
|
+
try {
|
|
388
|
+
dispose()
|
|
389
|
+
} catch {
|
|
390
|
+
// 协调器自身已释放:忽略。
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
let active = true
|
|
394
|
+
/** 释放本执行器:失效来源不再注入,也不再向新协调器重新注册。 */
|
|
395
|
+
const release = () => {
|
|
396
|
+
active = false
|
|
397
|
+
releaseRegistration()
|
|
398
|
+
injectedMemo.clear()
|
|
399
|
+
}
|
|
400
|
+
keepDisposer(ctx, release)
|
|
131
401
|
|
|
132
402
|
// 非 pre-step 提示词配置接入各自声明的官方层级通道(system-section /
|
|
133
403
|
// runtime-context / agent-request / llm-stream / tool-pipeline)。
|
|
134
|
-
wireLayers(ctx, effectiveList.filter((config) => config.layer !== 'pre-step'), warnOnce)
|
|
135
|
-
|
|
136
|
-
ctx.on('agent/pre-step', async ({ agent }, next) => {
|
|
137
|
-
const decision = await next()
|
|
138
|
-
try {
|
|
139
|
-
if (decision.kind === 'reject') return decision
|
|
140
|
-
if (agent === undefined) return decision
|
|
141
|
-
const session = agent.session
|
|
142
|
-
if (session === undefined) return decision
|
|
143
|
-
|
|
144
|
-
const messages = Array.isArray(decision.messages) ? [...decision.messages] : []
|
|
145
|
-
let changed = false
|
|
404
|
+
const releaseLayers = wireLayers(ctx, effectiveList.filter((config) => config.layer !== 'pre-step'), warnOnce)
|
|
146
405
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (config.audience === 'subagent' && !delegated) continue
|
|
154
|
-
if (!matchesModel(config.modelScope, agent.options?.model)) continue
|
|
155
|
-
if (config.promotion === 'main' && !main.status(agent).promoted) continue
|
|
156
|
-
if (config.promotion === 'include-subagents' && !withSubagents.status(agent).promoted) continue
|
|
157
|
-
|
|
158
|
-
const memo = configMemo(config)
|
|
159
|
-
if (config.dedupe === 'session') {
|
|
160
|
-
if (memo.has(session.id) || hasInjected(config, session)) {
|
|
161
|
-
memo.add(session.id)
|
|
162
|
-
continue
|
|
163
|
-
}
|
|
164
|
-
} else if (config.dedupe === 'batch' && hasInBatch(config, messages)) {
|
|
165
|
-
continue
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const resolved = await config.resolve({ ctx, agent, session, decision, messages })
|
|
169
|
-
if (resolved === null || resolved === undefined) continue
|
|
170
|
-
const patched = { ...resolved }
|
|
171
|
-
// params 并入插值变量:ST 变量(setvar/getvar 收集 + 预设参数)顶层 key 直接可插值
|
|
172
|
-
//(含中文 key 如 {{接受值}}——引擎正则已支持 Unicode 字母)。
|
|
173
|
-
// 会话变量(session_var 工具维护)覆盖配置/预设默认:resolved > 会话 > params > 配置。
|
|
174
|
-
const mergedVars = {
|
|
175
|
-
...config.variables,
|
|
176
|
-
...config.params,
|
|
177
|
-
...sessionVarsSnapshot(session),
|
|
178
|
-
...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}),
|
|
179
|
-
}
|
|
180
|
-
if (typeof patched.text === 'string') {
|
|
181
|
-
// 提示词配置级模板变量 + filler 变量 + 内置环境变量插值。
|
|
182
|
-
patched.text = interpolateVariables(patched.text, mergedVars, session)
|
|
183
|
-
}
|
|
184
|
-
if (config.texts.length > 0) {
|
|
185
|
-
const blocks = config.texts
|
|
186
|
-
.map((item) => interpolateVariables(item, mergedVars, session))
|
|
187
|
-
.filter((item) => item.length > 0)
|
|
188
|
-
.map((item) => ({ type: 'text', text: item }))
|
|
189
|
-
if (blocks.length > 0) patched.content = blocks
|
|
190
|
-
}
|
|
191
|
-
const hasText = typeof patched.text === 'string' && patched.text.length > 0
|
|
192
|
-
const hasContent = Array.isArray(patched.content) && patched.content.length > 0
|
|
193
|
-
if (!hasText && !hasContent) continue
|
|
194
|
-
due.push({ config, resolved: patched })
|
|
195
|
-
} catch (error) {
|
|
196
|
-
// 单个提示词配置失败不得伤及会话:跳过该提示词配置,只告警一次。
|
|
197
|
-
warnOnce(`${name}: config ${String(config?.id ?? '<unknown>')} failed, skipping: ${String((error && error.message) || error)}`)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// order 升序(同值保持声明顺序):决定拼接顺序与同位置插入顺序。
|
|
202
|
-
due.sort((a, b) => a.config.order - b.config.order)
|
|
203
|
-
|
|
204
|
-
// merged 模式的多条提示词配置在首条配置的位置合并为一条消息;
|
|
205
|
-
// 文本按内容块拼接,source 身份改用 merged:<position> 以保持持久幂等。
|
|
206
|
-
const orderedGroups = []
|
|
207
|
-
const groupIndex = new Map()
|
|
208
|
-
for (const entry of due) {
|
|
209
|
-
const group = entry.config.mergeMode === 'merged'
|
|
210
|
-
? mergedIdentity(entry.config)
|
|
211
|
-
: undefined
|
|
212
|
-
if (group === undefined) {
|
|
213
|
-
orderedGroups.push([entry])
|
|
214
|
-
continue
|
|
215
|
-
}
|
|
216
|
-
let index = groupIndex.get(group)
|
|
217
|
-
if (index === undefined) {
|
|
218
|
-
index = orderedGroups.length
|
|
219
|
-
groupIndex.set(group, index)
|
|
220
|
-
orderedGroups.push([])
|
|
221
|
-
}
|
|
222
|
-
orderedGroups[index].push(entry)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const planned = []
|
|
226
|
-
for (const group of orderedGroups) {
|
|
227
|
-
const base = group[0]
|
|
228
|
-
const message = buildMessage(base.config, base.resolved)
|
|
229
|
-
if (group.length > 1) {
|
|
230
|
-
message.content = group.flatMap((entry) => {
|
|
231
|
-
if (Array.isArray(entry.resolved.content)) return entry.resolved.content
|
|
232
|
-
return typeof entry.resolved.text === 'string' && entry.resolved.text.length > 0
|
|
233
|
-
? [{ type: 'text', text: entry.resolved.text }]
|
|
234
|
-
: []
|
|
235
|
-
})
|
|
236
|
-
if (message.source !== null && typeof message.source === 'object') {
|
|
237
|
-
message.source = { ...message.source, plugin: mergedIdentity(base.config) }
|
|
238
|
-
}
|
|
239
|
-
if (new Set(group.map((entry) => entry.config.position)).size > 1) {
|
|
240
|
-
warnOnce(`${name}: merged group mixes positions — using ${String(base.config.position)} from the first config`)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
planned.push({ position: base.config.position, message, group })
|
|
244
|
-
}
|
|
406
|
+
// 协调器已在(正常装配顺序:插件先加载、预设后挂载)时立即登记来源;
|
|
407
|
+
// 迟到/消失由下面的监听器按「先撤旧再启新」处理。
|
|
408
|
+
let registeredCoordinator = coordinatorOf(ctx)
|
|
409
|
+
if (registeredCoordinator !== undefined) {
|
|
410
|
+
registration = registeredCoordinator.registerPreset(ctx, sourceId, source) ?? null
|
|
411
|
+
}
|
|
245
412
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (beforeAll.length > 0) {
|
|
256
|
-
messages.unshift(...beforeAll.map((item) => item.message))
|
|
257
|
-
for (const item of beforeAll) markGroup(item.group)
|
|
258
|
-
changed = true
|
|
259
|
-
}
|
|
260
|
-
const userIndex = messages.findIndex((item) => item?.source?.kind === 'user')
|
|
261
|
-
if (afterUser.length > 0 && userIndex >= 0) {
|
|
262
|
-
messages.splice(userIndex + 1, 0, ...afterUser.map((item) => item.message))
|
|
263
|
-
for (const item of afterUser) markGroup(item.group)
|
|
264
|
-
changed = true
|
|
413
|
+
const preStepDisposer = ctx.on('agent/pre-step', async (payload, next) => {
|
|
414
|
+
// waterfall 可已捕获随后被释放的回调;失效来源不得向新协调器重新注册。
|
|
415
|
+
if (!active) return next()
|
|
416
|
+
const coordinator = coordinatorOf(ctx)
|
|
417
|
+
// HMR 可在相邻两步之间替换服务:旧句柄非空不代表仍向当前实例注册。
|
|
418
|
+
if (coordinator !== registeredCoordinator) {
|
|
419
|
+
releaseRegistration()
|
|
420
|
+
if (coordinator === undefined) {
|
|
421
|
+
warnOnce(`${name}: pre-step coordinator unavailable, resuming standalone execution`)
|
|
265
422
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return changed ? { ...decision, messages } : decision
|
|
273
|
-
} catch (error) {
|
|
274
|
-
warnOnce(`${name}: prompt config failed, skipping: ${String((error && error.message) || error)}`)
|
|
275
|
-
return decision
|
|
423
|
+
registeredCoordinator = coordinator
|
|
424
|
+
}
|
|
425
|
+
if (coordinator !== undefined) {
|
|
426
|
+
// 管理路径:先登记本来源,再交出唯一执行权(本监听器不再注入)。
|
|
427
|
+
if (registration === null) registration = coordinator.registerPreset(ctx, sourceId, source) ?? null
|
|
428
|
+
return next()
|
|
276
429
|
}
|
|
430
|
+
const decision = await next()
|
|
431
|
+
if (!active) return decision
|
|
432
|
+
return runPreStepBatch({
|
|
433
|
+
ctx,
|
|
434
|
+
agent: payload?.agent,
|
|
435
|
+
decision,
|
|
436
|
+
configs: effectiveList,
|
|
437
|
+
promotion,
|
|
438
|
+
memo: injectedMemo,
|
|
439
|
+
warnOnce,
|
|
440
|
+
})
|
|
277
441
|
}, { prepend })
|
|
442
|
+
// 释放边界:先摘监听器,再撤层级注册,最后丢弃去重快路径(顺序与 keepDisposer 内的 release 一致)。
|
|
443
|
+
return () => {
|
|
444
|
+
release()
|
|
445
|
+
try {
|
|
446
|
+
preStepDisposer()
|
|
447
|
+
} catch {
|
|
448
|
+
// 宿主已释放该 fiber 时监听器可能已不在:忽略。
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
observerDisposer()
|
|
452
|
+
} catch {
|
|
453
|
+
// 同上。
|
|
454
|
+
}
|
|
455
|
+
releaseLayers()
|
|
456
|
+
}
|
|
278
457
|
}
|