klypix-mcp 1.33.0 → 1.37.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
@@ -10,17 +10,33 @@ You've seen the setup: point an AI at a folder of notes, watch the graph fill up
10
10
 
11
11
  ## Quick start
12
12
 
13
- **Claude Code + Codex (native, machine-wide):**
13
+ **Claude Code + Codex:**
14
14
 
15
15
  ```bash
16
16
  npx klypix-mcp install
17
17
  ```
18
18
 
19
- Claude Code gets live hooks for auto-brief + auto-capture. Codex gets a native
20
- `~/.codex/config.toml` MCP registration and conditional global guidance that activates
21
- only when a project contains `./brain.klypix`. Existing MCP servers, Codex settings, and
22
- personal instructions are preserved and backed up before KLYPIX-owned blocks change.
23
- Restart Codex after installation so it loads the new server.
19
+ Claude Code keeps its live hooks for auto-brief + auto-capture. Codex gets native MCP
20
+ tools, **automatic live-session presence**, and approval-free **smart task synchronization**
21
+ from the authorized MCP connection itself. `brain_sync` is a bounded **Context Gateway**:
22
+ one call returns task-relevant brain memory, meaningful active-task peers, structured
23
+ exact-file conflicts, one-time notes, and automatic late-arrival overlap alerts. Idle MCP
24
+ connections stay counted for diagnostics but are hidden from the task-peer list. MCP
25
+ server instructions plus a managed Codex `AGENTS.md` block teach Codex to call it at task
26
+ start, when scope changes, and on completion—no Codex hook approval required. Run inside a
27
+ project containing `brain.klypix`, or run `npx klypix-mcp link` in each brain project.
28
+ The installer removes the obsolete global KLYPIX `--vault "."` table that could resolve
29
+ outside the active project; every unrelated Codex setting and MCP server is preserved.
30
+
31
+ Optional native lifecycle capture for mechanical prompt/tool/file events:
32
+
33
+ ```bash
34
+ npx klypix-mcp install --codex-hooks
35
+ ```
36
+
37
+ Codex owns this native layer's security decision and asks the user to review/trust the
38
+ hooks on surfaces that expose hook management. `brain_doctor` reports it separately as
39
+ off, execution-unverified, or active; the approval-free smart layer remains active either way.
24
40
 
25
41
  Give a project a brain by dropping a `brain.klypix` in it — the
26
42
  [KLYPIX app](https://klypix.com) does it in one click (*Save canvas as project brain*),
@@ -40,7 +56,7 @@ managed files without changing them with `npx klypix-mcp link --check`.
40
56
 
41
57
  The difference from a folder of notes is not the shape — it's that this memory is a **mechanism, not a filing convention**:
42
58
 
43
- - **It briefs every session.** Each agent session starts already knowing the project's decisions, open questions, and standing rules — a compact brief (≈3–5k tokens even at 600+ cards, growing sublinearly). Measured on our own brain: **73% of past decisions recovered with one search round (55% brief-only) vs 0% cold** (n=20).
59
+ - **It briefs each task, not just each session.** `brain_sync` ranks a bounded memory capsule from the actual task intent and expected files. The full generated brief remains available for broad history/status work, while the always-loaded `AGENTS.md` fallback stays compact. Measured on our own brain: **73% of past decisions recovered with one search round (55% brief-only) vs 0% cold** (n=20).
44
60
  - **It argues back.** `brain_challenge`: propose a decision and the brain answers with receipts — *"you reversed this on June 12; here's the correction — captured by a different agent, coordinate before overriding."* Deterministic evidence only (correction-cues, opposite-polarity), never mere topical similarity. A memory that can't disagree with you is flattery.
45
61
  - **It knows when it's stale.** Decision cards can anchor to the exact code they were decided against (git blob OID). When that code moves on, the card raises its hand in the next brief. Their notes rot silently; ours confess.
46
62
  - **It answers from the past.** `brain_ask` with `as_of: 2026-03-01` answers what the project believed *then* — corrections from the future never leak backwards.
@@ -58,7 +74,8 @@ And it's not a cage: everything **exports to Markdown and JSON Canvas** in one c
58
74
 
59
75
  ## Setup for plain MCP clients
60
76
 
61
- Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `claude_desktop_config.json`:
77
+ Any MCP client gets the tools and connection-lifecycle presence without host hooks. For
78
+ **Claude Desktop**, in `claude_desktop_config.json`:
62
79
 
63
80
  ```json
64
81
  {
@@ -71,9 +88,14 @@ Any MCP client gets the tools without the hooks. For **Claude Desktop**, in `cla
71
88
  }
