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.
Files changed (206) hide show
  1. package/README.md +100 -233
  2. package/engine/actions/append-context.mjs +3 -24
  3. package/engine/actions/assembly.mjs +70 -94
  4. package/engine/actions/catalog.mjs +65 -28
  5. package/engine/actions/content.mjs +8 -152
  6. package/engine/actions/examples.mjs +8 -4
  7. package/engine/actions/inbox-prepend.mjs +4 -5
  8. package/engine/actions/index.mjs +17 -9
  9. package/engine/actions/inject-text.mjs +24 -4
  10. package/engine/actions/pre-step-filter.mjs +3 -4
  11. package/engine/actions/registration.mjs +20 -28
  12. package/engine/actions/request-params.mjs +21 -1
  13. package/engine/actions/shared.mjs +22 -13
  14. package/engine/actions/stream-output.mjs +26 -0
  15. package/engine/actions/tool-gate.mjs +31 -0
  16. package/engine/actions/tool-result.mjs +36 -0
  17. package/engine/actions/tool-visibility.mjs +248 -0
  18. package/engine/agent-capability.mjs +67 -0
  19. package/engine/anchor-match.mjs +14 -21
  20. package/engine/audience.d.mts +2 -0
  21. package/engine/audience.mjs +102 -0
  22. package/engine/branch.d.mts +4 -0
  23. package/engine/branch.mjs +61 -18
  24. package/engine/compaction-epoch.d.mts +25 -0
  25. package/engine/compaction-epoch.mjs +27 -4
  26. package/engine/compositions/source/local/filesystem-editor.yml +3 -3
  27. package/engine/compositions/source/local/instruction-hint.yml +2 -4
  28. package/engine/compositions/source/local/module-file-tools.yml +2 -0
  29. package/engine/compositions/source/local/module-import-tools.yml +4 -0
  30. package/engine/compositions/source/local/module-manifest-tools.yml +2 -0
  31. package/engine/compositions/source/local/module-rule-tools.yml +5 -0
  32. package/engine/compositions/source/local/module-variable-tools.yml +5 -0
  33. package/engine/compositions/source/local/present-as.yml +26 -0
  34. package/engine/compositions/source/local/tool-config-engine.yml +2 -3
  35. package/engine/compositions/source/local/tool-search.yml +11 -0
  36. package/engine/condition-facts.mjs +84 -0
  37. package/engine/conditions/anchor.mjs +63 -23
  38. package/engine/conditions/availability.mjs +1 -1
  39. package/engine/conditions/composite.mjs +3 -3
  40. package/engine/conditions/count.mjs +42 -11
  41. package/engine/conditions/evaluation.mjs +74 -19
  42. package/engine/conditions/examples.mjs +2 -1
  43. package/engine/conditions/index.d.mts +15 -0
  44. package/engine/conditions/index.mjs +23 -9
  45. package/engine/conditions/operators.mjs +4 -0
  46. package/engine/conditions/phase.mjs +9 -3
  47. package/engine/conditions/preset.mjs +1 -1
  48. package/engine/conditions/session.mjs +8 -2
  49. package/engine/conditions/subject.mjs +144 -65
  50. package/engine/conditions/text.mjs +26 -8
  51. package/engine/conditions/variable.mjs +83 -0
  52. package/engine/context-activation.d.mts +11 -0
  53. package/engine/{st-world-book.mjs → context-activation.mjs} +104 -64
  54. package/engine/delivery.d.mts +2 -0
  55. package/engine/delivery.mjs +93 -0
  56. package/engine/discovery.d.mts +38 -0
  57. package/engine/discovery.mjs +146 -0
  58. package/engine/env-safety.mjs +14 -0
  59. package/engine/executor.d.mts +28 -0
  60. package/engine/executor.mjs +290 -205
  61. package/engine/fillers.mjs +39 -8
  62. package/engine/fixed-registration.mjs +12 -0
  63. package/engine/history.mjs +71 -0
  64. package/engine/instruction-blocks.d.mts +13 -0
  65. package/engine/instruction-blocks.mjs +21 -0
  66. package/engine/instruction-files.d.mts +5 -0
  67. package/engine/instruction-files.mjs +97 -0
  68. package/engine/instruction-hint.mjs +69 -103
  69. package/engine/interpolate.d.mts +7 -0
  70. package/engine/interpolate.mjs +44 -13
  71. package/engine/layers.mjs +163 -227
  72. package/engine/machine-paths.mjs +40 -0
  73. package/engine/module-file-tools.mjs +8 -0
  74. package/engine/module-import-tools.mjs +10 -0
  75. package/engine/module-manifest-tools.mjs +8 -0
  76. package/engine/module-rule-tools.mjs +10 -0
  77. package/engine/module-variable-tools.mjs +10 -0
  78. package/engine/order.d.mts +14 -0
  79. package/engine/order.mjs +0 -3
  80. package/engine/path-safety.d.mts +2 -0
  81. package/engine/path-safety.mjs +58 -0
  82. package/engine/pre-step-source.d.mts +69 -0
  83. package/engine/pre-step-source.mjs +103 -0
  84. package/engine/present-as.mjs +77 -0
  85. package/engine/promotion-pool.d.mts +9 -0
  86. package/engine/promotion-pool.mjs +69 -0
  87. package/engine/record.mjs +2 -0
  88. package/engine/rule-engine.mjs +3 -2
  89. package/engine/rule-facts.d.mts +11 -0
  90. package/engine/rule-facts.mjs +60 -0
  91. package/engine/rule-runtime.d.mts +46 -0
  92. package/engine/rule-runtime.mjs +98 -13
  93. package/engine/rule-spec.d.mts +106 -0
  94. package/engine/rule-spec.mjs +301 -27
  95. package/engine/run-code-env.mjs +73 -19
  96. package/engine/schema.mjs +165 -243
  97. package/engine/sdk-strip.mjs +2 -2
  98. package/engine/service-names.d.mts +10 -0
  99. package/engine/service-names.mjs +10 -0
  100. package/engine/shared.d.mts +21 -0
  101. package/engine/shared.mjs +46 -13
  102. package/engine/skill-search.mjs +125 -53
  103. package/engine/source-kind.d.mts +9 -0
  104. package/engine/source-kind.mjs +16 -0
  105. package/engine/strategy-ids.mjs +5 -0
  106. package/engine/template-evaluation.d.mts +16 -0
  107. package/engine/template-evaluation.mjs +178 -0
  108. package/engine/template-frame.d.mts +16 -0
  109. package/engine/template-frame.mjs +69 -0
  110. package/engine/tool-config-engine.mjs +38 -34
  111. package/engine/tool-definition.d.mts +4 -0
  112. package/engine/tool-definition.mjs +5 -1
  113. package/engine/tool-git-bash.mjs +5 -1
  114. package/engine/tool-scope.mjs +22 -0
  115. package/engine/{dev-tool-search.mjs → tool-search.mjs} +63 -65
  116. package/engine/trigger.mjs +15 -270
  117. package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  118. package/engine/vendor/yaml/dist/nodes/Alias.js +25 -23
  119. package/engine/vendor/yaml/package.json +1 -1
  120. package/lib/client.js +8312 -6724
  121. package/lib/client.js.map +1 -1
  122. package/lib/index.d.mts +16 -2120
  123. package/lib/index.mjs +26914 -5816
  124. package/modules/ponytail/module.yml +83 -203
  125. package/modules/ptc-mode/module.yml +45 -0
  126. package/modules/soul/assets/core-hard.md +6 -0
  127. package/modules/soul/assets/criteria.md +12 -0
  128. package/modules/soul/assets/echo.md +12 -0
  129. package/modules/soul/assets/layers.md +11 -0
  130. package/modules/soul/assets/ledger.md +9 -0
  131. package/modules/soul/assets/manifest.yml +20 -0
  132. package/modules/soul/assets/reference/00-layers.md +27 -0
  133. package/modules/soul/assets/reference/01-criteria.md +32 -0
  134. package/modules/soul/assets/reference/02-echo.md +161 -0
  135. package/modules/soul/assets/reference/03-vital.md +183 -0
  136. package/modules/soul/assets/reference/04-manifest.json +106 -0
  137. package/modules/soul/assets/reference/05-doctor.md +67 -0
  138. package/modules/soul/assets/reference/06-reference.md +189 -0
  139. package/modules/soul/assets/reference/07-bundle.json +431 -0
  140. package/modules/soul/assets/reference/08-/350/270/251/345/235/221/345/256/236/345/275/225.md +59 -0
  141. package/modules/soul/assets/reference/09-dev-workflow.md +109 -0
  142. package/modules/soul/assets/reference/10-echo-anatomy.md +109 -0
  143. package/modules/soul/assets/reference/11-self.md +80 -0
  144. package/modules/soul/assets/reference/12-ledger.md +159 -0
  145. package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -0
  146. 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
  147. package/modules/soul/assets/reference/README.md +12 -0
  148. package/modules/soul/assets/templates/agents-template.md +6 -0
  149. package/modules/soul/assets/vital.md +9 -0
  150. package/modules/soul/module.yml +311 -0
  151. package/modules/superpowers/module.yml +122 -0
  152. package/modules/tool-surface/module.yml +51 -47
  153. package/package.json +37 -40
  154. package/skills/dsh-module/SKILL.md +67 -147
  155. package/skills/dsh-module/reference.md +113 -86
  156. package/templates/10-pre-step.yml +2 -3
  157. package/templates/14-first-turn-anchor.yml +10 -14
  158. package/templates/15-guide-auto.yml +32 -28
  159. package/templates/16-custom-fallback.yml +33 -21
  160. package/templates/20-system-section.yml +1 -4
  161. package/templates/50-llm-stream.yml +4 -10
  162. package/templates/60-tool-pipeline.yml +12 -10
  163. package/templates/63-append-context.yml +4 -6
  164. package/templates/65-turn-stop.yml +2 -2
  165. package/templates/67-inbox-prepend.yml +2 -2
  166. package/templates/70-subagent-maintenance.yml +9 -10
  167. package/templates/75-subagent-end.yml +2 -2
  168. package/templates/80-tool-surface.yml +41 -57
  169. package/templates/tools/40-delegate-module-import.yml +22 -0
  170. package/templates/tools/50-delegate-module-rules.yml +14 -0
  171. package/templates/tools/60-delegate-module-variables.yml +17 -0
  172. package/templates/tools/70-delegate-module-rules-edit.yml +31 -0
  173. package/engine/actions/decision.mjs +0 -54
  174. package/engine/actions/guard.mjs +0 -80
  175. package/engine/actions/sdk-strip.mjs +0 -30
  176. package/engine/character-tools.mjs +0 -8
  177. package/engine/classify-task.mjs +0 -53
  178. package/engine/compositions/source/local/character-tools.yml +0 -5
  179. package/engine/compositions/source/local/dev-tool-search.yml +0 -11
  180. package/engine/compositions/source/local/session-var-tools.yml +0 -5
  181. package/engine/compositions/source/local/subagent-tool-policy.yml +0 -21
  182. package/engine/compositions/source/local/world-book-tools.yml +0 -5
  183. package/engine/condition.mjs +0 -96
  184. package/engine/declared-triggers.mjs +0 -93
  185. package/engine/session-var-tools.mjs +0 -8
  186. package/engine/session-vars.mjs +0 -54
  187. package/engine/st-macros.d.mts +0 -22
  188. package/engine/st-macros.mjs +0 -208
  189. package/engine/st-render.mjs +0 -109
  190. package/engine/st-world-book.d.mts +0 -10
  191. package/engine/subagent-tool-policy-core.mjs +0 -400
  192. package/engine/subagent-tool-policy.mjs +0 -352
  193. package/engine/trigger-spec.mjs +0 -173
  194. package/engine/world-book-tools.mjs +0 -8
  195. package/lib/migrate-rules.d.mts +0 -1
  196. package/lib/migrate-rules.mjs +0 -610
  197. package/lib/module-package-DqJZaE71.mjs +0 -2
  198. package/lib/module-package-q2gDRu9H.mjs +0 -2869
  199. package/lib/rule-builder-DMrJten6.mjs +0 -14767
  200. package/templates/61-deny-tools-presentation.yml +0 -27
  201. package/templates/62-guard-deny-tools.yml +0 -20
  202. package/templates/policies/legacy-defaults.yml +0 -34
  203. package/templates/tools/40-delegate-character.yml +0 -15
  204. package/templates/tools/50-delegate-worldbook.yml +0 -15
  205. package/templates/tools/60-delegate-session-var.yml +0 -15
  206. 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
- }
@@ -1,8 +0,0 @@
1
- /** 会话变量工具预设模块。 */
2
- export const name = "session-var-tools"
3
-
4
- export function apply(ctx) {
5
- ctx.inject(["pt-session-var-tools"], (scopeCtx) => {
6
- scopeCtx.effect(() => scopeCtx.get("pt-session-var-tools").mount(scopeCtx), name + ": mount")
7
- })
8
- }
@@ -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
- }
@@ -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
@@ -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
- }
@@ -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 } }