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.
- package/README.md +185 -46
- package/engine/THIRD_PARTY_LICENSES +25 -0
- package/engine/actions.mjs +931 -0
- package/engine/anchor-match.mjs +6 -3
- package/engine/character-tools.mjs +11 -0
- package/engine/classify-task.mjs +18 -0
- package/engine/compaction-epoch.mjs +41 -20
- package/engine/compositions/library/agent-instructions.yml +9 -0
- package/engine/compositions/library/command-goal.yml +2 -6
- package/engine/compositions/library/compaction.yml +2 -32
- package/engine/compositions/library/delegation-ptc.yml +58 -0
- package/engine/compositions/library/delegation.yml +5 -38
- package/engine/compositions/library/persistent-shell.yml +4 -13
- package/engine/compositions/library/planning.yml +8 -23
- package/engine/compositions/library/skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/skill-filesystem.yml +2 -8
- package/engine/compositions/library/tool-ask-user.yml +2 -3
- package/engine/compositions/library/tool-bash.yml +4 -12
- package/engine/compositions/library/tool-cordis.yml +7 -0
- package/engine/compositions/library/tool-fs-search.yml +2 -11
- package/engine/compositions/library/tool-fs.yml +2 -5
- package/engine/compositions/library/tool-goal.yml +2 -6
- package/engine/compositions/library/tool-jobs.yml +2 -18
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +8 -0
- package/engine/compositions/library/tool-plugin-manager.yml +8 -0
- package/engine/compositions/library/tool-present.yml +7 -0
- package/engine/compositions/library/tool-presentation.yml +9 -0
- package/engine/compositions/library/tool-pwsh.yml +2 -6
- package/engine/compositions/library/tool-skill.yml +7 -0
- package/engine/compositions/library/tool-todo.yml +2 -4
- package/engine/compositions/library/tool-web.yml +2 -1
- package/engine/compositions/source/local/character-tools.yml +5 -0
- package/engine/compositions/source/local/declared-triggers.yml +12 -0
- package/engine/compositions/source/local/filesystem-editor.yml +30 -0
- package/engine/compositions/source/local/instruction-hint.yml +15 -0
- package/engine/compositions/{library/official-persistent-shell.yml → source/local/persistent-shell-posix.yml} +10 -8
- package/engine/compositions/source/local/prompt-config-engine.yml +3 -9
- package/engine/compositions/source/local/run-code-env.yml +4 -7
- package/engine/compositions/source/local/session-var-tools.yml +5 -0
- package/engine/compositions/source/local/skill-search.yml +3 -9
- package/engine/compositions/source/local/subagent-tool-policy.yml +21 -0
- package/engine/compositions/source/local/tool-bash-disabled.yml +9 -0
- package/engine/compositions/source/local/tool-config-engine.yml +8 -0
- package/engine/compositions/source/local/tool-git-bash.yml +15 -0
- package/engine/compositions/source/local/world-book-tools.yml +5 -0
- package/engine/condition.mjs +83 -0
- package/engine/declared-triggers.mjs +93 -0
- package/engine/executor.mjs +364 -185
- package/engine/fields.mjs +219 -0
- package/engine/fillers.mjs +12 -141
- package/engine/host-package.mjs +51 -0
- package/engine/instruction-hint.mjs +285 -0
- package/engine/interpolate.mjs +157 -29
- package/engine/layers.mjs +495 -75
- package/engine/predicates.mjs +753 -0
- package/engine/prompt-config-engine.mjs +63 -8
- package/engine/run-code-env.mjs +48 -31
- package/engine/schema.mjs +444 -103
- package/engine/sdk-strip.mjs +365 -0
- package/engine/session-var-tools.mjs +11 -0
- package/engine/session-vars.mjs +8 -6
- package/engine/shared.mjs +137 -0
- package/engine/skill-search.mjs +34 -21
- package/engine/st-macros.d.mts +22 -0
- package/engine/st-macros.mjs +208 -0
- package/engine/st-render.mjs +105 -0
- package/engine/st-world-book.d.mts +10 -0
- package/engine/st-world-book.mjs +259 -0
- package/engine/strategies.mjs +31 -20
- package/engine/subagent-tool-policy-core.mjs +400 -0
- package/engine/subagent-tool-policy.mjs +327 -0
- package/engine/tool-config-engine.mjs +207 -145
- package/engine/tool-definition.mjs +143 -0
- package/engine/{custom-bash.mjs → tool-git-bash.mjs} +38 -13
- package/engine/trigger-editor-meta.mjs +48 -0
- package/engine/trigger-spec.mjs +257 -0
- package/engine/trigger.mjs +304 -0
- package/engine/world-book-tools.mjs +11 -0
- package/lib/client.js +16495 -5456
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +628 -382
- package/lib/index.mjs +5391 -2942
- package/lib/paths-CY5psBqE.d.mts +1233 -0
- package/lib/preset-package-BnJqrU4R.mjs +16792 -0
- package/lib/preset-transfer.d.mts +2 -0
- package/lib/preset-transfer.mjs +2 -0
- package/package.json +91 -49
- package/preset/pt-cordis/preset.yml +43 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +29 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +501 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +38 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +60 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +13 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +22 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +3 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +2 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +15 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +8 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +7 -0
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +84 -0
- package/preset/pt-custom/preset.yml +15 -0
- package/preset/pt-minimal/preset.yml +27 -0
- package/preset/pt-ptc/preset.yml +45 -0
- package/preset/pt-standard/preset.yml +43 -0
- package/templates/14-first-turn-anchor.yml +4 -4
- package/templates/15-guide-auto.yml +3 -3
- package/templates/16-custom-fallback.yml +1 -0
- package/templates/18-placeholder-env-facts.yml +3 -3
- package/templates/20-system-section.yml +3 -2
- package/templates/65-turn-stop.yml +18 -0
- package/templates/66-subagent-start.yml +14 -0
- package/templates/75-subagent-end.yml +15 -0
- package/templates/tools/10-shell-command.yml +1 -1
- package/templates/tools/40-delegate-character.yml +1 -1
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/templates/tools/60-delegate-session-var.yml +1 -1
- package/templates/tools/70-delegate-worldbook-upsert.yml +1 -1
- package/engine/anchor-turn.mjs +0 -68
- package/engine/code-presentation.mjs +0 -103
- package/engine/compositions/library/anchor-turn.yml +0 -10
- package/engine/compositions/library/bootstrap-filesystem.yml +0 -19
- package/engine/compositions/library/code-presentation.yml +0 -8
- package/engine/compositions/library/context-gate.yml +0 -39
- package/engine/compositions/library/cot-drip.yml +0 -11
- package/engine/compositions/library/custom-bash.yml +0 -14
- package/engine/compositions/library/deliberation-gate.yml +0 -11
- package/engine/compositions/library/official-agent-instructions.yml +0 -17
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +0 -10
- package/engine/compositions/library/official-tool-bash.yml +0 -6
- package/engine/compositions/library/official-tool-cordis.yml +0 -13
- package/engine/compositions/library/official-tool-presentation.yml +0 -7
- package/engine/compositions/library/official-tool-skill.yml +0 -14
- package/engine/compositions/library/persona.yml +0 -13
- package/engine/compositions/library/prompt-config-engine.yml +0 -16
- package/engine/compositions/library/run-code-env.yml +0 -15
- package/engine/compositions/library/skill-search.yml +0 -14
- package/engine/compositions/library/str-replace-editor.yml +0 -10
- package/engine/compositions/library/tool-bootstrap.yml +0 -14
- package/engine/compositions/library/tool-config-engine.yml +0 -9
- package/engine/compositions/library/tool-filter.yml +0 -8
- package/engine/compositions/source/local/code-presentation.yml +0 -8
- package/engine/compositions/source/local/context-gate.yml +0 -36
- package/engine/compositions/source/local/custom-bash.yml +0 -11
- package/engine/compositions/source/local/str-replace-editor.yml +0 -10
- package/engine/compositions/source/local/tool-bootstrap.yml +0 -11
- package/engine/compositions/source/local/tool-filter.yml +0 -8
- package/engine/context-gate.mjs +0 -305
- package/engine/cot-drip.mjs +0 -137
- package/engine/deliberation-gate.mjs +0 -127
- package/engine/tool-bootstrap.mjs +0 -476
- package/engine/tool-filter.mjs +0 -80
- package/lib/preset-core.d.mts +0 -10
- package/lib/preset-core.mjs +0 -77
- package/lib/prompt-configs-BnQKRHVb.mjs +0 -884
- package/lib/prompt-configs-CN0sJTUL.d.mts +0 -193
- package/preset/anchored/preset.yml +0 -296
- package/preset/creative/preset.yml +0 -67
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +0 -420
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +0 -165
- package/preset/custom/preset.yml +0 -12
- package/preset/liangshen/preset.yml +0 -75
- package/preset/minimal/preset.yml +0 -45
- package/preset/ptc/preset.yml +0 -58
- package/preset/standard/preset.yml +0 -57
- package/skills/manifest.json +0 -7
- package/skills/sandboxmod/SKILL.md +0 -49
- package/skills/web ui/SKILL.md +0 -42
package/engine/context-gate.mjs
DELETED
|
@@ -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
|
-
}
|
package/engine/cot-drip.mjs
DELETED
|
@@ -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
|
-
}
|