72
89
  ```
73
90
 
74
- Then ask your agent things like *"what did we decide about auth?"*, *"challenge this: let's switch to polling"*, or *"turn these notes into a board."*
91
+ If the vault contains `brain.klypix`, that MCP connection also appears as an active
92
+ session. `brain_sync` adds compact task memory, expected-file coordination, task-only peer
93
+ reporting, structured exact-overlap warnings, automatic conflict alerts, timing receipts,
94
+ and one-time peer-note delivery. Then ask your
95
+ agent things like *"what did we decide about auth?"*, *"challenge this: let's switch
96
+ to polling"*, or *"turn these notes into a board."*
75
97
 
76
- ## The 16 verbs
98
+ ## The 18 verbs
77
99
 
78
100
  | Tool | What it does |
79
101
  |---|---|
@@ -82,9 +104,11 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
82
104
  | `brain_note` | Capture with the full lifecycle — supersede / ✓ resolve / ~ update / 🛠 skill / closes: |
83
105
  | `brain_reconcile` | Find stale-vs-correction pairs + unrecorded migrations |
84
106
  | `brain_insights` | Hubs, orphaned decisions, stale questions, area sizes |
107
+ | `brain_lens` | Machine-readable freshness, provenance, activity, timeline, orrery, and unresolved views |
85
108
  | `brain_garden` | Maintenance pass over the brain |
86
- | `brain_doctor` | Self-diagnosis: version alignment, hooks wired, projection drift |
109
+ | `brain_doctor` | Self-diagnosis: version, core/enhanced host adapters, active sessions, projection drift |
87
110
  | `brain_message` | Session-to-session coordination notes |
111
+ | `brain_sync` | Context Gateway: compact task memory, active-task peers, structured conflicts, automatic alerts, and timing |
88
112
  | `brain_connect` | Find + draw related-but-unlinked cards |
89
113
  | `canvas_view` | The board as an MCP App — Apps-capable chats get an interactive spatial view; everyone else gets clean text |
90
114
  | `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
@@ -96,11 +120,25 @@ Then ask your agent things like *"what did we decide about auth?"*, *"challenge
96
120
 
97
121
  ### Tools vs. the *automatic* brain
98
122
 
99
- This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*), in any project. `install` wires Claude Code's live hooks and Codex's native MCP + conditional global guidance; `link` projects the repository-level MCP and instruction files used by Codex and the other coding agents. Full transcript-driven auto-capture remains a Claude Code hook capability; Codex and other hookless clients capture durable decisions through the MCP instructions and `brain_note`.
123
+ This package is the **agent-neutral read/write surface** — any MCP client gets the tools above on demand (*pull*), automatically registers presence for the lifetime of its authorized connection, and receives the Context Gateway workflow through standard MCP server instructions. `install` wires Claude Code's existing capture path and installs the local runtime; `link` projects repository-level MCP and instruction files for Codex and other coding agents. Codex and other clients retrieve relevant project memory and coordinate task intent/files through one `brain_sync` call, while `brain_note` captures durable decisions explicitly; optional host hooks add mechanically guaranteed lifecycle capture.
124
+
125
+ ### Agent-neutral live presence
126
+
127
+ An active session means an authorized MCP connection or host lifecycle adapter
128
+ heartbeated within 10 minutes; a row in a recent-chat list is history, not presence.
129
+ Each MCP connection registers at initialization and removes itself on disconnect.
130
+ Optional host adapters merge into that same logical session rather than double-counting
131
+ it, enrich it with intent/files, and remove only their own channel. The TTL covers crashes.
132
+
133
+ Future hosts get baseline support merely by connecting the MCP server. A deeper adapter
134
+ can import `klypix-mcp/presence` and map lifecycle events to `upsertSession`,
135
+ `removeSession`, and `receiveMessages`. The shared contract accepts `id`, `client`,
136
+ `surface`, `model`, `branch`, `intent`, touched `files`, and adapter `channel`;
137
+ host-specific transcript parsing stays outside the protocol.
100
138
 
101
139
  ### Updates — the propagation contract
102
140
 
103
- `install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain`, then points both Claude and Codex at that installed bundle so there is no npx cache to go stale. Updates are automatic through the Claude session-start hook: it checks npm (≤ once/24h, fail-open, disable with `KLYPIX_AUTO_UPDATE=0`) and self-installs newer releases, including healing the Codex registration. One honest caveat: a running stdio server can't hot-swap — a new binary loads on the next full app launch (or `/mcp` reconnect); `brain_doctor`'s RUNNING line tells you when that's needed.
141
+ `install` lays the whole brain (hooks + engine + local MCP server) into `~/.claude/project-brain` and keeps the current brain project's Codex config machine-portable. Updates are automatic through the Claude session-start hook: it checks npm (≤ once/24h, fail-open, disable with `KLYPIX_AUTO_UPDATE=0`) and self-installs newer releases. One honest caveat: a running stdio server can't hot-swap — a new binary loads on the next full app launch (or `/mcp` reconnect); `brain_doctor`'s RUNNING line tells you when that's needed.
104
142
 
105
143
  ## Also speaks A2A (Agent-to-Agent)
