klypix-mcp 1.66.1 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,7 +79,7 @@ npx klypix-mcp conformance
79
79
 
80
80
  It runs in a temporary fixture and touches nothing else. It checks tool discovery, task memory,
81
81
  truthful peer reporting, overlap surfacing, proactive logging, and in-band delivery of a peer note.
82
- It verifies 15 required coordination behaviours — not the 21 tools, and not the retrieval engine.
82
+ It verifies 15 required coordination behaviours — not the 26 tools, and not the retrieval engine.
83
83
 
84
84
  ---
85
85
 
@@ -333,10 +333,13 @@ have to have declared their files for the overlap to be visible at all.
333
333
  `brain_message` leaves one-time coordination notes for other sessions. A supported KLYPIX action
334
334
  offers the note in model-visible context; the next independent supported action replays it and
335
335
  records an acknowledgement. That acknowledgement proves only that a later action followed the
336
- offer — never that a person read it or that an agent acted on it. Pending and offered notes survive
337
- reconnects. Expiry or bounded-capacity eviction records a failed per-recipient receipt instead of
338
- silently looking delivered. The core lane is machine-local, notes expire after 24 hours, and they
339
- are never written into the brain.
336
+ offer — never that a person read it or that an agent acted on it. The note keeps replaying until the
337
+ receiving model calls `brain_message_receipt` with the exact message id and per-recipient offer
338
+ token; only that token-bound action records `consumed`. Pending, offered, and acknowledged notes
339
+ survive reconnects. Expiry or bounded-capacity eviction records a failed per-recipient receipt
340
+ instead of silently looking delivered. The send-time audience is fixed, unresolved targeted sends
341
+ fail closed, the core lane is machine-local, notes expire after 24 hours, and they are never written
342
+ into the brain.
340
343
 
341
344
  Durable handoffs go in the brain itself — decisions, findings, open questions and skills captured
342
345
  as cards, each stamped with the agent that wrote it.
@@ -345,14 +348,18 @@ as cards, each stamped with the agent that wrote it.
345
348
 
346
349
  When a task publishes a quantified or otherwise machine-checkable claim, it can attach one or more
347
350
  versioned result manifests to `brain_sync { phase: "complete" }`. Each manifest binds the claim to a
348
- report hash, producer/run provenance, the exact input and configuration fingerprints, and named
349
- metrics with counts and tolerances. Matching peer evidence is recorded as corroboration; conflicting
350
- or incomparable evidence returns `needs-reconciliation` and keeps the task scope active.
351
+ report hash, producer/run provenance, the exact declared task scope, material artifact hashes,
352
+ evaluation outputs, public metric wording, input/configuration fingerprints, and named metrics with
353
+ counts and tolerances. Matching peer evidence is recorded as corroboration; conflicting or
354
+ incomparable evidence returns `needs-reconciliation` and keeps the task scope active.
351
355
 
352
356
  The gate fails closed. Once a task submits result evidence, it cannot bypass an invalid or
353
357
  conflicting result by retrying completion without the manifest, and that obligation survives worker
354
358
  restart, hibernation, and transparent hot-swap. A fresh `phase: "start"` is the explicit boundary for
355
359
  a new task. The strict schema and reusable validator are exported as `klypix-mcp/result-reconcile`.
360
+ Schema-v2 receipts can be converted into commit-bound publication evidence and independently checked
361
+ with `klypix-mcp/release-evidence`; legacy schema-v1 results remain usable for coordination but cannot
362
+ authorize publication.
356
363
 
357
364
  ## Human control in Klypix
358
365
 
@@ -488,7 +495,7 @@ The MCP verbs below are what agents call. These are what **you** call:
488
495
 
489
496
  ---
490
497
 
491
- ## The 21 verbs
498
+ ## The 26 verbs
492
499
 
493
500
  | Tool | What it does |
494
501
  |---|---|
@@ -500,12 +507,17 @@ The MCP verbs below are what agents call. These are what **you** call:
500
507
  | `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery and unresolved views |
501
508
  | `brain_garden` | Maintenance pass — proposes first, and cannot apply without an approval code the human generates |
502
509
  | `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
