dsh-plugin-prompt-tool 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +5 -7
- 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 +23 -15
- 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 +62 -19
- 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 +4 -7
- 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 +4 -4
- package/engine/conditions/count.mjs +43 -13
- 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 +10 -5
- 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 +165 -230
- 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 +302 -28
- 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 +47 -26
- 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 +39 -35
- 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 +37 -762
- package/lib/index.mjs +26919 -5791
- 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 +158 -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 +100 -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/README.md +10 -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 +40 -41
- package/skills/dsh-module/SKILL.md +119 -0
- package/skills/dsh-module/reference.md +185 -0
- 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 +20 -0
- package/templates/64-pre-step-filter.yml +23 -0
- package/templates/65-turn-stop.yml +2 -2
- package/templates/67-inbox-prepend.yml +22 -0
- 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/library/agent-instructions.yml +0 -9
- package/engine/compositions/library/command-goal.yml +0 -7
- package/engine/compositions/library/compaction.yml +0 -22
- package/engine/compositions/library/delegation-ptc.yml +0 -58
- package/engine/compositions/library/delegation.yml +0 -56
- package/engine/compositions/library/persistent-shell.yml +0 -50
- package/engine/compositions/library/planning.yml +0 -26
- package/engine/compositions/library/skill-filesystem-cordis.yml +0 -10
- package/engine/compositions/library/skill-filesystem.yml +0 -7
- package/engine/compositions/library/tool-ask-user.yml +0 -7
- package/engine/compositions/library/tool-bash.yml +0 -8
- package/engine/compositions/library/tool-cordis.yml +0 -7
- package/engine/compositions/library/tool-fs-search.yml +0 -9
- package/engine/compositions/library/tool-fs.yml +0 -7
- package/engine/compositions/library/tool-goal.yml +0 -7
- package/engine/compositions/library/tool-jobs.yml +0 -7
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +0 -8
- package/engine/compositions/library/tool-plugin-manager.yml +0 -8
- package/engine/compositions/library/tool-present.yml +0 -7
- package/engine/compositions/library/tool-presentation.yml +0 -9
- package/engine/compositions/library/tool-pwsh.yml +0 -8
- package/engine/compositions/library/tool-skill.yml +0 -7
- package/engine/compositions/library/tool-todo.yml +0 -9
- package/engine/compositions/library/tool-web.yml +0 -10
- package/engine/compositions/source/local/character-tools.yml +0 -5
- package/engine/compositions/source/local/declared-triggers.yml +0 -12
- 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 -39
- package/lib/module-package-CHPFweqK.mjs +0 -2
- package/lib/module-package-Cnd1_mwx.mjs +0 -2872
- package/lib/paths-CyP0IkaM.d.mts +0 -1390
- package/lib/preset-transfer.d.mts +0 -2
- package/lib/preset-transfer.mjs +0 -3
- package/lib/rules-migration-DJ6vASB5.mjs +0 -15334
- 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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 机器路径事实的**唯一取值来源**:`DSH_HOME` / `CWD` / `WORKSPACE` 三个键怎么推导,只在这里定。
|
|
3
|
+
*
|
|
4
|
+
* 此前这条配方在四处各写一遍——`interpolate` 的内置变量、`fillers` 的 env facts、
|
|
5
|
+
* `run-code-env` 的进程环境兜底、`instruction-hint` 的指令文件探测——而且已经分叉:
|
|
6
|
+
* 未设 `DSH_HOME` 时前两者按用户主目录推导出 `<USERPROFILE>\.dsh`,`run-code-env` 则
|
|
7
|
+
* **不给该键**。同一台机器上 `{{DSH_HOME}}` 与 `run_code` 里的 `env.DSH_HOME` 因此可能
|
|
8
|
+
* 是两个答案。
|
|
9
|
+
*
|
|
10
|
+
* 本模块只提供**取值**:各处的白名单、注入形态(插值保留名 / variables / 进程 env)与
|
|
11
|
+
* 「空值算不算有值」的策略留在调用方——它们的产出物本来就不同,不该被这一个函数接管。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {object|undefined} session 会话(读它的 `header.cwd`;缺省回退进程 cwd)
|
|
16
|
+
* @param {Record<string, string|undefined>|undefined} env 环境来源(缺省取当前 `process.env`;
|
|
17
|
+
* 宿主快照与测试可显式替换——测试**应该**传它,否则读真实环境、用例不稳定)
|
|
18
|
+
* @returns {{ DSH_HOME: string, WORKSPACE: string, CWD: string }}
|
|
19
|
+
* `DSH_HOME`:显式环境变量 → `<USERPROFILE>\.dsh` → 空串(**不是** `undefined`:
|
|
20
|
+
* 调用方若要区分「不可用」,自行把空串当缺省——例如 `instruction-hint` 需要
|
|
21
|
+
* `undefined` 表示「没有可探测的全局目录」)。
|
|
22
|
+
*
|
|
23
|
+
* **只给取值,不给策略**:键要不要出现在产物里由调用方决定。例如 `run-code-env` 原来
|
|
24
|
+
* 「没有会话 cwd 就不写 `DSH_WORKSPACE`」,统一取值后 `CWD` 会回退到 `process.cwd()`,
|
|
25
|
+
* 那处仍按自己的规则判空后再决定是否落键。
|
|
26
|
+
*
|
|
27
|
+
* 无 `process` 的环境(非 Node 宿主)不抛:环境与 cwd 都退化成空值,与 `run-code-env`
|
|
28
|
+
* 原来的 `typeof process === 'undefined'` 守卫同口径。
|
|
29
|
+
*/
|
|
30
|
+
export function machinePaths(session, env = currentEnv()) {
|
|
31
|
+
const cwd = session?.header?.cwd ?? currentCwd()
|
|
32
|
+
return {
|
|
33
|
+
DSH_HOME: env?.DSH_HOME ?? (env?.USERPROFILE ? `${env.USERPROFILE}\\.dsh` : ''),
|
|
34
|
+
WORKSPACE: env?.DSH_WORKSPACE ?? cwd,
|
|
35
|
+
CWD: cwd,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const currentEnv = () => (typeof process === 'undefined' ? undefined : process.env)
|
|
40
|
+
const currentCwd = () => (typeof process === 'undefined' ? '' : process.cwd())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { toolServiceName } from './service-names.mjs'
|
|
2
|
+
|
|
3
|
+
export const name = 'module-file-tools'
|
|
4
|
+
export function apply(ctx) {
|
|
5
|
+
ctx.inject([toolServiceName('module-file-tools')], scopeCtx => {
|
|
6
|
+
scopeCtx.effect(() => scopeCtx.get(toolServiceName('module-file-tools')).mount(scopeCtx), name + ': mount')
|
|
7
|
+
})
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 角色卡库工具预设模块。 */
|
|
2
|
+
import { toolServiceName } from "./service-names.mjs"
|
|
3
|
+
|
|
4
|
+
export const name = "module-import-tools"
|
|
5
|
+
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
ctx.inject([toolServiceName("module-import-tools")], (scopeCtx) => {
|
|
8
|
+
scopeCtx.effect(() => scopeCtx.get(toolServiceName("module-import-tools")).mount(scopeCtx), name + ": mount")
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { toolServiceName } from './service-names.mjs'
|
|
2
|
+
|
|
3
|
+
export const name = 'module-manifest-tools'
|
|
4
|
+
export function apply(ctx) {
|
|
5
|
+
ctx.inject([toolServiceName('module-manifest-tools')], scopeCtx => {
|
|
6
|
+
scopeCtx.effect(() => scopeCtx.get(toolServiceName('module-manifest-tools')).mount(scopeCtx), name + ': mount')
|
|
7
|
+
})
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 完整模块规则工具,按声明等待宿主 provider。 */
|
|
2
|
+
import { toolServiceName } from './service-names.mjs'
|
|
3
|
+
|
|
4
|
+
export const name = 'module-rule-tools'
|
|
5
|
+
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
ctx.inject([toolServiceName('module-rule-tools')], scopeCtx => {
|
|
8
|
+
scopeCtx.effect(() => scopeCtx.get(toolServiceName('module-rule-tools')).mount(scopeCtx), name + ': mount')
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 两级持久模块变量工具,按声明等待宿主 provider。 */
|
|
2
|
+
import { toolServiceName } from './service-names.mjs'
|
|
3
|
+
|
|
4
|
+
export const name = 'module-variable-tools'
|
|
5
|
+
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
ctx.inject([toolServiceName('module-variable-tools')], scopeCtx => {
|
|
8
|
+
scopeCtx.effect(() => scopeCtx.get(toolServiceName('module-variable-tools')).mount(scopeCtx), name + ': mount')
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** `order.mjs` 的手写声明:只声明 `src/` 消费的导出。 */
|
|
2
|
+
|
|
3
|
+
/** 排序所需的身份字段;仅覆盖插件依赖面(其余字段引擎不读)。 */
|
|
4
|
+
export interface ConfigSequenceLike {
|
|
5
|
+
id: string
|
|
6
|
+
sequence?: number
|
|
7
|
+
order?: number
|
|
8
|
+
sourceModuleId?: string
|
|
9
|
+
ruleId?: string
|
|
10
|
+
ruleActionIndex?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 模块配置的执行顺序:`sequence`(回退 `order`)优先,其次模块与规则身份。 */
|
|
14
|
+
export function compareConfigSequence(a: ConfigSequenceLike, b: ConfigSequenceLike): number
|
package/engine/order.mjs
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* path-safety — 真实路径(realpath)边界:词法 containment 拦不住 symlink / junction。
|
|
3
|
+
*
|
|
4
|
+
* 两个消费者共用这一条判据:instruction-hint 读 params.file、tool-config-engine 的 fs 执行器。
|
|
5
|
+
* 已存在的目标解析自身;新文件解析父目录(writeFileSync 不创建目录),悬空链接先按
|
|
6
|
+
* readlink 追到缺失目标——否则 write 会顺着链接写到信任根之外。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readlinkSync, realpathSync } from 'node:fs'
|
|
10
|
+
import { basename, dirname, join, resolve, sep } from 'node:path'
|
|
11
|
+
|
|
12
|
+
/** target 在 root 内(含 root 自身);两者都必须是真实路径。 */
|
|
13
|
+
function isWithin(root, target) {
|
|
14
|
+
if (target === root) return true
|
|
15
|
+
return target.startsWith(root.endsWith(sep) ? root : root + sep)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 目标真实路径;深层目录不存在或链接环时抛出原始错误。 */
|
|
19
|
+
function realPathOf(target) {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(target)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error?.code !== 'ENOENT') throw error
|
|
24
|
+
let link
|
|
25
|
+
try { link = readlinkSync(target) } catch { link = undefined }
|
|
26
|
+
// 悬空符号链接:realpath 看不到目标,但 write 会顺着它落盘,所以先解析链接本身。
|
|
27
|
+
if (link !== undefined) return realPathOf(resolve(dirname(target), link))
|
|
28
|
+
return join(realpathSync(dirname(target)), basename(target))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 信任根的真实路径;不存在或不可解析的根跳过——根不可用不等于放行。 */
|
|
33
|
+
function usableRoots(roots) {
|
|
34
|
+
const allowed = []
|
|
35
|
+
for (const root of roots) {
|
|
36
|
+
if (typeof root !== 'string' || root.length === 0) continue
|
|
37
|
+
try { allowed.push(realpathSync(root)) } catch { /* 根不存在或不可解析 */ }
|
|
38
|
+
}
|
|
39
|
+
return allowed
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 解析 target 并要求真实路径落在任一信任根内(fail closed)。
|
|
44
|
+
*
|
|
45
|
+
* @param target 已 resolve 的绝对路径(相对路径由调用方按自己的基目录解析)
|
|
46
|
+
* @param roots 信任根:会话 cwd、DSH_HOME 等
|
|
47
|
+
* @param label 报错中的对象名(path / instruction file)
|
|
48
|
+
* @returns 真实路径:调用方后续 IO 都用它,避免校验与使用之间再次经过链接
|
|
49
|
+
*/
|
|
50
|
+
export function resolveRealWithin(target, roots, label = 'path') {
|
|
51
|
+
const real = realPathOf(target)
|
|
52
|
+
const allowed = usableRoots(roots)
|
|
53
|
+
if (allowed.some((root) => isWithin(root, real))) return real
|
|
54
|
+
const shown = allowed.length > 0
|
|
55
|
+
? allowed.join(', ')
|
|
56
|
+
: roots.filter((root) => typeof root === 'string' && root.length > 0).join(', ') || 'the trust roots'
|
|
57
|
+
throw new Error(`${label} ${JSON.stringify(target)} escapes the trust root: real path ${JSON.stringify(real)} is outside ${shown}`)
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** `pre-step-source.mjs` 的手写声明:来源形状与批合并的唯一权威。 */
|
|
2
|
+
import type { PromotionTracker } from './compaction-epoch.mjs'
|
|
3
|
+
|
|
4
|
+
/** pre-step 条目;只声明调度/批执行读取的字段。 */
|
|
5
|
+
export interface PreStepConfigLike {
|
|
6
|
+
id: string
|
|
7
|
+
layer?: string
|
|
8
|
+
position?: string
|
|
9
|
+
promotion?: string
|
|
10
|
+
audience?: string | null
|
|
11
|
+
modelScope?: string
|
|
12
|
+
dedupe?: string
|
|
13
|
+
texts?: string[]
|
|
14
|
+
params?: Record<string, unknown>
|
|
15
|
+
variables?: Record<string, unknown>
|
|
16
|
+
sequence?: number
|
|
17
|
+
order?: number
|
|
18
|
+
sourceModuleId?: string
|
|
19
|
+
/** 规则实例;批执行器据此把判定上报分给来源。 */
|
|
20
|
+
rule?: unknown
|
|
21
|
+
/** 由来源生产者绑定 ctx 的逐条求值事务。 */
|
|
22
|
+
resolve?(input: Record<string, unknown>): unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 来源侧判定上报;moduleId 等归属由 source 自己闭包,不在此重复。 */
|
|
26
|
+
export interface PreStepOutcomeReport {
|
|
27
|
+
ruleId: string
|
|
28
|
+
channel: string
|
|
29
|
+
outcome: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type PreStepOutcomeSink = (report: PreStepOutcomeReport) => void
|
|
33
|
+
|
|
34
|
+
/** 执行 owner 唯一持有的晋升实例对;source 只携带同一引用。 */
|
|
35
|
+
export interface PreStepPromotion {
|
|
36
|
+
main: PromotionTracker
|
|
37
|
+
withSubagents: PromotionTracker
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 一次 pre-step 注册;ctx/onOutcome/promotion 都是来源事实。 */
|
|
41
|
+
export interface PreStepSource {
|
|
42
|
+
sourceId: string
|
|
43
|
+
configs: readonly PreStepConfigLike[]
|
|
44
|
+
ruleActions?: readonly unknown[]
|
|
45
|
+
ctx: unknown
|
|
46
|
+
onOutcome?: PreStepOutcomeSink
|
|
47
|
+
promotion?: PreStepPromotion
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CreatePreStepSourceOptions {
|
|
51
|
+
sourceId: string
|
|
52
|
+
configs: readonly PreStepConfigLike[]
|
|
53
|
+
ruleActions?: readonly unknown[]
|
|
54
|
+
ctx: unknown
|
|
55
|
+
onOutcome?: PreStepOutcomeSink
|
|
56
|
+
promotion?: PreStepPromotion
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 批执行所需的扁平事实;report 是三参内部适配器(来源 onOutcome 是对象 sink)。 */
|
|
60
|
+
export interface ComposedPreStepSources {
|
|
61
|
+
configs: PreStepConfigLike[]
|
|
62
|
+
ruleActions: unknown[]
|
|
63
|
+
promotion?: PreStepPromotion
|
|
64
|
+
report?: (rule: { id: string }, channel: string, outcome: string) => void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createPreStepSource(options: CreatePreStepSourceOptions): PreStepSource
|
|
68
|
+
export function promotionOwnerOf(sources: ReadonlyArray<PreStepSource>): PreStepPromotion | undefined
|
|
69
|
+
export function composePreStepSources(sources: ReadonlyArray<PreStepSource>): ComposedPreStepSources
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre-step-source — 一次 pre-step 注册所携带的事实。
|
|
3
|
+
*
|
|
4
|
+
* 来源生产者(mountRuleSources / 直接 inject-text)在这里绑定自己的 ctx;执行 owner
|
|
5
|
+
* (coordinator 或独立 apply)只合并来源、检查 promotion 唯一性,并按 rule 分派判定上报。
|
|
6
|
+
* 本模块不做注入、不排序、不读会话。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 来源身份前缀的唯一出处;调用方不要再手写 `module:` / `preset:`。 */
|
|
10
|
+
export const moduleSourceId = moduleId => `module:${moduleId}`
|
|
11
|
+
export const presetSourceId = id => `preset:${id}`
|
|
12
|
+
|
|
13
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
14
|
+
|
|
15
|
+
/** 来源 ctx 绑定一次;批执行传入的 ctx 不覆盖来源自己的 scope。 */
|
|
16
|
+
export function createPreStepSource(options) {
|
|
17
|
+
if (!isRecord(options)) throw new TypeError('pre-step source options must be an object')
|
|
18
|
+
const { sourceId, configs, ruleActions, ctx, onOutcome, promotion } = options
|
|
19
|
+
if (typeof sourceId !== 'string' || sourceId.length === 0) {
|
|
20
|
+
throw new TypeError('pre-step source requires a non-empty sourceId')
|
|
21
|
+
}
|
|
22
|
+
if (ctx === undefined || ctx === null) throw new TypeError(`pre-step source ${sourceId} requires a ctx`)
|
|
23
|
+
if (!Array.isArray(configs)) throw new TypeError(`pre-step source ${sourceId}.configs must be an array`)
|
|
24
|
+
if (ruleActions !== undefined && !Array.isArray(ruleActions)) {
|
|
25
|
+
throw new TypeError(`pre-step source ${sourceId}.ruleActions must be an array when present`)
|
|
26
|
+
}
|
|
27
|
+
if (onOutcome !== undefined && typeof onOutcome !== 'function') {
|
|
28
|
+
throw new TypeError(`pre-step source ${sourceId}.onOutcome must be a function when present`)
|
|
29
|
+
}
|
|
30
|
+
if (promotion !== undefined && (!isRecord(promotion) || !isRecord(promotion.main) || !isRecord(promotion.withSubagents))) {
|
|
31
|
+
throw new TypeError(`pre-step source ${sourceId}.promotion must be a { main, withSubagents } pair when present`)
|
|
32
|
+
}
|
|
33
|
+
const bound = configs.map((config, index) => {
|
|
34
|
+
if (!isRecord(config)) throw new TypeError(`pre-step source ${sourceId}.configs[${index}] must be an object`)
|
|
35
|
+
const resolve = config.resolve
|
|
36
|
+
return {
|
|
37
|
+
...config,
|
|
38
|
+
...(typeof resolve !== 'function' ? {} : {
|
|
39
|
+
resolve: input => resolve({ ...input, ctx }),
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
return {
|
|
44
|
+
sourceId,
|
|
45
|
+
configs: bound,
|
|
46
|
+
...(ruleActions === undefined ? {} : { ruleActions: [...ruleActions] }),
|
|
47
|
+
ctx,
|
|
48
|
+
...(onOutcome === undefined ? {} : { onOutcome }),
|
|
49
|
+
...(promotion === undefined ? {} : { promotion }),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 所有有 owner 的来源必须引用同一个实例;返回该 owner(没有则 undefined)。 */
|
|
54
|
+
export function promotionOwnerOf(sources) {
|
|
55
|
+
const owners = sources.map(source => source.promotion).filter(owner => owner !== undefined)
|
|
56
|
+
if (owners.length > 1 && owners.some(owner => owner !== owners[0])) {
|
|
57
|
+
throw new TypeError('pre-step sources must share one promotion owner')
|
|
58
|
+
}
|
|
59
|
+
return owners[0]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 合并来源,建立唯一的 promotion owner 与按 rule 分派的判定上报。
|
|
64
|
+
* 有实际工作(configs/ruleActions 非空)却没有 promotion,或 owner 不一致,都 fail loud。
|
|
65
|
+
*/
|
|
66
|
+
export function composePreStepSources(sources) {
|
|
67
|
+
if (!Array.isArray(sources)) throw new TypeError('pre-step sources must be an array')
|
|
68
|
+
if (sources.length === 0) return { configs: [], ruleActions: [] }
|
|
69
|
+
for (const source of sources) {
|
|
70
|
+
if (!isRecord(source) || typeof source.sourceId !== 'string' || !Array.isArray(source.configs)) {
|
|
71
|
+
throw new TypeError('pre-step sources must be objects created by createPreStepSource')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const configs = sources.flatMap(source => source.configs)
|
|
75
|
+
const ruleActions = sources.flatMap(source => source.ruleActions ?? [])
|
|
76
|
+
const hasWork = configs.length > 0 || ruleActions.length > 0
|
|
77
|
+
const promotion = promotionOwnerOf(sources)
|
|
78
|
+
if (hasWork && promotion === undefined) {
|
|
79
|
+
throw new TypeError('pre-step sources need one promotion owner before batch execution')
|
|
80
|
+
}
|
|
81
|
+
// rule → source sink;只保留首个来源,避免同一条规则被来源重复声明时重复记账。
|
|
82
|
+
const sinks = new Map()
|
|
83
|
+
const collect = (entry, sink) => {
|
|
84
|
+
if (!isRecord(entry) || !isRecord(entry.rule) || typeof sink !== 'function' || sinks.has(entry.rule)) return
|
|
85
|
+
sinks.set(entry.rule, sink)
|
|
86
|
+
}
|
|
87
|
+
for (const source of sources) {
|
|
88
|
+
for (const config of source.configs) collect(config, source.onOutcome)
|
|
89
|
+
for (const action of source.ruleActions ?? []) collect(action, source.onOutcome)
|
|
90
|
+
}
|
|
91
|
+
const report = sinks.size === 0
|
|
92
|
+
? undefined
|
|
93
|
+
: (rule, channel, outcome) => {
|
|
94
|
+
const sink = sinks.get(rule)
|
|
95
|
+
if (sink !== undefined) sink({ ruleId: rule.id, channel, outcome })
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
configs,
|
|
99
|
+
ruleActions,
|
|
100
|
+
...(promotion === undefined ? {} : { promotion }),
|
|
101
|
+
...(report === undefined ? {} : { report }),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* present-as — 声明**本模块覆盖的 Agent** 以哪种形态呈现工具(官方 `ctx.tools.presentAs`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:官方 PTC 呈现由 `@deepseek-ai/dsh-agent-tool-presentation` 行声明,而那是
|
|
5
|
+
* **预设侧**的装配(preset 的常驻 scope)。本插件的模块行挂载在 **Agent scope**
|
|
6
|
+
* (`src/runtime/agent-assembly.ts` 的 `agent.ctx.plugin(...)` → `module.apply(scopeCtx, …)`),
|
|
7
|
+
* 拿到的正是 `presentAs` 要求的 scoped ctx——所以模块可以直接声明呈现,不必在预设里再挂
|
|
8
|
+
* 官方行,也不必动 `cordis.patch.yml`(那条只用于**进程级默认**呈现,见下)。
|
|
9
|
+
*
|
|
10
|
+
* 与官方行的关系:调的是同一个 `presentAs`,语义等价;差别在 scope——本行落在 Agent scope
|
|
11
|
+
* (更近),`modeFor` 沿 scope 链**最近者优先**,所以它与预设里的官方行可以并存。但**同一
|
|
12
|
+
* scope 二次声明会被官方拒绝**(挂载期抛错 `tools.presentAs("x") conflicts with "y" already
|
|
13
|
+
* declared for this scope`)——本行不捕获、不降级,让它响亮失败(装配失败优于静默换形态)。
|
|
14
|
+
*
|
|
15
|
+
* `ptc` 需要 `ctx.ptcRuntime`(宿主 base bundle 的 `ptc-runtime` 行提供):用 `ctx.inject`
|
|
16
|
+
* 等它就绪再声明;等不到本行停在 pending,由预设注册表指名报告**不可用的行**——刻意不静默
|
|
17
|
+
* 退回 native,否则「开了 PTC」的部署会悄悄以 native 跑。`native` 不需要运行时,直接声明。
|
|
18
|
+
*
|
|
19
|
+
* disposer:`presentAs` 本身就是 effect(它用调用方的 ctx 注册、并把那个 disposer 还回来),
|
|
20
|
+
* 本行不再包一层,随 fiber 释放即撤销。
|
|
21
|
+
*
|
|
22
|
+
* 进程级默认呈现不走本行:那要改宿主 `tools` 行的 `config.mode`(部署选择)。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { bool, defineConfig, enumOf } from './fields.mjs'
|
|
26
|
+
import { capabilityAudience } from './agent-capability.mjs'
|
|
27
|
+
|
|
28
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
29
|
+
export const name = 'present-as'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 呈现声明走 `ctx.tools`。
|
|
33
|
+
* `ptcRuntime` **刻意不进 inject**(官方同一取舍):那样会让 `native` 行也被绑死在
|
|
34
|
+
* 「部署里必须有 PTC 运行时」上,而它与运行时毫无关系。
|
|
35
|
+
*/
|
|
36
|
+
export const inject = ['tools']
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 配置契约:字段声明是白名单(未知键 = 打字错误)与逐字段归一化的单一来源
|
|
40
|
+
* (导出供契约对拍测试使用)。`enabled` 未声明 = 关闭,组合源显式写 `enabled: true`。
|
|
41
|
+
*/
|
|
42
|
+
export const configContract = defineConfig({
|
|
43
|
+
enabled: bool({ default: false }),
|
|
44
|
+
mode: enumOf(['native', 'ptc'], { required: true }),
|
|
45
|
+
mainMode: enumOf(['native', 'ptc']),
|
|
46
|
+
subagentMode: enumOf(['native', 'ptc']),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 把本 scope 的工具呈现声明成 `config.mode`。
|
|
51
|
+
*
|
|
52
|
+
* @param ctx - 本模块行的挂载作用域(Agent scope)上下文。
|
|
53
|
+
* @param config - `mode` 为必填兼容默认;主/子覆盖独立、缺省继承 mode。
|
|
54
|
+
* @param options - 配装方持有的 Agent;不从 Context 猜测会话身份。
|
|
55
|
+
*/
|
|
56
|
+
export function apply(ctx, config, { agent } = {}) {
|
|
57
|
+
// 校验入口:未知键 / enabled 非布尔 / mode 缺失或不在枚举内一律挂载期 fail loud。
|
|
58
|
+
const source = configContract.parse(config, name)
|
|
59
|
+
if (!source.enabled) return
|
|
60
|
+
const mode = (capabilityAudience(agent) === 'subagent' ? source.subagentMode : source.mainMode) ?? source.mode
|
|
61
|
+
if (mode === 'native') {
|
|
62
|
+
ctx.tools.presentAs('native')
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
// ptcRuntime 晚到也继续等待;缺失时先告警,避免“ptc 已启用却按 native 跑”没有任何痕迹。
|
|
66
|
+
let warnedPending = false
|
|
67
|
+
if (typeof ctx.get === 'function' && ctx.get('ptcRuntime') === undefined) {
|
|
68
|
+
warnedPending = true
|
|
69
|
+
try { ctx.logger?.warn?.('present-as: ptcRuntime 尚未就绪;本行保持 pending,不会声明 native;若宿主始终不提供,装配停在未呈现状态') } catch { /* logger 不可用不阻断装配 */ }
|
|
70
|
+
}
|
|
71
|
+
ctx.inject(['ptcRuntime'], (runtimeCtx) => {
|
|
72
|
+
if (warnedPending) {
|
|
73
|
+
try { ctx.logger?.info?.('present-as: ptcRuntime 已就绪,声明 ptc') } catch { /* logger 不可用不阻断装配 */ }
|
|
74
|
+
}
|
|
75
|
+
runtimeCtx.tools.presentAs('ptc')
|
|
76
|
+
})
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** `promotion-pool.mjs` 的手写声明:池按配置复用并统一喂事件。 */
|
|
2
|
+
import type { PromotionTracker } from './compaction-epoch.mjs'
|
|
3
|
+
|
|
4
|
+
export interface PromotionPool {
|
|
5
|
+
tracker(promoteEvents: readonly string[], options?: Record<string, unknown>): PromotionTracker
|
|
6
|
+
dispose(): void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createPromotionPool(ctx: unknown): PromotionPool
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 晋升相位追踪器池:同一挂载内按配置复用追踪器,事件由池**统一喂**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要池:`createEpochPromotion` 自己不订阅任何事件(它的 ctx 参数完全用不到,
|
|
5
|
+
* 见 `compaction-epoch.mjs` 的条目容器注释),初始相位靠 `status()` 触发的**全量冷扫**建立。
|
|
6
|
+
* 此前 `executor` 每次 pre-step apply 建两份、`phase` 每个编译谓词建一份、
|
|
7
|
+
* `instruction-hint` 每次 apply 建一份,各自订阅、各自喂——重复建的代价就是重复冷扫。
|
|
8
|
+
*
|
|
9
|
+
* **池自己订阅**:订阅随池的归属走(自建池随所在挂载释放,注入池由创建者释放),
|
|
10
|
+
* 调用方不需要记住「注入时谁来喂事件」——忘了就是相位停在冷扫那一刻的静默失效。
|
|
11
|
+
*
|
|
12
|
+
* 状态仍按 `session.id` 分条(追踪器内部的 `sessionState`):池只合并**实例与订阅**,
|
|
13
|
+
* 不共享会话状态,也不改变「状态是快路径、真相在事件流」这条纪律。
|
|
14
|
+
*/
|
|
15
|
+
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 进缓存的选项键:**任何影响判定的配置都必须登记**,否则同 key 会掩盖不同行为。
|
|
19
|
+
* 与 `createEpochPromotion` 读取的选项一一对应。
|
|
20
|
+
*/
|
|
21
|
+
const KEYED_OPTIONS = [
|
|
22
|
+
'includeSubagents',
|
|
23
|
+
'promoteGate',
|
|
24
|
+
'promoteAfterFirstResponse',
|
|
25
|
+
'reasoningPattern',
|
|
26
|
+
'reasoningNegativePattern',
|
|
27
|
+
'reasoningFlags',
|
|
28
|
+
'maxPromoteSteps',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const trackKey = (promoteEvents, options) => JSON.stringify([
|
|
32
|
+
// 追踪器内部是 `new Set(promoteEvents)`:声明顺序无判定语义,不排序会把同一配置缓存成两份。
|
|
33
|
+
[...promoteEvents].sort(),
|
|
34
|
+
// 固定键序,避免书写顺序造成同配置两份。
|
|
35
|
+
KEYED_OPTIONS.map(key => [key, options[key]]),
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} ctx 归属上下文(`ctx.on` 用来订阅 `session/event`)
|
|
40
|
+
* @returns {{ tracker: (promoteEvents: readonly string[], options?: object) => object, dispose: () => void }}
|
|
41
|
+
*/
|
|
42
|
+
export function createPromotionPool(ctx) {
|
|
43
|
+
const trackers = new Map()
|
|
44
|
+
let disposed = false
|
|
45
|
+
// 喂给池内所有实例:追踪器对未 scan 的会话本身是 no-op(首次 `status()` 会全量重建),
|
|
46
|
+
// 因此统一喂不会把状态喂给还没建立相位的会话。
|
|
47
|
+
const release = ctx.on('session/event', (session, event) => {
|
|
48
|
+
for (const tracker of trackers.values()) tracker.observe(session, event)
|
|
49
|
+
})
|
|
50
|
+
return {
|
|
51
|
+
/** 按配置取追踪器:同配置复用、异配置分建(配置不同就是不同的相位语义)。 */
|
|
52
|
+
tracker(promoteEvents, options = {}) {
|
|
53
|
+
const key = trackKey(promoteEvents, options)
|
|
54
|
+
let tracker = trackers.get(key)
|
|
55
|
+
if (tracker === undefined) {
|
|
56
|
+
tracker = createEpochPromotion(promoteEvents, options)
|
|
57
|
+
trackers.set(key, tracker)
|
|
58
|
+
}
|
|
59
|
+
return tracker
|
|
60
|
+
},
|
|
61
|
+
/** 释放订阅并丢弃实例;追踪器本身没有 disposer(状态随闭包回收)。 */
|
|
62
|
+
dispose() {
|
|
63
|
+
if (disposed) return
|
|
64
|
+
disposed = true
|
|
65
|
+
trackers.clear()
|
|
66
|
+
release?.()
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
package/engine/rule-engine.mjs
CHANGED
|
@@ -35,6 +35,7 @@ export async function apply(ctx, config) {
|
|
|
35
35
|
: isAbsolute(source.moduleRoot) ? pathToFileURL(source.moduleRoot.replace(/[\\/]?$/, '/')) : new URL(source.moduleRoot.replace(/\/?$/, '/'), templateBaseUrl)
|
|
36
36
|
const strategyDir = source.strategyDir === undefined ? undefined : isAbsolute(source.strategyDir) ? pathToFileURL(source.strategyDir).href : new URL(source.strategyDir, templateBaseUrl).href
|
|
37
37
|
const moduleId = typeof packet.moduleId === 'string' ? packet.moduleId : basename(dirname(file))
|
|
38
|
-
const rules = compileRules(packet.rules, { moduleId, variables: packet.variables, variablesEnabled: packet.variablesEnabled, configOrder: packet.configOrder,
|
|
39
|
-
|
|
38
|
+
const rules = compileRules(packet.rules, { moduleId, variables: packet.variables, variablesEnabled: packet.variablesEnabled, configOrder: packet.configOrder, promptConfigOptions: { templateBaseUrl, templateModuleRoot, strategyDir } })
|
|
39
|
+
// 条件在挂载缝绑定(preset 需要挂载 scope 的 ctx);官方模块导出只作可选兜底。
|
|
40
|
+
return mountRuleSources(ctx, [{ moduleId, rules }], { standingMountFor: await loadStandingMountFor() })
|
|
40
41
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `rule-facts.mjs` 的客户端消费面;形状按实现写实。 */
|
|
2
|
+
import type { RuleAudience } from './audience.mjs'
|
|
3
|
+
export type { RuleAudience }
|
|
4
|
+
|
|
5
|
+
export interface RuleFacts {
|
|
6
|
+
/** 可能受众的上界;undefined = 无法静态收窄,调用方按两个受众处理。 */
|
|
7
|
+
audiences: Set<RuleAudience> | undefined
|
|
8
|
+
/** 是否包含 canonical context-activation 的 inject-text 动作。 */
|
|
9
|
+
contextActivation: boolean
|
|
10
|
+
}
|
|
11
|
+
export function ruleFacts(rule: unknown): RuleFacts
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* raw rule 的规则事实(只读):可能受众 + 是否包含 context activation。
|
|
3
|
+
*
|
|
4
|
+
* 只服务 UI/消费者,不参与执行、不替代 `rule-spec` 的编译与 `audienceOf` 的运行时判据。
|
|
5
|
+
* 纯 ESM,无宿主 import;与 `expandActions` 同构地展开 then/else/嵌套分支,但不编译动作。
|
|
6
|
+
*/
|
|
7
|
+
import { audienceOf, intersectAudiences, isAudience } from './audience.mjs'
|
|
8
|
+
import { conjunction, expandActionsLenient } from './branch.mjs'
|
|
9
|
+
import { isFixedRegistration } from './fixed-registration.mjs'
|
|
10
|
+
import { record } from './record.mjs'
|
|
11
|
+
import { CONTEXT_ACTIVATION_STRATEGY } from './strategy-ids.mjs'
|
|
12
|
+
|
|
13
|
+
function targetAudiences(action, ruleLayer) {
|
|
14
|
+
// 文档只允许固定 system-section 的 registration target 用 config.audience 收窄;
|
|
15
|
+
// 与引擎共用 isFixedRegistration(含 config.layer ?? rule.layer 回退)。
|
|
16
|
+
if (action.kind !== 'inject-text') return undefined
|
|
17
|
+
const config = record(action.config) ? action.config : undefined
|
|
18
|
+
if (!isFixedRegistration(config, ruleLayer)) return undefined
|
|
19
|
+
const raw = config.audience
|
|
20
|
+
return isAudience(raw) ? new Set([raw]) : undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasContextActivation(expanded) {
|
|
24
|
+
return expanded.some(({ node }) => node.kind === 'inject-text'
|
|
25
|
+
&& record(node.config)
|
|
26
|
+
&& node.config.strategy === CONTEXT_ACTIVATION_STRATEGY)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 一条 raw rule 的规则事实。
|
|
31
|
+
*
|
|
32
|
+
* `audiences` 是 then/else/嵌套分支动作可能受众的并集;任一动作无法收窄则整体
|
|
33
|
+
* `undefined`(调用方按两个受众处理)。没有动作时回退到规则级 `if`,让正在编辑的
|
|
34
|
+
* 空卡仍按它的受众条件过滤。`contextActivation` 只识别 `inject-text` 且 strategy
|
|
35
|
+
* 为 canonical context-activation 的动作,不执行选择或注入。
|
|
36
|
+
*/
|
|
37
|
+
export function ruleFacts(rule) {
|
|
38
|
+
if (!record(rule)) return { audiences: undefined, contextActivation: false }
|
|
39
|
+
const expanded = [
|
|
40
|
+
...expandActionsLenient(rule.then, { bypass: false }),
|
|
41
|
+
...expandActionsLenient(rule.else, {
|
|
42
|
+
outer: rule.if === undefined ? [] : [{ not: rule.if }],
|
|
43
|
+
bypass: true,
|
|
44
|
+
}),
|
|
45
|
+
]
|
|
46
|
+
const when = audienceOf(rule.if)
|
|
47
|
+
if (expanded.length === 0) return { audiences: when, contextActivation: false }
|
|
48
|
+
let audiences
|
|
49
|
+
for (const { node, conditions, bypass } of expanded) {
|
|
50
|
+
const conditioned = intersectAudiences(bypass ? undefined : when, audienceOf(conjunction(conditions)))
|
|
51
|
+
const effective = intersectAudiences(conditioned, targetAudiences(node, rule.layer))
|
|
52
|
+
if (effective === undefined) {
|
|
53
|
+
audiences = undefined
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
audiences ??= new Set()
|
|
57
|
+
for (const audience of effective) audiences.add(audience)
|
|
58
|
+
}
|
|
59
|
+
return { audiences, contextActivation: hasContextActivation(expanded) }
|
|
60
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** `rule-runtime.mjs` 的手写声明:只声明 `src/` 消费的导出。 */
|
|
2
|
+
import type { StandingMountFor } from './conditions/index.mjs'
|
|
3
|
+
import type { PromotionPool } from './promotion-pool.mjs'
|
|
4
|
+
import type { PreStepPromotion } from './pre-step-source.mjs'
|
|
5
|
+
import type { WarnOnce } from './shared.mjs'
|
|
6
|
+
|
|
7
|
+
/** 变量读写通道(插件侧的模块变量适配器);引擎只按这三个方法调用。 */
|
|
8
|
+
export interface RuleVariableStore {
|
|
9
|
+
read(moduleId: string): unknown
|
|
10
|
+
commit(moduleId: string, request: unknown): unknown
|
|
11
|
+
publish(moduleId: string): void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 判定上报载荷;仅覆盖插件依赖面(宿主按 `RuleOutcomeReport` 消费它)。 */
|
|
15
|
+
export interface RuleOutcomeReport {
|
|
16
|
+
moduleId?: string
|
|
17
|
+
ruleId: string
|
|
18
|
+
channel: string
|
|
19
|
+
outcome: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 一次挂载的规则来源;规则对象是 `rule-spec.mjs#compileRules` 的产物。 */
|
|
23
|
+
export interface RuleSourceLike {
|
|
24
|
+
moduleId: string
|
|
25
|
+
rules: readonly unknown[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MountRuleSourcesOptions {
|
|
29
|
+
agent?: unknown
|
|
30
|
+
plugin?: string
|
|
31
|
+
warnOnce?: WarnOnce
|
|
32
|
+
variableStore?: RuleVariableStore
|
|
33
|
+
/** 官方预设兜底;缺省=没有兜底(预设条件恒不命中)。 */
|
|
34
|
+
standingMountFor?: StandingMountFor
|
|
35
|
+
/** 注入的晋升相位实例对;缺省从协调器或自建池取。 */
|
|
36
|
+
promotion?: PreStepPromotion
|
|
37
|
+
promotionPool?: PromotionPool
|
|
38
|
+
onOutcome?(report: RuleOutcomeReport): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 所有规则来源的唯一装配入口;返回释放本次挂载的 disposer。 */
|
|
42
|
+
export function mountRuleSources(
|
|
43
|
+
ctx: unknown,
|
|
44
|
+
sources: ReadonlyArray<RuleSourceLike>,
|
|
45
|
+
options?: MountRuleSourcesOptions,
|
|
46
|
+
): () => void
|