nexus-prime 7.5.0 → 7.6.2

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.
@@ -962,8 +962,13 @@ export function buildMcpToolDefinitions() {
962
962
  required: ['taskId', 'goal', 'findings'],
963
963
  },
964
964
  },
965
- ...synapseToolDefinitions,
966
- ...architectsToolDefinitions,
965
+ // When NEXUS_DISABLE_WORKFORCE=1 (the daemon default since v7.6.1),
966
+ // strip the 24 nexus_synapse_* / nexus_architects_* tools from the
967
+ // catalog so the model doesn't see surface area for engines that
968
+ // aren't initialized at runtime. Set NEXUS_DISABLE_WORKFORCE=0 to
969
+ // re-enable both engines + their tools.
970
+ ...(process.env.NEXUS_DISABLE_WORKFORCE === '1' ? [] : synapseToolDefinitions),
971
+ ...(process.env.NEXUS_DISABLE_WORKFORCE === '1' ? [] : architectsToolDefinitions),
967
972
  // ── Workforce (unified worker+job layer) ──────────────────────────
968
973
  {
969
974
  name: 'nexus_work_enqueue',
package/dist/cli.js CHANGED
@@ -788,6 +788,16 @@ program
788
788
  if (process.env.NEXUS_DAEMON_FAST_START === undefined) {
789
789
  process.env.NEXUS_DAEMON_FAST_START = '1';
790
790
  }
791
+ // Default the daemon to "workforce-disabled" mode: Synapse + Architects
792
+ // engines stay in the source tree (tests still run, code is preserved)
793
+ // but they don't open their SQLite DBs, run schema migrations, or
794
+ // surface their 24 MCP tools to clients. Saves ~700ms boot, shrinks
795
+ // the model-visible tool catalog, and removes the empty Workforce
796
+ // surface from the dashboard. Re-enable with NEXUS_DISABLE_WORKFORCE=0
797
+ // for users who actually hire operatives + dispatch missions.
798
+ if (process.env.NEXUS_DISABLE_WORKFORCE === undefined) {
799
+ process.env.NEXUS_DISABLE_WORKFORCE = '1';
800
+ }
791
801
  const workspaceContext = resolveWorkspaceContext({ workspaceRoot: getWorkspaceRoot() });
792
802
  const daemon = new NexusDaemonServer(workspaceContext);
793
803
  const { started, record } = await daemon.start();
@@ -1905,6 +1915,75 @@ program
1905
1915
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
1906
1916
  });
1907
1917
  });