503
- | `brain_message` | Session-to-session coordination notes with per-recipient offer / later-action acknowledgement / failure receipts (24h TTL, never written into the brain) |
510
+ | `brain_message` | Session-to-session coordination notes with a fixed send-time audience and per-recipient pending / offer / acknowledgement / consumption / failure receipts (24h TTL, never written into the brain) |
511
+ | `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
504
512
  | `brain_sync` | Context Gateway: task capsule, active-task peers, exact-file overlap, one-time alerts, timing, and optional result-manifest reconciliation |
505
513
  | `brain_connect` | Find and draw related-but-unlinked cards |
506
514
  | `project_map_context` | Read-only, bounded code-graph evidence beside correction-aware brain context, with exact-path review proposals; external artifacts (e.g. Graphify) are supported but never installed or run locally |
507
515
  | `project_map_scan` | KLYPIX's own zero-install scanner: gitignore-aware file inventory + file-level import edges (relative, tsconfig-alias, and monorepo-workspace imports resolved) written to `klypix-map/graph.json` — which then serves `project_map_context` automatically |
508
516
  | `project_map_drift` | Read-only drift report: brain cards whose referenced files are gone or moved (with rename candidates), plus a headline when the checkout itself is behind its origin default branch |
517
+ | `remote_status` | Inspect the local KLYPIX tray relay and its verified Remote capabilities |
518
+ | `remote_sessions` | List coding-agent sessions with exact provider, host-binding and capability receipts |
519
+ | `remote_actions` | List pending questions, approvals, failures, conflicts and reviews reported by supported providers |
520
+ | `remote_command` | Control one exact verified coding-agent session using a fresh capability receipt; unsupported operations fail closed |
509
521
  | `canvas_view` | Returns the board as a structured render spec plus a text summary, and declares an MCP Apps (SEP-1865) UI resource |
510
522
  | `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
511
523
  | `search_canvases` | Search across canvases by name and content |
@@ -514,7 +526,7 @@ The MCP verbs below are what agents call. These are what **you** call:
514
526
  | `add_to_canvas` | Append cards/connections (positions preserved) |
515
527
  | `list_canvases` | List every `.klypix` in the vault |
516
528
 
517
- Exactly 21, machine-verifiable with `npx klypix-mcp doctor`.
529
+ Exactly 26, machine-verifiable with `npx klypix-mcp doctor`.
518
530
 
519
531
  > **`canvas_view`:** no MCP Apps host has been observed rendering the UI resource yet — there is no
520
532
  > screenshot and no host-level test. Hosts without the extension get clean text, which is the path
@@ -714,7 +726,8 @@ Your `brain.klypix` is yours — it is a plain ZIP and stays readable with or wi
714
726
  Issues and pull requests: [github.com/dahshanlabs/klypix-mcp](https://github.com/dahshanlabs/klypix-mcp).
715
727
  Questions or feedback: [hello@klypix.com](mailto:hello@klypix.com).
716
728
 
717
- The repository carries 59 test files, 54 of them in the `npm test` chain, covering the presence
729
+ The repository carries 68 test files: 62 listed directly in `scripts.test`, plus the
730
+ `pretest` workflow gate. Together they cover the presence
718
731
  lane and its cross-machine relay, the Context Gateway, supervisor hot-swap, auto-update, retrieval
719
732
  quality, decay, challenge, lenses, the format guard, the git tools (including a real `git merge`
720
733
  through the merge driver), uninstall, and conformance. Run them with `npm test` from a clone — they
@@ -214,19 +214,21 @@ try {
214
214
  text: 'verified note',
215
215
  ts: now - 1_000,
216
216
  candidateIds: ['finding-owner'],
217
- deliveryVersion: 2,
217
+ deliveryVersion: 3,
218
218
  deliveries: [{
219
219
  recipientId: 'finding-owner',
220
- state: 'acknowledged',
220
+ state: 'consumed',
221
221
  attempts: 1,
222
+ offerToken: 'conformance-offer-token-000000',
222
223
  offeredAt: now - 900,
223
224
  acknowledgedAt: now - 500,
225
+ consumedAt: now - 250,
224
226
  }],
225
227
  seen: ['finding-owner'],
226
228
  }],
227
229
  sessions: lane, selfId: 'finding-sender', now,
228
230
  });
229
- checks.findingReceiptRendered = /model-context delivery acknowledged by all 1 target peer\(s\) on a later action \(not human-read\)/.test(renderReceiptSummary(receipt));
231
+ checks.findingReceiptRendered = /explicitly consumed by all 1 target peer\(s\) after model-context delivery \(not human-read\)/.test(renderReceiptSummary(receipt));
230
232
  }
231
233
 
232
234
  // ── Cross-PC presence: simulated two-machine scenario ─────────────────────
@@ -277,6 +279,23 @@ try {
277
279
  });
278
280
  checks.crossMachineConsentGate = framesSent === 0 && gated.reason === 'no-consent';
279
281
 
