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,178 @@
|
|
|
1
|
+
/** 有界模板解析与原生求值;只返回正文和变更,不持有或写入持久状态。 */
|
|
2
|
+
import { Buffer } from 'node:buffer'
|
|
3
|
+
import { interpolateVariables, isReservedInterpolationName, normalizeMacroSyntax } from './interpolate.mjs'
|
|
4
|
+
|
|
5
|
+
const MAX_BYTES = 1024 * 1024
|
|
6
|
+
const MAX_DEPTH = 32
|
|
7
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
|
|
8
|
+
|
|
9
|
+
function byteLength(text) {
|
|
10
|
+
const size = Buffer.byteLength(text, 'utf8')
|
|
11
|
+
if (size > MAX_BYTES) throw new RangeError('Template text exceeds 1 MiB')
|
|
12
|
+
return size
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** JSON 单花括号及字符串内的右括号不构成模板边界。 */
|
|
16
|
+
function macroEnd(text, start, depth = 0) {
|
|
17
|
+
const frames = [{ braces: 0, brackets: 0, quote: false, escape: false }]
|
|
18
|
+
if (depth >= MAX_DEPTH) throw new RangeError('Template depth exceeds 32')
|
|
19
|
+
for (let index = start + 2; index < text.length; index++) {
|
|
20
|
+
const frame = frames.at(-1), char = text[index]
|
|
21
|
+
if (frame.escape) { frame.escape = false; continue }
|
|
22
|
+
if (frame.quote && char === '\\') { frame.escape = true; continue }
|
|
23
|
+
if (char === '"' && (frame.quote || frame.braces > 0 || frame.brackets > 0)) { frame.quote = !frame.quote; continue }
|
|
24
|
+
if (text.startsWith('{{', index)) {
|
|
25
|
+
if (depth + frames.length >= MAX_DEPTH) throw new RangeError('Template depth exceeds 32')
|
|
26
|
+
frames.push({ braces: 0, brackets: 0, quote: false, escape: false }); index++; continue
|
|
27
|
+
}
|
|
28
|
+
if (frame.quote) continue
|
|
29
|
+
if (char === '{') frame.braces++
|
|
30
|
+
else if (char === '}' && frame.braces > 0) frame.braces--
|
|
31
|
+
else if (text.startsWith('}}', index)) {
|
|
32
|
+
frames.pop()
|
|
33
|
+
if (!frames.length) return index + 2
|
|
34
|
+
index++
|
|
35
|
+
} else if (char === '[') frame.brackets++
|
|
36
|
+
else if (char === ']') frame.brackets = Math.max(0, frame.brackets - 1)
|
|
37
|
+
}
|
|
38
|
+
return -1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** ST 编辑残片不是可执行表达式;只保留顶层尾部未知裸名,不放宽操作或嵌套语法。 */
|
|
42
|
+
function isLiteralFragment(text) {
|
|
43
|
+
const match = /^\{\{\s*([A-Za-z0-9_.\u4e00-\u9fff-]+)\s*\}?$/.exec(text.trimEnd())
|
|
44
|
+
if (!match) return false
|
|
45
|
+
const key = match[1]
|
|
46
|
+
return !FORBIDDEN_KEYS.has(key) && !isReservedInterpolationName(key)
|
|
47
|
+
&& !/^(?:(?:module|global)(?:\.|$)|(?:set|add|get|inc|dec)(?:global)?var$|char$|charifnotgroup$|user$|trim$|ERA$)/i.test(key)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 转换端和求值端共用边界与预算;出现身份不含正文字符偏移。 */
|
|
51
|
+
export function transformTemplate(text, macro) {
|
|
52
|
+
let work = 0, occurrence = 0
|
|
53
|
+
function walk(source, depth = 0) {
|
|
54
|
+
work += byteLength(source)
|
|
55
|
+
if (work > MAX_BYTES * MAX_DEPTH) throw new RangeError('Template expansion work limit exceeded')
|
|
56
|
+
const chunks = []
|
|
57
|
+
let size = 0, cursor = 0
|
|
58
|
+
const append = value => {
|
|
59
|
+
size += byteLength(value)
|
|
60
|
+
if (size > MAX_BYTES) throw new RangeError('Template output exceeds 1 MiB')
|
|
61
|
+
chunks.push(value)
|
|
62
|
+
}
|
|
63
|
+
while (cursor < source.length) {
|
|
64
|
+
const start = source.indexOf('{{', cursor)
|
|
65
|
+
if (start < 0) { append(source.slice(cursor)); break }
|
|
66
|
+
append(source.slice(cursor, start))
|
|
67
|
+
const end = macroEnd(source, start, depth)
|
|
68
|
+
if (end < 0) {
|
|
69
|
+
const rest = source.slice(start)
|
|
70
|
+
if (depth === 0 && isLiteralFragment(rest)) { append(rest); break }
|
|
71
|
+
throw new TypeError('Unclosed template expression')
|
|
72
|
+
}
|
|
73
|
+
append(macro(source.slice(start + 2, end - 2), value => walk(value, depth + 1), occurrence++))
|
|
74
|
+
cursor = end
|
|
75
|
+
}
|
|
76
|
+
return chunks.join('')
|
|
77
|
+
}
|
|
78
|
+
return walk(text)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 当前层第一个分隔符;嵌套表达式里的分隔符属于子表达式。 */
|
|
82
|
+
export function splitTemplateArgument(text) {
|
|
83
|
+
for (let index = 0; index < text.length; index++) {
|
|
84
|
+
if (text.startsWith('{{', index)) {
|
|
85
|
+
const end = macroEnd(text, index)
|
|
86
|
+
if (end < 0) throw new TypeError('Unclosed template expression')
|
|
87
|
+
index = end - 1
|
|
88
|
+
} else if (text.startsWith('::', index)) return [text.slice(0, index), text.slice(index + 2)]
|
|
89
|
+
}
|
|
90
|
+
return [text, undefined]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function addValue(value, increment) {
|
|
94
|
+
const current = value || '0'
|
|
95
|
+
let parsed
|
|
96
|
+
try { parsed = JSON.parse(current) } catch { /* 非 JSON 继续按数值或文本处理。 */ }
|
|
97
|
+
if (Array.isArray(parsed)) { parsed.push(increment); return JSON.stringify(parsed) }
|
|
98
|
+
if (Number.isNaN(Number(increment)) || Number.isNaN(Number(current))) return (value || '') + increment
|
|
99
|
+
const result = Number(current) + Number(increment)
|
|
100
|
+
if (!Number.isFinite(result)) throw new RangeError('Template variable number is not finite')
|
|
101
|
+
return String(result)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function evaluateTemplate(text, { module = {}, global = {}, session, sourceId = '', readOnly = false, samples = new Map() } = {}) {
|
|
105
|
+
const tables = { module: { ...module }, global: { ...global } }
|
|
106
|
+
const reads = new Set(), resolving = new Set(), values = {}
|
|
107
|
+
let scope, pick = 0
|
|
108
|
+
const keyOf = key => {
|
|
109
|
+
if (!key || FORBIDDEN_KEYS.has(key) || isReservedInterpolationName(key)) throw new TypeError(`Template variable key blocked: ${key}`)
|
|
110
|
+
return key
|
|
111
|
+
}
|
|
112
|
+
const raw = (domain, key) => {
|
|
113
|
+
reads.add(domain)
|
|
114
|
+
if (Object.hasOwn(tables[domain], key)) return { domain, value: tables[domain][key] }
|
|
115
|
+
if (domain === 'module') {
|
|
116
|
+
reads.add('global')
|
|
117
|
+
if (Object.hasOwn(tables.global, key)) return { domain: 'global', value: tables.global[key] }
|
|
118
|
+
}
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
const read = (found, key, expand) => {
|
|
122
|
+
if (!found) return ''
|
|
123
|
+
const id = `${found.domain}:${key}`
|
|
124
|
+
if (resolving.has(id)) throw new RangeError(`Template variable cycle: ${id}`)
|
|
125
|
+
resolving.add(id)
|
|
126
|
+
try { return expand(String(found.value)) } finally { resolving.delete(id) }
|
|
127
|
+
}
|
|
128
|
+
const write = (domain, key, value) => {
|
|
129
|
+
if (readOnly) throw new TypeError('Read-only template cannot write variables')
|
|
130
|
+
if (scope !== undefined && scope !== domain) throw new TypeError('One template may write only one variable scope')
|
|
131
|
+
byteLength(value)
|
|
132
|
+
scope = domain
|
|
133
|
+
tables[domain][key] = value
|
|
134
|
+
values[key] = value
|
|
135
|
+
}
|
|
136
|
+
const output = transformTemplate(text, (body, expand, occurrence) => {
|
|
137
|
+
const [name, args] = splitTemplateArgument(body)
|
|
138
|
+
const operation = /^(module|global)\.(set|get|add|inc|dec)$/.exec(name.trim())
|
|
139
|
+
if (operation) {
|
|
140
|
+
if (args === undefined) throw new TypeError(`Missing arguments for ${name}`)
|
|
141
|
+
const [rawKey, rawValue] = splitTemplateArgument(args)
|
|
142
|
+
const key = keyOf(expand(rawKey).trim()), domain = operation[1], action = operation[2]
|
|
143
|
+
if (readOnly && (action !== 'get' || rawValue !== undefined)) throw new TypeError('Read-only template cannot write variables')
|
|
144
|
+
if (action === 'get') {
|
|
145
|
+
const found = raw(domain, key)
|
|
146
|
+
if (found) return read(found, key, expand)
|
|
147
|
+
const fallback = rawValue === undefined ? '' : expand(rawValue)
|
|
148
|
+
if (fallback !== '') write(domain, key, fallback)
|
|
149
|
+
return fallback
|
|
150
|
+
}
|
|
151
|
+
if ((action === 'set' || action === 'add') && rawValue !== undefined) {
|
|
152
|
+
const value = expand(rawValue)
|
|
153
|
+
write(domain, key, action === 'set' ? value : addValue(read(raw(domain, key), key, expand), value))
|
|
154
|
+
return ''
|
|
155
|
+
}
|
|
156
|
+
if ((action === 'inc' || action === 'dec') && rawValue === undefined) {
|
|
157
|
+
const value = addValue(read(raw(domain, key), key, expand), action === 'inc' ? '1' : '-1')
|
|
158
|
+
write(domain, key, value)
|
|
159
|
+
return value
|
|
160
|
+
}
|
|
161
|
+
throw new TypeError(`Invalid arguments for ${name}`)
|
|
162
|
+
}
|
|
163
|
+
if (/^(set|add|get|inc|dec)(global)?var$/i.test(name.trim())) throw new TypeError('ST variable operations are retired; reimport or explicitly convert to module/global operations')
|
|
164
|
+
const whole = normalizeMacroSyntax(`{{${expand(body)}}}`)
|
|
165
|
+
const [key] = splitTemplateArgument(whole.slice(2, -2))
|
|
166
|
+
if (FORBIDDEN_KEYS.has(key)) throw new TypeError(`Template variable key blocked: ${key}`)
|
|
167
|
+
if (args === undefined && !isReservedInterpolationName(key)) {
|
|
168
|
+
const found = raw('module', key)
|
|
169
|
+
if (found) return read(found, key, expand)
|
|
170
|
+
}
|
|
171
|
+
if (whole.slice(2, -2).includes('{{')) return whole
|
|
172
|
+
const identity = key.toLowerCase() === 'pick' ? `pick:${pick++}` : String(occurrence)
|
|
173
|
+
const sample = `${identity}:${whole}`
|
|
174
|
+
if (!samples.has(sample)) samples.set(sample, interpolateVariables(whole, {}, session, undefined, `${sourceId}:${identity}`))
|
|
175
|
+
return samples.get(sample)
|
|
176
|
+
})
|
|
177
|
+
return { text: output, reads: [...reads], ...(scope === undefined ? {} : { changes: { scope, values } }) }
|
|
178
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TemplateSnapshot } from './template-evaluation.mjs'
|
|
2
|
+
export interface TemplateVariableStore {
|
|
3
|
+
read(moduleId: string): { module: Record<string, string>; global: Record<string, string>; enabled?: boolean; revisions: { module: string; global: string | null; settings?: string } }
|
|
4
|
+
commit(moduleId: string, request: { scope: 'global' | 'module'; changes: Record<string, string>; expectedRevisions: { module?: string; global?: string | null; settings?: string } }): { publicationPending?: boolean }
|
|
5
|
+
publish(moduleId: string): void
|
|
6
|
+
}
|
|
7
|
+
export interface TemplateConfig {
|
|
8
|
+
sourceModuleId: string
|
|
9
|
+
ruleId?: string
|
|
10
|
+
id: string
|
|
11
|
+
texts: string[]
|
|
12
|
+
variables?: TemplateSnapshot['module']
|
|
13
|
+
variableStore?: TemplateVariableStore
|
|
14
|
+
}
|
|
15
|
+
export function templateExecutionKey(agent: unknown, signal?: AbortSignal): string | undefined
|
|
16
|
+
export function renderTemplate(config: TemplateConfig, context?: { agent?: unknown; signal?: AbortSignal }, options?: { variableStore?: TemplateVariableStore; enabled?: boolean; warn?: (message: string) => void }): string
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** 执行凭据独立于规则 mount;变量真值始终归显式宿主 adapter。 */
|
|
2
|
+
import { historyEvents } from './history.mjs'
|
|
3
|
+
import { evaluateTemplate } from './template-evaluation.mjs'
|
|
4
|
+
import { interpolateVariables, stripUnresolvedRefs } from './interpolate.mjs'
|
|
5
|
+
|
|
6
|
+
const frames = new WeakMap()
|
|
7
|
+
const MAX_CARDS = 4096
|
|
8
|
+
|
|
9
|
+
/** 官方循环先 assemble、再 pre-step、最后打开 step/start;两出口共享已提交边界。 */
|
|
10
|
+
export function templateExecutionKey(agent, signal) {
|
|
11
|
+
if (agent?.status !== 'running' || !signal || signal.aborted) return undefined
|
|
12
|
+
const events = historyEvents(agent.session)
|
|
13
|
+
const turn = events.findLast(event => event.type === 'turn/start' || event.type === 'turn/end')
|
|
14
|
+
if (turn?.type !== 'turn/start' || !Number.isSafeInteger(turn.seq)) return undefined
|
|
15
|
+
const step = events.findLast(event => event.type === 'step/start' || event.type === 'step/end')
|
|
16
|
+
if (step?.type === 'step/start' && step.seq > turn.seq) return undefined
|
|
17
|
+
return `${turn.seq}:${step?.seq ?? -1}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function renderTemplate(config, context = {}, { variableStore = config.variableStore, enabled = true, warn = () => {} } = {}) {
|
|
21
|
+
const text = config.texts.join('\n\n'), agent = context.agent, signal = context.signal
|
|
22
|
+
signal?.throwIfAborted()
|
|
23
|
+
if (!enabled) return stripUnresolvedRefs(interpolateVariables(text, {}, agent?.session, undefined, config.id)).text
|
|
24
|
+
const execution = templateExecutionKey(agent, signal)
|
|
25
|
+
let frame
|
|
26
|
+
if (execution !== undefined) {
|
|
27
|
+
frame = frames.get(agent)
|
|
28
|
+
if (frame?.execution !== execution) { frame = { execution, cards: new Map() }; frames.set(agent, frame) }
|
|
29
|
+
}
|
|
30
|
+
const identity = JSON.stringify([config.sourceModuleId, config.ruleId, config.id])
|
|
31
|
+
const previous = frame?.cards.get(identity)
|
|
32
|
+
const publish = entry => {
|
|
33
|
+
if (!entry.pending) return
|
|
34
|
+
try {
|
|
35
|
+
if (!variableStore?.publish) throw new Error('Variable publication adapter unavailable')
|
|
36
|
+
variableStore.publish(config.sourceModuleId)
|
|
37
|
+
entry.pending = false
|
|
38
|
+
} catch (error) { warn(`Template ${config.id}: variables saved; publication pending: ${String(error?.message ?? error)}`) }
|
|
39
|
+
}
|
|
40
|
+
if (previous) { publish(previous); return previous.text }
|
|
41
|
+
if (frame?.cards.size >= MAX_CARDS) throw new RangeError('Template execution card limit exceeded')
|
|
42
|
+
const samples = new Map()
|
|
43
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
44
|
+
const snapshot = variableStore?.read(config.sourceModuleId) ?? { module: config.variables ?? {}, global: {} }
|
|
45
|
+
if (snapshot.enabled === false) return stripUnresolvedRefs(interpolateVariables(text, {}, agent?.session, undefined, config.id)).text
|
|
46
|
+
const result = evaluateTemplate(text, { module: snapshot.module, global: snapshot.global, session: agent?.session,
|
|
47
|
+
sourceId: `${config.sourceModuleId ?? ''}:${config.id}`, readOnly: execution === undefined, samples })
|
|
48
|
+
const entry = { text: stripUnresolvedRefs(result.text).text, pending: false }
|
|
49
|
+
if (result.changes) {
|
|
50
|
+
if (!variableStore?.commit) throw new Error('Persistent template writes require a variable adapter')
|
|
51
|
+
signal?.throwIfAborted()
|
|
52
|
+
const domains = new Set([...result.reads, result.changes.scope])
|
|
53
|
+
const expectedRevisions = Object.fromEntries([...domains].map(domain => [domain, snapshot.revisions[domain]]))
|
|
54
|
+
if (snapshot.revisions.settings !== undefined) expectedRevisions.settings = snapshot.revisions.settings
|
|
55
|
+
try {
|
|
56
|
+
const saved = variableStore.commit(config.sourceModuleId, { scope: result.changes.scope, changes: result.changes.values, expectedRevisions })
|
|
57
|
+
entry.pending = saved.publicationPending === true
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error?.persisted === true) entry.pending = true
|
|
60
|
+
else if (error?.status === 409 && attempt < 2) continue
|
|
61
|
+
else throw error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
frame?.cards.set(identity, entry)
|
|
65
|
+
if (entry.pending) warn(`Template ${config.id}: variables saved; publication pending`)
|
|
66
|
+
return entry.text
|
|
67
|
+
}
|
|
68
|
+
throw new Error('Template variable commit conflict')
|
|
69
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* delegate — 委托已注册工具(经 ctx.tools.execute 的 nested dispatch 走
|
|
15
15
|
* 完整官方管线:参数/输出校验、allow/deny/ask、pre/execute/post
|
|
16
16
|
* waterfall、timeout、nested token 与 durable result)
|
|
17
|
-
* fs — 文件操作(read/write/append/list/delete
|
|
17
|
+
* fs — 文件操作(read/write/append/list/delete,真实路径限定 cwd 内)
|
|
18
18
|
* ask-user — approval 通道询问用户(结果文本化)
|
|
19
19
|
*
|
|
20
20
|
* 安全:行 config.requireApproval = [kind...] 时该执行器先过 approval 门
|
|
@@ -22,22 +22,22 @@
|
|
|
22
22
|
* 工具注册经 disposer 契约(keepDisposer),插件卸载自动撤销。
|
|
23
23
|
*
|
|
24
24
|
* ── 能力提供者边界(T4)────────────────────────────────────────────────────
|
|
25
|
-
* 本模块是**能力提供者**,不是触发器。分类判据(与组合源 tool-config-engine.yml
|
|
26
|
-
*
|
|
25
|
+
* 本模块是**能力提供者**,不是触发器。分类判据(与组合源 tool-config-engine.yml
|
|
26
|
+
* 同一份措辞):
|
|
27
27
|
* - 会给模型提供可调用能力(注册工具 / 域 / 服务)的 → 能力提供者;
|
|
28
28
|
* - 干预流程(改提示词、改装配、裁决、追加)的 → 声明式触发器。
|
|
29
29
|
* 因此本模块**不接入** engine/trigger.mjs:它不订阅装配 waterfall,也没有
|
|
30
|
-
*
|
|
30
|
+
* 声明式触发器(`if`/`then` 声明),只在装配期把 module.yml 的 customTools 段编译出的工具注册进官方
|
|
31
31
|
* registry。强行并进触发器会让引擎同时承担「提供能力」与「干预流程」两种职责。
|
|
32
32
|
*
|
|
33
33
|
* 样板收敛:
|
|
34
34
|
* - 配置声明走 fields.mjs 的 `defineConfig`(未知键在挂载期 fail loud);
|
|
35
35
|
* - 注册走 disposer 契约:单次注册用 shared.keepDisposer(register() 返回的正是
|
|
36
36
|
* 它自己的 effect disposer);需要整组原子回滚或定向撤销句柄时才用 ctx.effect;
|
|
37
|
-
* - 降级告警统一 `${name}: <what>; <fallback>`
|
|
37
|
+
* - 降级告警统一 `${name}: <what>; <fallback>` 一个前缀格式。
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* 能力提供:把 module.yml 的 customTools 段注册进官方 tools registry;模型可见工具名
|
|
40
|
+
* 在装配期决定,引擎侧无法静态枚举(本模块不导出 provider 登记)。
|
|
41
41
|
*
|
|
42
42
|
* 文件归属校正(PLAN T4):第三个指定提供者 str-replace-editor 由
|
|
43
43
|
* engine/compositions/source/local/filesystem-editor.yml:20 引用**官方**包
|
|
@@ -46,11 +46,12 @@
|
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
48
|
import { readFileSync, readdirSync, writeFileSync, appendFileSync, rmSync, statSync } from 'node:fs'
|
|
49
|
-
import { execFile } from 'node:child_process'
|
|
49
|
+
import { execFile, execFileSync } from 'node:child_process'
|
|
50
50
|
import { isAbsolute, join, resolve, sep } from 'node:path'
|
|
51
51
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
52
52
|
import { parse as parseYaml } from './vendor/yaml/index.js'
|
|
53
53
|
import { importHostPackage } from './host-package.mjs'
|
|
54
|
+
import { resolveRealWithin } from './path-safety.mjs'
|
|
54
55
|
import { validateDefinition } from './tool-definition.mjs'
|
|
55
56
|
import { defineConfig, passthrough } from './fields.mjs'
|
|
56
57
|
import { keepDisposer } from './shared.mjs'
|
|
@@ -68,6 +69,24 @@ const ENV_ALLOWLIST = [
|
|
|
68
69
|
'USERPROFILE', 'HOME', 'DSH_HOME', 'DSH_WORKSPACE', 'DSH_SESSION_ID',
|
|
69
70
|
]
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* shell 输出上限(stdout/stderr 各自):超限时 execFile 以
|
|
74
|
+
* ERR_CHILD_PROCESS_STDIO_MAXBUFFER 失败并保留截断输出,错误文本说明原因。
|
|
75
|
+
*/
|
|
76
|
+
const SHELL_MAX_OUTPUT_BYTES = 1024 * 1024
|
|
77
|
+
|
|
78
|
+
/** 超时尽量结束整棵子进程树;Windows 用 taskkill /T,失败再退回 child.kill。 */
|
|
79
|
+
function killShellTree(child) {
|
|
80
|
+
if (child === undefined || child.pid === undefined) return
|
|
81
|
+
if (process.platform === 'win32') {
|
|
82
|
+
try {
|
|
83
|
+
execFileSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true })
|
|
84
|
+
return
|
|
85
|
+
} catch { /* taskkill 不可用时退回单进程 kill */ }
|
|
86
|
+
}
|
|
87
|
+
try { child.kill() } catch { /* 已退出 */ }
|
|
88
|
+
}
|
|
89
|
+
|
|
71
90
|
/** 插值 {{args.x.y}} → 参数深取值(缺失保留字面)。 */
|
|
72
91
|
function interpolateArgs(text, args) {
|
|
73
92
|
return String(text ?? '').replace(/\{\{args\.([A-Za-z0-9_.]+)\}\}/g, (_whole, path) => {
|
|
@@ -163,16 +182,6 @@ async function approvalGate(ctx, exec, kind, reason) {
|
|
|
163
182
|
return { ok: false, message: `approval for tool ${exec.name} was ${outcome}` }
|
|
164
183
|
}
|
|
165
184
|
|
|
166
|
-
/** cwd 内路径限定(防越界;resolve 后必须位于 cwd 之下)。 */
|
|
167
|
-
function withinCwd(cwd, target) {
|
|
168
|
-
const resolved = resolve(cwd, target)
|
|
169
|
-
const root = resolve(cwd)
|
|
170
|
-
if (resolved !== root && !resolved.startsWith(root + sep)) {
|
|
171
|
-
throw new Error(`path ${JSON.stringify(target)} escapes the workspace`)
|
|
172
|
-
}
|
|
173
|
-
return resolved
|
|
174
|
-
}
|
|
175
|
-
|
|
176
185
|
/** delegate args 映射:完整引用({{args.x}})透传原始类型(数组/数字/布尔不字符串化);
|
|
177
186
|
* 部分引用(前缀-{{args.x}})插值为字符串;非插值值原样。 */
|
|
178
187
|
function mapDelegateArgs(execArgs, args) {
|
|
@@ -231,18 +240,25 @@ function createExecute(ctx, def, requireApproval) {
|
|
|
231
240
|
const env = { ...Object.fromEntries(ENV_ALLOWLIST.filter((key) => process.env[key] !== undefined).map((key) => [key, process.env[key]])), ...(exec.env ?? {}) }
|
|
232
241
|
const output = await new Promise((resolvePromise) => {
|
|
233
242
|
let killTimer
|
|
234
|
-
|
|
243
|
+
let timedOut = false
|
|
244
|
+
const child = execFile(shell, argv, { cwd, env, windowsHide: true, maxBuffer: SHELL_MAX_OUTPUT_BYTES }, (error, stdout, stderr) => {
|
|
235
245
|
clearTimeout(killTimer)
|
|
246
|
+
// 超时被杀与输出超限都不是普通退出码:error 文本必须能区分二者(不新增输出字段)。
|
|
247
|
+
const reason = error === null ? undefined
|
|
248
|
+
: timedOut ? `shell command timed out after ${def.timeoutMs} ms and was killed`
|
|
249
|
+
: error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'
|
|
250
|
+
? `shell command output exceeded ${SHELL_MAX_OUTPUT_BYTES} bytes (truncated)`
|
|
251
|
+
: String(error.message)
|
|
236
252
|
resolvePromise({
|
|
237
253
|
exitCode: error === null ? 0 : (typeof error.code === 'number' ? error.code : 1),
|
|
238
254
|
stdout: String(stdout ?? ''),
|
|
239
255
|
stderr: String(stderr ?? ''),
|
|
240
|
-
...(
|
|
256
|
+
...(reason === undefined ? {} : { error: reason }),
|
|
241
257
|
})
|
|
242
258
|
})
|
|
243
259
|
const timeout = def.timeoutMs
|
|
244
260
|
if (timeout !== undefined && Number.isSafeInteger(timeout) && timeout > 0) {
|
|
245
|
-
killTimer = setTimeout(() => child
|
|
261
|
+
killTimer = setTimeout(() => { timedOut = true; killShellTree(child) }, timeout)
|
|
246
262
|
}
|
|
247
263
|
})
|
|
248
264
|
return output
|
|
@@ -292,7 +308,8 @@ function createExecute(ctx, def, requireApproval) {
|
|
|
292
308
|
if (exec.kind === 'fs') {
|
|
293
309
|
const cwd = run.agent?.session?.header?.cwd ?? process.cwd()
|
|
294
310
|
try {
|
|
295
|
-
|
|
311
|
+
// 词法 resolve 挡不住 symlink/junction:统一用真实路径判定,不存在的目标按父目录解析。
|
|
312
|
+
const file = resolveRealWithin(resolve(cwd, interpolateArgs(exec.path, args)), [cwd])
|
|
296
313
|
const action = interpolateArgs(exec.action, args)
|
|
297
314
|
if (action === 'read') {
|
|
298
315
|
const raw = readFileSync(file, 'utf8')
|
|
@@ -423,19 +440,6 @@ export const configContract = {
|
|
|
423
440
|
},
|
|
424
441
|
}
|
|
425
442
|
|
|
426
|
-
/**
|
|
427
|
-
* 能力提供者登记(T4 边界守卫的数据源)。
|
|
428
|
-
* `provides.kind === 'dynamic'`:模型可见的工具名由 module.yml 的 customTools 段在
|
|
429
|
-
* 装配期决定,引擎侧无法静态枚举——守卫只断言登记形状、
|
|
430
|
-
* 注册通道,以及「提供者不导出触发器声明」这条互斥。
|
|
431
|
-
*/
|
|
432
|
-
export const engineProvider = {
|
|
433
|
-
kind: 'provider',
|
|
434
|
-
moduleId: name,
|
|
435
|
-
registers: 'tools',
|
|
436
|
-
provides: { kind: 'dynamic', source: 'module.yml#customTools → tools' },
|
|
437
|
-
}
|
|
438
|
-
|
|
439
443
|
/** 插件入口:优先注册内联 tools,缺字段时兼容 configsDir。 */
|
|
440
444
|
export function apply(ctx, config) {
|
|
441
445
|
const source = configContract.parse(config, name)
|
|
@@ -62,7 +62,7 @@ export function validateDefinition(def) {
|
|
|
62
62
|
throw new TypeError(`tool ${def.id}: description is required`)
|
|
63
63
|
}
|
|
64
64
|
if (def.scope !== undefined) {
|
|
65
|
-
throw new TypeError(`tool ${def.id}: customTools.scope is not supported (
|
|
65
|
+
throw new TypeError(`tool ${def.id}: customTools.scope is not supported (a custom tool carries no per-tool scope)`)
|
|
66
66
|
}
|
|
67
67
|
if (def.parameters !== undefined) {
|
|
68
68
|
validateJsonSchemaNode(def.parameters, `tool ${def.id}: parameters`)
|
|
@@ -88,6 +88,10 @@ export function validateDefinition(def) {
|
|
|
88
88
|
if (exec.kind === 'delegate' && (typeof exec.tool !== 'string' || exec.tool.length === 0)) {
|
|
89
89
|
throw new TypeError(`tool ${def.id}: execute.tool is required for delegate`)
|
|
90
90
|
}
|
|
91
|
+
if (exec.kind === 'delegate' && ['character_import', 'character_list', 'character_apply', 'character_remove', 'character_delete',
|
|
92
|
+
'world_book_list', 'world_book_upsert', 'world_book_delete', 'world_book_read_memory', 'session_var'].includes(exec.tool)) {
|
|
93
|
+
throw new TypeError(`tool ${def.id}: delegate ${exec.tool} 已退役;请重新导入或显式转换为模块工具`)
|
|
94
|
+
}
|
|
91
95
|
if (exec.kind === 'fs' && (typeof exec.action !== 'string'
|
|
92
96
|
|| (!['read', 'write', 'append', 'list', 'delete'].includes(exec.action) && !exec.action.includes('{{args.')))) {
|
|
93
97
|
throw new TypeError(`tool ${def.id}: execute.action must be read/write/append/list/delete (or {{args.*}} template) for fs`)
|
package/engine/tool-git-bash.mjs
CHANGED
|
@@ -133,12 +133,16 @@ const commandSchema = {
|
|
|
133
133
|
type: 'string',
|
|
134
134
|
description: 'The bash command to execute (`bash -c` string domain).',
|
|
135
135
|
},
|
|
136
|
+
description: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'Clear, concise description of what this command does, 5-10 words (shown in the UI).',
|
|
139
|
+
},
|
|
136
140
|
workdir: {
|
|
137
141
|
type: 'string',
|
|
138
142
|
description: 'Optional working directory; defaults to the session cwd. On Windows, Git Bash paths like /e/foo are accepted and converted to E:\\foo.',
|
|
139
143
|
},
|
|
140
144
|
},
|
|
141
|
-
required: ['command'],
|
|
145
|
+
required: ['command', 'description'],
|
|
142
146
|
additionalProperties: false,
|
|
143
147
|
}
|
|
144
148
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-scope — 工具面的作用域快照,避免各消费方把全局视图当成模型可见面。
|
|
3
|
+
*
|
|
4
|
+
* 返回两个字段,各自只服务一个调用方:
|
|
5
|
+
* - visible:带 agent 的 `tools.schemas(agent)`,包含预设/祖先/本层工具;模型可见面。
|
|
6
|
+
* - globalNames:不带 scope 的 `tools.schemas()` 名字集合;只供 `restrict` 过滤已知名。
|
|
7
|
+
*
|
|
8
|
+
* 全局原始 schema 与 visible-only 差集不外露;等真实诊断消费方出现再加字段。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const schemaList = (tools, scope) => {
|
|
12
|
+
if (typeof tools?.schemas !== 'function') return []
|
|
13
|
+
const schemas = tools.schemas(scope)
|
|
14
|
+
return Array.isArray(schemas) ? schemas : []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toolScopeSnapshot(tools, agent) {
|
|
18
|
+
const global = schemaList(tools)
|
|
19
|
+
const visible = agent === undefined ? global : schemaList(tools, agent)
|
|
20
|
+
const globalNames = new Set(global.map((schema) => schema?.name).filter((name) => typeof name === 'string'))
|
|
21
|
+
return { visible, globalNames }
|
|
22
|
+
}
|