deepseek-coordination 0.1.5 → 0.1.7
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 +7 -6
- package/README.zh-CN.md +1 -1
- package/package.json +2 -1
- package/src/permission/index.js +6 -1
- package/src/plugin/index.js +5 -1
- package/src/plugin/invoke.js +7 -2
- package/src/plugin/settings.js +13 -4
- package/src/plugin/takeover.js +5 -1
- package/src/plugin/workflow.js +55 -25
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ deepseek-coordination/
|
|
|
85
85
|
├─ tests/ golden (Windows/POSIX/desktop injection), table conformance, encoding, plugin, permission, takeover, workflow suites
|
|
86
86
|
│ ├─ conformance.ps1 command.txt and spec/verbs.tsv must describe the same surface (5 cases)
|
|
87
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 (
|
|
88
|
+
│ ├─ workflow.test.mjs phase gates, the user's choice, and the KV-cache invariant (31 cases)
|
|
89
89
|
│ ├─ smoke-desktop-sh.sh what the POSIX desktop verbs actually inject (52 cases)
|
|
90
90
|
│ └─ fake-xdotool, fake-wmctrl recording stand-ins, so the desktop verbs can be verified with no X server
|
|
91
91
|
└─ permission.json authorization data (an empty file in the repository; that is the initial state)
|
|
@@ -914,7 +914,7 @@ npm run test:all # runs everything below, in order
|
|
|
914
914
|
| Plugin | `node tests/plugin.test.mjs` | 13 | **13 passed, 0 failed** |
|
|
915
915
|
| Takeover interceptions | `node tests/takeover.test.mjs` | 41 | **41 passed, 0 failed** |
|
|
916
916
|
| Permission delegate | `node tests/permission.test.mjs` | 18 | **18 passed, 0 failed** |
|
|
917
|
-
| Workflow | `node tests/workflow.test.mjs` |
|
|
917
|
+
| Workflow | `node tests/workflow.test.mjs` | 31 | **31 passed, 0 failed** |
|
|
918
918
|
| Bundle resolution | `node tools/verify-bundle.mjs` | 16 | **16 passed, 0 failed** |
|
|
919
919
|
| Package completeness | `node tools/verify-package.mjs` | 21 | **21 passed, 0 failed** |
|
|
920
920
|
| Plugin load | `node tools/verify-plugin-load.mjs` | 30 | **30 passed, 0 failed** |
|
|
@@ -1068,10 +1068,11 @@ Copyright (c) 2026 Eloy Kim
|
|
|
1068
1068
|
```
|
|
1069
1069
|
|
|
1070
1070
|
The licence file is pure ASCII, LF-terminated and BOM-free, consistent with the rest of the
|
|
1071
|
-
repository's encoding contracts.
|
|
1072
|
-
`package.json`'s `files` whitelist
|
|
1073
|
-
|
|
1074
|
-
|
|
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.
|
|
1075
1076
|
|
|
1076
1077
|
The verb contract and the specification documents are part of the source: a behaviour change should be
|
|
1077
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
|
@@ -603,7 +603,7 @@ npm run test:all # 按顺序跑下面全部
|
|
|
603
603
|
| 插件 | `node tests/plugin.test.mjs` | 13 | **13 通过,0 失败** |
|
|
604
604
|
| 接管拦截 | `node tests/takeover.test.mjs` | 41 | **41 通过,0 失败** |
|
|
605
605
|
| 授权委托 | `node tests/permission.test.mjs` | 18 | **18 通过,0 失败** |
|
|
606
|
-
| 工作流 | `node tests/workflow.test.mjs` |
|
|
606
|
+
| 工作流 | `node tests/workflow.test.mjs` | 31 | **31 通过,0 失败** |
|
|
607
607
|
| bundle 解析 | `node tools/verify-bundle.mjs` | 16 | **16 通过,0 失败** |
|
|
608
608
|
| 打包完整性 | `node tools/verify-package.mjs` | 21 | **21 通过,0 失败** |
|
|
609
609
|
| 插件装载 | `node tools/verify-plugin-load.mjs` | 30 | **30 通过,0 失败** |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-coordination",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Cross-platform atomic OS-interaction surface for the DeepSeek Harness: one argument-stable verb set implemented natively on each OS, with byte-exact charset, BOM and line-ending handling.",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"test:encoding": "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File tests/encoding.ps1",
|
|
94
94
|
"test:conformance": "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File tests/conformance.ps1",
|
|
95
95
|
"test:plugin": "node tests/plugin.test.mjs",
|
|
96
|
+
"test:activation": "node tests/activation.test.mjs",
|
|
96
97
|
"test:takeover": "node tests/takeover.test.mjs",
|
|
97
98
|
"test:permission": "node tests/permission.test.mjs",
|
|
98
99
|
"test:workflow": "node tests/workflow.test.mjs",
|
package/src/permission/index.js
CHANGED
|
@@ -192,7 +192,12 @@ function nextRuleId(document) {
|
|
|
192
192
|
return `r-${String(max + 1).padStart(4, '0')}`
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
export function apply(ctx,
|
|
195
|
+
export function apply(ctx, rawConfig) {
|
|
196
|
+
// `?? {}` rather than a default parameter: Cordis passes a row's config as `null` when the row declares
|
|
197
|
+
// none, and a default parameter only fires for `undefined`. This row happens to ship with a config, so
|
|
198
|
+
// it never hit the fault — but the fix belongs here too, or a deployment that trims the row's config
|
|
199
|
+
// takes the consent delegate down with it.
|
|
200
|
+
const config = rawConfig ?? {}
|
|
196
201
|
const path = permissionPathOf(config)
|
|
197
202
|
const auditLimit = config.auditLimit ?? AUDIT_LIMIT
|
|
198
203
|
|
package/src/plugin/index.js
CHANGED
|
@@ -27,7 +27,11 @@ export const name = 'dsh-os'
|
|
|
27
27
|
/** The plugin is inert without the tool registry. */
|
|
28
28
|
export const inject = ['tools']
|
|
29
29
|
|
|
30
|
-
export function apply(ctx,
|
|
30
|
+
export function apply(ctx, rawConfig) {
|
|
31
|
+
// `?? {}` rather than a default parameter: Cordis passes a row's config as `null` when the row declares
|
|
32
|
+
// none, and a default parameter only fires for `undefined`. Every entry point in this package guards the
|
|
33
|
+
// same way, so a row whose config is trimmed degrades to defaults instead of failing to activate.
|
|
34
|
+
const config = rawConfig ?? {}
|
|
31
35
|
const timeoutMs = config.timeoutMs ?? 120000
|
|
32
36
|
|
|
33
37
|
ctx.tools.register({
|
package/src/plugin/invoke.js
CHANGED
|
@@ -113,7 +113,11 @@ export const DEFAULT_SCRIPT_DIR = 'src/host/bin'
|
|
|
113
113
|
* DSH_OS_POWERSHELL still overrides the executable. `'auto'` uses 7.6 only when both the artefact and
|
|
114
114
|
* a pwsh executable are present, and falls back to 5.1 otherwise.
|
|
115
115
|
*/
|
|
116
|
-
export function resolveInvocation(
|
|
116
|
+
export function resolveInvocation(rawConfig) {
|
|
117
|
+
// `?? {}` rather than a default parameter. Cordis passes a row's config through as `null` when the row
|
|
118
|
+
// declares none, and a default parameter only fires for `undefined` — so `config.scriptPath` on that
|
|
119
|
+
// null threw and the plugin row failed to activate. Every caller may pass null.
|
|
120
|
+
const config = rawConfig ?? {}
|
|
117
121
|
const explicit = config.scriptPath ? [config.scriptPath] : []
|
|
118
122
|
const dir = config.scriptPath
|
|
119
123
|
? dirname(config.scriptPath)
|
|
@@ -192,7 +196,8 @@ export function resolveInvocation(config = {}) {
|
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
/** The directory an invocation would search, for error messages that name a real path. */
|
|
195
|
-
export function searchedDirectory(
|
|
199
|
+
export function searchedDirectory(rawConfig) {
|
|
200
|
+
const config = rawConfig ?? {}
|
|
196
201
|
return config.scriptPath ?? join(PKG_ROOT, config.scriptDir ?? DEFAULT_SCRIPT_DIR)
|
|
197
202
|
}
|
|
198
203
|
|
package/src/plugin/settings.js
CHANGED
|
@@ -110,9 +110,15 @@ export function writeSettings(file, settings) {
|
|
|
110
110
|
* second one is evidence that the feature works.
|
|
111
111
|
*/
|
|
112
112
|
export class SettingsService {
|
|
113
|
-
constructor(ctx, config
|
|
113
|
+
constructor(ctx, config) {
|
|
114
114
|
this.ctx = ctx
|
|
115
|
-
|
|
115
|
+
// `config ?? {}` rather than a default parameter: Cordis passes the row's config as `null` when the
|
|
116
|
+
// row declares none, and a default parameter does not fire for null. This is exactly what made the
|
|
117
|
+
// shipped 0.1.5 fail to activate with "Cannot read properties of null (reading 'settingsPath')".
|
|
118
|
+
const resolved = config ?? {}
|
|
119
|
+
this.file = typeof resolved.settingsPath === 'string' && resolved.settingsPath.trim() !== ''
|
|
120
|
+
? resolved.settingsPath
|
|
121
|
+
: settingsPath()
|
|
116
122
|
this.settings = readSettings(this.file)
|
|
117
123
|
this.activity = []
|
|
118
124
|
this.listeners = new Set()
|
|
@@ -312,8 +318,11 @@ export function makeSettingsRoutes(service) {
|
|
|
312
318
|
*/
|
|
313
319
|
export const name = 'dsh-os-settings'
|
|
314
320
|
|
|
315
|
-
export function apply(ctx,
|
|
316
|
-
|
|
321
|
+
export function apply(ctx, rawConfig) {
|
|
322
|
+
// `?? {}` rather than a default parameter: Cordis passes a row's config as `null` when the row declares
|
|
323
|
+
// none, and a default parameter only fires for `undefined`. This row ships WITHOUT a config, which is
|
|
324
|
+
// what made the shipped 0.1.5 fail with "Cannot read properties of null (reading 'settingsPath')".
|
|
325
|
+
const service = new SettingsService(ctx, rawConfig ?? {})
|
|
317
326
|
|
|
318
327
|
// `ctx.provide` REGISTERS the service and returns a disposer; `ctx.set` only overwrites the value of a
|
|
319
328
|
// service that was already provided, and calling it first throws
|
package/src/plugin/takeover.js
CHANGED
|
@@ -388,7 +388,11 @@ function eligible(tool, args) {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
export function apply(ctx,
|
|
391
|
+
export function apply(ctx, rawConfig) {
|
|
392
|
+
// `?? {}` rather than a default parameter: Cordis passes a row's config as `null` when the row declares
|
|
393
|
+
// none, and a default parameter only fires for `undefined`, so the first property read threw and the
|
|
394
|
+
// row never activated. This row is shipped WITHOUT a config, so it hit exactly that.
|
|
395
|
+
const config = rawConfig ?? {}
|
|
392
396
|
const toolTimeoutMs = config.timeoutMs ?? 120000
|
|
393
397
|
// Mirrors the `tool-fs` row's `readLimit` (2000 by default): the interception must not quietly apply
|
|
394
398
|
// a different window size than the built-in it replaced.
|
package/src/plugin/workflow.js
CHANGED
|
@@ -1199,20 +1199,31 @@ export function workflowSectionText() {
|
|
|
1199
1199
|
* The tool description.
|
|
1200
1200
|
*
|
|
1201
1201
|
* This is now the ONLY discovery path, because the prompt section is a frozen constant and cannot carry
|
|
1202
|
-
* guidance.
|
|
1203
|
-
*
|
|
1202
|
+
* guidance. Two consequences it has to honour, and the first version of this text failed the first one:
|
|
1203
|
+
*
|
|
1204
|
+
* 1. it must NAME the tool. The earlier text said "Four calls:" and then listed `start` / `plan` /
|
|
1205
|
+
* `choose` / `run`, which reads as four tool names — a model following it would call tools that do not
|
|
1206
|
+
* exist. Caught by verifying the packed tarball rather than the checkout.
|
|
1207
|
+
* 2. every call in the shortest path is spelled as `workflow_plan(action: "...")`, so the model never has
|
|
1208
|
+
* to guess how the pieces fit together. The per-phase guidance is deliberately not here: it travels in
|
|
1209
|
+
* each result's `next` field, which is appended history and therefore costs the cache nothing.
|
|
1210
|
+
*
|
|
1211
|
+
* It lives in the tool catalog, inside the cached prefix, sent once and unchanged for the session.
|
|
1204
1212
|
*/
|
|
1205
1213
|
const TOOL_DESCRIPTION = [
|
|
1206
|
-
'
|
|
1207
|
-
'
|
|
1208
|
-
'
|
|
1209
|
-
'"start"
|
|
1210
|
-
'
|
|
1211
|
-
'"
|
|
1212
|
-
'
|
|
1213
|
-
'
|
|
1214
|
-
'
|
|
1215
|
-
'
|
|
1214
|
+
'workflow_plan: agree on what is wanted and how it will be done, before doing it. Use it when a request is',
|
|
1215
|
+
'more than one obvious step, or when you would have to guess the user\'s intent, their preferred approach,',
|
|
1216
|
+
'or where the work belongs. Call it four times:',
|
|
1217
|
+
'1. workflow_plan(action: "start") — the user\'s prompt verbatim, plus 1-3 questions about intent OR the',
|
|
1218
|
+
'assumptions you are making.',
|
|
1219
|
+
'2. workflow_plan(action: "plan") — the approach and process, at least two alternatives with pros and cons,',
|
|
1220
|
+
'and which one you recommend.',
|
|
1221
|
+
'3. workflow_plan(action: "choose") — ASKS THE USER; their answer is recorded and you cannot answer it for them.',
|
|
1222
|
+
'4. workflow_plan(action: "run") — execute the chosen option; add "outcome" to close the workflow in this call.',
|
|
1223
|
+
'Each result carries "next", the one call to make. Then workflow_plan(action: "note") for a decision, or',
|
|
1224
|
+
'"checkpoint" at a boundary. Long documents go to files and only a line comes back, so the conversation stays',
|
|
1225
|
+
'small; results list the files. A step out of order is refused, and the refusal names the phase to record',
|
|
1226
|
+
'first. Also available: "status", "reset".',
|
|
1216
1227
|
].join(' ')
|
|
1217
1228
|
|
|
1218
1229
|
const QUESTION_SCHEMA = {
|
|
@@ -1394,12 +1405,17 @@ export function workflowToolDefinition(handlers) {
|
|
|
1394
1405
|
}
|
|
1395
1406
|
}
|
|
1396
1407
|
|
|
1397
|
-
export function apply(ctx, config
|
|
1398
|
-
|
|
1399
|
-
|
|
1408
|
+
export function apply(ctx, config) {
|
|
1409
|
+
// `config ?? {}` rather than a `config = {}` default parameter, because the two are NOT equivalent:
|
|
1410
|
+
// a default parameter only fires for `undefined`, and Cordis hands the row's config through as `null`
|
|
1411
|
+
// when the row declares none. The shipped 0.1.5 read `config.workflowRoot` off that null and the row
|
|
1412
|
+
// failed to activate with "Cannot read properties of null".
|
|
1413
|
+
const resolved = config ?? {}
|
|
1414
|
+
const root = typeof resolved.workflowRoot === 'string' && resolved.workflowRoot.trim() !== ''
|
|
1415
|
+
? resolved.workflowRoot
|
|
1400
1416
|
: workflowRoot()
|
|
1401
|
-
const autoCompact =
|
|
1402
|
-
const maxCheckpoints = Number.isInteger(
|
|
1417
|
+
const autoCompact = resolved.autoCompact !== false
|
|
1418
|
+
const maxCheckpoints = Number.isInteger(resolved.maxCheckpoints) && resolved.maxCheckpoints > 0 ? resolved.maxCheckpoints : 20
|
|
1403
1419
|
|
|
1404
1420
|
// The engine's own automatic compaction is named in the checkpoint result rather than invoked: this
|
|
1405
1421
|
// plugin holds no session handle, and a call into a service whose context contract is not published
|
|
@@ -1435,14 +1451,28 @@ export function apply(ctx, config = {}) {
|
|
|
1435
1451
|
|
|
1436
1452
|
if (typeof ctx.tools?.register === 'function') ctx.tools.register(workflowToolDefinition(handlers))
|
|
1437
1453
|
|
|
1438
|
-
// The prompt half is registered
|
|
1439
|
-
//
|
|
1440
|
-
//
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1454
|
+
// The prompt half is registered through `ctx.inject`, NOT by reading `ctx.systemPrompt` directly.
|
|
1455
|
+
//
|
|
1456
|
+
// Reading it directly is what broke the shipped 0.1.5: Cordis resolves a service property through an
|
|
1457
|
+
// enforcing proxy, so an undeclared service does not read as `undefined` — it THROWS
|
|
1458
|
+
// Error: cannot get property "systemPrompt" without inject
|
|
1459
|
+
// and a guard of the form `typeof ctx.systemPrompt?.section === 'function'` never gets to run, because
|
|
1460
|
+
// the property access itself is the throw. The row then failed to activate entirely, losing the tool
|
|
1461
|
+
// as well as the section.
|
|
1462
|
+
//
|
|
1463
|
+
// `ctx.inject` is the first-party pattern for an optional service (`dsh-tool-fs` registers its image
|
|
1464
|
+
// tool the same way): the callback runs when the service is present and unwinds when it goes away, and
|
|
1465
|
+
// a deployment without a system prompt keeps the tool instead of losing everything.
|
|
1466
|
+
//
|
|
1467
|
+
// The text is a constant: no state is read, nothing is looked up, and the model's cached prefix
|
|
1468
|
+
// survives every phase of every workflow. See SECTION_TEXT.
|
|
1469
|
+
if (typeof ctx.inject === 'function') {
|
|
1470
|
+
ctx.inject(['systemPrompt'], (scoped) => {
|
|
1471
|
+
scoped.systemPrompt.section({
|
|
1472
|
+
name: 'dsh-os:workflow',
|
|
1473
|
+
order: sectionOrder(scoped.systemPrompt),
|
|
1474
|
+
text: SECTION_TEXT,
|
|
1475
|
+
})
|
|
1446
1476
|
})
|
|
1447
1477
|
}
|
|
1448
1478
|
|