106
144
 
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // klypix-doctor — `npx klypix-mcp doctor`. The brain's self-check: is THIS machine's
3
- // brain current, are the 4 Claude Code hooks wired, what verbs does it expose, who else
4
- // is live on this project's brain, and is the harness projection in sync? ONE fact, ONE
5
- // reconcile block. Read-only (never writes). Exits 0 = ALIGNED, 1 = DRIFTED — so it
3
+ // brain current, are the Claude capture and Codex presence adapters wired, what verbs
4
+ // does it expose, which lifecycle sessions are live, and is the harness projection in
5
+ // sync? ONE fact, ONE reconcile block. Read-only (never writes). Exits 0 = ALIGNED,
6
+ // 1 = DRIFTED — so it
6
7
  // doubles as a pre-commit / CI readiness gate.
7
8
  //
8
9
  // npx klypix-mcp doctor # this project + this machine's brain
@@ -9,6 +9,7 @@
9
9
  //
10
10
  // npx klypix-mcp install # install / update the brain on this machine
11
11
  // npx klypix-mcp install --force # overwrite even a newer / dev-deployed brain
12
+ // npx klypix-mcp install --codex-hooks # optional prompt/file awareness; Codex asks for trust
12
13
  //
13
14
  // Never-throws-silently: it's an explicit CLI, so it reports what it did and exits
14
15
  // non-zero on a real failure. Never wires a broken settings.json (refuse + restore).
@@ -18,9 +19,14 @@ import path from 'path';
18
19
  import { fileURLToPath } from 'url';
19
20
  import {
20
21
  connectCodexMcpServer,
22
+ disconnectCodexMcpServer,
21
23
  mcpServerEntry,
22
24
  mergeCodexGlobalInstructions,
23
25
  } from '../src/agent-rules.mjs';
26
+ import {
27
+ codexPresenceHookStatus,
28
+ mergeCodexPresenceHooks,
29
+ } from '../src/codex-hooks.mjs';
24
30
 
25
31
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
32
  const PKG_ROOT = path.resolve(__dirname, '..');
@@ -28,6 +34,7 @@ const SRC = path.join(PKG_ROOT, 'src');
28
34
  const BIN = path.join(PKG_ROOT, 'bin');
29
35
  const VERSION = (() => { try { return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version || ''; } catch { return ''; } })();
30
36
  const FORCE = process.argv.includes('--force');
37
+ const CODEX_HOOKS = process.argv.includes('--codex-hooks');
31
38
 
32
39
  const HOME = os.homedir();
33
40
  const CLAUDE_DIR = path.join(HOME, '.claude');
@@ -37,33 +44,96 @@ const CODEX_CONFIG = path.join(HOME, '.codex', 'config.toml');
37
44
  const HOOK_MARK = 'global-brain-hook';
38
45
  const exists = (p) => { try { fs.statSync(p); return true; } catch { return false; } };
39
46
  const fwd = (p) => p.replace(/\\/g, '/');
47
+ const copyRetrySleepSync = (ms) => {
48
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
49
+ catch { /* best effort */ }
50
+ };
51
+ function copyFileRobust(src, dest, tries = 8) {
52
+ for (let attempt = 1; attempt <= tries; attempt++) {
53
+ try { fs.copyFileSync(src, dest); return; }
54
+ catch (error) {
55
+ const retryable = ['EBUSY', 'EPERM', 'EACCES'].includes(error?.code);
56
+ if (!retryable || attempt === tries) throw error;
57
+ copyRetrySleepSync(20 * attempt);
58
+ }
59
+ }
60
+ }
40
61
  function copyDir(src, dest) {
41
62
  fs.mkdirSync(dest, { recursive: true });
42
63
  for (const e of fs.readdirSync(src, { withFileTypes: true })) {
43
64
  const s = path.join(src, e.name), d = path.join(dest, e.name);
44
- if (e.isDirectory()) copyDir(s, d); else if (e.isFile()) fs.copyFileSync(s, d);
65
+ if (e.isDirectory()) copyDir(s, d); else if (e.isFile()) copyFileRobust(s, d);
45
66
  }
46
67
  }
47
68
 