282
+ // Give A a current B recipient before the message is created. Delivery v3
283
+ // snapshots concrete sessions at send time; a message created while A is
284
+ // still unaware of B is correctly rejected as a zero-audience broadcast.
285
+ const bToA = [];
286
+ relayOutbound({
287
+ sessions: listActiveSessions({ brainPath: brainB, home: homeB, now }),
288
+ consent: GRANT, machineId: 'xpc-mach-b', hostLabel: 'MACHINE-B', root: repoB, now,
289
+ send: (frame) => bToA.push(frame),
290
+ });
291
+ const rowsOnA = bToA
292
+ .map((frame) => relayInbound(frame, { consent: GRANT, machineId: 'xpc-mach-a', now: now + 250 }))
293
+ .filter((inbound) => inbound?.type === 'presence')
294
+ .map((inbound) => inbound.row);
295
+ if (rowsOnA.length) {
296
+ upsertRemoteSessions({ brainPath: brainA, rows: rowsOnA, machineId: 'xpc-mach-a', home: homeA, now: now + 250 });
297
+ }
298
+
280
299
  // Live channel: A's session and message reach B exactly once.
281
300
  const wire = [];
282
301
  relayOutbound({
@@ -358,7 +377,7 @@ const result = {
358
377
  metrics,
359
378
  contract: {
360
379
  proactive: 'best-effort MCP logging notification',
361
- inBand: 'a retained machine-local note is offered on a supported model-context KLYPIX action, then acknowledged only by a later independent action; expiry/overflow are failed receipts',
380
+ inBand: 'a retained machine-local note is offered on a supported model-context KLYPIX action, acknowledged only by a later independent action, and retired only by explicit token-bound consumption; expiry/overflow are failed receipts',
362
381
  crossMachine: 'relay primitives require caller-confirmed durable insertion and a per-recipient-machine acknowledgement; app bridge wiring is a separate conformance boundary',
363
382
  },
364
383
  };
@@ -372,6 +391,6 @@ if (jsonMode) {
372
391
  }
373
392
  if (checks.error) console.log(` error: ${checks.error}`);
374
393
  console.log(` memory/coordination: ${metrics.firstClientMs ?? '?'}ms / ${metrics.secondClientMs ?? '?'}ms`);
375
- console.log(' proactive notifications are best-effort; retained notes use offer → later-action acknowledgement, with explicit failure receipts.');
394
+ console.log(' proactive notifications are best-effort; retained notes use offer → later-action acknowledgement → explicit token-bound consumption, with explicit failure receipts.');
376
395
  }
377
396
  process.exit(ok ? 0 : 1);
@@ -29,6 +29,8 @@ import {
29
29
  codexPresenceHookStatus,
30
30
  mergeCodexPresenceHooks,
31
31
  } from '../src/codex-hooks.mjs';
32
+ import { brainInstallDecision } from '../src/install-version.mjs';
33
+ import { acquireInstallLockSync, releaseInstallLockSync } from '../src/install-lock.mjs';
32
34
 
33
35
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
36
  const PKG_ROOT = path.resolve(__dirname, '..');
@@ -147,45 +149,10 @@ function reportCodex(result) {
147
149
  console.error(' Claude Code installation is intact. Fix the Codex warning, then re-run this command.');
148
150
  }
149
151
 
150
- // ── Never-downgrade gate ─────────────────────────────────────────────────────
151
- // The brain version is a UNIFIED namespace = the klypix-mcp version, stamped by BOTH
152
- // the npm install (here) and the desktop bundle, so the two channels compare cleanly.
153
- // A dev deploy ({dev:true}) is authoritative; a strictly-newer install is not
154
- // downgraded. --force overrides both.
155
- const cmpSemver = (a, b) => { const pa = String(a || '').split('.').map(n => parseInt(n, 10) || 0), pb = String(b || '').split('.').map(n => parseInt(n, 10) || 0); for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); } return 0; };
156
- const cur = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.brain-version.json'), 'utf8')); } catch { return null; } })();
157
- if (!FORCE && cur) {
158
- if (cur.dev === true) {
159
- console.log(`• A dev deploy owns ${BRAIN_DIR} (dev:true) — leaving it untouched. Re-run with --force to override.`);
160
- if (!RUNTIME_ONLY) reportCodex(wireCodex());
161
- process.exit(0);
162
- }
163
- if (cur.brainVersion && cmpSemver(cur.brainVersion, VERSION) > 0) {
164
- console.log(`• Installed brain v${cur.brainVersion} is newer than this package v${VERSION} — not downgrading. Re-run with --force to override.`);
165
- if (!RUNTIME_ONLY) reportCodex(wireCodex());
166
- process.exit(0);
167
- }
168
- }
169
-
170
152
  // ── Install lock (auto-propagation, part D — concurrency) ─────────────────────