1918
+ program
1919
+ .command('fix-claude-hooks')
1920
+ .description('Strip stale Nexus hook entries from ~/.claude/settings.json (use after downgrading from a newer version)')
1921
+ .option('--dry-run', 'Show what would change without writing')
1922
+ .option('--workspace', 'Also clean ./.claude/settings.json in the current workspace')
1923
+ .action(async (opts) => {
1924
+ const fs = await import('fs');
1925
+ const path = await import('path');
1926
+ const os = await import('os');
1927
+ const { NEXUS_HOOK_COMMAND_MARKER } = await import('./install/claude-code-hooks.js');
1928
+ const targets = [path.join(os.homedir(), '.claude', 'settings.json')];
1929
+ if (opts.workspace)
1930
+ targets.push(path.join(process.cwd(), '.claude', 'settings.json'));
1931
+ let totalRemoved = 0;
1932
+ for (const target of targets) {
1933
+ if (!fs.existsSync(target)) {
1934
+ console.log(` skip: ${target} (not found)`);
1935
+ continue;
1936
+ }
1937
+ let parsed;
1938
+ try {
1939
+ parsed = JSON.parse(fs.readFileSync(target, 'utf8'));
1940
+ }
1941
+ catch (err) {
1942
+ console.error(` error: ${target} (invalid JSON: ${err instanceof Error ? err.message : String(err)})`);
1943
+ continue;
1944
+ }
1945
+ const hooks = parsed?.hooks;
1946
+ if (!hooks || typeof hooks !== 'object') {
1947
+ console.log(` ok: ${target} (no hooks block)`);
1948
+ continue;
1949
+ }
1950
+ let removedHere = 0;
1951
+ for (const event of Object.keys(hooks)) {
1952
+ if (!Array.isArray(hooks[event]))
1953
+ continue;
1954
+ const before = hooks[event];
1955
+ const after = before.filter((entry) => {
1956
+ const entryHooks = entry?.hooks;
1957
+ if (!Array.isArray(entryHooks))
1958
+ return true;
1959
+ const hasNexus = entryHooks.some((h) => typeof h?.command === 'string' && h.command.trimStart().startsWith(NEXUS_HOOK_COMMAND_MARKER));
1960
+ if (hasNexus)
1961
+ removedHere += 1;
1962
+ return !hasNexus;
1963
+ });
1964
+ if (after.length === 0) {
1965
+ delete hooks[event];
1966
+ }
1967
+ else {
1968
+ hooks[event] = after;
1969
+ }
1970
+ }
1971
+ if (Object.keys(hooks).length === 0)
1972
+ delete parsed.hooks;
1973
+ totalRemoved += removedHere;
1974
+ if (removedHere === 0) {
1975
+ console.log(` ok: ${target} (no Nexus entries)`);
1976
+ continue;
1977
+ }
1978
+ if (opts.dryRun) {
1979
+ console.log(` dry-run: ${target} would remove ${removedHere} Nexus hook entry/entries`);
1980
+ continue;
1981
+ }
1982
+ fs.writeFileSync(target, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
1983
+ console.log(` fixed: ${target} (removed ${removedHere} entry/entries)`);
1984
+ }
1985
+ console.log(opts.dryRun ? `\nDry-run total: ${totalRemoved} entry/entries.` : `\nDone. Removed ${totalRemoved} stale Nexus hook entry/entries.`);
1986
+ });
1908
1987
  program
1909
1988
  .command('watch')
1910
1989
  .description('Tail live SSE events from the running daemon in a colored terminal feed')
@@ -1923,4 +2002,26 @@ program
1923
2002
  if (process.argv.length === 2) {
1924
2003
  process.argv.push('setup', 'auto');
1925
2004
  }
1926
- program.parse();
2005
+ // Graceful fallback for unknown `nexus-prime hook <subcommand>` invocations.
2006
+ // When users downgrade from a version that wrote hook entries to
2007
+ // ~/.claude/settings.json, the older binary may not have the referenced
2008
+ // subcommand. Exiting non-zero would fail every Claude Code session start.
2009
+ // Drain piped stdin (Claude pipes JSON into hooks) and exit 0 silently.
2010
+ const KNOWN_HOOK_SUBCOMMANDS = new Set([
2011
+ 'bootstrap', 'memory', 'mindkit', 'ghost-pass', 'session-dna',
2012
+ 'generate', 'deploy', 'undeploy', 'list', 'help', '--help', '-h',
2013
+ ]);
2014
+ if (process.argv[2] === 'hook' && process.argv[3] && !KNOWN_HOOK_SUBCOMMANDS.has(process.argv[3])) {
2015
+ if (!process.stdin.isTTY) {
2016
+ process.stdin.resume();
2017
+ process.stdin.on('data', () => { });
2018
+ process.stdin.on('end', () => process.exit(0));
2019
+ setTimeout(() => process.exit(0), 200).unref();
2020
+ }
2021
+ else {
2022
+ process.exit(0);
2023
+ }
2024
+ }
2025
+ else {
2026
+ program.parse();
2027
+ }
@@ -95,6 +95,12 @@ if(location.protocol==='file:'){
95
95
  <div id="board-pyramid"></div>
96
96
  </div>
97
97
 
98
+ <!-- Connected Ecosystem (per-client MCP status) -->
99
+ <div id="connected-ecosystem" class="card" style="margin-bottom:16px;display:none">
100
+ <div class="shd" style="margin-bottom:8px">Connected Ecosystem <span id="ecosystem-stamp" style="font-size:11px;opacity:.5;font-weight:400"></span></div>
101
+ <div id="ecosystem-list" style="display:flex;flex-wrap:wrap;gap:8px"></div>
102
+ </div>
103
+
98
104
  <!-- Hero KPI row -->
99
105
  <div id="hero-stats" role="region" aria-label="Key metrics">
100
106
  <div class="hero-stat"><div class="metric-val" id="m-tokens">—</div><div class="metric-lbl">Tokens Saved</div><canvas id="spark-tokens" class="sparkline" width="80" height="24" aria-hidden="true"></canvas></div>
@@ -116,6 +122,16 @@ if(location.protocol==='file:'){
116
122
  <!-- Orchestration pipeline (live decomposition + completion) -->
117
123
  <div id="orchestration-pipeline" class="card" style="margin-bottom:16px;display:none"></div>
118
124
 
125
+ <!-- Neural Stream HUD (live SSE event feed strip) -->
126
+ <div id="neural-stream-card" class="card" style="margin-bottom:16px;display:none">
127
+ <div class="shd" style="margin-bottom:8px;display:flex;align-items:center;gap:8px">
128
+ <span>Neural Stream</span>
129
+ <span id="neural-stream-count" style="font-size:11px;opacity:.5;font-weight:400"></span>
130
+ <span style="margin-left:auto;display:inline-flex;align-items:center;gap:4px;font-size:11px;opacity:.6;font-weight:400"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent-good,#4ade80);animation:pulse 2s ease-in-out infinite"></span> live</span>
131
+ </div>
132
+ <div id="neural-stream-list" style="display:flex;flex-direction:column;gap:4px;max-height:200px;overflow-y:auto"></div>
133
+ </div>
134
+
119
135
  <!-- Tool health strip -->
120
136
  <div id="tool-health-card" class="card" style="margin-bottom:16px;display:none">
121
137
  <div class="shd" style="margin-bottom:8px">Tool Health <span id="tool-health-stamp" style="font-size:11px;opacity:.5;font-weight:400"></span></div>
@@ -45,6 +45,9 @@ setOnEvent(evt => {
45
45
  // Runtime tab ingests ALL events regardless of active tab (for live history)
46
46
  Runtime.ingestEvent(evt);
47
47
 
48
+ // Neural Stream HUD ring buffer ingests ALL events too (board card auto-renders)
49
+ Board.pushNeuralEvent?.(evt);
50
+
48
51
  const tab = S.tab;
49
52
  if (tab === 'board') {
50
53
  Board.render();
@@ -82,6 +82,9 @@ export const S = {
82
82
  lastDecomposition: null,
83
83
  lastCompletion: null,
84
84
 
85
+ // Neural Stream HUD ring buffer (latest SSE events, restored from v3.8.0)
86
+ neuralStream: [],
87
+
85
88
  // Assets sub-tabs
86
89
  assetsSubTab: 'skills',
87
90
 
@@ -93,6 +93,7 @@ export async function load() {
93
93
  }
94
94
 
95
95
  export function render() {
96
+ renderConnectedEcosystem();
96
97
  renderPyramidWidget();
97
98
  renderHero();
98
99
  renderFirstRunHero();
@@ -101,6 +102,126 @@ export function render() {
101
102
  renderEvents();
102
103
  renderPulse();
103
104
  renderToolHealth();
105
+ renderNeuralStream();
106
+ }
107
+
108
+ /* ── Connected Ecosystem (per-client MCP status, restored from v3.8.0) ── */
109
+ const _CLIENT_LABEL = {
110
+ 'claude-code': 'Claude Code',
111
+ claude: 'Claude',
112
+ 'claude-desktop': 'Claude Desktop',
113
+ codex: 'Codex',
114
+ cursor: 'Cursor',
115
+ opencode: 'Opencode',
116
+ windsurf: 'Windsurf',
117
+ antigravity: 'Antigravity',
118
+ aider: 'Aider',
119
+ continue: 'Continue',
120
+ cline: 'Cline',
121
+ openclaw: 'OpenClaw',
122
+ mcp: 'MCP',
123
+ };
124
+ function _clientStateColor(state) {
125
+ const s = String(state || '').toLowerCase();
126
+ if (s === 'primaryactive' || s === 'active') return 'var(--accent-good, #4ade80)';
127
+ if (s === 'idle' || s === 'detected') return 'var(--accent-warn, #fbbf24)';
128
+ if (s === 'installed') return 'var(--accent, #60a5fa)';
129
+ return 'var(--muted, #6b7280)';
130
+ }
131
+ function renderConnectedEcosystem() {
132
+ const host = document.getElementById('connected-ecosystem');
133
+ if (!host) return;
134
+ const list = document.getElementById('ecosystem-list');
135
+ const stamp = document.getElementById('ecosystem-stamp');
136
+ const op = S.operateSurface;
137
+ const clients = (op?.clients?.detected || op?.clients || []);
138
+ const arr = Array.isArray(clients) ? clients : [];
139
+ if (arr.length === 0) {
140
+ host.style.display = 'none';
141
+ return;
142
+ }
143
+ host.style.display = 'block';
144
+ const primaryId = op?.clients?.primary?.clientId || op?.primaryClient?.clientId || null;
145
+ const ordered = [...arr].sort((a, b) => {
146
+ const ap = a.clientId === primaryId ? 0 : 1;
147
+ const bp = b.clientId === primaryId ? 0 : 1;
148
+ if (ap !== bp) return ap - bp;
149
+ return String(a.clientId || '').localeCompare(String(b.clientId || ''));
150
+ });
151
+ const html = ordered.slice(0, 12).map((c) => {
152
+ const id = String(c.clientId || c.id || 'unknown');
153
+ const label = _CLIENT_LABEL[id] || id;
154
+ const isPrimary = id === primaryId;
155
+ const stateRaw = isPrimary ? 'primaryActive' : (c.state || c.status || 'idle');
156
+ const color = _clientStateColor(stateRaw);
157
+ const stateLabel = isPrimary ? 'PRIMARY·ACTIVE' : String(stateRaw).toUpperCase();
158
+ const lastSeen = c.lastHeartbeatAt || c.lastSeenAt;
159
+ const ago = lastSeen ? timeAgo(lastSeen) : '';
160
+ return `<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:6px;background:var(--surface2);font-size:12px;border-left:3px solid ${color}">
161
+ <span style="width:6px;height:6px;border-radius:50%;background:${color};flex-shrink:0"></span>
162
+ <span style="font-family:var(--font-mono);font-weight:600;color:var(--fg1)">${esc(label)}</span>
163
+ <span style="font-size:10px;color:${color};font-family:var(--font-mono);text-transform:uppercase;letter-spacing:0.5px">${esc(stateLabel)}</span>
164
+ ${ago ? `<span style="margin-left:auto;font-size:10px;color:var(--muted)">${esc(ago)}</span>` : ''}
165
+ </div>`;
166
+ }).join('');
167
+ list.innerHTML = html;
168
+ if (stamp) stamp.textContent = `${ordered.length} client${ordered.length === 1 ? '' : 's'}`;
169
+ }
170
+
171
+ /* ── Neural Stream HUD (live SSE event feed strip, restored from v3.8.0) ── */
172
+ const _NEURAL_MAX = 12;
173
+ function _neuralColor(category) {
174
+ switch (String(category || '').toLowerCase()) {
175
+ case 'memory': return 'oklch(68% 0.22 295)';
176
+ case 'token':
177
+ case 'tokens': return 'oklch(87% 0.19 152)';
178
+ case 'orchestration':
179
+ case 'planner': return 'oklch(83% 0.14 220)';
180
+ case 'synapse':
181
+ case 'operative':
182
+ case 'mission': return 'oklch(83% 0.17 55)';
183
+ case 'architects':
184
+ case 'dispatch':
185
+ case 'sentinel': return 'oklch(75% 0.20 30)';
186
+ case 'darwin':
187
+ case 'governance': return 'oklch(78% 0.18 340)';
188
+ case 'graph': return 'oklch(80% 0.15 180)';
189
+ default: return 'var(--muted, #6b7280)';
190
+ }
191
+ }
192
+ export function pushNeuralEvent(evt) {
193
+ if (!evt || typeof evt !== 'object') return;
194
+ if (!Array.isArray(S.neuralStream)) S.neuralStream = [];
195
+ S.neuralStream.unshift({
196
+ type: String(evt.type || ''),
197
+ category: String(evt.category || ''),
198
+ time: Number(evt.time || Date.now()),
199
+ summary: typeof evt.summary === 'string' ? evt.summary : '',
200
+ });
201
+ if (S.neuralStream.length > _NEURAL_MAX) S.neuralStream.length = _NEURAL_MAX;
202
+ renderNeuralStream();
203
+ }
204
+ function renderNeuralStream() {
205
+ const host = document.getElementById('neural-stream-card');
206
+ if (!host) return;
207
+ const list = document.getElementById('neural-stream-list');
208
+ const count = document.getElementById('neural-stream-count');
209
+ const items = Array.isArray(S.neuralStream) ? S.neuralStream : [];
210
+ if (items.length === 0) {
211
+ host.style.display = 'none';
212
+ return;
213
+ }
214
+ host.style.display = 'block';
215
+ if (count) count.textContent = `${items.length} signal${items.length === 1 ? '' : 's'}`;
216
+ list.innerHTML = items.map((e) => {
217
+ const color = _neuralColor(e.category);
218
+ const ago = timeAgo(e.time);
219
+ return `<div style="display:flex;align-items:baseline;gap:8px;padding:3px 6px;border-left:2px solid ${color};font-size:11px;font-family:var(--font-mono);line-height:1.4">
220
+ <span style="color:${color};font-weight:600;min-width:90px">${esc(e.type)}</span>
221
+ <span style="color:var(--fg1);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(e.summary || e.category || '')}</span>
222
+ <span style="color:var(--muted);font-size:10px;flex-shrink:0">${esc(ago)}</span>
223
+ </div>`;
224
+ }).join('');
104
225
  }
105
226
 
106
227
  /* ── Memory pyramid (above kanban) ── */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.5.0",
3
+ "version": "7.6.2",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",