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
|
@@ -7,8 +7,10 @@ import { createSessionStatePredicate } from './session.mjs'
|
|
|
7
7
|
import { createPresetPredicate } from './preset.mjs'
|
|
8
8
|
import { createScopePredicate } from './scope.mjs'
|
|
9
9
|
import { createAnchorPredicate } from './anchor.mjs'
|
|
10
|
+
import { createVariablePredicate } from './variable.mjs'
|
|
10
11
|
import { withAvailableInput } from './availability.mjs'
|
|
11
12
|
import { composite } from './composite.mjs'
|
|
13
|
+
import { COMPOSITE_OPERATORS } from './operators.mjs'
|
|
12
14
|
export * from './text.mjs'
|
|
13
15
|
export * from './phase.mjs'
|
|
14
16
|
export * from './source.mjs'
|
|
@@ -18,7 +20,8 @@ export * from './session.mjs'
|
|
|
18
20
|
export * from './preset.mjs'
|
|
19
21
|
export * from './scope.mjs'
|
|
20
22
|
export * from './anchor.mjs'
|
|
21
|
-
export
|
|
23
|
+
export * from './variable.mjs'
|
|
24
|
+
export { subjectOf, channelTextSubjects, channelFactSubjects, FACT_PREDICATE_SUBJECTS } from './subject.mjs'
|
|
22
25
|
export { composite } from './composite.mjs'
|
|
23
26
|
export const PREDICATE_FACTORIES = Object.freeze({
|
|
24
27
|
text: createTextPredicate,
|
|
@@ -30,13 +33,14 @@ export const PREDICATE_FACTORIES = Object.freeze({
|
|
|
30
33
|
preset: createPresetPredicate,
|
|
31
34
|
scope: createScopePredicate,
|
|
32
35
|
anchor: createAnchorPredicate,
|
|
36
|
+
variable: createVariablePredicate,
|
|
33
37
|
})
|
|
34
|
-
export
|
|
38
|
+
export { COMPOSITE_OPERATORS }
|
|
35
39
|
|
|
36
40
|
const objectOrUndefined = (value, label) => {
|
|
37
41
|
if (value === undefined || value === null) return undefined
|
|
38
42
|
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
39
|
-
throw new TypeError(`
|
|
43
|
+
throw new TypeError(`rule.if: ${label} must be an object`)
|
|
40
44
|
}
|
|
41
45
|
return value
|
|
42
46
|
}
|
|
@@ -44,15 +48,15 @@ const objectOrUndefined = (value, label) => {
|
|
|
44
48
|
export function compileWhen(node, context = {}) {
|
|
45
49
|
if (node === undefined || node === null) return undefined
|
|
46
50
|
if (typeof node !== 'object' || Array.isArray(node)) {
|
|
47
|
-
throw new TypeError('
|
|
51
|
+
throw new TypeError('rule.if: when must be an object (a composite node or a predicate node)')
|
|
48
52
|
}
|
|
49
53
|
const keys = Object.keys(node)
|
|
50
54
|
if (keys.length === 0) {
|
|
51
|
-
throw new TypeError('
|
|
55
|
+
throw new TypeError('rule.if: when must not be an empty object — 空节点是「恒真」还是「写漏了」无法区分')
|
|
52
56
|
}
|
|
53
57
|
if (keys.length > 1) {
|
|
54
58
|
throw new TypeError(
|
|
55
|
-
`
|
|
59
|
+
`rule.if: when must declare exactly one key — got ${keys.sort().join(', ')}`
|
|
56
60
|
+ `(allowed: ${[...COMPOSITE_OPERATORS, ...Object.keys(PREDICATE_FACTORIES)].sort().join(', ')})`,
|
|
57
61
|
)
|
|
58
62
|
}
|
|
@@ -64,7 +68,7 @@ export function compileWhen(node, context = {}) {
|
|
|
64
68
|
return composite({ not: compileWhen(value, context) })
|
|
65
69
|
}
|
|
66
70
|
if (!Array.isArray(value) || value.length === 0) {
|
|
67
|
-
throw new TypeError(`
|
|
71
|
+
throw new TypeError(`rule.if: when.${key} must be a non-empty array of nodes`)
|
|
68
72
|
}
|
|
69
73
|
return composite({ [key]: value.map((child) => compileWhen(child, context)) })
|
|
70
74
|
}
|
|
@@ -72,14 +76,24 @@ export function compileWhen(node, context = {}) {
|
|
|
72
76
|
const factory = PREDICATE_FACTORIES[key]
|
|
73
77
|
if (factory === undefined) {
|
|
74
78
|
throw new TypeError(
|
|
75
|
-
`
|
|
79
|
+
`rule.if: unknown predicate ${JSON.stringify(key)}`
|
|
76
80
|
+ ` — known predicates: ${Object.keys(PREDICATE_FACTORIES).join(', ')}; composite: ${COMPOSITE_OPERATORS.join(', ')}`,
|
|
77
81
|
)
|
|
78
82
|
}
|
|
79
83
|
const options = objectOrUndefined(value, `when.${key}`) ?? {}
|
|
80
|
-
//
|
|
84
|
+
// 条件上下文只补宿主查询、共享晋升池和同规则动作引用,不建立第二套运行状态。
|
|
85
|
+
if (key === 'anchor') {
|
|
86
|
+
return withAvailableInput(key, options, createAnchorPredicate(options, context))
|
|
87
|
+
}
|
|
81
88
|
if (key === 'preset') {
|
|
82
89
|
return withAvailableInput(key, options, createPresetPredicate({ ...options, ctx: context.ctx, standingMountFor: context.standingMountFor }))
|
|
83
90
|
}
|
|
91
|
+
if (key === 'phase') {
|
|
92
|
+
return withAvailableInput(key, options, createPhasePredicate({ ...options, promotionPool: context.promotionPool }))
|
|
93
|
+
}
|
|
94
|
+
// 变量谓词自带三值处理:适配器缺失返回 UNAVAILABLE,不走通道事实的 availableInput。
|
|
95
|
+
if (key === 'variable') {
|
|
96
|
+
return createVariablePredicate(options, { store: context.variableStore, moduleId: context.moduleId })
|
|
97
|
+
}
|
|
84
98
|
return withAvailableInput(key, options, factory(options))
|
|
85
99
|
}
|
|
@@ -18,7 +18,13 @@ export function createPhasePredicate(options = {}) {
|
|
|
18
18
|
reasoningNegativePattern: options.reasoningNegativePattern,
|
|
19
19
|
reasoningFlags: options.reasoningFlags,
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// 订阅模式:追踪器从挂载缝的池里取(同配置复用、事件由池统一喂),本谓词**不再提供 observe**
|
|
22
|
+
// ——共用实例后重复喂会让门控步数翻倍(`applyEvent` 的 `steps += 1` 不幂等)。
|
|
23
|
+
// 池缺席(不经挂载缝直接调 `compileWhen` 的调用方,例如谓词单测):退回自建并自带 observe,
|
|
24
|
+
// 行为与从前一致。不订阅模式(作者显式要求):保持每次判定冷扫、且不给 observe——那正是
|
|
25
|
+
// 「不订阅」的语义,多一个入口只会诱使调用方以为状态被记住了。
|
|
26
|
+
const pooled = subscribe ? options.promotionPool : undefined
|
|
27
|
+
const tracker = pooled === undefined ? createEpochPromotion(promoteEvents, trackerOptions) : pooled.tracker(promoteEvents, trackerOptions)
|
|
22
28
|
// 载荷归一后 agent 在 `payload.agent`;旧的手工形态(载荷本身就是 agent)由 agentOf 兼容。
|
|
23
29
|
//
|
|
24
30
|
// `compacted`(2026-09-22 用户拍板「扩原语」):`status()` 本来就返回 `boundary`
|
|
@@ -47,7 +53,7 @@ export function createPhasePredicate(options = {}) {
|
|
|
47
53
|
return status.promoted === (promotedOption ?? true)
|
|
48
54
|
}
|
|
49
55
|
predicate.kind = 'phase'
|
|
50
|
-
//
|
|
51
|
-
if (subscribe) predicate.observe = (session, event) => tracker.observe(session, event)
|
|
56
|
+
// 只有「自建 + 订阅」才提供增量入口:池已经统一喂过,再给入口就是重复推进相位。
|
|
57
|
+
if (subscribe && pooled === undefined) predicate.observe = (session, event) => tracker.observe(session, event)
|
|
52
58
|
return predicate
|
|
53
59
|
}
|
|
@@ -46,7 +46,7 @@ export function agentPresetId(ctx, agent, standingMountFor) {
|
|
|
46
46
|
const mount = typeof standingMountFor === 'function' ? standingMountFor(agent?.ctx) : undefined
|
|
47
47
|
return mount?.presetId
|
|
48
48
|
} catch {
|
|
49
|
-
// 判定不抛出:拿不到就是「没有预设」(不命中),与 condition.mjs 的降级纪律一致。
|
|
49
|
+
// 判定不抛出:拿不到就是「没有预设」(不命中),与 condition-facts.mjs 的降级纪律一致。
|
|
50
50
|
return undefined
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SURFACE_MESSAGE_TYPES, currentEvents, historyEvents } from '../history.mjs'
|
|
2
|
+
import { isDelegated } from '../shared.mjs'
|
|
2
3
|
import { sessionOf } from './subject.mjs'
|
|
3
4
|
import { optionalBoolean } from './values.mjs'
|
|
4
5
|
|
|
@@ -12,9 +13,14 @@ export function createSessionStatePredicate(options = {}) {
|
|
|
12
13
|
// `true` = 只在子代理命中,未声明 = 不判受众。子代理判定复用 `shared.isDelegated`,
|
|
13
14
|
// 不写字面量比较(trigger.mjs 的会话态契约第 (2) 条纪律)。
|
|
14
15
|
const delegated = optionalBoolean(options.delegated, 'delegated', undefined)
|
|
16
|
+
// `present` 对消息类镜像的是**当前上下文**,不是「本会话发生过」:被压缩遮蔽的事件模型已经看不到。
|
|
17
|
+
// 非 surface 承载的类型(`tool/call`、`turn/start`、`compaction/end`)永远不是 surface 节点,
|
|
18
|
+
// 按当前上下文判只会恒 false(`present: true`)/ 恒 true(`present: false`)——配置能编译能挂载,
|
|
19
|
+
// 却永不按意图命中,所以这类类型按完整历史判(类型表见 `history.mjs#SURFACE_MESSAGE_TYPES`)。
|
|
20
|
+
const view = SURFACE_MESSAGE_TYPES.has(type) ? currentEvents : historyEvents
|
|
15
21
|
const predicate = (payload) => {
|
|
16
22
|
if (delegated !== undefined && isDelegated(sessionOf(payload)) !== delegated) return false
|
|
17
|
-
return
|
|
23
|
+
return view(sessionOf(payload))
|
|
18
24
|
.some((event) => event?.type === type) === present
|
|
19
25
|
}
|
|
20
26
|
predicate.kind = 'session'
|
|
@@ -1,30 +1,6 @@
|
|
|
1
|
-
import { lastAssistantText, subagentTextOf, toolArgsText, userMessagesText } from '../condition.mjs'
|
|
1
|
+
import { SUBJECT_FIELDS, lastAssistantText, subagentTextOf, toolArgsText, userMessagesText } from '../condition-facts.mjs'
|
|
2
2
|
import { extractText } from '../shared.mjs'
|
|
3
3
|
|
|
4
|
-
const CHANNEL_SUBJECTS = {
|
|
5
|
-
'system-prompt/assemble': (args) => ({ agent: args[1]?.agent }),
|
|
6
|
-
'agent/request': (args) => ({ agent: args[0]?.agent }),
|
|
7
|
-
'agent/pre-step': (args) => ({ agent: args[0]?.agent }),
|
|
8
|
-
'agent/inbox/inserted': (args) => ({ agent: args[0]?.agent, source: args[0]?.message?.source }),
|
|
9
|
-
'agent/turn-stopping': (args) => ({ agent: args[0]?.agent }),
|
|
10
|
-
'tools/pre-execute': (args) => ({ agent: args[0]?.agent, name: args[0]?.name }),
|
|
11
|
-
'tools/post-execute': (args) => ({ agent: args[0]?.agent, name: args[0]?.name }),
|
|
12
|
-
'session/event': (args) => ({ session: args[0] }),
|
|
13
|
-
'llm/stream': (args) => ({ model: args[0]?.model }),
|
|
14
|
-
'subagent/start': () => ({}),
|
|
15
|
-
'subagent/end': () => ({}),
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 未知通道的告警去重(每个通道一次;判定期不刷屏)。 */
|
|
19
|
-
const warnedChannels = new Set()
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 归一化载荷的标记(Symbol,非字符串键):`subjectOf` 产出它,`agentOf`/`sessionOf` 据此
|
|
23
|
-
* 区分「归一化载荷」与「旧形态的直接 payload」——**不用启发式猜**(曾按「有没有 `.session`」
|
|
24
|
-
* 判断,结果把测试里 `{ id, ctx }` 形状的假 agent 误判成归一化载荷)。
|
|
25
|
-
*/
|
|
26
|
-
const SUBJECT_MARK = Symbol('predicate-subject')
|
|
27
|
-
|
|
28
4
|
/**
|
|
29
5
|
* 子代理事件判定期可见的 agent 事实。
|
|
30
6
|
*
|
|
@@ -34,22 +10,139 @@ const SUBJECT_MARK = Symbol('predicate-subject')
|
|
|
34
10
|
* 字符串)与 `scope.audience`(要求 session 可达)在子代理两层恒判 UNAVAILABLE——UI 上
|
|
35
11
|
* 配了却永远不命中,正是 schema 注释要避免的「配了没效果也不报错」。
|
|
36
12
|
*
|
|
37
|
-
* 这里在**判定前**用事件里的 `id` 反查 agent
|
|
38
|
-
*
|
|
13
|
+
* 这里在**判定前**用事件里的 `id` 反查 agent 补齐同样的事实。取不到时返回 `undefined`
|
|
14
|
+
* (取值器不写键):判定退回 UNAVAILABLE(fail-closed),不得乐观放行。
|
|
15
|
+
*/
|
|
16
|
+
const subagentAgent = (args, ctx) => {
|
|
17
|
+
const id = args[0]?.id
|
|
18
|
+
if (typeof id !== 'string' || id.length === 0) return undefined
|
|
19
|
+
return ctx?.agents?.get?.(id) ?? undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const streamAgent = (args, ctx) => {
|
|
23
|
+
const call = args[0]
|
|
24
|
+
if (call?.agent !== undefined) return call.agent
|
|
25
|
+
if (typeof call?.sessionId !== 'string') return undefined
|
|
26
|
+
return (ctx?.get?.('agents') ?? ctx?.agents)?.get?.(call.sessionId)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 子代理两层的事实。`name` = provider(`SubagentRunInfo` 里唯一稳定的分类事实:
|
|
31
|
+
* spawn / fork / acp / codex / claude-code / dsh-sdk),`names` 谓词读的就是它,
|
|
32
|
+
* 因此 `names` 在这两层合法(编译期据此放行)。provider 可能缺席(官方注释:已接受的
|
|
33
|
+
* one-shot 变 ready 或持久 Activation 冷恢复时提供方未必仍注册)——那时返回 `undefined`
|
|
34
|
+
* 让取值器不写键、`names` 走 **运行期** UNAVAILABLE,而不是塞空串当「已知为空」,
|
|
35
|
+
* 也不是编译期拒绝。
|
|
36
|
+
*/
|
|
37
|
+
const SUBAGENT_FACTS = {
|
|
38
|
+
name: (args) => {
|
|
39
|
+
const provider = args[0]?.provider
|
|
40
|
+
return typeof provider === 'string' && provider.length > 0 ? provider : undefined
|
|
41
|
+
},
|
|
42
|
+
agent: subagentAgent,
|
|
43
|
+
session: (args, ctx) => subagentAgent(args, ctx)?.session,
|
|
44
|
+
model: (args, ctx) => {
|
|
45
|
+
const model = subagentAgent(args, ctx)?.options?.model
|
|
46
|
+
return typeof model === 'string' && model.length > 0 ? model : undefined
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 通道 → 归一化载荷的字段与取值器:**唯一**的「该通道提供什么事实」真相。
|
|
52
|
+
* `channelTextSubjects` 与 `channelFactSubjects` 都从这张表派生,不另立第二张手抄表。
|
|
53
|
+
*
|
|
54
|
+
* - `text[字段名] = args => value`:字段名取自 `condition-facts.mjs#SUBJECT_FIELDS`,
|
|
55
|
+
* 经 `SUBJECT_OF_FIELD` 反查成文本 subject。
|
|
56
|
+
* - `facts[字段名] = (args, ctx) => value`:字段名本身即 subject(`name` / `source` /
|
|
57
|
+
* `agent` / `session` / `model`);返回 `undefined` 即不写键(判定退回 UNAVAILABLE)。
|
|
39
58
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
const CHANNELS = {
|
|
60
|
+
'system-prompt/assemble': { facts: { agent: args => args[1]?.agent } },
|
|
61
|
+
'agent/request': { facts: { agent: args => args[0]?.agent } },
|
|
62
|
+
'agent/pre-step': {
|
|
63
|
+
facts: { agent: args => args[0]?.agent },
|
|
64
|
+
text: { userText: args => userMessagesText(args[0]?.messages) },
|
|
65
|
+
},
|
|
66
|
+
'agent/inbox/inserted': {
|
|
67
|
+
facts: { agent: args => args[0]?.agent, source: args => args[0]?.message?.source },
|
|
68
|
+
text: { userText: args => userMessagesText([args[0]?.message]) },
|
|
69
|
+
},
|
|
70
|
+
'agent/turn-stopping': {
|
|
71
|
+
facts: { agent: args => args[0]?.agent },
|
|
72
|
+
text: { assistantText: args => lastAssistantText(args[0]?.agent?.session) },
|
|
73
|
+
},
|
|
74
|
+
'tools/pre-execute': {
|
|
75
|
+
facts: { agent: args => args[0]?.agent, name: args => args[0]?.name },
|
|
76
|
+
text: { argsText: args => toolArgsText(args[0]?.arguments) },
|
|
77
|
+
},
|
|
78
|
+
'tools/post-execute': {
|
|
79
|
+
facts: { agent: args => args[0]?.agent, name: args => args[0]?.name },
|
|
80
|
+
text: { argsText: args => toolArgsText(args[0]?.arguments), resultText: args => extractText(args[1]) },
|
|
81
|
+
},
|
|
82
|
+
'session/event': { facts: { session: args => args[0] } },
|
|
83
|
+
'llm/stream': { facts: { model: args => args[0]?.model, agent: streamAgent } },
|
|
84
|
+
'subagent/start': { facts: SUBAGENT_FACTS, text: { subagentText: args => subagentTextOf(args[0]) } },
|
|
85
|
+
'subagent/end': { facts: SUBAGENT_FACTS, text: { subagentText: args => subagentTextOf(args[0]) } },
|
|
51
86
|
}
|
|
52
87
|
|
|
88
|
+
/** 字段名 → subject 反查;键名只在 SUBJECT_FIELDS 声明一次,表与名单交叉而不重复。 */
|
|
89
|
+
const SUBJECT_OF_FIELD = Object.fromEntries(Object.entries(SUBJECT_FIELDS).map(([subject, field]) => [field, subject]))
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 事实谓词 → 它读取的事实 subject:校验面与运行面的**唯一**登记处——新谓词还要在
|
|
93
|
+
* `PREDICATE_FACTORIES` 与通道可用性表登记,但「谓词 → subject」只此一处,
|
|
94
|
+
* `rule-spec.mjs#conditionSubjects` 直接消费本对象、`FACT_SUBJECTS` 也由它派生。
|
|
95
|
+
* shortcut: 漂移断言靠运行时改写本对象;将来若 `Object.freeze`,需把判据换成「同一引用」。
|
|
96
|
+
*/
|
|
97
|
+
export const FACT_PREDICATE_SUBJECTS = { names: 'name', source: 'source' }
|
|
98
|
+
|
|
99
|
+
/** 谓词能消费的事实 subject:`names` 读 `name`、`source` 读 `source`;agent/session/model 不是谓词 subject。 */
|
|
100
|
+
const FACT_SUBJECTS = Object.values(FACT_PREDICATE_SUBJECTS)
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 本通道可用的文本 subject。未列出的通道(`system-prompt/assemble`、`agent/request`、
|
|
104
|
+
* `llm/stream`、`session/event`)不提供任何文本,其上声明的 `text` 条件永不命中。
|
|
105
|
+
* @param {string} channel 官方事件名
|
|
106
|
+
* @returns {string[]} 可用 subject
|
|
107
|
+
*/
|
|
108
|
+
export function channelTextSubjects(channel) {
|
|
109
|
+
const fields = CHANNELS[channel]?.text
|
|
110
|
+
if (fields === undefined) return []
|
|
111
|
+
return Object.keys(fields).map(field => SUBJECT_OF_FIELD[field]).filter(subject => subject !== undefined)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 本通道可用的事实 subject(`name` / `source` 中它具备的那些)——编译期据此拒绝
|
|
116
|
+
* `names` / `source` 的死条件(见 `rule-spec.mjs`)。
|
|
117
|
+
* @param {string} channel 官方事件名
|
|
118
|
+
* @returns {string[]} 可用 subject
|
|
119
|
+
*/
|
|
120
|
+
export function channelFactSubjects(channel) {
|
|
121
|
+
const facts = CHANNELS[channel]?.facts
|
|
122
|
+
if (facts === undefined) return []
|
|
123
|
+
return FACT_SUBJECTS.filter(subject => Object.hasOwn(facts, subject))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 按表取字段:`undefined` 不写键——判定据此退回 UNAVAILABLE,而不是把「取不到」当成「已知为空」。 */
|
|
127
|
+
function pickFields(readers, args, ctx) {
|
|
128
|
+
const out = {}
|
|
129
|
+
for (const [field, read] of Object.entries(readers ?? {})) {
|
|
130
|
+
const value = read(args, ctx)
|
|
131
|
+
if (value !== undefined) out[field] = value
|
|
132
|
+
}
|
|
133
|
+
return out
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** 未知通道的告警去重(每个通道一次;判定期不刷屏)。 */
|
|
137
|
+
const warnedChannels = new Set()
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 归一化载荷的标记(Symbol,非字符串键):`subjectOf` 产出它,`agentOf`/`sessionOf` 据此
|
|
141
|
+
* 区分「归一化载荷」与「旧形态的直接 payload」——**不用启发式猜**(曾按「有没有 `.session`」
|
|
142
|
+
* 判断,结果把测试里 `{ id, ctx }` 形状的假 agent 误判成归一化载荷)。
|
|
143
|
+
*/
|
|
144
|
+
const SUBJECT_MARK = Symbol('predicate-subject')
|
|
145
|
+
|
|
53
146
|
/**
|
|
54
147
|
* 把事件参数表归一为谓词载荷。
|
|
55
148
|
*
|
|
@@ -68,36 +161,22 @@ export function subjectOf(channel, args, warn, ctx) {
|
|
|
68
161
|
// `{ agent, turn }`)因此继续可见,既有断言不会因归一化而失效。
|
|
69
162
|
const first = args[0]
|
|
70
163
|
const base = first !== null && typeof first === 'object' && !Array.isArray(first) ? { ...first } : {}
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
switch (channel) {
|
|
74
|
-
case 'tools/pre-execute': return { argsText: toolArgsText(args[0]?.arguments) }
|
|
75
|
-
case 'tools/post-execute': return { argsText: toolArgsText(args[0]?.arguments), resultText: extractText(args[1]) }
|
|
76
|
-
case 'agent/pre-step': return { userText: userMessagesText(args[0]?.messages) }
|
|
77
|
-
case 'agent/inbox/inserted': return { userText: userMessagesText([args[0]?.message]) }
|
|
78
|
-
case 'agent/turn-stopping': return { assistantText: lastAssistantText(args[0]?.agent?.session) }
|
|
79
|
-
// 子代理事件的 `name` = provider(`SubagentRunInfo` 里唯一稳定的分类事实:
|
|
80
|
-
// spawn / fork / acp / codex / claude-code / dsh-sdk)。`names` 谓词读的就是
|
|
81
|
-
// `payload.name`,因此 `if: { names: { allow: ['fork'] } }` 在此可用。
|
|
82
|
-
// provider 可能缺席(官方注释:已接受的 one-shot 变 ready 或持久 Activation 冷恢复时
|
|
83
|
-
// 提供方未必仍注册)——那时不写键,让 `names` 走 UNAVAILABLE,而不是塞空串当「已知为空」。
|
|
84
|
-
case 'subagent/start':
|
|
85
|
-
case 'subagent/end': return {
|
|
86
|
-
subagentText: subagentTextOf(args[0]),
|
|
87
|
-
...(typeof args[0]?.provider === 'string' && args[0].provider.length > 0 ? { name: args[0].provider } : {}),
|
|
88
|
-
...subagentFacts(args[0], ctx),
|
|
89
|
-
}
|
|
90
|
-
default: return {}
|
|
91
|
-
}
|
|
92
|
-
})()
|
|
93
|
-
if (pick === undefined) {
|
|
164
|
+
const spec = CHANNELS[channel]
|
|
165
|
+
if (spec === undefined) {
|
|
94
166
|
if (typeof warn === 'function' && !warnedChannels.has(channel)) {
|
|
95
167
|
warnedChannels.add(channel)
|
|
96
168
|
warn(`predicates: unknown channel ${JSON.stringify(channel)} — 无显式取法,退回「第一个实参即载荷」的旧语义`)
|
|
97
169
|
}
|
|
98
|
-
return { ...base,
|
|
170
|
+
return { ...base, [SUBJECT_MARK]: true, channel, args }
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
...base,
|
|
174
|
+
...pickFields(spec.facts, args, ctx),
|
|
175
|
+
...pickFields(spec.text, args, ctx),
|
|
176
|
+
[SUBJECT_MARK]: true,
|
|
177
|
+
channel,
|
|
178
|
+
args,
|
|
99
179
|
}
|
|
100
|
-
return { ...base, ...pick(args), ...text, [SUBJECT_MARK]: true, channel, args }
|
|
101
180
|
}
|
|
102
181
|
|
|
103
182
|
/**
|
|
@@ -111,8 +190,8 @@ export function agentOf(payload) {
|
|
|
111
190
|
|
|
112
191
|
/**
|
|
113
192
|
* 从载荷取 session:归一化载荷走 `session` → `agent.session`;旧形态走
|
|
114
|
-
* `session` → 载荷自身。取不到时返回原载荷——`
|
|
115
|
-
* 的对象返回空,语义是「无事件」,与归一化之前逐例一致。
|
|
193
|
+
* `session` → 载荷自身。取不到时返回原载荷——`history.mjs#historyEvents` / `#currentEvents`
|
|
194
|
+
* 对无 `snapshotEvents` 的对象返回空,语义是「无事件」,与归一化之前逐例一致。
|
|
116
195
|
*/
|
|
117
196
|
export function sessionOf(payload) {
|
|
118
197
|
if (payload === null || typeof payload !== 'object') return payload
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { MATCH_LOGIC, createAnchorMatcher } from '../anchor-match.mjs'
|
|
2
|
-
import { subjectTextOf } from '../condition.mjs'
|
|
2
|
+
import { subjectTextOf } from '../condition-facts.mjs'
|
|
3
3
|
import { keyList } from './values.mjs'
|
|
4
4
|
import { conditionInputAvailable, UNAVAILABLE } from './availability.mjs'
|
|
5
|
-
const MATCH_FLAGS = ['caseSensitive', 'wholeWords', 'useRegex'
|
|
5
|
+
const MATCH_FLAGS = ['caseSensitive', 'wholeWords', 'useRegex']
|
|
6
6
|
const MATCH_MODES = ['scan', 'prefix']
|
|
7
7
|
|
|
8
8
|
export function createTextPredicate(options = {}) {
|
|
9
|
+
if (options.stWords !== undefined) throw new TypeError('stWords 已退役,请显式转换为 wordBoundary')
|
|
10
|
+
if (options.wordBoundary !== undefined && !['ascii-token', 'unicode'].includes(options.wordBoundary)) throw new TypeError('wordBoundary 必须是 ascii-token 或 unicode')
|
|
9
11
|
const keys = keyList(options.keys, 'keys')
|
|
10
12
|
const secondaryKeys = keyList(options.secondaryKeys, 'secondaryKeys')
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const bounds = ['minChars', 'maxChars'].map(field => {
|
|
14
|
+
const value = options[field]
|
|
15
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
16
|
+
throw new TypeError(`predicates: ${field} must be a non-negative safe integer`)
|
|
17
|
+
}
|
|
18
|
+
return value
|
|
19
|
+
})
|
|
20
|
+
const [minChars, maxChars] = bounds
|
|
21
|
+
if (minChars !== undefined && maxChars !== undefined && minChars > maxChars) {
|
|
22
|
+
throw new TypeError('predicates: minChars cannot exceed maxChars')
|
|
23
|
+
}
|
|
24
|
+
const hasKeys = keys.length > 0 || secondaryKeys.length > 0
|
|
25
|
+
if (!hasKeys && minChars === undefined && maxChars === undefined) {
|
|
26
|
+
throw new TypeError('predicates: a text predicate needs a non-empty key or minChars/maxChars')
|
|
13
27
|
}
|
|
14
28
|
const logic = options.logic ?? MATCH_LOGIC.ANY
|
|
15
29
|
if (!Object.values(MATCH_LOGIC).includes(logic)) {
|
|
@@ -33,12 +47,16 @@ export function createTextPredicate(options = {}) {
|
|
|
33
47
|
useRegex: options.useRegex,
|
|
34
48
|
logic,
|
|
35
49
|
mode,
|
|
36
|
-
|
|
50
|
+
wordBoundary: options.wordBoundary,
|
|
37
51
|
})
|
|
38
52
|
const subject = options.subject
|
|
39
|
-
const predicate = (payload) =>
|
|
40
|
-
|
|
41
|
-
:
|
|
53
|
+
const predicate = (payload) => {
|
|
54
|
+
if (!conditionInputAvailable('text', options, payload)) return UNAVAILABLE
|
|
55
|
+
const text = typeof payload === 'string' ? payload : subjectTextOf(subject, payload)
|
|
56
|
+
return (minChars === undefined || text.length >= minChars)
|
|
57
|
+
&& (maxChars === undefined || text.length <= maxChars)
|
|
58
|
+
&& (!hasKeys || matcher.scan(text).active === true)
|
|
59
|
+
}
|
|
42
60
|
predicate.kind = 'text'
|
|
43
61
|
return predicate
|
|
44
62
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* variable 谓词:把模块级持久变量接进规则条件。
|
|
3
|
+
*
|
|
4
|
+
* 适配器由挂载期绑定(`variableStore.read(moduleId)` → { module, global, effective, enabled }),
|
|
5
|
+
* 与模板求值共用同一个 owner;编译期没有适配器,判定为 UNAVAILABLE(三值语义,不能经 not 反转)。
|
|
6
|
+
*/
|
|
7
|
+
import { UNAVAILABLE } from './availability.mjs'
|
|
8
|
+
|
|
9
|
+
const OPS = new Set(['eq', 'ne', 'in', 'gt', 'gte', 'lt', 'lte', 'present', 'absent'])
|
|
10
|
+
const SCOPES = new Set(['effective', 'module', 'global'])
|
|
11
|
+
|
|
12
|
+
/** 严格数字:空串与空白不算数字(`Number('')` 是 0,是陷阱);非有限数不算数字。 */
|
|
13
|
+
function numeric(text) {
|
|
14
|
+
if (text.trim().length === 0) return undefined
|
|
15
|
+
const value = Number(text)
|
|
16
|
+
return Number.isFinite(value) ? value : undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} options
|
|
21
|
+
* @param {string} options.key 变量名(模块声明,引擎不认前缀)
|
|
22
|
+
* @param {'eq'|'ne'|'in'|'gt'|'gte'|'lt'|'lte'|'present'|'absent'} [options.op] 缺省 eq
|
|
23
|
+
* @param {string|string[]} [options.value] in 用数组;present/absent 不收
|
|
24
|
+
* @param {'effective'|'module'|'global'} [options.scope] 缺省 effective(模块覆盖全局)
|
|
25
|
+
* @param {{ store?: { read: (moduleId: string) => { module?: Record<string, string>, global?: Record<string, string>, effective?: Record<string, string>, enabled?: boolean } }, moduleId?: string }} [context]
|
|
26
|
+
*/
|
|
27
|
+
export function createVariablePredicate(options = {}, context = {}) {
|
|
28
|
+
const key = options.key
|
|
29
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
30
|
+
throw new TypeError('predicates: variable key must be a non-empty string')
|
|
31
|
+
}
|
|
32
|
+
const op = options.op ?? 'eq'
|
|
33
|
+
if (!OPS.has(op)) {
|
|
34
|
+
throw new TypeError(`predicates: variable op must be one of ${[...OPS].join(', ')}`)
|
|
35
|
+
}
|
|
36
|
+
const scope = options.scope ?? 'effective'
|
|
37
|
+
if (!SCOPES.has(scope)) {
|
|
38
|
+
throw new TypeError(`predicates: variable scope must be one of ${[...SCOPES].join(', ')}`)
|
|
39
|
+
}
|
|
40
|
+
let expected
|
|
41
|
+
if (op === 'present' || op === 'absent') {
|
|
42
|
+
if (options.value !== undefined) throw new TypeError(`predicates: variable ${op} does not take value`)
|
|
43
|
+
} else if (op === 'in') {
|
|
44
|
+
if (!Array.isArray(options.value) || options.value.length === 0
|
|
45
|
+
|| options.value.some((item) => typeof item !== 'string')) {
|
|
46
|
+
throw new TypeError('predicates: variable in needs a non-empty array of strings')
|
|
47
|
+
}
|
|
48
|
+
expected = options.value
|
|
49
|
+
} else {
|
|
50
|
+
if (typeof options.value !== 'string') throw new TypeError('predicates: variable value must be a string')
|
|
51
|
+
expected = options.value
|
|
52
|
+
}
|
|
53
|
+
const store = context.store
|
|
54
|
+
const moduleId = context.moduleId
|
|
55
|
+
const predicate = () => {
|
|
56
|
+
// 适配器缺失(准备期、复制协议未接)是「缺事实」,不是「变量为假」。
|
|
57
|
+
if (store === undefined || typeof store.read !== 'function') return UNAVAILABLE
|
|
58
|
+
if (typeof moduleId !== 'string' || moduleId.length === 0) return UNAVAILABLE
|
|
59
|
+
const state = store.read(moduleId)
|
|
60
|
+
if (state === null || typeof state !== 'object' || state.enabled === false) return UNAVAILABLE
|
|
61
|
+
const values = scope === 'global' ? state.global : scope === 'module' ? state.module : state.effective
|
|
62
|
+
if (values === null || typeof values !== 'object' || Array.isArray(values)) return UNAVAILABLE
|
|
63
|
+
const present = Object.hasOwn(values, key)
|
|
64
|
+
if (op === 'present') return present
|
|
65
|
+
if (op === 'absent') return !present
|
|
66
|
+
// 键缺失不是错误:比较类按假处理(作者写 present 才能区分「没有」)。
|
|
67
|
+
if (!present) return false
|
|
68
|
+
const actual = values[key]
|
|
69
|
+
if (typeof actual !== 'string') return false
|
|
70
|
+
if (op === 'eq') return actual === expected
|
|
71
|
+
if (op === 'ne') return actual !== expected
|
|
72
|
+
if (op === 'in') return expected.includes(actual)
|
|
73
|
+
const left = numeric(actual)
|
|
74
|
+
const right = numeric(expected)
|
|
75
|
+
if (left === undefined || right === undefined) return false
|
|
76
|
+
if (op === 'gt') return left > right
|
|
77
|
+
if (op === 'gte') return left >= right
|
|
78
|
+
if (op === 'lt') return left < right
|
|
79
|
+
return left <= right
|
|
80
|
+
}
|
|
81
|
+
predicate.kind = 'variable'
|
|
82
|
+
return predicate
|
|
83
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 上下文激活诊断的只读类型声明。 */
|
|
2
|
+
export interface ContextActivationDiagnosticsSnapshot {
|
|
3
|
+
records: Array<{ id: string; stage: string; reason: string; [key: string]: unknown }>
|
|
4
|
+
truncated: boolean
|
|
5
|
+
step: number
|
|
6
|
+
/** false = 该会话尚未求值;true = 已求值(records 为空表示本次没有参与/入选条目)。 */
|
|
7
|
+
evaluated: boolean
|
|
8
|
+
}
|
|
9
|
+
export function lastContextActivationDiagnostics(session: unknown): ContextActivationDiagnosticsSnapshot
|
|
10
|
+
export function selectContextActivation(...args: unknown[]): Set<Record<string, unknown>> & { diagnostics?: { records: unknown[]; truncated: boolean } }
|
|
11
|
+
export function conversationMessages(session: unknown, pending?: unknown[]): Array<{ id: unknown; role: string; text: string }>
|