171
- // Two sessions can self-update at the same moment; serialize so their writes never
172
- // tear. O_EXCL create wins; a lock older than 60s (a sub-second install should never
173
- // take that) is stolen so a crashed installer can't wedge every future update.
174
- const LOCK = path.join(BRAIN_DIR, '.install.lock');
175
- const sleepSync = (ms) => { try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* */ } };
176
- function acquireLock() {
177
- try { fs.mkdirSync(BRAIN_DIR, { recursive: true }); } catch { /* */ }
178
- for (let i = 0; i < 120; i++) {
179
- try { const fd = fs.openSync(LOCK, 'wx'); fs.writeSync(fd, String(process.pid)); fs.closeSync(fd); return true; }
180
- catch (e) {
181
- if (e && e.code !== 'EEXIST') return false;
182
- try { if (Date.now() - fs.statSync(LOCK).mtimeMs > 60000) { fs.unlinkSync(LOCK); continue; } } catch { /* raced on the stale file — retry */ }
183
- sleepSync(50);
184
- }
185
- }
186
- return false;
187
- }
188
- const releaseLock = () => { try { fs.unlinkSync(LOCK); } catch { /* */ } };
153
+ // npm and desktop use this exact token-owned lock. The version decision is made
154
+ // only AFTER acquisition, so an older queued installer re-reads and preserves a
155
+ // newer runtime that committed while it waited.
189
156
 
190
157
  // Migrate THIS project's .mcp.json klypix-canvas entry off `npx` onto the local
191
158
  // bundle now that it's installed — the desync fix for EXISTING configs (the self-
@@ -230,12 +197,33 @@ const flatten = (code) => code
230
197
  .replace(/klypix-worker\.mjs/g, 'klypix-mcp-worker.mjs')
231
198
  .replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
232
199
 
233
- const gotLock = acquireLock();
234
- if (!gotLock) {
235
- console.error(`✗ another KLYPIX install still owns ${LOCK}; no files were changed`);
200
+ const installLock = acquireInstallLockSync(BRAIN_DIR);
201
+ if (!installLock) {
202
+ console.error(`✗ another KLYPIX install still owns ${path.join(BRAIN_DIR, '.install.lock')}; no files were changed`);
236
203
  process.exit(1);
237
204
  }
238
205
  try {
206
+ // Never-downgrade gate: Brain Core semver is the unified payload identity.
207
+ // Read both receipts under the shared lock and conservatively keep the
208
+ // highest valid committed/recorded version. appVersion is provenance only.
209
+ const stamp = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.brain-version.json'), 'utf8')); } catch { return null; } })();
210
+ const runtimeReceipt = (() => { try { return JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, '.mcp-runtime.json'), 'utf8')); } catch { return null; } })();
211
+ const decision = brainInstallDecision({ candidateVersion: VERSION, stamp, runtime: runtimeReceipt, force: FORCE });
212
+ if (decision.action === 'refuse') {
213
+ releaseInstallLockSync(installLock);
214
+ console.error(`✗ package Brain Core version ${JSON.stringify(VERSION)} is invalid; refusing to install unidentified runtime files`);
215
+ process.exit(1);
216
+ }
217
+ if (decision.action === 'preserve') {
218
+ releaseInstallLockSync(installLock);
219
+ if (decision.reason === 'dev-owned') {
220
+ console.log(`• A dev deploy owns ${BRAIN_DIR} (dev:true) — leaving it untouched. Re-run with --force to override.`);
221
+ } else {
222
+ console.log(`• Installed brain v${decision.installedVersion} is newer than this package v${VERSION} — not downgrading. Re-run with --force to override.`);
223
+ }
224
+ if (!RUNTIME_ONLY) reportCodex(wireCodex());
225
+ process.exit(0);
226
+ }
239
227
  fs.mkdirSync(BRAIN_DIR, { recursive: true });
240
228
  // 1) runtime dependency CLOSURE (jszip+fractional-indexing for the hook/engine,
241
229
  // @modelcontextprotocol/sdk+zod for the local MCP server). Resolve each via