48
- // Codex has two independent integration layers: MCP gives it the brain tools,
49
- // while a conditional managed block in ~/.codex/AGENTS.md tells every session
50
- // to use those tools when ./brain.klypix exists.
69
+ // Codex has two approval-free layers and one optional native layer:
70
+ // project-scoped MCP gives it tools + live presence + brain_sync, while the
71
+ // conditional ~/.codex/AGENTS.md block makes the Context Gateway workflow
72
+ // automatic on every task. Native lifecycle hooks add mechanical prompt/file
73
+ // capture only when the user explicitly asks for --codex-hooks and approves them
74
+ // in a Codex surface that exposes trust review.
51
75
  function wireCodex() {
52
- const mcp = connectCodexMcpServer({
53
- configPath: CODEX_CONFIG,
54
- entry: mcpServerEntry({ vault: '.', home: HOME }),
55
- });
76
+ const projectDir = process.cwd();
77
+ const hasProjectBrain = exists(path.join(projectDir, 'brain.klypix'))
78
+ || exists(path.join(projectDir, 'brain.any'));
79
+ const projectConfig = path.join(projectDir, '.codex', 'config.toml');
80
+ const mcp = hasProjectBrain
81
+ ? connectCodexMcpServer({
82
+ configPath: projectConfig,
83
+ // Project config is commonly committed. Keep it portable: the
84
+ // package invocation that is running this installer is exactly the
85
+ // version the project asked for, while an absolute home path would
86
+ // dirty the repo and break it on every other machine.
87
+ entry: { command: 'npx', args: ['-y', 'klypix-mcp', '--vault', '.'] },
88
+ cwd: '..',
89
+ })
90
+ : { ok: true, action: 'skipped-no-project-brain', path: projectConfig };
91
+ // Pre-1.35 installed a global `--vault "."` entry. A global Codex process
92
+ // resolves that dot from the app install directory, not the user's project,
93
+ // so it can silently bind the wrong brain and override the correct project
94
+ // table. Remove only KLYPIX-owned global tables; preserve every other server.
95
+ const globalMcp = disconnectCodexMcpServer({ configPath: CODEX_CONFIG });
56
96
  const instructions = mergeCodexGlobalInstructions(HOME);
57
- return { mcp, instructions, ok: mcp.ok && instructions.ok };
97
+ const hookScript = path.join(BRAIN_DIR, 'codex-brain-hook.mjs');
98
+ const presence = CODEX_HOOKS && exists(hookScript)
99
+ ? mergeCodexPresenceHooks({
100
+ home: HOME,
101
+ command: `node "${fwd(hookScript)}"`,
102
+ })
103
+ : {
104
+ ok: true,
105
+ action: codexPresenceHookStatus(HOME).installed ? 'existing' : 'off',
106
+ ...codexPresenceHookStatus(HOME),
107
+ };
108
+ return {
109
+ mcp,
110
+ globalMcp,
111
+ instructions,
112
+ presence,
113
+ ok: mcp.ok && globalMcp.ok && instructions.ok && presence.ok,
114
+ };
58
115
  }
59
116
 
60
117
  function reportCodex(result) {
61
118
  if (result.ok) {
62
- console.log(`✓ wired Codex: MCP tools (${result.mcp.action}) + conditional project-brain guidance (${result.instructions.action})`);
119
+ const mcp = result.mcp.action === 'skipped-no-project-brain'
120
+ ? 'project MCP skipped (run `npx klypix-mcp link` inside a brain project)'
121
+ : `project MCP + automatic presence/Context Gateway (${result.mcp.action})`;
122
+ const enhanced = result.presence.action === 'off'
123
+ ? 'enhanced hooks off (optional)'
124
+ : result.presence.action === 'existing'
125
+ ? `enhanced hooks already configured (${result.presence.executionStatus || 'unverified'})`
126
+ : `enhanced hooks ${result.presence.action} — approve/review them in Codex /hooks`;
127
+ console.log(`✓ wired Codex: ${mcp} + approval-free task guidance (${result.instructions.action}) + ${enhanced}`);
128
+ if (result.globalMcp.action === 'disconnected') {
129
+ console.log('✓ removed obsolete global Codex KLYPIX MCP entry (it could resolve "." outside the project); other MCP servers were preserved');
130
+ }
63
131
  return;
64
132
  }
65
133
  if (!result.mcp.ok) console.error(`⚠ Codex MCP was not changed: ${result.mcp.error}`);
134
+ if (!result.globalMcp.ok) console.error(`⚠ obsolete global Codex MCP entry was not removed: ${result.globalMcp.error}`);
66
135
  if (!result.instructions.ok) console.error(`⚠ Codex guidance was not changed: ${result.instructions.error}`);
136
+ if (!result.presence.ok) console.error(`⚠ Codex enhanced hooks were not changed: ${result.presence.error}`);
67
137
  console.error(' Claude Code installation is intact. Fix the Codex warning, then re-run this command.');
68
138
  }
69
139
 
