dsh-plugin-prompt-tool 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -222
- package/engine/anchor-match.mjs +117 -0
- package/engine/compaction-epoch.mjs +139 -0
- package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
- package/engine/compositions/library/compaction.yml +52 -0
- package/engine/compositions/library/context-gate.yml +40 -0
- package/engine/compositions/library/custom-bash.yml +14 -0
- package/engine/compositions/library/delegation.yml +85 -0
- package/engine/compositions/library/official-agent-instructions.yml +17 -0
- package/engine/compositions/library/official-persistent-shell.yml +56 -0
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/official-tool-bash.yml +6 -0
- package/engine/compositions/library/official-tool-cordis.yml +13 -0
- package/engine/compositions/library/official-tool-presentation.yml +7 -0
- package/engine/compositions/library/official-tool-skill.yml +14 -0
- package/engine/compositions/library/persistent-shell.yml +36 -0
- package/engine/compositions/library/persona.yml +9 -0
- package/engine/compositions/library/planning.yml +41 -0
- package/engine/compositions/library/prompt-config-engine.yml +16 -0
- package/engine/compositions/library/run-code-env.yml +15 -0
- package/engine/compositions/library/skill-filesystem.yml +13 -0
- package/engine/compositions/library/skill-search.yml +14 -0
- package/engine/compositions/library/str-replace-editor.yml +10 -0
- package/engine/compositions/library/tool-ask-user.yml +8 -0
- package/engine/compositions/library/tool-bash.yml +16 -0
- package/engine/compositions/library/tool-bootstrap.yml +16 -0
- package/engine/compositions/library/tool-filter.yml +12 -0
- package/engine/compositions/library/tool-fs-search.yml +18 -0
- package/engine/compositions/library/tool-fs.yml +10 -0
- package/engine/compositions/library/tool-goal.yml +19 -0
- package/engine/compositions/library/tool-jobs.yml +23 -0
- package/engine/compositions/library/tool-pwsh.yml +12 -0
- package/engine/compositions/library/tool-todo.yml +11 -0
- package/engine/compositions/library/tool-web.yml +9 -0
- package/engine/compositions/source/local/context-gate.yml +37 -0
- package/engine/compositions/source/local/custom-bash.yml +11 -0
- package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
- package/engine/compositions/source/local/run-code-env.yml +12 -0
- package/engine/compositions/source/local/skill-search.yml +11 -0
- package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
- package/engine/context-gate.mjs +305 -0
- package/{preset → engine}/custom-bash.mjs +243 -243
- package/engine/executor.mjs +278 -0
- package/engine/fillers.mjs +273 -0
- package/engine/interpolate.mjs +66 -0
- package/engine/layers.mjs +224 -0
- package/engine/prompt-config-engine.mjs +49 -0
- package/engine/run-code-env.mjs +208 -0
- package/engine/schema.mjs +312 -0
- package/engine/session-vars.mjs +52 -0
- package/{preset → engine}/shared.mjs +35 -0
- package/engine/strategies.mjs +213 -0
- package/{preset → engine}/tool-bootstrap.mjs +350 -282
- package/engine/tool-filter.mjs +80 -0
- package/engine/vendor/yaml/LICENSE +13 -0
- package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
- package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
- package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
- package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
- package/engine/vendor/yaml/dist/compose/composer.js +219 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
- package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
- package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
- package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
- package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
- package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
- package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
- package/engine/vendor/yaml/dist/doc/Document.js +335 -0
- package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
- package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
- package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
- package/engine/vendor/yaml/dist/doc/directives.js +176 -0
- package/engine/vendor/yaml/dist/errors.js +57 -0
- package/engine/vendor/yaml/dist/index.js +17 -0
- package/engine/vendor/yaml/dist/log.js +11 -0
- package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
- package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
- package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
- package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
- package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
- package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
- package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
- package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
- package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
- package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
- package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
- package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
- package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
- package/engine/vendor/yaml/dist/parse/cst.js +98 -0
- package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
- package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
- package/engine/vendor/yaml/dist/parse/parser.js +975 -0
- package/engine/vendor/yaml/dist/public-api.js +102 -0
- package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
- package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
- package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
- package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
- package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
- package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
- package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
- package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
- package/engine/vendor/yaml/dist/schema/tags.js +96 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
- package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
- package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
- package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
- package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
- package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
- package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
- package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
- package/engine/vendor/yaml/dist/util.js +11 -0
- package/engine/vendor/yaml/dist/visit.js +233 -0
- package/engine/vendor/yaml/index.js +5 -0
- package/engine/vendor/yaml/package.json +11 -0
- package/lib/client.js +4724 -775
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +530 -51
- package/lib/index.mjs +4164 -740
- package/lib/preset-core.d.mts +7 -37
- package/lib/preset-core.mjs +43 -285
- package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
- package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
- package/package.json +36 -29
- package/preset/anchored/preset.yml +342 -0
- package/preset/creative/preset.yml +65 -0
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
- package/preset/custom/preset.yml +13 -0
- package/preset/liangshen/preset.yml +72 -0
- package/preset/minimal/preset.yml +44 -0
- package/preset/ptc/preset.yml +56 -0
- package/preset/standard/preset.yml +55 -0
- package/skills/manifest.json +7 -0
- package/skills/sandboxmod/SKILL.md +49 -49
- package/skills/web ui/SKILL.md +42 -0
- package/templates/10-pre-step.yml +28 -0
- package/templates/11-merged-a.yml +11 -0
- package/templates/13-anchor.yml +19 -0
- package/templates/14-first-turn-anchor.yml +27 -0
- package/templates/15-guide-auto.yml +25 -0
- package/templates/16-custom-fallback.yml +21 -0
- package/templates/17-instruction-hint.yml +23 -0
- package/templates/18-placeholder-env-facts.yml +11 -0
- package/templates/19-placeholder-skill-catalog.yml +19 -0
- package/templates/20-system-section.yml +19 -0
- package/templates/30-runtime-context.yml +10 -0
- package/templates/31-runtime-context-placeholder.yml +18 -0
- package/templates/40-agent-request.yml +11 -0
- package/templates/50-llm-stream.yml +9 -0
- package/templates/60-tool-pipeline.yml +12 -0
- package/AGENTS.md +0 -4
- package/plan.md +0 -312
- package/preset/agent.cordis.yml +0 -443
- package/preset/compaction-epoch.mjs +0 -81
- package/preset/context-gate.mjs +0 -165
- package/preset/instruction-hint.mjs +0 -217
- package/preset/near-anchor.mjs +0 -101
- package/preset/preset.yml +0 -3
- package/preset/prompt-injector.mjs +0 -112
- package/preset/router-first-turn.mjs +0 -73
- package/preset/router-guide.mjs +0 -79
- package/preset.md +0 -115
- package/upstream/dsh-anchored-standard/LICENSE +0 -22
- package/upstream/dsh-anchored-standard/NOTICE +0 -19
- package/upstream/dsh-anchored-standard/REVISION +0 -1
- package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
- package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
- package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
- package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
- package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
- package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
- package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
- package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
- package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
- /package/{preset → engine}/skill-search.mjs +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# module: tool-pwsh
|
|
2
|
+
# source: E:\Documents\GitHub\deepseek-harness/apps/cli/config/agent-presets/standard/agent.cordis.yml
|
|
3
|
+
# local patches: 0
|
|
4
|
+
|
|
5
|
+
- id: tool-pwsh
|
|
6
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
7
|
+
disabled: !!js process.platform !== 'win32'
|
|
8
|
+
|
|
9
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
12
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# module: tool-todo
|
|
2
|
+
# source: E:\Documents\GitHub\deepseek-harness/apps/cli/config/agent-presets/standard/agent.cordis.yml
|
|
3
|
+
# local patches: 0
|
|
4
|
+
|
|
5
|
+
- id: tool-todo
|
|
6
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
7
|
+
config:
|
|
8
|
+
allowParallelInProgress: true
|
|
9
|
+
|
|
10
|
+
# The `web` service and its search provider stay in the host composition; only
|
|
11
|
+
# the model-facing tool is per-session.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
- id: context-gate
|
|
2
|
+
name: ./engine/context-gate.mjs
|
|
3
|
+
config:
|
|
4
|
+
promoteOn: either
|
|
5
|
+
includeSubagents: false
|
|
6
|
+
allowKinds: __allowKinds__
|
|
7
|
+
|
|
8
|
+
# ── bootstrap ───────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
# Tool catalog control (mode-owned tool-bootstrap.mjs). V4 Pro conditions
|
|
11
|
+
# strongly on the API tool catalog AND the first request output budget:
|
|
12
|
+
# request #1 exposes the OFFICIAL Minimal preset's real tool pair —
|
|
13
|
+
# persistent `bash` + `str_replace_editor` — which anchors at the
|
|
14
|
+
# adapter-default maxTokens (256000) with no output cap needed (issue #11:
|
|
15
|
+
# 5/5 anchored vs 11/11 standard-like for every standard-family schema);
|
|
16
|
+
# after the session records its first durable promotion signal (a tool call OR
|
|
17
|
+
# the first assistant message, default `promoteOn: either`), later steps keep
|
|
18
|
+
# the assembled catalog and usePtcMode optionally switches the wire to Code
|
|
19
|
+
# Mode (PTC). `bootstrapMaxTokens` is
|
|
20
|
+
# opt-in for standard-schema bootstraps; unset, the adapter default flows. See
|
|
21
|
+
# tool-bootstrap.mjs for the other triggers. Context stripping is NOT here —
|
|
22
|
+
# the context-gate row above owns it.
|
|
23
|
+
#
|
|
24
|
+
# POST-PROMOTION (prompt-tool local addition): both modes keep the assembled
|
|
25
|
+
# catalog after promotion. `usePtcMode` switches the wire presentation to Code
|
|
26
|
+
# Mode (PTC, a single run_code backed by the full registry SDK) instead of
|
|
27
|
+
# narrowing the resident set; the pre-promotion and post-compaction controlled
|
|
28
|
+
# phase below still stays on the bootstrap pair + `compactionTools`.
|
|
29
|
+
#
|
|
30
|
+
# COMPACTION (local addition): after `compaction/end` the session falls back
|
|
31
|
+
# to the controlled phase — bootstrap pair + `compactionTools` — until a NEW
|
|
32
|
+
# durable promotion signal exists past the boundary (epoch-aware, see
|
|
33
|
+
# compaction-epoch.mjs).
|
|
34
|
+
#
|
|
35
|
+
# `includeSubagents: true` keeps the subagent phase in sync with the
|
|
36
|
+
# context-gate row: a subagent's first request sees the bootstrap pair, then
|
|
37
|
+
# its own first reply or tool call promotes it to the assembled catalog.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- id: custom-bash
|
|
2
|
+
name: ./engine/custom-bash.mjs
|
|
3
|
+
disabled: !!js process.platform !== 'win32'
|
|
4
|
+
config:
|
|
5
|
+
timeoutMs: 120000
|
|
6
|
+
maxOutputBytes: 64000
|
|
7
|
+
|
|
8
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
11
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- id: prompt-config-engine
|
|
2
|
+
name: ./engine/prompt-config-engine.mjs
|
|
3
|
+
config:
|
|
4
|
+
configsDir: ../prompt-configs
|
|
5
|
+
|
|
6
|
+
# ── PTC run_code environment (prompt-tool local addition) ──────────────────
|
|
7
|
+
#
|
|
8
|
+
# Official Code Mode runs the model program in a worker with env={}; the
|
|
9
|
+
# program cannot read system environment. This row patches the reserved
|
|
10
|
+
# run_code transport so every program gets a frozen `env` global containing
|
|
11
|
+
# the whitelisted non-secret host variables below plus managed DSH_* facts
|
|
12
|
+
# and DSH_WORKSPACE. Add deployment-specific names to envKeys as needed.
|
|
13
|
+
# Credential-shaped names (KEY/PASSWORD/SECRET/TOKEN) are always rejected.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
- id: run-code-env
|
|
2
|
+
name: ./engine/run-code-env.mjs
|
|
3
|
+
config:
|
|
4
|
+
enabled: true
|
|
5
|
+
envKeys: [PATH, PATHEXT, HOME, USERPROFILE, USERNAME, COMPUTERNAME, OS, TEMP, TMP, SystemRoot, ProgramFiles, ProgramFiles(x86), LOCALAPPDATA, APPDATA]
|
|
6
|
+
|
|
7
|
+
# ── identity ────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
# Keep this text byte-identical to the Minimal preset. `complete` prevents the
|
|
10
|
+
# Harness identity and per-tool guidance from changing the system prompt, while
|
|
11
|
+
# runtime-context suppression leaves task and repository rules to user messages
|
|
12
|
+
# and explicit file reads. Tool schemas and their runtime enforcement remain.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- id: skill-search
|
|
2
|
+
name: ./engine/skill-search.mjs
|
|
3
|
+
|
|
4
|
+
# ── goals ───────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
# Only the model-facing tool. The goal SERVICE, its session driver, and the
|
|
7
|
+
# `/goal` command stay on the host plane: the Gateway serves the goal domain as
|
|
8
|
+
# Remote endpoints whose receiver comes from a generated descriptor, so it
|
|
9
|
+
# resolves `goals` on the host and an entry-local realm here would hide it. The
|
|
10
|
+
# registry is keyed by session anyway, so one host instance serves every
|
|
11
|
+
# session. What a preset chooses is whether its agent can call the goal tool.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- id: tool-bootstrap
|
|
2
|
+
name: ./engine/tool-bootstrap.mjs
|
|
3
|
+
config:
|
|
4
|
+
bootstrapTools: [bash, str_replace_editor]
|
|
5
|
+
promoteOn: either
|
|
6
|
+
includeSubagents: true
|
|
7
|
+
usePtcMode: __usePtcMode__
|
|
8
|
+
__bootstrapMaxTokens__
|
|
9
|
+
# Post-compaction core work set: the model is mid-task and needs to keep
|
|
10
|
+
# working, but faces a small catalog instead of the full Standard set.
|
|
11
|
+
compactionTools: [read, write, edit, glob, grep, todo_write, ask_user_question]
|
|
12
|
+
|
|
13
|
+
# ── prompt-tool 模板自带行(由 write-preset 按 manifest 变量物化) ─────────────
|
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
}
|