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
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* `assistant/message`, per the caller's `promoteEvents`) recorded AFTER the
|
|
12
12
|
* last successful `compaction/end` boundary counts as promoted. Before any compaction
|
|
13
13
|
* the boundary is -1, which preserves the original one-shot semantics.
|
|
14
|
+
* 重置判据看事件不看代次;理由与失效条件见 `isSuccessfulCompactionEnd`。
|
|
14
15
|
*
|
|
15
16
|
* State is memoized per session id and maintained incrementally through
|
|
16
17
|
* `observe()`; a cold session scans its durable log once (so resume and
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
* Non-gate mode keeps the original event-set semantics byte-for-byte.
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
|
-
import {
|
|
36
|
+
import { sessionState } from './shared.mjs'
|
|
37
|
+
import { historyEvents } from './history.mjs'
|
|
36
38
|
|
|
37
39
|
/** 匹配内容与大小写均由调用方显式声明;空 pattern 不承担任何业务识别。 */
|
|
38
40
|
function reasoningClassifier(options = {}) {
|
|
@@ -69,7 +71,21 @@ function firstReasoningMatches(content, classify) {
|
|
|
69
71
|
|
|
70
72
|
export function hasAnchoredReasoning(content, options = {}) { return firstReasoningMatches(content, reasoningClassifier(options)) }
|
|
71
73
|
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* True only when compaction completed and changed the model-visible surface.
|
|
76
|
+
*
|
|
77
|
+
* 判据刻意是**事件维**(成功 `compaction/end`),不是 surface 的 `replaceGeneration` 代次:
|
|
78
|
+
* 宿主对「一次已提交的位置替换」只给单调计数(`packages/core/session/src/surface.ts:569-572`),
|
|
79
|
+
* 不说是哪一种,也不给边界。于是代次判据有三处改语义:① 非压缩的 replace(区域编辑 / 手动裁剪 /
|
|
80
|
+
* 含 `surfaceOp: { op: 'replace' }` 的插件重写)同样推进代次,门控会被误重置;② 轮询只能得到
|
|
81
|
+
* 一个整数,定不出边界给 `applyEvent` 的 `seq <= boundary` 守卫,压缩替身落地**之前**的事件会被
|
|
82
|
+
* 当成新 epoch 的事件重新晋升(5b);③ 无 surface / 无代次(恢复、重放、其它宿主、既有桩)时
|
|
83
|
+
* 代次恒 undefined,压缩重置永不触发。压缩的**权威判定**因此还得看事件:宿主只在压缩时写
|
|
84
|
+
* 无 `error` 的 `compaction/end`,而成功压缩必然伴随一次 replace。
|
|
85
|
+
* shortcut: 近似而非权威 —— 失效条件:既不写 `compaction/end`、又靠 replace 重写可见上下文,
|
|
86
|
+
* 或写了成功 `compaction/end` 却没改变可见上下文时,该重置误触发/漏触发。宿主给出「这次 replace
|
|
87
|
+
* 是否压缩 + 边界 seq」的显式信号时改用它。
|
|
88
|
+
*/
|
|
73
89
|
export function isSuccessfulCompactionEnd(event) {
|
|
74
90
|
return event?.type === 'compaction/end' && event.data?.error === undefined
|
|
75
91
|
}
|
|
@@ -142,10 +158,17 @@ export function createEpochPromotion(promoteEvents, options = {}) {
|
|
|
142
158
|
return entry
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Scan a session's durable log from scratch (cold start / resume).
|
|
163
|
+
*
|
|
164
|
+
* 判据来源**刻意是完整历史**(`historyEvents`,不是 `currentEvents`):语义是「本会话在最后
|
|
165
|
+
* 一次成功压缩之后**曾经** tool/call 过」——状态性的历史事实。压缩前的信号在 surface 上已
|
|
166
|
+
* 不可见,按当前上下文冷启动会把 `promoted` 错判为 false,门控重回 bootstrap/锚定阶段、
|
|
167
|
+
* 工具目录被裁剪。
|
|
168
|
+
*/
|
|
146
169
|
const scan = (session) => {
|
|
147
170
|
let entry = freshEntry(-1)
|
|
148
|
-
for (const event of
|
|
171
|
+
for (const event of historyEvents(session)) entry = applyEvent(entry, event)
|
|
149
172
|
state.set(session, entry)
|
|
150
173
|
return entry
|
|
151
174
|
}
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
#
|
|
6
6
|
# 能力提供者边界(T4 文件归属校正):本行行下的 str-replace-editor 是**官方实现**
|
|
7
7
|
# —— `@deepseek-ai/dsh-tool-str-replace-editor`(官方包,配置声明与注册都在包内),
|
|
8
|
-
# 本仓库**没有**对应的 .mjs。它连同
|
|
9
|
-
#
|
|
8
|
+
# 本仓库**没有**对应的 .mjs。它连同 tool-config-engine 一起构成 PLAN T4 的两个
|
|
9
|
+
# 能力提供者,两者都不接入 engine/trigger.mjs。
|
|
10
10
|
# 判据:会给模型提供可调用能力(注册工具/域)的 → 能力提供者;干预流程
|
|
11
11
|
# (改提示词、改装配、裁决、追加)的 → 声明式触发器。
|
|
12
|
-
# 注意 engine/tool-git-bash.mjs 提供的是 `bash`
|
|
12
|
+
# 注意 engine/tool-git-bash.mjs 提供的是 `bash` 工具,**不是**第二个提供者。
|
|
13
13
|
|
|
14
14
|
# The bare local filesystem shadows the host's sandboxed provider only for this
|
|
15
15
|
# preset. The editor shares that realm and requires absolute paths.
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
# 四个模板必须随行给全(2026-10-05 修):`instruction-hint.mjs#apply` 的首行是
|
|
10
10
|
# `if (!enabled || templateOf(source, 'messageTemplate').trim().length === 0) return`
|
|
11
11
|
# 而本行此前只配了 enabled/promoteOn/includeSubagents —— 于是把开关打开**不注册任何
|
|
12
|
-
# 转换**:配置合法、零效果、零报错(与 count.delegated
|
|
13
|
-
#
|
|
14
|
-
# 的迁移默认值同源;两处必须一致,否则「迁移进来的定义」与「新装的组合源」行为分叉。
|
|
15
|
-
# legacy-defaults.yml 自身标注「引擎运行时不得读取」,所以这里必须是实体而非引用。
|
|
12
|
+
# 转换**:配置合法、零效果、零报错(与 count.delegated 的静默失效同类)。四个模板的正文
|
|
13
|
+
# 是本行自己的实体数据;旧业务默认值快照(legacy-defaults.yml)已随旧兼容移除。
|
|
16
14
|
#
|
|
17
15
|
# includeSubagents: true —— 子代理首次请求即视为已晋升(官方相位语义),所以子代理
|
|
18
16
|
# 从第 1 轮就拿提示而非全文。主会话则是首个工具调用/助手消息之后才降级(promoteOn)。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# module: present-as
|
|
2
|
+
# source: engine/compositions/source/local/present-as.yml (本地附加,非官方模块)
|
|
3
|
+
# 让本模块覆盖的 Agent 以 `config.mode` 指定的形态呈现工具(官方 `ctx.tools.presentAs`):
|
|
4
|
+
# `ptc` 时注册表只贡献保留的 `run_code` 传输与生成的 SDK 段,其余可见能力由程序内绑定
|
|
5
|
+
# (`tools.xxx()`)调用。
|
|
6
|
+
#
|
|
7
|
+
# **行名必须与引擎模块文件同名**:agent-assembly 按 `engine/<行 id>.mjs` 找实现
|
|
8
|
+
# (src/runtime/agent-assembly.ts:186-188 的 engineModuleFile),找不到就**静默跳过这一行**
|
|
9
|
+
# ——官方组合行没有 engine 文件属正常(由会话原有预设提供),所以这条跳过不告警;写错名字
|
|
10
|
+
# 的后果是「行装了等于没装」而毫无提示。本文件因此叫 present-as.yml + id: present-as,
|
|
11
|
+
# 与 engine/present-as.mjs 对齐。
|
|
12
|
+
#
|
|
13
|
+
# 为什么不用官方 `@deepseek-ai/dsh-agent-tool-presentation` 行:这是插件自己的实现
|
|
14
|
+
# (engine/present-as.mjs 调的是同一个 `presentAs`),模块可经 `modules:` 声明它,因此
|
|
15
|
+
# **不需要在预设侧再挂官方行**,也不需要动 `cordis.patch.yml`(那条只用于进程级默认呈现)。
|
|
16
|
+
# 两者落在不同 scope——本行 = Agent scope、官方行 = 预设常驻 scope,`modeFor` 最近者优先;
|
|
17
|
+
# 而**同一 scope 二次声明会被官方拒绝**(挂载期 fail loud,不静默覆盖)。
|
|
18
|
+
#
|
|
19
|
+
# 前置:`mode: ptc` 需要 `ctx.ptcRuntime`(宿主 base bundle 的 `ptc-runtime` 行提供)。缺它时
|
|
20
|
+
# 本行停在 pending,由预设注册表指名报告不可用的行——不静默退回 native。
|
|
21
|
+
|
|
22
|
+
- id: present-as
|
|
23
|
+
name: ./engine/present-as.mjs
|
|
24
|
+
config:
|
|
25
|
+
enabled: true
|
|
26
|
+
mode: ptc
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
# 能力提供者边界(T4):本行是**能力提供者**(把预设声明的自定义工具注册进官方
|
|
7
7
|
# registry),**不接入** engine/trigger.mjs。判据:会给模型提供可调用能力
|
|
8
8
|
# (注册工具/域)的 → 能力提供者;干预流程(改提示词、改装配、裁决、追加)的 →
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# `engineTriggers`(声明式触发器)互斥;守卫见 test/engine/provider-boundary.test.mjs。
|
|
9
|
+
# 声明式触发器。模型可见的工具名在装配期由 module.yml 的 customTools 段决定,
|
|
10
|
+
# 引擎侧无法静态枚举。
|
|
12
11
|
# 模块内 approvalGate 与 withinCwd 是安全边界,改装配方式时不得放宽。
|
|
13
12
|
|
|
14
13
|
- id: tool-config-engine
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# module: tool-search
|
|
2
|
+
# source: engine/compositions/source/local/tool-search.yml (本地附加,非官方模块)
|
|
3
|
+
#
|
|
4
|
+
# 工具面的按需发现与解锁。与 assembly 动作的 allowFrom 配对使用:
|
|
5
|
+
# 本工具把解锁名写进持久调用参数(tool/call;PTC 嵌套为 tool/ptc-dispatch)→ allowExtra 读回 → 下次请求起生效。
|
|
6
|
+
# 只挂本行而不配 allowFrom 的话,解锁是一次性的(当次请求用完即被裁掉)。
|
|
7
|
+
#
|
|
8
|
+
# 常驻集由模板声明(见 templates/ 的工具面收窄模板),本行只负责发现入口。
|
|
9
|
+
|
|
10
|
+
- id: tool-search
|
|
11
|
+
name: ./engine/tool-search.mjs
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* condition-facts — live 规则条件的文本事实提取(if.text.subject / if.anchor.subject)。
|
|
3
|
+
*
|
|
4
|
+
* 声明式 `config.subject` / `config.match` 执行链已退役;这里只保留:
|
|
5
|
+
* - subject → 载荷文本字段的映射(conditions/subject 与 availability 共用);
|
|
6
|
+
* - 真实对话消息判据与 pre-step / turn-stop / 子代理事件的文本提取。
|
|
7
|
+
* 条件判定本身归 conditions/*;本模块不做命中判断、不预编译匹配器。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { currentEvents } from './history.mjs'
|
|
11
|
+
import { extractText } from './shared.mjs'
|
|
12
|
+
|
|
13
|
+
/** 按 subject 取参与匹配的文本;live 条件 conditions/text 消费。 */
|
|
14
|
+
export function subjectTextOf(subject, payload) {
|
|
15
|
+
if (payload === undefined || payload === null) return ''
|
|
16
|
+
const field = SUBJECT_FIELDS[subject]
|
|
17
|
+
return field !== undefined && typeof payload[field] === 'string' ? payload[field] : ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** subject → 载荷字段名。改这里等于改所有层的匹配对象。 */
|
|
21
|
+
export const SUBJECT_FIELDS = {
|
|
22
|
+
toolArgs: 'argsText',
|
|
23
|
+
toolResult: 'resultText',
|
|
24
|
+
userMessage: 'userText',
|
|
25
|
+
assistantText: 'assistantText',
|
|
26
|
+
subagentInfo: 'subagentText',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 工具参数的可匹配文本:对象按 JSON 序列化,字符串原样;序列化失败按空串。 */
|
|
30
|
+
export function toolArgsText(args) {
|
|
31
|
+
if (typeof args === 'string') return args
|
|
32
|
+
try {
|
|
33
|
+
return JSON.stringify(args ?? {})
|
|
34
|
+
} catch {
|
|
35
|
+
return ''
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 「真实对话消息」的唯一判据(角色过滤由消费方各自保留)。
|
|
41
|
+
*
|
|
42
|
+
* 引擎自己注入的消息同样是 user-role(`executor.mjs` 的 `role: PRE_STEP_ROLE`、
|
|
43
|
+
* `pluginMessage` 的 `source` 是 `plugin:<name>` 生产者身份),把它们算进来会让
|
|
44
|
+
* `text.match` / 关键词世界书匹配到自己注入过的正文——正文里出现「重构」「实现」
|
|
45
|
+
* 这类词就会自我命中,且命中判定在去重之前,`dedupe` 挡不住。
|
|
46
|
+
* `userMessagesText`、原生关键词世界书的扫描 haystack 与
|
|
47
|
+
* `context-activation.mjs#conversationMessages` 共用这一份,不再各写各的。
|
|
48
|
+
*/
|
|
49
|
+
export function isConversationMessage(message) {
|
|
50
|
+
if (message === null || typeof message !== 'object') return false
|
|
51
|
+
if (message.source?.plugin !== undefined) return false
|
|
52
|
+
return message.source?.kind === undefined || message.source.kind === 'user'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* pre-step 的缺省匹配对象:本批**真实对话**的用户消息文本(判据见
|
|
57
|
+
* {@link isConversationMessage})。
|
|
58
|
+
*/
|
|
59
|
+
export function userMessagesText(messages) {
|
|
60
|
+
if (!Array.isArray(messages)) return ''
|
|
61
|
+
return messages
|
|
62
|
+
.filter((message) => message?.role === 'user' && isConversationMessage(message))
|
|
63
|
+
.map((message) => extractText(message))
|
|
64
|
+
.filter((text) => text.length > 0)
|
|
65
|
+
.join('\n')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 最后一条 assistant 消息的文本(turn-stop 的缺省匹配对象);取**当前上下文**里可见的那条。 */
|
|
69
|
+
export function lastAssistantText(session) {
|
|
70
|
+
const events = currentEvents(session)
|
|
71
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
72
|
+
if (events[index]?.type === 'assistant/message') return extractText(events[index].data)
|
|
73
|
+
}
|
|
74
|
+
return ''
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 子代理事件的匹配文本:事件载荷序列化,取不到即空串。 */
|
|
78
|
+
export function subagentTextOf(info) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.stringify(info ?? {})
|
|
81
|
+
} catch {
|
|
82
|
+
return ''
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,31 +1,71 @@
|
|
|
1
|
-
import { createAnchorMatcher } from '../anchor-match.mjs'
|
|
2
|
-
import {
|
|
1
|
+
import { MATCH_LOGIC, createAnchorMatcher } from '../anchor-match.mjs'
|
|
2
|
+
import { findActionDelivery, visibleMessages } from '../delivery.mjs'
|
|
3
3
|
import { sessionOf } from './subject.mjs'
|
|
4
|
-
import {
|
|
4
|
+
import { keyList, optionalBoolean } from './values.mjs'
|
|
5
|
+
import { UNAVAILABLE } from './availability.mjs'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
7
|
+
const SUBJECTS = ['userMessage', 'assistantReasoning', 'assistantText', 'toolResult']
|
|
8
|
+
const FIELDS = new Set(['subject', 'keys', 'secondaryKeys', 'caseSensitive', 'wholeWords', 'useRegex', 'logic', 'mode', 'wordBoundary', 'fallbackAfter', 'after'])
|
|
9
|
+
|
|
10
|
+
/** 按所选来源取正文;缺少该来源的内容块不算一次相应输出。 */
|
|
11
|
+
function outputText(subject, event, message) {
|
|
12
|
+
if (subject === 'userMessage') {
|
|
13
|
+
if (event.type !== 'user/message' || message.role !== 'user' || message.source?.kind !== 'user' || message.source?.plugin !== undefined) return undefined
|
|
14
|
+
} else if (subject === 'toolResult') {
|
|
15
|
+
if (event.type !== 'tool/result') return undefined
|
|
16
|
+
} else if (event.type !== 'assistant/message' || event.data?.interrupted === true) return undefined
|
|
17
|
+
const type = subject === 'assistantReasoning' ? 'reasoning' : 'text'
|
|
18
|
+
const blocks = (Array.isArray(message.content) ? message.content : []).filter(block => block?.type === type)
|
|
19
|
+
return blocks.length === 0 ? undefined : blocks.map(block => typeof block.text === 'string' ? block.text : '').join('\n')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
export function createAnchorPredicate(options = {}) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
22
|
+
/** 从当前可见、已结算消息重建确认;关联只从目标实际投递的日志序号之后开始。 */
|
|
23
|
+
export function createAnchorPredicate(options = {}, context = {}) {
|
|
24
|
+
for (const key of Object.keys(options)) if (!FIELDS.has(key)) throw new TypeError(`anchor: unknown field ${key}`)
|
|
25
|
+
const subject = options.subject ?? 'assistantReasoning'
|
|
26
|
+
if (!SUBJECTS.includes(subject)) throw new TypeError(`anchor.subject must be one of ${SUBJECTS.join(', ')}`)
|
|
27
|
+
const mode = options.mode ?? 'prefix'
|
|
28
|
+
if (!['prefix', 'scan'].includes(mode)) throw new TypeError('anchor.mode must be prefix or scan')
|
|
29
|
+
if (mode === 'prefix' && ['secondaryKeys', 'caseSensitive', 'wholeWords', 'useRegex', 'logic', 'wordBoundary'].some(key => options[key] !== undefined)) {
|
|
30
|
+
throw new TypeError('anchor prefix matches literal keys only; use scan for other matching options')
|
|
31
|
+
}
|
|
32
|
+
if (options.logic !== undefined && !Object.values(MATCH_LOGIC).includes(options.logic)) throw new TypeError('anchor.logic is invalid')
|
|
33
|
+
const fallbackAfter = options.fallbackAfter
|
|
34
|
+
if (fallbackAfter !== undefined && (!Number.isSafeInteger(fallbackAfter) || fallbackAfter <= 0)) throw new TypeError('anchor.fallbackAfter must be a positive integer')
|
|
35
|
+
if (options.after !== undefined && (options.after === null || typeof options.after !== 'object' || Array.isArray(options.after)
|
|
36
|
+
|| Object.keys(options.after).some(key => key !== 'action') || typeof options.after.action !== 'string' || options.after.action.trim().length === 0)) {
|
|
37
|
+
throw new TypeError('anchor.after must be { action: non-empty string }')
|
|
38
|
+
}
|
|
39
|
+
const matcher = createAnchorMatcher({
|
|
40
|
+
keys: keyList(options.keys, 'anchor.keys'),
|
|
41
|
+
secondaryKeys: keyList(options.secondaryKeys, 'anchor.secondaryKeys'),
|
|
42
|
+
caseSensitive: optionalBoolean(options.caseSensitive, 'anchor.caseSensitive', false),
|
|
43
|
+
wholeWords: optionalBoolean(options.wholeWords, 'anchor.wholeWords', false),
|
|
44
|
+
useRegex: optionalBoolean(options.useRegex, 'anchor.useRegex', undefined),
|
|
45
|
+
logic: options.logic,
|
|
46
|
+
wordBoundary: options.wordBoundary,
|
|
47
|
+
mode,
|
|
48
|
+
})
|
|
26
49
|
const predicate = payload => {
|
|
27
|
-
const
|
|
28
|
-
|
|
50
|
+
const session = sessionOf(payload)
|
|
51
|
+
if (typeof session?.snapshotEvents !== 'function') return UNAVAILABLE
|
|
52
|
+
const messages = visibleMessages(session)
|
|
53
|
+
let start
|
|
54
|
+
if (options.after !== undefined) {
|
|
55
|
+
const target = context.resolveAction?.(options.after.action)
|
|
56
|
+
if (target === undefined) return UNAVAILABLE
|
|
57
|
+
start = findActionDelivery(session, target, messages)
|
|
58
|
+
if (start === undefined) return UNAVAILABLE
|
|
59
|
+
}
|
|
60
|
+
let outputs = 0
|
|
61
|
+
for (const { event, seq, message } of messages) {
|
|
62
|
+
if (start !== undefined && seq <= start.seq) continue
|
|
63
|
+
const text = outputText(subject, event, message)
|
|
64
|
+
if (text === undefined) continue
|
|
65
|
+
if (matcher.scan(text).active) return true
|
|
66
|
+
if (fallbackAfter !== undefined && ++outputs >= fallbackAfter) return true
|
|
67
|
+
}
|
|
68
|
+
return false
|
|
29
69
|
}
|
|
30
70
|
predicate.kind = 'anchor'
|
|
31
71
|
return predicate
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agentOf, sessionOf } from './subject.mjs'
|
|
2
|
-
import { SUBJECT_FIELDS } from '../condition.mjs'
|
|
2
|
+
import { SUBJECT_FIELDS } from '../condition-facts.mjs'
|
|
3
3
|
|
|
4
4
|
/** 缺少真实事件事实不是 false;尤其不能经 not 反转成允许。 */
|
|
5
5
|
export const UNAVAILABLE = Symbol('condition-input-unavailable')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MATCH_LOGIC } from '../anchor-match.mjs'
|
|
2
2
|
import { UNAVAILABLE } from './availability.mjs'
|
|
3
|
-
|
|
3
|
+
import { COMPOSITE_OPERATORS } from './operators.mjs'
|
|
4
4
|
|
|
5
5
|
export function composite(node) {
|
|
6
6
|
if (typeof node === 'function') return node
|
|
@@ -69,10 +69,10 @@ export function composite(node) {
|
|
|
69
69
|
predicate.kind = 'composite'
|
|
70
70
|
// 组合层必须**转发子谓词的 observe**(B7 T1 的跨能力缺口修复):两条挂载路径的观察器
|
|
71
71
|
// 接线都按 `when.observe` 过滤(`trigger.mjs` 的 `wireTriggerObservers`),被组合包住的
|
|
72
|
-
// phase / count
|
|
72
|
+
// phase / count 谓词否则永远收不到活的 `session/event`——状态按 `session.id`
|
|
73
73
|
// 缓存后不再重扫(`shared.mjs` 的 `sessionState`),判定会停在冷扫那一刻(计数恒旧值、
|
|
74
74
|
// 相位永不晋升)。`phase` 有 `subscribe: false` 可绕(代价是每次判定冷扫),
|
|
75
|
-
// `count`
|
|
75
|
+
// `count` 没有该档,所以这条转发是它唯一的活路。
|
|
76
76
|
const observers = children.filter((child) => typeof child.observe === 'function')
|
|
77
77
|
if (observers.length > 0) {
|
|
78
78
|
predicate.observe = (session, event) => {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { MAX_TRACKED_SESSIONS, extractText, isDelegated
|
|
1
|
+
import { MAX_TRACKED_SESSIONS, extractText, isDelegated } from '../shared.mjs'
|
|
2
|
+
import { SURFACE_MESSAGE_TYPES, currentEvents, historyEvents, viewRestricted } from '../history.mjs'
|
|
3
|
+
import { isSuccessfulCompactionEnd } from '../compaction-epoch.mjs'
|
|
2
4
|
import { sessionOf } from './subject.mjs'
|
|
3
5
|
import { boundOf, optionalBoolean } from './values.mjs'
|
|
4
6
|
|
|
@@ -20,12 +22,18 @@ const TURN_EVENT = 'turn/start'
|
|
|
20
22
|
/**
|
|
21
23
|
* 计数判断:次数 / 字符数 / 轮次,带上下限与**冷启动重建**。
|
|
22
24
|
*
|
|
23
|
-
* - `per: 'session'`
|
|
25
|
+
* - `per: 'session'` 计本会话全部(视图口径见下条);`per: 'turn'` 只计**最新轮**,当前轮由
|
|
24
26
|
* `turn/start` 与计数信号自身带轮号的事件共同推进(与 deliberation-gate 的
|
|
25
27
|
* `turnOf` 同规则),轮号取自 `event.data.turn`;无轮号的事件不参与轮内计数
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* (宁可少计,也不把跨轮数据算进当前轮)。消息类信号读当前上下文时看不到 `turn/start`,
|
|
29
|
+
* 当前轮只由带轮号的可见信号推进。
|
|
30
|
+
* - 视图:消息类信号读**当前上下文**——被压缩 / 位置替换遮蔽的消息不再计入;`tool/call` 与
|
|
31
|
+
* `turn/start` 不是 surface 承载类型,读**完整历史**,「重启/恢复/重挂后同一结果」由此保住。
|
|
32
|
+
* - 冷启动:首次判定冷扫重建(重启/恢复/重挂后同一结果),之后 O(1);`observe` 是增量喂入,
|
|
33
|
+
* **只喂已判定过的会话**,避免「冷扫 + 增量」重复计数。成功压缩或 surface 代次推进时条目
|
|
34
|
+
* 整体作废、下次求值重建——这条复位只对**消息类**信号生效(它读可见上下文),log-only
|
|
35
|
+
* 信号重建读完整历史、压缩不改变它;压缩后 `every: N` 的节奏随之重置,可能立刻再命中一次
|
|
36
|
+
* (已接受的用户可见行为,见 CHANGELOG)。
|
|
29
37
|
* - 上下限:`count >= min`(声明时)且 `count <= max`(声明时)。至少要声明一侧,
|
|
30
38
|
* 否则该判定恒真——那是把配置错误伪装成命中。
|
|
31
39
|
* - 取不到会话 = 无事件 = 计数 0(与空会话同一语义,不发明数据);无 `id` 的会话
|
|
@@ -73,9 +81,12 @@ export function createCountPredicate(options = {}) {
|
|
|
73
81
|
// **主判据就是 count**,受众不能只挂在 `session` 谓词上——那会迫使声明额外耦合一个
|
|
74
82
|
// durable 事件类型(还要回答「哪种 type 恒真」,而哨兵轮表明甚至可能没有轮事件)。
|
|
75
83
|
const delegated = optionalBoolean(options.delegated, 'delegated', undefined)
|
|
76
|
-
/** sessionId -> { total, turns, lastTurn }(进程内快路径,真相在 durable 事件流)。 */
|
|
84
|
+
/** sessionId -> { total, turns, lastTurn, generation }(进程内快路径,真相在 durable 事件流)。 */
|
|
77
85
|
const state = new Map()
|
|
78
|
-
const fresh = () => ({ total: 0, turns: new Map(), lastTurn: undefined })
|
|
86
|
+
const fresh = (generation) => ({ total: 0, turns: new Map(), lastTurn: undefined, generation })
|
|
87
|
+
const generationOf = (session) => session?.surface?.replaceGeneration
|
|
88
|
+
/** 消息类信号按当前上下文计,log-only 信号按完整历史计(判据见 `SURFACE_MESSAGE_TYPES`)。 */
|
|
89
|
+
const view = SURFACE_MESSAGE_TYPES.has(signal.type) ? currentEvents : historyEvents
|
|
79
90
|
const measure = (event) => (signal.measure === 'chars' ? extractText(event.data).length : 1)
|
|
80
91
|
/** 轮号推进:与 deliberation-gate 的 turnOf/turnEntryOf 同规则——`turn/start` 与
|
|
81
92
|
* 计数信号自身带轮号的事件都推进当前轮,且取最大值;新轮先落 0,再累加。 */
|
|
@@ -111,14 +122,24 @@ export function createCountPredicate(options = {}) {
|
|
|
111
122
|
? entry.total
|
|
112
123
|
: (entry.lastTurn === undefined ? 0 : entry.turns.get(entry.lastTurn) ?? 0))
|
|
113
124
|
const rebuild = (session) => {
|
|
114
|
-
const entry = fresh()
|
|
115
|
-
|
|
125
|
+
const entry = fresh(generationOf(session))
|
|
126
|
+
// 位置替换后同一事件可在 `surface.nodes` 里占多个位置(`history.mjs` 头部):按条数直数会虚高,
|
|
127
|
+
// `every: N` 提前触发。增量 `observe` 一次只收一条新事件,无需去重。
|
|
128
|
+
const seen = new Set()
|
|
129
|
+
for (const event of view(session)) {
|
|
130
|
+
if (event?.seq !== undefined) {
|
|
131
|
+
if (seen.has(event.seq)) continue
|
|
132
|
+
seen.add(event.seq)
|
|
133
|
+
}
|
|
134
|
+
applyEvent(entry, event)
|
|
135
|
+
}
|
|
116
136
|
return entry
|
|
117
137
|
}
|
|
118
138
|
const entryOf = (session) => {
|
|
119
139
|
if (session?.id === undefined) return rebuild(session)
|
|
120
140
|
const known = state.get(session.id)
|
|
121
|
-
|
|
141
|
+
// 位置替换没有对应的 durable 事件可观察,只在 surface 代次上前进:代次变了就按当前上下文重建。
|
|
142
|
+
if (known !== undefined && known.generation === generationOf(session)) return known
|
|
122
143
|
const entry = rebuild(session)
|
|
123
144
|
if (state.size >= MAX_TRACKED_SESSIONS) state.clear()
|
|
124
145
|
state.set(session.id, entry)
|
|
@@ -132,7 +153,17 @@ export function createCountPredicate(options = {}) {
|
|
|
132
153
|
}
|
|
133
154
|
predicate.kind = 'count'
|
|
134
155
|
predicate.observe = (session, event) => {
|
|
135
|
-
|
|
156
|
+
if (session?.id === undefined) return
|
|
157
|
+
// 失效条件必须与它读的视图一致:只有消息类信号按可见上下文计数,压缩复位对
|
|
158
|
+
// log-only 信号(重建读 `historyEvents`)不改变任何结果,白白触发一次重建。
|
|
159
|
+
const flagged = SURFACE_MESSAGE_TYPES.has(signal.type)
|
|
160
|
+
// 成功压缩换掉了可见历史,而条目按**全部** durable 事件累加过(含被遮蔽的),无法反演
|
|
161
|
+
// → 整体清条目、下次求值重建(宿主没暴露 `replaceGeneration` 时这是唯一的复位信号)。
|
|
162
|
+
if (flagged && isSuccessfulCompactionEnd(event)) { state.delete(session.id); return }
|
|
163
|
+
// 视图受限时重建路径读 `currentEvents`,那里没有 `turn/start` 这类 log-only 事件;
|
|
164
|
+
// 增量若照单全收就会靠它推进当前轮,同一会话两种结论(「冷启动 == 增量」失效)。
|
|
165
|
+
if (flagged && viewRestricted(session) && !SURFACE_MESSAGE_TYPES.has(event?.type)) return
|
|
166
|
+
const entry = state.get(session.id)
|
|
136
167
|
if (entry !== undefined) applyEvent(entry, event)
|
|
137
168
|
}
|
|
138
169
|
return predicate
|
|
@@ -1,24 +1,59 @@
|
|
|
1
1
|
import { subjectOf } from './subject.mjs'
|
|
2
|
+
import { UNAVAILABLE } from './availability.mjs'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 一次官方调用的局部判定帧;不按 session、turn 或载荷身份缓存。
|
|
5
|
-
* `ctx` 是判定期服务入口:子代理事件靠它用 `id` 反查 agent(见 subject.mjs
|
|
6
|
-
* `
|
|
6
|
+
* `ctx` 是判定期服务入口:子代理事件靠它用 `id` 反查 agent(见 `subject.mjs` 的通道事实表
|
|
7
|
+
* `CHANNELS`),使 `modelScope`/`audience` 在判定阶段就有事实可用。
|
|
8
|
+
* `onOutcome` 是可选只读上报(规则、通道、判定类别),供宿主统计「配了没生效」;
|
|
9
|
+
* 缺省不传即零开销,也不改变任何判定结果。
|
|
7
10
|
*/
|
|
8
|
-
export function ruleFrame(channel, args, warnOnce = () => {}, ctx) {
|
|
9
|
-
return { channel, args, subject: subjectOf(channel, args, warnOnce, ctx), decisions: new Map(), warnOnce }
|
|
11
|
+
export function ruleFrame(channel, args, warnOnce = () => {}, ctx, onOutcome) {
|
|
12
|
+
return { channel, args, subject: subjectOf(channel, args, warnOnce, ctx), decisions: new Map(), actionDecisions: new Set(), warnOnce, onOutcome }
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
/** 条件返回值的三值归一:词表与 bridge 的 `RuleOutcomeKind` 共享,故只此一处定义。 */
|
|
16
|
+
function classifyOutcome(decided) {
|
|
17
|
+
return decided === true ? 'hit' : decided === UNAVAILABLE ? 'unavailable' : 'miss'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 只读上报:诊断抛错不得反过来打断判定(调用方可能在记账里访问规则身份)。 */
|
|
21
|
+
function report(frame, rule, outcome) {
|
|
22
|
+
try { frame.onOutcome?.(rule, frame.channel, outcome) } catch { /* 诊断不得反过来打断判定 */ }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 单次规则级判定(帧内缓存,键为规则实例):三值语义见下;只判定、不上报。 */
|
|
26
|
+
function decideRule(rule, frame) {
|
|
27
|
+
if (rule === undefined) return { hit: true, outcome: 'hit' }
|
|
28
|
+
const cached = frame.decisions.get(rule)
|
|
29
|
+
if (cached !== undefined) return cached
|
|
15
30
|
let hit = rule.enabled !== false
|
|
31
|
+
let outcome = hit ? 'hit' : 'disabled'
|
|
16
32
|
if (hit && typeof rule.when === 'function') {
|
|
17
|
-
try {
|
|
18
|
-
|
|
33
|
+
try {
|
|
34
|
+
const decided = rule.when(frame.subject)
|
|
35
|
+
hit = decided === true
|
|
36
|
+
// 三值语义:缺事实(UNAVAILABLE)与「条件为假」在诊断里必须分开——前者是
|
|
37
|
+
// 「为什么没触发」的答案,后者是条件按设计不满足。
|
|
38
|
+
outcome = classifyOutcome(decided)
|
|
39
|
+
} catch (error) {
|
|
40
|
+
hit = false
|
|
41
|
+
outcome = 'error'
|
|
42
|
+
frame.warnOnce(`rule ${rule.id} condition failed: ${String(error?.message ?? error)}`)
|
|
43
|
+
}
|
|
19
44
|
}
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
const decided = { hit, outcome }
|
|
46
|
+
frame.decisions.set(rule, decided)
|
|
47
|
+
return decided
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function ruleMatches(rule, frame) {
|
|
51
|
+
if (rule === undefined) return true
|
|
52
|
+
const cached = frame.decisions.get(rule)
|
|
53
|
+
if (cached !== undefined) return cached.hit
|
|
54
|
+
const decided = decideRule(rule, frame)
|
|
55
|
+
report(frame, rule, decided.outcome)
|
|
56
|
+
return decided.hit
|
|
22
57
|
}
|
|
23
58
|
|
|
24
59
|
/**
|
|
@@ -27,15 +62,35 @@ export function ruleMatches(rule, frame) {
|
|
|
27
62
|
* `bypassRuleWhen` 的动作来自规则级 `else`——它的条件里已经含 `not(if)`,再叠加
|
|
28
63
|
* `rule.when` 会自相矛盾、令该分支永不执行。动作条件缺省 = 恒真;非 `true`
|
|
29
64
|
* (含 UNAVAILABLE 三值语义)一律不命中,与 `ruleMatches` 同一纪律。
|
|
65
|
+
*
|
|
66
|
+
* 上报(方案 A:不加 actionId 维度):带动作级分支的动作上报「规则∧动作」的**最终**
|
|
67
|
+
* 判定,按帧对每个动作实例去重一次——分支为真记 `hit`、为假记 `miss`、缺事实记
|
|
68
|
+
* `unavailable`、异常记 `error`;这类动作不再单独发规则级上报,避免同一事件先记
|
|
69
|
+
* `hit` 再记 `miss`。没有分支的动作最终判定就是规则级判定,沿用规则级上报。
|
|
30
70
|
*/
|
|
31
71
|
export function actionMatches(entry, frame) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
const branched = entry.actionWhen !== undefined || entry.bypassRuleWhen === true
|
|
73
|
+
if (!branched) return ruleMatches(entry.rule, frame)
|
|
74
|
+
// `else`(bypassRuleWhen)自带 not(if),不叠加规则级判定;其余带分支的动作自判
|
|
75
|
+
// 规则级(不发上报),判定仍走同一份帧内缓存,同帧同规则依旧只判一次。
|
|
76
|
+
const ruled = entry.bypassRuleWhen === true ? { hit: true, outcome: 'hit' } : decideRule(entry.rule, frame)
|
|
77
|
+
let hit = ruled.hit
|
|
78
|
+
let outcome = ruled.outcome
|
|
79
|
+
if (hit) {
|
|
80
|
+
try {
|
|
81
|
+
const decided = entry.actionWhen === undefined ? true : entry.actionWhen(frame.subject)
|
|
82
|
+
hit = decided === true
|
|
83
|
+
outcome = classifyOutcome(decided)
|
|
84
|
+
} catch (error) {
|
|
85
|
+
hit = false
|
|
86
|
+
outcome = 'error'
|
|
87
|
+
frame.warnOnce(`action ${entry.id} condition failed: ${String(error?.message ?? error)}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// 只去重上报,不缓存判定结果:同帧内重复求值仍按当次事实判定。
|
|
91
|
+
if (!frame.actionDecisions.has(entry)) {
|
|
92
|
+
frame.actionDecisions.add(entry)
|
|
93
|
+
report(frame, entry.rule, outcome)
|
|
40
94
|
}
|
|
95
|
+
return hit
|
|
41
96
|
}
|
|
@@ -7,5 +7,6 @@ export const PREDICATE_EXAMPLES = {
|
|
|
7
7
|
session: { type: 'user/message', present: false },
|
|
8
8
|
preset: { presetId: 'preset-id' },
|
|
9
9
|
scope: { audience: 'main', modelScope: 'all' },
|
|
10
|
-
anchor: { keys: ['
|
|
10
|
+
anchor: { subject: 'assistantReasoning', keys: ['确认'], mode: 'prefix' },
|
|
11
|
+
variable: { key: 'quota', op: 'lte', value: '0', scope: 'effective' },
|
|
11
12
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** `conditions/index.mjs` 的手写声明:只声明 `src/` 消费的导出。 */
|
|
2
|
+
|
|
3
|
+
/** 官方 `standingMountFor` 的挂载事实;仅覆盖插件依赖面(引擎只读 `presetId`)。 */
|
|
4
|
+
export interface StandingMount {
|
|
5
|
+
presetId?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** 官方 `@deepseek-ai/dsh-agent-preset-registry` 的 `standingMountFor` 导出。 */
|
|
9
|
+
export type StandingMountFor = (ctx: unknown) => StandingMount | undefined
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 惰性解析官方 `standingMountFor`:包缺席(最小组合、测试)返回 `undefined`,
|
|
13
|
+
* 不是错误——此时没有兜底,预设条件判定恒不命中。
|
|
14
|
+
*/
|
|
15
|
+
export function loadStandingMountFor(entry?: string): Promise<StandingMountFor | undefined>
|