@@ -122,6 +192,10 @@ function migrateProjectMcpConfig() {
122
192
  const entry = servers && servers['klypix-canvas'];
123
193
  if (!entry || (entry.command !== 'npx' && entry.command !== 'node')) return null; // absent / hand-customized → leave it
124
194
  const args = Array.isArray(entry.args) ? entry.args : [];
195
+ // A repo-relative node launch (for example scripts/klypix-mcp-server.mjs)
196
+ // is deliberately portable and often tracks a project-owned bundle.
197
+ // Never replace it with a machine-specific ~/.claude path.
198
+ if (entry.command === 'node' && args[0] && !path.isAbsolute(String(args[0]))) return null;
125
199
  const vi = args.indexOf('--vault');
126
200
  const vault = vi >= 0 && args[vi + 1] ? args[vi + 1] : '.';
127
201
  const next = mcpServerEntry({ vault }); // local now that the bundle is installed
@@ -142,7 +216,7 @@ const flatten = (code) => code
142
216
  .replace(/\.\.\/src\/klypix-(core|format)\.mjs/g, './klypix-$1.mjs')
143
217
  // brain-doctor + agent-rules (the server's lazy `import('../src/brain-doctor.mjs')`
144
218
  // for the brain_doctor tool) → flat sibling refs in the runtime layout.
145
- .replace(/\.\.\/src\/(brain-doctor|agent-rules)\.mjs/g, './$1.mjs')
219
+ .replace(/\.\.\/src\/(brain-doctor|agent-rules|mcp-presence)\.mjs/g, './$1.mjs')
146
220
  .replace(/const PKG_VERSION = \(\(\) => \{[\s\S]*?\}\)\(\);/, `const PKG_VERSION = '${VERSION}'; // baked at install (flat layout has no package.json)`);
147
221
 
148
222
  const gotLock = acquireLock();
@@ -199,7 +273,7 @@ try {
199
273
  // canvas-view-app.html is the canvas_view MCP App UI — staged raw (an HTML
200
274
  // file must never get a JS-comment banner) beside the flat server, which
201
275
  // resolves it via its ./canvas-view-app.html candidate path.
202
- for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'canvas-view-app.html']) {
276
+ for (const f of ['global-brain-hook.mjs', 'brain-semantic.mjs', 'brain-note.mjs', 'brain-git-hook.mjs', 'klypix-format.mjs', 'klypix-core.mjs', 'agent-rules.mjs', 'brain-doctor.mjs', 'agent-presence.mjs', 'mcp-presence.mjs', 'codex-brain-hook.mjs', 'codex-hooks.mjs', 'canvas-view-app.html']) {
203
277
  const s = path.join(SRC, f); if (exists(s)) staged.push({ dst: f, content: fs.readFileSync(s, 'utf8') });
204
278
  }
205
279
  for (const [src, dst] of [['klypix-mcp.mjs', 'klypix-mcp-server.mjs'], ['klypix-a2a.mjs', 'klypix-a2a-server.mjs']]) {
@@ -253,7 +327,7 @@ try {
253
327
  // (heals an existing stale config so the next MCP server spawn runs current).
254
328
  const migrated = migrateProjectMcpConfig();
255
329
 
256
- // Codex needs both native MCP tools and conditional global guidance.
330
+ // Codex needs native MCP tools, conditional guidance, and lifecycle presence.
257
331
  const codex = wireCodex();
258
332
 
259
333
  // 8) READINESS check — re-read what we just wrote and confirm all 4 hooks actually
@@ -270,7 +344,8 @@ try {
270
344
  else console.error(`⚠ readiness: ${notWired.length} hook(s) did NOT take (${notWired.join(', ')}) — the brain will read but not capture/sync. Re-run \`npx klypix-mcp install --force\` or check ${SETTINGS}.`);
271
345
  console.log(`✓ MCP server runs from the local bundle (node ${fwd(path.join(BRAIN_DIR, 'klypix-mcp-server.mjs'))}) — no npx cache, works offline, always the installed version.`);
272
346
  if (migrated) console.log(`✓ migrated ${migrated.file} klypix-canvas server: ${migrated.from} → ${migrated.to} (backup: .mcp.json.klypix-bak). Reconnect (/mcp) or restart to pick it up.`);
273
- console.log(' Claude Code auto-briefs + captures through hooks; Codex reads + writes through native MCP and managed guidance.');
347
+ console.log(' Claude Code keeps its existing auto-brief/capture hooks; Codex gets the brain_sync Context Gateway (compact task memory + clean peers + automatic conflict alerts) with no hook trust prompt.');
348
+ console.log(' Optional mechanical Codex lifecycle capture: re-run with `--codex-hooks`, then approve/review KLYPIX in a Codex surface that supports hook trust.');
274
349
  console.log(' ⚠ Open sessions keep their OLD server until relaunched: fully quit & reopen the app (a session resume / new chat does NOT respawn the MCP server). `brain_doctor`\'s RUNNING line confirms when you\'re current.');
275
350
  console.log(' Verify anytime: `npx klypix-mcp doctor` (is the brain current + wired + in sync, who else is live).');
276
351
  } catch (e) {
@@ -17,7 +17,7 @@
17
17
  // Synchronous top-level by design: the dispatcher does `await import(this); process.exit(0)`,
18
18
  // so all work (and its logs) must complete during module evaluation, before exit.
19
19
  import path from 'path';
20
- import { linkProject } from '../src/agent-rules.mjs';
20
+ import { compactAgentsBrief, linkProject } from '../src/agent-rules.mjs';
21
21
 
22
22
  try {
23
23
  const args = process.argv.slice(3);
@@ -25,6 +25,7 @@ try {
25
25
  const dirArg = args.find(a => !a.startsWith('-'));
26
26
  const projectDir = path.resolve(dirArg || process.cwd());
27
27
  const { rules, mcp, hasBrain, version } = linkProject(projectDir, { check });
28
+ const compactBrief = await compactAgentsBrief(projectDir, { check });
28
29
 
29
30
  if (check) {
30
31
  // Audit-only: classify, report, exit 1 on any drift.
@@ -34,7 +35,11 @@ try {
34
35
  for (const r of mcp) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.why ? ' (' + r.why + ')' : ''}`);
35
36
  console.log('\n Rules / instructions:');
36
37
  for (const r of rules) console.log(` ${mark(r.status)} ${r.tool.padEnd(26)} ${r.file} — ${r.status.toUpperCase()}${r.stampedVersion ? ' (stamped v' + r.stampedVersion + ')' : ''}`);
37
- const drift = [...rules, ...mcp].filter(r => r.status !== 'ok');
38
+ if (compactBrief.status !== 'skipped') {
39
+ console.log(` ${mark(compactBrief.status)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md — ${compactBrief.status.toUpperCase()}${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
40
+ }
41
+ const briefDrift = ['stale', 'error'].includes(compactBrief.status) ? [compactBrief] : [];
42
+ const drift = [...rules, ...mcp, ...briefDrift].filter(r => r.status !== 'ok' && r.status !== 'skipped');
38
43
  if (!drift.length) { console.log(`\n✓ in sync — all ${rules.length + mcp.length} managed file(s) match brain v${version}.`); process.exit(0); }
39
44
  console.log(`\n✗ ${drift.length} file(s) drifted (stale / hand-edited / missing) — run \`npx klypix-mcp link\` to re-project.`);
40
45
  process.exit(1);
@@ -46,8 +51,11 @@ try {
46
51
  for (const r of mcp) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
47
52
  console.log('\n Rules (so each tool auto-reads + captures the brain):');
48
53
  for (const r of rules) console.log(` ${mark(r.action)} ${r.tool.padEnd(26)} ${r.file}${r.why ? ' (' + r.why + ')' : ''}`);
54
+ if (compactBrief.status !== 'skipped') {
55
+ console.log(` ${mark(compactBrief.action)} ${'AGENTS.md compact fallback'.padEnd(26)} AGENTS.md${compactBrief.bytes ? ` (${compactBrief.bytes} bytes)` : ''}`);
56
+ }
49
57
 
50
- const changed = [...rules, ...mcp].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
58
+ const changed = [...rules, ...mcp, compactBrief].filter(r => r.action && !['unchanged', 'skipped'].includes(r.action)).length;
51
59
  console.log(`\n✓ ${changed} file(s) written/updated — every agent opened in this project now reads + captures ./brain.klypix.`);
52
60
  console.log(' Codex gets .codex/config.toml + AGENTS.md; Cline & Windsurf use their global MCP config plus project rules.');
53
61
  console.log(' Verify anytime with `npx klypix-mcp link --check` (or `npx klypix-mcp doctor`).');
@@ -27,8 +27,10 @@ import {
27
27
  resolveVault, getEmbedder, buildKlypixMap, cardSchema, connSchema,
28
28
  opListCanvases, opReadCanvas, opSearchCanvases, opSearchAllBrains,
29
29
  opBrainInsights, opBrainConnect, opBrainReconcile, opBrainGarden, opCreateCanvas, opAddToCanvas, opBrainNote, opBrainMessage, opBrainAsk, opBrainChallenge, opCanvasView, opBrainLens,
30
+ opBrainTaskContext,
30
31
  } from '../src/klypix-core.mjs';
31
32
  import { mcpServerEntry } from '../src/agent-rules.mjs';
33
+ import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS } from '../src/mcp-presence.mjs';
32
34
 
33
35
  // Real package version for the MCP handshake (was hardcoded '1.0.0', which
34
36
  // misled every client/version diagnosis — it could never reflect the true release).
@@ -102,17 +104,21 @@ if (process.argv[2] === 'init') {
102
104
 
103
105
  const vaultArgIdx = process.argv.indexOf('--vault');
104
106
  const VAULT = resolveVault(vaultArgIdx >= 0 ? process.argv[vaultArgIdx + 1] : undefined);
107
+ const server = new McpServer(
108
+ { name: 'klypix-canvas', version: PKG_VERSION },
109
+ { instructions: KLYPIX_MCP_INSTRUCTIONS },
110
+ );
111
+ const mcpPresence = createMcpPresence({ server, initialVault: VAULT });
105
112
 
106
113
  // Map a protocol-neutral core result → an MCP tool result.
107
114
  const toContent = (r) => {
108
115
  const content = r.blocks.map(b => b.kind === 'image'
109
116
  ? { type: 'image', data: b.data, mimeType: b.mime }
110
117
  : { type: 'text', text: b.text });
111
- return r.isError ? { content, isError: true } : { content };
118
+ const result = r.isError ? { content, isError: true } : { content };
119
+ return mcpPresence.decorateToolResult(result);
112
120
  };
113
121
 
114
- const server = new McpServer({ name: 'klypix-canvas', version: PKG_VERSION });
115
-
116
122
  server.registerTool('list_canvases', {
117
123
  title: 'List KLYPIX canvases',
118
124
  description: 'List all .klypix / .any canvas files in the vault, with card and connection counts.',
@@ -265,7 +271,7 @@ server.registerTool('brain_note', {
265
271
 
266
272
  server.registerTool('brain_message', {
267
273
  title: 'Message the other live agent sessions on this project (one-time note, not a brain card)',
268
- description: 'Leave a DELIBERATE, targeted note for the OTHER live agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can SEND (the twin of the `🧠 MSG [to]: text` marker); delivery is to HOOK-WIRED sessions (Claude Code with the brain hooks) — each sees the note once at its next prompt. A hookless peer (plain Cursor/Cline session) will NOT receive it, so don\'t rely on this to warn one. Ephemeral (expires in 24h) and NOT persisted to the brain — for a durable decision use brain_note instead.',
274
+ description: 'Leave a DELIBERATE, targeted note for the OTHER active agent sessions working on this project right now ("merged the hook refactor — rebase before you commit", "don\'t touch canvasStore, mid-refactor"). Any MCP client can send and receive through the shared presence lane. Hookless MCP peers receive each note once on their next KLYPIX tool call (and may also see a host logging notification); lifecycle hooks provide more proactive delivery. Ephemeral (expires in 24h) and NOT persisted to the brain — for a durable decision use brain_note instead.',
269
275
  inputSchema: {
270
276
  text: z.string().describe('The note to deliver (kept to 400 chars).'),
271
277
  to: z.string().optional().describe('Target hint — a peer session id-prefix or branch name; omit or "all" for every live session.'),
@@ -273,12 +279,75 @@ server.registerTool('brain_message', {
273
279
  },
274
280
  }, async ({ text, to, canvas }) => {
275
281
  let via; try { via = server.server.getClientVersion()?.name; } catch { /* optional */ }
282
+ mcpPresence.noteSent(text);
276
283
  return toContent(await opBrainMessage({ vault: VAULT, canvas, text, to, via }));
277
284
  });
278
285
 
286
+ server.registerTool('brain_sync', {
287
+ title: 'KLYPIX Context Gateway — synchronize task, peers, conflicts, and relevant memory',
288
+ 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 automatic late-arrival overlap alerts. Portable across Codex/Antigravity and every MCP host; native hooks are optional.',
289
+ annotations: {
290
+ destructiveHint: false,
291
+ idempotentHint: true,
292
+ openWorldHint: false,
293
+ },
294
+ inputSchema: {
295
+ intent: z.string().max(160).optional().describe('One sentence describing the current task. Supply for start/checkpoint; completion clears it.'),
296
+ files: z.array(z.string()).max(20).optional().describe('Project-relative files you expect to touch or have touched. Exact overlaps with peers are flagged.'),
297
+ phase: z.enum(['start', 'checkpoint', 'complete']).optional().describe('start replaces prior task scope; checkpoint merges changed scope; complete clears task intent/files. Default: checkpoint.'),
298
+ include_context: z.boolean().optional().describe('Include fast task-relevant brain cards in the same response. Defaults true; ignored for phase complete.'),
299
+ },
300
+ }, async ({ intent, files, phase, include_context }) => {
301
+ const totalStartedAt = Date.now();
302
+ const report = mcpPresence.sync({ intent, files, phase });
303
+ let taskContext = null;
304
+ if (phase !== 'complete' && include_context !== false && (intent || files?.length)) {
305
+ taskContext = await opBrainTaskContext({
306
+ vault: VAULT,
307
+ intent,
308
+ files,
309
+ k: 5,
310
+ budgetChars: 2800,
311
+ });
312
+ }
313
+ const contextText = taskContext?.blocks
314
+ ?.filter((block) => block.kind === 'text')
315
+ .map((block) => block.text)
316
+ .filter(Boolean)
317
+ .join('\n\n') || '';
318
+ const totalMs = Math.max(0, Date.now() - totalStartedAt);
319
+ const structuredContent = {
320
+ ...(report.structured || {
321
+ schemaVersion: 1,
322
+ status: 'idle',
323
+ phase: phase || 'checkpoint',
324
+ }),
325
+ context: taskContext?.context || {
326
+ mode: 'not-requested',
327
+ hits: [],
328
+ sufficient: false,
329
+ durationMs: 0,
330
+ },
331
+ timingMs: {
332
+ ...(report.structured?.timingMs || {}),
333
+ context: taskContext?.context?.durationMs || 0,
334
+ total: totalMs,
335
+ },
336
+ };
337
+ const timingText = `Context Gateway total: ${totalMs}ms`
338
+ + (taskContext?.context ? ` (memory ${taskContext.context.durationMs}ms)` : '') + '.';
339
+ return {
340
+ content: [{
341
+ type: 'text',
342
+ text: [report.text, contextText, timingText].filter(Boolean).join('\n\n'),
343
+ }],
344
+ structuredContent,
345
+ };
346
+ });
347
+
279
348
  server.registerTool('brain_doctor', {
280
349
  title: 'Brain doctor — is this brain current, wired, and in sync?',
281
- description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (the deployed brain-core version + optional npm-latest currency), HOOKS (are all 4 Claude Code hooks wired — liveness vs readiness), TOOLS (the discoverable MCP verb manifest), PEERS (other live sessions on this project\'s brain right now), and HARNESS (per-file projection drift: ok/stale/hand-edited/missing). Use to answer "is my brain current + correctly installed + in sync, and who else is live?" without file-spelunking. Never writes. The MCP-callable twin of `npx klypix-mcp doctor`.',
350
+ description: 'Read-only self-check of the installed klypix brain, as ONE verdict: VERSION (deployed brain-core + optional npm currency), CLAUDE (existing 4-hook capture readiness), CODEX (automatic MCP presence plus optional enhanced-hook status), TOOLS (discoverable MCP verbs), SESSIONS (all active presence-adapter sessions across hosts, never recent-chat history), and HARNESS (projection drift). Use to answer "is my brain current, correctly installed, in sync, and who is actually live?" without file-spelunking. Never writes. The MCP-callable twin of `npx klypix-mcp doctor`.',
282
351
  inputSchema: {
283
352
  project: z.string().optional().describe('Project dir to audit harness + peers for. Defaults to the server\'s working directory.'),
284
353
  check_npm: z.boolean().optional().describe('Also fetch npm latest to flag a stale brain (default false — this one does a network `npm view`).'),
@@ -297,7 +366,7 @@ server.registerTool('brain_doctor', {
297
366
  // makes the RUNNING check report the caller's actual server version, never a
298
367
  // phantom another session's heartbeat wrote to the shared registry.
299
368
  const report = inspect({ projectDir: project ? path.resolve(project) : process.cwd(), npmLatest, self: { pid: process.pid, version: PKG_VERSION } });
300
- return { content: [{ type: 'text', text: render(report, { color: false }) }] };
369
+ return mcpPresence.decorateToolResult({ content: [{ type: 'text', text: render(report, { color: false }) }] });
301
370
  } catch (e) {
302
371
  return { content: [{ type: 'text', text: `brain_doctor unavailable: ${e?.message || e}` }], isError: true };
303
372
  }
@@ -394,9 +463,14 @@ if (!canvasViewAsApp) {
394
463
  }
395
464
 
396
465
  const transport = new StdioServerTransport();
466
+ server.server.oninitialized = () => {
467
+ mcpPresence.start(VAULT);
468
+ recordRunningServer();
469
+ log(`ready · vault=${VAULT} · presence=mcp`);
470
+ };
471
+ server.server.onclose = () => mcpPresence.stop();
472
+ process.once('exit', () => mcpPresence.stop());
397
473
  await server.connect(transport);
398
- recordRunningServer();
399
- log(`ready · vault=${VAULT}`);
400
474
  // Pre-warm the on-device embedder in the BACKGROUND so the first cross-project
401
475
  // search of a session is already semantic, not a lexical fallback.
402
476
  getEmbedder(log).then(p => log(p ? 'semantic ready (pre-warmed)' : 'semantic unavailable — lexical only')).catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.33.0",
3
+ "version": "1.37.0",
4
4
  "description": "Every project gets a brain — one open .klypix file your AI agents read, write, and argue from, over MCP. Works with Claude, Codex, Cursor, Cline, any model.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,7 +38,9 @@
38
38
  "exports": {
39
39
  ".": "./index.mjs",
40
40
  "./format": "./src/klypix-format.mjs",
41
- "./core": "./src/klypix-core.mjs"
41
+ "./core": "./src/klypix-core.mjs",
42
+ "./presence": "./src/agent-presence.mjs",
43
+ "./mcp-presence": "./src/mcp-presence.mjs"
42
44
  },
43
45
  "files": [
44
46
  "src",
@@ -53,7 +55,7 @@
53
55
  "node": ">=18"
54
56
  },
55
57
  "scripts": {
56
- "test": "node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.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/canvas-view.mjs && node test/status-completeness.mjs"
58
+ "test": "node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/context-gateway.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.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/canvas-view.mjs && node test/status-completeness.mjs"
57
59
  },
58
60
  "dependencies": {
59
61
  "@modelcontextprotocol/ext-apps": "^1.7.4",