deepseek-coordination 0.1.6 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepseek-coordination",
3
- "version": "0.1.6",
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",
@@ -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, config = {}) {
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
 
@@ -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, config = {}) {
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({
@@ -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(config = {}) {
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(config = {}) {
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
 
@@ -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
- this.file = config.settingsPath ?? settingsPath()
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, config = {}) {
316
- const service = new SettingsService(ctx, config)
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
@@ -388,7 +388,11 @@ function eligible(tool, args) {
388
388
  }
389
389
  }
390
390
 
391
- export function apply(ctx, config = {}) {
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.
@@ -1405,12 +1405,17 @@ export function workflowToolDefinition(handlers) {
1405
1405
  }
1406
1406
  }
1407
1407
 
1408
- export function apply(ctx, config = {}) {
1409
- const root = typeof config.workflowRoot === 'string' && config.workflowRoot.trim() !== ''
1410
- ? config.workflowRoot
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
1411
1416
  : workflowRoot()
1412
- const autoCompact = config.autoCompact !== false
1413
- const maxCheckpoints = Number.isInteger(config.maxCheckpoints) && config.maxCheckpoints > 0 ? config.maxCheckpoints : 20
1417
+ const autoCompact = resolved.autoCompact !== false
1418
+ const maxCheckpoints = Number.isInteger(resolved.maxCheckpoints) && resolved.maxCheckpoints > 0 ? resolved.maxCheckpoints : 20
1414
1419
 
1415
1420
  // The engine's own automatic compaction is named in the checkpoint result rather than invoked: this
1416
1421
  // plugin holds no session handle, and a call into a service whose context contract is not published
@@ -1446,14 +1451,28 @@ export function apply(ctx, config = {}) {
1446
1451
 
1447
1452
  if (typeof ctx.tools?.register === 'function') ctx.tools.register(workflowToolDefinition(handlers))
1448
1453
 
1449
- // The prompt half is registered only when the registry is present, so a deployment without it loses
1450
- // the section rather than the feature. The text is a constant: no state is read, nothing is looked up,
1451
- // and the model's cached prefix survives every phase of every workflow. See SECTION_TEXT.
1452
- if (typeof ctx.systemPrompt?.section === 'function') {
1453
- ctx.systemPrompt.section({
1454
- name: 'dsh-os:workflow',
1455
- order: sectionOrder(ctx.systemPrompt),
1456
- text: SECTION_TEXT,
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
+ })
1457
1476
  })
1458
1477
  }
1459
1478