dsh-plugin-prompt-tool 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +185 -46
  2. package/engine/THIRD_PARTY_LICENSES +25 -0
  3. package/engine/actions.mjs +931 -0
  4. package/engine/anchor-match.mjs +6 -3
  5. package/engine/character-tools.mjs +11 -0
  6. package/engine/classify-task.mjs +18 -0
  7. package/engine/compaction-epoch.mjs +41 -20
  8. package/engine/compositions/library/agent-instructions.yml +9 -0
  9. package/engine/compositions/library/command-goal.yml +2 -6
  10. package/engine/compositions/library/compaction.yml +2 -32
  11. package/engine/compositions/library/delegation-ptc.yml +58 -0
  12. package/engine/compositions/library/delegation.yml +5 -38
  13. package/engine/compositions/library/persistent-shell.yml +4 -13
  14. package/engine/compositions/library/planning.yml +8 -23
  15. package/engine/compositions/library/skill-filesystem-cordis.yml +10 -0
  16. package/engine/compositions/library/skill-filesystem.yml +2 -8
  17. package/engine/compositions/library/tool-ask-user.yml +2 -3
  18. package/engine/compositions/library/tool-bash.yml +4 -12
  19. package/engine/compositions/library/tool-cordis.yml +7 -0
  20. package/engine/compositions/library/tool-fs-search.yml +2 -11
  21. package/engine/compositions/library/tool-fs.yml +2 -5
  22. package/engine/compositions/library/tool-goal.yml +2 -6
  23. package/engine/compositions/library/tool-jobs.yml +2 -18
  24. package/engine/compositions/library/tool-plugin-manager-disabled.yml +8 -0
  25. package/engine/compositions/library/tool-plugin-manager.yml +8 -0
  26. package/engine/compositions/library/tool-present.yml +7 -0
  27. package/engine/compositions/library/tool-presentation.yml +9 -0
  28. package/engine/compositions/library/tool-pwsh.yml +2 -6
  29. package/engine/compositions/library/tool-skill.yml +7 -0
  30. package/engine/compositions/library/tool-todo.yml +2 -4
  31. package/engine/compositions/library/tool-web.yml +2 -1
  32. package/engine/compositions/source/local/character-tools.yml +5 -0
  33. package/engine/compositions/source/local/declared-triggers.yml +12 -0
  34. package/engine/compositions/source/local/filesystem-editor.yml +30 -0
  35. package/engine/compositions/source/local/instruction-hint.yml +15 -0
  36. package/engine/compositions/{library/official-persistent-shell.yml → source/local/persistent-shell-posix.yml} +10 -8
  37. package/engine/compositions/source/local/prompt-config-engine.yml +3 -9
  38. package/engine/compositions/source/local/run-code-env.yml +4 -7
  39. package/engine/compositions/source/local/session-var-tools.yml +5 -0
  40. package/engine/compositions/source/local/skill-search.yml +3 -9
  41. package/engine/compositions/source/local/subagent-tool-policy.yml +21 -0
  42. package/engine/compositions/source/local/tool-bash-disabled.yml +9 -0
  43. package/engine/compositions/source/local/tool-config-engine.yml +8 -0
  44. package/engine/compositions/source/local/tool-git-bash.yml +15 -0
  45. package/engine/compositions/source/local/world-book-tools.yml +5 -0
  46. package/engine/condition.mjs +83 -0
  47. package/engine/declared-triggers.mjs +93 -0
  48. package/engine/executor.mjs +364 -185
  49. package/engine/fields.mjs +219 -0
  50. package/engine/fillers.mjs +12 -141
  51. package/engine/host-package.mjs +51 -0
  52. package/engine/instruction-hint.mjs +285 -0
  53. package/engine/interpolate.mjs +157 -29
  54. package/engine/layers.mjs +495 -75
  55. package/engine/predicates.mjs +753 -0
  56. package/engine/prompt-config-engine.mjs +63 -8
  57. package/engine/run-code-env.mjs +48 -31
  58. package/engine/schema.mjs +444 -103
  59. package/engine/sdk-strip.mjs +365 -0
  60. package/engine/session-var-tools.mjs +11 -0
  61. package/engine/session-vars.mjs +8 -6
  62. package/engine/shared.mjs +137 -0
  63. package/engine/skill-search.mjs +34 -21
  64. package/engine/st-macros.d.mts +22 -0
  65. package/engine/st-macros.mjs +208 -0
  66. package/engine/st-render.mjs +105 -0
  67. package/engine/st-world-book.d.mts +10 -0
  68. package/engine/st-world-book.mjs +259 -0
  69. package/engine/strategies.mjs +31 -20
  70. package/engine/subagent-tool-policy-core.mjs +400 -0
  71. package/engine/subagent-tool-policy.mjs +327 -0
  72. package/engine/tool-config-engine.mjs +207 -145
  73. package/engine/tool-definition.mjs +143 -0
  74. package/engine/{custom-bash.mjs → tool-git-bash.mjs} +38 -13
  75. package/engine/trigger-editor-meta.mjs +48 -0
  76. package/engine/trigger-spec.mjs +257 -0
  77. package/engine/trigger.mjs +304 -0
  78. package/engine/world-book-tools.mjs +11 -0
  79. package/lib/client.js +16495 -5456
  80. package/lib/client.js.map +1 -1
  81. package/lib/index.d.mts +628 -382
  82. package/lib/index.mjs +5391 -2942
  83. package/lib/paths-CY5psBqE.d.mts +1233 -0
  84. package/lib/preset-package-BnJqrU4R.mjs +16792 -0
  85. package/lib/preset-transfer.d.mts +2 -0
  86. package/lib/preset-transfer.mjs +2 -0
  87. package/package.json +91 -49
  88. package/preset/pt-cordis/preset.yml +43 -0
  89. package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +29 -0
  90. package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +501 -0
  91. package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +38 -0
  92. package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +60 -0
  93. package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +5 -0
  94. package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +13 -0
  95. package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +5 -0
  96. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +22 -0
  97. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +3 -0
  98. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +2 -0
  99. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +15 -0
  100. package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +8 -0
  101. package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +7 -0
  102. package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +84 -0
  103. package/preset/pt-custom/preset.yml +15 -0
  104. package/preset/pt-minimal/preset.yml +27 -0
  105. package/preset/pt-ptc/preset.yml +45 -0
  106. package/preset/pt-standard/preset.yml +43 -0
  107. package/templates/14-first-turn-anchor.yml +4 -4
  108. package/templates/15-guide-auto.yml +3 -3
  109. package/templates/16-custom-fallback.yml +1 -0
  110. package/templates/18-placeholder-env-facts.yml +3 -3
  111. package/templates/20-system-section.yml +3 -2
  112. package/templates/65-turn-stop.yml +18 -0
  113. package/templates/66-subagent-start.yml +14 -0
  114. package/templates/75-subagent-end.yml +15 -0
  115. package/templates/tools/10-shell-command.yml +1 -1
  116. package/templates/tools/40-delegate-character.yml +1 -1
  117. package/templates/tools/50-delegate-worldbook.yml +1 -1
  118. package/templates/tools/60-delegate-session-var.yml +1 -1
  119. package/templates/tools/70-delegate-worldbook-upsert.yml +1 -1
  120. package/engine/anchor-turn.mjs +0 -68
  121. package/engine/code-presentation.mjs +0 -103
  122. package/engine/compositions/library/anchor-turn.yml +0 -10
  123. package/engine/compositions/library/bootstrap-filesystem.yml +0 -19
  124. package/engine/compositions/library/code-presentation.yml +0 -8
  125. package/engine/compositions/library/context-gate.yml +0 -39
  126. package/engine/compositions/library/cot-drip.yml +0 -11
  127. package/engine/compositions/library/custom-bash.yml +0 -14
  128. package/engine/compositions/library/deliberation-gate.yml +0 -11
  129. package/engine/compositions/library/official-agent-instructions.yml +0 -17
  130. package/engine/compositions/library/official-skill-filesystem-cordis.yml +0 -10
  131. package/engine/compositions/library/official-tool-bash.yml +0 -6
  132. package/engine/compositions/library/official-tool-cordis.yml +0 -13
  133. package/engine/compositions/library/official-tool-presentation.yml +0 -7
  134. package/engine/compositions/library/official-tool-skill.yml +0 -14
  135. package/engine/compositions/library/persona.yml +0 -13
  136. package/engine/compositions/library/prompt-config-engine.yml +0 -16
  137. package/engine/compositions/library/run-code-env.yml +0 -15
  138. package/engine/compositions/library/skill-search.yml +0 -14
  139. package/engine/compositions/library/str-replace-editor.yml +0 -10
  140. package/engine/compositions/library/tool-bootstrap.yml +0 -14
  141. package/engine/compositions/library/tool-config-engine.yml +0 -9
  142. package/engine/compositions/library/tool-filter.yml +0 -8
  143. package/engine/compositions/source/local/code-presentation.yml +0 -8
  144. package/engine/compositions/source/local/context-gate.yml +0 -36
  145. package/engine/compositions/source/local/custom-bash.yml +0 -11
  146. package/engine/compositions/source/local/str-replace-editor.yml +0 -10
  147. package/engine/compositions/source/local/tool-bootstrap.yml +0 -11
  148. package/engine/compositions/source/local/tool-filter.yml +0 -8
  149. package/engine/context-gate.mjs +0 -305
  150. package/engine/cot-drip.mjs +0 -137
  151. package/engine/deliberation-gate.mjs +0 -127
  152. package/engine/tool-bootstrap.mjs +0 -476
  153. package/engine/tool-filter.mjs +0 -80
  154. package/lib/preset-core.d.mts +0 -10
  155. package/lib/preset-core.mjs +0 -77
  156. package/lib/prompt-configs-BnQKRHVb.mjs +0 -884
  157. package/lib/prompt-configs-CN0sJTUL.d.mts +0 -193
  158. package/preset/anchored/preset.yml +0 -296
  159. package/preset/creative/preset.yml +0 -67
  160. package/preset/creative/skills/cordis-plugin-development/SKILL.md +0 -420
  161. package/preset/creative/skills/editing-cordis-compositions/SKILL.md +0 -165
  162. package/preset/custom/preset.yml +0 -12
  163. package/preset/liangshen/preset.yml +0 -75
  164. package/preset/minimal/preset.yml +0 -45
  165. package/preset/ptc/preset.yml +0 -58
  166. package/preset/standard/preset.yml +0 -57
  167. package/skills/manifest.json +0 -7
  168. package/skills/sandboxmod/SKILL.md +0 -49
  169. package/skills/web ui/SKILL.md +0 -42