@@ -280,7 +268,7 @@ try {
280
268
  if (!exists(path.join(destMods, name))) { copyDir(dir, path.join(destMods, name)); deps++; }
281
269
  try { const pj = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')); for (const d of Object.keys(pj?.dependencies || {})) queue.push({ name: d, fromDir: dir }); } catch { /* no readable package.json */ }
282
270
  }
283
- if (missing.length) { if (gotLock) releaseLock(); console.error(`✗ could not resolve required dep(s): ${missing.join(', ')} — aborting (the brain hook needs them).`); process.exit(1); }
271
+ if (missing.length) { releaseInstallLockSync(installLock); console.error(`✗ could not resolve required dep(s): ${missing.join(', ')} — aborting (the brain hook needs them).`); process.exit(1); }
284
272
 
285
273
  // 2) STAGE the scripts (engine + hook + flattened servers), write each to
286
274
  // `<name>.klypix-new`, back up the current copy to .prev/, then atomically
@@ -337,7 +325,7 @@ try {
337
325
  rawSettings = fs.readFileSync(SETTINGS, 'utf8');
338
326
  if (rawSettings.trim()) {
339
327
  try { settings = JSON.parse(rawSettings); }
340
- catch (e) { if (gotLock) releaseLock(); console.error(`✗ ${SETTINGS} is invalid JSON (${e.message}). Fix it and re-run — refusing to overwrite a broken config.`); process.exit(1); }
328
+ catch (e) { releaseInstallLockSync(installLock); console.error(`✗ ${SETTINGS} is invalid JSON (${e.message}). Fix it and re-run — refusing to overwrite a broken config.`); process.exit(1); }
341
329
  }
342
330
  }
343
331
  if (!RUNTIME_ONLY) {
@@ -351,12 +339,11 @@ try {
351
339
  fs.renameSync(tmp, SETTINGS);
352
340
  }
353
341
 
354
- // 6) stamp the install (unified brain version → never-downgrade across channels)
342
+ // 6) Commit the runtime pointer and version receipt atomically while the
343
+ // shared cross-channel lock is still held. The runtime receipt goes first;
344
+ // a crash between receipts remains recoverable because the next installer
345
+ // compares both and keeps the highest valid Brain Core version.
355
346
  const installedAt = new Date().toISOString();
356
- fs.writeFileSync(path.join(BRAIN_DIR, '.brain-version.json'), JSON.stringify({ brainVersion: VERSION, via: 'npm', dirty: false, installedAt }, null, 2));
357
- // Commit the runtime pointer LAST. A running supervisor watches only this
358
- // atomic file, validates every staged hash, and keeps the old worker if the
359
- // candidate is incomplete or incompatible.
360
347
  const runtime = {
361
348
  protocol: 1,
362
349
  version: VERSION,
@@ -368,6 +355,9 @@ try {
368
355
  const runtimePath = path.join(BRAIN_DIR, '.mcp-runtime.json');
369
356
  fs.writeFileSync(runtimePath + '.klypix-new', JSON.stringify(runtime, null, 2) + '\n', 'utf8');
370
357
  fs.renameSync(runtimePath + '.klypix-new', runtimePath);
358
+ const versionPath = path.join(BRAIN_DIR, '.brain-version.json');
359
+ fs.writeFileSync(versionPath + '.klypix-new', JSON.stringify({ brainVersion: VERSION, via: 'npm', dirty: false, installedAt }, null, 2), 'utf8');
360
+ fs.renameSync(versionPath + '.klypix-new', versionPath);
371
361
 
372
362
  // 7) migrate THIS project's .mcp.json off npx onto the now-installed local bundle
373
363
  // (heals an existing stale config so the next MCP server spawn runs current).
@@ -383,7 +373,7 @@ try {
383
373
  const wiredFor = (evt) => Array.isArray(verify?.hooks?.[evt]) && verify.hooks[evt].some(g => Array.isArray(g?.hooks) && g.hooks.some(h => typeof h?.command === 'string' && h.command.includes(HOOK_MARK)));
384
374
  const notWired = ['SessionStart', 'UserPromptSubmit', 'Stop', 'PostToolUse'].filter(e => !wiredFor(e));
385
375
 
386
- if (gotLock) releaseLock();
376
+ releaseInstallLockSync(installLock);
387
377
  // Users who already enabled the optional local semantic runtime should not
388
378
  // pay a multi-minute first question after a model/cache contract upgrade.
389
379
  // Migrate registered brains once in a detached process after the atomic
@@ -419,7 +409,7 @@ try {
419
409
  console.log(' Compatible brain-core updates hot-swap behind the same MCP connection. Only the one-time legacy→supervisor migration, a supervisor change, or an intentionally breaking tool/protocol change needs reconnect.');
420
410
  console.log(' Verify anytime: `npx klypix-mcp doctor`; prove two-client behavior with `npx klypix-mcp conformance`.');
421
411
  } catch (e) {
422
- if (gotLock) releaseLock();
412
+ releaseInstallLockSync(installLock);
423
413
  console.error(`✗ install failed: ${e?.message || e}`);
424
414
  process.exit(1);
425
415
  }