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
package/engine/fillers.mjs
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { getService, parseToolNames } from './shared.mjs'
|
|
7
|
+
import { machinePaths } from './machine-paths.mjs'
|
|
8
|
+
import { SAFE_ENV_KEYS } from './env-safety.mjs'
|
|
7
9
|
import { createInstructionHintResolver } from './instruction-hint.mjs'
|
|
10
|
+
import { DISCOVERY_SERVICE } from './service-names.mjs'
|
|
8
11
|
|
|
9
12
|
const name = 'prompt-config-engine'
|
|
10
13
|
|
|
@@ -13,30 +16,57 @@ function explicitTemplate(config) {
|
|
|
13
16
|
return texts.length > 0 ? texts.join('\n\n') : typeof config.params?.text === 'string' ? config.params.text : ''
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/** 只读同规则工具面声明;文案、条件和投递仍归普通注入动作。 */
|
|
20
|
+
function createToolCatalogResolver(config) {
|
|
21
|
+
if (config.params?.templateEvaluation === true) throw new TypeError('tool-catalog is read-only and does not support templateEvaluation')
|
|
22
|
+
if (typeof config.params?.action !== 'string' || config.params.action.trim().length === 0) {
|
|
23
|
+
throw new TypeError('tool-catalog requires params.action referencing a tool-visibility action')
|
|
24
|
+
}
|
|
25
|
+
const text = explicitTemplate(config)
|
|
26
|
+
return ({ ctx, agent }) => {
|
|
27
|
+
if (text.trim().length === 0) return null
|
|
28
|
+
const source = config.toolCatalogSource
|
|
29
|
+
if (source === undefined) throw new TypeError('tool-catalog requires a same-rule tool-visibility action')
|
|
30
|
+
const deny = new Set(source.deny ?? [])
|
|
31
|
+
const resident = [...new Set(source.allow)].filter(tool => !deny.has(tool))
|
|
32
|
+
const adapter = source.discovery === undefined ? undefined
|
|
33
|
+
: getService(ctx, DISCOVERY_SERVICE)?.get?.(agent, source.discovery.kind)
|
|
34
|
+
if (source.discovery !== undefined && typeof adapter?.tool !== 'string') {
|
|
35
|
+
throw new Error(`tool-catalog discovery adapter ${source.discovery.kind} is unavailable`)
|
|
36
|
+
}
|
|
37
|
+
return { text, variables: { RESIDENT_TOOLS: resident.join(', '), DISCOVERY_TOOL: adapter?.tool ?? '' } }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
/**
|
|
17
42
|
* env-facts:机器事实动态填充器。
|
|
18
43
|
* params.envKeys 逗号分隔环境变量白名单:取值归模板/预设,留空 = 不附加宿主环境
|
|
19
|
-
* 变量(CWD/WORKSPACE
|
|
44
|
+
* 变量(CWD/WORKSPACE 仍由会话事实提供)。白名单外的键在构造期拒绝,不读取任意 process.env。
|
|
20
45
|
* 只提供 facts 变量;正文必须由 text/params.text 显式模板声明。
|
|
21
46
|
*/
|
|
22
47
|
function createEnvFactsResolver(config) {
|
|
23
48
|
const template = explicitTemplate(config)
|
|
24
|
-
if (template.trim().length === 0) return () => null
|
|
25
49
|
const keys = parseToolNames(typeof config.params?.envKeys === 'string' ? config.params.envKeys : '')
|
|
50
|
+
const rejected = keys.filter((key) => !SAFE_ENV_KEYS.has(key))
|
|
51
|
+
if (rejected.length > 0) {
|
|
52
|
+
throw new TypeError(`${name}: env-facts envKeys not in SAFE_ENV_KEYS: ${rejected.join(', ')} — safe keys: ${[...SAFE_ENV_KEYS].join(', ')}`)
|
|
53
|
+
}
|
|
54
|
+
if (template.trim().length === 0) return () => null
|
|
26
55
|
return ({ agent }) => {
|
|
27
56
|
const session = agent?.session
|
|
28
|
-
|
|
29
|
-
const
|
|
57
|
+
// 路径类事实取唯一来源;白名单(envKeys)与「空串不算有值」是本处的策略,不外包。
|
|
58
|
+
const paths = machinePaths(session)
|
|
30
59
|
const facts = {}
|
|
31
60
|
for (const key of keys) {
|
|
32
61
|
let value
|
|
33
|
-
if (key === 'CWD'
|
|
34
|
-
else if (key === '
|
|
62
|
+
if (key === 'CWD') value = paths.CWD
|
|
63
|
+
else if (key === 'WORKSPACE') value = paths.WORKSPACE
|
|
64
|
+
else if (key === 'DSH_HOME') value = paths.DSH_HOME
|
|
35
65
|
else value = process.env[key]
|
|
36
66
|
if (typeof value === 'string' && value.length > 0) facts[key] = value
|
|
37
67
|
}
|
|
38
|
-
facts.WORKSPACE =
|
|
39
|
-
facts.CWD =
|
|
68
|
+
facts.WORKSPACE = paths.WORKSPACE
|
|
69
|
+
facts.CWD = paths.CWD
|
|
40
70
|
const envFacts = Object.entries(facts).map(([key, value]) => `- ${key}=${value}`).join('\n')
|
|
41
71
|
return { text: template, variables: { ...facts, ENV_FACTS: envFacts } }
|
|
42
72
|
}
|
|
@@ -138,6 +168,7 @@ export const FILLERS = {
|
|
|
138
168
|
'instruction-hint': (config) => createInstructionHintResolver(config),
|
|
139
169
|
'env-facts': (config) => createEnvFactsResolver(config),
|
|
140
170
|
'skill-catalog': (config) => createSkillCatalogResolver(config),
|
|
171
|
+
'tool-catalog': (config) => createToolCatalogResolver(config),
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
export function createPlaceholderResolver(config) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「固定注册效果」判据的唯一实现:只有 `system-section` 层的 `complete` /
|
|
3
|
+
* `suppressRuntimeContext` 是注册期效果(无逐轮求值时机)。其他层写同名键是死配置。
|
|
4
|
+
* `layer` 是声明缺省层——config 可能省略 layer,回退规则与
|
|
5
|
+
* `injectionConfigSpec` 的 `config.layer ?? rule.layer ?? 'pre-step'` 一致。
|
|
6
|
+
*/
|
|
7
|
+
import { record } from './record.mjs'
|
|
8
|
+
|
|
9
|
+
export function isFixedRegistration(config, layer) {
|
|
10
|
+
return record(config) && (config.layer ?? layer) === 'system-section'
|
|
11
|
+
&& (config.params?.complete === true || config.params?.suppressRuntimeContext === true)
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* history — 会话历史的**唯一**读取入口,给出两个互不替代的视图。
|
|
3
|
+
*
|
|
4
|
+
* 1. `currentEvents()`:**当前模型可见上下文**,即 surface 有序节点(`session.surface.nodes`)指向的事件。
|
|
5
|
+
* 压缩 / 位置替换遮蔽掉的节点不在其中,这正是「当前上下文」与「本会话曾经发生过」的差别。
|
|
6
|
+
* 2. `historyEvents()`:**完整历史**,按 log 序返回本会话全部 durable 事件,供「必须看全量」的消费点。
|
|
7
|
+
*
|
|
8
|
+
* 两个视图都是纯函数:每次都读当场快照,不缓存、不跨会话保存任何状态。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 完整历史:`snapshotEvents()` 的全量不可变快照。
|
|
13
|
+
* 宿主缺该接口(或返回非数组)时按空日志降级,不读取旧 `events` 数组。
|
|
14
|
+
*/
|
|
15
|
+
export function historyEvents(session) {
|
|
16
|
+
const snapshot = session?.snapshotEvents?.()
|
|
17
|
+
return Array.isArray(snapshot) ? snapshot : []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 一次工具实际调用的 durable 事件:顶层 `tool/call`,以及 PTC `run_code` 内的
|
|
22
|
+
* `tool/ptc-dispatch`。只观察「执行了哪个工具」时读两者;观察「模型动作 / 相位晋升」
|
|
23
|
+
* 仍只看顶层 `tool/call`,不要把本集合直接替换过去。
|
|
24
|
+
*/
|
|
25
|
+
export const TOOL_INVOCATION_EVENT_TYPES = Object.freeze(['tool/call', 'tool/ptc-dispatch'])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 当前上下文是否**真的**受限:surface 在场(`nodes` 是数组)且每个节点都指向日志里的事件。
|
|
29
|
+
* 判据只有这一处 —— `currentEvents()` 的降级与消费点的视图口径都由它回答,避免两边静默分叉。
|
|
30
|
+
* 空 `nodes` 是受限(可见上下文为空),不是降级。
|
|
31
|
+
* `events` 是调用方已经取好的 `historyEvents(session)`(可选,省掉一次快照读)。
|
|
32
|
+
*/
|
|
33
|
+
export function viewRestricted(session, events = historyEvents(session)) {
|
|
34
|
+
const nodes = session?.surface?.nodes
|
|
35
|
+
return Array.isArray(nodes) && nodes.every(seq => events[seq] !== undefined)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 当前模型可见上下文:按 surface 节点序取出对应事件。
|
|
40
|
+
*
|
|
41
|
+
* `surface.nodes` 存的是 log seq,而 seq 就是 log 下标(`snapshotEvents()` 返回 [0, seq) 的切片),
|
|
42
|
+
* 因此一次快照加索引即可映射,无需折叠、无需已 deprecated 的 `eventAt()`。
|
|
43
|
+
*
|
|
44
|
+
* 降级:surface 缺失、`nodes` 非数组,或任何节点越界(seq >= 日志长度)时退回完整历史 ——
|
|
45
|
+
* 「宁可多看见,不少看见」,且等价于迁移前的旧行为。空 `nodes` 是合法状态(新会话尚无消息),
|
|
46
|
+
* 按「当前上下文为空」返回空数组,**不**降级。
|
|
47
|
+
* shortcut: 与 `deriveMessages()` 的差别是这里不跳过派生出 null 的空内容节点(判据读事件而非正文);
|
|
48
|
+
* 需要正文的消费点出现时再按 `deriveMessages()` 收窄。
|
|
49
|
+
* 同一 seq 可在 `nodes` 中出现多次(位置替换后同一事件占多个位置),按条数计数的消费点需自行按 seq 去重。
|
|
50
|
+
*/
|
|
51
|
+
export function currentEvents(session) {
|
|
52
|
+
const events = historyEvents(session)
|
|
53
|
+
if (!viewRestricted(session, events)) return events
|
|
54
|
+
return session.surface.nodes.map(seq => events[seq])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* surface 只承载**消息类**事件——`system/message`、`developer/message`、`user/message`、
|
|
59
|
+
* `assistant/message`、`tool/result`(真值源:宿主 `packages/core/session/src/surface.ts` 的
|
|
60
|
+
* `SURFACE_EVENT_TYPES`)。非该集合的事件(`tool/call`、`turn/start`、`compaction/end`)永远不是
|
|
61
|
+
* surface 节点,按 `currentEvents()` 读它们只会读到空 —— 它们只能按完整历史读。
|
|
62
|
+
* shortcut: 宿主扩大 `SURFACE_EVENT_TYPES` 时这里要同步,否则新的消息类事件会被当成 log-only
|
|
63
|
+
* (count 多计、session 恒不命中)。
|
|
64
|
+
*/
|
|
65
|
+
export const SURFACE_MESSAGE_TYPES = new Set([
|
|
66
|
+
'system/message',
|
|
67
|
+
'developer/message',
|
|
68
|
+
'user/message',
|
|
69
|
+
'assistant/message',
|
|
70
|
+
'tool/result',
|
|
71
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `instruction-blocks.mjs` 的手写声明:只声明 `src/` 消费的导出,形状按实现写实。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** 一个官方指令标题块;`index` 是标题行首在正文中的位置。 */
|
|
6
|
+
export interface InstructionBlock {
|
|
7
|
+
action: 'set' | 'replace' | 'remove'
|
|
8
|
+
path: string
|
|
9
|
+
index: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 解析系统提醒正文里的 4 种官方指令标题。 */
|
|
13
|
+
export declare function instructionBlocks(text: string): InstructionBlock[]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 官方系统提醒正文里的指令文件标题:filter 分段与 hint 路径提取共用的唯一解析。
|
|
3
|
+
* 只认这 4 种行首标题;path 原样返回(调用方各自 trim/匹配),index 是标题行首位置。
|
|
4
|
+
*/
|
|
5
|
+
const ACTIONS = {
|
|
6
|
+
'Instructions from:': 'set',
|
|
7
|
+
'Additional instructions from:': 'set',
|
|
8
|
+
'Updated instructions from:': 'replace',
|
|
9
|
+
'Instructions removed:': 'remove',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HEADING_RE = new RegExp('^(' + Object.keys(ACTIONS).join('|') + ') ([^\r\n]+)\r?$', 'gm')
|
|
13
|
+
|
|
14
|
+
export function instructionBlocks(text) {
|
|
15
|
+
if (typeof text !== 'string' || text.length === 0) return []
|
|
16
|
+
return [...text.matchAll(HEADING_RE)].map(match => ({
|
|
17
|
+
action: ACTIONS[match[1]],
|
|
18
|
+
path: match[2],
|
|
19
|
+
index: match.index,
|
|
20
|
+
}))
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** 宿主依赖面:只声明 src/ 用到的符号,引擎全貌不在此(见 docs/engine-reuse.md 的类型边界)。 */
|
|
2
|
+
export declare const PROJECT_INSTRUCTION_CANDIDATES: readonly string[]
|
|
3
|
+
export declare const USER_GLOBAL_INSTRUCTION_FILE: string
|
|
4
|
+
export declare function ancestorDirs(cwd: string): string[]
|
|
5
|
+
export declare function instructionPlan(cwd: string, markerDirs: ReadonlySet<string>): { root: string; dirs: string[] }
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instruction-files — 指令文件探测的判据。
|
|
3
|
+
*
|
|
4
|
+
* 探测判据(候选清单、项目根、目录链)是纯函数;读盘由两个消费者各自完成——
|
|
5
|
+
* 引擎用本文件提供的异步执行器(collectInstructionFiles),宿主用 node:fs 同步 +
|
|
6
|
+
* 信任根校验。本文件随 engine/ 一起复制;行 instruction-hint 只消费这里的执行器。
|
|
7
|
+
*/
|
|
8
|
+
import { dirname, join } from 'node:path'
|
|
9
|
+
import { machinePaths } from './machine-paths.mjs'
|
|
10
|
+
|
|
11
|
+
/** 项目目录链中按优先级探测的指令文件名。 */
|
|
12
|
+
export const PROJECT_INSTRUCTION_CANDIDATES = ['AGENTS.md', 'CLAUDE.md', 'AGENTS.local.md', 'CLAUDE.local.md']
|
|
13
|
+
export const USER_GLOBAL_INSTRUCTION_FILE = 'AGENTS.md'
|
|
14
|
+
|
|
15
|
+
/** 引擎侧的项目根标记;宿主有自己的集合(例如只要 .git),因此这里不做统一。 */
|
|
16
|
+
export const ENGINE_PROJECT_ROOT_MARKERS = ['.git', '.hg', '.svn']
|
|
17
|
+
|
|
18
|
+
/** cwd 到文件系统根:含自身,父目录等于自身时停止。 */
|
|
19
|
+
export function ancestorDirs(cwd) {
|
|
20
|
+
const dirs = [cwd]
|
|
21
|
+
for (;;) {
|
|
22
|
+
const parent = dirname(dirs[dirs.length - 1])
|
|
23
|
+
if (parent === dirs[dirs.length - 1]) return dirs
|
|
24
|
+
dirs.push(parent)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 最近的含 marker 祖先;没有 marker 或 marker 不在祖先链上时回落 cwd。 */
|
|
29
|
+
function resolveProjectRoot(cwd, markerDirs) {
|
|
30
|
+
for (const dir of ancestorDirs(cwd)) if (markerDirs.has(dir)) return dir
|
|
31
|
+
return cwd
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 项目根到 cwd 的目录链(广到窄);root 必来自 ancestorDirs(cwd),因此一定在链上。 */
|
|
35
|
+
function instructionDirs(cwd, root) {
|
|
36
|
+
const dirs = ancestorDirs(cwd)
|
|
37
|
+
return dirs.slice(0, dirs.indexOf(root) + 1).reverse()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 一次给出项目根与目录链(广到窄);要「近者优先」的注入顺序由调用方自行反转。
|
|
42
|
+
* markerDirs 的字符串必须取自 ancestorDirs(同一套规范化,UNC 共享名根带尾分隔符)。
|
|
43
|
+
*/
|
|
44
|
+
export function instructionPlan(cwd, markerDirs) {
|
|
45
|
+
const root = resolveProjectRoot(cwd, markerDirs)
|
|
46
|
+
return { root, dirs: instructionDirs(cwd, root) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 列出某目录里的候选文件(按候选顺序);探测失败逐条跳过,不阻断会话。 */
|
|
50
|
+
async function presentInDir(fs, dir, candidates, signal) {
|
|
51
|
+
const found = []
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
try {
|
|
54
|
+
const target = await fs.resolve(join(dir, candidate), { cwd: dir, signal })
|
|
55
|
+
const info = await fs.stat(target, signal)
|
|
56
|
+
if (info !== undefined && info.type === 'file') found.push(candidate)
|
|
57
|
+
} catch {
|
|
58
|
+
// 不存在或不可读——跳过。
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return found
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 探测 cwd 到项目根的完整目录链,以及 DSH_HOME 下的用户级指令文件。
|
|
66
|
+
* 计划与读盘分离:先由 ancestorDirs 得到祖先,再异步探 marker,最后交给纯函数选根。
|
|
67
|
+
*/
|
|
68
|
+
export async function collectInstructionFiles(fs, cwd, signal, home) {
|
|
69
|
+
const markerDirs = new Set()
|
|
70
|
+
for (const dir of ancestorDirs(cwd)) {
|
|
71
|
+
for (const marker of ENGINE_PROJECT_ROOT_MARKERS) {
|
|
72
|
+
try {
|
|
73
|
+
const target = await fs.resolve(join(dir, marker), { cwd: dir, signal })
|
|
74
|
+
if (await fs.stat(target, signal) !== undefined) { markerDirs.add(dir); break }
|
|
75
|
+
} catch {
|
|
76
|
+
// 标记不存在——继续看上一层。
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 最近 marker 即项目根:再向上的探测不会改变 instructionPlan 的结果。
|
|
80
|
+
if (markerDirs.has(dir)) break
|
|
81
|
+
}
|
|
82
|
+
const plan = instructionPlan(cwd, markerDirs)
|
|
83
|
+
const projectFiles = []
|
|
84
|
+
// 注入顺序要求近者优先:cwd 先于项目根;根目录下的文件用裸候选名。
|
|
85
|
+
for (const dir of [...plan.dirs].reverse()) {
|
|
86
|
+
for (const candidate of await presentInDir(fs, dir, PROJECT_INSTRUCTION_CANDIDATES, signal)) {
|
|
87
|
+
projectFiles.push(dir === plan.root ? candidate : join(dir, candidate))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const userGlobalFiles = []
|
|
91
|
+
// 路径类事实取唯一来源;本处需要 undefined 表示「没有可探测的全局目录」,故把空串折回缺省。
|
|
92
|
+
const dshHome = home ?? (machinePaths().DSH_HOME || undefined)
|
|
93
|
+
if (dshHome !== undefined) {
|
|
94
|
+
userGlobalFiles.push(...await presentInDir(fs, dshHome, [USER_GLOBAL_INSTRUCTION_FILE], signal))
|
|
95
|
+
}
|
|
96
|
+
return { root: plan.root, projectFiles, userGlobalFiles, userGlobalHome: dshHome }
|
|
97
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* instruction-hint —
|
|
2
|
+
* instruction-hint — 通用内置指令文件提示引擎(可挂载的行 + 消息构造)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 探测(候选清单、项目根、目录链)归 engine/instruction-files.mjs,本模块只消费它的
|
|
5
|
+
* 异步执行器。两个入口:
|
|
5
6
|
* - prompt-config 的 strategy=placeholder + fill=instruction-hint
|
|
6
|
-
*
|
|
7
|
+
* 直接使用本模块的消息构造与 instruction-files 的探测结果;
|
|
7
8
|
* - plugin 形态(见文件末尾 apply):**挂本行 = 开启**「晋升后把
|
|
8
9
|
* agent-instructions 全文替换为一次性 hint,后续全文消息丢弃」的转换。
|
|
9
10
|
*
|
|
@@ -19,97 +20,30 @@
|
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import { randomUUID } from 'node:crypto'
|
|
22
|
-
import { readFileSync } from 'node:fs'
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
24
|
+
import { basename, join, resolve } from 'node:path'
|
|
23
25
|
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
26
|
+
import {
|
|
27
|
+
ancestorDirs,
|
|
28
|
+
collectInstructionFiles,
|
|
29
|
+
ENGINE_PROJECT_ROOT_MARKERS,
|
|
30
|
+
PROJECT_INSTRUCTION_CANDIDATES,
|
|
31
|
+
USER_GLOBAL_INSTRUCTION_FILE,
|
|
32
|
+
} from './instruction-files.mjs'
|
|
24
33
|
import { interpolateStatic } from './interpolate.mjs'
|
|
34
|
+
import { machinePaths } from './machine-paths.mjs'
|
|
35
|
+
import { resolveRealWithin } from './path-safety.mjs'
|
|
25
36
|
import { booleanOption, createWarnOnce, getService, parsePromoteOn, validateConfig } from './shared.mjs'
|
|
37
|
+
import { PRE_STEP_COORDINATOR_SERVICE } from './service-names.mjs'
|
|
38
|
+
import { instructionBlocks } from './instruction-blocks.mjs'
|
|
26
39
|
|
|
27
40
|
export const name = 'instruction-hint'
|
|
28
41
|
|
|
29
42
|
/** 无 inject:监听器只在使用时触碰 ctx 服务(同 tool-bootstrap / context-gate 纪律)。 */
|
|
30
43
|
export const inject = []
|
|
31
44
|
|
|
32
|
-
/** 项目目录链中按优先级探测的指令文件名。 */
|
|
33
|
-
export const PROJECT_INSTRUCTION_CANDIDATES = ['AGENTS.md', 'CLAUDE.md', 'AGENTS.local.md', 'CLAUDE.local.md']
|
|
34
|
-
export const USER_GLOBAL_INSTRUCTION_CANDIDATE = 'AGENTS.md'
|
|
35
45
|
const templateOf = (templates, key) => typeof templates?.[key] === 'string' ? templates[key] : ''
|
|
36
46
|
|
|
37
|
-
/** Join one path segment onto a directory (platform-agnostic string join). */
|
|
38
|
-
export function joinPath(dir, segment) {
|
|
39
|
-
if (dir.endsWith('/') || dir.endsWith('\\')) return dir + segment
|
|
40
|
-
const sep = dir.includes('\\') ? '\\' : '/'
|
|
41
|
-
return dir + sep + segment
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Parent of an absolute Windows or POSIX path. */
|
|
45
|
-
export function parentPath(path) {
|
|
46
|
-
const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
47
|
-
if (idx <= 0) return path
|
|
48
|
-
const parent = path.slice(0, idx)
|
|
49
|
-
return parent.length === 0 ? path : parent
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Find the project root: first ancestor containing any root marker. */
|
|
53
|
-
export async function findProjectRoot(fs, cwd, signal) {
|
|
54
|
-
let current = cwd
|
|
55
|
-
for (;;) {
|
|
56
|
-
for (const marker of ['.git', '.hg', '.svn']) {
|
|
57
|
-
try {
|
|
58
|
-
const target = await fs.resolve(joinPath(current, marker), { cwd, signal })
|
|
59
|
-
const info = await fs.stat(target, signal)
|
|
60
|
-
if (info !== undefined) return current
|
|
61
|
-
} catch {
|
|
62
|
-
// Probe failure = marker absent; continue.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const parent = parentPath(current)
|
|
66
|
-
if (parent === current || parent.length === 0) return cwd
|
|
67
|
-
current = parent
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** List instruction files present in one directory. */
|
|
72
|
-
export async function presentInDir(fs, dir, candidates, signal) {
|
|
73
|
-
const found = []
|
|
74
|
-
for (const candidate of candidates) {
|
|
75
|
-
try {
|
|
76
|
-
const target = await fs.resolve(joinPath(dir, candidate), { cwd: dir, signal })
|
|
77
|
-
const info = await fs.stat(target, signal)
|
|
78
|
-
if (info !== undefined && info.type === 'file') found.push(candidate)
|
|
79
|
-
} catch {
|
|
80
|
-
// Absent or unreadable — skip.
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return found
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 探测 cwd 到项目根的完整目录链,以及 DSH_HOME 下的用户级 AGENTS.md。
|
|
88
|
-
* home 未传时使用当前宿主的 DSH_HOME/USERPROFILE 推导值。
|
|
89
|
-
*/
|
|
90
|
-
export async function collectInstructionFiles(fs, cwd, signal, home) {
|
|
91
|
-
const root = await findProjectRoot(fs, cwd, signal)
|
|
92
|
-
const projectFiles = []
|
|
93
|
-
let probed = cwd
|
|
94
|
-
for (;;) {
|
|
95
|
-
for (const candidate of await presentInDir(fs, probed, PROJECT_INSTRUCTION_CANDIDATES, signal)) {
|
|
96
|
-
projectFiles.push(probed === root ? candidate : joinPath(probed, candidate))
|
|
97
|
-
}
|
|
98
|
-
if (probed === root) break
|
|
99
|
-
const parent = parentPath(probed)
|
|
100
|
-
if (parent === probed || parent.length === 0) break
|
|
101
|
-
probed = parent
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const userGlobalFiles = []
|
|
105
|
-
const dshHome = home ?? process.env.DSH_HOME
|
|
106
|
-
?? (process.env.USERPROFILE ? `${process.env.USERPROFILE}\\.dsh` : undefined)
|
|
107
|
-
if (dshHome !== undefined) {
|
|
108
|
-
userGlobalFiles.push(...await presentInDir(fs, dshHome, [USER_GLOBAL_INSTRUCTION_CANDIDATE], signal))
|
|
109
|
-
}
|
|
110
|
-
return { root, projectFiles, userGlobalFiles, userGlobalHome: dshHome }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
47
|
/**
|
|
114
48
|
* 用显式模板格式化探测结果;无文件或无正文模板时返回空字符串。
|
|
115
49
|
* scope 限定只报告某一来源:all(默认)/ global($DSH_HOME/AGENTS.md)/ project(cwd→项目根链)。
|
|
@@ -117,14 +51,14 @@ export async function collectInstructionFiles(fs, cwd, signal, home) {
|
|
|
117
51
|
export function buildInstructionHintText({ root, projectFiles = [], userGlobalFiles = [], userGlobalHome } = {}, scope = 'all', templates = {}) {
|
|
118
52
|
const sections = []
|
|
119
53
|
if (scope !== 'global' && projectFiles.length > 0) {
|
|
120
|
-
const text = interpolateStatic(templateOf(templates, 'projectTemplate'), { FILES: projectFiles.join(', '), ROOT: root ?? '' })
|
|
54
|
+
const text = interpolateStatic(templateOf(templates, 'projectTemplate'), { FILES: projectFiles.join(', '), ROOT: root ?? '' }, undefined, 'projectTemplate')
|
|
121
55
|
if (text.trim().length > 0) sections.push(text)
|
|
122
56
|
}
|
|
123
57
|
if (scope !== 'project' && userGlobalFiles.length > 0) {
|
|
124
58
|
const paths = typeof userGlobalHome === 'string' && userGlobalHome.length > 0
|
|
125
|
-
? userGlobalFiles.map((name) =>
|
|
59
|
+
? userGlobalFiles.map((name) => join(userGlobalHome, name))
|
|
126
60
|
: userGlobalFiles
|
|
127
|
-
const text = interpolateStatic(templateOf(templates, 'globalTemplate'), { FILES: paths.join(', '), ROOT: root ?? '' })
|
|
61
|
+
const text = interpolateStatic(templateOf(templates, 'globalTemplate'), { FILES: paths.join(', '), ROOT: root ?? '' }, undefined, 'globalTemplate')
|
|
128
62
|
if (text.trim().length > 0) sections.push(text)
|
|
129
63
|
}
|
|
130
64
|
if (sections.length === 0) return ''
|
|
@@ -139,13 +73,54 @@ function hintScope(value) {
|
|
|
139
73
|
return typeof value === 'string' && HINT_SCOPES.has(value) ? value : 'all'
|
|
140
74
|
}
|
|
141
75
|
|
|
76
|
+
/** params.file 的白名单:与探测清单同源,只允许指令文件候选名。 */
|
|
77
|
+
const INSTRUCTION_FILE_NAMES = new Set([...PROJECT_INSTRUCTION_CANDIDATES, USER_GLOBAL_INSTRUCTION_FILE])
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 指令文件的信任根:cwd 的目录链,向上到最近的项目根为止(与探测范围一致);
|
|
81
|
+
* 没有 marker 时只有 cwd。不延伸到项目根之上——那会把同一卷上的任意路径都算进来。
|
|
82
|
+
*/
|
|
83
|
+
function instructionRoots(cwd) {
|
|
84
|
+
const dirs = ancestorDirs(cwd)
|
|
85
|
+
for (const [index, dir] of dirs.entries()) {
|
|
86
|
+
if (ENGINE_PROJECT_ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dirs.slice(0, index + 1)
|
|
87
|
+
}
|
|
88
|
+
return [cwd]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 文件即真相:每次注入都重读绑定文件,外部编辑立即生效。
|
|
93
|
+
* 信任根 = cwd 的目录链到最近项目根(探测同一范围,故项目根 AGENTS.md 合法)+ DSH_HOME。
|
|
94
|
+
* 越界、链接逃逸与非法名字直接抛出(executor 会 warn/skip 该配置);
|
|
95
|
+
* 目标或其父目录不存在时按缺失文件返回空串。
|
|
96
|
+
*/
|
|
97
|
+
function readBoundInstructionFile(file, cwd) {
|
|
98
|
+
if (!INSTRUCTION_FILE_NAMES.has(basename(file))) {
|
|
99
|
+
throw new Error(`instruction-hint: params.file must be an instruction file name (${[...INSTRUCTION_FILE_NAMES].join(', ')}), got ${JSON.stringify(file)}`)
|
|
100
|
+
}
|
|
101
|
+
let real
|
|
102
|
+
try {
|
|
103
|
+
real = resolveRealWithin(resolve(cwd, file), [...instructionRoots(cwd), machinePaths().DSH_HOME], 'instruction file')
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error?.code === 'ENOENT') return ''
|
|
106
|
+
throw error
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
return readFileSync(real, 'utf8').trim()
|
|
110
|
+
} catch {
|
|
111
|
+
return ''
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
142
115
|
/**
|
|
143
116
|
* prompt-config placeholder fill=instruction-hint 的 resolver。
|
|
144
117
|
* params.text(自定义提示)优先;params.file(文件卡绑定的单个指令文件)次之——
|
|
145
118
|
* 运行时读该文件正文并加 `Instructions from:` 头注入;最后按 params.scope 探测来源
|
|
146
119
|
* (all / global / project)按显式 projectTemplate/globalTemplate 生成提示。
|
|
147
120
|
* 未配置提示模板时返回 null;引擎不提供默认引导文案。
|
|
148
|
-
*
|
|
121
|
+
* params.file 不是任意文件读取:名字必须是指令文件候选名,真实路径必须在会话 cwd 到
|
|
122
|
+
* 项目根的目录链或 DSH_HOME 内;越界、链接逃逸与非法名字在解析期抛错(executor 按单配置
|
|
123
|
+
* 告警跳过)。文件缺失、不可读或空内容时返回 null。
|
|
149
124
|
*/
|
|
150
125
|
export function createInstructionHintResolver(config = {}) {
|
|
151
126
|
const customText = typeof config?.params?.text === 'string' ? config.params.text.trim()
|
|
@@ -157,14 +132,6 @@ export function createInstructionHintResolver(config = {}) {
|
|
|
157
132
|
const fileLabel = typeof config?.params?.displayPath === 'string' && config.params.displayPath.trim().length > 0
|
|
158
133
|
? config.params.displayPath.trim()
|
|
159
134
|
: file
|
|
160
|
-
/** 文件即真相:每次注入都重读绑定文件,外部编辑立即生效。 */
|
|
161
|
-
const readBoundFile = () => {
|
|
162
|
-
try {
|
|
163
|
-
return readFileSync(file, 'utf8').trim()
|
|
164
|
-
} catch {
|
|
165
|
-
return ''
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
135
|
|
|
169
136
|
return async ({ ctx, agent, session }) => {
|
|
170
137
|
const id = `instruction-hint-${session.id}-${randomUUID()}`
|
|
@@ -172,7 +139,7 @@ export function createInstructionHintResolver(config = {}) {
|
|
|
172
139
|
return { id, text: customText, source: { kind: 'instruction-hint', form: 'hint' } }
|
|
173
140
|
}
|
|
174
141
|
if (file.length > 0) {
|
|
175
|
-
const content =
|
|
142
|
+
const content = readBoundInstructionFile(file, session.header?.cwd ?? process.cwd())
|
|
176
143
|
return content.length > 0
|
|
177
144
|
? { id, text: `Instructions from: ${fileLabel}\n\n${content}`, source: { kind: 'instruction-file', form: 'instructions' } }
|
|
178
145
|
: null
|
|
@@ -189,16 +156,15 @@ export function createInstructionHintResolver(config = {}) {
|
|
|
189
156
|
}
|
|
190
157
|
}
|
|
191
158
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
/** 从 agent-instructions 注入消息提取参考文件路径。 */
|
|
159
|
+
/** 从 agent-instructions 注入消息提取参考文件路径;removed 不是参考文件。 */
|
|
195
160
|
export function extractInstructionPaths(message) {
|
|
196
161
|
const paths = []
|
|
197
162
|
const blocks = Array.isArray(message?.content) ? message.content : []
|
|
198
163
|
for (const block of blocks) {
|
|
199
164
|
if (block?.type !== 'text' || typeof block.text !== 'string') continue
|
|
200
|
-
for (const
|
|
201
|
-
|
|
165
|
+
for (const parsed of instructionBlocks(block.text)) {
|
|
166
|
+
if (parsed.action === 'remove') continue
|
|
167
|
+
const path = parsed.path.trim()
|
|
202
168
|
if (path !== '' && !paths.includes(path)) paths.push(path)
|
|
203
169
|
}
|
|
204
170
|
}
|
|
@@ -209,7 +175,7 @@ export function extractInstructionPaths(message) {
|
|
|
209
175
|
export function buildInstructionHint(original, paths, sourceName = 'instruction-hint', templates = {}) {
|
|
210
176
|
const template = templateOf(templates, 'messageTemplate')
|
|
211
177
|
if (template.trim().length === 0) return original
|
|
212
|
-
const text = interpolateStatic(template, { FILES: paths.join(', '), SUFFIX: templateOf(templates, 'suffixTemplate') })
|
|
178
|
+
const text = interpolateStatic(template, { FILES: paths.join(', '), SUFFIX: templateOf(templates, 'suffixTemplate') }, undefined, 'messageTemplate')
|
|
213
179
|
if (text.trim().length === 0) return original
|
|
214
180
|
return {
|
|
215
181
|
id: typeof original?.id === 'string' && original.id !== '' ? original.id : `instruction-hint-${randomUUID()}`,
|
|
@@ -284,7 +250,7 @@ export function apply(ctx, config) {
|
|
|
284
250
|
if (!promotion.status(agent).promoted) return decision
|
|
285
251
|
if (!Array.isArray(decision.messages)) return decision
|
|
286
252
|
if (agent?.session === undefined) return decision
|
|
287
|
-
const coordinator = getService(ctx,
|
|
253
|
+
const coordinator = getService(ctx, PRE_STEP_COORDINATOR_SERVICE)
|
|
288
254
|
const messages = typeof coordinator?.filterInstructions === 'function'
|
|
289
255
|
? coordinator.filterInstructions(agent, decision.messages)
|
|
290
256
|
: decision.messages
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** `interpolate.mjs` 的手写声明:只声明 `src/` 消费的导出。 */
|
|
2
|
+
|
|
3
|
+
/** 保留名判据(内建变量 + 动态宏);任何值都接受,非字符串按空名处理。 */
|
|
4
|
+
export function isReservedInterpolationName(key: unknown): boolean
|
|
5
|
+
|
|
6
|
+
/** ST 宏写法归一(`{{roll 1d6}}` / `{{roll:1d6}}` → `{{roll::1d6}}`),幂等。 */
|
|
7
|
+
export function normalizeMacroSyntax(text: string): string
|