@@ -0,0 +1,219 @@
1
+ /**
2
+ * fields — 配置字段类型系统(B2 配置契约统一的底座)。
3
+ *
4
+ * 把「每个模块自己写白名单、自己写归一化、自己决定缺省语义」收敛为
5
+ * 「一份字段声明 + 一个校验运行时」:
6
+ *
7
+ * const { allowedKeys, parse } = defineConfig({
8
+ * enabled: bool({ default: false }),
9
+ * envKeys: stringList({ trim: true, dedupe: true, message: '...', emptyMessage: '...' }),
10
+ * })
11
+ * const config = parse(source, name) // 未知键 / 类型错 / 缺必填一律 fail loud
12
+ *
13
+ * 硬约束(B2):**逐字段的校验结果与错误消息必须与迁移前逐字一致**,
14
+ * 唯一允许的行为变更是 `enabled` 的 fail loud(见 PLAN T3)。因此:
15
+ * - 缺省语义由每个字段**显式声明**,不再有隐式 `undefined` / `[]` / `new Set()` 的混用;
16
+ * - 底层复用 shared.mjs 的既有 helper(`booleanOption` / `requiredInt` / `requiredText` /
17
+ * `validateConfig`),不复制它们的逻辑,默认消息也就自动逐字相同;
18
+ * - 模块需要用别的措辞时,用 `message` / `emptyMessage` 覆盖(模板里的 `{plugin}`、
19
+ * `{field}` 会被替换)。
20
+ *
21
+ * 字段类型与它替代的历史 helper:
22
+ * bool(...) ← booleanOption (缺省 → default)
23
+ * int({min,...}) ← requiredInt / optionalPositiveInt (required 缺键即抛)
24
+ * text(...) ← requiredText / requiredStageField (requiredText 的空串 → undefined)
25
+ * stringList(...) ← nameSet / allowKindList / sourceList / deferredList / stringList /
26
+ * stringListOrEmpty / normalizeEnvKeys(返回数组或 Set、可选 dedupe/trim)
27
+ * enumOf(...) ← 枚举白名单(如 promoteOn 一族)
28
+ */
29
+
30
+ import { booleanOption, requiredInt, requiredText, validateConfig } from './shared.mjs'
31
+
32
+ /** 把 `{plugin}` / `{field}` 占位替换进消息模板。 */
33
+ const format = (template, plugin, field) =>
34
+ String(template).replaceAll('{plugin}', plugin).replaceAll('{field}', field)
35
+
36
+ /**
37
+ * 缺省值解析:`default` 既可以是值,也可以是**工厂函数**。
38
+ * 工厂形态是必要的 —— 像 `new Set()` / `[]` 这种可变默认值若被多次 parse 共享,
39
+ * 调用方一旦 mutate 就会串味到别的会话。
40
+ */
41
+ const resolveDefault = (fallback) => (typeof fallback === 'function' ? fallback() : fallback)
42
+
43
+ /** 字段公共形状:每个字段都提供 parse(value, plugin, field)。 */
44
+ const makeField = (spec) => {
45
+ if (spec === null || typeof spec !== 'object' || typeof spec.parse !== 'function') {
46
+ throw new TypeError('fields: a field must be produced by bool()/int()/text()/stringList()/enumOf()')
47
+ }
48
+ return spec
49
+ }
50
+
51
+ /** 布尔开关:缺省返回 default;非布尔一律 fail loud。 */
52
+ export function bool({ default: fallback = false, message } = {}) {
53
+ return makeField({
54
+ kind: 'bool',
55
+ default: fallback,
56
+ parse: (value, plugin, field) => {
57
+ if (message === undefined) return booleanOption(plugin, value, field, resolveDefault(fallback))
58
+ if (value === undefined) return resolveDefault(fallback)
59
+ if (typeof value !== 'boolean') throw new TypeError(format(message, plugin, field))
60
+ return value
61
+ },
62
+ })
63
+ }
64
+
65
+ /**
66
+ * 整数:`required: true` 时缺键即抛(消息与 shared.requiredInt 逐字一致);
67
+ * 否则缺键返回 default。`max` 只在声明了才检查。
68
+ */
69
+ export function int({ min = 0, default: fallback, required = false, message } = {}) {
70
+ if (!Number.isSafeInteger(min)) throw new TypeError('fields: int({min}) must be a safe integer')
71
+ return makeField({
72
+ kind: 'int',
73
+ default: fallback,
74
+ parse: (value, plugin, field) => {
75
+ // 缺键的 required 错误交给 requiredInt 生成,消息逐字相同。
76
+ if (value === undefined) {
77
+ if (required) requiredInt(plugin, value, field, min)
78
+ return resolveDefault(fallback)
79
+ }
80
+ // 不设 max:engine 里 11 处 requiredInt 调用全部只传最小值,上限语义零需求(YAGNI)。
81
+ // message 只在需要偏离 requiredInt 默认措辞时使用。
82
+ if (message !== undefined) {
83
+ if (!Number.isSafeInteger(value) || value < min) throw new TypeError(format(message, plugin, field))
84
+ return value
85
+ }
86
+ return requiredInt(plugin, value, field, min)
87
+ },
88
+ })
89
+ }
90
+
91
+ /**
92
+ * 文本:
93
+ * - `required: true` 走 shared.requiredText —— 非字符串抛、**空串返回 undefined**
94
+ * (「空串 = 显式留空」是既有语义,调用方据此跳过注册);
95
+ * - `allowEmpty: false` 时空串按错值处理(对应 tool-bootstrap 的 requiredStageField);
96
+ * - 否则缺键返回 default。
97
+ */
98
+ export function text({ required = false, default: fallback, allowEmpty = true, message } = {}) {
99
+ return makeField({
100
+ kind: 'text',
101
+ default: fallback,
102
+ parse: (value, plugin, field) => {
103
+ // required 且未覆盖消息、且不需要"空串即错"时,**完全委托** requiredText:
104
+ // 非字符串抛它自己的消息(含中文后缀)、空串返回 undefined —— 与迁移前逐字相同。
105
+ if (required && message === undefined && allowEmpty) return requiredText(plugin, value, field)
106
+ if (value === undefined) {
107
+ if (required) requiredText(plugin, value, field)
108
+ return resolveDefault(fallback)
109
+ }
110
+ if (typeof value !== 'string') {
111
+ throw new TypeError(message === undefined
112
+ ? `${plugin}: ${field} must be a string`
113
+ : format(message, plugin, field))
114
+ }
115
+ if (!allowEmpty && value.length === 0) {
116
+ throw new TypeError(message === undefined
117
+ ? `${plugin}: ${field} must be a non-empty string`
118
+ : format(message, plugin, field))
119
+ }
120
+ // requiredText 的空串契约:显式留空 → undefined,由调用方跳过注册。
121
+ return required && value.length === 0 ? undefined : value
122
+ },
123
+ })
124
+ }
125
+
126
+ /**
127
+ * 字符串列表:
128
+ * - `required: true` 时缺键即抛;否则缺键返回 default(可给工厂,如 `() => new Set()`);
129
+ * - 形状错误(非数组 / 含非字符串 / 含空串)抛 `message`;
130
+ * - `trim: true` 先修剪再**丢弃**空项(normalizeEnvKeys 的语义),修剪后为空抛 `emptyMessage`;
131
+ * - 空数组在 `allowEmpty: false` 时抛 `emptyMessage ?? message`;
132
+ * - `as: 'set'` 返回 Set(nameSet / allowKindList / sourceList / deferredList 的形态)。
133
+ */
134
+ export function stringList({
135
+ required = false, default: fallback, dedupe = false, trim = false,
136
+ allowEmpty = false, as = 'array', message, emptyMessage,
137
+ } = {}) {
138
+ const shapeTemplate = message ?? '{plugin}: {field} must be an array of non-empty strings'
139
+ const emptyTemplate = emptyMessage ?? shapeTemplate
140
+ return makeField({
141
+ kind: 'stringList',
142
+ default: fallback,
143
+ parse: (value, plugin, field) => {
144
+ if (value === undefined) {
145
+ if (required) throw new TypeError(format(shapeTemplate, plugin, field))
146
+ return resolveDefault(fallback)
147
+ }
148
+ if (!Array.isArray(value)) throw new TypeError(format(shapeTemplate, plugin, field))
149
+ // 空数组是**形状**问题,与「给出了但被 trim 丢光」是两回事:normalizeEnvKeys 对 [] 与
150
+ // [' '] 给的是两条不同消息,必须能分别表达。
151
+ if (value.length === 0 && !allowEmpty) throw new TypeError(format(shapeTemplate, plugin, field))
152
+ const items = []
153
+ for (const item of value) {
154
+ if (typeof item !== 'string') throw new TypeError(format(shapeTemplate, plugin, field))
155
+ const trimmed = trim ? item.trim() : item
156
+ if (trimmed.length === 0) {
157
+ // trim 模式下空项是「被丢弃的噪声」;非 trim 模式下空串是形状错误。
158
+ if (trim) continue
159
+ throw new TypeError(format(shapeTemplate, plugin, field))
160
+ }
161
+ items.push(trimmed)
162
+ }
163
+ if (items.length === 0 && !allowEmpty) throw new TypeError(format(emptyTemplate, plugin, field))
164
+ const list = dedupe ? [...new Set(items)] : items
165
+ return as === 'set' ? new Set(list) : list
166
+ },
167
+ })
168
+ }
169
+
170
+ /**
171
+ * 直通字段:**只登记白名单**,值的归一化交给模块自己的 parse —— 用于旧代码里刻意存在的
172
+ * 「非法值即静默取默认」语义(如 `typeof x === 'string' && x.length > 0 ? x : 默认值`)。
173
+ * 换用严格字段类型会把这些静默降级变成挂载期报错,而那是 B2 未授权的行为变更,
174
+ * 所以这类键一律用 passthrough 保住原语义,并在模块里注明依据。
175
+ */
176
+ export function passthrough(parse, { default: fallback } = {}) {
177
+ if (typeof parse !== 'function') throw new TypeError('fields: passthrough(parse) requires a function')
178
+ return makeField({ kind: 'passthrough', default: fallback, parse })
179
+ }
180
+
181
+ /** 枚举:值必须落在 `values` 里;`required: true` 时缺键即抛。 */
182
+ export function enumOf(values, { default: fallback, required = false, message } = {}) {
183
+ const allowed = [...values]
184
+ if (allowed.length === 0) throw new TypeError('fields: enumOf(values) must not be empty')
185
+ const template = message ?? `{plugin}: {field} must be one of ${allowed.map((item) => JSON.stringify(item)).join(', ')}`
186
+ return makeField({
187
+ kind: 'enum',
188
+ default: fallback,
189
+ parse: (value, plugin, field) => {
190
+ if (value === undefined) {
191
+ if (required) throw new TypeError(format(template, plugin, field))
192
+ return resolveDefault(fallback)
193
+ }
194
+ if (!allowed.includes(value)) throw new TypeError(format(template, plugin, field))
195
+ return value
196
+ },
197
+ })
198
+ }
199
+
200
+ /**
201
+ * 由字段声明派生 `{ allowedKeys, parse }`:
202
+ * - `allowedKeys` 取代手写 `ALLOWED_KEYS`(未知键的报错格式沿用 shared.validateConfig);
203
+ * - `parse(source, plugin)` 先做信封校验再逐字段归一化,返回**只含声明键**的对象。
204
+ */
205
+ export function defineConfig(fields) {
206
+ if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {
207
+ throw new TypeError('fields: defineConfig(fields) requires an object of field declarations')
208
+ }
209
+ const entries = Object.entries(fields)
210
+ for (const [key, spec] of entries) makeField(spec)
211
+ const allowedKeys = new Set(entries.map(([key]) => key))
212
+ const parse = (source, plugin) => {
213
+ const config = validateConfig(plugin, source, allowedKeys)
214
+ const normalized = {}
215
+ for (const [key, spec] of entries) normalized[key] = spec.parse(config[key], plugin, key)
216
+ return normalized
217
+ }
218
+ return { allowedKeys, parse, fields }
219
+ }
@@ -3,152 +3,19 @@
3
3
  * 全部遵循同一铁律:服务缺失或列表失败时返回 null(跳过该配置),绝不伤及会话。
