deepseek-coordination 0.1.2 → 0.1.6
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 +263 -14
- package/README.zh-CN.md +158 -9
- package/client.js +338 -0
- package/cordis.patch.yml +40 -0
- package/package.json +20 -2
- package/src/plugin/settings.js +340 -0
- package/src/plugin/takeover.js +102 -62
- package/src/plugin/workflow.js +1475 -0
- package/tools/measure-cache.mjs +223 -0
package/README.md
CHANGED
|
@@ -68,15 +68,24 @@ deepseek-coordination/
|
|
|
68
68
|
├─ src/host/docs/os-spec.md full specification: contracts, error codes, golden matrix
|
|
69
69
|
├─ src/host/docs/permission-model.md the authorization model and its residual risk
|
|
70
70
|
├─ src/plugin/index.js host plugin: registers the `dsh_os` tool
|
|
71
|
+
├─ src/plugin/invoke.js shared invocation layer: artefact/edition resolution, spawn, envelope parsing
|
|
72
|
+
├─ src/plugin/settings.js settings service + the panel's two HTTP routes (`/api/dsh-os/*`)
|
|
73
|
+
├─ src/plugin/takeover.js `tools/execute` interception: answers the built-in read/write/edit/pwsh through dsh-os
|
|
74
|
+
├─ src/plugin/workflow.js intent-first workflow: the `workflow_plan` tool, its phase gates and its prompt section
|
|
75
|
+
├─ client.js browser half: the `dsh.client` artifact that renders the settings panel
|
|
71
76
|
├─ src/permission/index.js consent delegate: the only writer of permission.json, registers five `os_permission_*` tools
|
|
72
77
|
├─ tools/build.ps1 assembles the three generated scripts; `-Check` fails on drift
|
|
73
78
|
├─ tools/parse-check.ps1 parse-checks each module under the runtime that will execute it, and reports non-ASCII bytes and a BOM
|
|
74
79
|
├─ tools/json-field.mjs reads one field of an envelope by dotted path (used by the shell suites; `sed` cannot see into a nested object)
|
|
75
|
-
├─ tools/verify-bundle.mjs verifies the loader's resolution contract (
|
|
76
|
-
├─ tools/verify-plugin-load.mjs runs the real `apply()` path (
|
|
80
|
+
├─ tools/verify-bundle.mjs verifies the loader's resolution contract (16 cases)
|
|
81
|
+
├─ tools/verify-plugin-load.mjs runs the real `apply()` path, including the workflow state machine (30 cases)
|
|
82
|
+
├─ tools/verify-package.mjs packs the package and asserts every runtime-resolved file ships (21 cases)
|
|
77
83
|
├─ tools/verify-editions.mjs proves both PowerShell artefacts are reachable (14 cases)
|
|
78
|
-
├─
|
|
84
|
+
├─ tools/probe-pipeline.mjs drives the interception end to end against a stubbed verb layer (23 cases)
|
|
85
|
+
├─ tests/ golden (Windows/POSIX/desktop injection), table conformance, encoding, plugin, permission, takeover, workflow suites
|
|
79
86
|
│ ├─ conformance.ps1 command.txt and spec/verbs.tsv must describe the same surface (5 cases)
|
|
87
|
+
│ ├─ takeover.test.mjs byte-level format preservation, read windowing, eligibility (41 cases)
|
|
88
|
+
│ ├─ workflow.test.mjs phase gates, the user's choice, and the KV-cache invariant (31 cases)
|
|
80
89
|
│ ├─ smoke-desktop-sh.sh what the POSIX desktop verbs actually inject (52 cases)
|
|
81
90
|
│ └─ fake-xdotool, fake-wmctrl recording stand-ins, so the desktop verbs can be verified with no X server
|
|
82
91
|
└─ permission.json authorization data (an empty file in the repository; that is the initial state)
|
|
@@ -115,8 +124,13 @@ precisely the class of encoding fault this project exists to remove.
|
|
|
115
124
|
|
|
116
125
|
## Getting started
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
Published on npm, so a DSH profile can install it as a bundle:
|
|
119
128
|
|
|
129
|
+
```console
|
|
130
|
+
dsh plugin install deepseek-coordination
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Or work from source:
|
|
120
134
|
```console
|
|
121
135
|
git clone https://github.com/eloyhere/deepseek-coordination.git
|
|
122
136
|
cd deepseek-coordination
|
|
@@ -145,11 +159,25 @@ because batch-file argument handling is itself a source of quoting bugs.
|
|
|
145
159
|
### Using it from the harness
|
|
146
160
|
|
|
147
161
|
`package.json` declares `dsh.bundle.patch`, so the package installs into a DSH profile as a bundle and
|
|
148
|
-
|
|
162
|
+
inserts five rows:
|
|
163
|
+
|
|
164
|
+
| Row | Entry | What it does |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `dsh-os` | `.` | registers the `dsh_os` tool |
|
|
167
|
+
| `dsh-os-settings` | `./settings` | the settings the panel edits, plus its two HTTP routes |
|
|
168
|
+
| `dsh-os-takeover` | `./takeover` | answers the built-in `read`/`write`/`edit`/`pwsh` through dsh-os while enabled |
|
|
169
|
+
| `dsh-os-permission` | `./permission` | the consent delegate, the only writer of `permission.json` |
|
|
170
|
+
| `dsh-os-workflow` | `./workflow` | the intent-first workflow tool and its system-prompt section |
|
|
171
|
+
|
|
172
|
+
The rows must name **different module specifiers**: `name` is resolved as a package specifier, and giving
|
|
173
|
+
two rows the bare package name loads the same module twice, which throws `tool "dsh_os" is already
|
|
174
|
+
registered` and aborts the whole entry. That is why the subpath exports exist.
|
|
149
175
|
|
|
150
176
|
| Tool | Purpose |
|
|
151
177
|
|---|---|
|
|
152
178
|
| `dsh_os` | Run one verb of the surface and return its JSON envelope verbatim |
|
|
179
|
+
| `os_permission_*` | Read, grant, revoke and audit durable authorization decisions |
|
|
180
|
+
| `workflow_plan` | Run one phase of the intent-first workflow (see [The workflow](#the-workflow)) |
|
|
153
181
|
|
|
154
182
|
One tool rather than one per verb, for three reasons: the verb table is the contract and lives in
|
|
155
183
|
`src/host/spec/verbs.tsv`, so no schema needs regenerating when a verb is added; an agent discovers the
|
|
@@ -165,6 +193,48 @@ shape, so there is no second contract to drift from the script's.
|
|
|
165
193
|
The implementation is spawned by absolute path with an argv array, never through a shell string, so a
|
|
166
194
|
value containing spaces stays one value.
|
|
167
195
|
|
|
196
|
+
### Taking over the built-in operations
|
|
197
|
+
|
|
198
|
+
`dsh_os` is a tool an agent has to be *told* to use, and it never is: the built-in `read`/`write`/`edit`/
|
|
199
|
+
`pwsh` tools are right there, they overlap almost completely, and nothing in the prompt prefers one over
|
|
200
|
+
the other. The guarantees this project exists for therefore stay unexercised — the plugin is installed
|
|
201
|
+
and imperceptible.
|
|
202
|
+
|
|
203
|
+
**Takeover** removes the choice. Enable it in **Settings → DSH OS** and the built-in calls are answered
|
|
204
|
+
by the same machinery, whether or not the agent cooperates. Per-tool switches let you redirect only
|
|
205
|
+
`write`, or only `edit`, and leave the rest alone.
|
|
206
|
+
|
|
207
|
+
It works through the `tools/execute` around-dispatch waterfall: a handler returns `{ value }` without
|
|
208
|
+
calling `next()`, and that value is then validated against the **built-in** tool's own `output.schema`
|
|
209
|
+
and rendered by the **built-in** `output.render`. The model-facing text, the diff cards and the
|
|
210
|
+
presentation metadata are therefore unchanged; only the byte-level source of the value is.
|
|
211
|
+
|
|
212
|
+
**What it does not take over, and why:**
|
|
213
|
+
|
|
214
|
+
* `glob` and `grep`. This surface has no content-search verb at all — `file search` matches *names* — so
|
|
215
|
+
`grep`'s `lineNumber`/`line` cannot be produced, and glob's ignore rules and mtime ordering have no
|
|
216
|
+
equivalent. Redirecting them would silently return a different result set.
|
|
217
|
+
* Background commands and sandbox escalations. A background call's result is a harness job id, which
|
|
218
|
+
this surface cannot mint, and `sandbox_permissions` is the harness's own approval seam.
|
|
219
|
+
* Relative paths. The built-in tools resolve them against the session workspace through `ctx.fs`; this
|
|
220
|
+
plugin has no equivalent resolution, and guessing the process working directory would read a different
|
|
221
|
+
file than the one named.
|
|
222
|
+
|
|
223
|
+
**Two properties are deliberately given up, and you should know them before enabling it:**
|
|
224
|
+
|
|
225
|
+
1. **The filesystem sandbox does not bound a redirected call.** The built-in write path goes through
|
|
226
|
+
`ctx.fs`, which is where the sandbox policy and the read-before-write observation guard live. A
|
|
227
|
+
redirected write runs the verb directly, so dsh-os's own protected-path check is what remains. This
|
|
228
|
+
is a real reduction in defence, not a detail.
|
|
229
|
+
2. **The reported text and the bytes on disk can differ.** With the default *follow the target* format, a
|
|
230
|
+
CRLF file stays CRLF and a BOM stays a BOM, while `value.after` is normalised LF text — which is what
|
|
231
|
+
the built-in `read` would show and what the diff card compares. Choose *same as built-in* if you would
|
|
232
|
+
rather the takeover change nothing about the bytes.
|
|
233
|
+
|
|
234
|
+
Every redirected call falls back to the built-in implementation on any failure, so takeover can change
|
|
235
|
+
how a result is produced but never whether the caller gets one. The panel's activity feed shows what was
|
|
236
|
+
actually taken over and why anything fell back.
|
|
237
|
+
|
|
168
238
|
**Choosing the PowerShell edition.** The default remains the 5.1 artefact (`bin/dsh-os.ps1`),
|
|
169
239
|
**deliberately unchanged**: a deployment that already works should not change behaviour underneath
|
|
170
240
|
itself because a plugin was upgraded. To select the 7.6 artefact (`bin/dsh-os7.ps1`):
|
|
@@ -179,13 +249,177 @@ install locations, because `pwsh` is not always on `PATH` — it is not on this
|
|
|
179
249
|
environment, while 7.6.6 is plainly installed in the standard location.
|
|
180
250
|
|
|
181
251
|
> [!IMPORTANT]
|
|
182
|
-
> **The bundle now loads in a live desktop session.** The plugin
|
|
183
|
-
>
|
|
252
|
+
> **The bundle now loads in a live desktop session.** The plugin is installed into the active profile as
|
|
253
|
+
> a published npm package, and the tools it registers — `dsh_os` plus the five `os_permission_*` tools —
|
|
184
254
|
> are present and callable in a running session. What is still **not** proven is the part that
|
|
185
255
|
> `tools/verify-bundle.mjs` cannot cover: that the *harness's own loader* composes the bundle by the
|
|
186
256
|
> same contract the verifier checks, rather than by a shortcut that happens to work here. The
|
|
187
257
|
> verifier remains the evidence; the live session is corroboration.
|
|
188
258
|
|
|
259
|
+
### The workflow
|
|
260
|
+
|
|
261
|
+
Every other row in this package makes an OS call **correct**. This one makes a *request* decided before it
|
|
262
|
+
is executed. The failure mode it removes is a request that was never a specification: the model starts
|
|
263
|
+
editing files, and the divergence is discovered at review — after the tokens have been spent.
|
|
264
|
+
|
|
265
|
+
`dsh-os-workflow` registers one tool, `workflow_plan`, and one system-prompt section.
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
intake -> plan -> choose -> run -> done
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Phase | Action | What it establishes |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| intake | `start` | the prompt **verbatim**, plus 1–3 questions about intent *or* the assumptions being made |
|
|
274
|
+
| plan | `plan` | the approach and process, **at least two** alternatives with pros and cons, the criteria, and a recommendation |
|
|
275
|
+
| choose | `choose` | **the user's decision**, taken through `ctx.userQuestions` inside the call |
|
|
276
|
+
| run | `run` | execution, its steps, and — in the same call — the outcome that closes the workflow |
|
|
277
|
+
| — | `note` | a remark or a decision made during execution |
|
|
278
|
+
| — | `checkpoint` | folds the record into one file and reports a summary |
|
|
279
|
+
| — | `status`, `reset` | read the state; discard it and start again |
|
|
280
|
+
|
|
281
|
+
> [!IMPORTANT]
|
|
282
|
+
> **The tool is named `workflow_plan`, not `workflow`, and the reason is a real collision.** A host
|
|
283
|
+
> composition already exposes a built-in `workflow` tool — the JavaScript multi-agent orchestrator from
|
|
284
|
+
> `@deepseek-ai/dsh-tool-workflow`. Two tools sharing one name in one scope collide: the second
|
|
285
|
+
> registration aborts, or both resolve and the model is offered two unrelated tools under one name. Since
|
|
286
|
+
> this row's tool lives in the host plane while `workflow` is mounted per agent preset, no test in this
|
|
287
|
+
> repository would have caught it; it was caught by reading the live tool catalog. `_plan` also says what
|
|
288
|
+
> this tool is: the planning half of a workflow.
|
|
289
|
+
|
|
290
|
+
**The gates are real.** A phase whose entry has not been recorded is refused, and the refusal names the
|
|
291
|
+
phase that must run first. `choose` cannot be answered by the model: the question goes to the user through
|
|
292
|
+
the same seam `ask_user_question` uses, and nothing else can move the workflow into `run`.
|
|
293
|
+
|
|
294
|
+
**Four calls, one question.** The unavoidable path is `start` → `plan` → `choose` → `run`. It was
|
|
295
|
+
originally eight calls and two questions, and the difference is three deliberate fusions — each of which
|
|
296
|
+
removes a round-trip without removing a decision:
|
|
297
|
+
|
|
298
|
+
* **design and options are one call**, because they are one decision. This is the QOC shape (Questions,
|
|
299
|
+
Options, Criteria) and the ADR shape (Context, Decision, Consequences): a decision record holds the
|
|
300
|
+
alternatives it was chosen between and the criteria it was judged against. Splitting them made the
|
|
301
|
+
second call re-derive the first and made the user read the same architecture twice.
|
|
302
|
+
* **a clear request costs zero questions.** `start` accepts `assumptions` instead of `questions`, so an
|
|
303
|
+
unambiguous prompt is not interrupted — and the assumption is on record where the user can correct it,
|
|
304
|
+
which is cheaper than the question it replaced. The evidence for *minimal, relevant* questions over
|
|
305
|
+
interrogation is the Ask-to-Act line of work ([arXiv:2504.00907](https://arxiv.org/abs/2504.00907)).
|
|
306
|
+
* **a single-step execution closes in the same call it opens.** Passing `outcome` to `run` finishes the
|
|
307
|
+
workflow; only multi-step work needs a second call.
|
|
308
|
+
|
|
309
|
+
**The compression is the other half.** A plan narrated into the conversation is re-sent with every later
|
|
310
|
+
step, which is what makes a short task expensive. So it never enters the conversation: each phase writes a
|
|
311
|
+
real document and returns a line — phase, summary, file path, one next call. `tests/workflow.test.mjs`
|
|
312
|
+
measures it and holds every phase envelope under 2000 characters; in the current build the four calls of a
|
|
313
|
+
full workflow total about 1.8 kB, roughly 500 tokens, for a plan document measured in kilobytes. This shape
|
|
314
|
+
is measured, not stylistic: pruning plus summarisation improves completion while cutting tokens and runtime
|
|
315
|
+
([Less Context, Better Agents](https://export.arxiv.org/pdf/2608.16370)), and a planner/executor split
|
|
316
|
+
matches interleaved reasoning at a fraction of the token cost
|
|
317
|
+
([Plan Once, Ground Locally](https://zenodo.org/records/22904595)).
|
|
318
|
+
|
|
319
|
+
**Where the cost goes**, stated as complexity because that is what it is:
|
|
320
|
+
|
|
321
|
+
| Path | Cost |
|
|
322
|
+
|---|---|
|
|
323
|
+
| reading state | once per workflow call, cached on the file's mtime — and **never** on a model step, because the prompt section reads no state at all |
|
|
324
|
+
| a phase transition | one atomic state write; the first version wrote it three times per action |
|
|
325
|
+
| a note during execution | one `appendFileSync` append, O(1), bounded by compaction — not the read-push-rewrite that is O(n²) over a workflow |
|
|
326
|
+
| the prompt section | a **constant**: no read, no lookup, no formatting, identical at every phase of every workflow |
|
|
327
|
+
|
|
328
|
+
### The KV cache effect, which is why the prompt section is a constant
|
|
329
|
+
|
|
330
|
+
The section this row registers into the system prompt is a **constant string**. That is not tidiness; it is
|
|
331
|
+
the design, and the reason is that the prompt is the worst possible place for anything that moves.
|
|
332
|
+
|
|
333
|
+
**What this package measured.** `tools/measure-cache.mjs` drives the real endpoint
|
|
334
|
+
(`deepseek-flash`, `max_tokens: 1`, one request per variant after one warm request on the same lineage) and
|
|
335
|
+
reports `cache_read_input_tokens` for the shapes this plugin actually produces. Measured twice, with
|
|
336
|
+
identical results:
|
|
337
|
+
|
|
338
|
+
| Shape | Cache read | Total input | Hit |
|
|
339
|
+
|---|---:|---:|---:|
|
|
340
|
+
| an unchanged request (control) | 5,632 | 5,780 | 97.4% |
|
|
341
|
+
| appending a user message | 5,632 | 5,784 | 97.4% |
|
|
342
|
+
| appending a tool result | 5,632 | 5,853 | 96.2% |
|
|
343
|
+
| **one workflow: four appended calls** | 5,632 → 5,888 | ~5,900 each | **96.6% mean** |
|
|
344
|
+
|
|
345
|
+
So the row's own shape is measured warm: four calls, 23,040 cached tokens, every call above 96%.
|
|
346
|
+
|
|
347
|
+
**What this package did NOT manage to measure, and will not claim.** The experiment also tries the
|
|
348
|
+
counterfactual — rewrite the leading system prompt, edit a tool description, reorder two tools — and those
|
|
349
|
+
readings are **not reproducible**: the first run reported 0.0% for all of them, the second reported 97%. The
|
|
350
|
+
provider documents the reason and it is worth knowing: it persists a **common prefix detected across
|
|
351
|
+
requests**, so by the second run the repeated identical prefixes had been learned and a mutation still
|
|
352
|
+
matched. Per-variant A/B isolation on a shared account cannot settle the mutation side, so
|
|
353
|
+
`tools/measure-cache.mjs` asserts only the append side, which is stable, and prints the rest as
|
|
354
|
+
observations rather than claims.
|
|
355
|
+
|
|
356
|
+
**The counterfactual rests on independent evidence instead**, and it is strong:
|
|
357
|
+
|
|
358
|
+
* the provider's own rule is that a cache hit requires a request to **fully match a persisted cache prefix
|
|
359
|
+
unit**, and the system prompt sits before the tool catalog and the entire history
|
|
360
|
+
([Context Caching](https://api-docs.deepseek.com/guides/kv_cache/));
|
|
361
|
+
* a ten-variant experiment on the same endpoint found appending a message kept a **98.7%** hit while adding
|
|
362
|
+
one line at the **end** of the system prompt produced **0.0%**
|
|
363
|
+
([prefix-stability chapter](https://github.com/diguike/book-deepseek-harness/blob/main/book/11-prefix-stability.md));
|
|
364
|
+
* the harness itself paid for this lesson: the sandbox policy used to live in the system prompt, and one
|
|
365
|
+
permission switch dropped cache reads from **~14,700 tokens to 256**. The fix was to move the changing
|
|
366
|
+
fact out of the prompt into the message sequence
|
|
367
|
+
([DSH agent note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md)).
|
|
368
|
+
|
|
369
|
+
**The first version of this row rendered the current phase into that section**, so a four-call workflow
|
|
370
|
+
re-wrote the system prompt three times. Whether or not the provider happens to forgive that on a given day,
|
|
371
|
+
the row has no business depending on it — so the fix is structural, not cosmetic:
|
|
372
|
+
|
|
373
|
+
* the section is a constant, so this row's **cache effect is `prefix-stable`**: zero prompt changes per
|
|
374
|
+
workflow, asserted by `tests/workflow.test.mjs` against the registered text at every phase, and again
|
|
375
|
+
after deleting `state.json` outright;
|
|
376
|
+
* the phase and the **one next call** travel in the `workflow_plan` tool's own result instead — appended
|
|
377
|
+
history, measured above at 96–97%;
|
|
378
|
+
* a step taken out of order is refused, and the refusal names the phase to record first, so the model can
|
|
379
|
+
recover from a stale plan without the prompt having told it anything;
|
|
380
|
+
* the tool **description** carries the discovery text. It lives in the tool catalog: inside the cached
|
|
381
|
+
prefix, sent once, and unchanged for the life of the session.
|
|
382
|
+
|
|
383
|
+
> [!IMPORTANT]
|
|
384
|
+
> **The consequence a reader should draw for their own plugins.** Anything that changes per step — the
|
|
385
|
+
> time, the current directory, a branch, a permission, a phase — must not be written into a prompt section.
|
|
386
|
+
> This harness gives that content its own path: register it with `ctx.systemPrompt.context()` and it is
|
|
387
|
+
> delivered as an appended user message, re-projected only when it changes. A section is for text that is
|
|
388
|
+
> stable for the session.
|
|
389
|
+
|
|
390
|
+
**Where the state lives.** `$DSH_HOME/dsh-os/workflows/<session>/`, beside the settings document and for
|
|
391
|
+
the same reason: the package directory is replaced on upgrade, and the workspace belongs to the user.
|
|
392
|
+
`state.json` is the compact record; every other file is the detail. A finished workflow is archived rather
|
|
393
|
+
than overwritten, so the next one starts clean and the last one stays readable.
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
state.json the compact record: phase, intent, decisions, steps, artifact paths
|
|
397
|
+
journal.jsonl append-only: notes and steps, bounded, folded in by a checkpoint
|
|
398
|
+
01-intent.md the prompt verbatim, then the questions and answers (or the assumptions)
|
|
399
|
+
02-plan.md approach and process, alternatives with pros and cons, criteria, the decision
|
|
400
|
+
03-execution.md chosen option, goal, steps, files in scope, constraints
|
|
401
|
+
04-outcome.md what was delivered, and any step left open
|
|
402
|
+
05-checkpoint.md the fold: everything decided so far, written when a compaction is wanted
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**What is configured.** `workflowRoot` moves the directory; `autoCompact` (default `true`) controls
|
|
406
|
+
whether a checkpoint reports the compaction the engine may perform, and `maxCheckpoints` bounds it. The
|
|
407
|
+
structural half — detail to disk, one line back — is **not** configurable, because it is the reason the
|
|
408
|
+
feature saves anything.
|
|
409
|
+
|
|
410
|
+
> [!WARNING]
|
|
411
|
+
> **This row asks the user questions, so the deployment must be able to answer them.** It injects
|
|
412
|
+
> `userQuestions`; a host without it registers nothing rather than registering a tool that would fail on
|
|
413
|
+
> its second call. Two calls block on a human: `start` when questions are given, and `choose` always.
|
|
414
|
+
|
|
415
|
+
> [!NOTE]
|
|
416
|
+
> **What this row does not do: it does not invoke the engine's compaction service.** `ctx.compaction`
|
|
417
|
+
> exists, and calling it from here would mean constructing a session-shaped context whose contract is not
|
|
418
|
+
> published — a guess dressed up as a feature. What the plugin does instead is deterministic and
|
|
419
|
+
> inspectable: the detail leaves the conversation for a file, and `checkpoint` records the boundary where a
|
|
420
|
+
> compaction is safe. That is why the compression is measurable by `tests/workflow.test.mjs` rather than
|
|
421
|
+
> merely asserted.
|
|
422
|
+
|
|
189
423
|
---
|
|
190
424
|
|
|
191
425
|
## JSON contract
|
|
@@ -546,7 +780,7 @@ been closed by three verifiers, and they found one real defect:
|
|
|
546
780
|
| Verifier | What it proves | Cases |
|
|
547
781
|
|---|---|---|
|
|
548
782
|
| `tools/verify-bundle.mjs` | the loader's **resolution** contract: the manifest header, the patch file, every row resolving from a profile-style `node_modules`, every resolved module exporting the Cordis shape the loader calls (`apply`/`inject`/`name`), the two rows resolving to **different** modules (a shared specifier registers the same tool twice and aborts the whole entry), and the version gate | 10 |
|
|
549
|
-
| `tools/verify-plugin-load.mjs` | the real `apply()` path: the host plugin registers `dsh_os` with a parameters schema whose **root type is `object`**, the permission plugin registers its five tools, and **the registered handler really does spawn the script** and return a real envelope (`selfcheck` → ok, `verbCount` 86); a mutating verb without `allow` is refused with a typed `E_PERMISSION` rather than throwing |
|
|
783
|
+
| `tools/verify-plugin-load.mjs` | the real `apply()` path: the host plugin registers `dsh_os` with a parameters schema whose **root type is `object`**, the permission plugin registers its five tools, and **the registered handler really does spawn the script** and return a real envelope (`selfcheck` → ok, `verbCount` 86); a mutating verb without `allow` is refused with a typed `E_PERMISSION` rather than throwing. It also covers the workflow row, whose failure would otherwise be silent: the tool and its prompt section **are** registered, and `start → plan → choose → run` runs through the real handler with its gates intact | 30 |
|
|
550
784
|
| `tools/verify-editions.mjs` | edition selection: the default is still PowerShell 5.1; `powershellEdition: '7.6'` and `DSH_OS_POWERSHELL_EDITION` select the 7.6 artefact; `'auto'` uses 7.6 only when the artefact and a working `pwsh` are present **together**; configuration beats the environment; an explicit `scriptPath` disables the choice. `-ExecutionPolicy` is passed only to the 5.1 executable | 14 |
|
|
551
785
|
|
|
552
786
|
The bundle verifier links the package into a throwaway profile sandbox by junction, so resolution
|
|
@@ -678,11 +912,25 @@ npm run test:all # runs everything below, in order
|
|
|
678
912
|
| POSIX golden | `bash tests/golden-sh.sh` | 116 | **116 passed, 0 failed** |
|
|
679
913
|
| POSIX desktop injection | `bash tests/smoke-desktop-sh.sh` | 52 | **52 passed, 0 failed** |
|
|
680
914
|
| Plugin | `node tests/plugin.test.mjs` | 13 | **13 passed, 0 failed** |
|
|
915
|
+
| Takeover interceptions | `node tests/takeover.test.mjs` | 41 | **41 passed, 0 failed** |
|
|
681
916
|
| Permission delegate | `node tests/permission.test.mjs` | 18 | **18 passed, 0 failed** |
|
|
682
|
-
|
|
|
683
|
-
|
|
|
917
|
+
| Workflow | `node tests/workflow.test.mjs` | 31 | **31 passed, 0 failed** |
|
|
918
|
+
| Bundle resolution | `node tools/verify-bundle.mjs` | 16 | **16 passed, 0 failed** |
|
|
919
|
+
| Package completeness | `node tools/verify-package.mjs` | 21 | **21 passed, 0 failed** |
|
|
920
|
+
| Plugin load | `node tools/verify-plugin-load.mjs` | 30 | **30 passed, 0 failed** |
|
|
684
921
|
| Edition selection | `node tools/verify-editions.mjs` | 14 | **14 passed, 0 failed** |
|
|
685
922
|
| Build drift | `powershell -File tools/build.ps1 -Check` | 3 artefacts | **no drift** |
|
|
923
|
+
| Prefix cache (`npm run measure:cache`) | `node tools/measure-cache.mjs` | 2 claims | **PASS — 96.6% mean hit over four workflow calls** |
|
|
924
|
+
|
|
925
|
+
`measure:cache` is **not** part of `test:all`: it calls the real provider, so it needs
|
|
926
|
+
`DEEPSEEK_API_KEY` (read from `$DSH_HOME/.credentials.yaml`) and it costs tokens. It exits non-zero only
|
|
927
|
+
when a measured claim does not hold.
|
|
928
|
+
|
|
929
|
+
> [!NOTE]
|
|
930
|
+
> **`test:desktop` needs `bash`, and this machine has none.** `npm run test:all` therefore ends with
|
|
931
|
+
> `'bash' is not recognized` on a Windows host without Git Bash or WSL on `PATH`. That is an environment
|
|
932
|
+
> gap in the runner, not a failing assertion: every suite above it ran and passed in the same invocation.
|
|
933
|
+
> The POSIX suites remain unverified here for the same reason they always were — they need a POSIX host.
|
|
686
934
|
|
|
687
935
|
### Conformance of the two tables
|
|
688
936
|
|
|
@@ -820,10 +1068,11 @@ Copyright (c) 2026 Eloy Kim
|
|
|
820
1068
|
```
|
|
821
1069
|
|
|
822
1070
|
The licence file is pure ASCII, LF-terminated and BOM-free, consistent with the rest of the
|
|
823
|
-
repository's encoding contracts.
|
|
824
|
-
`package.json`'s `files` whitelist
|
|
825
|
-
|
|
826
|
-
|
|
1071
|
+
repository's encoding contracts. An earlier description here claimed it was **not** distributed because it
|
|
1072
|
+
is not listed in `package.json`'s `files` whitelist. That was wrong, and the published tarball proves it:
|
|
1073
|
+
`npm pack deepseek-coordination@0.1.5` contains `package/LICENSE` **and** `package/README.zh-CN.md`, because
|
|
1074
|
+
npm always includes those two names regardless of `files`. The whitelist has 20 entries and includes
|
|
1075
|
+
`src/host/bin/dsh-os7.ps1`; the two files simply do not need to be in it.
|
|
827
1076
|
|
|
828
1077
|
The verb contract and the specification documents are part of the source: a behaviour change should be
|
|
829
1078
|
reflected in `src/host/spec/verbs.tsv`, `src/host/command.txt` and `src/host/docs/os-spec.md`, not only
|
package/README.zh-CN.md
CHANGED
|
@@ -58,15 +58,21 @@ deepseek-coordination/
|
|
|
58
58
|
├─ src/host/docs/os-spec.md 完整规范:契约、错误码、黄金用例矩阵
|
|
59
59
|
├─ src/host/docs/permission-model.md 授权模型及其残余风险
|
|
60
60
|
├─ src/plugin/index.js 宿主插件:注册 `dsh_os` 工具
|
|
61
|
+
├─ src/plugin/invoke.js 共享调用层:生成物/版本解析、spawn、信封解析
|
|
62
|
+
├─ src/plugin/settings.js 设置服务 + 面板的两条 HTTP 路由(`/api/dsh-os/*`)
|
|
63
|
+
├─ src/plugin/takeover.js `tools/execute` 拦截:把内置 read/write/edit/pwsh 交给 dsh-os 答复
|
|
64
|
+
├─ client.js 浏览器半边:渲染设置面板的 `dsh.client` 生成物
|
|
61
65
|
├─ src/permission/index.js 同意写入委托:`permission.json` 的唯一写入方,注册 5 个 `os_permission_*` 工具
|
|
62
66
|
├─ tools/build.ps1 拼装三个生成脚本;`-Check` 在漂移时失败
|
|
63
67
|
├─ tools/parse-check.ps1 在**将要执行代码的那个运行时**下解析检查模块,并报告非 ASCII 与 BOM
|
|
64
68
|
├─ tools/json-field.mjs 按点分路径读取信封里的一个字段(shell 测试用,`sed` 看不进嵌套对象)
|
|
65
|
-
├─ tools/verify-bundle.mjs 校验 loader 的解析契约(
|
|
66
|
-
├─ tools/verify-plugin-load.mjs 走真实 `apply()` 路径(
|
|
69
|
+
├─ tools/verify-bundle.mjs 校验 loader 的解析契约(14 项)
|
|
70
|
+
├─ tools/verify-plugin-load.mjs 走真实 `apply()` 路径(30 项)
|
|
67
71
|
├─ tools/verify-editions.mjs 证明两个 PowerShell 生成物都可达(14 项)
|
|
68
|
-
├─
|
|
72
|
+
├─ tools/probe-pipeline.mjs 在打桩的动词层上端到端驱动拦截链路(23 项)
|
|
73
|
+
├─ tests/ 黄金(Windows/POSIX/桌面注入)、两表一致性、编码、插件、授权、接管各套测试
|
|
69
74
|
│ ├─ conformance.ps1 `command.txt` 与 `spec/verbs.tsv` 必须描述同一个面(5 项)
|
|
75
|
+
│ ├─ takeover.test.mjs 字节级格式保留、读取窗口切片、可拦截资格(41 项)
|
|
70
76
|
│ ├─ smoke-desktop-sh.sh POSIX 桌面动词**实际注入了什么**(52 项)
|
|
71
77
|
│ └─ fake-xdotool, fake-wmctrl 会记录调用的替身,使无 X server 的机器也能校验桌面动词
|
|
72
78
|
└─ permission.json 授权数据(随仓库为空文件,这就是初始状态)
|
|
@@ -101,7 +107,13 @@ deepseek-coordination/
|
|
|
101
107
|
|
|
102
108
|
## 开始使用
|
|
103
109
|
|
|
104
|
-
|
|
110
|
+
已发布到 npm,因此 DSH profile 可以把它作为 bundle 安装:
|
|
111
|
+
|
|
112
|
+
```console
|
|
113
|
+
dsh plugin install deepseek-coordination
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
或从源码使用:
|
|
105
117
|
|
|
106
118
|
```console
|
|
107
119
|
git clone https://github.com/eloyhere/deepseek-coordination.git
|
|
@@ -128,11 +140,23 @@ src/host/bin/dsh-os.sh file charset "./a file.txt"
|
|
|
128
140
|
|
|
129
141
|
### 从 harness 使用
|
|
130
142
|
|
|
131
|
-
`package.json` 声明了 `dsh.bundle.patch`,因此本包可以作为一个 bundle 安装进 DSH profile
|
|
143
|
+
`package.json` 声明了 `dsh.bundle.patch`,因此本包可以作为一个 bundle 安装进 DSH profile,并插入五行:
|
|
144
|
+
|
|
145
|
+
| 行 | 入口 | 作用 |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `dsh-os` | `.` | 注册 `dsh_os` 工具 |
|
|
148
|
+
| `dsh-os-settings` | `./settings` | 面板编辑的设置,以及它的两条 HTTP 路由 |
|
|
149
|
+
| `dsh-os-takeover` | `./takeover` | 在开启时把内置 `read`/`write`/`edit`/`pwsh` 交给 dsh-os 答复 |
|
|
150
|
+
| `dsh-os-permission` | `./permission` | 同意写入委托,`permission.json` 的唯一写入方 |
|
|
151
|
+
| `dsh-os-workflow` | `./workflow` | 意图优先的工作流工具及其系统提示词小节 |
|
|
152
|
+
|
|
153
|
+
各行必须使用**不同的模块说明符**:`name` 按包说明符解析,若两行都用裸包名就会把同一个模块加载两次,从而抛出 `tool "dsh_os" is already registered` 并中止整个 entry。子路径导出正是为此存在。
|
|
132
154
|
|
|
133
155
|
| 工具 | 用途 |
|
|
134
156
|
|---|---|
|
|
135
157
|
| `dsh_os` | 执行动词面上的一个动词,并原样返回其 JSON 信封 |
|
|
158
|
+
| `os_permission_*` | 读取、授予、撤销与审计持久授权决定 |
|
|
159
|
+
| `workflow_plan` | 执行意图优先工作流的一个阶段(见[工作流](#工作流)) |
|
|
136
160
|
|
|
137
161
|
只注册一个工具而非每个动词一个,理由有三:动词表就是契约且存放在 `src/host/spec/verbs.tsv`,新增动词时无需重新生成 schema;agent 用一次 `verb: "selfcheck"` 调用就能发现整个动词面;信封本身已是稳定的结果形状,不会出现第二份会与脚本漂移的契约。
|
|
138
162
|
|
|
@@ -144,6 +168,27 @@ src/host/bin/dsh-os.sh file charset "./a file.txt"
|
|
|
144
168
|
|
|
145
169
|
实现以绝对路径加 argv 数组方式拉起,永不经 shell,因此含空格的值仍然是一个值。
|
|
146
170
|
|
|
171
|
+
### 接管内置操作
|
|
172
|
+
|
|
173
|
+
`dsh_os` 是一个**必须被告知才会用**的工具,而实际上永远不会被告知:内置的 `read`/`write`/`edit`/`pwsh` 就在手边,能力几乎完全重叠,提示词里也没有任何东西偏向其中一方。于是本项目存在的那些保证从未被真正走到——插件装上了,却感知不到。
|
|
174
|
+
|
|
175
|
+
**接管**消除了这个选择。在 **设置 → DSH OS** 里打开后,内置调用会由同一套机制答复,无论 agent 是否配合。逐工具开关可以只接管 `write`、或只接管 `edit`,其余保持不变。
|
|
176
|
+
|
|
177
|
+
它走的是 `tools/execute` 这条 around-dispatch waterfall:处理器返回 `{ value }` 并且不调用 `next()`,该值随后由**内置**工具自己的 `output.schema` 校验、由**内置**的 `output.render` 渲染。因此模型看到的文本、diff 卡片与展示元数据都没变,变的只是这个值的字节来源。
|
|
178
|
+
|
|
179
|
+
**不接管的部分及原因:**
|
|
180
|
+
|
|
181
|
+
* `glob` 与 `grep`。本动词面**完全没有按内容检索的动词**(`file search` 匹配的是**文件名**),所以 `grep` 的 `lineNumber`/`line` 物理上无法产出,glob 的忽略规则与 mtime 排序也没有对应物。接管它们会静默返回一个不同的结果集。
|
|
182
|
+
* 后台命令与沙箱升级。后台调用的结果是一个 harness 作业 id,本动词面造不出来;`sandbox_permissions` 则是 harness 自己的审批缝。
|
|
183
|
+
* 相对路径。内置工具经 `ctx.fs` 按会话工作区解析它们,本插件没有等价能力;去猜进程工作目录只会读到与调用方所指不同的文件。
|
|
184
|
+
|
|
185
|
+
**有两项性质是被刻意放弃的,开启前应当知道:**
|
|
186
|
+
|
|
187
|
+
1. **被接管的调用不再受文件系统沙箱约束。** 内置写路径经 `ctx.fs`,沙箱策略与"写前必读"的观察守卫都在那里;被接管的写直接执行动词,只剩 dsh-os 自己的受保护路径检查。这是真实的防御削弱,不是细节。
|
|
188
|
+
2. **上报的文本与磁盘上的字节可以不同。** 默认的*沿用原文件*格式下,CRLF 文件保持 CRLF、BOM 保持 BOM,而 `value.after` 是归一化后的 LF 文本——也就是内置 `read` 会显示、diff 卡片会比较的那份文本。若希望接管在字节上什么都不改,请选择*同内置*。
|
|
189
|
+
|
|
190
|
+
每一次被接管的调用在任何失败下都会回退到内置实现,因此接管只能改变结果**如何产生**,绝不会改变调用方**是否拿到结果**。面板的「最近活动」会显示实际接管了什么、以及任何回退的原因。
|
|
191
|
+
|
|
147
192
|
**选择 PowerShell 版本。** 默认仍是 5.1 产物(`bin/dsh-os.ps1`),**刻意不变**:已经工作的部署不应因为插件升级而在脚下改变行为。要选 7.6 产物(`bin/dsh-os7.ps1`):
|
|
148
193
|
|
|
149
194
|
* `powershellEdition: '7.6'` —— 插件配置;
|
|
@@ -155,6 +200,101 @@ src/host/bin/dsh-os.sh file charset "./a file.txt"
|
|
|
155
200
|
> [!IMPORTANT]
|
|
156
201
|
> **仍未证明:harness 自身的 loader 会在活动 profile 里组合这个 bundle。** 插件的解析、装载与执行路径现由 `tools/verify-bundle.mjs`、`tools/verify-plugin-load.mjs` 与 `tools/verify-editions.mjs` 覆盖(详见[安装现状](#安装现状与已知缺陷)),但"装进用户的桌面活动 profile 并重启 harness"这一步**没有被跑过**。
|
|
157
202
|
|
|
203
|
+
### 工作流
|
|
204
|
+
|
|
205
|
+
本包其余各行让一次**操作系统调用**变正确;这一行让一个**需求**在被执行之前就被决定。它消除的失效模式是:一个从未成为规格的需求——模型直接开始改文件,等到评审时才发现方向偏了,而 token 已经花掉。
|
|
206
|
+
|
|
207
|
+
`dsh-os-workflow` 注册一个工具 `workflow_plan`,以及一个系统提示词小节。
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
intake -> plan -> choose -> run -> done
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
| 阶段 | 动作 | 它确立了什么 |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| intake | `start` | **逐字**记录的提示词,外加 1–3 个关于真实意图的问题,**或**你所做的假设 |
|
|
216
|
+
| plan | `plan` | 方案与流程、**至少两个**各带利弊的备选、评判标准,以及你的推荐 |
|
|
217
|
+
| choose | `choose` | **用户的选择**,在同一次调用内经 `ctx.userQuestions` 取得 |
|
|
218
|
+
| run | `run` | 执行、步骤,以及在同一次调用内关闭工作流的结论 |
|
|
219
|
+
| — | `note` | 执行期间记下的一条备注或一个决定 |
|
|
220
|
+
| — | `checkpoint` | 把记录折叠进一个文件,并回传摘要 |
|
|
221
|
+
| — | `status`、`reset` | 读取状态;丢弃并重新开始 |
|
|
222
|
+
|
|
223
|
+
> [!IMPORTANT]
|
|
224
|
+
> **工具名是 `workflow_plan` 而不是 `workflow`,原因是真实存在的命名冲突。** 宿主组合里已经有一个内置的 `workflow` 工具——来自 `@deepseek-ai/dsh-tool-workflow` 的 JavaScript 多 agent 编排器。同一作用域内两个工具同名会相撞:后注册者中止,或者两者都解析成功、模型在同一名字下拿到两个互不相关的工具。由于本行的工具位于 host 层,而 `workflow` 是按 agent preset 挂载的,本仓库里没有任何测试能发现这一点;它是在读取**实时工具目录**时发现的。`_plan` 同时也说明了这个工具是什么:工作流的规划那一半。
|
|
225
|
+
|
|
226
|
+
**门是硬的。** 入口尚未记录的阶段会被拒绝,且拒绝信息会点明必须先跑哪个阶段。`choose` 无法由模型自己回答:问题经与 `ask_user_question` 相同的缝交给用户,除此之外没有任何路径能进入 `run`。
|
|
227
|
+
|
|
228
|
+
**四次调用、一次提问。** 不可避免的路径是 `start` → `plan` → `choose` → `run`。它原本是八次调用、两次提问,差别来自三处刻意的合并——每一处都去掉一个来回,而没有去掉任何一个决定:
|
|
229
|
+
|
|
230
|
+
* **设计与备选是同一次调用**,因为它们本就是同一个决定。这就是 QOC(Questions、Options、Criteria)与 ADR(Context、Decision、Consequences)的形状:一条决策记录同时持有它被选出的那些备选、以及评判它们所用的标准。拆成两步会让第二步重新推导第一步,并让用户把同一份架构读两遍。
|
|
231
|
+
* **意图清晰的请求零提问。** `start` 接受 `assumptions` 而非 `questions`,因此不含歧义的提示词不会被打断——而假设被记录在案,用户可以纠正,这比它所替代的那个问题更便宜。"最小且相关"的提问优于盘问,其证据是 Ask-to-Act 这一线工作([arXiv:2504.00907](https://arxiv.org/abs/2504.00907))。
|
|
232
|
+
* **单步执行在开启的同一次调用内关闭。** 给 `run` 传 `outcome` 即结束工作流;只有多步工作才需要第二次调用。
|
|
233
|
+
|
|
234
|
+
**压缩是另一半。** 被叙述进对话的计划会随着之后每一步被重新发送,这正是短任务变贵的原因。所以它从不进入对话:每个阶段写下一份真正的文档,只回传一行——阶段、摘要、文件路径、下一个调用。`tests/workflow.test.mjs` 会度量这个比例,并断言任何阶段的信封都不超过 2000 字符。这个形状有实测依据而非风格偏好:剪枝加摘要能在降低 token 与运行时的同时提升完成率([Less Context, Better Agents](https://export.arxiv.org/pdf/2608.16370)),而规划/执行分离能以 token 成本的一小部分达到与交错推理相当的准确率([Plan Once, Ground Locally](https://zenodo.org/records/22904595))。
|
|
235
|
+
|
|
236
|
+
**开销落在哪里**,以复杂度表述,因为它本来就是复杂度:
|
|
237
|
+
|
|
238
|
+
| 路径 | 开销 |
|
|
239
|
+
|---|---|
|
|
240
|
+
| 读取状态 | 每次工作流调用一次,按文件 mtime 缓存——而**绝不在模型步上**发生,因为提示词小节根本不读状态 |
|
|
241
|
+
| 一次阶段跃迁 | 一次原子状态写入;第一版每个动作写三次 |
|
|
242
|
+
| 执行期间的一条 note | 一次 `appendFileSync` 追加,O(1),由压实维持有界——而不是"读-推-重写"那种在整个工作流上 O(n²) 的做法 |
|
|
243
|
+
| 提示词小节 | 一个**常量**:不读、不查、不格式化,在每个工作流的每个阶段都完全一致 |
|
|
244
|
+
|
|
245
|
+
### KV 缓存效应——提示词小节为何必须是常量
|
|
246
|
+
|
|
247
|
+
这一行注册进系统提示词的小节是一个**常量字符串**。这不是为了整洁,这就是设计本身,理由是:提示词是任何会变动的内容最不该出现的地方。
|
|
248
|
+
|
|
249
|
+
**本包实际测到了什么。** `tools/measure-cache.mjs` 直连真实端点(`deepseek-flash`、`max_tokens: 1`、同一条谱系先热身一次、每个变量只发一次请求),读取 `cache_read_input_tokens`。测了两遍,结果一致:
|
|
250
|
+
|
|
251
|
+
| 形状 | 缓存读 | 总输入 | 命中率 |
|
|
252
|
+
|---|---:|---:|---:|
|
|
253
|
+
| 不作改动(对照) | 5,632 | 5,780 | 97.4% |
|
|
254
|
+
| 追加一条 user 消息 | 5,632 | 5,784 | 97.4% |
|
|
255
|
+
| 追加一条 tool result | 5,632 | 5,853 | 96.2% |
|
|
256
|
+
| **一次工作流:四次追加调用** | 5,632 → 5,888 | 每次约 5,900 | **均值 96.6%** |
|
|
257
|
+
|
|
258
|
+
也就是说,本行自身的形状是被实测为「热」的:四次调用、23,040 个缓存命中 token、每一次都在 96% 以上。
|
|
259
|
+
|
|
260
|
+
**本包没能测到、因此不会声称的部分。** 实验同样尝试了反事实——重写前导系统提示词、改一个工具描述、对调两个工具——但这些读数**不可复现**:第一遍全部读作 0.0%,第二遍全部读作 97%。provider 文档给出了原因,且这一点本身值得知道:它会持久化**跨请求检测到的公共前缀**,因此到第二遍时,反复发送的相同前缀已被学习,改动之后依然能匹配。共享账号上的「每变量 A/B 隔离」无法对改动一侧下定论,所以 `tools/measure-cache.mjs` 只对追加一侧下断言(那一侧稳定),其余只作为观察打印、不作为结论。
|
|
261
|
+
|
|
262
|
+
**反事实一侧改由独立证据支撑**,而这些证据很强:
|
|
263
|
+
|
|
264
|
+
* provider 自己的规则是:一次缓存命中要求请求**完整匹配一个已持久化的 cache prefix unit**,而系统提示词排在工具目录与全部历史之前([上下文硬盘缓存](https://api-docs.deepseek.com/zh-cn/guides/kv_cache/));
|
|
265
|
+
* 在同一端点上的十变量实验发现:末尾追加消息保住 **98.7%** 命中,而在**系统提示词末尾**加一行得到 **0.0%**([前缀稳定性一章](https://github.com/diguike/book-deepseek-harness/blob/main/book/11-prefix-stability.md));
|
|
266
|
+
* harness 自己付过这笔账:沙箱策略原本写在系统提示词里,用户切一次权限,缓存命中从 **约 14,700 token 掉到 256**;修法是把会变的事实挪出提示词、放进消息序列([DSH Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md))。
|
|
267
|
+
|
|
268
|
+
**这一行的第一版把当前阶段渲染进了那个小节**,于是四次调用的工作流会三次重写系统提示词。无论 provider 某一天是否恰好宽容这件事,本行都不该依赖它——所以修法是结构性的:
|
|
269
|
+
|
|
270
|
+
* 小节是常量,因此本行的**缓存效应是 `prefix-stable`**:每个工作流零次提示词变更,由 `tests/workflow.test.mjs` 在每个阶段对注册文本逐字节断言,并在删掉 `state.json` 之后再断言一次;
|
|
271
|
+
* 当前阶段与**下一个该发的调用**改由 `workflow_plan` 工具自身的结果携带——追加的历史,上表实测 96–97%;
|
|
272
|
+
* 顺序错误的调用会被拒绝,且拒绝信息点明必须先记录哪个阶段,因此模型无需提示词告知任何东西也能自我纠正;
|
|
273
|
+
* 发现入口放在工具**描述**里。它位于工具目录中:在前缀之内、只发送一次、整个会话不变。
|
|
274
|
+
|
|
275
|
+
> [!IMPORTANT]
|
|
276
|
+
> **读者应当为自己插件得出的结论。** 任何逐步变化的东西——时间、当前目录、分支、权限、阶段——都不能写进提示词小节。本 harness 为这类内容准备了专门的通道:用 `ctx.systemPrompt.context()` 注册,它会作为一条追加的 user 消息送达,并且只在内容变化时重新投影。小节只用于在整个会话内稳定的文本。
|
|
277
|
+
|
|
278
|
+
**状态存放在哪。** `$DSH_HOME/dsh-os/workflows/<session>/`,与设置文档同级,理由相同:包目录会在升级时被整体替换,而工作区属于用户。`state.json` 是紧凑记录,其余文件都是细节。已结束的工作流会被归档而不是覆盖,因此下一个从干净状态开始,而上一个仍然可读。
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
state.json 紧凑记录:阶段、意图、决定、步骤、产物路径
|
|
282
|
+
journal.jsonl 仅追加:备注与步骤,有界,由 checkpoint 折入
|
|
283
|
+
01-intent.md 逐字提示词,随后是问答(或假设)
|
|
284
|
+
02-plan.md 方案与流程、备选及其利弊、标准、决定
|
|
285
|
+
03-execution.md 选中的选项、目标、步骤、涉及文件、约束
|
|
286
|
+
04-outcome.md 交付了什么,以及任何仍未关闭的步骤
|
|
287
|
+
05-checkpoint.md 折叠:至今为止的全部决定,在需要压缩时写出
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**可配置项。** `workflowRoot` 改变目录位置;`autoCompact`(默认 `true`)控制 checkpoint 是否报告引擎可能执行的压缩,`maxCheckpoints` 限制其数量。结构性的一半——细节落盘、只回传一行——**不可配置**,因为它正是这个功能能省下东西的原因。
|
|
291
|
+
|
|
292
|
+
> [!WARNING]
|
|
293
|
+
> **这一行会向用户提问,因此部署必须能回答。** 它注入 `userQuestions`;缺少该服务的宿主什么都不注册,而不是注册一个第二次调用就会失败的工具。有两次调用会阻塞等人:给了问题的 `start`,以及永远如此的 `choose`。
|
|
294
|
+
|
|
295
|
+
> [!NOTE]
|
|
296
|
+
> **这一行不会调用引擎的压缩服务。** `ctx.compaction` 确实存在,但从这里调用它意味着构造一个会话形状的上下文,而那个契约并未公开——那是把猜测包装成功能。本插件的做法是确定且可检查的:细节离开对话、进入文件,而 `checkpoint` 记录下压缩安全的边界。正因如此,这里的压缩是被 `tests/workflow.test.mjs` **度量**的,而不是仅仅被声称。
|
|
297
|
+
|
|
158
298
|
---
|
|
159
299
|
|
|
160
300
|
## JSON 契约
|
|
@@ -397,7 +537,7 @@ dsh-os permission audit --limit 50 # 追加式尾部;委托裁剪到 200
|
|
|
397
537
|
| 校验器 | 证明什么 | 用例 |
|
|
398
538
|
|---|---|---|
|
|
399
539
|
| `tools/verify-bundle.mjs` | loader 的**解析**契约:manifest 头、patch 文件、每一行都能从一个 profile 形态的 `node_modules` 解析、每个被解析模块都导出 loader 会调用的 Cordis 形状(`apply`/`inject`/`name`)、两行解析到**不同**模块(共享说明符会导致同一个工具注册两次并让整个入口中止)、以及版本闸门 | 10 |
|
|
400
|
-
| `tools/verify-plugin-load.mjs` | 真实的 `apply()` 路径:宿主插件以**根类型为 `object`** 的参数 schema 注册 `dsh_os`,授权插件注册其五个工具,并且**注册后的 handler 真的 spawn 了脚本**并返回真实信封(`selfcheck` → ok,`verbCount` 86);不带 `allow` 的变更类动词以带类型的 `E_PERMISSION`
|
|
540
|
+
| `tools/verify-plugin-load.mjs` | 真实的 `apply()` 路径:宿主插件以**根类型为 `object`** 的参数 schema 注册 `dsh_os`,授权插件注册其五个工具,并且**注册后的 handler 真的 spawn 了脚本**并返回真实信封(`selfcheck` → ok,`verbCount` 86);不带 `allow` 的变更类动词以带类型的 `E_PERMISSION` 被拒,而不是抛异常。另含工作流一节:工具与提示词小节确实注册,且 `start → plan → choose → run` 经真实 handler 跑通、阶段门依然成立 | 30 |
|
|
401
541
|
| `tools/verify-editions.mjs` | 版本选择:默认仍是 PowerShell 5.1;`powershellEdition: '7.6'` 与 `DSH_OS_POWERSHELL_EDITION` 选中 7.6 产物;`'auto'` 仅在产物与可用的 `pwsh` **同时**存在时才用 7.6;配置优先于环境变量;显式 `scriptPath` 完全禁用该选择。`-ExecutionPolicy` 只传给 5.1 可执行文件 | 14 |
|
|
402
542
|
|
|
403
543
|
bundle 解析校验把包以 junction 链接进一个一次性的 profile 沙箱,因此解析行为与真实 profile 一致,而**活动 profile 从不被写入**。
|
|
@@ -451,7 +591,7 @@ bundle 解析校验把包以 junction 链接进一个一次性的 profile 沙箱
|
|
|
451
591
|
npm run test:all # 按顺序跑下面全部
|
|
452
592
|
```
|
|
453
593
|
|
|
454
|
-
`test:all` 依次串联 `test:conformance`、`test:encoding`、`test`(Windows 黄金)、`test:plugin`、`test:permission`、`test:bundle`(`tools/verify-bundle.mjs`)、`test:load`(先 `tools/verify-plugin-load.mjs`,再 `tools/verify-editions.mjs`)与 `test:desktop`(`bash tests/smoke-desktop-sh.sh`)。
|
|
594
|
+
`test:all` 依次串联 `test:conformance`、`test:encoding`、`test`(Windows 黄金)、`test:plugin`、`test:takeover`、`test:permission`、`test:workflow`、`test:bundle`(`tools/verify-bundle.mjs`)、`test:package`(`tools/verify-package.mjs`)、`test:load`(先 `tools/verify-plugin-load.mjs`,再 `tools/verify-editions.mjs`)与 `test:desktop`(`bash tests/smoke-desktop-sh.sh`)。
|
|
455
595
|
|
|
456
596
|
| 套件 | 命令 | 用例数 | 实测结果 |
|
|
457
597
|
|---|---|---|---|
|
|
@@ -461,11 +601,20 @@ npm run test:all # 按顺序跑下面全部
|
|
|
461
601
|
| POSIX 黄金 | `bash tests/golden-sh.sh` | 116 | **116 通过,0 失败** |
|
|
462
602
|
| POSIX 桌面注入 | `bash tests/smoke-desktop-sh.sh` | 52 | **52 通过,0 失败** |
|
|
463
603
|
| 插件 | `node tests/plugin.test.mjs` | 13 | **13 通过,0 失败** |
|
|
604
|
+
| 接管拦截 | `node tests/takeover.test.mjs` | 41 | **41 通过,0 失败** |
|
|
464
605
|
| 授权委托 | `node tests/permission.test.mjs` | 18 | **18 通过,0 失败** |
|
|
465
|
-
|
|
|
466
|
-
|
|
|
606
|
+
| 工作流 | `node tests/workflow.test.mjs` | 31 | **31 通过,0 失败** |
|
|
607
|
+
| bundle 解析 | `node tools/verify-bundle.mjs` | 16 | **16 通过,0 失败** |
|
|
608
|
+
| 打包完整性 | `node tools/verify-package.mjs` | 21 | **21 通过,0 失败** |
|
|
609
|
+
| 插件装载 | `node tools/verify-plugin-load.mjs` | 30 | **30 通过,0 失败** |
|
|
467
610
|
| 版本选择 | `node tools/verify-editions.mjs` | 14 | **14 通过,0 失败** |
|
|
468
611
|
| 构建漂移 | `powershell -File tools/build.ps1 -Check` | 3 个生成物 | **无漂移** |
|
|
612
|
+
| 前缀缓存(`npm run measure:cache`) | `node tools/measure-cache.mjs` | 2 项断言 | **PASS——四次工作流调用均值命中 96.6%** |
|
|
613
|
+
|
|
614
|
+
`measure:cache` **不在** `test:all` 里:它直连真实 provider,因此需要 `DEEPSEEK_API_KEY`(从 `$DSH_HOME/.credentials.yaml` 读取)并会消耗 token。只有当某项被测量的断言不成立时它才以非零码退出。
|
|
615
|
+
|
|
616
|
+
> [!NOTE]
|
|
617
|
+
> **`test:desktop` 需要 `bash`,而本机没有。** 因此在没有 Git Bash 或 WSL 的 Windows 主机上,`npm run test:all` 会以 `'bash' is not recognized` 结束。这是运行环境缺口而非断言失败:同一次调用中位于它之前的所有套件都跑过并通过。POSIX 套件在这里依旧未被验证,原因与过去相同——它们需要一台 POSIX 主机。
|
|
469
618
|
|
|
470
619
|
### 两张表的一致性
|
|
471
620
|
|