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
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 两个动作合一:
|
|
8
8
|
* - `query` 关键词搜索**完整装配目录**(只回名字 + 一行截断描述,不回 parameters);
|
|
9
|
-
* - `toolNames`
|
|
10
|
-
* `
|
|
9
|
+
* - `toolNames` 按**精确名**解锁。解锁名写进本次调用的持久参数(顶层 `tool/call`;
|
|
10
|
+
* PTC 嵌套为 `tool/ptc-dispatch`),发现消费方的 allowExtra 从那里读回 → 下次请求起
|
|
11
|
+
* 生效,且跨压缩保留。
|
|
11
12
|
*
|
|
12
13
|
* **能力摘要来自运行时截获,不手工维护**(2026-10-05 用户指出):空查询时按前缀把
|
|
13
14
|
* `ctx.tools.schemas()` 的可见目录折叠成分组摘要(`mcp__github__* ×46`,实测 433 字符
|
|
@@ -15,17 +16,22 @@
|
|
|
15
16
|
* 名字写错还会让模型照着错名字解锁(那份实现甚至需要一条专门测试来防错名)。
|
|
16
17
|
* 官方注册表只投影 name/description(无分组字段),所以分组只能按前缀在本地折叠。
|
|
17
18
|
*
|
|
18
|
-
* 与原 `
|
|
19
|
+
* 与原 `tool-search.mjs` 的两处修正照搬:
|
|
19
20
|
* 1. 匹配是**打分**而非 AND 过滤——长自然语言查询("file edit write replace")在
|
|
20
21
|
* AND 口径下命中零个,等于白搜。现在精确名优先,其次按命中 token 数排序。
|
|
21
22
|
* 2. 描述里显式教解锁路径("搜索为空 ≠ 工具不存在")——实测模型只会搜、不会传
|
|
22
23
|
* toolNames,于是收窄后永远拿不到工具。
|
|
23
24
|
*/
|
|
24
|
-
export const name = '
|
|
25
|
+
export const name = 'tool-search'
|
|
25
26
|
|
|
26
27
|
/** 工具注册表必须先于本工具存在(同 skill-search 的 inject 纪律)。 */
|
|
27
28
|
export const inject = ['tools']
|
|
28
29
|
|
|
30
|
+
import { toJsonSchema } from './shared.mjs'
|
|
31
|
+
import { renderRows, rankRows, scoreRows, queryTokens } from './discovery.mjs'
|
|
32
|
+
import { toolScopeSnapshot } from './tool-scope.mjs'
|
|
33
|
+
import { DISCOVERY_SERVICE } from './service-names.mjs'
|
|
34
|
+
|
|
29
35
|
/** 单次搜索结果上限:再多就该让模型收窄关键词。 */
|
|
30
36
|
export const MAX_RESULTS = 25
|
|
31
37
|
|
|
@@ -36,31 +42,17 @@ const DESC_LIMIT = 90
|
|
|
36
42
|
const MAX_GROUPS = 24
|
|
37
43
|
|
|
38
44
|
/** 常驻核心集:由工具面收窄模板声明,这里只在描述里如实告知模型。 */
|
|
39
|
-
const RESIDENT = ['pwsh', 'read', 'write', 'edit', 'glob', 'grep', 'todo_write', 'skill_search', 'skill_load']
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
function toJsonSchema(spec) {
|
|
43
|
-
const properties = {}
|
|
44
|
-
const required = []
|
|
45
|
-
for (const [key, meta] of Object.entries(spec || {})) {
|
|
46
|
-
const prop = { type: meta.type }
|
|
47
|
-
if (meta.description) prop.description = meta.description
|
|
48
|
-
if (meta.items) prop.items = meta.items
|
|
49
|
-
properties[key] = prop
|
|
50
|
-
if (meta.required) required.push(key)
|
|
51
|
-
}
|
|
52
|
-
return { type: 'object', properties, required, additionalProperties: false }
|
|
53
|
-
}
|
|
45
|
+
const RESIDENT = ['bash', 'pwsh', 'read', 'write', 'edit', 'glob', 'grep', 'todo_write', 'skill_search', 'skill_load']
|
|
46
|
+
|
|
47
|
+
/** 参数 schema 由 `shared.mjs` 的统一实现生成(与 `skill-search` 同一份)。 */
|
|
54
48
|
|
|
55
49
|
/**
|
|
56
|
-
* 能力索引:常驻核心集(pwsh / read / write / edit / glob / grep / todo_write /
|
|
50
|
+
* 能力索引:常驻核心集(bash / pwsh / read / write / edit / glob / grep / todo_write /
|
|
57
51
|
* skill_search / skill_load)覆盖不到的能力。名字全部取自实测工具面。
|
|
58
52
|
*/
|
|
59
53
|
|
|
60
|
-
/**
|
|
61
|
-
export
|
|
62
|
-
return (typeof query === 'string' ? query : '').toLowerCase().split(/[^\p{L}\p{N}_-]+/u).filter(Boolean)
|
|
63
|
-
}
|
|
54
|
+
/** 查询分词由发现引擎统一实现(与技能面同一口径)。 */
|
|
55
|
+
export { queryTokens }
|
|
64
56
|
|
|
65
57
|
/**
|
|
66
58
|
* 一个工具名的分组键:MCP 工具折到服务级(`mcp__github__*`),其余折到首个下划线段
|
|
@@ -102,17 +94,10 @@ export function summarizeCatalog(schemas) {
|
|
|
102
94
|
* `wanted` 为空时返回空数组——调用方据此区分「没给查询」与「没搜到」。
|
|
103
95
|
*/
|
|
104
96
|
export function scoreTools(schemas, wanted) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const haystack = `${name} ${schema?.description ?? ''}`.toLowerCase()
|
|
110
|
-
let score = 0
|
|
111
|
-
for (const token of wanted) if (haystack.includes(token)) score += 1
|
|
112
|
-
return { schema, name, score, exact: wanted.includes(name.toLowerCase()) }
|
|
113
|
-
})
|
|
114
|
-
.filter((entry) => entry.name.length > 0 && (entry.exact || entry.score >= 1))
|
|
115
|
-
.sort((a, b) => (b.exact - a.exact) || (b.score - a.score) || a.name.localeCompare(b.name))
|
|
97
|
+
return rankRows(scoreRows(schemas, wanted, {
|
|
98
|
+
nameOf: (schema) => schema?.name,
|
|
99
|
+
haystackOf: (schema) => `${schema?.name ?? ''} ${schema?.description ?? ''}`,
|
|
100
|
+
})).map((row) => ({ ...row, schema: row.item }))
|
|
116
101
|
}
|
|
117
102
|
|
|
118
103
|
/** 解锁名单归一:只留非空字符串,去重保序。 */
|
|
@@ -129,46 +114,38 @@ export function unlockNames(value) {
|
|
|
129
114
|
|
|
130
115
|
/** 把目录搜索结果渲染成给模型的文本(带截断提示与解锁引导)。 */
|
|
131
116
|
export function renderMatches(scored, query) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
lines.push(`Matching tools (${matches.length}${scored.length > MAX_RESULTS ? ` of ${scored.length}` : ''}):`)
|
|
141
|
-
for (const { schema, name, exact } of matches) {
|
|
142
|
-
const desc = String(schema?.description ?? '').split('\n')[0].slice(0, DESC_LIMIT)
|
|
143
|
-
lines.push(`- ${name}${exact ? ' (exact)' : ''}: ${desc}`)
|
|
144
|
-
}
|
|
145
|
-
if (scored.length > MAX_RESULTS) {
|
|
146
|
-
lines.push(`(truncated at ${MAX_RESULTS} — add tokens to narrow the query)`)
|
|
147
|
-
}
|
|
148
|
-
lines.push('Unlock with dev_tool_search({"toolNames": ["<exact name>"]}).')
|
|
149
|
-
return lines
|
|
117
|
+
return renderRows(scored, {
|
|
118
|
+
query,
|
|
119
|
+
limit: MAX_RESULTS,
|
|
120
|
+
noun: 'tools',
|
|
121
|
+
emptyText: `No tools match "${query}". An empty result only means no tool matched your keywords — if you need a specific tool, unlock it directly by exact name, e.g. tool_search({"toolNames":["web_search"]}).`,
|
|
122
|
+
lineOf: ({ schema, name, exact }) => `- ${name}${exact ? ' (exact)' : ''}: ${String(schema?.description ?? '').split('\n')[0].slice(0, DESC_LIMIT)}`,
|
|
123
|
+
footer: 'Unlock with tool_search({"toolNames": ["<exact name>"]}).',
|
|
124
|
+
})
|
|
150
125
|
}
|
|
151
126
|
|
|
152
|
-
/** 注册模型可见的 `
|
|
153
|
-
export function
|
|
154
|
-
|
|
155
|
-
|
|
127
|
+
/** 注册模型可见的 `tool_search`。 */
|
|
128
|
+
export function createToolSearchDefinition(ctx, options = {}) {
|
|
129
|
+
const resident = options.resident ?? RESIDENT
|
|
130
|
+
const unlockKey = options.unlockKey ?? 'toolNames'
|
|
131
|
+
return {
|
|
132
|
+
name: options.tool ?? 'tool_search',
|
|
156
133
|
description: [
|
|
157
134
|
'Discover and unlock tools that are NOT currently available.',
|
|
158
135
|
'',
|
|
159
|
-
`This session starts with a minimal resident set: ${
|
|
136
|
+
`This session starts with a minimal resident set: ${resident.join(', ')}. Everything else is unlocked on demand through this tool.`,
|
|
160
137
|
'',
|
|
161
138
|
'Usage:',
|
|
162
139
|
'- `query` — search the catalog by keyword (matches tool names AND descriptions, 1-2 short words work best).',
|
|
163
140
|
'- call with NO arguments — list the catalog as group counts (`<prefix>_*(count) ...`), so you can see what exists before searching.',
|
|
164
|
-
|
|
141
|
+
`- \`${unlockKey}\` — unlock exact names. Unlocked tools appear from the NEXT request on and stay unlocked for the session.`,
|
|
165
142
|
'',
|
|
166
|
-
|
|
167
|
-
'IMPORTANT: an empty search result does NOT mean the tool does not exist
|
|
143
|
+
`Example: ${options.tool ?? 'tool_search'}({"query":"pull request","${unlockKey}":["mcp__github__create_pull_request"]}) — search AND unlock in one call.`,
|
|
144
|
+
'IMPORTANT: if the task needs a capability outside that resident set — web search, subagents, task boards, MCP servers, anything not listed above — search for it here and unlock it. Do NOT make do with a resident tool when the tool you actually need is merely locked; an empty search result does NOT mean the tool does not exist, it only means no tool matched your keywords. Call with no arguments to see the catalog groups, then unlock by exact name.',
|
|
168
145
|
].join('\n'),
|
|
169
146
|
parameters: toJsonSchema({
|
|
170
147
|
query: { type: 'string', required: false, description: 'search keywords (e.g. "web", "subagent", "github")' },
|
|
171
|
-
|
|
148
|
+
[unlockKey]: {
|
|
172
149
|
type: 'array',
|
|
173
150
|
required: false,
|
|
174
151
|
items: { type: 'string' },
|
|
@@ -181,7 +158,7 @@ export function apply(ctx) {
|
|
|
181
158
|
},
|
|
182
159
|
async execute(args, exec) {
|
|
183
160
|
const query = typeof args?.query === 'string' ? args.query.trim() : ''
|
|
184
|
-
const unlock = unlockNames(args?.
|
|
161
|
+
const unlock = unlockNames(args?.[unlockKey])
|
|
185
162
|
const lines = []
|
|
186
163
|
// 解锁动作本身不需要任何运行时服务:引擎的 allowFrom 只读本工具的持久调用参数,
|
|
187
164
|
// 所以即便目录不可用(无 tools 服务/无 agent),解锁也必须照样回报成功。
|
|
@@ -191,7 +168,7 @@ export function apply(ctx) {
|
|
|
191
168
|
try {
|
|
192
169
|
// 执行中的 agent 就是观察作用域:preset 工具注册在 agent scope 层,
|
|
193
170
|
// 不带 scope 的 schemas() 只看全局层,会漏掉它们。
|
|
194
|
-
schemas = ctx.tools
|
|
171
|
+
schemas = toolScopeSnapshot(ctx.tools, exec?.agent).visible
|
|
195
172
|
} catch (error) {
|
|
196
173
|
return { text: [...lines, `catalog search unavailable: ${String(error?.message ?? error)}`].join('\n') }
|
|
197
174
|
}
|
|
@@ -209,5 +186,26 @@ export function apply(ctx) {
|
|
|
209
186
|
lines.push(...renderMatches(scoreTools(schemas, queryTokens(query)), query))
|
|
210
187
|
return { text: lines.join('\n') }
|
|
211
188
|
},
|
|
212
|
-
}
|
|
189
|
+
}
|
|
213
190
|
}
|
|
191
|
+
|
|
192
|
+
/** 本引擎向发现注册表提供的元数据:工具名与「解锁名单从哪个参数回收」。 */
|
|
193
|
+
export const discoveryAdapter = Object.freeze({
|
|
194
|
+
kind: 'tools',
|
|
195
|
+
tool: 'tool_search',
|
|
196
|
+
unlock: Object.freeze({ tool: 'tool_search', key: 'toolNames' }),
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 组合源入口:注册工具(行语义不变),并向发现注册表登记元数据。
|
|
201
|
+
* 注册表由宿主 root provide;引擎单独使用时它不存在,跳过登记即可。
|
|
202
|
+
*/
|
|
203
|
+
export function apply(ctx, _config, options = {}) {
|
|
204
|
+
const registered = ctx.tools.register(createToolSearchDefinition(ctx))
|
|
205
|
+
const registry = typeof ctx.get === 'function' ? ctx.get(DISCOVERY_SERVICE) : undefined
|
|
206
|
+
const disposeAdapter = registry === undefined ? undefined : registry.register(options.agent, 'tools', discoveryAdapter)
|
|
207
|
+
return () => {
|
|
208
|
+
if (typeof disposeAdapter === 'function') disposeAdapter()
|
|
209
|
+
if (typeof registered === 'function') registered()
|
|
210
|
+
}
|
|
211
|
+
}
|
package/engine/trigger.mjs
CHANGED
|
@@ -1,245 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* trigger —
|
|
2
|
+
* trigger — 触发器的两个运行时设施:waterfall 位置取值,与观察者谓词的会话事件接线。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - `do` 由 `engine/actions.mjs` 提供(七类动作);
|
|
7
|
-
* - 本文件只做:声明校验(复用 B2 的 fields.mjs)、同通道内的稳定排序、
|
|
8
|
-
* 宿主 waterfall 位置映射、注册与 disposer、降级与统一告警、会话态生命周期。
|
|
4
|
+
* 本文件不承担声明校验、排序与注册:那些归 `rule-spec.mjs`(编译)与 `rule-runtime.mjs`
|
|
5
|
+
* (挂载)——规则是唯一的声明载体,`engine/rules` 之外没有第二条触发通道。
|
|
9
6
|
*
|
|
10
|
-
* ##
|
|
7
|
+
* ## waterfallPosition 为什么与 channelOrder 分开
|
|
11
8
|
*
|
|
12
9
|
* 官方 Cordis(`node_modules/@deepseek-ai/cordis/src/events.ts`)**没有数字排序**:
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
* - `:228-229` 注释:不调用 `next()` 的监听器会否决其后整条链(含内置行为)。
|
|
10
|
+
* - `register()` 只做 `options.prepend ? 'unshift' : 'push'`;
|
|
11
|
+
* - `waterfall()` 用 `cbs.shift()` 从链首取,所以 unshift 的监听器最先执行(最外层),
|
|
12
|
+
* 而**多个 prepend 之间后注册的更靠前**;不调用 `next()` 的监听器会否决其后整条链。
|
|
17
13
|
*
|
|
18
|
-
*
|
|
19
|
-
* - `channelOrder: number`
|
|
20
|
-
*
|
|
21
|
-
* - `waterfallPosition` —— 适配器在宿主 waterfall 中的位置,只有 `'outermost'`
|
|
22
|
-
* 才映射为 `prepend: true`,**不承担**声明间排序。
|
|
14
|
+
* 因此两个字段互不混淆,**不得**用一个字段同时承担两件事:
|
|
15
|
+
* - `channelOrder: number` —— 同一通道内的声明顺序,由 `rule-spec.mjs` 做有界稳定排序;
|
|
16
|
+
* - `waterfallPosition` —— 在宿主 waterfall 中的位置,只有 `'outermost'` 映射为 `prepend: true`。
|
|
23
17
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* ## 声明载体约定(与 T4 的能力提供者边界守卫对齐)
|
|
28
|
-
*
|
|
29
|
-
* 本文件导出的是**引擎本身**(`mountTriggers` / `orderTriggers` / `validateTrigger` /
|
|
30
|
-
* `registrationOptions` / `createDecisionLog` 等函数与常量),**不是**某个模块的触发器声明。
|
|
31
|
-
* 声明由**消费方模块**导出,约定名为 `engineTriggers`(数组,元素交给 `validateTrigger`);
|
|
32
|
-
* 它与能力提供者的 `engineProvider` 登记**互斥**——同一模块不得同时导出两者,
|
|
33
|
-
* 该互斥由 `test/engine/provider-boundary.test.mjs` 直接断言。
|
|
34
|
-
* 因此「某个模块是触发器还是提供者」看的是它导出了哪一个,而不是它 import 了什么。
|
|
35
|
-
*
|
|
36
|
-
* ## 与 predicates 的接线要点(T1 交付时确认的事实)
|
|
37
|
-
*
|
|
38
|
-
* - `when` 直接接 `createXxxPredicate(...)` 的返回值——它们是**函数**,`await when(...)` 原生兼容;
|
|
39
|
-
* - **相位 / 计数**两类谓词带 `observe(session, event)` 入口:本文件在同组声明上**共用一条**
|
|
40
|
-
* `session/event` 监听喂它们(不按触发器各接一条),且没有这类谓词时**不接**(零开销);
|
|
41
|
-
* - **当前预设**类需在**挂载期一次** `await loadStandingMountFor()`,把结果传进
|
|
42
|
-
* `createPresetPredicate({ ctx, presetId, standingMountFor })`,这样判定期零 IO;
|
|
43
|
-
* - 谓词**运行期不抛**(配置错误在挂载期 fail loud),所以 `when` 的 try/catch 是兜底而非主路径。
|
|
44
|
-
*
|
|
45
|
-
* ## `state`:会话态声明的最小接口(B4 迁移的契约基准)
|
|
46
|
-
*
|
|
47
|
-
* `state` 是**声明**(该触发器需要什么会话态),不是运行时对象:运行时的会话态一律经
|
|
48
|
-
* `engine/shared.mjs` 的既有读法取得,**不新造接口、不新造存储通道**。B4 迁移既有模块时
|
|
49
|
-
* 只允许依赖下面这一组,超出即是新发明的接口,须先回到本文件扩契约:
|
|
50
|
-
*
|
|
51
|
-
* | 读取 | 既有实现 | 最小形状 |
|
|
52
|
-
* |--------------------|-----------------------------------|--------------------------------|
|
|
53
|
-
* | 会话标识(记账键) | `session.id` | `string \| undefined` |
|
|
54
|
-
* | durable 事件快照 | `shared.sessionEvents(session)` | `{ type, data? }[]`,缺接口=空 |
|
|
55
|
-
* | 轮号 | 各模块内联 `event.data.turn` | 有限 `number`,缺失=无轮号 |
|
|
56
|
-
* | 子代理判定 | `shared.isDelegated(session)` | `header.delegationDepth > 0` |
|
|
57
|
-
* | 会话态 Map 上限 | `shared.MAX_TRACKED_SESSIONS` | 超出整体清空(触发冷扫重建) |
|
|
58
|
-
*
|
|
59
|
-
* 三条纪律:**(1)** 没有 `session.id` 的会话一律不记账(`predicates.mjs:343` 的既有决定:
|
|
60
|
-
* 共用一个 Map 键会让两个会话串味),每次冷扫而非缓存;**(2)** 子代理判定复用
|
|
61
|
-
* `isDelegated`,不写字面量比较(原 `progress-reminder` 的 `== 0` 判定与它是同一语义的
|
|
62
|
-
* 两种写法,B4 迁移时统一到前者,行为不变);**(3)** `snapshotEvents()` 是正式 API
|
|
63
|
-
* (DSH 0.1.2-alpha.4+),缺失时按空日志处理——**不得**回退读旧的 `events` 数组。
|
|
64
|
-
*
|
|
65
|
-
* **给 B4 的边界(B4 PLAN 的措辞与本文件不一致处,以此为准)**:B4 计划里的
|
|
66
|
-
* `session(create, { resetOn })` **不能放进 `shared.mjs`**——`resetOn` 是复位时机,要落地
|
|
67
|
-
* 就得在挂载期注册 `session/event` 监听,而 `shared.mjs` 是**没有 `ctx` 的纯函数模块**
|
|
68
|
-
* (现有导出全是无状态工具)。B4 的统一访问接口须满足:**(a)** 接收挂载期 `ctx`(或由
|
|
69
|
-
* `mountTriggers` 一侧构造),**(b)** 建在本节这组既有读法之上、**不新造事件类型或持久通道**,
|
|
70
|
-
* **(c)** 逐模块保留原有键类型与淘汰策略(`Map<session.id>` / `WeakMap<session 对象>` /
|
|
71
|
-
* 无上限),**(d)** `resetOn` 按**状态字段**声明,不是按模块(`tool-bootstrap` 的
|
|
72
|
-
* `stage` 与 `promotion` 两个字段策略相反,是这条的现成反例)。
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
|
-
import { subjectOf } from './predicates.mjs'
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 本文件此前**零 import**(`when` / `do` / `warnOnce` 全由调用方注入,会话态读法经 `ctx`
|
|
79
|
-
* 取得)。唯一新增的依赖是谓词的**输入契约** `subjectOf`:调度层调用 `when` 时必须先把
|
|
80
|
-
* 事件参数表归一为**单个载荷对象**(契约见 `predicates.mjs` 头部),否则除 `names` 外,
|
|
81
|
-
* 各原语在真实通道上都取不到它们要的域——`phase` 曾因此恒为「已晋升」、`count` 恒为 0。
|
|
82
|
-
* 声明路径同源:`actions.mjs` 的 `withWhen` 用同一函数归一。
|
|
18
|
+
* 跨九层的全局调度禁止:官方九层插入点彼此独立,`prepend` 也不得替代 `ctx.tools.guard()`
|
|
19
|
+
* 的单调边界。
|
|
83
20
|
*/
|
|
84
21
|
|
|
85
22
|
/** 声明里的合法位置取值(与 events.ts 的布尔注册策略一一对应)。 */
|
|
86
23
|
export const WATERFALL_POSITIONS = new Set(['default', 'outermost'])
|
|
87
24
|
|
|
88
|
-
/** 动作在 `next()` 之前还是之后执行 —— 影响同通道内的可观测顺序。 */
|
|
89
|
-
export const ACTION_PHASES = new Set(['before-next', 'after-next'])
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 同通道内的稳定排序:`channelOrder` 升序,同值保持**声明序**。
|
|
93
|
-
* 稳定是硬要求:同值声明的相对顺序若随排序实现变化,声明就不可预测。
|
|
94
|
-
*/
|
|
95
|
-
export function orderTriggers(declarations) {
|
|
96
|
-
const list = Array.isArray(declarations) ? declarations : []
|
|
97
|
-
return list
|
|
98
|
-
.map((declaration, index) => ({ declaration, index }))
|
|
99
|
-
.sort((left, right) => {
|
|
100
|
-
const delta = (left.declaration.channelOrder ?? 0) - (right.declaration.channelOrder ?? 0)
|
|
101
|
-
return delta !== 0 ? delta : left.index - right.index
|
|
102
|
-
})
|
|
103
|
-
.map((entry) => entry.declaration)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 把声明的位置字段映射为 Cordis 的注册选项。
|
|
108
|
-
* 只有 `'outermost'` 才 prepend —— 这是**位置**,不是排序;同通道顺序归 `orderTriggers`。
|
|
109
|
-
*/
|
|
110
|
-
export function registrationOptions(declaration) {
|
|
111
|
-
return declaration.waterfallPosition === 'outermost' ? { prepend: true } : {}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 校验一条触发器声明:
|
|
116
|
-
* - 必填:`id`(非空串)、`channel`(官方事件名)、`when`(判定函数)、`do`(动作函数);
|
|
117
|
-
* - `channelOrder` 缺省 0(非负安全整数)、`waterfallPosition` 缺省 `'default'`、
|
|
118
|
-
* `phase` 缺省 `'after-next'`、`degrade` 缺省告警并跳过本触发器。
|
|
119
|
-
* 未知字段一律 fail loud:声明写错键名是最容易被静默忽略的一类错。
|
|
120
|
-
*/
|
|
121
|
-
export function validateTrigger(declaration, plugin) {
|
|
122
|
-
if (declaration === null || typeof declaration !== 'object' || Array.isArray(declaration)) {
|
|
123
|
-
throw new TypeError(`${plugin}: trigger declaration must be an object`)
|
|
124
|
-
}
|
|
125
|
-
const known = new Set(['id', 'channel', 'channelOrder', 'waterfallPosition', 'phase', 'when', 'do', 'degrade', 'state'])
|
|
126
|
-
const unknown = Object.keys(declaration).filter((key) => !known.has(key))
|
|
127
|
-
if (unknown.length > 0) {
|
|
128
|
-
throw new TypeError(`${plugin}: unknown trigger field(s) ${unknown.join(', ')} — allowed: ${[...known].sort().join(', ')}`)
|
|
129
|
-
}
|
|
130
|
-
if (typeof declaration.id !== 'string' || declaration.id.length === 0) {
|
|
131
|
-
throw new TypeError(`${plugin}: trigger.id must be a non-empty string`)
|
|
132
|
-
}
|
|
133
|
-
if (typeof declaration.channel !== 'string' || declaration.channel.length === 0) {
|
|
134
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: channel must be a non-empty event name`)
|
|
135
|
-
}
|
|
136
|
-
if (typeof declaration.when !== 'function') {
|
|
137
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: when must be a predicate function`)
|
|
138
|
-
}
|
|
139
|
-
if (typeof declaration.do !== 'function') {
|
|
140
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: do must be an action function`)
|
|
141
|
-
}
|
|
142
|
-
const channelOrder = declaration.channelOrder ?? 0
|
|
143
|
-
if (!Number.isSafeInteger(channelOrder) || channelOrder < 0) {
|
|
144
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: channelOrder must be a non-negative safe integer`)
|
|
145
|
-
}
|
|
146
|
-
const waterfallPosition = declaration.waterfallPosition ?? 'default'
|
|
147
|
-
if (!WATERFALL_POSITIONS.has(waterfallPosition)) {
|
|
148
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: waterfallPosition must be one of ${[...WATERFALL_POSITIONS].join(', ')}`)
|
|
149
|
-
}
|
|
150
|
-
const phase = declaration.phase ?? 'after-next'
|
|
151
|
-
if (!ACTION_PHASES.has(phase)) {
|
|
152
|
-
throw new TypeError(`${plugin}: trigger ${declaration.id}: phase must be one of ${[...ACTION_PHASES].join(', ')}`)
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
id: declaration.id,
|
|
156
|
-
channel: declaration.channel,
|
|
157
|
-
channelOrder,
|
|
158
|
-
waterfallPosition,
|
|
159
|
-
phase,
|
|
160
|
-
when: declaration.when,
|
|
161
|
-
do: declaration.do,
|
|
162
|
-
degrade: declaration.degrade ?? 'skip',
|
|
163
|
-
state: declaration.state,
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 决策链诊断(T5):把「这次为什么没生效」变成一次可读的输出。
|
|
169
|
-
*
|
|
170
|
-
* 借鉴 dsh-agent-studio 的 `reportFirstDecision`(`apply.ts:238-250`):一次装配看起来
|
|
171
|
-
* 没生效时,原因可能有多种而**结果长得一样** —— 取不到 agent/session、触发器未启用、
|
|
172
|
-
* 谓词未命中、动作被外层门控剥离、判定走了降级。逐项试太慢,所以按顺序记一条链。
|
|
173
|
-
*
|
|
174
|
-
* 约束(PLAN T5 的 security):
|
|
175
|
-
* - **每会话只输出一次**(按 sessionId 去重);
|
|
176
|
-
* - 只记录触发器 id、通道、判定结果、动作是否执行、是否走降级 ——
|
|
177
|
-
* **绝不打印消息正文、变量值或用户内容**;
|
|
178
|
-
* - 默认关闭时零额外日志;日志自身抛错不得影响装配。
|
|
179
|
-
*
|
|
180
|
-
* **flush 由调用方驱动**:引擎无法知道「一轮装配」何时结束(同一通道可能有多个触发器、
|
|
181
|
-
* 跨通道还会继续判定),所以 `mountTriggers` 只 `step`(累积到内存),
|
|
182
|
-
* 由最外层监听器在整轮处理结束时调一次 `flush`。在步与步之间提前 flush 会输出**不完整的链**
|
|
183
|
-
* —— 那正是本文件最初实现踩过的坑(被 T5 自己的断言抓出来)。
|
|
184
|
-
*/
|
|
185
|
-
export function createDecisionLog({ enabled = false, logger, sessionOf } = {}) {
|
|
186
|
-
const reported = new Set()
|
|
187
|
-
const pending = new Map()
|
|
188
|
-
// args 有两种形状:step 收到的是 handler 的完整参数数组,调用方 flush 时可能只给 payload。
|
|
189
|
-
// 两种都要归一到同一个 key —— 否则 step 存 's-1'、flush 查 '<no-session>',链永远输出不出来
|
|
190
|
-
//(这正是 T5 断言抓到的现象:logged.length 恒为 0)。
|
|
191
|
-
const keyOf = (args) => {
|
|
192
|
-
try {
|
|
193
|
-
const values = Array.isArray(args) ? args : [args]
|
|
194
|
-
return String(sessionOf?.(...values) ?? '<no-session>')
|
|
195
|
-
} catch {
|
|
196
|
-
return '<no-session>'
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
/** 记一步(只入内存,不输出)。 */
|
|
201
|
-
step(args, line) {
|
|
202
|
-
if (!enabled) return
|
|
203
|
-
const key = keyOf(args)
|
|
204
|
-
const lines = pending.get(key) ?? []
|
|
205
|
-
lines.push(line)
|
|
206
|
-
pending.set(key, lines)
|
|
207
|
-
},
|
|
208
|
-
/** 该会话的链只输出一次;失败静默 —— 日志不得影响装配。 */
|
|
209
|
-
flush(args) {
|
|
210
|
-
if (!enabled) return
|
|
211
|
-
const key = keyOf(args)
|
|
212
|
-
if (reported.has(key)) return
|
|
213
|
-
const lines = pending.get(key)
|
|
214
|
-
if (lines === undefined || lines.length === 0) return
|
|
215
|
-
reported.add(key)
|
|
216
|
-
try {
|
|
217
|
-
logger?.info?.(`[trigger-decision] session=${key} :: ${lines.join(' | ')}`)
|
|
218
|
-
} catch {
|
|
219
|
-
// 日志失败不得影响装配。
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* 注册一组触发器。
|
|
227
|
-
*
|
|
228
|
-
* 每个触发器在**自己的合法通道**上注册一次;`when` 抛错按 `degrade` 处理
|
|
229
|
-
* (默认 `'skip'`:告警一次并跳过本次,**不吞掉下游异常** —— 只有本触发器自身的
|
|
230
|
-
* 判定失败才降级,`next()` 之后的错误照常向上抛)。
|
|
231
|
-
*
|
|
232
|
-
* @returns disposer:注销本组全部监听器。
|
|
233
|
-
*/
|
|
234
25
|
/**
|
|
235
26
|
* 把 `session/event` 喂给带 `observe(session, event)` 入口的谓词(相位 / 计数两类)。
|
|
236
27
|
*
|
|
237
|
-
*
|
|
238
|
-
* **不接**(返回 `undefined
|
|
28
|
+
* 同一组观察者**共用一条**监听(不按规则各接一条,避免 N 倍监听);没有这类谓词时
|
|
29
|
+
* **不接**(返回 `undefined`,零开销)。观察者自身抛错只告警、不影响其它观察者
|
|
239
30
|
* ——与判定失败的降级纪律一致。
|
|
240
31
|
*
|
|
241
|
-
*
|
|
242
|
-
* `trigger-spec.mjs` 的 `mountDeclarations`(`do` 是动作声明的路径)。
|
|
32
|
+
* 由 `rule-runtime.mjs` 的 `mountRuleSources` 接线:规则条件里的观察者谓词共用这一条监听。
|
|
243
33
|
*
|
|
244
34
|
* @returns disposer,或 `undefined`(本组无观察者)
|
|
245
35
|
*/
|
|
@@ -257,48 +47,3 @@ export function wireTriggerObservers(ctx, triggers, { plugin, warnOnce } = {}) {
|
|
|
257
47
|
}
|
|
258
48
|
})
|
|
259
49
|
}
|
|
260
|
-
|
|
261
|
-
export function mountTriggers(ctx, declarations, { plugin, warnOnce, diagnose } = {}) {
|
|
262
|
-
const warn = typeof warnOnce === 'function' ? warnOnce : () => {}
|
|
263
|
-
const disposers = []
|
|
264
|
-
const ordered = orderTriggers((Array.isArray(declarations) ? declarations : []).map((raw) => validateTrigger(raw, plugin)))
|
|
265
|
-
|
|
266
|
-
// 会话态生命周期:共用 `wireTriggerObservers`(与声明路径同一实现)。
|
|
267
|
-
const observer = wireTriggerObservers(ctx, ordered, { plugin, warnOnce: warn })
|
|
268
|
-
if (observer !== undefined) disposers.push(observer)
|
|
269
|
-
|
|
270
|
-
for (const trigger of ordered) {
|
|
271
|
-
const handler = async (...args) => {
|
|
272
|
-
const next = args[args.length - 1]
|
|
273
|
-
const payload = args.slice(0, -1)
|
|
274
|
-
const result = trigger.phase === 'after-next' ? await next() : undefined
|
|
275
|
-
const pass = () => trigger.phase === 'after-next' ? result : next()
|
|
276
|
-
let decided
|
|
277
|
-
try {
|
|
278
|
-
decided = await trigger.when(subjectOf(trigger.channel, payload, warn))
|
|
279
|
-
} catch (error) {
|
|
280
|
-
// 判定失败只影响本触发器:告警一次并放行下游(expose-all 语义)。
|
|
281
|
-
warn(`${plugin}: trigger ${trigger.id} predicate failed: ${String(error?.message ?? error)}`)
|
|
282
|
-
diagnose?.step(args, `${trigger.id}@${trigger.channel} predicate=error action=skipped`)
|
|
283
|
-
return pass()
|
|
284
|
-
}
|
|
285
|
-
if (decided !== true) {
|
|
286
|
-
diagnose?.step(args, `${trigger.id}@${trigger.channel} predicate=miss action=skipped`)
|
|
287
|
-
return pass()
|
|
288
|
-
}
|
|
289
|
-
diagnose?.step(args, `${trigger.id}@${trigger.channel} predicate=hit phase=${trigger.phase} action=ran`)
|
|
290
|
-
if (trigger.phase === 'before-next') {
|
|
291
|
-
// 返回值参与瀑布:裁决类动作在 `next()` **之前**给出终局(deny/ask),
|
|
292
|
-
// 不返回值即「无话可说」,交给下游(与「只 guard 自身逻辑」一致)。
|
|
293
|
-
const early = await trigger.do(...payload)
|
|
294
|
-
return early === undefined ? next() : early
|
|
295
|
-
}
|
|
296
|
-
// 返回值同样参与瀑布:这正是裁决类动作(deny / ask / replace / block)的出口
|
|
297
|
-
// ——丢弃它会让「命中却拦不住」,是本文件被 trigger-rebuild 对拍抓到的缺陷。
|
|
298
|
-
const overridden = await trigger.do(...payload, result)
|
|
299
|
-
return overridden === undefined ? result : overridden
|
|
300
|
-
}
|
|
301
|
-
disposers.push(ctx.on(trigger.channel, handler, registrationOptions(trigger)))
|
|
302
|
-
}
|
|
303
|
-
return () => { for (const dispose of disposers) dispose() }
|
|
304
|
-
}
|
|
@@ -64,46 +64,47 @@ function plainValue(source, onError) {
|
|
|
64
64
|
}
|
|
65
65
|
if (badChar)
|
|
66
66
|
onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
|
|
67
|
-
return
|
|
67
|
+
return unfoldLines(source);
|
|
68
68
|
}
|
|
69
69
|
function singleQuotedValue(source, onError) {
|
|
70
70
|
if (source[source.length - 1] !== "'" || source.length === 1)
|
|
71
71
|
onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
|
|
72
|
-
return
|
|
72
|
+
return unfoldLines(source.slice(1, -1)).replace(/''/g, "'");
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function unfoldLines(source) {
|
|
75
|
+
const line = /(.*?)\r?\n/sy;
|
|
76
|
+
let match = line.exec(source);
|
|
77
|
+
if (!match)
|
|
78
|
+
return source;
|
|
75
79
|
/**
|
|
76
|
-
* The negative
|
|
80
|
+
* The negative lookbehinds in these RegExps are to
|
|
77
81
|
* prevent causing a polynomial search time in certain cases.
|
|
78
82
|
*
|
|
79
|
-
* The try-catch is for Safari
|
|
83
|
+
* The try-catch is for Safari < 16.4 and other old browsers:
|
|
80
84
|
* https://caniuse.com/js-regexp-lookbehind
|
|
81
85
|
*/
|
|
82
|
-
let
|
|
86
|
+
let trimEnd, trimBoth;
|
|
83
87
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
trimEnd = new RegExp('(?<![ \t])[ \t]+$');
|
|
89
|
+
trimBoth = new RegExp('^[ \t]+|(?<![ \t])[ \t]+$', 'g');
|
|
86
90
|
}
|
|
87
91
|
catch {
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
trimEnd = /[ \t]+$/;
|
|
93
|
+
trimBoth = /^[ \t]+|[ \t]+$/g;
|
|
90
94
|
}
|
|
91
|
-
let
|
|
92
|
-
if (!match)
|
|
93
|
-
return source;
|
|
94
|
-
let res = match[1];
|
|
95
|
+
let res = match[1].replace(trimEnd, '');
|
|
95
96
|
let sep = ' ';
|
|
96
|
-
let pos =
|
|
97
|
-
line.lastIndex = pos;
|
|
97
|
+
let pos = line.lastIndex;
|
|
98
98
|
while ((match = line.exec(source))) {
|
|
99
|
-
|
|
99
|
+
const lm = match[1].replace(trimBoth, '');
|
|
100
|
+
if (lm === '') {
|
|
100
101
|
if (sep === '\n')
|
|
101
102
|
res += sep;
|
|
102
103
|
else
|
|
103
104
|
sep = '\n';
|
|
104
105
|
}
|
|
105
106
|
else {
|
|
106
|
-
res += sep +
|
|
107
|
+
res += sep + lm;
|
|
107
108
|
sep = ' ';
|
|
108
109
|
}
|
|
109
110
|
pos = line.lastIndex;
|
|
@@ -43,38 +43,40 @@ class Alias extends NodeBase {
|
|
|
43
43
|
if (node.anchor === this.source)
|
|
44
44
|
found = node;
|
|
45
45
|
}
|
|
46
|
+
if (found && ctx) {
|
|
47
|
+
const { anchors, doc, maxAliasCount } = ctx;
|
|
48
|
+
let data = anchors.get(found);
|
|
49
|
+
if (!data) {
|
|
50
|
+
// Resolve anchors for Node.prototype.toJS()
|
|
51
|
+
toJS(found, null, ctx);
|
|
52
|
+
data = anchors.get(found);
|
|
53
|
+
}
|
|
54
|
+
/* istanbul ignore if */
|
|
55
|
+
if (data?.res === undefined) {
|
|
56
|
+
const msg = 'This should not happen: Alias anchor was not resolved?';
|
|
57
|
+
throw new ReferenceError(msg);
|
|
58
|
+
}
|
|
59
|
+
if (maxAliasCount >= 0) {
|
|
60
|
+
data.count += 1;
|
|
61
|
+
if (data.aliasCount === 0)
|
|
62
|
+
data.aliasCount = getAliasCount(doc, found, anchors);
|
|
63
|
+
if (data.count * data.aliasCount > maxAliasCount) {
|
|
64
|
+
const msg = 'Excessive alias count indicates a resource exhaustion attack';
|
|
65
|
+
throw new ReferenceError(msg);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
46
69
|
return found;
|
|
47
70
|
}
|
|
48
71
|
toJSON(_arg, ctx) {
|
|
49
72
|
if (!ctx)
|
|
50
73
|
return { source: this.source };
|
|
51
|
-
const
|
|
52
|
-
const source = this.resolve(doc, ctx);
|
|
74
|
+
const source = this.resolve(ctx.doc, ctx);
|
|
53
75
|
if (!source) {
|
|
54
76
|
const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
|
|
55
77
|
throw new ReferenceError(msg);
|
|
56
78
|
}
|
|
57
|
-
|
|
58
|
-
if (!data) {
|
|
59
|
-
// Resolve anchors for Node.prototype.toJS()
|
|
60
|
-
toJS(source, null, ctx);
|
|
61
|
-
data = anchors.get(source);
|
|
62
|
-
}
|
|
63
|
-
/* istanbul ignore if */
|
|
64
|
-
if (data?.res === undefined) {
|
|
65
|
-
const msg = 'This should not happen: Alias anchor was not resolved?';
|
|
66
|
-
throw new ReferenceError(msg);
|
|
67
|
-
}
|
|
68
|
-
if (maxAliasCount >= 0) {
|
|
69
|
-
data.count += 1;
|
|
70
|
-
if (data.aliasCount === 0)
|
|
71
|
-
data.aliasCount = getAliasCount(doc, source, anchors);
|
|
72
|
-
if (data.count * data.aliasCount > maxAliasCount) {
|
|
73
|
-
const msg = 'Excessive alias count indicates a resource exhaustion attack';
|
|
74
|
-
throw new ReferenceError(msg);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return data.res;
|
|
79
|
+
return ctx.anchors.get(source).res;
|
|
78
80
|
}
|
|
79
81
|
toString(ctx, _onComment, _onChompKeep) {
|
|
80
82
|
const src = `*${this.source}`;
|