4
4
  */
5
5
 
6
- import { readFileSync } from 'node:fs'
7
6
  import { getService, parseToolNames } from './shared.mjs'
7
+ import { createInstructionHintResolver } from './instruction-hint.mjs'
8
8
 
9
9
  const name = 'prompt-config-engine'
10
10
 
11
- const PROJECT_CANDIDATES = ['AGENTS.md', 'CLAUDE.md', 'AGENTS.local.md', 'CLAUDE.local.md']
12
- const USER_GLOBAL_CANDIDATE = 'AGENTS.md'
13
-
14
- /** instruction-hint 提示文本:agents-instruction.md 优先,否则动态探测。
15
- * path 可经提示词配置 params.agentsInstructionPath 指定(共享引擎时引擎文件在
16
- * 预设根 .engine,必须显式指向预设目录内的文件,如 ../<预设>/agents-instruction.md)。 */
17
- function readAgentsInstructionText(path = './agents-instruction.md') {
18
- try {
19
- return readFileSync(new URL(path, import.meta.url), 'utf8').trim()
20
- } catch {
21
- return ''
22
- }
23
- }
24
-
25
- /** Find the project root: first ancestor containing any root marker (e.g. .git). */
26
- async function findProjectRoot(fs, cwd, signal) {
27
- let current = cwd
28
- for (;;) {
29
- for (const marker of ['.git', '.hg', '.svn']) {
30
- try {
31
- const target = await fs.resolve(joinPath(current, marker), { cwd, signal })
32
- const info = await fs.stat(target, signal)
33
- if (info !== undefined) return current
34
- } catch {
35
- // Probe failure = marker absent; continue.
36
- }
37
- }
38
- const parent = parentPath(current)
39
- if (parent === current || parent.length === 0) return cwd
40
- current = parent
41
- }
42
- }
43
-
44
- /** List instruction files present in one directory (project candidates). */
45
- async function presentInDir(fs, dir, candidates, signal) {
46
- const found = []
47
- for (const candidate of candidates) {
48
- try {
49
- const target = await fs.resolve(joinPath(dir, candidate), { cwd: dir, signal })
50
- const info = await fs.stat(target, signal)
51
- if (info !== undefined && info.type === 'file') found.push(candidate)
52
- } catch {
53
- // Absent or unreadable — skip.
54
- }
55
- }
56
- return found
57
- }
58
-
59
- /** Join one path segment onto a directory (platform-agnostic string join). */
60
- function joinPath(dir, segment) {
61
- if (dir.endsWith('/') || dir.endsWith('\\')) return dir + segment
62
- const sep = dir.includes('\\') ? '\\' : '/'
63
- return dir + sep + segment
64
- }
65
-
66
- /** Parent of an absolute Windows or POSIX path. */
67
- function parentPath(path) {
68
- const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
69
- if (idx <= 0) return path
70
- const parent = path.slice(0, idx)
71
- return parent.length === 0 ? path : parent
72
- }
73
-
74
- /**
75
- * instruction-hint:晋升后一次性提示指令文件存在(agents-instruction.md 优先)。
76
- * 可自定义参数:params.text 直接作为提示文本(覆盖文件与动态探测);
77
- * 其余字段语义与 env-facts / skill-catalog 一致(config.params 由单一配置源下发)。
78
- */
79
- function createInstructionHintResolver(config) {
80
- const customText = typeof config?.params?.text === 'string' && config.params.text.trim().length > 0
81
- ? config.params.text.trim()
82
- : ''
83
- // 共享引擎:引擎文件在预设根 .engine,agents-instruction.md 必须经
84
- // params.agentsInstructionPath 显式指向预设目录内文件(旧版无此参数时回退
85
- // './agents-instruction.md'——引擎文件与内容资产同目录的旧布局;显式 .txt 旧路径
86
- // 仍可读(升级前生成的组合,文件未重建前保持兼容))。
87
- const instructionPath = typeof config?.params?.agentsInstructionPath === 'string'
88
- && config.params.agentsInstructionPath.trim().length > 0
89
- ? config.params.agentsInstructionPath
90
- : './agents-instruction.md'
91
- const agentsInstructionText = readAgentsInstructionText(instructionPath)
92
- return async ({ ctx, agent, session }) => {
93
- const id = `instruction-hint-${session.id}`
94
- if (customText.length > 0) {
95
- return {
96
- id,
97
- text: customText,
98
- source: { kind: 'instruction-hint', form: 'hint' },
99
- }
100
- }
101
- if (agentsInstructionText.length > 0) {
102
- return {
103
- id,
104
- text: agentsInstructionText,
105
- source: { kind: 'instruction-hint', form: 'hint' },
106
- }
107
- }
108
- const fs = ctx.get('fs')
109
- if (fs === undefined) return null
110
- const cwd = session.header?.cwd ?? process.cwd()
111
- const projectFiles = []
112
- const root = await findProjectRoot(fs, cwd, agent.signal)
113
- projectFiles.push(...await presentInDir(fs, root, PROJECT_CANDIDATES, agent.signal))
114
- const userGlobalFiles = []
115
- try {
116
- const dshHome = process.env.DSH_HOME ?? (process.env.USERPROFILE ? `${process.env.USERPROFILE}\\.dsh` : undefined)
117
- if (dshHome !== undefined) {
118
- userGlobalFiles.push(...await presentInDir(fs, dshHome, [USER_GLOBAL_CANDIDATE], agent.signal))
119
- }
120
- } catch {
121
- // Unreadable home probe — ignore.
122
- }
123
- const sections = []
124
- if (projectFiles.length > 0) {
125
- sections.push(`Workspace instruction files exist: ${projectFiles.join(', ')} (project root: ${root}).`)
126
- }
127
- if (userGlobalFiles.length > 0) {
128
- sections.push(`A user-global instruction file exists: ${USER_GLOBAL_CANDIDATE}.`)
129
- }
130
- if (sections.length === 0) return null
131
- return {
132
- id,
133
- text: [
134
- ...sections,
135
- 'Do NOT assume their content. When a task touches this workspace, read the relevant instruction files first and follow them.',
136
- ].join(' '),
137
- source: { kind: 'instruction-hint', form: 'hint' },
138
- }
139
- }
140
- }
141
-
142
11
  /**
143
12
  * env-facts:机器事实动态填充器。
144
- * params.envKeys 逗号分隔环境变量白名单,默认 DSH_HOME,DSH_WORKSPACE;
145
- * CWD 特殊映射到 session.header.cwd ?? process.cwd()。
13
+ * params.envKeys 逗号分隔环境变量白名单:取值归模板/预设,留空 = 不附加宿主环境
14
+ * 变量(CWD/WORKSPACE 仍由会话事实提供)。
146
15
  * 返回 facts 变量表与默认文本;用户可用 text 模板 + {{变量}} 完全自定义输出。
147
16
  */
