nexus-prime 7.9.20 → 7.9.21
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/dist/agents/adapters/mcp/handlers/runtime.js +1 -1
- package/dist/agents/adapters/mcp/types.d.ts +6 -0
- package/dist/agents/adapters/mcp.js +17 -9
- package/dist/cli.js +11 -6
- package/dist/dashboard/app/views/board.js +19 -5
- package/dist/dashboard/app/views/runtime.js +110 -14
- package/dist/engines/workflow-runtime.js +9 -1
- package/dist/synapse/operatives/crud.js +5 -1
- package/package.json +1 -1
|
@@ -77,7 +77,7 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
|
77
77
|
if (!requestedTool) {
|
|
78
78
|
return { content: [{ type: 'text', text: 'tool_name is required.' }] };
|
|
79
79
|
}
|
|
80
|
-
const allToolDefs =
|
|
80
|
+
const allToolDefs = hctx.getDecoratedToolDefinitions(hctx.getToolProfile());
|
|
81
81
|
const found = allToolDefs.find(t => t.name === requestedTool);
|
|
82
82
|
if (!found) {
|
|
83
83
|
const available = allToolDefs.map(t => t.name).join(', ');
|
|
@@ -110,6 +110,12 @@ export interface McpHandlerCtx {
|
|
|
110
110
|
getDarwinLoop(args?: Record<string, unknown>): DarwinLoop;
|
|
111
111
|
/** Current MCP tool profile — 'autonomous' or 'full' */
|
|
112
112
|
getToolProfile(): McpToolProfile;
|
|
113
|
+
/** Get decorated tool definitions for lazy tool discovery */
|
|
114
|
+
getDecoratedToolDefinitions(profile?: McpToolProfile): Array<{
|
|
115
|
+
name: string;
|
|
116
|
+
description: string;
|
|
117
|
+
inputSchema: Record<string, unknown>;
|
|
118
|
+
}>;
|
|
113
119
|
/** True if `target` looks like a package (node_modules, built artifacts, etc.) */
|
|
114
120
|
isPackageLikeWorkspace(target: string): boolean;
|
|
115
121
|
/** Resolve a candidate path relative to the workspace root */
|
|
@@ -623,7 +623,7 @@ export class MCPAdapter {
|
|
|
623
623
|
: 'Full MCP profile active. Low-level and authoring tools are exposed.';
|
|
624
624
|
}
|
|
625
625
|
finalizeToolDefinitions(tools, profile = this.getToolProfile()) {
|
|
626
|
-
const taskContext = this.currentTask || this.
|
|
626
|
+
const taskContext = this.currentTask || this.runtime?.getUsageSnapshot()?.orchestration?.lastPrompt || '';
|
|
627
627
|
const userTier = getSharedLicenseManager().getStatus().tier;
|
|
628
628
|
const profileFiltered = profile === 'full'
|
|
629
629
|
? tools.slice()
|
|
@@ -715,14 +715,21 @@ export class MCPAdapter {
|
|
|
715
715
|
setupToolHandlers() {
|
|
716
716
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
717
717
|
const profile = this.getToolProfile();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
718
|
+
setTimeout(() => {
|
|
719
|
+
try {
|
|
720
|
+
this.getRuntime().recordClientInstructionStatus({
|
|
721
|
+
clientId: this.name,
|
|
722
|
+
clientFamily: this.name === 'openclaw' ? 'antigravity' : this.name,
|
|
723
|
+
toolProfile: profile,
|
|
724
|
+
status: profile === 'autonomous' ? 'guided' : 'manual',
|
|
725
|
+
summary: this.describeClientInstructionStatus(profile),
|
|
726
|
+
updatedAt: Date.now(),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
// Tool enumeration must stay fast and side-effect best-effort.
|
|
731
|
+
}
|
|
732
|
+
}, 0);
|
|
726
733
|
return {
|
|
727
734
|
tools: this.listTools(profile),
|
|
728
735
|
};
|
|
@@ -957,6 +964,7 @@ export class MCPAdapter {
|
|
|
957
964
|
getRepoNgramIndex: (a = {}) => self.getRepoNgramIndex(a),
|
|
958
965
|
getDarwinLoop: (a = {}) => self.getDarwinLoop(a),
|
|
959
966
|
getToolProfile: () => self.getToolProfile(),
|
|
967
|
+
getDecoratedToolDefinitions: (profile) => self.getDecoratedToolDefinitions(profile),
|
|
960
968
|
isPackageLikeWorkspace: (target) => self.isPackageLikeWorkspace(target),
|
|
961
969
|
resolveToolPath: (p, a = {}) => self.resolveToolPath(p, a),
|
|
962
970
|
telemetry: self.telemetry,
|
package/dist/cli.js
CHANGED
|
@@ -977,11 +977,16 @@ program
|
|
|
977
977
|
});
|
|
978
978
|
}
|
|
979
979
|
console.error('Starting Nexus Prime MCP Server (standalone)...');
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
980
|
+
setTimeout(() => {
|
|
981
|
+
void runStartupHygiene({
|
|
982
|
+
repoRoot: workspaceContext.repoRoot,
|
|
983
|
+
workspaceStateRoot: workspaceContext.stateRoot,
|
|
984
|
+
mode: 'stale',
|
|
985
|
+
}).catch((error) => {
|
|
986
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
987
|
+
console.error(`[nexus-prime] Startup hygiene skipped: ${message}`);
|
|
988
|
+
});
|
|
989
|
+
}, 0);
|
|
985
990
|
nexus = createNexusPrime({
|
|
986
991
|
adapters: ['mcp'],
|
|
987
992
|
runtime: {
|
|
@@ -996,8 +1001,8 @@ program
|
|
|
996
1001
|
if (!adapterReady) {
|
|
997
1002
|
throw new Error('MCP adapter did not become ready before the startup deadline.');
|
|
998
1003
|
}
|
|
999
|
-
await startup;
|
|
1000
1004
|
flushPrimedMcpStdioInput();
|
|
1005
|
+
await startup;
|
|
1001
1006
|
console.error('Nexus Prime MCP Server running on stdio (standalone)');
|
|
1002
1007
|
console.error('Memory persistence: active (~/.nexus-prime/memory.db)');
|
|
1003
1008
|
const shutdown = async (signal) => {
|
|
@@ -258,10 +258,15 @@ function renderPyramidWidget() {
|
|
|
258
258
|
if (!container) return;
|
|
259
259
|
const h = S.memHealth;
|
|
260
260
|
if (!h) { container.innerHTML = ''; return; }
|
|
261
|
+
const tierCounts = h.tierCounts || {};
|
|
262
|
+
const count = (...values) => {
|
|
263
|
+
const positive = values.find(v => Number(v) > 0);
|
|
264
|
+
return positive != null ? Number(positive) : 0;
|
|
265
|
+
};
|
|
261
266
|
const counts = {
|
|
262
|
-
prefrontal: h.working
|
|
263
|
-
hippocampus: h.episodic
|
|
264
|
-
cortex: h.semantic
|
|
267
|
+
prefrontal: count(h.working, h.prefrontal, tierCounts.prefrontal),
|
|
268
|
+
hippocampus: count(h.episodic, h.hippocampus, tierCounts.hippocampus),
|
|
269
|
+
cortex: count(h.semantic, h.cortex, tierCounts.cortex),
|
|
265
270
|
};
|
|
266
271
|
renderPyramid(container, counts, _activeTier, tier => {
|
|
267
272
|
_activeTier = tier;
|
|
@@ -793,6 +798,14 @@ function renderOrchestrationPipeline() {
|
|
|
793
798
|
}
|
|
794
799
|
host.style.display = 'block';
|
|
795
800
|
const same = dec && cmp && dec.runId === cmp.runId;
|
|
801
|
+
const completionState = (completion) => {
|
|
802
|
+
const state = completion?.state || '';
|
|
803
|
+
const result = String(completion?.result || '');
|
|
804
|
+
if (state === 'failed' && /without a repository patch|no applicable diff|no selected mutation bindings|advisory/i.test(result)) {
|
|
805
|
+
return 'inspected';
|
|
806
|
+
}
|
|
807
|
+
return state;
|
|
808
|
+
};
|
|
796
809
|
const stateColor = (state) => state === 'merged' || state === 'inspected' ? 'var(--accent-good, #4ade80)'
|
|
797
810
|
: state === 'rolled_back' ? 'var(--accent-warn, #fbbf24)'
|
|
798
811
|
: state === 'failed' ? 'var(--accent-bad, #ff5f57)'
|
|
@@ -814,9 +827,10 @@ function renderOrchestrationPipeline() {
|
|
|
814
827
|
${chipList('skills', dec.skills)}
|
|
815
828
|
${chipList('files', dec.files)}
|
|
816
829
|
</div>` : '';
|
|
830
|
+
const cmpState = completionState(cmp);
|
|
817
831
|
const cmpBlock = cmp ? `
|
|
818
|
-
<div style="border-left:3px solid ${stateColor(
|
|
819
|
-
<div style="font-size:13px;font-weight:600;margin-bottom:4px">Completion · run ${esc((cmp.runId || '').slice(-8))} · <span style="color:${stateColor(
|
|
832
|
+
<div style="border-left:3px solid ${stateColor(cmpState)};padding:8px 12px">
|
|
833
|
+
<div style="font-size:13px;font-weight:600;margin-bottom:4px">Completion · run ${esc((cmp.runId || '').slice(-8))} · <span style="color:${stateColor(cmpState)}">${esc(cmpState || '')}</span></div>
|
|
820
834
|
<div style="font-size:12px;color:var(--muted);margin-bottom:6px">${esc(cmp.result || '')}</div>
|
|
821
835
|
<div>${chip('verified', `${cmp.verifiedWorkers ?? 0}/${cmp.totalWorkers ?? 0}`)}${chip('saved', `${fmtNum(cmp.savedTokens ?? 0)} t`)}${chip('compression', `${cmp.compressionPct ?? 0}%`)}${chip('duration', `${Math.round((cmp.durationMs ?? 0) / 100) / 10}s`)}</div>
|
|
822
836
|
</div>` : '';
|
|
@@ -14,6 +14,7 @@ const esc = s => s == null ? '' : String(s)
|
|
|
14
14
|
const MAX_EVENTS = 200;
|
|
15
15
|
const MAX_RESOLVED_MCP = 24;
|
|
16
16
|
const LONG_CALL_MS = 2000;
|
|
17
|
+
const STALE_CALL_MS = 5 * 60 * 1000;
|
|
17
18
|
const _events = [];
|
|
18
19
|
let _mounted = false;
|
|
19
20
|
let _filter = 'all';
|
|
@@ -23,6 +24,8 @@ let _toolCalls = 0;
|
|
|
23
24
|
let _activeTools = new Map();
|
|
24
25
|
// resolved MCP calls (newest-first, capped at MAX_RESOLVED_MCP)
|
|
25
26
|
let _resolvedMcp = [];
|
|
27
|
+
// tool name -> latest completion/shutdown timestamp, used to ignore older replayed starts
|
|
28
|
+
let _settledToolTimes = new Map();
|
|
26
29
|
// active-bar pulse timer
|
|
27
30
|
let _pulseTimer = null;
|
|
28
31
|
// toast queue
|
|
@@ -62,6 +65,89 @@ function humanMs(ms) {
|
|
|
62
65
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
function toolNameFromPayload(payload) {
|
|
69
|
+
return String(payload.toolName ?? payload.tool ?? payload.name ?? '').trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function eventMillis(evt, payload) {
|
|
73
|
+
const raw = evt?.time ?? payload?.time ?? payload?.ts ?? null;
|
|
74
|
+
const numeric = Number(raw);
|
|
75
|
+
if (Number.isFinite(numeric) && numeric > 0) return numeric;
|
|
76
|
+
const parsed = Date.parse(String(raw ?? ''));
|
|
77
|
+
return Number.isFinite(parsed) ? parsed : Date.now();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function capResolvedMcp() {
|
|
81
|
+
if (_resolvedMcp.length > MAX_RESOLVED_MCP) _resolvedMcp.length = MAX_RESOLVED_MCP;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rememberSettledTool(tool, eventTime) {
|
|
85
|
+
if (!tool) return;
|
|
86
|
+
const previous = _settledToolTimes.get(tool) ?? 0;
|
|
87
|
+
if (eventTime >= previous) _settledToolTimes.set(tool, eventTime);
|
|
88
|
+
if (_settledToolTimes.size > MAX_RESOLVED_MCP * 2) {
|
|
89
|
+
const oldest = [..._settledToolTimes.entries()].sort((a, b) => a[1] - b[1])[0]?.[0];
|
|
90
|
+
if (oldest) _settledToolTimes.delete(oldest);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wasSettledAfter(tool, eventTime) {
|
|
95
|
+
const settledAt = tool ? _settledToolTimes.get(tool) : null;
|
|
96
|
+
return Number.isFinite(settledAt) && Number.isFinite(eventTime) && eventTime <= settledAt;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function activeToolKey(trackKey, tool) {
|
|
100
|
+
if (trackKey && _activeTools.has(trackKey)) return trackKey;
|
|
101
|
+
if (tool && _activeTools.has(tool)) return tool;
|
|
102
|
+
if (tool) {
|
|
103
|
+
for (const [key, entry] of _activeTools.entries()) {
|
|
104
|
+
if (entry.tool === tool) return key;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!trackKey && !tool && _activeTools.size === 1) {
|
|
108
|
+
return _activeTools.keys().next().value;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function settleActiveTool(trackKey, tool, payload = {}, opts = {}) {
|
|
114
|
+
const key = activeToolKey(trackKey, tool);
|
|
115
|
+
if (!key) return false;
|
|
116
|
+
const entry = _activeTools.get(key);
|
|
117
|
+
if (!entry) return false;
|
|
118
|
+
const elapsed = Number(opts.durationMs ?? payload.durationMs ?? 0) || (Date.now() - entry.startedAt);
|
|
119
|
+
const status = String(payload.status ?? '').toLowerCase();
|
|
120
|
+
_resolvedMcp.unshift({
|
|
121
|
+
tool: entry.tool || tool || key,
|
|
122
|
+
durationMs: elapsed,
|
|
123
|
+
error: payload.error ?? (status && status !== 'ok' ? status : null),
|
|
124
|
+
stale: opts.stale === true,
|
|
125
|
+
});
|
|
126
|
+
rememberSettledTool(entry.tool || tool || key, Number(opts.eventTime ?? Date.now()));
|
|
127
|
+
capResolvedMcp();
|
|
128
|
+
_activeTools.delete(key);
|
|
129
|
+
if (entry.callId) _activeTools.delete(entry.callId);
|
|
130
|
+
if (entry.tool) _activeTools.delete(entry.tool);
|
|
131
|
+
if (trackKey) _activeTools.delete(trackKey);
|
|
132
|
+
if (tool) _activeTools.delete(tool);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function expireStaleActiveTools() {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
for (const [key, entry] of [..._activeTools.entries()]) {
|
|
139
|
+
if (now - entry.startedAt > STALE_CALL_MS) {
|
|
140
|
+
settleActiveTool(key, entry.tool, {}, { stale: true, durationMs: now - entry.startedAt });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function clearActiveToolsAsStale() {
|
|
146
|
+
for (const [key, entry] of [..._activeTools.entries()]) {
|
|
147
|
+
settleActiveTool(key, entry.tool, {}, { stale: true, durationMs: Date.now() - entry.startedAt });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
65
151
|
/* ── Mount ──────────────────────────────────────────────────────────────────── */
|
|
66
152
|
function mount() {
|
|
67
153
|
const el = $('runtime-view');
|
|
@@ -122,6 +208,7 @@ function mount() {
|
|
|
122
208
|
_totalTokensSaved = 0;
|
|
123
209
|
_toolCalls = 0;
|
|
124
210
|
_activeTools.clear();
|
|
211
|
+
_settledToolTimes.clear();
|
|
125
212
|
renderAll();
|
|
126
213
|
});
|
|
127
214
|
|
|
@@ -131,6 +218,7 @@ function mount() {
|
|
|
131
218
|
|
|
132
219
|
/* ── Render ─────────────────────────────────────────────────────────────────── */
|
|
133
220
|
function renderAll() {
|
|
221
|
+
expireStaleActiveTools();
|
|
134
222
|
renderKPIs();
|
|
135
223
|
renderMcpStrip();
|
|
136
224
|
renderFeed();
|
|
@@ -162,10 +250,12 @@ function renderMcpStrip() {
|
|
|
162
250
|
</div>`;
|
|
163
251
|
});
|
|
164
252
|
|
|
165
|
-
const resolved = _resolvedMcp.slice(0, MAX_RESOLVED_MCP).map(({ tool, durationMs, error }) => {
|
|
166
|
-
const cls = error ? 'rt-mcp-pill rt-mcp-err' : 'rt-mcp-pill rt-mcp-done';
|
|
253
|
+
const resolved = _resolvedMcp.slice(0, MAX_RESOLVED_MCP).map(({ tool, durationMs, error, stale }) => {
|
|
254
|
+
const cls = error ? 'rt-mcp-pill rt-mcp-err' : stale ? 'rt-mcp-pill rt-mcp-long' : 'rt-mcp-pill rt-mcp-done';
|
|
167
255
|
const badge = error
|
|
168
256
|
? `<span class="rt-mcp-badge rt-mcp-badge-err">err</span>`
|
|
257
|
+
: stale
|
|
258
|
+
? `<span class="rt-mcp-badge rt-mcp-badge-ok">stale</span>`
|
|
169
259
|
: `<span class="rt-mcp-badge rt-mcp-badge-ok">${humanMs(durationMs)}</span>`;
|
|
170
260
|
return `<div class="${cls}">${esc(tool)}${badge}</div>`;
|
|
171
261
|
});
|
|
@@ -182,6 +272,7 @@ function renderMcpStrip() {
|
|
|
182
272
|
function ensurePulseTimer() {
|
|
183
273
|
if (_pulseTimer || _activeTools.size === 0) return;
|
|
184
274
|
_pulseTimer = setInterval(() => {
|
|
275
|
+
expireStaleActiveTools();
|
|
185
276
|
if (_activeTools.size === 0) {
|
|
186
277
|
clearInterval(_pulseTimer);
|
|
187
278
|
_pulseTimer = null;
|
|
@@ -271,7 +362,8 @@ function showUpgradeNudge(msg, ctaUrl) {
|
|
|
271
362
|
export function ingestEvent(evt) {
|
|
272
363
|
const { type = '', payload = {} } = evt;
|
|
273
364
|
const category = categoryFor(type);
|
|
274
|
-
const tool =
|
|
365
|
+
const tool = toolNameFromPayload(payload);
|
|
366
|
+
const eventTime = eventMillis(evt, payload);
|
|
275
367
|
const tokensSaved = Number(payload.tokensSaved ?? evt.tokensSaved ?? 0);
|
|
276
368
|
const durationMs = Number(payload.durationMs ?? 0);
|
|
277
369
|
const phase = String(payload.phase ?? payload.stage ?? '');
|
|
@@ -280,18 +372,22 @@ export function ingestEvent(evt) {
|
|
|
280
372
|
const callId = String(payload.callId ?? '');
|
|
281
373
|
const trackKey = callId || tool;
|
|
282
374
|
if (type === 'mcp.call.start' && trackKey) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
375
|
+
if (!wasSettledAfter(tool, eventTime)) {
|
|
376
|
+
_activeTools.set(trackKey, { tool, startedAt: Date.now(), callId });
|
|
377
|
+
_toolCalls++;
|
|
378
|
+
ensurePulseTimer();
|
|
379
|
+
}
|
|
380
|
+
} else if (
|
|
381
|
+
type === 'mcp.call.complete' ||
|
|
382
|
+
type === 'mcp.handler.complete' ||
|
|
383
|
+
type === 'mcp.handler.failed' ||
|
|
384
|
+
(type === 'tool.invocation' && trackKey)
|
|
385
|
+
) {
|
|
386
|
+
if (!settleActiveTool(trackKey, tool, payload, { durationMs, eventTime })) {
|
|
387
|
+
rememberSettledTool(tool || trackKey, eventTime);
|
|
294
388
|
}
|
|
389
|
+
} else if (type === 'nexus.shutdown' || type === 'orchestrator.disposed') {
|
|
390
|
+
clearActiveToolsAsStale();
|
|
295
391
|
}
|
|
296
392
|
|
|
297
393
|
// Toast for license events
|
|
@@ -169,7 +169,15 @@ export class WorkflowRuntime {
|
|
|
169
169
|
workflow.steps.forEach((step) => {
|
|
170
170
|
if (step.command)
|
|
171
171
|
verify.add(step.command);
|
|
172
|
-
|
|
172
|
+
const verifierStep = step.role === 'verifier' || step.checkpoint === 'before-verify';
|
|
173
|
+
if (verifierStep) {
|
|
174
|
+
step.bindings
|
|
175
|
+
.filter((binding) => binding.type === 'run_command' && binding.command)
|
|
176
|
+
.forEach((binding) => verify.add(binding.command || ''));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
actions.push(...step.bindings);
|
|
180
|
+
}
|
|
173
181
|
});
|
|
174
182
|
}
|
|
175
183
|
return {
|
|
@@ -26,7 +26,11 @@ function mapOperative(row) {
|
|
|
26
26
|
}
|
|
27
27
|
export function insertOperative(db, input) {
|
|
28
28
|
const id = input.id ?? randomUUID();
|
|
29
|
-
const
|
|
29
|
+
const baseName = (input.name?.trim() || `operative-${id.slice(0, 8)}`).slice(0, 80);
|
|
30
|
+
let name = baseName;
|
|
31
|
+
for (let attempt = 2; getOperativeByName(db, name); attempt++) {
|
|
32
|
+
name = `${baseName}-${attempt}`;
|
|
33
|
+
}
|
|
30
34
|
const budgetScope = input.budgetScope ?? (input.strikeTeamId ? 'crew' : 'operative');
|
|
31
35
|
const budgetScopeId = input.budgetScopeId ?? input.strikeTeamId ?? id;
|
|
32
36
|
db.prepare(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-prime",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.21",
|
|
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",
|