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.
Files changed (191) hide show
  1. package/README.md +131 -222
  2. package/engine/anchor-match.mjs +117 -0
  3. package/engine/compaction-epoch.mjs +139 -0
  4. package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
  5. package/engine/compositions/library/compaction.yml +52 -0
  6. package/engine/compositions/library/context-gate.yml +40 -0
  7. package/engine/compositions/library/custom-bash.yml +14 -0
  8. package/engine/compositions/library/delegation.yml +85 -0
  9. package/engine/compositions/library/official-agent-instructions.yml +17 -0
  10. package/engine/compositions/library/official-persistent-shell.yml +56 -0
  11. package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
  12. package/engine/compositions/library/official-tool-bash.yml +6 -0
  13. package/engine/compositions/library/official-tool-cordis.yml +13 -0
  14. package/engine/compositions/library/official-tool-presentation.yml +7 -0
  15. package/engine/compositions/library/official-tool-skill.yml +14 -0
  16. package/engine/compositions/library/persistent-shell.yml +36 -0
  17. package/engine/compositions/library/persona.yml +9 -0
  18. package/engine/compositions/library/planning.yml +41 -0
  19. package/engine/compositions/library/prompt-config-engine.yml +16 -0
  20. package/engine/compositions/library/run-code-env.yml +15 -0
  21. package/engine/compositions/library/skill-filesystem.yml +13 -0
  22. package/engine/compositions/library/skill-search.yml +14 -0
  23. package/engine/compositions/library/str-replace-editor.yml +10 -0
  24. package/engine/compositions/library/tool-ask-user.yml +8 -0
  25. package/engine/compositions/library/tool-bash.yml +16 -0
  26. package/engine/compositions/library/tool-bootstrap.yml +16 -0
  27. package/engine/compositions/library/tool-filter.yml +12 -0
  28. package/engine/compositions/library/tool-fs-search.yml +18 -0
  29. package/engine/compositions/library/tool-fs.yml +10 -0
  30. package/engine/compositions/library/tool-goal.yml +19 -0
  31. package/engine/compositions/library/tool-jobs.yml +23 -0
  32. package/engine/compositions/library/tool-pwsh.yml +12 -0
  33. package/engine/compositions/library/tool-todo.yml +11 -0
  34. package/engine/compositions/library/tool-web.yml +9 -0
  35. package/engine/compositions/source/local/context-gate.yml +37 -0
  36. package/engine/compositions/source/local/custom-bash.yml +11 -0
  37. package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
  38. package/engine/compositions/source/local/run-code-env.yml +12 -0
  39. package/engine/compositions/source/local/skill-search.yml +11 -0
  40. package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
  41. package/engine/context-gate.mjs +305 -0
  42. package/{preset → engine}/custom-bash.mjs +243 -243
  43. package/engine/executor.mjs +278 -0
  44. package/engine/fillers.mjs +273 -0
  45. package/engine/interpolate.mjs +66 -0
  46. package/engine/layers.mjs +224 -0
  47. package/engine/prompt-config-engine.mjs +49 -0
  48. package/engine/run-code-env.mjs +208 -0
  49. package/engine/schema.mjs +312 -0
  50. package/engine/session-vars.mjs +52 -0
  51. package/{preset → engine}/shared.mjs +35 -0
  52. package/engine/strategies.mjs +213 -0
  53. package/{preset → engine}/tool-bootstrap.mjs +350 -282
  54. package/engine/tool-filter.mjs +80 -0
  55. package/engine/vendor/yaml/LICENSE +13 -0
  56. package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
  57. package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
  58. package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
  59. package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
  60. package/engine/vendor/yaml/dist/compose/composer.js +219 -0
  61. package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
  62. package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
  63. package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
  64. package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
  65. package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
  66. package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
  67. package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
  68. package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
  69. package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
  70. package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
  71. package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
  72. package/engine/vendor/yaml/dist/doc/Document.js +335 -0
  73. package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
  74. package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
  75. package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
  76. package/engine/vendor/yaml/dist/doc/directives.js +176 -0
  77. package/engine/vendor/yaml/dist/errors.js +57 -0
  78. package/engine/vendor/yaml/dist/index.js +17 -0
  79. package/engine/vendor/yaml/dist/log.js +11 -0
  80. package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
  81. package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
  82. package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
  83. package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
  84. package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
  85. package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
  86. package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
  87. package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
  88. package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
  89. package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
  90. package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
  91. package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
  92. package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
  93. package/engine/vendor/yaml/dist/parse/cst.js +98 -0
  94. package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
  95. package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
  96. package/engine/vendor/yaml/dist/parse/parser.js +975 -0
  97. package/engine/vendor/yaml/dist/public-api.js +102 -0
  98. package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
  99. package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
  100. package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
  101. package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
  102. package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
  103. package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
  104. package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
  105. package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
  106. package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
  107. package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
  108. package/engine/vendor/yaml/dist/schema/tags.js +96 -0
  109. package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
  110. package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
  111. package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
  112. package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
  113. package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
  114. package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
  115. package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
  116. package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
  117. package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
  118. package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
  119. package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
  120. package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
  121. package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
  122. package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
  123. package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
  124. package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
  125. package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
  126. package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
  127. package/engine/vendor/yaml/dist/util.js +11 -0
  128. package/engine/vendor/yaml/dist/visit.js +233 -0
  129. package/engine/vendor/yaml/index.js +5 -0
  130. package/engine/vendor/yaml/package.json +11 -0
  131. package/lib/client.js +4724 -775
  132. package/lib/client.js.map +1 -1
  133. package/lib/index.d.mts +530 -51
  134. package/lib/index.mjs +4164 -740
  135. package/lib/preset-core.d.mts +7 -37
  136. package/lib/preset-core.mjs +43 -285
  137. package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
  138. package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
  139. package/package.json +36 -29
  140. package/preset/anchored/preset.yml +342 -0
  141. package/preset/creative/preset.yml +65 -0
  142. package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
  143. package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
  144. package/preset/custom/preset.yml +13 -0
  145. package/preset/liangshen/preset.yml +72 -0
  146. package/preset/minimal/preset.yml +44 -0
  147. package/preset/ptc/preset.yml +56 -0
  148. package/preset/standard/preset.yml +55 -0
  149. package/skills/manifest.json +7 -0
  150. package/skills/sandboxmod/SKILL.md +49 -49
  151. package/skills/web ui/SKILL.md +42 -0
  152. package/templates/10-pre-step.yml +28 -0
  153. package/templates/11-merged-a.yml +11 -0
  154. package/templates/13-anchor.yml +19 -0
  155. package/templates/14-first-turn-anchor.yml +27 -0
  156. package/templates/15-guide-auto.yml +25 -0
  157. package/templates/16-custom-fallback.yml +21 -0
  158. package/templates/17-instruction-hint.yml +23 -0
  159. package/templates/18-placeholder-env-facts.yml +11 -0
  160. package/templates/19-placeholder-skill-catalog.yml +19 -0
  161. package/templates/20-system-section.yml +19 -0
  162. package/templates/30-runtime-context.yml +10 -0
  163. package/templates/31-runtime-context-placeholder.yml +18 -0
  164. package/templates/40-agent-request.yml +11 -0
  165. package/templates/50-llm-stream.yml +9 -0
  166. package/templates/60-tool-pipeline.yml +12 -0
  167. package/AGENTS.md +0 -4
  168. package/plan.md +0 -312
  169. package/preset/agent.cordis.yml +0 -443
  170. package/preset/compaction-epoch.mjs +0 -81
  171. package/preset/context-gate.mjs +0 -165
  172. package/preset/instruction-hint.mjs +0 -217
  173. package/preset/near-anchor.mjs +0 -101
  174. package/preset/preset.yml +0 -3
  175. package/preset/prompt-injector.mjs +0 -112
  176. package/preset/router-first-turn.mjs +0 -73
  177. package/preset/router-guide.mjs +0 -79
  178. package/preset.md +0 -115
  179. package/upstream/dsh-anchored-standard/LICENSE +0 -22
  180. package/upstream/dsh-anchored-standard/NOTICE +0 -19
  181. package/upstream/dsh-anchored-standard/REVISION +0 -1
  182. package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
  183. package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
  184. package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
  185. package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
  186. package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
  187. package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
  188. package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
  189. package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
  190. package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
  191. /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,9 @@
1
+ # module: tool-web
2
+ # source: E:\Documents\GitHub\deepseek-harness/apps/cli/config/agent-presets/standard/agent.cordis.yml
3
+ # local patches: 0
4
+
5
+ - id: tool-web
6
+ name: '@deepseek-ai/dsh-tool-web'
7
+ config:
8
+ fetch: false
9
+ searchTimeoutMs: 60000
@@ -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
+ }