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
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* declared-triggers — 声明式触发器的运行时(B7)。
|
|
3
|
-
*
|
|
4
|
-
* 链路:`preset.yml` 顶层 `triggers` 段 →(`writePreset` 保存期校验 + 物化)→ 预设根
|
|
5
|
-
* `triggers.yml` → 本模块读入、编译、注册。
|
|
6
|
-
*
|
|
7
|
-
* ## 声明由预设提供,引擎不带默认(2026-09-22 拍板)
|
|
8
|
-
*
|
|
9
|
-
* 所以 `triggers.yml` 缺失 = **没有声明**,不是错误:不注册任何触发器,也不让预设启动失败
|
|
10
|
-
* ——与 `subagent-tool-policy` 的 ENOENT 降级同一纪律。文件在但内容非法才是真错误:
|
|
11
|
-
* fail loud(且保存期就会先被 `engine/trigger-spec.mjs` 拒一次,这里是第二道)。
|
|
12
|
-
*
|
|
13
|
-
* ## 为什么 `apply` 是 async
|
|
14
|
-
*
|
|
15
|
-
* `preset` 原语要在**挂载期一次**解析官方模块导出(判定期零 IO,见 `predicates.mjs` 的
|
|
16
|
-
* 三条硬约束),而那是动态 import。async `apply` 有官方先例(`dsh-api-remotes`、
|
|
17
|
-
* `dsh-client-connection`),本仓库此前没有——这是第一处。
|
|
18
|
-
*/
|
|
19
|
-
import { readFileSync } from 'node:fs'
|
|
20
|
-
import { isAbsolute } from 'node:path'
|
|
21
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
22
|
-
import { parse as parseYaml } from './vendor/yaml/index.js'
|
|
23
|
-
import { defineConfig, passthrough } from './fields.mjs'
|
|
24
|
-
import { createWarnOnce, keepDisposer } from './shared.mjs'
|
|
25
|
-
import { compileDeclarations, mountDeclarations } from './trigger-spec.mjs'
|
|
26
|
-
import { loadStandingMountFor } from './predicates.mjs'
|
|
27
|
-
|
|
28
|
-
/** Cordis plugin name used by loader diagnostics. */
|
|
29
|
-
export const name = 'declared-triggers'
|
|
30
|
-
|
|
31
|
-
/** 只经 `ctx.on`(动作注册)与 agent scope 的 tools 工作,无宿主服务依赖。 */
|
|
32
|
-
export const inject = []
|
|
33
|
-
|
|
34
|
-
export const configContract = defineConfig({
|
|
35
|
-
triggersFile: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : '../triggers.yml')),
|
|
36
|
-
strategyDir: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : undefined)),
|
|
37
|
-
presetRoot: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : undefined)),
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 插件装配由注册层传入声明文件的绝对 file URL;引擎实现始终来自已安装插件包。
|
|
42
|
-
* 独立使用时才按本模块 URL 解析相对值,缺省路径不代表任何用户预设目录。
|
|
43
|
-
*
|
|
44
|
-
* 绝对路径必须走 `isAbsolute` 分支:`new URL(绝对路径, base)` 只在 POSIX 形态下忽略 base,
|
|
45
|
-
* Windows 盘符会被当成 URL scheme 并抛 `ERR_INVALID_URL_SCHEME`。而「测试/嵌入可显式
|
|
46
|
-
* 指定绝对路径」是本模块的契约(`loadStandingMountFor(entry)` 有同一约定)。
|
|
47
|
-
*/
|
|
48
|
-
function resolveTriggersFile(config) {
|
|
49
|
-
const raw = typeof config?.triggersFile === 'string' && config.triggersFile.length > 0
|
|
50
|
-
? config.triggersFile
|
|
51
|
-
: '../triggers.yml'
|
|
52
|
-
if (isAbsolute(raw)) return raw
|
|
53
|
-
return fileURLToPath(new URL(raw, import.meta.url))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 读声明;文件缺失返回 `undefined`(= 没有声明,交由调用方静默返回)。 */
|
|
57
|
-
function readTriggerSpecs(file) {
|
|
58
|
-
let raw
|
|
59
|
-
try {
|
|
60
|
-
raw = readFileSync(file, 'utf8')
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (error?.code === 'ENOENT') return undefined
|
|
63
|
-
throw error
|
|
64
|
-
}
|
|
65
|
-
const parsed = parseYaml(raw, { logLevel: 'silent' })
|
|
66
|
-
if (!Array.isArray(parsed)) {
|
|
67
|
-
throw new TypeError(`${name}: triggers.yml must be a YAML array of trigger declarations`)
|
|
68
|
-
}
|
|
69
|
-
return parsed
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function apply(ctx, config) {
|
|
73
|
-
const source = configContract.parse(config, name)
|
|
74
|
-
const file = resolveTriggersFile(source)
|
|
75
|
-
const specs = readTriggerSpecs(file)
|
|
76
|
-
if (specs === undefined || specs.length === 0) return
|
|
77
|
-
// 数据路径相对实际声明文件;允许根由注册层提供,不从包内引擎位置猜测用户目录。
|
|
78
|
-
const templateBaseUrl = pathToFileURL(file)
|
|
79
|
-
const templatePresetRoot = source.presetRoot === undefined
|
|
80
|
-
? new URL('./', templateBaseUrl)
|
|
81
|
-
: isAbsolute(source.presetRoot) ? pathToFileURL(source.presetRoot.replace(/[\\/]?$/, '/'))
|
|
82
|
-
: new URL(source.presetRoot.replace(/\/?$/, '/'), templateBaseUrl)
|
|
83
|
-
const strategyDir = source.strategyDir === undefined ? undefined : (isAbsolute(source.strategyDir)
|
|
84
|
-
? pathToFileURL(source.strategyDir).href
|
|
85
|
-
: new URL(source.strategyDir, templateBaseUrl).href)
|
|
86
|
-
const promptConfigOptions = { templateBaseUrl, templatePresetRoot, strategyDir }
|
|
87
|
-
// `preset` 原语需要官方模块导出(判定期零 IO,故在这里一次解析);其余原语用不到它。
|
|
88
|
-
const standingMountFor = await loadStandingMountFor()
|
|
89
|
-
// 声明非法在这里 fail loud——保存期已校验过一次,这里再炸说明文件被绕过保存手改了。
|
|
90
|
-
const compiled = compileDeclarations(specs, { ctx, standingMountFor, promptConfigOptions })
|
|
91
|
-
const dispose = mountDeclarations(ctx, compiled, { plugin: name, warnOnce: createWarnOnce(ctx, name) })
|
|
92
|
-
keepDisposer(ctx, dispose, `${name}: declarations`)
|
|
93
|
-
}
|
package/engine/session-vars.mjs
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* session-vars — 会话变量(ST setvar/getvar 运行时语义)。
|
|
3
|
-
*
|
|
4
|
-
* 变量挂在 session 对象上(SESSION_VARS_KEY 属性),因此 .engine 引擎实例与
|
|
5
|
-
* 插件进程工具实例(不同模块副本)操作同一 session 即共享同一份数据——
|
|
6
|
-
* 无跨实例状态同步问题。WeakMap 不需要:session 释放后属性随对象回收。
|
|
7
|
-
*
|
|
8
|
-
* 插值优先级:resolved 运行时 > 会话变量 > 配置 params > 配置 variables(含预设)。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** session 对象上的变量属性键(字符串常量,跨模块实例一致)。 */
|
|
12
|
-
export const SESSION_VARS_KEY = '__pt_session_vars__'
|
|
13
|
-
|
|
14
|
-
function varsOf(session, create = false) {
|
|
15
|
-
if (session === null || typeof session !== 'object') return undefined
|
|
16
|
-
let vars = Object.hasOwn(session, SESSION_VARS_KEY) ? session[SESSION_VARS_KEY] : undefined
|
|
17
|
-
if (vars === undefined) {
|
|
18
|
-
if (!create) return undefined
|
|
19
|
-
vars = Object.create(null)
|
|
20
|
-
session[SESSION_VARS_KEY] = vars
|
|
21
|
-
}
|
|
22
|
-
return vars
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 会话变量快照(无会话/未设置 → 空对象)。 */
|
|
26
|
-
export function sessionVarsSnapshot(session) {
|
|
27
|
-
const vars = varsOf(session)
|
|
28
|
-
return vars === undefined ? {} : { ...vars }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** 读取单个会话变量(未设置 → undefined)。 */
|
|
32
|
-
export function getSessionVar(session, key) {
|
|
33
|
-
const vars = varsOf(session)
|
|
34
|
-
return vars === undefined || !Object.hasOwn(vars, String(key)) ? undefined : vars[String(key)]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 设置会话变量(值转字符串;空值仍记录)。 */
|
|
38
|
-
export function setSessionVar(session, key, value) {
|
|
39
|
-
const vars = varsOf(session, true)
|
|
40
|
-
if (vars === undefined) return
|
|
41
|
-
// defineProperty 同时保护仍在运行的旧实例创建的普通对象(__proto__ 不能触发 setter)。
|
|
42
|
-
Object.defineProperty(vars, String(key), { value: String(value ?? ''), writable: true, enumerable: true, configurable: true })
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 清除会话变量;key 缺省时清空全部。 */
|
|
46
|
-
export function clearSessionVars(session, key) {
|
|
47
|
-
const vars = varsOf(session)
|
|
48
|
-
if (vars === undefined) return
|
|
49
|
-
if (key === undefined || key === '') {
|
|
50
|
-
for (const name of Object.keys(vars)) delete vars[name]
|
|
51
|
-
} else {
|
|
52
|
-
delete vars[String(key)]
|
|
53
|
-
}
|
|
54
|
-
}
|
package/engine/st-macros.d.mts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/** 数字运算保留 number;JSON 数组以字符串保存,与 ST 变量存储一致。 */
|
|
2
|
-
export type StVariableTable = Record<string, string | number>
|
|
3
|
-
|
|
4
|
-
export interface StTextOptions {
|
|
5
|
-
variables?: Readonly<Record<string, unknown>>
|
|
6
|
-
local?: StVariableTable
|
|
7
|
-
global?: StVariableTable
|
|
8
|
-
/** 原样传给 interpolateVariables,仅供读取会话事实。 */
|
|
9
|
-
session?: unknown
|
|
10
|
-
sourceId?: string
|
|
11
|
-
warn?: (message: string) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** 清理注释/trim/ERA、归一宏写法、替换已知角色与默认用户;不求值变量。 */
|
|
15
|
-
export function prepareStText(text: string, cardName?: string): string
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 顺序求值,仅修改显式 local/global;getvar 的非空 fallback 初始化所属表。
|
|
19
|
-
* 未知普通引用保留;危险键拒绝并 warn;循环、超过 32 层或 1 MiB 抛 RangeError。
|
|
20
|
-
* 另设总展开工作量上限,防止零输出的重复展开耗尽资源。
|
|
21
|
-
*/
|
|
22
|
-
export function renderStText(text: string, options?: StTextOptions): string
|
package/engine/st-macros.mjs
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/** ST 文本预处理与顺序求值;状态只归调用方传入的 local/global 所有。 */
|
|
2
|
-
import { Buffer } from 'node:buffer'
|
|
3
|
-
import { createHash } from 'node:crypto'
|
|
4
|
-
import { interpolateVariables, normalizeMacroSyntax } from './interpolate.mjs'
|
|
5
|
-
|
|
6
|
-
const MAX_BYTES = 1024 * 1024
|
|
7
|
-
const MAX_DEPTH = 32
|
|
8
|
-
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
|
|
9
|
-
const DISCARDED_MACRO = /^\s*(?:\/\/|(?:trim|ERA)(?=$|[:\s]))/i
|
|
10
|
-
|
|
11
|
-
function byteLength(text) {
|
|
12
|
-
const size = Buffer.byteLength(text, 'utf8')
|
|
13
|
-
if (size > MAX_BYTES) throw new RangeError('ST macro text exceeds 1 MiB')
|
|
14
|
-
return size
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** 双花括号是宏边界;JSON 单花括号及字符串内的右括号不是。 */
|
|
18
|
-
function macroEnd(text, start, depth = 0) {
|
|
19
|
-
const frames = [{ braces: 0, brackets: 0, quote: false, escape: false }]
|
|
20
|
-
if (depth >= MAX_DEPTH) throw new RangeError('ST macro depth exceeds 32')
|
|
21
|
-
for (let index = start + 2; index < text.length; index++) {
|
|
22
|
-
const frame = frames.at(-1)
|
|
23
|
-
const char = text[index]
|
|
24
|
-
if (frame.escape) {
|
|
25
|
-
frame.escape = false
|
|
26
|
-
continue
|
|
27
|
-
}
|
|
28
|
-
if (frame.quote && char === '\\') {
|
|
29
|
-
frame.escape = true
|
|
30
|
-
continue
|
|
31
|
-
}
|
|
32
|
-
if (char === '"' && (frame.quote || frame.braces > 0 || frame.brackets > 0)) {
|
|
33
|
-
frame.quote = !frame.quote
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
36
|
-
if (text.startsWith('{{', index)) {
|
|
37
|
-
if (depth + frames.length >= MAX_DEPTH) throw new RangeError('ST macro depth exceeds 32')
|
|
38
|
-
frames.push({ braces: 0, brackets: 0, quote: false, escape: false })
|
|
39
|
-
index++
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
if (frame.quote) continue
|
|
43
|
-
if (char === '{') frame.braces++
|
|
44
|
-
else if (char === '}' && frame.braces > 0) frame.braces--
|
|
45
|
-
else if (text.startsWith('}}', index)) {
|
|
46
|
-
frames.pop()
|
|
47
|
-
if (frames.length === 0) return index + 2
|
|
48
|
-
index++
|
|
49
|
-
} else if (char === '[') frame.brackets++
|
|
50
|
-
else if (char === ']') frame.brackets = Math.max(0, frame.brackets - 1)
|
|
51
|
-
}
|
|
52
|
-
return -1
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 先找到完整边界再求值,未闭合宏的内部指令不会被意外执行。 */
|
|
56
|
-
function transformText(text, macro) {
|
|
57
|
-
let work = 0
|
|
58
|
-
function walk(source, depth = 0, path = '') {
|
|
59
|
-
work += byteLength(source)
|
|
60
|
-
// 零输出的重复变量展开也必须有界,不能只限制最终输出。
|
|
61
|
-
if (work > MAX_BYTES * MAX_DEPTH) throw new RangeError('ST macro expansion work limit exceeded')
|
|
62
|
-
const chunks = []
|
|
63
|
-
let size = 0
|
|
64
|
-
const append = (value) => {
|
|
65
|
-
size += byteLength(value)
|
|
66
|
-
if (size > MAX_BYTES) throw new RangeError('ST macro output exceeds 1 MiB')
|
|
67
|
-
chunks.push(value)
|
|
68
|
-
}
|
|
69
|
-
let cursor = 0
|
|
70
|
-
while (cursor < source.length) {
|
|
71
|
-
const start = source.indexOf('{{', cursor)
|
|
72
|
-
if (start < 0) {
|
|
73
|
-
append(source.slice(cursor))
|
|
74
|
-
break
|
|
75
|
-
}
|
|
76
|
-
append(source.slice(cursor, start))
|
|
77
|
-
const end = macroEnd(source, start, depth)
|
|
78
|
-
if (end < 0) {
|
|
79
|
-
if (!DISCARDED_MACRO.test(source.slice(start + 2))) append(source.slice(start))
|
|
80
|
-
break
|
|
81
|
-
}
|
|
82
|
-
append(macro(source.slice(start + 2, end - 2), (value) => walk(value, depth + 1, `${path}/${start}`), `${path}/${start}`))
|
|
83
|
-
cursor = end
|
|
84
|
-
}
|
|
85
|
-
return chunks.join('')
|
|
86
|
-
}
|
|
87
|
-
return walk(text)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** 只分割当前层的第一个 ::,嵌套宏中的分隔符属于嵌套宏。 */
|
|
91
|
-
function splitArgument(text) {
|
|
92
|
-
for (let index = 0; index < text.length; index++) {
|
|
93
|
-
if (text.startsWith('{{', index)) {
|
|
94
|
-
const end = macroEnd(text, index)
|
|
95
|
-
if (end < 0) break
|
|
96
|
-
index = end - 1
|
|
97
|
-
} else if (text.startsWith('::', index)) {
|
|
98
|
-
return [text.slice(0, index), text.slice(index + 2)]
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return [text, undefined]
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** 对齐 ST getVariable 的数值转换;空白文本仍是文本。 */
|
|
105
|
-
function stValue(value) {
|
|
106
|
-
return (typeof value === 'string' && value.trim() === '') || Number.isNaN(Number(value))
|
|
107
|
-
? (value || '')
|
|
108
|
-
: Number(value)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function addValue(value, increment) {
|
|
112
|
-
const current = stValue(value) || 0
|
|
113
|
-
byteLength(String(current))
|
|
114
|
-
let parsed
|
|
115
|
-
try { parsed = JSON.parse(current) } catch { /* 非 JSON 值继续按数字或文本处理。 */ }
|
|
116
|
-
if (Array.isArray(parsed)) {
|
|
117
|
-
parsed.push(increment)
|
|
118
|
-
return JSON.stringify(parsed)
|
|
119
|
-
}
|
|
120
|
-
const amount = Number(increment)
|
|
121
|
-
if (Number.isNaN(amount) || Number.isNaN(Number(current))) return String(current || '') + increment
|
|
122
|
-
const result = Number(current) + amount
|
|
123
|
-
return Number.isNaN(result) ? undefined : result
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** 不求值变量/动态宏,不执行 ERA 内容;未知角色名留给运行时。 */
|
|
127
|
-
export function prepareStText(text, cardName = '') {
|
|
128
|
-
const name = cardName.trim()
|
|
129
|
-
return transformText(text, (body, expand) => {
|
|
130
|
-
if (DISCARDED_MACRO.test(body)) return ''
|
|
131
|
-
if (/^char$/i.test(body) && name) return name
|
|
132
|
-
if (/^user$/i.test(body)) return '用户'
|
|
133
|
-
return normalizeMacroSyntax(`{{${expand(body)}}}`)
|
|
134
|
-
}).replace(/(?:\r?\n){3,}/g, '\n\n').trim()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** 按出现顺序求值;variables/session 只读,不持久化或缓存任何状态。 */
|
|
138
|
-
export function renderStText(text, { variables = {}, local = {}, global = {}, session, sourceId = '', warn = () => {} } = {}) {
|
|
139
|
-
const sourceKey = createHash('sha256').update(sourceId).update(text).digest('hex')
|
|
140
|
-
const tables = { local, global, variables }
|
|
141
|
-
const resolving = new Set()
|
|
142
|
-
const allowed = (key) => {
|
|
143
|
-
if (key && !FORBIDDEN_KEYS.has(key)) return true
|
|
144
|
-
warn(`ST variable key blocked: ${key}`)
|
|
145
|
-
return false
|
|
146
|
-
}
|
|
147
|
-
const write = (table, key, value) => {
|
|
148
|
-
if (value === undefined) return
|
|
149
|
-
byteLength(String(value))
|
|
150
|
-
// 禁止危险键,并显式定义 own 数据属性,避免触发继承的 setter。
|
|
151
|
-
Object.defineProperty(table, key, { value, writable: true, enumerable: true, configurable: true })
|
|
152
|
-
}
|
|
153
|
-
const read = (scope, key, expand, numeric = false) => {
|
|
154
|
-
const id = `${scope}:${key}`
|
|
155
|
-
if (resolving.has(id)) throw new RangeError(`ST variable cycle: ${id}`)
|
|
156
|
-
resolving.add(id)
|
|
157
|
-
try {
|
|
158
|
-
const value = tables[scope][key]
|
|
159
|
-
return expand(String((numeric ? stValue(value) : value) ?? ''))
|
|
160
|
-
} finally {
|
|
161
|
-
resolving.delete(id)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return transformText(text, (body, expand, path) => {
|
|
166
|
-
if (DISCARDED_MACRO.test(body)) return ''
|
|
167
|
-
const [name, args] = splitArgument(body)
|
|
168
|
-
const operation = /^(set|add|get|inc|dec)(global)?var$/i.exec(name.trim())
|
|
169
|
-
if (operation && args !== undefined) {
|
|
170
|
-
const [rawKey, rawValue] = splitArgument(args)
|
|
171
|
-
const key = expand(rawKey).trim()
|
|
172
|
-
if (!allowed(key)) return ''
|
|
173
|
-
const action = operation[1].toLowerCase()
|
|
174
|
-
const scope = operation[2] ? 'global' : 'local'
|
|
175
|
-
const table = tables[scope]
|
|
176
|
-
const exists = Object.hasOwn(table, key)
|
|
177
|
-
if (action === 'get') {
|
|
178
|
-
if (exists) return read(scope, key, expand, true)
|
|
179
|
-
if (scope === 'local' && Object.hasOwn(variables, key)) return read('variables', key, expand, true)
|
|
180
|
-
if (rawValue === undefined) return ''
|
|
181
|
-
const fallback = expand(rawValue)
|
|
182
|
-
if (fallback !== '') write(table, key, fallback)
|
|
183
|
-
return fallback
|
|
184
|
-
}
|
|
185
|
-
if ((action === 'set' || action === 'add') && rawValue !== undefined) {
|
|
186
|
-
const value = expand(rawValue)
|
|
187
|
-
write(table, key, action === 'set' ? value : addValue(exists ? table[key] : '', value))
|
|
188
|
-
return ''
|
|
189
|
-
}
|
|
190
|
-
if ((action === 'inc' || action === 'dec') && rawValue === undefined) {
|
|
191
|
-
const value = addValue(exists ? table[key] : '', action === 'inc' ? 1 : -1)
|
|
192
|
-
write(table, key, value)
|
|
193
|
-
return String(value ?? '')
|
|
194
|
-
}
|
|
195
|
-
return `{{${body}}}`
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const whole = normalizeMacroSyntax(`{{${expand(body)}}}`)
|
|
199
|
-
const [key] = splitArgument(whole.slice(2, -2))
|
|
200
|
-
if (!allowed(key)) return whole
|
|
201
|
-
if (Object.hasOwn(local, key)) return read('local', key, expand)
|
|
202
|
-
if (Object.hasOwn(variables, key)) return read('variables', key, expand)
|
|
203
|
-
if (/^user$/i.test(key)) return '用户'
|
|
204
|
-
// 未解析的嵌套引用交给官方出口清洗,不让扁平插值器截断它。
|
|
205
|
-
if (whole.slice(2, -2).includes('{{')) return whole
|
|
206
|
-
return interpolateVariables(whole, {}, session, undefined, `${sourceKey}:${path}`)
|
|
207
|
-
})
|
|
208
|
-
}
|
package/engine/st-render.mjs
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/** ST 模板变量帧:只求值显式导入的模板,不决定任何官方插入点的执行顺序。 */
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { renderStText } from './st-macros.mjs'
|
|
4
|
-
import { sessionVarsSnapshot } from './session-vars.mjs'
|
|
5
|
-
import { sessionEvents, isDelegated, matchesModel } from './shared.mjs'
|
|
6
|
-
import { stripUnresolvedRefs } from './interpolate.mjs'
|
|
7
|
-
import { isSuccessfulCompactionEnd } from './compaction-epoch.mjs'
|
|
8
|
-
import { compareConfigSequence } from './order.mjs'
|
|
9
|
-
|
|
10
|
-
function visible(config, agent) {
|
|
11
|
-
if (config.enabled === false) return false
|
|
12
|
-
const delegated = isDelegated(agent?.session)
|
|
13
|
-
if (config.audience === 'main' && delegated) return false
|
|
14
|
-
if (config.audience === 'subagent' && !delegated) return false
|
|
15
|
-
return matchesModel(config.modelScope, agent?.options?.model)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 历史与尚未入库的真实输入使用同一消息身份;插件注入消息不触发第二次赋值。 */
|
|
19
|
-
function generationKey(session, pending) {
|
|
20
|
-
const events = sessionEvents(session)
|
|
21
|
-
// 宿主先 assemble/pre-step,之后才 append step/start;不能用 step/start 切帧。
|
|
22
|
-
const boundary = events.findLast(event => event?.type === 'turn/start' || event?.type === 'step/end' || isSuccessfulCompactionEnd(event))
|
|
23
|
-
if (boundary) return JSON.stringify([boundary.type, boundary.seq, boundary.data?.turn, boundary.data?.step])
|
|
24
|
-
const seen = new Set()
|
|
25
|
-
const hash = createHash('sha256')
|
|
26
|
-
const add = (message, kind) => {
|
|
27
|
-
if (!message || message.source?.plugin || (message.source?.kind && message.source.kind !== 'user')) return
|
|
28
|
-
const value = JSON.stringify([kind, message.id ?? null, message.content ?? []])
|
|
29
|
-
if (seen.has(value)) return
|
|
30
|
-
seen.add(value)
|
|
31
|
-
hash.update(value)
|
|
32
|
-
}
|
|
33
|
-
for (const event of events) {
|
|
34
|
-
if (event?.type === 'user/message' || event?.type === 'assistant/message') {
|
|
35
|
-
add(event.data?.message ?? event.data, event.type)
|
|
36
|
-
} else if (event?.type === 'tool/call') hash.update(JSON.stringify([event.type, event.seq, event.data?.id]))
|
|
37
|
-
}
|
|
38
|
-
for (const message of pending) add(message, `${message.role ?? 'user'}/message`)
|
|
39
|
-
return hash.digest('hex')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function attachStRenderers(configs) {
|
|
43
|
-
const claimed = new Set()
|
|
44
|
-
const templates = configs.filter(config => {
|
|
45
|
-
if (config.enabled === false) return false
|
|
46
|
-
if (config.group !== undefined && config.exclusive === true) {
|
|
47
|
-
if (claimed.has(config.group)) return false
|
|
48
|
-
claimed.add(config.group)
|
|
49
|
-
}
|
|
50
|
-
return config.params?.stMacros === true
|
|
51
|
-
})
|
|
52
|
-
if (!templates.length) return configs
|
|
53
|
-
// 状态只活在本次 mount 与会话对象的生命周期内;不持有会话 id 的无界全局 Map。
|
|
54
|
-
const sessions = new WeakMap()
|
|
55
|
-
const tokens = new WeakMap()
|
|
56
|
-
for (const target of templates) {
|
|
57
|
-
target.renderSt = (agent, messages = [], warn = () => {}, token, eligible) => {
|
|
58
|
-
// 官方 assembly 只拥有两个文本插入点;pre-step 的资格由执行器确认。
|
|
59
|
-
const approved = config => visible(config, agent) && (eligible === undefined
|
|
60
|
-
? (config.layer === 'system-section' || config.layer === 'runtime-context') && config.promotion === 'none'
|
|
61
|
-
: eligible.has(config.renderSt))
|
|
62
|
-
if (!approved(target)) return ''
|
|
63
|
-
const session = agent?.session
|
|
64
|
-
const cached = token && tokens.get(token)
|
|
65
|
-
if (cached) {
|
|
66
|
-
if (!cached.text.has(target)) cached.evaluate(target)
|
|
67
|
-
return cached.text.get(target) ?? ''
|
|
68
|
-
}
|
|
69
|
-
const manual = sessionVarsSnapshot(session)
|
|
70
|
-
const generation = generationKey(session, messages)
|
|
71
|
-
const key = `${generation}:${JSON.stringify(manual)}`
|
|
72
|
-
const previous = session && sessions.get(session)
|
|
73
|
-
let frame = previous
|
|
74
|
-
if (!frame || frame.key !== key) {
|
|
75
|
-
const initialLocal = { ...(previous?.generation === generation ? previous.initialLocal : previous?.local), ...manual }
|
|
76
|
-
for (const key of Object.keys(previous?.manual ?? {})) if (!Object.hasOwn(manual, key)) delete initialLocal[key]
|
|
77
|
-
const initialGlobal = { ...(previous?.generation === generation ? previous.initialGlobal : previous?.global) }
|
|
78
|
-
frame = { key, generation, manual, initialLocal, initialGlobal, local: { ...initialLocal }, global: { ...initialGlobal }, text: new Map() }
|
|
79
|
-
const evaluate = config => {
|
|
80
|
-
try {
|
|
81
|
-
const local = { ...frame.local }, global = { ...frame.global }
|
|
82
|
-
const text = renderStText(config.texts.join('\n\n'), { variables: config.variables, local, global, session, sourceId: config.id, warn })
|
|
83
|
-
const cleaned = stripUnresolvedRefs(text)
|
|
84
|
-
if (cleaned.stripped.length) warn(`prompt-config-engine: ST 模板 ${config.id} 有 ${cleaned.stripped.length} 处未支持引用,已移除`)
|
|
85
|
-
frame.local = local
|
|
86
|
-
frame.global = global
|
|
87
|
-
frame.text.set(config, cleaned.text)
|
|
88
|
-
} catch (error) {
|
|
89
|
-
frame.text.set(config, '')
|
|
90
|
-
warn(`prompt-config-engine: ST 模板 ${config.id} 求值失败:${String(error?.message ?? error)}`)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
frame.evaluate = evaluate
|
|
94
|
-
if (session) sessions.set(session, frame)
|
|
95
|
-
}
|
|
96
|
-
// 模块 pre-step 已跨来源按序调度,不能预跑本模块后面的卡而越过另一模块。
|
|
97
|
-
// 其它入口沿用原预求值语义;同帧已求值模板不重放副作用或随机宏。
|
|
98
|
-
if (target.sequence === undefined || eligible === undefined) {
|
|
99
|
-
for (const config of templates.filter(config => approved(config)
|
|
100
|
-
&& config.strategy === 'static' && config.dedupe === 'none' && !frame.text.has(config))
|
|
101
|
-
.sort(compareConfigSequence)) frame.evaluate(config)
|
|
102
|
-
}
|
|
103
|
-
if (token && typeof token === 'object') tokens.set(token, frame)
|
|
104
|
-
if (!frame.text.has(target)) frame.evaluate(target)
|
|
105
|
-
return frame.text.get(target) ?? ''
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return configs
|
|
109
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** 引擎侧 TS 类型声明(运行期实现在 st-world-book.mjs;仅 src/ 侧导入需要)。 */
|
|
2
|
-
export interface WorldBookDiagnosticsSnapshot {
|
|
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 lastWorldBookDiagnostics(session: unknown): WorldBookDiagnosticsSnapshot
|
|
10
|
-
export function selectStWorldBook(...args: unknown[]): Set<Record<string, unknown>> & { diagnostics?: { records: unknown[]; truncated: boolean } }
|