dsh-plugin-prompt-tool 0.6.2 → 0.7.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.
Files changed (87) hide show
  1. package/README.md +11 -9
  2. package/engine/anchor-match.mjs +38 -12
  3. package/engine/anchor-turn.mjs +68 -0
  4. package/engine/classify-task.mjs +35 -0
  5. package/engine/code-presentation.mjs +103 -0
  6. package/engine/compositions/library/anchor-turn.yml +10 -0
  7. package/engine/compositions/library/bootstrap-filesystem.yml +1 -1
  8. package/engine/compositions/library/code-presentation.yml +8 -0
  9. package/engine/compositions/library/command-goal.yml +11 -0
  10. package/engine/compositions/library/compaction.yml +1 -1
  11. package/engine/compositions/library/context-gate.yml +1 -2
  12. package/engine/compositions/library/cot-drip.yml +11 -0
  13. package/engine/compositions/library/custom-bash.yml +1 -1
  14. package/engine/compositions/library/delegation.yml +11 -7
  15. package/engine/compositions/library/deliberation-gate.yml +11 -0
  16. package/engine/compositions/library/persistent-shell.yml +25 -2
  17. package/engine/compositions/library/persona.yml +6 -2
  18. package/engine/compositions/library/planning.yml +1 -1
  19. package/engine/compositions/library/prompt-config-engine.yml +1 -1
  20. package/engine/compositions/library/run-code-env.yml +1 -1
  21. package/engine/compositions/library/skill-filesystem.yml +1 -1
  22. package/engine/compositions/library/skill-search.yml +1 -1
  23. package/engine/compositions/library/str-replace-editor.yml +2 -2
  24. package/engine/compositions/library/tool-ask-user.yml +1 -1
  25. package/engine/compositions/library/tool-bash.yml +1 -1
  26. package/engine/compositions/library/tool-bootstrap.yml +1 -3
  27. package/engine/compositions/library/tool-config-engine.yml +9 -0
  28. package/engine/compositions/library/tool-filter.yml +3 -7
  29. package/engine/compositions/library/tool-fs-search.yml +1 -1
  30. package/engine/compositions/library/tool-fs.yml +1 -1
  31. package/engine/compositions/library/tool-goal.yml +1 -9
  32. package/engine/compositions/library/tool-jobs.yml +1 -1
  33. package/engine/compositions/library/tool-pwsh.yml +1 -1
  34. package/engine/compositions/library/tool-todo.yml +1 -1
  35. package/engine/compositions/library/tool-web.yml +2 -2
  36. package/engine/compositions/source/local/code-presentation.yml +8 -0
  37. package/engine/compositions/source/local/context-gate.yml +0 -1
  38. package/engine/compositions/source/local/str-replace-editor.yml +10 -0
  39. package/engine/compositions/source/local/tool-bootstrap.yml +1 -3
  40. package/engine/compositions/source/local/tool-config-engine.yml +9 -0
  41. package/engine/compositions/source/local/tool-filter.yml +8 -0
  42. package/engine/cot-drip.mjs +137 -0
  43. package/engine/custom-bash.mjs +73 -21
  44. package/engine/deliberation-gate.mjs +127 -0
  45. package/engine/layers.mjs +223 -198
  46. package/engine/run-code-env.mjs +8 -2
  47. package/engine/schema.mjs +1 -1
  48. package/engine/strategies.mjs +25 -14
  49. package/engine/tool-bootstrap.mjs +185 -59
  50. package/engine/tool-config-engine.mjs +390 -0
  51. package/lib/client.js +3840 -2311
  52. package/lib/client.js.map +1 -1
  53. package/lib/index.d.mts +185 -79
  54. package/lib/index.mjs +1000 -356
  55. package/lib/preset-core.d.mts +1 -1
  56. package/lib/preset-core.mjs +29 -3
  57. package/lib/{prompt-configs-BgFw_bFf.mjs → prompt-configs-BnQKRHVb.mjs} +211 -136
  58. package/lib/prompt-configs-CN0sJTUL.d.mts +193 -0
  59. package/package.json +121 -105
  60. package/preset/anchored/preset.yml +262 -308
  61. package/preset/creative/preset.yml +34 -32
  62. package/preset/custom/preset.yml +0 -1
  63. package/preset/liangshen/preset.yml +53 -50
  64. package/preset/minimal/preset.yml +18 -17
  65. package/preset/ptc/preset.yml +35 -33
  66. package/preset/standard/preset.yml +34 -32
  67. package/templates/10-pre-step.yml +18 -15
  68. package/templates/14-first-turn-anchor.yml +13 -13
  69. package/templates/15-guide-auto.yml +13 -13
  70. package/templates/16-custom-fallback.yml +19 -18
  71. package/templates/18-placeholder-env-facts.yml +13 -2
  72. package/templates/30-runtime-context.yml +3 -0
  73. package/templates/40-agent-request.yml +9 -9
  74. package/templates/60-tool-pipeline.yml +9 -8
  75. package/templates/tools/10-shell-command.yml +20 -0
  76. package/templates/tools/20-http-fetch.yml +24 -0
  77. package/templates/tools/30-fs-read.yml +20 -0
  78. package/templates/tools/40-delegate-character.yml +15 -0
  79. package/templates/tools/50-delegate-worldbook.yml +15 -0
  80. package/templates/tools/60-delegate-session-var.yml +15 -0
  81. package/templates/tools/70-delegate-worldbook-upsert.yml +36 -0
  82. package/lib/prompt-configs-B4vH09wx.d.mts +0 -100
  83. package/templates/11-merged-a.yml +0 -11
  84. package/templates/13-anchor.yml +0 -19
  85. package/templates/17-instruction-hint.yml +0 -23
  86. package/templates/19-placeholder-skill-catalog.yml +0 -19
  87. package/templates/31-runtime-context-placeholder.yml +0 -18
