dsh-plugin-prompt-tool 2.1.0 → 3.0.1
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 +100 -233
- package/engine/actions/append-context.mjs +3 -24
- package/engine/actions/assembly.mjs +70 -94
- package/engine/actions/catalog.mjs +65 -28
- package/engine/actions/content.mjs +8 -152
- package/engine/actions/examples.mjs +8 -4
- package/engine/actions/inbox-prepend.mjs +4 -5
- package/engine/actions/index.mjs +17 -9
- package/engine/actions/inject-text.mjs +24 -4
- package/engine/actions/pre-step-filter.mjs +3 -4
- package/engine/actions/registration.mjs +20 -28
- package/engine/actions/request-params.mjs +21 -1
- package/engine/actions/shared.mjs +22 -13
- package/engine/actions/stream-output.mjs +26 -0
- package/engine/actions/tool-gate.mjs +31 -0
- package/engine/actions/tool-result.mjs +36 -0
- package/engine/actions/tool-visibility.mjs +248 -0
- package/engine/agent-capability.mjs +67 -0
- package/engine/anchor-match.mjs +14 -21
- package/engine/audience.d.mts +2 -0
- package/engine/audience.mjs +102 -0
- package/engine/branch.d.mts +4 -0
- package/engine/branch.mjs +61 -18
- package/engine/compaction-epoch.d.mts +25 -0
- package/engine/compaction-epoch.mjs +27 -4
- package/engine/compositions/source/local/filesystem-editor.yml +3 -3
- package/engine/compositions/source/local/instruction-hint.yml +2 -4
- package/engine/compositions/source/local/module-file-tools.yml +2 -0
- package/engine/compositions/source/local/module-import-tools.yml +4 -0
- package/engine/compositions/source/local/module-manifest-tools.yml +2 -0
- package/engine/compositions/source/local/module-rule-tools.yml +5 -0
- package/engine/compositions/source/local/module-variable-tools.yml +5 -0
- package/engine/compositions/source/local/present-as.yml +26 -0
- package/engine/compositions/source/local/tool-config-engine.yml +2 -3
- package/engine/compositions/source/local/tool-search.yml +11 -0
- package/engine/condition-facts.mjs +84 -0
- package/engine/conditions/anchor.mjs +63 -23
- package/engine/conditions/availability.mjs +1 -1
- package/engine/conditions/composite.mjs +3 -3
- package/engine/conditions/count.mjs +42 -11
- package/engine/conditions/evaluation.mjs +74 -19
- package/engine/conditions/examples.mjs +2 -1
- package/engine/conditions/index.d.mts +15 -0
- package/engine/conditions/index.mjs +23 -9
- package/engine/conditions/operators.mjs +4 -0
- package/engine/conditions/phase.mjs +9 -3
- package/engine/conditions/preset.mjs +1 -1
- package/engine/conditions/session.mjs +8 -2
- package/engine/conditions/subject.mjs +144 -65
- package/engine/conditions/text.mjs +26 -8
- package/engine/conditions/variable.mjs +83 -0
- package/engine/context-activation.d.mts +11 -0
- package/engine/{st-world-book.mjs → context-activation.mjs} +104 -64
- package/engine/delivery.d.mts +2 -0
- package/engine/delivery.mjs +93 -0
- package/engine/discovery.d.mts +38 -0
- package/engine/discovery.mjs +146 -0
- package/engine/env-safety.mjs +14 -0
- package/engine/executor.d.mts +28 -0
- package/engine/executor.mjs +290 -205
- package/engine/fillers.mjs +39 -8
- package/engine/fixed-registration.mjs +12 -0
- package/engine/history.mjs +71 -0
- package/engine/instruction-blocks.d.mts +13 -0
- package/engine/instruction-blocks.mjs +21 -0
- package/engine/instruction-files.d.mts +5 -0
- package/engine/instruction-files.mjs +97 -0
- package/engine/instruction-hint.mjs +69 -103
- package/engine/interpolate.d.mts +7 -0
- package/engine/interpolate.mjs +44 -13
- package/engine/layers.mjs +163 -227
- package/engine/machine-paths.mjs +40 -0
- package/engine/module-file-tools.mjs +8 -0
- package/engine/module-import-tools.mjs +10 -0
- package/engine/module-manifest-tools.mjs +8 -0
- package/engine/module-rule-tools.mjs +10 -0
- package/engine/module-variable-tools.mjs +10 -0
- package/engine/order.d.mts +14 -0
- package/engine/order.mjs +0 -3
- package/engine/path-safety.d.mts +2 -0
- package/engine/path-safety.mjs +58 -0
- package/engine/pre-step-source.d.mts +69 -0
- package/engine/pre-step-source.mjs +103 -0
- package/engine/present-as.mjs +77 -0
- package/engine/promotion-pool.d.mts +9 -0
- package/engine/promotion-pool.mjs +69 -0
- package/engine/record.mjs +2 -0
- package/engine/rule-engine.mjs +3 -2
- package/engine/rule-facts.d.mts +11 -0
- package/engine/rule-facts.mjs +60 -0
- package/engine/rule-runtime.d.mts +46 -0
- package/engine/rule-runtime.mjs +98 -13
- package/engine/rule-spec.d.mts +106 -0
- package/engine/rule-spec.mjs +301 -27
- package/engine/run-code-env.mjs +73 -19
- package/engine/schema.mjs +165 -243
- package/engine/sdk-strip.mjs +2 -2
- package/engine/service-names.d.mts +10 -0
- package/engine/service-names.mjs +10 -0
- package/engine/shared.d.mts +21 -0
- package/engine/shared.mjs +46 -13
- package/engine/skill-search.mjs +125 -53
- package/engine/source-kind.d.mts +9 -0
- package/engine/source-kind.mjs +16 -0
- package/engine/strategy-ids.mjs +5 -0
- package/engine/template-evaluation.d.mts +16 -0
- package/engine/template-evaluation.mjs +178 -0
- package/engine/template-frame.d.mts +16 -0
- package/engine/template-frame.mjs +69 -0
- package/engine/tool-config-engine.mjs +38 -34
- package/engine/tool-definition.d.mts +4 -0
- package/engine/tool-definition.mjs +5 -1
- package/engine/tool-git-bash.mjs +5 -1
- package/engine/tool-scope.mjs +22 -0
- package/engine/{dev-tool-search.mjs → tool-search.mjs} +63 -65
- package/engine/trigger.mjs +15 -270
- package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/engine/vendor/yaml/dist/nodes/Alias.js +25 -23
- package/engine/vendor/yaml/package.json +1 -1
- package/lib/client.js +8312 -6724
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +16 -2120
- package/lib/index.mjs +26914 -5816
- package/modules/ponytail/module.yml +83 -203
- package/modules/ptc-mode/module.yml +45 -0
- package/modules/soul/assets/core-hard.md +6 -0
- package/modules/soul/assets/criteria.md +12 -0
- package/modules/soul/assets/echo.md +12 -0
- package/modules/soul/assets/layers.md +11 -0
- package/modules/soul/assets/ledger.md +9 -0
- package/modules/soul/assets/manifest.yml +20 -0
- package/modules/soul/assets/reference/00-layers.md +27 -0
- package/modules/soul/assets/reference/01-criteria.md +32 -0
- package/modules/soul/assets/reference/02-echo.md +161 -0
- package/modules/soul/assets/reference/03-vital.md +183 -0
- package/modules/soul/assets/reference/04-manifest.json +106 -0
- package/modules/soul/assets/reference/05-doctor.md +67 -0
- package/modules/soul/assets/reference/06-reference.md +189 -0
- package/modules/soul/assets/reference/07-bundle.json +431 -0
- package/modules/soul/assets/reference/08-/350/270/251/345/235/221/345/256/236/345/275/225.md +59 -0
- package/modules/soul/assets/reference/09-dev-workflow.md +109 -0
- package/modules/soul/assets/reference/10-echo-anatomy.md +109 -0
- package/modules/soul/assets/reference/11-self.md +80 -0
- package/modules/soul/assets/reference/12-ledger.md +159 -0
- package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -0
- package/modules/soul/assets/reference/ECHO/345/205/255/345/261/202/344/270/216mnemon/345/215/217/345/220/214/350/257/264/346/230/216/344/271/246.md +229 -0
- package/modules/soul/assets/reference/README.md +12 -0
- package/modules/soul/assets/templates/agents-template.md +6 -0
- package/modules/soul/assets/vital.md +9 -0
- package/modules/soul/module.yml +311 -0
- package/modules/superpowers/module.yml +122 -0
- package/modules/tool-surface/module.yml +51 -47
- package/package.json +37 -40
- package/skills/dsh-module/SKILL.md +67 -147
- package/skills/dsh-module/reference.md +113 -86
- package/templates/10-pre-step.yml +2 -3
- package/templates/14-first-turn-anchor.yml +10 -14
- package/templates/15-guide-auto.yml +32 -28
- package/templates/16-custom-fallback.yml +33 -21
- package/templates/20-system-section.yml +1 -4
- package/templates/50-llm-stream.yml +4 -10
- package/templates/60-tool-pipeline.yml +12 -10
- package/templates/63-append-context.yml +4 -6
- package/templates/65-turn-stop.yml +2 -2
- package/templates/67-inbox-prepend.yml +2 -2
- package/templates/70-subagent-maintenance.yml +9 -10
- package/templates/75-subagent-end.yml +2 -2
- package/templates/80-tool-surface.yml +41 -57
- package/templates/tools/40-delegate-module-import.yml +22 -0
- package/templates/tools/50-delegate-module-rules.yml +14 -0
- package/templates/tools/60-delegate-module-variables.yml +17 -0
- package/templates/tools/70-delegate-module-rules-edit.yml +31 -0
- package/engine/actions/decision.mjs +0 -54
- package/engine/actions/guard.mjs +0 -80
- package/engine/actions/sdk-strip.mjs +0 -30
- package/engine/character-tools.mjs +0 -8
- package/engine/classify-task.mjs +0 -53
- package/engine/compositions/source/local/character-tools.yml +0 -5
- package/engine/compositions/source/local/dev-tool-search.yml +0 -11
- package/engine/compositions/source/local/session-var-tools.yml +0 -5
- package/engine/compositions/source/local/subagent-tool-policy.yml +0 -21
- package/engine/compositions/source/local/world-book-tools.yml +0 -5
- package/engine/condition.mjs +0 -96
- package/engine/declared-triggers.mjs +0 -93
- package/engine/session-var-tools.mjs +0 -8
- package/engine/session-vars.mjs +0 -54
- package/engine/st-macros.d.mts +0 -22
- package/engine/st-macros.mjs +0 -208
- package/engine/st-render.mjs +0 -109
- package/engine/st-world-book.d.mts +0 -10
- package/engine/subagent-tool-policy-core.mjs +0 -400
- package/engine/subagent-tool-policy.mjs +0 -352
- package/engine/trigger-spec.mjs +0 -173
- package/engine/world-book-tools.mjs +0 -8
- package/lib/migrate-rules.d.mts +0 -1
- package/lib/migrate-rules.mjs +0 -610
- package/lib/module-package-DqJZaE71.mjs +0 -2
- package/lib/module-package-q2gDRu9H.mjs +0 -2869
- package/lib/rule-builder-DMrJten6.mjs +0 -14767
- package/templates/61-deny-tools-presentation.yml +0 -27
- package/templates/62-guard-deny-tools.yml +0 -20
- package/templates/policies/legacy-defaults.yml +0 -34
- package/templates/tools/40-delegate-character.yml +0 -15
- package/templates/tools/50-delegate-worldbook.yml +0 -15
- package/templates/tools/60-delegate-session-var.yml +0 -15
- package/templates/tools/70-delegate-worldbook-upsert.yml +0 -36
package/engine/executor.mjs
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* 任一提示词配置失败只跳过该提示词配置并 warnOnce,绝不让注入 bug 卡死会话。
|
|
5
5
|
*
|
|
6
6
|
* 装配路径有两条,批执行算法只有一份(runPreStepBatch):
|
|
7
|
-
* - 管理路径:宿主 prompt-tool 插件提供 `
|
|
8
|
-
* (预设 mount
|
|
7
|
+
* - 管理路径:宿主 prompt-tool 插件提供 pre-step 协调服务(服务名见 `service-names.mjs`)时,本来源
|
|
8
|
+
* (预设 mount)把配置注册给协调器,由协调器统一执行各预设来源;官方指令正文的
|
|
9
|
+
* 逐文件过滤归协调器的 prepend 监听器,旧生成卡的 `agents-file-*` 按 id 前缀跳过;
|
|
9
10
|
* - 独立路径:没有协调服务(引擎被复制到无宿主的目录复用)时,本行 ctx 注册本地
|
|
10
11
|
* pre-step 监听器,只执行自身预设配置。
|
|
11
12
|
* 协调服务迟到或消失时按「先撤旧再启新」切换,任一时刻每 scope 只有一条执行路径。
|
|
@@ -20,21 +21,28 @@ import {
|
|
|
20
21
|
keepDisposer,
|
|
21
22
|
matchesModel,
|
|
22
23
|
newMessageId,
|
|
23
|
-
sessionEvents,
|
|
24
24
|
} from './shared.mjs'
|
|
25
|
+
// 去重判据读**当前模型可见上下文**(视图①),不读完整历史:被压缩遮蔽的注入要重新注入。
|
|
26
|
+
import { eventMessage, moduleIdOf, moduleOf, pluginIdentityOf, identityOf, identityStampOf, identityHit, injectedMessage, actionStamp, findActionDelivery } from './delivery.mjs'
|
|
25
27
|
import { interpolateVariables } from './interpolate.mjs'
|
|
26
|
-
import { conditionHit, userMessagesText } from './condition.mjs'
|
|
27
28
|
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
28
29
|
import { wireLayers } from './layers.mjs'
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
30
|
+
import { renderTemplate } from './template-frame.mjs'
|
|
31
|
+
import { selectContextActivation } from './context-activation.mjs'
|
|
31
32
|
import { compareConfigSequence } from './order.mjs'
|
|
32
|
-
import { ruleFrame,
|
|
33
|
+
import { ruleFrame, actionMatches } from './conditions/evaluation.mjs'
|
|
34
|
+
import { composePreStepSources, promotionOwnerOf } from './pre-step-source.mjs'
|
|
35
|
+
// 标记键名与「已登记进 config 白名单」同源(schema.mjs),此处不另写字面量。
|
|
36
|
+
import { SESSION_VARIABLES_DISABLED } from './schema.mjs'
|
|
37
|
+
import { PRE_STEP_COORDINATOR_SERVICE } from './service-names.mjs'
|
|
33
38
|
|
|
34
39
|
const name = 'prompt-config-engine'
|
|
35
40
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 「已确认投递」快路径的条目上限。真相是**当前上下文扫描**(见 injectedMessage):
|
|
43
|
+
* 这份 memo 只省一次扫描,丢了只是多扫一次,清空不改变任何判定结果,也不再需要淘汰策略。
|
|
44
|
+
*/
|
|
45
|
+
// shortcut: 上限防无界增长(Map 按「身份 × 代次」累积);若需精确淘汰改 LRU。
|
|
38
46
|
const MAX_MEMO_CONFIGS = 4096
|
|
39
47
|
|
|
40
48
|
/**
|
|
@@ -42,64 +50,52 @@ const MAX_MEMO_CONFIGS = 4096
|
|
|
42
50
|
* 缺失/形状不符时返回 undefined:本行回退独立执行路径。
|
|
43
51
|
*/
|
|
44
52
|
function coordinatorOf(ctx) {
|
|
45
|
-
const service = getService(ctx,
|
|
53
|
+
const service = getService(ctx, PRE_STEP_COORDINATOR_SERVICE)
|
|
46
54
|
return service !== null && typeof service === 'object' && typeof service.registerPreset === 'function'
|
|
47
55
|
? service
|
|
48
56
|
: undefined
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
/**
|
|
52
|
-
function
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const message = data.message !== null && typeof data.message === 'object' ? data.message : data
|
|
56
|
-
return message?.source !== null && typeof message?.source === 'object' ? message : undefined
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 合并身份:merged 模式按位置分组(merged:<position>);separate 模式保持自身身份。 */
|
|
60
|
-
function mergedIdentity(config) {
|
|
61
|
-
if (config.mergeMode !== 'merged') return config.id
|
|
62
|
-
return `merged:${config.position}`
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** 去重身份:merged 组用位置命名空间,独立配置用自身身份。 */
|
|
66
|
-
function identityOf(config) {
|
|
67
|
-
return config.mergeMode === 'merged' ? mergedIdentity(config) : config.identity.value
|
|
59
|
+
/** 当前消息批内是否已有该提示词配置注入(每轮去重)。 */
|
|
60
|
+
function hasInBatch(config, messages) {
|
|
61
|
+
const value = identityOf(config)
|
|
62
|
+
return messages.some((message) => identityHit(message?.source, config, value))
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
66
|
+
* 快路径条目键:通道 + 模块维 + 有效期戳 + 值。分隔符用 NUL(身份值里可能带 `:`)。
|
|
67
|
+
*
|
|
68
|
+
* 位置替换与内容投影的代次共同组成戳;任一变化都重新查询当前可见投递。
|
|
69
|
+
* 无 surface(降级到完整历史)时戳恒为 `-`:完整历史不会丢事件,条目一直有效。
|
|
73
70
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return typeof source.kind === 'string' && source.kind.startsWith('plugin:')
|
|
78
|
-
? source.kind.slice('plugin:'.length)
|
|
79
|
-
: undefined
|
|
71
|
+
function memoKey(field, moduleId, value, stamp) {
|
|
72
|
+
// 戳不可用(null)时调用方不查也不记。
|
|
73
|
+
return `${field}\u0000${moduleId ?? ''}\u0000${stamp}\u0000${value}`
|
|
80
74
|
}
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
76
|
+
/**
|
|
77
|
+
* 本会话当前可见上下文的代次戳,也是 memo 键的失效维。
|
|
78
|
+
*
|
|
79
|
+
* - 代次对:surface 的 `replaceGeneration:contentGeneration`;
|
|
80
|
+
* - `'-'`:无 surface(降级到完整历史),事件不会消失,条目一直有效;
|
|
81
|
+
* - `null`:**有 `surface.nodes` 却没有数字代次** —— memo 此时不可用:代次是它唯一的失效
|
|
82
|
+
* 信号,而真相(`currentEvents`)仍跟着 `nodes` 走,快路径会把被压缩移走的注入永久记成
|
|
83
|
+
* 「已投递」。这种宿主形态是真实情形(恢复、重放、其它宿主、既有桩;见
|
|
84
|
+
* `compaction-epoch.mjs` 的失效条件③),调用方据此跳过 memo、退回当前上下文扫描
|
|
85
|
+
* —— memo 只是省一次扫描的缓存,跳过不改变任何判定结果。
|
|
86
|
+
*/
|
|
87
|
+
function contextStamp(session) {
|
|
88
|
+
const generation = session?.surface?.replaceGeneration
|
|
89
|
+
if (typeof generation === 'number') return `${generation}:${session.surface.contentGeneration ?? 0}`
|
|
90
|
+
return Array.isArray(session?.surface?.nodes) ? null : '-'
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
94
|
+
* 按投递身份取「已确认投递」的会话集合 —— 纯性能缓存(省一次当前上下文扫描),
|
|
95
|
+
* **不是**真相:条目可随时丢弃(上限、`release()`、`clear()`),丢了只多扫一次。
|
|
96
|
+
*
|
|
97
|
+
* 记账只发生在宿主真正接纳并持久化该消息之后(见 confirmDelivered):候选在瀑布外层被
|
|
98
|
+
* 门控/策略剥离时不算已投递,否则晋升后(门控放行时)正文会永久缺失。
|
|
103
99
|
*/
|
|
104
100
|
function deliveredSessions(memo, key) {
|
|
105
101
|
let set = memo.get(key)
|
|
@@ -114,30 +110,49 @@ function deliveredSessions(memo, key) {
|
|
|
114
110
|
|
|
115
111
|
/**
|
|
116
112
|
* 记录一次宿主已接纳的注入消息(由调用方在 session/event 里转发)。
|
|
117
|
-
* plugin(本引擎身份,merged 组用 merged:<position
|
|
118
|
-
* context-gate 的 instruction-hint
|
|
113
|
+
* plugin(本引擎身份,merged 组用 merged:<position>)、kind(外来通道,如
|
|
114
|
+
* context-gate 的 instruction-hint)与消息的模块维都以会话为界记账,
|
|
115
|
+
* 与 identityHit 的三元组判据一致。
|
|
119
116
|
*/
|
|
120
117
|
export function confirmDelivered(memo, session, event) {
|
|
121
118
|
if (memo === null || memo === undefined || session === null || session === undefined) return
|
|
122
119
|
const source = eventMessage(event)?.source
|
|
123
120
|
if (source === null || typeof source !== 'object') return
|
|
121
|
+
const moduleId = moduleOf(source)
|
|
122
|
+
const stamp = contextStamp(session)
|
|
123
|
+
// 戳不可用 → 不记账:这条快路径无法在视图变化时失效,判定改由当前上下文扫描给出。
|
|
124
|
+
if (stamp === null) return
|
|
124
125
|
for (const field of ['plugin', 'kind']) {
|
|
125
126
|
const value = source[field]
|
|
126
127
|
if (typeof value !== 'string' || value.length === 0) continue
|
|
127
|
-
deliveredSessions(memo,
|
|
128
|
+
deliveredSessions(memo, memoKey(field, moduleId, value, stamp)).add(session.id)
|
|
128
129
|
}
|
|
129
130
|
// v4 起生产者身份挂在 kind(`plugin:<name>`),旧日志才有 plugin 字段;
|
|
130
131
|
// 两种形态都补记到 plugin: 账本,alreadyDelivered 的 `confirmed('plugin', …)` 才不会漏。
|
|
131
132
|
const plugin = pluginIdentityOf(source)
|
|
132
|
-
if (plugin !== undefined && source.plugin === undefined)
|
|
133
|
+
if (plugin !== undefined && source.plugin === undefined) {
|
|
134
|
+
deliveredSessions(memo, memoKey('plugin', moduleId, plugin, stamp)).add(session.id)
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
/** dedupe=session
|
|
138
|
+
/** dedupe=session:本会话**当前上下文**里是否已有该身份的已确认投递(快路径 + 当前上下文真相)。 */
|
|
136
139
|
function alreadyDelivered(config, session, memo) {
|
|
137
|
-
|
|
140
|
+
if (config.linkedDelivery) return config.mergeMode === 'merged'
|
|
141
|
+
? injectedMessage(config, session) !== undefined : findActionDelivery(session, config) !== undefined
|
|
142
|
+
const stamp = contextStamp(session)
|
|
143
|
+
// 模块维有两种命中:本模块的键,以及升级前注入(无模块维)那份的保守键。
|
|
144
|
+
const modules = [moduleIdOf(config, 'sourceModuleId') ?? '', '']
|
|
145
|
+
// 戳不可用(有 surface 却无代次)→ 不查 memo:那里记的「已投递」可能已被视图改写。
|
|
146
|
+
const confirmed = (field, value) => stamp !== null && modules.some((moduleId) =>
|
|
147
|
+
memo.get(memoKey(field, moduleId, value, stamp))?.has(session.id) === true)
|
|
138
148
|
if (confirmed('plugin', identityOf(config))) return true
|
|
139
149
|
if (typeof config.sourceKind === 'string' && confirmed('kind', config.sourceKind)) return true
|
|
140
|
-
|
|
150
|
+
const message = injectedMessage(config, session)
|
|
151
|
+
if (message === undefined) return false
|
|
152
|
+
// 慢路径命中即按既有入口回填快路径(真相仍是当前上下文,不直接写 memo):
|
|
153
|
+
// 无 id 的会话不记账——memo 的键就是 session.id,记了会让两个无 id 会话串味。
|
|
154
|
+
if (session.id !== undefined) confirmDelivered(memo, session, { data: { message } })
|
|
155
|
+
return true
|
|
141
156
|
}
|
|
142
157
|
|
|
143
158
|
/**
|
|
@@ -148,7 +163,7 @@ const PRE_STEP_ROLE = 'user'
|
|
|
148
163
|
|
|
149
164
|
/**
|
|
150
165
|
* 记录一次角色降级:保留原角色、明确告警、不改正文,也不把非法值传给宿主。
|
|
151
|
-
*
|
|
166
|
+
* 同一句告警只记一次(warnOnce 按消息去重),且不记录正文或用户内容。
|
|
152
167
|
*/
|
|
153
168
|
function downgradeRole(config, requested, warnOnce) {
|
|
154
169
|
if (typeof requested !== 'string' || requested === PRE_STEP_ROLE) return undefined
|
|
@@ -157,21 +172,33 @@ function downgradeRole(config, requested, warnOnce) {
|
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
/** 构造默认 user 消息;策略返回完整 patch 时覆盖对应字段,但非法角色在出口兜底降级。 */
|
|
160
|
-
function buildMessage(config, resolved, warnOnce) {
|
|
175
|
+
function buildMessage(config, resolved, warnOnce, session) {
|
|
161
176
|
const text = typeof resolved.text === 'string' ? resolved.text : ''
|
|
162
177
|
const defaultContent = config.texts.length > 0
|
|
163
178
|
? config.texts.map((item) => ({ type: 'text', text: item }))
|
|
164
179
|
: [{ type: 'text', text }]
|
|
165
|
-
|
|
180
|
+
// 盖章身份与 alreadyDelivered 的查找键同源(identityOf):显式 identity 才真正共享去重,
|
|
181
|
+
// 也不会与他卡的 id 误撞;merged 组 identityOf 本就等于 mergedIdentity,存量输出不变。
|
|
182
|
+
const sourceValue = identityOf(config)
|
|
166
183
|
// 策略 patch(templateFile 的 role 等)与配置声明都必须经过同一出口判定。
|
|
167
184
|
const requested = downgradeRole(config, typeof resolved.role === 'string' ? resolved.role : config.role, warnOnce)
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
// 模块维与 `identityStampOf` 同源(`moduleIdOf`:空串与缺失同等):两条分支必须盖同一份章,
|
|
186
|
+
// 否则解析器自带 source 的消息比合成分支多带一个空模块维,memo 与判定就会分叉。
|
|
187
|
+
const moduleId = moduleIdOf(config, 'sourceModuleId')
|
|
188
|
+
const source = resolved.source !== null && typeof resolved.source === 'object'
|
|
189
|
+
// 解析器自带的 source 也要盖章:否则这类消息(如 fill=instruction-hint 的
|
|
190
|
+
// `{ kind: 'instruction-hint' }`)只带 kind 通道,dedupe=session 每步重复注入。
|
|
191
|
+
// 解析器自带的 kind 优先(缺省 kind 只在它缺席时补),plugin 只在其非空时覆盖身份。
|
|
192
|
+
? {
|
|
193
|
+
...resolved.source,
|
|
194
|
+
plugin: typeof resolved.source.plugin === 'string' && resolved.source.plugin.length > 0 ? resolved.source.plugin : sourceValue,
|
|
195
|
+
...(resolved.source.kind === undefined ? { kind: identityStampOf({ ...config, sourceKind: undefined }).kind } : {}),
|
|
196
|
+
...(moduleId === undefined ? {} : { moduleId }),
|
|
197
|
+
}
|
|
170
198
|
: {
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
plugin: sourceValue,
|
|
199
|
+
// 模块维是身份判据的新一维,但**不并进身份字符串**:`source.plugin` 的格式是
|
|
200
|
+
// blockPlugins 的精确匹配契约,一字不改;整模块复制的区分只靠这个独立字段。
|
|
201
|
+
...identityStampOf(config),
|
|
175
202
|
...(typeof config.form === 'string' ? { form: config.form } : {}),
|
|
176
203
|
...(typeof config.summary === 'string' && config.summary.length > 0 ? { summary: config.summary } : {}),
|
|
177
204
|
}
|
|
@@ -179,7 +206,41 @@ function buildMessage(config, resolved, warnOnce) {
|
|
|
179
206
|
id: typeof resolved.id === 'string' && resolved.id.length > 0 ? resolved.id : newMessageId(config.id),
|
|
180
207
|
role: PRE_STEP_ROLE,
|
|
181
208
|
content: Array.isArray(resolved.content) ? resolved.content : defaultContent,
|
|
182
|
-
source: requested === undefined ?
|
|
209
|
+
source: { ...source, ...actionStamp(config, session), ...(requested === undefined ? {} : { requestedRole: requested }) },
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 本步(一个 pre-step 批次,含被 `ruleActions` 切开的多次 flush)的批级快照:
|
|
215
|
+
* 合格配置集合、模板位置身份集合与 ST 世界书选择,全部在批首取定一次。
|
|
216
|
+
*
|
|
217
|
+
* 三者必须同一集合:`qualified` 每配置只判一次(R02),ST 世界书的组互斥与概率/
|
|
218
|
+
* 粘滞窗口按**步**成立——旧实现每个 flush 各选一次,同组两条会各赢一条(F06)。
|
|
219
|
+
* 返回 undefined = 本批无法执行(reject / 无 agent / 无 session),调用方原样返回。
|
|
220
|
+
*/
|
|
221
|
+
function batchScope(options, frame) {
|
|
222
|
+
const { agent, decision, configs, promotion, warnOnce } = options
|
|
223
|
+
const session = agent?.session
|
|
224
|
+
if (decision === null || typeof decision !== 'object' || decision.kind === 'reject'
|
|
225
|
+
|| agent === undefined || session === undefined) return undefined
|
|
226
|
+
const messages = Array.isArray(decision.messages) ? [...decision.messages] : []
|
|
227
|
+
const { main, withSubagents } = promotion
|
|
228
|
+
const delegated = isDelegated(session)
|
|
229
|
+
// 本批资格判定(唯一实现):层通道、受众、模型、晋升与 live 规则条件。
|
|
230
|
+
// 条件判定放在去重之前:未命中的配置不算「已注入」,条件恢复后仍应能注入。
|
|
231
|
+
// ST 模板的跨配置变量帧按 order 预求值,但只有这里的获准集合才允许产生副作用
|
|
232
|
+
// (未命中的 setter 提前 setvar 会污染同批 reader)——渲染器不再复制判定。
|
|
233
|
+
const qualified = configs.filter((config) => config.layer === 'pre-step'
|
|
234
|
+
&& !(config.audience === 'main' && delegated)
|
|
235
|
+
&& !(config.audience === 'subagent' && !delegated)
|
|
236
|
+
&& matchesModel(config.modelScope, agent.options?.model)
|
|
237
|
+
&& (config.promotion !== 'main' || main.status(agent).promoted)
|
|
238
|
+
&& (config.promotion !== 'include-subagents' || withSubagents.status(agent).promoted)
|
|
239
|
+
&& actionMatches(config, frame))
|
|
240
|
+
return {
|
|
241
|
+
qualified: new Set(qualified),
|
|
242
|
+
contextActivation: selectContextActivation(qualified, session, messages, warnOnce, config => config[SESSION_VARIABLES_DISABLED] === true
|
|
243
|
+
? { module: {}, global: {} } : config.variableStore?.read(config.sourceModuleId) ?? { module: config.variables, global: {} }),
|
|
183
244
|
}
|
|
184
245
|
}
|
|
185
246
|
|
|
@@ -193,65 +254,74 @@ function buildMessage(config, resolved, warnOnce) {
|
|
|
193
254
|
*
|
|
194
255
|
* @param options.configs 已过滤 enabled 与互斥组的最终提示词配置列表。
|
|
195
256
|
* @param options.promotion { main, withSubagents } 晋升追踪器(真相在持久事件流)。
|
|
196
|
-
* @param options.memo 已确认投递身份的进程内 session
|
|
257
|
+
* @param options.memo 已确认投递身份的进程内 session 去重快路径(纯缓存,丢了只多一次
|
|
258
|
+
* 当前上下文扫描;见 confirmDelivered)。
|
|
197
259
|
* @returns 注入后的 decision;reject、缺 agent/session、全部跳过或异常时原样返回。
|
|
198
260
|
*/
|
|
199
261
|
export async function runPreStepBatch(options) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
262
|
+
if (!Array.isArray(options.sources)) {
|
|
263
|
+
throw new TypeError('runPreStepBatch requires a sources array')
|
|
264
|
+
}
|
|
265
|
+
const sources = options.sources
|
|
266
|
+
const composed = composePreStepSources(sources)
|
|
267
|
+
if (composed.configs.length === 0 && composed.ruleActions.length === 0) return options.decision
|
|
268
|
+
const ctx = options.ctx ?? sources[0]?.ctx
|
|
269
|
+
const initialFrame = options.ruleFrame ?? ruleFrame('agent/pre-step', [{ agent: options.agent, messages: options.decision?.messages ?? [] }], options.warnOnce, ctx, composed.report)
|
|
270
|
+
// 批级快照与单配置失败同款 fail-safe:资格判定或 ST 世界书选择抛错时只告警一次并跳过本步
|
|
271
|
+
// 注入(scope 留空 → runPromptConfigBatch 原样返回 decision),绝不把异常抛进
|
|
272
|
+
// agent/pre-step waterfall。世界书键的 `interpolateVariables` 在 selectStWorldBook 的
|
|
273
|
+
// per-config try 之外,配置错误不得卡死会话。
|
|
274
|
+
const batchOptions = { ...options, ...composed, ctx }
|
|
275
|
+
let scope
|
|
276
|
+
try {
|
|
277
|
+
scope = batchScope(batchOptions, initialFrame)
|
|
278
|
+
} catch (error) {
|
|
279
|
+
options.warnOnce?.(`${name}: prompt config failed, skipping: ${String((error && error.message) || error)}`)
|
|
280
|
+
}
|
|
281
|
+
// 同一批的配置与动作按配置序号统一排序;没有规则动作时也必须排,不能依赖来源注册顺序。
|
|
282
|
+
const orderedConfigs = [...composed.configs].sort(compareConfigSequence)
|
|
283
|
+
if (composed.ruleActions.length === 0) {
|
|
284
|
+
return runPromptConfigBatch({ ...batchOptions, configs: orderedConfigs, ruleFrame: initialFrame, scope })
|
|
285
|
+
}
|
|
286
|
+
const entries = [...orderedConfigs.map(config => ({ ...config, config })), ...composed.ruleActions].sort(compareConfigSequence)
|
|
203
287
|
let decision = options.decision
|
|
204
288
|
if (decision?.kind === 'reject') return decision
|
|
205
289
|
let pending = []
|
|
206
290
|
const placement = { beforeAll: new Set(), afterUser: new Set() }
|
|
207
291
|
const flush = async () => {
|
|
208
|
-
if (pending.length) decision = await runPromptConfigBatch({ ...
|
|
292
|
+
if (pending.length) decision = await runPromptConfigBatch({ ...batchOptions, decision, configs: pending, ruleFrame: initialFrame, placement, scope })
|
|
209
293
|
pending = []
|
|
210
294
|
}
|
|
211
295
|
for (const entry of entries) {
|
|
212
296
|
if (entry.config) { pending.push(entry.config); continue }
|
|
213
297
|
await flush()
|
|
214
|
-
if (
|
|
298
|
+
if (actionMatches(entry, initialFrame)) decision = await entry.handler(options.payload ?? initialFrame.args[0], () => decision, initialFrame)
|
|
215
299
|
}
|
|
216
300
|
await flush()
|
|
217
301
|
return decision
|
|
218
302
|
}
|
|
219
303
|
|
|
220
304
|
async function runPromptConfigBatch(options) {
|
|
221
|
-
const { ctx, agent, decision, configs,
|
|
305
|
+
const { ctx, agent, decision, configs, memo, warnOnce, scope } = options
|
|
222
306
|
if (decision === null || typeof decision !== 'object') return decision
|
|
223
307
|
if (decision.kind === 'reject') return decision
|
|
224
308
|
if (agent === undefined) return decision
|
|
225
309
|
const session = agent.session
|
|
226
310
|
if (session === undefined) return decision
|
|
227
|
-
|
|
311
|
+
// 批级快照由 runPreStepBatch 在批首取定;这里只消费,不再重新判定。
|
|
312
|
+
if (scope === undefined) return decision
|
|
228
313
|
try {
|
|
229
314
|
const messages = Array.isArray(decision.messages) ? [...decision.messages] : []
|
|
230
|
-
// 条件判定的匹配对象在本批进入时取定:批次内后续注入不改变本批的判定依据。
|
|
231
|
-
const userText = userMessagesText(messages)
|
|
232
315
|
let changed = false
|
|
233
316
|
|
|
234
317
|
const due = []
|
|
235
|
-
// 本批资格判定(唯一实现):层通道、受众、模型、晋升与声明式条件。
|
|
236
|
-
// 条件判定放在去重之前:未命中的配置不算「已注入」,条件恢复后仍应能注入。
|
|
237
|
-
// ST 模板的跨配置变量帧按 order 预求值,但只有这里的获准集合才允许产生副作用
|
|
238
|
-
// (未命中的 setter 提前 setvar 会污染同批 reader)——渲染器不再复制判定。
|
|
239
|
-
const delegated = isDelegated(session)
|
|
240
|
-
const qualified = (config) => config.layer === 'pre-step'
|
|
241
|
-
&& !(config.audience === 'main' && delegated)
|
|
242
|
-
&& !(config.audience === 'subagent' && !delegated)
|
|
243
|
-
&& matchesModel(config.modelScope, agent.options?.model)
|
|
244
|
-
&& (config.promotion !== 'main' || main.status(agent).promoted)
|
|
245
|
-
&& (config.promotion !== 'include-subagents' || withSubagents.status(agent).promoted)
|
|
246
|
-
&& conditionHit(config, { userText })
|
|
247
|
-
&& actionMatches(config, options.ruleFrame)
|
|
248
|
-
const qualifiedConfigs = configs.filter(qualified)
|
|
249
|
-
// 协调器为绑定来源 ctx 会复制 config;renderSt 函数身份在副本间保持不变。
|
|
250
|
-
const eligible = new Set(qualifiedConfigs.map(config => config.renderSt))
|
|
251
|
-
const stWorldBook = selectStWorldBook(qualifiedConfigs, session, messages, warnOnce)
|
|
252
318
|
for (const config of configs) {
|
|
253
319
|
try {
|
|
254
|
-
if (!qualified(config)) continue
|
|
320
|
+
if (!scope.qualified.has(config)) continue
|
|
321
|
+
if (config.position === 'after-user' && !messages.some(message => message?.source?.kind === 'user')) {
|
|
322
|
+
warnOnce(`${name}: after-user config(s) ${config.id} skipped this batch — no user message anchor`)
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
255
325
|
|
|
256
326
|
if (config.dedupe === 'session') {
|
|
257
327
|
if (alreadyDelivered(config, session, memo)) continue
|
|
@@ -259,31 +329,33 @@ async function runPromptConfigBatch(options) {
|
|
|
259
329
|
continue
|
|
260
330
|
}
|
|
261
331
|
|
|
262
|
-
const
|
|
332
|
+
const signal = options.payload?.signal
|
|
333
|
+
if (signal?.aborted) continue
|
|
334
|
+
const resolved = await config.resolve({ ctx, agent, session, signal, decision, messages, contextActivationSelected: scope.contextActivation.has(config) })
|
|
335
|
+
if (signal?.aborted) continue
|
|
263
336
|
if (resolved === null || resolved === undefined) continue
|
|
264
337
|
const patched = { ...resolved }
|
|
265
|
-
//
|
|
266
|
-
//(含中文 key 如 {{接受值}}——引擎正则已支持 Unicode 字母)。
|
|
267
|
-
// 会话变量(session_var 工具维护)覆盖配置/预设默认:resolved > 会话 > params > 配置。
|
|
338
|
+
// 按来源读取最新持久值;无变量正文不因另一个坏候选的读盘失败撤回。
|
|
268
339
|
const mergedVars = {
|
|
269
|
-
...config.
|
|
270
|
-
|
|
271
|
-
...sessionVarsSnapshot(session),
|
|
340
|
+
...(config[SESSION_VARIABLES_DISABLED] === true ? {} : config.params?.templateEvaluation !== true && [patched.text, ...config.texts].some(text => typeof text === 'string' && text.includes('{{'))
|
|
341
|
+
? config.variableStore?.read(config.sourceModuleId).effective ?? config.variables : config.variables),
|
|
272
342
|
...(resolved.variables !== null && typeof resolved.variables === 'object' ? resolved.variables : {}),
|
|
273
343
|
}
|
|
274
|
-
if (
|
|
275
|
-
patched.text = config.
|
|
344
|
+
if (config.params?.templateEvaluation === true) {
|
|
345
|
+
patched.text = renderTemplate({ ...config, texts: config.texts.length ? config.texts : [patched.text ?? ''] }, { agent, signal }, { enabled: config[SESSION_VARIABLES_DISABLED] !== true, warn: warnOnce })
|
|
276
346
|
patched.content = patched.text.length > 0 ? [{ type: 'text', text: patched.text }] : []
|
|
277
347
|
} else if (typeof patched.text === 'string') {
|
|
278
348
|
// 提示词配置级模板变量 + filler 变量 + 内置环境变量插值。
|
|
279
|
-
|
|
349
|
+
// 第 5 参是模板位置身份(本配置的 id):{{pick}} 的 seed 只认「同会话 + 同模板位置 + 同一次出现」,
|
|
350
|
+
// 缺省空串会让不同配置的首个 pick 退化成同一个 seed。
|
|
351
|
+
patched.text = interpolateVariables(patched.text, mergedVars, session, undefined, config.id)
|
|
280
352
|
}
|
|
281
|
-
if (config.texts.length > 0 &&
|
|
353
|
+
if (config.texts.length > 0 && config.params?.templateEvaluation !== true) {
|
|
282
354
|
const blocks = config.texts
|
|
283
|
-
.map((item) => interpolateVariables(item, mergedVars, session))
|
|
355
|
+
.map((item, index) => interpolateVariables(item, mergedVars, session, undefined, `${config.id}#${index}`))
|
|
284
356
|
.filter((item) => item.length > 0)
|
|
285
357
|
.map((item) => ({ type: 'text', text: item }))
|
|
286
|
-
|
|
358
|
+
patched.content = blocks
|
|
287
359
|
}
|
|
288
360
|
const hasText = typeof patched.text === 'string' && patched.text.length > 0
|
|
289
361
|
const hasContent = Array.isArray(patched.content) && patched.content.length > 0
|
|
@@ -317,7 +389,7 @@ async function runPromptConfigBatch(options) {
|
|
|
317
389
|
const planned = []
|
|
318
390
|
for (const group of orderedGroups) {
|
|
319
391
|
const base = group[0]
|
|
320
|
-
const message = buildMessage(base.config, base.resolved, warnOnce)
|
|
392
|
+
const message = buildMessage(base.config, base.resolved, warnOnce, agent.session)
|
|
321
393
|
if (group.length > 1) {
|
|
322
394
|
message.content = group.flatMap((entry) => {
|
|
323
395
|
if (Array.isArray(entry.resolved.content)) return entry.resolved.content
|
|
@@ -326,7 +398,7 @@ async function runPromptConfigBatch(options) {
|
|
|
326
398
|
: []
|
|
327
399
|
})
|
|
328
400
|
if (message.source !== null && typeof message.source === 'object') {
|
|
329
|
-
message.source = { ...message.source, plugin:
|
|
401
|
+
message.source = { ...message.source, plugin: identityOf(base.config) }
|
|
330
402
|
}
|
|
331
403
|
// 合并组的角色由首条配置决定;其余成员声明的非法角色同样要告警并留下降级事实。
|
|
332
404
|
for (const entry of group.slice(1)) {
|
|
@@ -345,7 +417,7 @@ async function runPromptConfigBatch(options) {
|
|
|
345
417
|
// 同位置批量插入:planned 已按 order 升序,多元素 splice/unshift/push 保持该顺序。
|
|
346
418
|
// 去重记账不在这里:候选被外层门控剥离时也必须保持「未投递」,由 confirmDelivered 确认。
|
|
347
419
|
const markGroup = (group) => {
|
|
348
|
-
for (const entry of group)
|
|
420
|
+
for (const entry of group) scope.contextActivation.commit?.(entry.config)
|
|
349
421
|
}
|
|
350
422
|
const beforeAll = planned.filter((item) => item.position === 'before-all')
|
|
351
423
|
const afterUser = planned.filter((item) => item.position !== 'before-all' && item.position !== 'after-all')
|
|
@@ -359,6 +431,12 @@ async function runPromptConfigBatch(options) {
|
|
|
359
431
|
changed = true
|
|
360
432
|
}
|
|
361
433
|
const userIndex = messages.findIndex((item) => item?.source?.kind === 'user')
|
|
434
|
+
if (afterUser.length > 0 && userIndex < 0) {
|
|
435
|
+
// 本批没有真实用户消息可锚定:本步跳过(不记账,next 含 user 的批仍会注入)。不兜底到
|
|
436
|
+
// before-all / after-all —— 「锚点是真实用户消息、不挂到插件消息之后」是插入点契约。
|
|
437
|
+
// 按消息去重后,不同配置组合各记一次,不再互相占名额。
|
|
438
|
+
warnOnce(`${name}: after-user config(s) ${afterUser.map((item) => item.group[0].config.id).join(', ')} skipped this batch — no user message anchor`)
|
|
439
|
+
}
|
|
362
440
|
if (afterUser.length > 0 && userIndex >= 0) {
|
|
363
441
|
const last = options.placement === undefined ? -1 : messages.findLastIndex(message => options.placement.afterUser.has(message))
|
|
364
442
|
messages.splice(Math.max(userIndex, last) + 1, 0, ...afterUser.map((item) => item.message))
|
|
@@ -379,14 +457,6 @@ async function runPromptConfigBatch(options) {
|
|
|
379
457
|
}
|
|
380
458
|
}
|
|
381
459
|
|
|
382
|
-
/**
|
|
383
|
-
* 把一组运行时提示词配置装配为注入执行器(独立模式 / 协调器管理模式的接线口)。
|
|
384
|
-
*
|
|
385
|
-
* @param options.prepend 是否以 prepend 注册本地 pre-step(合并行恒 true;由参数决定)。
|
|
386
|
-
* @param options.sourceId 协调器里的来源 id(默认取首条配置 id 派生)。
|
|
387
|
-
* @param options.officialInstructions 本 mount 的组合是否仍挂着官方指令加载行
|
|
388
|
-
* (负责人冲突事实;协调器据此拒绝同时注入文件正文)。
|
|
389
|
-
*/
|
|
390
460
|
function effectivePromptConfigs(configs) {
|
|
391
461
|
const list = configs.filter((config) => config !== undefined && config !== null)
|
|
392
462
|
const claimedGroups = new Set()
|
|
@@ -400,120 +470,135 @@ function effectivePromptConfigs(configs) {
|
|
|
400
470
|
})
|
|
401
471
|
}
|
|
402
472
|
|
|
403
|
-
/**
|
|
404
|
-
|
|
473
|
+
/**
|
|
474
|
+
* 唯一的 pre-step 装配入口:来源自带 ctx/onOutcome/promotion,本函数只做固定效果接线、
|
|
475
|
+
* 协调器注册与(无协调器时的)单 listener 批执行。
|
|
476
|
+
*
|
|
477
|
+
* 所有来源必须共享同一个 ctx;没有 promotion 时由协调器或本次调用补一个 fallback owner,
|
|
478
|
+
* 已有 owner 不一致则 fail loud。
|
|
479
|
+
*/
|
|
480
|
+
export function applyPromptConfigSources(sources, options = {}) {
|
|
481
|
+
if (!Array.isArray(sources) || sources.length === 0) return () => {}
|
|
482
|
+
const ctx = sources[0].ctx
|
|
483
|
+
if (sources.some(source => source.ctx !== ctx)) {
|
|
484
|
+
throw new TypeError('prompt-tool: all pre-step sources in one apply call must share the same ctx')
|
|
485
|
+
}
|
|
486
|
+
const warnOnce = createWarnOnce(ctx, name)
|
|
405
487
|
const selected = sources.map(source => ({ ...source, configs: effectivePromptConfigs(source.configs) }))
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
releaseLayers()
|
|
488
|
+
const owner = promotionOwnerOf(selected)
|
|
489
|
+
const coordinator = coordinatorOf(ctx)
|
|
490
|
+
const coordinatorPromotion = coordinator?.promotion
|
|
491
|
+
if (coordinator !== undefined && coordinatorPromotion === undefined) {
|
|
492
|
+
throw new TypeError('prompt-tool: pre-step coordinator v2 must expose a promotion owner')
|
|
412
493
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
494
|
+
if (coordinatorPromotion !== undefined && owner !== undefined && owner !== coordinatorPromotion) {
|
|
495
|
+
throw new TypeError('prompt-tool: pre-step sources must use the active coordinator promotion owner')
|
|
496
|
+
}
|
|
497
|
+
// 执行 owner 唯一:有来源 owner 就用它,否则由本次 apply 建 fallback pair;协调器存在时
|
|
498
|
+
// 批执行与注册统一用协调器 pair,协调器消失/替换时再切回 standalone/新 pair。
|
|
499
|
+
const fallbackBacked = owner === undefined
|
|
500
|
+
const fallbackPromotion = fallbackBacked
|
|
501
|
+
? {
|
|
502
|
+
main: createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: false }),
|
|
503
|
+
withSubagents: createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: true }),
|
|
504
|
+
}
|
|
505
|
+
: undefined
|
|
506
|
+
const standalonePromotion = owner ?? fallbackPromotion
|
|
507
|
+
let activePromotion = coordinatorPromotion ?? standalonePromotion
|
|
508
|
+
let prepared = selected.map(source => ({ ...source, promotion: activePromotion }))
|
|
509
|
+
let feedsPromotion = fallbackBacked && activePromotion === fallbackPromotion
|
|
510
|
+
// 先验证固定效果的目标并完成 owner 解析;owner 不一致时不能留下 wireLayers 监听。
|
|
511
|
+
const releaseLayers = options.layers === false ? () => {} : wireLayers(ctx, selected.flatMap(source => source.configs)
|
|
512
|
+
.filter(config => config.layer !== 'pre-step'), warnOnce, { agent: options.agent })
|
|
422
513
|
const injectedMemo = new Map()
|
|
423
|
-
const main = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: false })
|
|
424
|
-
const withSubagents = createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: true })
|
|
425
514
|
const observerDisposer = ctx.on('session/event', (session, event) => {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
515
|
+
if (feedsPromotion) {
|
|
516
|
+
activePromotion.main.observe(session, event)
|
|
517
|
+
activePromotion.withSubagents.observe(session, event)
|
|
518
|
+
}
|
|
429
519
|
confirmDelivered(injectedMemo, session, event)
|
|
430
520
|
})
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const prepend = options.prepend === true ||
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
521
|
+
// shortcut: prepend 只认 options 与 configs;纯 ruleAction 的 pre-step-filter 在无协调器的
|
|
522
|
+
// standalone 路径拿不到。出现第二个无协调器的 pre-step 消费者时,把「需要最外层」上收到动作语义再统一。
|
|
523
|
+
const prepend = options.prepend === true || prepared.some(source => source.configs.some(config => config.prepend === true))
|
|
524
|
+
const registrations = new Map()
|
|
525
|
+
let active = true
|
|
526
|
+
const releaseRegistrations = () => {
|
|
527
|
+
for (const dispose of registrations.values()) {
|
|
528
|
+
try { dispose() } catch {}
|
|
529
|
+
}
|
|
530
|
+
registrations.clear()
|
|
439
531
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
532
|
+
const setOwner = (promotion) => {
|
|
533
|
+
activePromotion = promotion
|
|
534
|
+
feedsPromotion = fallbackBacked && promotion === fallbackPromotion
|
|
535
|
+
prepared = selected.map(source => ({ ...source, promotion }))
|
|
536
|
+
}
|
|
537
|
+
const registerAll = (target) => {
|
|
446
538
|
try {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
539
|
+
for (const source of prepared) {
|
|
540
|
+
if (registrations.has(source.sourceId)) throw new TypeError(`prompt-tool: duplicate pre-step sourceId ${source.sourceId}`)
|
|
541
|
+
registrations.set(source.sourceId, target.registerPreset(source.sourceId, source) ?? (() => {}))
|
|
542
|
+
}
|
|
543
|
+
} catch (error) {
|
|
544
|
+
releaseRegistrations()
|
|
545
|
+
throw error
|
|
450
546
|
}
|
|
451
547
|
}
|
|
452
|
-
let
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// 非 pre-step 提示词配置接入各自声明的官方层级通道(system-section /
|
|
462
|
-
// runtime-context / agent-request / llm-stream / tool-pipeline)。
|
|
463
|
-
const releaseLayers = options.layers === false ? () => {}
|
|
464
|
-
: wireLayers(ctx, effectiveList.filter((config) => config.layer !== 'pre-step'), warnOnce)
|
|
465
|
-
|
|
466
|
-
// 协调器已在(正常装配顺序:插件先加载、预设后挂载)时立即登记来源;
|
|
467
|
-
// 迟到/消失由下面的监听器按「先撤旧再启新」处理。
|
|
468
|
-
let registeredCoordinator = coordinatorOf(ctx)
|
|
469
|
-
if (registeredCoordinator !== undefined) {
|
|
470
|
-
registration = registeredCoordinator.registerPreset(ctx, sourceId, source) ?? null
|
|
548
|
+
let registeredCoordinator = coordinator
|
|
549
|
+
if (coordinator !== undefined) {
|
|
550
|
+
try {
|
|
551
|
+
registerAll(coordinator)
|
|
552
|
+
} catch (error) {
|
|
553
|
+
try { observerDisposer() } catch {}
|
|
554
|
+
releaseLayers()
|
|
555
|
+
throw error
|
|
556
|
+
}
|
|
471
557
|
}
|
|
472
|
-
|
|
473
558
|
const preStepDisposer = ctx.on('agent/pre-step', async (payload, next) => {
|
|
474
|
-
// waterfall 可已捕获随后被释放的回调;失效来源不得向新协调器重新注册。
|
|
475
559
|
if (!active) return next()
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (coordinator === undefined) {
|
|
560
|
+
const current = coordinatorOf(ctx)
|
|
561
|
+
if (current !== registeredCoordinator) {
|
|
562
|
+
releaseRegistrations()
|
|
563
|
+
if (current === undefined) {
|
|
481
564
|
warnOnce(`${name}: pre-step coordinator unavailable, resuming standalone execution`)
|
|
565
|
+
setOwner(standalonePromotion)
|
|
566
|
+
} else {
|
|
567
|
+
if (current.promotion === undefined) {
|
|
568
|
+
warnOnce(`${name}: pre-step coordinator v2 owner unavailable, keeping standalone execution`)
|
|
569
|
+
setOwner(standalonePromotion)
|
|
570
|
+
} else {
|
|
571
|
+
setOwner(current.promotion)
|
|
572
|
+
}
|
|
482
573
|
}
|
|
483
|
-
registeredCoordinator =
|
|
574
|
+
registeredCoordinator = current
|
|
484
575
|
}
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
if (registration === null) registration = coordinator.registerPreset(ctx, sourceId, source) ?? null
|
|
576
|
+
if (current !== undefined && current.promotion !== undefined) {
|
|
577
|
+
if (registrations.size === 0) registerAll(current)
|
|
488
578
|
return next()
|
|
489
579
|
}
|
|
490
580
|
const decision = await next()
|
|
491
581
|
if (!active) return decision
|
|
492
582
|
return runPreStepBatch({
|
|
583
|
+
sources: prepared,
|
|
493
584
|
ctx,
|
|
494
585
|
payload,
|
|
495
586
|
agent: payload?.agent,
|
|
496
587
|
decision,
|
|
497
|
-
configs: effectiveList,
|
|
498
|
-
ruleActions: source.ruleActions,
|
|
499
|
-
promotion,
|
|
500
588
|
memo: injectedMemo,
|
|
501
589
|
warnOnce,
|
|
502
590
|
})
|
|
503
591
|
}, { prepend })
|
|
504
|
-
|
|
592
|
+
const release = () => {
|
|
593
|
+
active = false
|
|
594
|
+
releaseRegistrations()
|
|
595
|
+
injectedMemo.clear()
|
|
596
|
+
}
|
|
597
|
+
keepDisposer(ctx, release)
|
|
505
598
|
return () => {
|
|
506
599
|
release()
|
|
507
|
-
try {
|
|
508
|
-
|
|
509
|
-
} catch {
|
|
510
|
-
// 宿主已释放该 fiber 时监听器可能已不在:忽略。
|
|
511
|
-
}
|
|
512
|
-
try {
|
|
513
|
-
observerDisposer()
|
|
514
|
-
} catch {
|
|
515
|
-
// 同上。
|
|
516
|
-
}
|
|
600
|
+
try { preStepDisposer() } catch {}
|
|
601
|
+
try { observerDisposer() } catch {}
|
|
517
602
|
releaseLayers()
|
|
518
603
|
}
|
|
519
604
|
}
|