148
17
  function createEnvFactsResolver(config) {
149
- const keys = parseToolNames(typeof config.params?.envKeys === 'string' && config.params.envKeys.length > 0
150
- ? config.params.envKeys
151
- : 'DSH_HOME,DSH_WORKSPACE')
18
+ const keys = parseToolNames(typeof config.params?.envKeys === 'string' ? config.params.envKeys : '')
152
19
  return ({ agent }) => {
153
20
  const session = agent?.session
154
21
  const cwd = session?.header?.cwd ?? process.cwd()
@@ -182,10 +49,14 @@ function createSkillCatalogResolver(config) {
182
49
  .filter((field) => ['name', 'description', 'whenToUse'].includes(field))
183
50
  const providers = parseToolNames(config.params?.providers)
184
51
  const emptyBehavior = config.params?.emptyBehavior === 'text' ? 'text' : 'skip'
185
- const emptyText = typeof config.params?.emptyText === 'string' && config.params.emptyText.length > 0
186
- ? config.params.emptyText
187
- : '当前没有可用技能。'
52
+ // 空结果提示文案归模板/预设:留空且 emptyBehavior=text 时按 skip 处理(不注入空消息)。
53
+ const emptyText = typeof config.params?.emptyText === 'string' ? config.params.emptyText : ''
188
54
  let warned = false
55
+ // 保留本地 warnOnce:本工厂只拿得到 config,ctx 要到每次 resolve 调用时才由
56
+ // 参数传入;换成 shared 的 createWarnOnce(ctx, name) 必须改工厂签名,而唯一
57
+ // 调用点 engine/strategies.mjs 的 createPlaceholderResolver(config) 不在本支
58
+ // 写区。为替换而扩大改动面违反 PLAN,故此处按原语义手写(一次性 + logger
59
+ // 不可用时不抛)。
189
60
  const warnOnceLocal = (ctx, message) => {
190
61
  if (warned) return
191
62
  warned = true
@@ -214,7 +85,7 @@ function createSkillCatalogResolver(config) {
214
85
  : visible.filter((skill) => providers.includes(skill?.provider))
215
86
  const total = scoped.length
216
87
  if (total === 0) {
217
- if (emptyBehavior !== 'text') return null
88
+ if (emptyBehavior !== 'text' || emptyText.length === 0) return null
218
89
  return { text: emptyText, variables: { SKILL_COUNT: '0', SKILL_NAMES: '', SKILLS_TEXT: '' } }
219
90
  }
220
91
 
@@ -0,0 +1,51 @@
1
+ /**
2
+ * host-package — 从运行中的 DSH 宿主入口解析宿主侧包。
3
+ *
4
+ * 全局 dsh 常以 junction/symlink 暴露(npm-global/@deepseek-ai/dsh → 真实包目录),
5
+ * 而 pnpm 在包内建立的相对 symlink 只在真实路径下可解析:沿 junction 路径用
6
+ * createRequire(process.argv[1]) 解析会得到 Cannot find module '@deepseek-ai/dsh-tools'。
7
+ * 入口先取 realpath,再回退原路径与引擎自身,两种安装形态都能解析到同一份宿主依赖。
8
+ */
9
+ import { realpathSync } from 'node:fs'
10
+ import { createRequire } from 'node:module'
11
+ import { fileURLToPath, pathToFileURL } from 'node:url'
12
+
13
+ /**
14
+ * 按优先级列出 createRequire 的解析基准:真实入口 → 原始入口 → 引擎文件自身。
15
+ * @param {string|undefined} entry 宿主入口路径(默认 process.argv[1])。
16
+ * @param {string|undefined} self 引擎文件路径(默认 import.meta.url)。
17
+ * @returns {string[]} 去重后的基准列表。
18
+ */
19
+ export function hostResolutionBases(entry = process.argv[1], self = fileURLToPath(import.meta.url)) {
20
+ const bases = []
21
+ const push = (value) => {
22
+ if (typeof value === 'string' && value.length > 0 && !bases.includes(value)) bases.push(value)
23
+ }
24
+ if (typeof entry === 'string' && entry.length > 0) {
25
+ try { push(realpathSync(entry)) } catch { /* 入口不存在(测试/嵌入式)时退回原路径 */ }
26
+ push(entry)
27
+ }
28
+ push(self)
29
+ return bases
30
+ }
31
+
32
+ /**
33
+ * 解析宿主侧包 id。
34
+ * @param {string} id 包名(如 '@deepseek-ai/dsh-tools')。
35
+ * @param {string|undefined} entry 宿主入口路径。
36
+ * @param {string|undefined} self 引擎文件路径。
37
+ * @returns {string} 解析到的绝对文件路径。
38
+ * @throws 全部基准都解析失败时抛出,并列出已尝试的基准。
39
+ */
40
+ export function resolveHostPackage(id, entry, self) {
41
+ const bases = hostResolutionBases(entry, self)
42
+ for (const base of bases) {
43
+ try { return createRequire(base).resolve(id) } catch { /* 下一个基准 */ }
44
+ }
45
+ throw new Error(`prompt-tool: cannot resolve ${id} from host entry (tried: ${bases.join(', ')})`)
46
+ }
47
+
48
+ /** 动态 import 宿主侧包,返回其命名空间。 */
49
+ export async function importHostPackage(id, entry, self) {
50
+ return import(pathToFileURL(resolveHostPackage(id, entry, self)).href)
51
+ }