klypix-mcp 1.90.0 → 1.91.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 +31 -5
- package/bin/klypix-install.mjs +57 -3
- package/bin/klypix-mcp.mjs +3 -1
- package/bin/klypix-sessions.mjs +109 -0
- package/bin/klypix-worker.mjs +155 -2
- package/package.json +2 -2
- package/src/agent-presence.mjs +429 -2
- package/src/brain-doctor.mjs +49 -13
- package/src/finding-routing.mjs +6 -1
- package/src/global-brain-hook.mjs +11 -3
- package/src/klypix-core.mjs +6 -1
- package/src/mcp-presence.mjs +1 -1
- package/src/mcp-supervisor.mjs +132 -29
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ npx klypix-mcp conformance
|
|
|
119
119
|
|
|
120
120
|
It runs in a temporary fixture and touches nothing else. It checks tool discovery, task memory,
|
|
121
121
|
truthful peer reporting, overlap surfacing, proactive logging, and in-band delivery of a peer note.
|
|
122
|
-
It verifies 15 required coordination behaviours — not the
|
|
122
|
+
It verifies 15 required coordination behaviours — not the 23 tools, and not the retrieval engine.
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
@@ -142,7 +142,7 @@ git repo, and then **proves the result** before it exits:
|
|
|
142
142
|
editors Claude Code · Cursor · Codex · Gemini CLI · Antigravity · VS Code
|
|
143
143
|
wired 9 file(s) · 9 updated (skipped 5 for tools you don't have)
|
|
144
144
|
git .klypix merge driver registered
|
|
145
|
-
verified ✓
|
|
145
|
+
verified ✓ 23 tools reachable via .mcp.json (892ms)
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
That last line is the point. MCP config fails **silently** — a wrong entry means the server never
|
|
@@ -451,8 +451,9 @@ KLYPIX has identified before that is **not on the lane now** (closed, or quiet)
|
|
|
451
451
|
directed note the moment it next acts — a directed note is kept 7 days, and the sender is told it
|
|
452
452
|
is *queued*, not delivered. That is what stops the human from being the courier between two
|
|
453
453
|
agents: the standing rule every host receives is that a message it would otherwise ask the person
|
|
454
|
-
to relay or paste is sent this way instead. Nothing here starts or wakes a session — the
|
|
455
|
-
waits until a person opens it
|
|
454
|
+
to relay or paste is sent this way instead. Nothing here starts or wakes a session on its own — the
|
|
455
|
+
note waits until a person opens it, and the person decides when that is (see *Reopen on your OK*
|
|
456
|
+
below). A supported
|
|
456
457
|
KLYPIX action offers the note in model-visible context; the next independent supported action
|
|
457
458
|
replays it and records an acknowledgement. That acknowledgement proves only that a later action followed the
|
|
458
459
|
offer — never that a person read it or that an agent acted on it. The note keeps replaying until the
|
|
@@ -463,6 +464,30 @@ instead of silently looking delivered. The send-time audience is fixed, unresolv
|
|
|
463
464
|
fail closed, the core lane is machine-local, a note to every session expires after 24 hours and a
|
|
464
465
|
directed note after 7 days, and notes are never written into the brain.
|
|
465
466
|
|
|
467
|
+
### Reopen on your OK
|
|
468
|
+
|
|
469
|
+
A note to a session that has closed would otherwise wait until someone happens to open that
|
|
470
|
+
session again. When the person wants it handled sooner, the sending agent calls `brain_reopen`, and
|
|
471
|
+
**KLYPIX asks the person** — an in-chat *Reopen / Not now* prompt in apps that support MCP
|
|
472
|
+
elicitation (Claude Code, Codex), otherwise a small native dialog. Only on *Reopen* does KLYPIX
|
|
473
|
+
open that Claude Code or Codex session in a **new, visible terminal**, in the folder it worked in,
|
|
474
|
+
with the host's own resume command (`claude --resume <id>`, `codex resume <id>`); the session
|
|
475
|
+
receives the waiting note at its first action and the sender sees the receipt as usual.
|
|
476
|
+
|
|
477
|
+
- The person decides, never a model: the answer comes from the app's prompt or KLYPIX's own
|
|
478
|
+
dialog, which no agent can click. With neither available, nothing opens and the person is given
|
|
479
|
+
the command to run.
|
|
480
|
+
- Only a session with a note **addressed to it** is reopened — KLYPIX reopens a conversation so a
|
|
481
|
+
note can reach it; it does not start, route or supervise agents.
|
|
482
|
+
- The first prompt the reopened session gets is fixed text that tells it to collect the note; the
|
|
483
|
+
note itself arrives through the labelled message channel as information from another session,
|
|
484
|
+
never typed in as if the person had said it. The sender's session identity never leaks into it.
|
|
485
|
+
- *Not now* is remembered: the same session is not offered again for a while, and a double click
|
|
486
|
+
cannot open two windows.
|
|
487
|
+
- From a terminal: `npx klypix-mcp sessions` lists this project's closed sessions and the notes
|
|
488
|
+
waiting for them; `npx klypix-mcp sessions reopen <id>` reopens one (`--quiet` lets it wait for
|
|
489
|
+
you instead of starting on the note).
|
|
490
|
+
|
|
466
491
|
Durable handoffs go in the brain itself — decisions, findings, open questions and skills captured
|
|
467
492
|
as cards, each stamped with the agent that wrote it.
|
|
468
493
|
|
|
@@ -637,7 +662,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
637
662
|
|
|
638
663
|
---
|
|
639
664
|
|
|
640
|
-
## The
|
|
665
|
+
## The 23 verbs
|
|
641
666
|
|
|
642
667
|
| Tool | What it does |
|
|
643
668
|
|---|---|
|
|
@@ -651,6 +676,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
651
676
|
| `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, tool count, projection drift |
|
|
652
677
|
| `brain_message` | Session-to-session coordination notes — to a live session, or queued for one that is not running until it next starts — with a fixed send-time audience and per-recipient pending / offer / acknowledgement / consumption / failure receipts (a directed note is kept 7 days, a broadcast 24h; never written into the brain) |
|
|
653
678
|
| `brain_message_receipt` | Explicitly record model-side consumption using the exact message id and per-recipient offer token; acknowledgement alone never consumes a note |
|
|
679
|
+
| `brain_reopen` | Reopen a closed Claude Code or Codex session so a note waiting for it gets there now — only after the person answers *Reopen* in an in-chat prompt or KLYPIX's own dialog; opens a new, visible terminal in that session's folder with the host's resume command |
|
|
654
680
|
| `brain_sync` | Context Gateway: task capsule, active-task peers, exact-file overlap, one-time alerts, timing, and optional result-manifest reconciliation |
|
|
655
681
|
| `brain_connect` | Find and draw related-but-unlinked cards |
|
|
656
682
|
| `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 |
|
package/bin/klypix-install.mjs
CHANGED
|
@@ -212,6 +212,33 @@ function renameSyncWithBackoff(from, to) {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// The live install as its own `.mcp-runtime.json` describes it, every listed
|
|
216
|
+
// file read and hashed now: { raw, entries: [[name, bytes]] }; { absent: true }
|
|
217
|
+
// with no manifest (an install from before the supervisor); { error } when a
|
|
218
|
+
// listed file is missing or differs — a half-applied install, two versions'
|
|
219
|
+
// files side by side. Only the flat layout (bare file names) is accepted.
|
|
220
|
+
function readVerifiedLiveRuntime(dir) {
|
|
221
|
+
const manifestPath = path.join(dir, '.mcp-runtime.json');
|
|
222
|
+
let raw;
|
|
223
|
+
try { raw = fs.readFileSync(manifestPath, 'utf8'); }
|
|
224
|
+
catch (e) { return e?.code === 'ENOENT' ? { absent: true } : { error: `manifest unreadable (${e?.code || e?.message})` }; }
|
|
225
|
+
let manifest;
|
|
226
|
+
try { manifest = JSON.parse(raw); } catch { return { error: 'manifest is not valid JSON' }; }
|
|
227
|
+
const files = manifest && typeof manifest.files === 'object' && !Array.isArray(manifest.files) ? manifest.files : null;
|
|
228
|
+
if (manifest?.protocol !== 1 || !files || !Object.prototype.hasOwnProperty.call(files, String(manifest.worker || ''))) {
|
|
229
|
+
return { error: 'manifest does not hash its worker' };
|
|
230
|
+
}
|
|
231
|
+
const entries = [];
|
|
232
|
+
for (const [name, expected] of Object.entries(files)) {
|
|
233
|
+
if (path.basename(name) !== name) return { error: `unexpected path ${name}` };
|
|
234
|
+
let bytes;
|
|
235
|
+
try { bytes = fs.readFileSync(path.join(dir, name)); } catch { return { error: `${name} is missing` }; }
|
|
236
|
+
if (crypto.createHash('sha256').update(bytes).digest('hex') !== expected) return { error: `${name} differs from its hash` };
|
|
237
|
+
entries.push([name, bytes]);
|
|
238
|
+
}
|
|
239
|
+
return { raw, entries };
|
|
240
|
+
}
|
|
241
|
+
|
|
215
242
|
function migrateProjectMcpConfig() {
|
|
216
243
|
try {
|
|
217
244
|
const file = path.join(process.cwd(), '.mcp.json');
|
|
@@ -416,10 +443,37 @@ try {
|
|
|
416
443
|
const s = path.join(BIN, src); if (exists(s)) staged.push({ dst, content: flatten(fs.readFileSync(s, 'utf8')) });
|
|
417
444
|
}
|
|
418
445
|
for (const st of staged) fs.writeFileSync(path.join(BRAIN_DIR, st.dst + '.klypix-new'), st.content);
|
|
446
|
+
// .prev is what a supervisor boots while an install is half-applied (a
|
|
447
|
+
// fresh connection, a wake, a crash recovery), so it must be ONE version,
|
|
448
|
+
// whole (K1-PREV-UNVERIFIED, 2026-10-03 review). It used to be refreshed from
|
|
449
|
+
// whatever the live directory held: a retry after an install that stopped
|
|
450
|
+
// mid-rename (the updater's own, 15 min later) copied the MIXED set over the
|
|
451
|
+
// last complete snapshot. Now the live files are snapshotted only when they
|
|
452
|
+
// verify against their own manifest — the very bytes that were hashed are
|
|
453
|
+
// written — and that manifest is copied in LAST: it is what the supervisor
|
|
454
|
+
// verifies before it boots .prev (prevSnapshotAt). A live directory that
|
|
455
|
+
// fails its manifest leaves the last complete snapshot where it is.
|
|
419
456
|
try {
|
|
420
|
-
const prevDir = path.join(BRAIN_DIR, '.prev');
|
|
421
|
-
|
|
422
|
-
|
|
457
|
+
const prevDir = path.join(BRAIN_DIR, '.prev');
|
|
458
|
+
const prevManifest = path.join(prevDir, '.mcp-runtime.json');
|
|
459
|
+
const live = readVerifiedLiveRuntime(BRAIN_DIR);
|
|
460
|
+
if (live.entries) {
|
|
461
|
+
fs.mkdirSync(prevDir, { recursive: true });
|
|
462
|
+
fs.rmSync(prevManifest, { force: true }); // nothing vouches for .prev while it changes
|
|
463
|
+
for (const [name, bytes] of live.entries) fs.writeFileSync(path.join(prevDir, name), bytes);
|
|
464
|
+
fs.writeFileSync(prevManifest + '.klypix-new', live.raw);
|
|
465
|
+
renameSyncWithBackoff(prevManifest + '.klypix-new', prevManifest);
|
|
466
|
+
} else if (live.absent) {
|
|
467
|
+
// No manifest to verify against (an install from before the
|
|
468
|
+
// supervisor): a plain copy for a manual rollback, which no
|
|
469
|
+
// supervisor boots — nothing vouches for it.
|
|
470
|
+
fs.mkdirSync(prevDir, { recursive: true });
|
|
471
|
+
fs.rmSync(prevManifest, { force: true });
|
|
472
|
+
for (const st of staged) { const file = path.join(BRAIN_DIR, st.dst); if (exists(file)) fs.copyFileSync(file, path.join(prevDir, st.dst)); }
|
|
473
|
+
} else {
|
|
474
|
+
console.log(`• .prev kept: the live core files do not match their manifest (${live.error}) — the last complete snapshot stays the rollback copy`);
|
|
475
|
+
}
|
|
476
|
+
} catch { /* .prev rollback snapshot is best-effort; an unfinished one has no manifest */ }
|
|
423
477
|
const renameOrder = staged.slice().sort((a, b) => (a.dst === 'global-brain-hook.mjs' ? 1 : 0) - (b.dst === 'global-brain-hook.mjs' ? 1 : 0));
|
|
424
478
|
let n = 0;
|
|
425
479
|
for (const st of renameOrder) { renameSyncWithBackoff(path.join(BRAIN_DIR, st.dst + '.klypix-new'), path.join(BRAIN_DIR, st.dst)); n++; }
|
package/bin/klypix-mcp.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const PKG_VERSION = (() => {
|
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
21
|
|
|
22
|
-
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench']);
|
|
22
|
+
const DIRECT = new Set(['install', 'link', 'doctor', 'runtime', 'conformance', 'garden-code', 'init', 'git-driver', 'git-hook', 'brain-history', 'brain-deleted', 'orphans', 'diff', 'pr-brief', 'uninstall', 'bench', 'sessions']);
|
|
23
23
|
|
|
24
24
|
const USAGE = [
|
|
25
25
|
`klypix-mcp ${PKG_VERSION} — shared project brain + MCP coordination server.`,
|
|
@@ -29,6 +29,8 @@ const USAGE = [
|
|
|
29
29
|
' [--no-project] [--json] brain, config for the editors you actually have, merge driver, verified',
|
|
30
30
|
' link [dir] [--check] project this project\'s 14 managed agent config files (--check audits, writes nothing, exits 1 on drift)',
|
|
31
31
|
' doctor [--npm] [--all] [--json] read-only self-check; exits 1 on drift',
|
|
32
|
+
' sessions [--json] closed agent sessions of this project and the notes waiting for them',
|
|
33
|
+
' sessions reopen <id> [--quiet] reopen one (Claude Code / Codex) in a new terminal so its waiting notes reach it',
|
|
32
34
|
' runtime [--json] [--watch seconds] passive MCP process/RAM attribution; never terminates a process',
|
|
33
35
|
' conformance [--json] launch two real MCP clients against this build',
|
|
34
36
|
' bench [--quick] [--json] [--out F] reproducible benchmark: concurrent-write safety, latency, soak, crash',
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// klypix-sessions — `npx klypix-mcp sessions`. The human side of "reopen on your
|
|
3
|
+
// OK": the recent agent sessions of THIS project that are not running, which of
|
|
4
|
+
// them have notes waiting, and a one-word way to bring one back so its notes
|
|
5
|
+
// reach it.
|
|
6
|
+
//
|
|
7
|
+
// npx klypix-mcp sessions # list (this project)
|
|
8
|
+
// npx klypix-mcp sessions --json # the same, machine-readable
|
|
9
|
+
// npx klypix-mcp sessions reopen <id> # reopen it in a new terminal; it starts on the note
|
|
10
|
+
// npx klypix-mcp sessions reopen <id> --quiet # reopen it, but let it wait for you
|
|
11
|
+
// npx klypix-mcp sessions ... --project <dir> | --dry-run
|
|
12
|
+
//
|
|
13
|
+
// Typing `reopen` IS the human's yes, so there is no second prompt here. Only a
|
|
14
|
+
// Claude Code or Codex session that has a note waiting can be reopened — KLYPIX
|
|
15
|
+
// reopens a conversation so a note can reach it; it does not start agents.
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import {
|
|
19
|
+
buildReopenLaunch,
|
|
20
|
+
findProjectBrain,
|
|
21
|
+
launchReopen,
|
|
22
|
+
listKnownSessions,
|
|
23
|
+
recordSessionReopen,
|
|
24
|
+
reopenCandidate,
|
|
25
|
+
REOPEN_NUDGE,
|
|
26
|
+
} from '../src/agent-presence.mjs';
|
|
27
|
+
|
|
28
|
+
const isDir = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };
|
|
29
|
+
const raw = process.argv.slice(2);
|
|
30
|
+
const argv = (raw[0] === 'sessions' && !isDir(path.resolve(raw[0]))) ? raw.slice(1) : raw;
|
|
31
|
+
const has = (flag) => argv.includes(flag);
|
|
32
|
+
const val = (flag) => { const i = argv.indexOf(flag); return i >= 0 ? argv[i + 1] : undefined; };
|
|
33
|
+
const projectDir = path.resolve(val('--project') || process.cwd());
|
|
34
|
+
const brainPath = findProjectBrain(projectDir);
|
|
35
|
+
const word = (client) => {
|
|
36
|
+
const key = String(client || '').toLowerCase();
|
|
37
|
+
if (key === 'claude-code' || key === 'claude') return 'Claude Code';
|
|
38
|
+
if (key === 'codex') return 'Codex';
|
|
39
|
+
return key ? key.replace(/(^|[-_ ])([a-z])/g, (_m, p, c) => `${p}${c.toUpperCase()}`) : 'Unknown';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!brainPath) {
|
|
43
|
+
console.error(`No project brain (brain.klypix) at or above ${projectDir} — run this in the project folder, or pass --project <dir>.`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (argv[0] === 'reopen') {
|
|
48
|
+
const target = argv[1] && !argv[1].startsWith('--') ? argv[1] : '';
|
|
49
|
+
if (!target) {
|
|
50
|
+
console.error('Usage: npx klypix-mcp sessions reopen <session id or 8+ character prefix> [--quiet] [--dry-run]');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
const candidate = reopenCandidate({ brainPath, target, humanInitiated: true });
|
|
54
|
+
if (!candidate.ok) {
|
|
55
|
+
const who = candidate.entry ? `${word(candidate.entry.client)} ${String(candidate.entry.id).slice(0, 8)}` : `"${target}"`;
|
|
56
|
+
const why = {
|
|
57
|
+
'unknown-session': `no session KLYPIX remembers in this project matches ${who}`,
|
|
58
|
+
'ambiguous-session': `${who} matches more than one session — use more characters of its id`,
|
|
59
|
+
live: `${who} is running${candidate.statusLabel ? ` (${candidate.statusLabel})` : ''}; it gets its notes at its next action`,
|
|
60
|
+
'unsupported-client': `${who} cannot be reopened by KLYPIX (only Claude Code and Codex have a verified resume-by-id)`,
|
|
61
|
+
'unsafe-id': `${who} has an id that cannot be passed to a terminal safely`,
|
|
62
|
+
'no-waiting-note': `no note is waiting for ${who} — KLYPIX reopens a session only so a note can reach it`,
|
|
63
|
+
'just-reopened': `${who} was reopened moments ago — look for its terminal window`,
|
|
64
|
+
}[candidate.reason] || `${who} cannot be reopened (${candidate.reason})`;
|
|
65
|
+
console.error(`Not reopened: ${why}.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const plan = buildReopenLaunch({ hostKey: candidate.hostKey, sessionId: candidate.entry.id, cwd: candidate.cwd, prompt: has('--quiet') ? '' : REOPEN_NUDGE });
|
|
69
|
+
const launch = launchReopen(plan, { env: has('--dry-run') ? { ...process.env, KLYPIX_REOPEN_LAUNCH: 'dry-run' } : process.env });
|
|
70
|
+
const label = `${candidate.hostLabel} ${candidate.entry.id.slice(0, 8)}`;
|
|
71
|
+
if (launch.dryRun) {
|
|
72
|
+
console.log(`Dry run: would reopen ${label} via ${plan.method} in ${candidate.cwd} with ${candidate.command}${has('--quiet') ? '' : ' (and let it act on the note)'}.`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
if (!launch.launched) {
|
|
76
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'manual', via: 'cli', method: plan.method || null });
|
|
77
|
+
console.error(`Could not open a terminal here (${plan.reason || launch.reason || 'unknown'}). Run it yourself:\n cd ${JSON.stringify(candidate.cwd)}\n ${candidate.command}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'reopened', via: 'cli', method: plan.method });
|
|
81
|
+
const notes = candidate.waitingNotes.length;
|
|
82
|
+
console.log(`🔓 Reopened ${label} in a new terminal (${candidate.cwd}) with ${candidate.command}${has('--quiet') ? ' — it waits for you' : ' — it starts on the note'}. ${notes === 1 ? 'Its waiting note is' : `Its ${notes} waiting notes are`} delivered at its first action.`);
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const rows = listKnownSessions({ brainPath, now }).filter((row) => !row.live);
|
|
88
|
+
if (has('--json')) {
|
|
89
|
+
console.log(JSON.stringify({ project: path.dirname(brainPath), brain: brainPath, sessions: rows.map((row) => ({
|
|
90
|
+
id: row.id, client: row.client, intent: row.intent, status: row.status, lastSeen: row.lastSeen,
|
|
91
|
+
endedAt: row.endedAt || null, waitingNotes: row.waitingDirectedNotes, waitingBroadcasts: row.waitingNotes - row.waitingDirectedNotes,
|
|
92
|
+
cwd: row.cwd || null, reopenable: Boolean(row.resumeCommand) && row.waitingDirectedNotes > 0, resumeCommand: row.resumeCommand || null,
|
|
93
|
+
})) }, null, 2));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
if (!rows.length) {
|
|
97
|
+
console.log('No recent sessions of this project are closed. (Live sessions: npx klypix-mcp doctor.)');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
const waiting = rows.filter((row) => row.waitingDirectedNotes > 0);
|
|
101
|
+
console.log(`Recent sessions of ${path.basename(path.dirname(brainPath))} that are not running (${rows.length}${waiting.length ? `, ${waiting.length} with notes waiting` : ''}):`);
|
|
102
|
+
for (const row of rows.slice(0, 20)) {
|
|
103
|
+
const n = row.waitingDirectedNotes;
|
|
104
|
+
const note = n ? ` · 📬 ${n} note${n === 1 ? '' : 's'} waiting` : '';
|
|
105
|
+
console.log(` ${row.id.slice(0, 8)} ${word(row.client).padEnd(11)} ${String(row.status).padEnd(18)}${note}${row.intent ? ` “${row.intent.slice(0, 70)}”` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
const first = waiting.find((row) => row.resumeCommand);
|
|
108
|
+
if (first) console.log(`\nReopen one so its notes reach it: npx klypix-mcp sessions reopen ${first.id.slice(0, 8)}${waiting.length > 1 ? ' (any id above with notes waiting)' : ''}`);
|
|
109
|
+
process.exit(0);
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -33,7 +33,18 @@ import {
|
|
|
33
33
|
import { compareProjectGraphResults, projectGraphContextMarkdown, queryProjectGraph, suggestProjectGraphBrainLinks, scanNativeProjectMap, checkBrainDrift, brainDriftMarkdown } from '../src/project-graph.mjs';
|
|
34
34
|
import { auditProject, compactAgentsBrief, linkProject, mcpServerEntry } from '../src/agent-rules.mjs';
|
|
35
35
|
import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS, normalizeMcpClient } from '../src/mcp-presence.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
buildReopenLaunch,
|
|
38
|
+
consumeMessageReceipt,
|
|
39
|
+
findProjectBrain,
|
|
40
|
+
launchReopen,
|
|
41
|
+
listActiveSessions,
|
|
42
|
+
nativeReopenDialog,
|
|
43
|
+
recordSessionReopen,
|
|
44
|
+
reopenCandidate,
|
|
45
|
+
reopenQuestion,
|
|
46
|
+
REOPEN_NUDGE,
|
|
47
|
+
} from '../src/agent-presence.mjs';
|
|
37
48
|
import { collectRepoState, commitsInRange, makeContainmentProbe } from '../src/repo-state.mjs';
|
|
38
49
|
import {
|
|
39
50
|
reconcileRegisteredProjects,
|
|
@@ -114,6 +125,11 @@ await runVerb('link', './klypix-link.mjs');
|
|
|
114
125
|
// projection in sync? One verdict, one reconcile block. Exits 1 on drift (CI gate).
|
|
115
126
|
await runVerb('doctor', './klypix-doctor.mjs');
|
|
116
127
|
|
|
128
|
+
// `npx klypix-mcp sessions` — this project's closed agent sessions and the notes
|
|
129
|
+
// waiting for them; `sessions reopen <id>` reopens one in a new terminal so its
|
|
130
|
+
// notes reach it (typing the verb is the human's yes). Runs before server setup.
|
|
131
|
+
await runVerb('sessions', './klypix-sessions.mjs');
|
|
132
|
+
|
|
117
133
|
// `npx klypix-mcp conformance` — launch two real, isolated MCP clients against
|
|
118
134
|
// this exact installed server and verify task memory, truthful peers, blocking
|
|
119
135
|
// overlap detection, proactive logging, and guaranteed next-action delivery.
|
|
@@ -751,7 +767,7 @@ server.registerTool('brain_note', {
|
|
|
751
767
|
|
|
752
768
|
server.registerTool('brain_message', {
|
|
753
769
|
title: 'Message another agent session on this project — live, idle, or recently closed (one-time note, not a brain card)',
|
|
754
|
-
description: 'Leave a DELIBERATE, targeted note for another agent session working on this project ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Address it to a session id from the brain_sync peer list or brain_doctor. A LIVE session gets it on its next lifecycle event or KLYPIX tool call, even if it is idle right now. A session KLYPIX has identified before that is NOT on the lane now (closed, or quiet) still receives a directed note the moment it next acts — the note is kept for up to 7 days — so if you would otherwise ask the human to relay or paste something to another agent session, send it here instead and tell them it was sent or queued. Broadcasts ("all") reach only sessions live right now. Any MCP client can send and receive through the shared machine-local presence lane. Delivery contract: a supported action offers the note into model-visible context, a later independent action acknowledges it, and it retires either by an explicit brain_message_receipt ("acted on it") or by auto-consumption on a further independent action; your receipt line names which, and neither is proof a human read it. Expiry or capacity loss leaves a failed per-recipient receipt instead of silently disappearing. OS-user-local and machine-local. Ephemeral and NOT persisted to the brain — for a durable decision use brain_note instead.',
|
|
770
|
+
description: 'Leave a DELIBERATE, targeted note for another agent session working on this project ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Address it to a session id from the brain_sync peer list or brain_doctor. A LIVE session gets it on its next lifecycle event or KLYPIX tool call, even if it is idle right now. A session KLYPIX has identified before that is NOT on the lane now (closed, or quiet) still receives a directed note the moment it next acts — the note is kept for up to 7 days — so if you would otherwise ask the human to relay or paste something to another agent session, send it here instead and tell them it was sent or queued. If the human wants a queued note handled sooner, brain_reopen asks them whether to reopen that session now. Broadcasts ("all") reach only sessions live right now. Any MCP client can send and receive through the shared machine-local presence lane. Delivery contract: a supported action offers the note into model-visible context, a later independent action acknowledges it, and it retires either by an explicit brain_message_receipt ("acted on it") or by auto-consumption on a further independent action; your receipt line names which, and neither is proof a human read it. Expiry or capacity loss leaves a failed per-recipient receipt instead of silently disappearing. OS-user-local and machine-local. Ephemeral and NOT persisted to the brain — for a durable decision use brain_note instead.',
|
|
755
771
|
inputSchema: {
|
|
756
772
|
text: z.string().describe('The note to deliver (kept to 400 chars).'),
|
|
757
773
|
to: z.string().optional().describe('Target — a session id (or unique prefix of at least 8 characters) from the peer list, or an exact unique branch; omit or "all" for every session live right now. A directed id may name a session that is not running: the note then waits and is delivered when that session next starts.'),
|
|
@@ -827,6 +843,143 @@ server.registerTool('brain_message_receipt', {
|
|
|
827
843
|
};
|
|
828
844
|
});
|
|
829
845
|
|
|
846
|
+
// ── brain_reopen (1.91.0): reopen a closed session so a waiting note reaches it
|
|
847
|
+
// The agent proposes; the HUMAN decides. Consent is collected by KLYPIX itself,
|
|
848
|
+
// never by the model: an in-chat prompt when the client supports MCP
|
|
849
|
+
// elicitation, otherwise a native "Reopen / Not now" dialog raised by this
|
|
850
|
+
// process, otherwise nothing opens and the human is given the command. The
|
|
851
|
+
// session opens in a new, visible terminal with its host's own resume command.
|
|
852
|
+
const REOPEN_ELICIT_TIMEOUT_MS = 110_000;
|
|
853
|
+
const REOPEN_CHOICE_ACT = 'Reopen it and let it act on the note';
|
|
854
|
+
const REOPEN_CHOICE_QUIET = 'Reopen it — I will prompt it myself';
|
|
855
|
+
const REOPEN_CHOICE_WAIT = 'Not now — let the note wait';
|
|
856
|
+
|
|
857
|
+
async function askHumanToReopen({ question, start, extra }) {
|
|
858
|
+
let caps = {};
|
|
859
|
+
try { caps = server.server.getClientCapabilities?.() || {}; } catch { /* older SDK */ }
|
|
860
|
+
if (caps.elicitation) {
|
|
861
|
+
const choices = start === false ? [REOPEN_CHOICE_QUIET, REOPEN_CHOICE_WAIT] : [REOPEN_CHOICE_ACT, REOPEN_CHOICE_QUIET, REOPEN_CHOICE_WAIT];
|
|
862
|
+
try {
|
|
863
|
+
const answer = await server.server.elicitInput({
|
|
864
|
+
mode: 'form',
|
|
865
|
+
message: question,
|
|
866
|
+
requestedSchema: {
|
|
867
|
+
type: 'object',
|
|
868
|
+
properties: { choice: { type: 'string', title: 'Reopen this session now?', enum: choices, default: choices[0] } },
|
|
869
|
+
required: ['choice'],
|
|
870
|
+
},
|
|
871
|
+
}, { timeout: REOPEN_ELICIT_TIMEOUT_MS, ...(extra?.requestId !== undefined ? { relatedRequestId: extra.requestId } : {}) });
|
|
872
|
+
if (answer?.action === 'accept') {
|
|
873
|
+
const choice = String(answer.content?.choice || choices[0]);
|
|
874
|
+
if (choice === REOPEN_CHOICE_WAIT) return { via: 'in-chat', answer: 'wait' };
|
|
875
|
+
return { via: 'in-chat', answer: 'reopen', start: choice === REOPEN_CHOICE_ACT };
|
|
876
|
+
}
|
|
877
|
+
if (answer?.action === 'decline') return { via: 'in-chat', answer: 'wait' };
|
|
878
|
+
return { via: 'in-chat', answer: 'no-answer' };
|
|
879
|
+
} catch (error) {
|
|
880
|
+
// An unanswered in-chat prompt is an answer ("not now, not here"): never
|
|
881
|
+
// follow it with a second prompt in another window.
|
|
882
|
+
if (/time(d)? ?out/i.test(String(error?.message || ''))) return { via: 'in-chat', answer: 'no-answer' };
|
|
883
|
+
log(`brain_reopen: in-chat prompt unavailable (${String(error?.message || error).slice(0, 120)}); using the native dialog`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const native = await nativeReopenDialog({
|
|
887
|
+
message: `${question}\n\nYes = reopen it${start === false ? '' : ' and let it act on the note'}. No = not now.`,
|
|
888
|
+
timeoutS: 50,
|
|
889
|
+
});
|
|
890
|
+
if (native === 'reopen') return { via: 'dialog', answer: 'reopen', start: start !== false };
|
|
891
|
+
if (native === 'wait') return { via: 'dialog', answer: 'wait' };
|
|
892
|
+
if (native === 'timeout') return { via: 'dialog', answer: 'no-answer' };
|
|
893
|
+
return { via: null, answer: 'unavailable' };
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const reopenClientWord = (client) => {
|
|
897
|
+
const key = String(client || '').toLowerCase();
|
|
898
|
+
if (key === 'claude-code' || key === 'claude') return 'Claude Code';
|
|
899
|
+
if (key === 'codex') return 'Codex';
|
|
900
|
+
return key ? key.replace(/(^|[-_ ])([a-z])/g, (_m, p, c) => `${p}${c.toUpperCase()}`) : 'Another';
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
server.registerTool('brain_reopen', {
|
|
904
|
+
title: 'Reopen a closed agent session so a waiting note reaches it — only after the human says yes',
|
|
905
|
+
description: 'A note to a session that has CLOSED waits up to 7 days and is delivered only when that session is next used. When the human wants it handled sooner, call this: KLYPIX asks the HUMAN (an in-chat Reopen / Not now prompt where the app supports it, otherwise a native dialog) and, only on yes, reopens that Claude Code or Codex session in a new, visible terminal in its own folder with the host\'s resume command; the reopened session receives the waiting note(s) at its first action. Nothing opens without the human\'s answer, nothing runs headless, and only a session with a note waiting can be reopened. Do not call it again for the same session after the human chose "Not now" unless they bring it up. Machine-local.',
|
|
906
|
+
annotations: {
|
|
907
|
+
destructiveHint: false,
|
|
908
|
+
idempotentHint: false,
|
|
909
|
+
openWorldHint: true,
|
|
910
|
+
},
|
|
911
|
+
inputSchema: {
|
|
912
|
+
session: z.string().min(1).max(160).describe('The session to reopen: its id, or a unique prefix of at least 8 characters, as shown in the queued-note reply, brain_sync peers, or brain_doctor. It must be a Claude Code or Codex session that is not running and has a note waiting.'),
|
|
913
|
+
start: z.boolean().optional().describe('true (default): the reopened session starts on the note right away. false: it opens and waits for the human to prompt it.'),
|
|
914
|
+
project: z.string().optional().describe('Absolute project root containing brain.klypix. Defaults to this connection\'s project.'),
|
|
915
|
+
},
|
|
916
|
+
}, async ({ session, start, project }, extra) => {
|
|
917
|
+
const brainPath = project ? findProjectBrain(path.resolve(project)) : (mcpPresence.brainPath || findProjectBrain(mcpPresence.vault));
|
|
918
|
+
const identity = extra?.klypixRequestIdentity || {};
|
|
919
|
+
const me = String(identity.sessionId || mcpPresence.id || '');
|
|
920
|
+
const senderLabel = `${reopenClientWord(identity.clientInfo?.client)} session ${me.slice(0, 8)}`.trim();
|
|
921
|
+
const now = Date.now();
|
|
922
|
+
const candidate = reopenCandidate({ brainPath, target: session, now, selfIds: [me, mcpPresence.id].filter(Boolean), explicit: true });
|
|
923
|
+
const reply = (status, text, structured = {}) => ({
|
|
924
|
+
content: [{ type: 'text', text }],
|
|
925
|
+
structuredContent: { schemaVersion: 1, status, session: candidate?.entry?.id || String(session || ''), ...structured },
|
|
926
|
+
...(status === 'failed' ? { isError: true } : {}),
|
|
927
|
+
});
|
|
928
|
+
if (!candidate.ok) {
|
|
929
|
+
const e = candidate.entry || {};
|
|
930
|
+
const who = e.id ? `${reopenClientWord(e.client)} ${String(e.id).slice(0, 8)}` : `"${String(session).slice(0, 40)}"`;
|
|
931
|
+
const ago = (at) => `${Math.max(1, Math.round((now - Number(at || now)) / 60_000))} minute(s) ago`;
|
|
932
|
+
const why = {
|
|
933
|
+
'no-brain': 'there is no project brain for this connection — call brain_sync with the project root first.',
|
|
934
|
+
'unknown-session': `no session KLYPIX remembers matches ${who}. Use the id from the queued-note reply, brain_sync peers, or brain_doctor.`,
|
|
935
|
+
'ambiguous-session': `${who} matches more than one session — use more characters of its id.`,
|
|
936
|
+
self: `${who} is this session.`,
|
|
937
|
+
live: `${who} is running${candidate.statusLabel ? ` (${candidate.statusLabel})` : ''} — it gets the note at its next action, so there is nothing to reopen.`,
|
|
938
|
+
'unsupported-client': `${who} cannot be reopened by KLYPIX — only Claude Code and Codex have a verified resume-by-id. The note stays queued until that session is next used.`,
|
|
939
|
+
'unsafe-id': `${who} has an id that cannot be passed to a terminal safely, so KLYPIX will not reopen it.`,
|
|
940
|
+
'no-waiting-note': `no note is waiting for ${who}. KLYPIX reopens a session only so a note can reach it — send it with brain_message first.`,
|
|
941
|
+
'just-reopened': `KLYPIX opened ${who} ${ago(candidate.reopenedAt)} — the terminal window should already be up.`,
|
|
942
|
+
'recently-declined': `the human chose "Not now" for ${who} ${ago(candidate.declinedAt)}. Ask them in chat before offering it again.`,
|
|
943
|
+
}[candidate.reason] || `KLYPIX could not reopen ${who} (${candidate.reason}).`;
|
|
944
|
+
return reply('refused', `Not reopened: ${why}`, { reason: candidate.reason });
|
|
945
|
+
}
|
|
946
|
+
const wantStart = start !== false;
|
|
947
|
+
const question = reopenQuestion(candidate, { senderLabel, start: wantStart, now });
|
|
948
|
+
const consent = await askHumanToReopen({ question, start: wantStart, extra });
|
|
949
|
+
const label = `${candidate.hostLabel} ${candidate.entry.id.slice(0, 8)}`;
|
|
950
|
+
const kept = candidate.expiresAt ? `kept ${Math.max(1, Math.round((candidate.expiresAt - Date.now()) / 86_400_000))} more day(s)` : 'kept up to 7 days';
|
|
951
|
+
const manualLine = `${candidate.cwdSource === 'session' ? 'In' : 'From the project folder'} ${candidate.cwd}, run: ${candidate.command}`;
|
|
952
|
+
if (consent.answer === 'unavailable') {
|
|
953
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'manual', by: me });
|
|
954
|
+
return reply('manual', `Not reopened: KLYPIX cannot show the human a Reopen button in this app or on this machine, so nothing was opened. Give them this to run themselves if they want it now — ${manualLine}. Otherwise the note stays queued (${kept}).`, { cwd: candidate.cwd, command: candidate.command });
|
|
955
|
+
}
|
|
956
|
+
if (consent.answer === 'wait') {
|
|
957
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'declined', via: consent.via, by: me });
|
|
958
|
+
return reply('declined', `Not reopened — the human chose "Not now". The note stays queued (${kept}) and is delivered when ${label} is next used. Do not offer this again unless the human brings it up.`, { via: consent.via });
|
|
959
|
+
}
|
|
960
|
+
if (consent.answer !== 'reopen') {
|
|
961
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'no-answer', via: consent.via, by: me });
|
|
962
|
+
return reply('no-answer', `Not reopened — the human did not answer the prompt, so nothing was opened. The note stays queued (${kept}). If they want it now: ${manualLine}.`, { via: consent.via, cwd: candidate.cwd, command: candidate.command });
|
|
963
|
+
}
|
|
964
|
+
const plan = buildReopenLaunch({ hostKey: candidate.hostKey, sessionId: candidate.entry.id, cwd: candidate.cwd, prompt: consent.start ? REOPEN_NUDGE : '' });
|
|
965
|
+
const launch = launchReopen(plan);
|
|
966
|
+
if (launch.dryRun) {
|
|
967
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'dry-run', via: consent.via, method: plan.method, by: me });
|
|
968
|
+
const would = plan.method === 'manual' ? 'would be handed to the human as a command (no terminal can be opened here)' : `would open via ${plan.method}`;
|
|
969
|
+
return reply('dry-run', `Dry run (KLYPIX_REOPEN_LAUNCH=dry-run): the human said yes, and ${label} ${would} in ${candidate.cwd} with ${candidate.command}. Nothing was started.`, { via: consent.via, method: plan.method, cwd: candidate.cwd, command: candidate.command, launch });
|
|
970
|
+
}
|
|
971
|
+
if (!launch.launched) {
|
|
972
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: plan.method === 'manual' ? 'manual' : 'failed', via: consent.via, method: plan.method || null, by: me });
|
|
973
|
+
const reason = plan.method === 'manual'
|
|
974
|
+
? (plan.reason === 'no-display' ? 'there is no display to open a terminal on' : 'no terminal app was found')
|
|
975
|
+
: String(launch.reason || plan.reason || 'the terminal did not start');
|
|
976
|
+
return reply(plan.method === 'manual' ? 'manual' : 'failed', `The human said yes, but KLYPIX could not open a terminal here (${reason}). Give them this to run — ${manualLine}. The note stays queued until then.`, { via: consent.via, cwd: candidate.cwd, command: candidate.command, reason });
|
|
977
|
+
}
|
|
978
|
+
recordSessionReopen({ brainPath, sessionId: candidate.entry.id, outcome: 'reopened', via: consent.via, method: plan.method, by: me });
|
|
979
|
+
const notes = candidate.waitingNotes.length;
|
|
980
|
+
return reply('reopened', `🔓 Reopened ${label} — the human said yes${consent.via === 'dialog' ? ' in the KLYPIX dialog' : ''}. It opened in a new ${plan.method === 'windows-terminal' ? 'Windows Terminal window' : plan.method === 'macos-terminal' ? 'Terminal window' : 'terminal window'} in ${candidate.cwd} with ${candidate.command}${consent.start ? ', and starts on the note right away' : ', waiting for the human to prompt it'}. ${notes === 1 ? 'The waiting note is' : `The ${notes} waiting notes are`} delivered at its first action; your next prompt shows the receipt. If the window shows an error (for example the command is not found), the note stays queued — ${manualLine}.`, { via: consent.via, method: plan.method, cwd: candidate.cwd, command: candidate.command, start: Boolean(consent.start), notes });
|
|
981
|
+
});
|
|
982
|
+
|
|
830
983
|
server.registerTool('brain_sync', {
|
|
831
984
|
title: 'KLYPIX Context Gateway — synchronize task, peers, conflicts, and relevant memory',
|
|
832
985
|
description: 'APPROVAL-FREE task gateway over the authorized MCP connection. Call FIRST with a concise intent and expected files, again when scope changes, and with phase:"complete" before the final response. One bounded response returns compact task-relevant brain context, active TASK peers (idle connections hidden), one-time messages, structured exact-file conflicts, and late-arrival overlap alerts. A completion that supplies machine-checkable result manifests is fail-closed: invalid, conflicting, or incomparable evidence returns needs-reconciliation and retains task scope. Works on any MCP host — it needs only the authorized MCP connection, so native lifecycle hooks are optional. LIMITS: conflict matching is EXACT-PATH and both sessions must have declared their files, coordination/result reconciliation is machine-local and OS-user-local (a teammate on another machine is invisible), and file overlap remains ADVISORY.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.91.0",
|
|
4
4
|
"mcpName": "io.github.dahshanlabs/klypix-mcp",
|
|
5
5
|
"description": "Active state management for multi-agent coding: a shared, versioned project brain over MCP.",
|
|
6
6
|
"type": "module",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"bench": "node bin/klypix-mcp.mjs bench",
|
|
85
85
|
"test:bench": "node test/bench.mjs",
|
|
86
86
|
"pretest": "node test/publish-workflow.mjs",
|
|
87
|
-
"test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/status-shape.mjs && node test/status-hook.mjs",
|
|
87
|
+
"test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/session-reopen.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/status-shape.mjs && node test/status-hook.mjs",
|
|
88
88
|
"test:memory": "node test/memory-runtime.mjs",
|
|
89
89
|
"test:memory:soak": "node --expose-gc test/memory-soak.mjs",
|
|
90
90
|
"runtime": "node bin/klypix-runtime.mjs",
|