@@ -2,9 +2,9 @@
2
2
  * Anchored tool bootstrap — keep the FIRST model request on the Minimal
3
3
  * preset's REAL tool schema (persistent `bash` + `str_replace_editor`), then
4
4
  * keep the assembled catalog once the session has produced its first durable
5
- * promotion signal; `usePtcMode` optionally switches the wire presentation to
6
- * Code Mode (PTC). Injected-context control lives in the companion
7
- * `context-gate` plugin, not here.
5
+ * promotion signal. Code Mode (PTC) wire presentation lives in the companion
6
+ * `code-presentation` plugin (mounted as a separate row), not here.
7
+ * Injected-context control lives in the companion `context-gate` plugin.
8
8
  *
9
9
  * GATE MODE (liangshen stabilize 扩展, source: xiaobright/dsh-anchored-standard
10
10
  * MIT + phase-1 quarantine): `promoteGate: true` gates the promotion on the
@@ -19,6 +19,15 @@
19
19
  * appended to the phase-1 persona (test builds, issue #274) — unset keeps
20
20
  * the exact one-line Minimal anchor.
21
21
  *
22
+ * STAGES MODE (渐进披露, 参考 dsh-router-standard progressive disclosure 自写,
23
+ * MIT): `stages: [{ name, tools }]` 声明时激活多级阶段窄化——目录 = 当前阶段
24
+ * 工具 + 预放(stagePreUnlock 档,默认 1);`stageAdvanceTool`(默认
25
+ * phase_advance)推进阶段;调用更高阶段工具 = 直达(自动跳到其档);阶段
26
+ * 状态由 durable tool/call 事件推导(resume/reload 自动恢复,无文件),
27
+ * compaction 不重置(阶段是会话级进度)。阶段文案经 `stageSectionTemplate`
28
+ * 参数化(引擎只提供动态状态,不写死引导文本)。stages 与 promoteOn 门控
29
+ * 互不影响:阶段窄化从首轮开始,晋升/compaction 仍由 context-gate 管注入。
30
+ *
22
31
  * The phase is derived from durable session events, so resume and reload
23
32
  * preserve it. By default (`promoteOn: 'either'`) a session promotes after the
24
33
  * first `tool/call` OR the first `assistant/message`, whichever comes first:
@@ -70,10 +79,8 @@
70
79
  * context-gate row's flag when both rows are present.
71
80
  *
72
81
  * POST-PROMOTION CATALOG (prompt-tool patch): after promotion both modes
73
- * keep the assembled catalog. `usePtcMode` switches the wire presentation
74
- * to Code Mode (PTC, single run_code) instead of narrowing the resident set.
75
- * The controlled phase below still narrows the catalog before promotion and
76
- * after compaction.
82
+ * keep the assembled catalog (no narrowing). The controlled phase below
83
+ * still narrows the catalog before promotion and after compaction.
77
84
  * COMPACTION (local addition): a compaction rewrites the whole surface, so the
78
85
  * first post-compaction request is a "second first request". Promotion is
79
86
  * epoch-aware (see compaction-epoch.mjs): after `compaction/end` the session
@@ -114,9 +121,10 @@ export const inject = []
114
121
  /** Every config key this plugin accepts — anything else is a typo. */
115
122
  const ALLOWED_KEYS = new Set([
116
123
  'bootstrapTools', 'promoteOn', 'bootstrapMaxTokens', 'compactionTools',
117
- 'includeSubagents', 'usePtcMode',
124
+ 'includeSubagents',
118
125
  'promoteGate', 'maxPromoteSteps', 'promoteAfterFirstResponse',
119
126
  'personaSectionsOnly', 'workspaceLine', 'phase1FirstCallInstruction',
127
+ 'stages', 'stagePreUnlock', 'stageAdvanceTool', 'stageAdvanceDescription', 'stageSectionTemplate',
120
128
  ])
121
129
 
122
130
  /** 预设 persona section 名(官方注册名 + 旧名)。 */
@@ -127,8 +135,8 @@ const WORKSPACE_LINE_PREFIX = '\n\nYour working directory is '
127
135
 
128
136
 
129
137
  /** Non-empty string list config validator. */
130
- function stringList(value, field) {
131
- if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== 'string' || item.length === 0)) {
138
+ function stringList(value, field, allowEmpty = false) {
139
+ if (!Array.isArray(value) || (value.length === 0 && !allowEmpty) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
132
140
  throw new TypeError(`${name}: ${field} must be a non-empty array of non-empty strings`)
133
141
  }
134
142
  return [...new Set(value)]
@@ -139,6 +147,25 @@ function stringListOrEmpty(value, field) {
139
147
  return stringList(value, field)
140
148
  }
141
149
 
150
+ /** 阶段定义校验:非空数组,每项 { name: string, tools: 非空字符串数组 }。 */
151
+ function stageDefs(value, field) {
152
+ if (value === undefined) return undefined
153
+ if (!Array.isArray(value) || value.length === 0) {
154
+ throw new TypeError(`${name}: ${field} must be a non-empty array of { name, tools }`)
155
+ }
156
+ return value.map((stage, index) => {
157
+ const label = `${field}[${index}]`
158
+ if (stage === null || typeof stage !== 'object' || Array.isArray(stage)) {
159
+ throw new TypeError(`${name}: ${label} must be an object`)
160
+ }
161
+ if (typeof stage.name !== 'string' || stage.name.length === 0) {
162
+ throw new TypeError(`${name}: ${label}.name must be a non-empty string`)
163
+ }
164
+ const tools = stringList(stage.tools, `${label}.tools`)
165
+ return { name: stage.name, tools }
166
+ })
167
+ }
168
+
142
169
 
143
170
  /**
144
171
  * Validate the optional first-request output cap. `undefined` means NO cap:
@@ -163,16 +190,84 @@ export function apply(ctx, config) {
163
190
  && source.promoteOn !== undefined && source.promoteOn !== 'either') {
164
191
  throw new TypeError(`${name}: promoteGate/promoteAfterFirstResponse 门控模式固定 either 晋升语义,promoteOn 必须省略或为 "either"`)
165
192
  }
166
- const bootstrapTools = stringList(source.bootstrapTools, 'bootstrapTools')
193
+ // bootstrapTools 允许空数组 = 零工具模式(上游 zero-tool-bootstrap 等价:
194
+ // 首请求 tools: [] 产生最深 "we" 轨迹,assistant/message 后晋升)。
195
+ const bootstrapTools = stringList(source.bootstrapTools, 'bootstrapTools', true)
167
196
  const promoteEvents = parsePromoteOn(name, source.promoteOn)
168
197
  const bootstrapMaxTokens = optionalPositiveInt(source.bootstrapMaxTokens, 'bootstrapMaxTokens')
169
198
  const includeSubagents = booleanOption(name, source.includeSubagents, 'includeSubagents', false)
170
- const usePtcMode = booleanOption(name, source.usePtcMode, 'usePtcMode', true)
171
199
  const personaSectionsOnly = booleanOption(name, source.personaSectionsOnly, 'personaSectionsOnly', false)
172
200
  const workspaceLine = booleanOption(name, source.workspaceLine, 'workspaceLine', false)
173
201
  const phase1FirstCallInstruction = typeof source.phase1FirstCallInstruction === 'string'
174
202
  ? source.phase1FirstCallInstruction
175
203
  : ''
204
+ // 渐进披露(stages 模式):声明时激活多级阶段窄化,替代"两相"窄化语义。
205
+ const stages = stageDefs(source.stages, 'stages')
206
+ const stagePreUnlock = source.stagePreUnlock === undefined
207
+ ? 1
208
+ : Number.isSafeInteger(source.stagePreUnlock) && source.stagePreUnlock >= 0
209
+ ? source.stagePreUnlock
210
+ : (() => { throw new TypeError(`${name}: stagePreUnlock must be an integer >= 0`) })()
211
+ const stageAdvanceTool = typeof source.stageAdvanceTool === 'string' && source.stageAdvanceTool.length > 0
212
+ ? source.stageAdvanceTool
213
+ : 'phase_advance'
214
+ const stageAdvanceDescription = typeof source.stageAdvanceDescription === 'string' && source.stageAdvanceDescription.length > 0
215
+ ? source.stageAdvanceDescription
216
+ : 'Declare the current stage complete and advance to the next stage (unlocks more tools). Call only when the current stage is genuinely done. Pre-unlocked tools are directly callable — using one jumps straight to its stage.'
217
+ const stageSectionTemplate = source.stageSectionTemplate === undefined
218
+ ? 'Stage {{stageName}} ({{stage}}/{{total}}). Unlocked tools: {{unlocked}}. Advance via {{advanceTool}} when the current stage is done.'
219
+ : (typeof source.stageSectionTemplate === 'string' ? source.stageSectionTemplate : '')
220
+ // 工具名 → 阶段索引(直达语义查询表)。
221
+ const toolStage = new Map()
222
+ if (stages !== undefined) {
223
+ stages.forEach((stage, index) => {
224
+ for (const tool of stage.tools) toolStage.set(tool, index)
225
+ })
226
+ }
227
+ /** sessionId -> 当前阶段(durable 推导;compaction 不重置)。 */
228
+ const stageBySession = new Map()
229
+ /** 从 tool/call 事件提取工具名(兼容 source.tool / toolName / name 形状)。 */
230
+ const toolNameOf = (event) => {
231
+ const data = event?.data
232
+ if (data === null || typeof data !== 'object') return ''
233
+ const message = data.message !== null && typeof data.message === 'object' ? data.message : data
234
+ if (message === null || typeof message !== 'object') return ''
235
+ if (typeof message?.source?.tool === 'string') return message.source.tool
236
+ if (typeof message?.toolName === 'string') return message.toolName
237
+ if (typeof message?.name === 'string') return message.name
238
+ return ''
239
+ }
240
+ const advanceStage = (sessionId, toolName) => {
241
+ if (stages === undefined) return
242
+ const current = stageBySession.get(sessionId) ?? 0
243
+ if (toolName === stageAdvanceTool) {
244
+ stageBySession.set(sessionId, Math.min(current + 1, stages.length - 1))
245
+ return
246
+ }
247
+ const owned = toolStage.get(toolName)
248
+ if (owned !== undefined && owned > current) stageBySession.set(sessionId, owned)
249
+ }
250
+ /** 冷启动:从 durable log 重建阶段(resume/reload 同相位)。 */
251
+ const scanStage = (session) => {
252
+ let stage = 0
253
+ for (const event of session.events) {
254
+ if (event?.type !== 'tool/call') continue
255
+ const toolName = toolNameOf(event)
256
+ if (toolName === stageAdvanceTool) stage = Math.min(stage + 1, stages.length - 1)
257
+ else {
258
+ const owned = toolStage.get(toolName)
259
+ if (owned !== undefined && owned > stage) stage = owned
260
+ }
261
+ }
262
+ stageBySession.set(session.id, stage)
263
+ return stage
264
+ }
265
+ const currentStage = (session) => {
266
+ if (stages === undefined) return 0
267
+ if (session === undefined) return 0
268
+ const entry = stageBySession.get(session.id)
269
+ return entry === undefined ? scanStage(session) : entry
270
+ }
176
271
  // Core work set exposed after a compaction, before re-promotion. Empty
177
272
  // means "no compaction recovery catalog": the session stays on the
178
273
  // bootstrap pair until a new promotion signal.
@@ -185,48 +280,14 @@ export function apply(ctx, config) {
185
280
  maxPromoteSteps: source.maxPromoteSteps,
186
281
  })
187
282
 
188
- // prompt-tool patch: optional Code Mode (PTC) wire presentation after promotion.
189
- const presentationBySession = new WeakMap()
190
- const agentBySession = new WeakMap()
191
- const presentationState = (session) => {
192
- let state = presentationBySession.get(session)
193
- if (state === undefined) {
194
- state = { applied: false, disposer: undefined }
195
- presentationBySession.set(session, state)
196
- }
197
- return state
198
- }
199
- const applyCodePresentation = (agent) => {
200
- const session = agent?.session
201
- if (session === undefined) return
202
- const state = presentationState(session)
203
- if (state.applied) return
204
- const tools = agent.ctx?.tools
205
- if (tools === undefined || typeof tools.presentAs !== 'function') return
206
- state.disposer = tools.presentAs('code')
207
- state.applied = true
208
- }
209
- const releaseCodePresentation = (session) => {
210
- const state = presentationBySession.get(session)
211
- if (state === undefined) return
212
- if (typeof state.disposer === 'function') {
213
- try { state.disposer() } catch { /* never brick the session */ }
214
- }
215
- state.disposer = undefined
216
- state.applied = false
217
- }
218
283
  ctx.on('session/event', (session, event) => promotion.observe(session, event))
219
-
220
- ctx.on('session/event', (session, event) => {
221
- if (!usePtcMode) return
222
- if (event.type === 'compaction/end') {
223
- releaseCodePresentation(session)
224
- return
225
- }
226
- if (event.type !== 'step/end' && event.type !== 'turn/end') return
227
- const agent = agentBySession.get(session)
228
- if (agent !== undefined && promotion.status(agent).promoted) applyCodePresentation(agent)
229
- })
284
+ // 渐进披露:阶段推进/直达(stages 模式;durable 推导,compaction 不重置)。
285
+ if (stages !== undefined) {
286
+ ctx.on('session/event', (session, event) => {
287
+ if (event?.type !== 'tool/call') return
288
+ advanceStage(session.id, toolNameOf(event))
289
+ })
290
+ }
230
291
 
231
292
  const warnOnce = createWarnOnce(ctx, name)
232
293
 
@@ -273,18 +334,44 @@ export function apply(ctx, config) {
273
334
  const agent = context.agent
274
335
  if (agent === undefined) return assembled
275
336
  if (agent.session === undefined) return assembled
276
- agentBySession.set(agent.session, agent)
277
337
  if ((agent.session?.header?.delegationDepth ?? 0) > 0 && !includeSubagents) {
278
338
  // 默认:子代理继承完整目录(历史 prompt-tool 默认);
279
339
  // includeSubagents=true 时落到下方正常相位逻辑(首轮裁剪 + 晋升)。
280
- if (usePtcMode) applyCodePresentation(agent)
281
340
  return assembled
282
341
  }
342
+ if (stages !== undefined) {
343
+ // 渐进披露:目录 = 当前阶段 + 预放档工具;阶段 section 按模板注入(文案参数化)。
344
+ const stage = currentStage(agent.session)
345
+ const keep = new Set()
346
+ const upper = Math.min(stage + stagePreUnlock, stages.length - 1)
347
+ for (let i = 0; i <= upper; i += 1) {
348
+ for (const toolName of stages[i].tools) keep.add(toolName)
349
+ }
350
+ let next = keepTools(assembled, keep, true)
351
+ if (stageSectionTemplate.length > 0) {
352
+ const unlocked = stages
353
+ .slice(0, upper + 1)
354
+ .flatMap((s) => s.tools)
355
+ .join(', ')
356
+ const text = stageSectionTemplate
357
+ .replaceAll('{{stage}}', String(stage + 1))
358
+ .replaceAll('{{stageName}}', stages[stage].name)
359
+ .replaceAll('{{unlocked}}', unlocked)
360
+ .replaceAll('{{total}}', String(stages.length))
361
+ .replaceAll('{{advanceTool}}', stageAdvanceTool)
362
+ next = {
363
+ ...next,
364
+ sections: Array.isArray(next.sections)
365
+ ? [...next.sections, { name: 'stage-status', text }]
366
+ : [{ name: 'stage-status', text }],
367
+ }
368
+ }
369
+ return next
370
+ }
283
371
  const status = promotion.status(agent)
284
372
  if (status.promoted) {
285
- // prompt-tool patch: both modes keep the assembled catalog after promotion.
286
- // usePtcMode switches the wire presentation instead of narrowing resident tools.
287
- if (usePtcMode) applyCodePresentation(agent)
373
+ // prompt-tool patch: keep the assembled catalog after promotion
374
+ // (Code Mode presentation is the companion code-presentation row's job).
288
375
  return workspaceLine ? withWorkspaceLine(assembled, agent) : assembled
289
376
  }
290
377
  // Controlled phase: the bootstrap pair; after a compaction, plus the
@@ -293,7 +380,15 @@ export function apply(ctx, config) {
293
380
  // note), so this filter touches only the tool catalog.
294
381
  const { boundary } = status
295
382
  const keep = new Set(bootstrapTools)
296
- if (boundary >= 0) for (const toolName of compactionTools) keep.add(toolName)
383
+ if (boundary >= 0) {
384
+ for (const toolName of compactionTools) keep.add(toolName)
385
+ // 零工具模式:compaction 回退补 shell(对齐上游 zero-tool-bootstrap),
386
+ // 否则 mid-task 模型无 shell 可用。
387
+ if (bootstrapTools.length === 0) {
388
+ const available = new Set(assembled.tools.map((tool) => tool.name))
389
+ for (const toolName of ['bash', 'pwsh']) if (available.has(toolName)) keep.add(toolName)
390
+ }
391
+ }
297
392
  let next = keepTools(assembled, keep, true)
298
393
  if (personaSectionsOnly) {
299
394
  // Phase-1 提示词段只留 persona(plan-mode 策略等晋升后才恢复)。
@@ -347,4 +442,35 @@ export function apply(ctx, config) {
347
442
  }
348
443
  }, { prepend: true })
349
444
  }
445
+
446
+ // 渐进披露:阶段推进工具(stages 模式)。推进状态由事件流推导(observe),
447
+ // 本工具只返回推进结果文案——绝不手改 state(durable 单一真相)。
448
+ if (stages !== undefined) {
449
+ ctx.tools.register({
450
+ name: stageAdvanceTool,
451
+ description: stageAdvanceDescription,
452
+ parameters: { type: 'object', properties: {}, additionalProperties: false },
453
+ output: {
454
+ schema: {
455
+ type: 'object',
456
+ additionalProperties: false,
457
+ properties: {
458
+ text: { type: 'string' },
459
+ },
460
+ required: ['text'],
461
+ },
462
+ render: (_args, value) => [{ type: 'text', text: value.text }],
463
+ },
464
+ async execute(_args, exec) {
465
+ const session = exec?.agent?.session
466
+ if (session === undefined) return { text: 'phase_advance: no session context' }
467
+ const stage = currentStage(session)
468
+ const next = Math.min(stage + 1, stages.length - 1)
469
+ const text = stage >= stages.length - 1
470
+ ? `Already at the final stage (${stages[stage].name}) — all tools unlocked.`
471
+ : `Advanced to stage ${next + 1}/${stages.length} (${stages[next].name}). Unlocked: ${stages[next].tools.join(', ')}.`
472
+ return { text }
473
+ },
474
+ })
475
+ }
350
476
  }