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
@@ -1,305 +0,0 @@
1
- /**
2
- * anchored-context-gate — reusable unified injection control for ANY preset.
3
- *
4
- * Mount this one plugin to keep a session's first model request free of
5
- * auto-injected context, whatever its source, and to have every injection
6
- * return on the second round. It intercepts the harness's two unified
7
- * injection paths — not a per-source denylist — so it covers sources that do
8
- * not exist yet:
9
- *
10
- * a. RUNTIME CONTEXT (system-prompt/assemble): while the session is
11
- * unpromoted, the assembly's `contexts` are blanked. That covers the
12
- * WHOLE `SystemPrompt.context()` family — the sandbox and approval
13
- * policy snapshots and any third-party context provider — without
14
- * enumerating them. The loop's own snapshot projection then emits no
15
- * message during the gate (no snapshot ever existed), and at the first
16
- * promoted request it emits exactly ONE fresh snapshot: "minimal first
17
- * round, inject on the second round" falls out of the projection's
18
- * diffing, with no reinjection logic here.
19
- *
20
- * b. STEP MESSAGES (agent/pre-step): the waterfall payload carries the
21
- * CLAIMED message batch (the inbox messages this step owns). While
22
- * unpromoted, the gate applies the `allowKinds` whitelist when declared
23
- * (claimed batch plus the listed kinds pass, everything any listener
24
- * appended is stripped). When `allowKinds` is UNCONFIGURED the pre-step
25
- * path performs no kind filtering — the official deepseek-harness
26
- * `agent/pre-step` default (claimed batch plus every injection passes);
27
- * the assembly path still blanks runtime-context contributions. Durable
28
- * history (compaction summaries included) never passes through this
29
- * gate: it enters the request via the session surface, not the
30
- * pre-step waterfall.
31
- *
32
- * The phase is the same epoch-aware promotion machine the anchored presets
33
- * use (see compaction-epoch.mjs): a durable `tool/call` and/or
34
- * `assistant/message` (per `promoteOn`, default `either`) promotes, and a
35
- * `compaction/end` boundary demotes again — the first post-compaction request
36
- * is a "second first request" and is gated the same way. Derived from durable
37
- * events, so resume and reload preserve it.
38
- *
39
- * SUBAGENTS: by default subagents (delegationDepth > 0) skip the gate (their
40
- * first request already sees full context). `includeSubagents: true` gates
41
- * them too — their first request is clean and their own first reply or tool
42
- * call opens the gate — so a delegation cannot reintroduce an uncontrolled
43
- * first request. Keep this flag in sync with the tool-bootstrap row's
44
- * includeSubagents when both rows are present (anchored: both false;
45
- * liangshen: both true).
46
- *
47
- * CONFIG:
48
- * - `promoteOn`: 'either' (default) | 'tool-call' | 'assistant-message'.
49
- * - `includeSubagents`: boolean, default false.
50
- * - `enabled`: boolean, default true. `false` disables both interception
51
- * paths (A/B testing without touching the row set).
52
- * - `allowKinds`: message `source.kind` names allowed beyond the claimed
53
- * batch. UNCONFIGURED = official pre-step behavior (no kind filtering:
54
- * claimed batch plus every injection pass, matching deepseek-harness
55
- * `agent/pre-step` default). An explicitly empty array keeps ONLY the
56
- * claimed batch; a non-empty array is the whitelist gate.
57
- * - `messageSources`: (liangshen quarantine) strict phase-1 whitelist —
58
- * when set, ONLY messages whose `source.kind` is in the list pass the
59
- * pre-step gate (claimed batch included), replacing the allowKinds
60
- * semantics. Default unset = allowKinds semantics.
61
- * - `deferredSources` + `deferredGraceSteps`: (liangshen) after promotion,
62
- * the listed injected kinds are filtered for the first N steps
63
- * (default 0 = no deferral).
64
- * - `instructionHint`: (liangshen, issue #388) after promotion, replace the
65
- * full-text agent-instructions dump with a one-time non-imperative hint
66
- * naming the reference files; later dumps are dropped. Default false.
67
- *
68
- * ROW ORDER: mount this row FIRST in the composition. Waterfall after-next
69
- * transforms apply in reverse registration order, so registering first (plus
70
- * the pre-step listener's `prepend: true`) makes the gate the outermost
71
- * transform — nothing registered later re-injects past it.
72
- *
73
- * Robustness: both filters degrade to "keep everything" on their own
74
- * failures — a gate bug must never eat the user's context — and invalid
75
- * config fails at apply time, i.e. at preset mount, where it is visible.
76
- */
77
-
78
- import { createEpochPromotion } from './compaction-epoch.mjs'
79
- import { booleanOption, createWarnOnce, parsePromoteOn, validateConfig } from './shared.mjs'
80
-
81
- /** Cordis plugin name used by loader diagnostics. */
82
- export const name = 'anchored-context-gate'
83
-
84
- /**
85
- * Deliberately NO inject list: the listeners only touch services at event
86
- * time, and applying without an inject lets this row register before the
87
- * context-injecting plugins (dsh-agent-instructions, dsh-tool-skill, host
88
- * plane policy projections) when it sits first in the composition.
89
- */
90
- export const inject = []
91
-
92
- /** Every config key this plugin accepts — anything else is a typo. */
93
- const ALLOWED_KEYS = new Set([
94
- 'promoteOn', 'includeSubagents', 'enabled', 'allowKinds',
95
- 'messageSources', 'deferredSources', 'deferredGraceSteps', 'instructionHint',
96
- ])
97
-
98
- /** agent-instructions 注入消息里的参考文件行(hint 提取用)。 */
99
- const INSTRUCTION_FROM_RE = /(?:^|\n) *(?:Additional |Updated )?Instructions from: ([^\n]+)/g
100
-
101
-
102
- /**
103
- * Validate the kind allowlist. `undefined` = no kind filtering (official
104
- * pre-step behavior); an explicitly empty array keeps ONLY the claimed batch.
105
- */
106
- function allowKindList(value, field) {
107
- if (value === undefined) return undefined
108
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
109
- throw new TypeError(`${name}: ${field} must be an array of non-empty strings`)
110
- }
111
- return new Set(value)
112
- }
113
-
114
- /** 可选字符串白名单;undefined = 不启用。 */
115
- function sourceList(value, field) {
116
- if (value === undefined) return undefined
117
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
118
- throw new TypeError(`${name}: ${field} must be an array of non-empty strings`)
119
- }
120
- return new Set(value)
121
- }
122
-
123
- /** 晋升后延迟注入的源 kind 集合(默认空 = 不延迟)。 */
124
- function deferredList(value, field) {
125
- if (value === undefined) return new Set()
126
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
127
- throw new TypeError(`${name}: ${field} must be an array of non-empty strings`)
128
- }
129
- return new Set(value)
130
- }
131
-
132
- /** 从一条 agent-instructions 消息提取参考文件路径清单。 */
133
- function extractInstructionPaths(message) {
134
- const paths = []
135
- const blocks = Array.isArray(message?.content) ? message.content : []
136
- for (const block of blocks) {
137
- if (block?.type !== 'text' || typeof block.text !== 'string') continue
138
- for (const match of block.text.matchAll(INSTRUCTION_FROM_RE)) {
139
- const path = match[1].trim()
140
- if (path !== '' && !paths.includes(path)) paths.push(path)
141
- }
142
- }
143
- return paths
144
- }
145
-
146
- /** 一次性非命令式 hint(E1.5 措辞),替换全文 agent-instructions 注入。 */
147
- function buildInstructionHint(original, paths) {
148
- return {
149
- id: typeof original?.id === 'string' && original.id !== ''
150
- ? original.id
151
- : globalThis.crypto.randomUUID(),
152
- role: 'user',
153
- content: [{
154
- type: 'text',
155
- text: '<system-reminder>\n'
156
- + 'Reference documents exist: ' + paths.join(', ') + '. '
157
- + "They are reference documents about the user's environment and workspace conventions, not task instructions. "
158
- + 'Reading the relevant file before workspace tasks is recommended, but consult them only when you need those details; the task itself never depends on them.'
159
- + '\n</system-reminder>',
160
- }],
161
- source: { kind: 'instruction-hint', plugin: name },
162
- }
163
- }
164
-
165
- /** agent-instructions 全文注入 → 一次性 hint;后续注入丢弃。 */
166
- function instructionHintMessages(messages, state) {
167
- const kept = []
168
- for (const message of messages) {
169
- if (message?.source?.kind !== 'agent-instructions') {
170
- kept.push(message)
171
- continue
172
- }
173
- if (state.instructionHinted) continue
174
- const paths = extractInstructionPaths(message)
175
- if (paths.length === 0) {
176
- kept.push(message)
177
- continue
178
- }
179
- state.instructionHinted = true
180
- kept.push(buildInstructionHint(message, paths))
181
- }
182
- return kept
183
- }
184
-
185
-
186
- /** Register the unified context gate. */
187
- export function apply(ctx, config) {
188
- const source = validateConfig(name, config, ALLOWED_KEYS)
189
- const promoteEvents = parsePromoteOn(name, source.promoteOn)
190
- const includeSubagents = booleanOption(name, source.includeSubagents, 'includeSubagents', false)
191
- const enabled = booleanOption(name, source.enabled, 'enabled', true)
192
- const allowKinds = allowKindList(source.allowKinds, 'allowKinds')
193
- const messageSources = sourceList(source.messageSources, 'messageSources')
194
- const deferredSources = deferredList(source.deferredSources, 'deferredSources')
195
- const deferredGraceSteps = source.deferredGraceSteps === undefined
196
- ? 0
197
- : Number.isSafeInteger(source.deferredGraceSteps) && source.deferredGraceSteps >= 0
198
- ? source.deferredGraceSteps
199
- : (() => { throw new TypeError(`${name}: deferredGraceSteps must be an integer >= 0`) })()
200
- const instructionHint = booleanOption(name, source.instructionHint, 'instructionHint', false)
201
-
202
- const promotion = createEpochPromotion(promoteEvents, { includeSubagents })
203
- /** sessionId -> { steps, instructionHinted }(晋升后延迟/转换状态)。 */
204
- const deferredBySession = new WeakMap()
205
- const deferredState = (session) => {
206
- let entry = deferredBySession.get(session)
207
- if (entry === undefined) {
208
- entry = { steps: 0, instructionHinted: false }
209
- deferredBySession.set(session, entry)
210
- }
211
- return entry
212
- }
213
- ctx.on('session/event', (session, event) => promotion.observe(session, event))
214
- ctx.on('session/event', (session, event) => {
215
- if (event.type === 'compaction/end') deferredBySession.delete(session)
216
- })
217
-
218
- const warnOnce = createWarnOnce(ctx, name)
219
-
220
- // Path (a): blank the dynamic runtime-context contributions while the
221
- // session is unpromoted. Covers the whole SystemPrompt.context() family
222
- // without enumerating it; the loop's snapshot projection then stays silent
223
- // and diffs exactly ONE fresh snapshot in at the first promoted request.
224
- ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
225
- // Downstream errors propagate untouched; only this filter's own logic is guarded.
226
- const assembled = await next()
227
- if (enabled === false) return assembled
228
- try {
229
- if (promotion.status(context.agent).promoted) return assembled
230
- if (!Array.isArray(assembled.contexts) || assembled.contexts.length === 0) return assembled
231
- return { ...assembled, contexts: [] }
232
- } catch (error) {
233
- // A gate bug must never break assembly: degrade to the assembled value.
234
- warnOnce(`${name}: runtime-context suppression failed, keeping contexts: ${String((error && error.message) || error)}`)
235
- return assembled
236
- }
237
- })
238
-
239
- // Path (b): claimed-baseline deny on the pre-step waterfall. The payload's
240
- // `messages` is the batch this step CLAIMED from the inbox — the baseline
241
- // every injection appends to. Keep that baseline plus the kind allowlist,
242
- // strip every appended message regardless of its source identity.
243
- ctx.on('agent/pre-step', async ({ agent, messages: claimed }, next) => {
244
- // Downstream errors propagate untouched; only this filter's own logic is guarded.
245
- const decision = await next()
246
- if (decision.kind === 'reject') return decision
247
- if (enabled === false) return decision
248
- try {
249
- if (promotion.status(agent).promoted) return decision
250
- if (messageSources !== undefined) {
251
- // liangshen quarantine:phase-1 只放行声明的 source.kind(含 claimed 批)。
252
- const kept = (decision.messages ?? []).filter((message) => messageSources.has(message?.source?.kind))
253
- return kept.length === decision.messages.length ? decision : { ...decision, messages: kept }
254
- }
255
- // allowKinds 未声明 = 官方 pre-step 行为(不过滤注入消息,与 deepseek-harness 一致)。
256
- if (allowKinds === undefined) return decision
257
- if (!Array.isArray(decision.messages)) return decision
258
- if (!Array.isArray(claimed)) return decision
259
- const baseline = new Set(claimed)
260
- const baselineIds = new Set(claimed
261
- .map((message) => message?.id)
262
- .filter((id) => id !== undefined && id !== null))
263
- const kept = decision.messages.filter((message) =>
264
- baseline.has(message)
265
- || (message?.id !== undefined && message?.id !== null && baselineIds.has(message.id))
266
- || allowKinds.has(message?.source?.kind),
267
- )
268
- return kept.length === decision.messages.length ? decision : { ...decision, messages: kept }
269
- } catch (error) {
270
- // A gate bug must never eat context: degrade to keeping every message.
271
- warnOnce(`${name}: pre-step gate failed, keeping injected context: ${String((error && error.message) || error)}`)
272
- return decision
273
- }
274
- }, { prepend: true })
275
-
276
- // 晋升后的注入控制:deferredSources 延迟 N 步 + instructionHint 转换。
277
- // 与 phase-1 门控共用同一 pre-step 监听器会互相覆盖,独立注册第二个监听器。
278
- ctx.on('agent/pre-step', async ({ agent }, next) => {
279
- const decision = await next()
280
- if (decision.kind === 'reject') return decision
281
- if (enabled === false) return decision
282
- try {
283
- if (!promotion.status(agent).promoted) return decision
284
- if (!Array.isArray(decision.messages)) return decision
285
- if (agent?.session === undefined) return decision
286
- const state = deferredState(agent.session)
287
- let result = decision
288
- if (deferredGraceSteps > 0 && deferredSources.size > 0 && state.steps < deferredGraceSteps) {
289
- state.steps += 1
290
- const kept = result.messages.filter((message) => !deferredSources.has(message?.source?.kind))
291
- result = kept.length === result.messages.length ? result : { ...result, messages: kept }
292
- }
293
- if (instructionHint) {
294
- // 1 换 1 的转换不能按长度判断(长度相同会误判为无变化),
295
- // instructionHintMessages 本身幂等保留非目标消息,直接采用结果。
296
- result = { ...result, messages: instructionHintMessages(result.messages, state) }
297
- }
298
- return result
299
- } catch (error) {
300
- // 转换失败不阻断会话:保留原消息。
301
- warnOnce(`${name}: promoted injection control failed, keeping messages: ${String((error && error.message) || error)}`)
302
- return decision
303
- }
304
- }, { prepend: true })
305
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * cot-drip — 执行期深思维持节拍(上游 dsh-anchored-standard 移植, MIT)。
3
- *
4
- * 锚定模式在每轮开场有深思考,但长工具循环中深思会衰减(后期步骤退化
5
- * 回 "Let me…")。本模块每 N 次工具结果向对话滴入一条短 user 提醒——
6
- * 永不阻塞、永不报错、不碰工具目录:
7
- *
8
- * `tools/post-execute` → { kind: 'accept', additionalContexts: [notice] }
9
- *
10
- * harness 把 additionalContexts 作为 durable user 消息追加在整批工具结果
11
- * 之后,落在下一请求的规划节拍位(与 Code Mode 嵌套上下文同形状)。
12
- * 模型读到后以一句 "We …" 重申剩余目标并继续。
13
- *
14
- * 节奏刻意温和:默认 every: 4 次结果、每轮最多 maxPerTurn: 1 条;
15
- * every: 0 禁用。计数在 await 前同步自增(并行调用无法竞态越过节奏);
16
- * 轮边界由 durable turn/start + assistant/chunk 双路跟踪(无轮号时降级
17
- * session 全局);子代理默认不滴(brief 即计划)。
18
- */
19
-
20
- import { booleanOption, validateConfig } from './shared.mjs'
21
-
22
- /** Cordis plugin name used by loader diagnostics. */
23
- export const name = 'cot-drip'
24
-
25
- /** 默认提醒文本——一条规划节拍,措辞维持 "We" 语态。 */
26
- export const DRIP_TEXT = [
27
- 'Progress check: before the next action, restate in one "We …" sentence what remains of the goal and why the next step is the right one.',
28
- ].join(' ')
29
-
30
- /** Every config key this plugin accepts — anything else is a typo. */
31
- const ALLOWED_KEYS = new Set(['enabled', 'every', 'maxPerTurn', 'includeSubagents', 'text'])
32
-
33
- function parseCounter(value, field, fallback, minimum) {
34
- if (value === undefined) return fallback
35
- if (!Number.isInteger(value) || value < minimum) {
36
- throw new TypeError(`${name}: ${field} must be an integer >= ${minimum}; got ${JSON.stringify(value)}`)
37
- }
38
- return value
39
- }
40
-
41
- /** 注册执行后深思滴入。 */
42
- export function apply(ctx, config) {
43
- const source = validateConfig(name, config, ALLOWED_KEYS)
44
- if (source.enabled === false) return
45
- const every = parseCounter(source.every, 'every', 4, 0)
46
- const maxPerTurn = parseCounter(source.maxPerTurn, 'maxPerTurn', 1, 1)
47
- const includeSubagents = booleanOption(name, source.includeSubagents, 'includeSubagents', false)
48
- const text = typeof source.text === 'string' && source.text.length > 0 ? source.text : DRIP_TEXT
49
-
50
- /** sessionId -> { results, drips, lastTurn } — 每轮计数。 */
51
- const state = new Map()
52
-
53
- const countersOf = (sessionId) => {
54
- let entry = state.get(sessionId)
55
- if (entry === undefined) {
56
- entry = { results: 0, drips: 0, lastTurn: undefined }
57
- state.set(sessionId, entry)
58
- }
59
- return entry
60
- }
61
-
62
- let warned = false
63
- const warnOnce = (message) => {
64
- if (warned) return
65
- warned = true
66
- try {
67
- ctx.logger.warn(message)
68
- } catch {
69
- // Logger unavailable — the guard exists only to avoid spamming.
70
- }
71
- }
72
-
73
- // 轮跟踪:turn/start 重置计数;无可用轮号时用 assistant/chunk 的新轮。
74
- ctx.on('session/event', (session, event) => {
75
- if (session === undefined || session.id === undefined) return
76
- if (event.type === 'turn/start') {
77
- const turn = event.data?.turn
78
- const entry = countersOf(session.id)
79
- if (entry.lastTurn !== turn) {
80
- entry.lastTurn = typeof turn === 'number' ? turn : entry.lastTurn
81
- entry.results = 0
82
- entry.drips = 0
83
- }
84
- return
85
- }
86
- if (event.type === 'assistant/chunk') {
87
- const turn = event.data?.turn
88
- if (typeof turn !== 'number') return
89
- const entry = countersOf(session.id)
90
- if (entry.lastTurn === undefined || turn > entry.lastTurn) {
91
- entry.lastTurn = turn
92
- entry.results = 0
93
- entry.drips = 0
94
- }
95
- }
96
- })
97
-
98
- ctx.on('tools/post-execute', async (exec, result, next) => {
99
- // await 前同步计数,并行调用不能竞态越过节奏。
100
- const session = exec?.agent?.session
101
- const eligible = session !== undefined
102
- && session.id !== undefined
103
- && (includeSubagents || (session.header?.delegationDepth ?? 0) === 0)
104
- const entry = eligible ? countersOf(session.id) : undefined
105
- if (entry !== undefined) entry.results += 1
106
- const due = entry !== undefined
107
- && every > 0
108
- && entry.results % every === 0
109
- && entry.drips < maxPerTurn
110
-
111
- const decision = await next()
112
- try {
113
- if (!due || decision?.kind !== 'accept') return decision
114
- entry.drips += 1
115
- const notice = {
116
- id: typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
117
- ? crypto.randomUUID()
118
- : `cot-drip-${Date.now()}-${Math.random().toString(36).slice(2)}`,
119
- role: 'user',
120
- content: [{ type: 'text', text }],
121
- source: {
122
- kind: 'plugin',
123
- plugin: name,
124
- form: 'notice',
125
- summary: 'deliberation maintenance beat',
126
- },
127
- }
128
- return {
129
- ...decision,
130
- additionalContexts: [...(decision.additionalContexts ?? []), notice],
131
- }
132
- } catch (error) {
133
- warnOnce(`${name}: drip injection failed, keeping the plain result: ${String((error && error.message) || error)}`)
134
- return decision
135
- }
136
- })
137
- }
@@ -1,127 +0,0 @@
1
- /**
2
- * deliberation-gate — 轨迹深度门(上游 dsh-anchored-standard 移植, MIT)。
3
- *
4
- * 完整目录保持可见可调(会话观感正常),但首工具调用前深思不足就拒绝
5
- * 一次。以 durable `assistant/chunk` 的 text/reasoning delta 累计长度作为
6
- * "深思深度"代理(adapter 无 logprobs,观测层只有流式 chunk)。
7
- *
8
- * 行为:
9
- * - session/event 累计每轮流式长度;tools/pre-execute 时当前轮深度
10
- * < minChars(默认 400)→ { kind:'deny', reason: gateText }(规划式
11
- * 提示,措辞明示"非工具失败"),每轮最多 maxGatesPerTurn 次(默认 1)。
12
- * - retry 把强制深思带进历史;无流式文本的轮 = 深度 0,恰好门一次
13
- * (fail-safe 向更多深思)。
14
- * - 冷启动从 durable log 扫描(重启保持深度);turn map 修剪
15
- * (MAX_TRACKED_TURNS=8);deny 前无 await(并行调用无法竞态越过预算)。
16
- * - 子代理默认不门控(brief 即计划);includeSubagents: true 同门控。
17
- */
18
-
19
- import { booleanOption, validateConfig } from './shared.mjs'
20
-
21
- /** Cordis plugin name used by loader diagnostics. */
22
- export const name = 'deliberation-gate'
23
-
24
- /** 默认深思下限(每轮首工具调用前,字符数)。 */
25
- export const DEFAULT_MIN_CHARS = 400
26
-
27
- /** 默认门控指令(拒绝回传的规划提示)。 */
28
- export const GATE_TEXT = [
29
- 'Deliberation gate: this turn has not shown its reasoning yet.',
30
- 'Before retrying this tool call, write out your full reasoning in your reply — start with "We", restate the goal, weigh the approaches, and lay out the concrete steps and risks — then issue the tool call again.',
31
- 'This message is a planning prompt, not a tool failure.',
32
- ].join(' ')
33
-
34
- /** 每会话最多保留的轮次深度状态。 */
35
- const MAX_TRACKED_TURNS = 8
36
-
37
- /** Every config key this plugin accepts — anything else is a typo. */
38
- const ALLOWED_KEYS = new Set(['enabled', 'minChars', 'maxGatesPerTurn', 'includeSubagents', 'gateText'])
39
-
40
- function parseCounter(value, field, fallback, minimum) {
41
- if (value === undefined) return fallback
42
- if (!Number.isInteger(value) || value < minimum) {
43
- throw new TypeError(`${name}: ${field} must be an integer >= ${minimum}; got ${JSON.stringify(value)}`)
44
- }
45
- return value
46
- }
47
-
48
- /** 注册轨迹深度门。 */
49
- export function apply(ctx, config) {
50
- const source = validateConfig(name, config, ALLOWED_KEYS)
51
- if (source.enabled === false) return
52
- const minChars = parseCounter(source.minChars, 'minChars', DEFAULT_MIN_CHARS, 0)
53
- const maxGatesPerTurn = parseCounter(source.maxGatesPerTurn, 'maxGatesPerTurn', 1, 1)
54
- const includeSubagents = booleanOption(name, source.includeSubagents, 'includeSubagents', false)
55
- const gateText = typeof source.gateText === 'string' && source.gateText.length > 0 ? source.gateText : GATE_TEXT
56
-
57
- /** sessionId -> { turns: Map<turn, { chars, gates }>, lastTurn } */
58
- const state = new Map()
59
-
60
- /** 实时路径:每条流式 chunk 扩展轮条目。 */
61
- const observeChunk = (sessionId, turn, textLength) => {
62
- let entry = state.get(sessionId)
63
- if (entry === undefined) {
64
- entry = { turns: new Map(), lastTurn: turn }
65
- state.set(sessionId, entry)
66
- }
67
- let turnEntry = entry.turns.get(turn)
68
- if (turnEntry === undefined) {
69
- // 修剪旧轮,长会话不累积状态。
70
- if (entry.turns.size >= MAX_TRACKED_TURNS) {
71
- const oldest = [...entry.turns.keys()].sort((a, b) => a - b).slice(0, entry.turns.size - MAX_TRACKED_TURNS + 1)
72
- for (const key of oldest) entry.turns.delete(key)
73
- }
74
- turnEntry = { chars: 0, gates: 0 }
75
- entry.turns.set(turn, turnEntry)
76
- }
77
- turnEntry.chars += textLength
78
- if (turn > entry.lastTurn) entry.lastTurn = turn
79
- }
80
-
81
- /** 会话深度状态;首见冷扫 durable log(重启保持已流式深度)。 */
82
- const depthOf = (session) => {
83
- let entry = state.get(session.id)
84
- if (entry === undefined) {
85
- entry = { turns: new Map(), lastTurn: -1 }
86
- if (Array.isArray(session.events)) {
87
- for (const event of session.events) {
88
- if (event.type !== 'assistant/chunk') continue
89
- const turn = event.data?.turn
90
- if (typeof turn !== 'number' || !Number.isFinite(turn)) continue
91
- const text = event.data?.chunk?.text
92
- observeChunk(session.id, turn, typeof text === 'string' ? text.length : 0)
93
- }
94
- entry = state.get(session.id) ?? entry
95
- }
96
- if (entry.turns.size === 0) {
97
- // 无任何流式文本:深度视为 0 于哨兵轮,会话恰好门一次后放行。
98
- entry.turns.set(entry.lastTurn, { chars: 0, gates: 0 })
99
- }
100
- state.set(session.id, entry)
101
- }
102
- return entry
103
- }
104
-
105
- // 深度代理:累计 durable assistant/chunk 的流式长度。
106
- ctx.on('session/event', (session, event) => {
107
- if (event.type !== 'assistant/chunk') return
108
- const turn = event.data?.turn
109
- if (typeof turn !== 'number' || !Number.isFinite(turn)) return
110
- const text = event.data?.chunk?.text
111
- observeChunk(session.id, turn, typeof text === 'string' ? text.length : 0)
112
- })
113
-
114
- // 门:同步决策,当前轮累计深度 < minChars 时最多 deny maxGatesPerTurn 次。
115
- ctx.on('tools/pre-execute', (exec, next) => {
116
- const session = exec?.agent?.session
117
- if (session === undefined || session.id === undefined) return next()
118
- if (!includeSubagents && (session.header?.delegationDepth ?? 0) > 0) return next()
119
- const entry = depthOf(session)
120
- const turnEntry = entry.turns.get(entry.lastTurn)
121
- if (turnEntry === undefined) return next()
122
- if (turnEntry.gates >= maxGatesPerTurn) return next()
123
- if (turnEntry.chars >= minChars) return next()
124
- turnEntry.gates += 1
125
- return { kind: 'deny', reason: gateText }
126
- })
127
- }