nexus-prime 7.9.8 → 7.9.10
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/ide-compat.js +2 -8
- package/dist/agents/adapters/mcp/definitions.js +13 -0
- package/dist/agents/adapters/mcp/handlers/orchestration.d.ts +2 -0
- package/dist/agents/adapters/mcp/handlers/orchestration.js +34 -5
- package/dist/agents/adapters/mcp/handlers/runtime.js +36 -1
- package/dist/agents/adapters/mcp/helpers.js +1 -1
- package/dist/cli/install-wizard.js +5 -14
- package/dist/cli.js +22 -26
- package/dist/daemon/proxy.d.ts +1 -0
- package/dist/daemon/proxy.js +8 -4
- package/dist/dashboard/app/api.js +32 -6
- package/dist/dashboard/app/index.html +5 -0
- package/dist/dashboard/app/main.js +64 -19
- package/dist/dashboard/app/state.js +1 -1
- package/dist/dashboard/app/styles/memory.css +15 -1
- package/dist/dashboard/app/views/federation.js +9 -9
- package/dist/dashboard/app/views/governance.js +21 -6
- package/dist/dashboard/app/views/memory.js +56 -12
- package/dist/dashboard/app/views/trust.js +8 -4
- package/dist/dashboard/routes/memory.js +2 -0
- package/dist/dashboard/routes/runtime.js +12 -2
- package/dist/dashboard/selectors/assets-selector.js +2 -0
- package/dist/dashboard/server.js +15 -7
- package/dist/dashboard/types.d.ts +1 -0
- package/dist/engines/client-bootstrap.js +18 -24
- package/dist/engines/mcp-entrypoint.d.ts +12 -0
- package/dist/engines/mcp-entrypoint.js +33 -0
- package/dist/engines/orchestrator.js +36 -8
- package/dist/engines/pattern-registry.d.ts +12 -0
- package/dist/engines/pattern-registry.js +65 -0
- package/dist/engines/specialist-roster.js +16 -8
- package/dist/engines/user-workflow-trace.d.ts +47 -0
- package/dist/engines/user-workflow-trace.js +281 -0
- package/dist/phantom/runtime.d.ts +4 -0
- package/dist/phantom/runtime.js +31 -1
- package/package.json +1 -1
|
@@ -7,17 +7,11 @@
|
|
|
7
7
|
import { existsSync } from 'fs';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { dirname, join, resolve } from 'path';
|
|
10
|
+
import { buildNexusMcpServerConfig } from '../../engines/mcp-entrypoint.js';
|
|
10
11
|
let callerIDECache;
|
|
11
12
|
/** Nexus Prime MCP server entry (stdio transport). */
|
|
12
13
|
function nexusMcpServerEntry(workspaceRoot) {
|
|
13
|
-
return
|
|
14
|
-
command: 'node',
|
|
15
|
-
args: [join(workspaceRoot, 'node_modules', 'nexus-prime', 'dist', 'cli.js'), 'mcp'],
|
|
16
|
-
env: {
|
|
17
|
-
NEXUS_MCP_TOOL_PROFILE: 'autonomous',
|
|
18
|
-
NEXUS_WORKSPACE_ROOT: workspaceRoot,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
14
|
+
return buildNexusMcpServerConfig(workspaceRoot);
|
|
21
15
|
}
|
|
22
16
|
function hasAnyEnvPrefix(prefix) {
|
|
23
17
|
return Object.keys(process.env).some((key) => key.startsWith(prefix));
|
|
@@ -15,6 +15,7 @@ export const FIRST_CLASS_TOOLS = new Set([
|
|
|
15
15
|
'nexus_search',
|
|
16
16
|
'nexus_runtime_health',
|
|
17
17
|
'nexus_run_status',
|
|
18
|
+
'nexus_user_workflow_trace',
|
|
18
19
|
'kernel_run_step',
|
|
19
20
|
'kernel_run_verify',
|
|
20
21
|
'kernel_run_merge',
|
|
@@ -881,6 +882,18 @@ export function buildMcpToolDefinitions() {
|
|
|
881
882
|
required: ['runId'],
|
|
882
883
|
},
|
|
883
884
|
},
|
|
885
|
+
{
|
|
886
|
+
name: 'nexus_user_workflow_trace',
|
|
887
|
+
description: 'Return the local user-agent workflow trace for a run: user goal, agent actions, context, verification, failure signals, and next actions.',
|
|
888
|
+
inputSchema: {
|
|
889
|
+
type: 'object',
|
|
890
|
+
properties: {
|
|
891
|
+
runId: { type: 'string', description: 'Execution run ID' },
|
|
892
|
+
format: { type: 'string', enum: ['text', 'json'], description: 'Response format' },
|
|
893
|
+
},
|
|
894
|
+
required: ['runId'],
|
|
895
|
+
},
|
|
896
|
+
},
|
|
884
897
|
// ── Darwin Loop ──────────────────────────────────────────────────
|
|
885
898
|
{
|
|
886
899
|
name: 'nexus_darwin_propose',
|
|
@@ -10,5 +10,7 @@ type McpResult = {
|
|
|
10
10
|
text: string;
|
|
11
11
|
}>;
|
|
12
12
|
};
|
|
13
|
+
export declare function extractSkillSelectorsFromPrompt(prompt: string): string[];
|
|
14
|
+
export declare function inferSpawnWorkersIntent(actions: unknown[]): 'plan' | 'mutate';
|
|
13
15
|
export declare function handleOrchestrationGroup(toolName: string, hctx: McpHandlerCtx, request: any, args: Record<string, unknown>, ctx?: any): Promise<McpResult | undefined>;
|
|
14
16
|
export {};
|
|
@@ -20,6 +20,25 @@ import { getSharedTelemetry } from '../../../../engines/telemetry-remote.js';
|
|
|
20
20
|
import { requireRuntime } from '../util/require-runtime.js';
|
|
21
21
|
import { ensureCrGraphBuilt } from '../../../../engines/code-review-graph-client.js';
|
|
22
22
|
import { recordFirstBootstrap } from '../../../../engines/telemetry.js';
|
|
23
|
+
export function extractSkillSelectorsFromPrompt(prompt) {
|
|
24
|
+
const selectors = new Set();
|
|
25
|
+
const add = (value) => {
|
|
26
|
+
const cleaned = value?.trim().replace(/^[$@]+/, '');
|
|
27
|
+
if (cleaned && /^[a-z0-9][a-z0-9._-]{1,120}$/i.test(cleaned)) {
|
|
28
|
+
selectors.add(cleaned);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
for (const match of prompt.matchAll(/\[[$@]?([a-z0-9][a-z0-9._-]{1,120})\]\([^)]*\/\.agents?\/skills\/[^)]*\/SKILL\.md\)/gi)) {
|
|
32
|
+
add(match[1]);
|
|
33
|
+
}
|
|
34
|
+
for (const match of prompt.matchAll(/\/\.agents?\/skills\/([^/\s)]+)\/SKILL\.md/gi)) {
|
|
35
|
+
add(match[1]);
|
|
36
|
+
}
|
|
37
|
+
return [...selectors];
|
|
38
|
+
}
|
|
39
|
+
export function inferSpawnWorkersIntent(actions) {
|
|
40
|
+
return actions.length > 0 ? 'mutate' : 'plan';
|
|
41
|
+
}
|
|
23
42
|
export async function handleOrchestrationGroup(toolName, hctx, request, args, ctx) {
|
|
24
43
|
const runtimeError = requireRuntime(hctx);
|
|
25
44
|
if (runtimeError)
|
|
@@ -315,9 +334,11 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
315
334
|
const workers = request.params.arguments?.workers == null
|
|
316
335
|
? undefined
|
|
317
336
|
: Number(request.params.arguments.workers);
|
|
318
|
-
const
|
|
337
|
+
const explicitSkills = Array.isArray(request.params.arguments?.skills)
|
|
319
338
|
? request.params.arguments.skills.map(String)
|
|
320
|
-
:
|
|
339
|
+
: [];
|
|
340
|
+
const promptSkills = extractSkillSelectorsFromPrompt(prompt);
|
|
341
|
+
const skills = [...new Set([...explicitSkills, ...promptSkills])];
|
|
321
342
|
const workflows = Array.isArray(request.params.arguments?.workflows)
|
|
322
343
|
? request.params.arguments.workflows.map(String)
|
|
323
344
|
: undefined;
|
|
@@ -374,7 +395,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
374
395
|
const execution = await hctx.nexusRef.orchestrate(prompt, applyExecutionPreset({
|
|
375
396
|
files,
|
|
376
397
|
workers,
|
|
377
|
-
skillNames: skills,
|
|
398
|
+
skillNames: skills.length > 0 ? skills : undefined,
|
|
378
399
|
workflowSelectors: workflows,
|
|
379
400
|
hookSelectors: hooks,
|
|
380
401
|
automationSelectors: automations,
|
|
@@ -769,6 +790,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
769
790
|
? String(request.params.arguments.executionPreset)
|
|
770
791
|
: undefined;
|
|
771
792
|
const preset = resolveExecutionPreset(executionPreset);
|
|
793
|
+
const inferredIntent = inferSpawnWorkersIntent(actions);
|
|
772
794
|
// Surface worker-plan intent on the SSE feed without persisting it as a memory.
|
|
773
795
|
try {
|
|
774
796
|
nexusEventBus.emit('memory.snapshot', {
|
|
@@ -796,11 +818,18 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
796
818
|
backendSelectors: { memoryBackend, compressionBackend, dslCompiler },
|
|
797
819
|
backendMode,
|
|
798
820
|
optimizationProfile,
|
|
821
|
+
intent: inferredIntent,
|
|
799
822
|
executionMode: 'manual-low-level',
|
|
800
823
|
manualOverrides: ['nexus_spawn_workers'],
|
|
801
824
|
}, executionPreset));
|
|
802
825
|
const verifiedWorkers = execution.workerResults.filter(result => result.verified).length;
|
|
803
826
|
const modifiedFiles = execution.workerResults.reduce((sum, result) => sum + result.modifiedFiles.length, 0);
|
|
827
|
+
const noDiffInspected = execution.state === 'inspected'
|
|
828
|
+
&& modifiedFiles === 0
|
|
829
|
+
&& !execution.workerResults.some((result) => result.diff?.trim());
|
|
830
|
+
const displayedDecision = noDiffInspected
|
|
831
|
+
? 'no-applicable-diff'
|
|
832
|
+
: execution.finalDecision?.action ?? 'none';
|
|
804
833
|
execution.activeSkills.forEach(skill => {
|
|
805
834
|
hctx.sessionDNA.recordSkill(skill.name);
|
|
806
835
|
if (skill.scope === 'global' || skill.rolloutStatus === 'promoted') {
|
|
@@ -826,7 +855,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
826
855
|
`Run: ${execution.runId.padEnd(28, ' ')} State: ${execution.state.padEnd(18, ' ')}`,
|
|
827
856
|
`Workers: ${execution.workerResults.length.toString().padEnd(5, ' ')} Verified: ${verifiedWorkers.toString().padEnd(10, ' ')} Files: ${String(modifiedFiles).padEnd(12, ' ')}`,
|
|
828
857
|
`${`Preset: ${preset?.name ?? 'manual'}`.padEnd(61, ' ')}`,
|
|
829
|
-
`Decision: ${
|
|
858
|
+
`Decision: ${displayedDecision.padEnd(52, ' ')}`
|
|
830
859
|
], execution.state === 'merged' || execution.state === 'inspected' ? '32' : execution.state === 'rolled_back' ? '33' : '31');
|
|
831
860
|
return {
|
|
832
861
|
content: [{
|
|
@@ -841,7 +870,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
841
870
|
preset ? `Preset: ${preset.name} (${preset.id})` : null,
|
|
842
871
|
`Verified Workers: ${verifiedWorkers}`,
|
|
843
872
|
`Modified Files: ${modifiedFiles}`,
|
|
844
|
-
`Decision: ${
|
|
873
|
+
`Decision: ${displayedDecision}`,
|
|
845
874
|
`Recommended Strategy: ${execution.finalDecision?.recommendedStrategy ?? 'n/a'}`,
|
|
846
875
|
`Planner: ${execution.plannerResult?.summary ?? 'n/a'}`,
|
|
847
876
|
`Crew: ${execution.plannerResult?.selectedCrew?.name ?? 'n/a'}`,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* federation_status, run_status.
|
|
5
5
|
* Extracted from mcp.ts (Phase 3 split).
|
|
6
6
|
*/
|
|
7
|
-
import { formatBullets, formatJsonDetails, buildAutoMemorySummary, } from '../helpers.js';
|
|
7
|
+
import { formatBullets, formatJsonDetails, truncateText, buildAutoMemorySummary, } from '../helpers.js';
|
|
8
8
|
import { nexusEventBus } from '../../../../engines/event-bus.js';
|
|
9
9
|
import { getSharedLicenseManager, snapshotPCU, formatPCUStatus } from '../../../../licensing/index.js';
|
|
10
10
|
import { TokenAnalyticsEngine } from '../../../../engines/token-analytics.js';
|
|
@@ -13,6 +13,7 @@ import { requireRuntime } from '../util/require-runtime.js';
|
|
|
13
13
|
import { getAsyncGate } from '../async-gate.js';
|
|
14
14
|
import { getRun as getOrchRun } from '../../../../engines/orchestrator/store.js';
|
|
15
15
|
import { nxlResult } from '../../../../nxl/index.js';
|
|
16
|
+
import { summarizeUserWorkflowTrace } from '../../../../engines/user-workflow-trace.js';
|
|
16
17
|
export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
17
18
|
const runtimeError = requireRuntime(hctx);
|
|
18
19
|
if (runtimeError)
|
|
@@ -226,6 +227,40 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
|
226
227
|
}],
|
|
227
228
|
};
|
|
228
229
|
}
|
|
230
|
+
case 'nexus_user_workflow_trace': {
|
|
231
|
+
const runId = String(request.params.arguments?.runId ?? '');
|
|
232
|
+
const fmt = String(request.params.arguments?.format ?? 'text');
|
|
233
|
+
const trace = await hctx.getRuntime().getUserWorkflowTrace?.(runId);
|
|
234
|
+
if (!trace) {
|
|
235
|
+
return { content: [{ type: 'text', text: `User workflow trace not found: ${runId}` }] };
|
|
236
|
+
}
|
|
237
|
+
if (fmt === 'json') {
|
|
238
|
+
return { content: [{ type: 'text', text: JSON.stringify(trace, null, 2) }] };
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
content: [{
|
|
242
|
+
type: 'text',
|
|
243
|
+
text: [
|
|
244
|
+
summarizeUserWorkflowTrace(trace),
|
|
245
|
+
'',
|
|
246
|
+
formatBullets([
|
|
247
|
+
`Run: ${trace.runId}`,
|
|
248
|
+
`Goal: ${truncateText(trace.goal, 120)}`,
|
|
249
|
+
`Actions: ${trace.userJourney.actions.length ? trace.userJourney.actions.slice(0, 5).join(' | ') : 'none recorded'}`,
|
|
250
|
+
`Files: ${trace.userJourney.selectedFiles.length ? trace.userJourney.selectedFiles.slice(0, 5).join(', ') : 'none selected'}`,
|
|
251
|
+
`Agents: ${trace.userJourney.selectedSpecialists.length ? trace.userJourney.selectedSpecialists.slice(0, 5).join(', ') : 'none selected'}`,
|
|
252
|
+
`Signals: ${trace.signals.length ? trace.signals.map(signal => `${signal.severity}:${signal.code}`).slice(0, 8).join(', ') : 'none'}`,
|
|
253
|
+
]),
|
|
254
|
+
trace.signals.length
|
|
255
|
+
? `Failure signals\n${formatBullets(trace.signals.map(signal => `${signal.code}: ${signal.summary}`))}`
|
|
256
|
+
: '',
|
|
257
|
+
trace.nextActions.length
|
|
258
|
+
? `Next actions\n${formatBullets(trace.nextActions)}`
|
|
259
|
+
: '',
|
|
260
|
+
].filter(Boolean).join('\n\n'),
|
|
261
|
+
}],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
229
264
|
case 'nexus_run_status': {
|
|
230
265
|
const runId = String(request.params.arguments?.runId ?? '');
|
|
231
266
|
const fmt = String(request.params.arguments?.format ?? 'text');
|
|
@@ -190,7 +190,7 @@ export function isReadOnlyMcpTool(toolName) {
|
|
|
190
190
|
'nexus_list_', 'nexus_describe_', 'nexus_runtime_health',
|
|
191
191
|
'nexus_doctor', 'nexus_token_report', 'nexus_recall_memory',
|
|
192
192
|
'nexus_temporal_query', 'nexus_memory_stats', 'nexus_federation_status',
|
|
193
|
-
'nexus_license_usage', 'nexus_run_status', 'nexus_selection_ledger',
|
|
193
|
+
'nexus_license_usage', 'nexus_run_status', 'nexus_user_workflow_trace', 'nexus_selection_ledger',
|
|
194
194
|
'nexus_operative_journal',
|
|
195
195
|
];
|
|
196
196
|
return skipPrefixes.some(p => toolName.startsWith(p));
|
|
@@ -11,6 +11,7 @@ import { dirname, join, resolve } from 'path';
|
|
|
11
11
|
import { detectInstalledIDEs, getMcpConfigForIDE } from '../agents/adapters/ide-compat.js';
|
|
12
12
|
import { DEFAULT_DAEMON_READY_TIMEOUT_MS, ensureDaemonReady } from '../daemon/client.js';
|
|
13
13
|
import { getSetupDefinition, installSetup } from '../engines/client-bootstrap.js';
|
|
14
|
+
import { buildNexusMcpServerConfig } from '../engines/mcp-entrypoint.js';
|
|
14
15
|
import { resolveNexusStateDir } from '../engines/runtime-registry.js';
|
|
15
16
|
import { resolveWorkspaceContext } from '../engines/workspace-resolver.js';
|
|
16
17
|
import { runPostinstallCleanup } from '../postinstall/cleanup.js';
|
|
@@ -136,14 +137,7 @@ export async function configureIDE(ide, opts = {}) {
|
|
|
136
137
|
}
|
|
137
138
|
/** Nexus Prime MCP server entry used in workspace-local config writes. */
|
|
138
139
|
function _nexusMcpEntry(workspaceRoot) {
|
|
139
|
-
return
|
|
140
|
-
command: 'node',
|
|
141
|
-
args: [join(workspaceRoot, 'node_modules', 'nexus-prime', 'dist', 'cli.js'), 'mcp'],
|
|
142
|
-
env: {
|
|
143
|
-
NEXUS_MCP_TOOL_PROFILE: 'autonomous',
|
|
144
|
-
NEXUS_WORKSPACE_ROOT: workspaceRoot,
|
|
145
|
-
},
|
|
146
|
-
};
|
|
140
|
+
return buildNexusMcpServerConfig(workspaceRoot);
|
|
147
141
|
}
|
|
148
142
|
/**
|
|
149
143
|
* Write Claude Code PreToolUse/PostToolUse/UserPromptSubmit/Stop hooks to
|
|
@@ -379,18 +373,15 @@ function mergeIntoExistingConfig(configPath, newContent, ide) {
|
|
|
379
373
|
}
|
|
380
374
|
/** Print how to add nexus-prime manually to an MCP client. */
|
|
381
375
|
export function printManualMcpInstructions() {
|
|
376
|
+
const server = buildNexusMcpServerConfig(process.cwd());
|
|
382
377
|
const snippet = JSON.stringify({
|
|
383
378
|
mcpServers: {
|
|
384
|
-
'nexus-prime':
|
|
385
|
-
command: 'node',
|
|
386
|
-
args: ['node_modules/nexus-prime/dist/cli.js', 'mcp'],
|
|
387
|
-
env: {},
|
|
388
|
-
},
|
|
379
|
+
'nexus-prime': server,
|
|
389
380
|
},
|
|
390
381
|
}, null, 2);
|
|
391
382
|
process.stdout.write('\n Add this to your IDE\'s MCP configuration:\n\n');
|
|
392
383
|
process.stdout.write(snippet.split('\n').map(l => ` ${l}`).join('\n') + '\n\n');
|
|
393
|
-
process.stdout.write(' Or run:
|
|
384
|
+
process.stdout.write(' Or run: nexus-prime setup\n\n');
|
|
394
385
|
}
|
|
395
386
|
/** Entry point for CLI: nexus-prime install / setup auto */
|
|
396
387
|
export async function cliSetup(opts = []) {
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { InstructionGateway } from './engines/instruction-gateway.js';
|
|
|
18
18
|
import { ensureBootstrap, collectBootstrapManifest, validateTargetPath, resolveCodexConfigPath, renderCodexMcpTomlConfig, writeCodexMcpConfig, hasExpectedCodexConfig, } from './engines/client-bootstrap.js';
|
|
19
19
|
import { resolveNexusStateDir } from './engines/runtime-registry.js';
|
|
20
20
|
import { runStartupHygiene } from './engines/runtime-hygiene.js';
|
|
21
|
+
import { buildNexusMcpServerConfig, isStableNexusMcpServerConfig } from './engines/mcp-entrypoint.js';
|
|
21
22
|
import { SessionDNAManager } from './engines/session-dna.js';
|
|
22
23
|
import { nexusEventBus } from './engines/event-bus.js';
|
|
23
24
|
import { buildRuntimeSetupCommand } from './cli-setup.js';
|
|
@@ -189,14 +190,7 @@ function readJson(targetPath) {
|
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
function buildStandardMcpServerConfig(workspaceRoot) {
|
|
192
|
-
return
|
|
193
|
-
command: 'npx',
|
|
194
|
-
args: ['-y', 'nexus-prime', 'mcp'],
|
|
195
|
-
env: {
|
|
196
|
-
NEXUS_MCP_TOOL_PROFILE: 'autonomous',
|
|
197
|
-
...(workspaceRoot ? { NEXUS_WORKSPACE_ROOT: workspaceRoot } : {}),
|
|
198
|
-
}
|
|
199
|
-
};
|
|
193
|
+
return buildNexusMcpServerConfig(workspaceRoot);
|
|
200
194
|
}
|
|
201
195
|
function writeStandardMcpConfig(targetPath, workspaceRoot) {
|
|
202
196
|
const existing = readJson(targetPath);
|
|
@@ -207,13 +201,13 @@ function writeStandardMcpConfig(targetPath, workspaceRoot) {
|
|
|
207
201
|
}
|
|
208
202
|
function writeOpencodeConfig(targetPath, workspaceRoot) {
|
|
209
203
|
const existing = readJson(targetPath);
|
|
204
|
+
const mcpConfig = buildStandardMcpServerConfig(workspaceRoot);
|
|
210
205
|
const server = {
|
|
211
206
|
type: 'local',
|
|
212
|
-
command:
|
|
213
|
-
args:
|
|
207
|
+
command: mcpConfig.command,
|
|
208
|
+
args: mcpConfig.args,
|
|
214
209
|
environment: {
|
|
215
|
-
|
|
216
|
-
...(workspaceRoot ? { NEXUS_WORKSPACE_ROOT: workspaceRoot } : {}),
|
|
210
|
+
...mcpConfig.env,
|
|
217
211
|
}
|
|
218
212
|
};
|
|
219
213
|
existing.mcp = existing.mcp ?? {};
|
|
@@ -251,21 +245,19 @@ function mergeCodexAgentsContent(existingContent, content) {
|
|
|
251
245
|
const endIndex = existing.indexOf(CODEX_MANAGED_END);
|
|
252
246
|
if (startIndex >= 0 && endIndex > startIndex) {
|
|
253
247
|
const before = existing.slice(0, startIndex).trimEnd();
|
|
254
|
-
const after = existing.slice(endIndex + CODEX_MANAGED_END.length).
|
|
248
|
+
const after = existing.slice(endIndex + CODEX_MANAGED_END.length).trim();
|
|
255
249
|
return [
|
|
256
250
|
before,
|
|
257
251
|
before ? '' : undefined,
|
|
258
252
|
managedBlock,
|
|
259
253
|
after ? '' : undefined,
|
|
260
254
|
after,
|
|
261
|
-
'',
|
|
262
255
|
].filter((value) => value !== undefined).join('\n');
|
|
263
256
|
}
|
|
264
257
|
return [
|
|
265
258
|
existing.trimEnd(),
|
|
266
259
|
'',
|
|
267
260
|
managedBlock,
|
|
268
|
-
'',
|
|
269
261
|
].join('\n');
|
|
270
262
|
}
|
|
271
263
|
function hasCurrentCodexManagedBlock(targetPath, content) {
|
|
@@ -301,17 +293,16 @@ function mergeClaudeContent(existingContent, content) {
|
|
|
301
293
|
const endIndex = existing.indexOf(CLAUDE_MANAGED_END);
|
|
302
294
|
if (startIndex >= 0 && endIndex > startIndex) {
|
|
303
295
|
const before = existing.slice(0, startIndex).trimEnd();
|
|
304
|
-
const after = existing.slice(endIndex + CLAUDE_MANAGED_END.length).
|
|
296
|
+
const after = existing.slice(endIndex + CLAUDE_MANAGED_END.length).trim();
|
|
305
297
|
return [
|
|
306
298
|
before,
|
|
307
299
|
before ? '' : undefined,
|
|
308
300
|
managedBlock,
|
|
309
301
|
after ? '' : undefined,
|
|
310
302
|
after,
|
|
311
|
-
'',
|
|
312
303
|
].filter((value) => value !== undefined).join('\n');
|
|
313
304
|
}
|
|
314
|
-
return [existing.trimEnd(), '', managedBlock
|
|
305
|
+
return [existing.trimEnd(), '', managedBlock].join('\n');
|
|
315
306
|
}
|
|
316
307
|
function hasCurrentClaudeManagedBlock(targetPath, content) {
|
|
317
308
|
if (!existsSync(targetPath))
|
|
@@ -346,17 +337,16 @@ function mergeClinerules(existingContent, content) {
|
|
|
346
337
|
const endIndex = existing.indexOf(CLINE_MANAGED_END);
|
|
347
338
|
if (startIndex >= 0 && endIndex > startIndex) {
|
|
348
339
|
const before = existing.slice(0, startIndex).trimEnd();
|
|
349
|
-
const after = existing.slice(endIndex + CLINE_MANAGED_END.length).
|
|
340
|
+
const after = existing.slice(endIndex + CLINE_MANAGED_END.length).trim();
|
|
350
341
|
return [
|
|
351
342
|
before,
|
|
352
343
|
before ? '' : undefined,
|
|
353
344
|
managedBlock,
|
|
354
345
|
after ? '' : undefined,
|
|
355
346
|
after,
|
|
356
|
-
'',
|
|
357
347
|
].filter((value) => value !== undefined).join('\n');
|
|
358
348
|
}
|
|
359
|
-
return [existing.trimEnd(), '', managedBlock
|
|
349
|
+
return [existing.trimEnd(), '', managedBlock].join('\n');
|
|
360
350
|
}
|
|
361
351
|
function hasCurrentClineManagedBlock(targetPath, content) {
|
|
362
352
|
if (!existsSync(targetPath))
|
|
@@ -591,12 +581,10 @@ function hasExpectedConfig(definition) {
|
|
|
591
581
|
const parsed = JSON.parse(readFileSync(definition.configPath, 'utf8'));
|
|
592
582
|
if (definition.id === 'opencode') {
|
|
593
583
|
const server = parsed?.mcp?.['nexus-prime'];
|
|
594
|
-
return Boolean(server
|
|
595
|
-
&& server?.environment?.NEXUS_MCP_TOOL_PROFILE === 'autonomous');
|
|
584
|
+
return Boolean(server && isStableNexusMcpServerConfig(server, 'environment'));
|
|
596
585
|
}
|
|
597
586
|
const server = parsed?.mcpServers?.['nexus-prime'];
|
|
598
|
-
return Boolean(server
|
|
599
|
-
&& server?.env?.NEXUS_MCP_TOOL_PROFILE === 'autonomous');
|
|
587
|
+
return Boolean(server && isStableNexusMcpServerConfig(server));
|
|
600
588
|
}
|
|
601
589
|
catch {
|
|
602
590
|
return false;
|
|
@@ -825,7 +813,8 @@ program
|
|
|
825
813
|
const workspaceContext = resolveWorkspaceContext({ workspaceRoot: getWorkspaceRoot() });
|
|
826
814
|
const caller = detectCallerIDE();
|
|
827
815
|
const marker = readSetupMarker();
|
|
828
|
-
|
|
816
|
+
const autoConfigEnabled = process.env.NEXUS_MCP_AUTO_CONFIG === '1';
|
|
817
|
+
if (caller && autoConfigEnabled) {
|
|
829
818
|
const isConfigured = marker?.configuredIDEs?.includes(caller) ?? false;
|
|
830
819
|
const storedHash = marker?.writtenHash?.[caller];
|
|
831
820
|
if (isConfigured && storedHash !== undefined) {
|
|
@@ -879,6 +868,10 @@ program
|
|
|
879
868
|
}
|
|
880
869
|
}
|
|
881
870
|
// Try daemon-backed proxy first unless --standalone is passed
|
|
871
|
+
const parsedDaemonTimeout = Number(process.env.NEXUS_MCP_DAEMON_TIMEOUT_MS ?? 5000);
|
|
872
|
+
const mcpDaemonTimeoutMs = Number.isFinite(parsedDaemonTimeout)
|
|
873
|
+
? Math.max(1000, Math.min(parsedDaemonTimeout, 15000))
|
|
874
|
+
: 5000;
|
|
882
875
|
if (!options.standalone) {
|
|
883
876
|
try {
|
|
884
877
|
const status = await getDaemonStatus(workspaceContext);
|
|
@@ -886,6 +879,7 @@ program
|
|
|
886
879
|
console.error('Connecting to Nexus Prime daemon...');
|
|
887
880
|
await startDaemonBackedMcpProxy(workspaceContext, {
|
|
888
881
|
entrypoint: getCliEntrypoint(),
|
|
882
|
+
timeoutMs: mcpDaemonTimeoutMs,
|
|
889
883
|
});
|
|
890
884
|
return;
|
|
891
885
|
}
|
|
@@ -900,9 +894,11 @@ program
|
|
|
900
894
|
try {
|
|
901
895
|
await ensureDaemonReady(workspaceContext, {
|
|
902
896
|
entrypoint: getCliEntrypoint(),
|
|
897
|
+
timeoutMs: mcpDaemonTimeoutMs,
|
|
903
898
|
});
|
|
904
899
|
await startDaemonBackedMcpProxy(workspaceContext, {
|
|
905
900
|
entrypoint: getCliEntrypoint(),
|
|
901
|
+
timeoutMs: mcpDaemonTimeoutMs,
|
|
906
902
|
});
|
|
907
903
|
return;
|
|
908
904
|
}
|
package/dist/daemon/proxy.d.ts
CHANGED
package/dist/daemon/proxy.js
CHANGED
|
@@ -3,12 +3,15 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { daemonRequest, ensureDaemonReady, } from './client.js';
|
|
6
|
-
async function withDaemonRetry(workspace, daemonRef,
|
|
6
|
+
async function withDaemonRetry(workspace, daemonRef, options, operation) {
|
|
7
7
|
try {
|
|
8
8
|
return await operation(daemonRef.current);
|
|
9
9
|
}
|
|
10
10
|
catch {
|
|
11
|
-
daemonRef.current = await ensureDaemonReady(workspace, {
|
|
11
|
+
daemonRef.current = await ensureDaemonReady(workspace, {
|
|
12
|
+
entrypoint: options.entrypoint,
|
|
13
|
+
timeoutMs: options.timeoutMs,
|
|
14
|
+
});
|
|
12
15
|
return operation(daemonRef.current);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
@@ -16,18 +19,19 @@ export async function startDaemonBackedMcpProxy(workspace, options = {}) {
|
|
|
16
19
|
const daemonRef = {
|
|
17
20
|
current: await ensureDaemonReady(workspace, {
|
|
18
21
|
entrypoint: options.entrypoint,
|
|
22
|
+
timeoutMs: options.timeoutMs,
|
|
19
23
|
}),
|
|
20
24
|
};
|
|
21
25
|
const sessionId = randomUUID();
|
|
22
26
|
const server = new Server({ name: 'nexus-prime-mcp-proxy', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
23
27
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
24
|
-
const payload = await withDaemonRetry(workspace, daemonRef, options
|
|
28
|
+
const payload = await withDaemonRetry(workspace, daemonRef, options, (record) => daemonRequest(record, '/rpc/list-tools', { sessionId }));
|
|
25
29
|
return {
|
|
26
30
|
tools: payload.tools,
|
|
27
31
|
};
|
|
28
32
|
});
|
|
29
33
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
30
|
-
const payload = await withDaemonRetry(workspace, daemonRef, options
|
|
34
|
+
const payload = await withDaemonRetry(workspace, daemonRef, options, (record) => daemonRequest(record, '/rpc/call-tool', {
|
|
31
35
|
sessionId,
|
|
32
36
|
name: request.params?.name,
|
|
33
37
|
arguments: request.params?.arguments ?? {},
|
|
@@ -9,21 +9,35 @@ import { CACHE_TTL, S, bus } from './state.js';
|
|
|
9
9
|
|
|
10
10
|
const _cache = new Map();
|
|
11
11
|
|
|
12
|
+
function _scopedUrl(url) {
|
|
13
|
+
if (typeof url !== 'string' || !url.startsWith('/api/')) return url;
|
|
14
|
+
const runtimeId = S.scope?.runtimeId;
|
|
15
|
+
if (!runtimeId) return url;
|
|
16
|
+
try {
|
|
17
|
+
const scoped = new URL(url, window.location.origin);
|
|
18
|
+
if (!scoped.searchParams.has('runtimeId')) scoped.searchParams.set('runtimeId', runtimeId);
|
|
19
|
+
return scoped.pathname + scoped.search + scoped.hash;
|
|
20
|
+
} catch {
|
|
21
|
+
return url;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
/**
|
|
13
26
|
* GET with SWR semantics: return cached data immediately (if within TTL),
|
|
14
27
|
* then kick off a background refresh. Callers get the fast synchronous hit
|
|
15
28
|
* for p50 <5ms; the next render cycle gets the fresh value.
|
|
16
29
|
*/
|
|
17
30
|
export async function api(url, ttl = CACHE_TTL) {
|
|
18
|
-
const
|
|
31
|
+
const requestUrl = _scopedUrl(url);
|
|
32
|
+
const hit = _cache.get(requestUrl);
|
|
19
33
|
const fresh = !hit || (Date.now() - hit.ts >= ttl);
|
|
20
34
|
|
|
21
35
|
// Return stale data synchronously while refresh fires in background.
|
|
22
|
-
const refreshPromise = fresh ? _fetch(
|
|
36
|
+
const refreshPromise = fresh ? _fetch(requestUrl) : Promise.resolve(hit.data);
|
|
23
37
|
|
|
24
38
|
if (hit && !fresh) {
|
|
25
39
|
// Background refresh — don't await, just schedule.
|
|
26
|
-
_fetch(
|
|
40
|
+
_fetch(requestUrl).catch(() => {});
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
return refreshPromise;
|
|
@@ -52,9 +66,10 @@ async function _fetch(url) {
|
|
|
52
66
|
*/
|
|
53
67
|
export async function post(url, body, opts = {}) {
|
|
54
68
|
const { optimistic } = opts;
|
|
69
|
+
const requestUrl = _scopedUrl(url);
|
|
55
70
|
// If an optimistic record is provided, return it right away and let the
|
|
56
71
|
// caller reconcile once the real response arrives.
|
|
57
|
-
const req = fetch(
|
|
72
|
+
const req = fetch(requestUrl, {
|
|
58
73
|
method: 'POST',
|
|
59
74
|
headers: { 'Content-Type': 'application/json' },
|
|
60
75
|
body: JSON.stringify(body),
|
|
@@ -77,7 +92,7 @@ export async function post(url, body, opts = {}) {
|
|
|
77
92
|
if (optimistic !== undefined) {
|
|
78
93
|
// Return immediately with optimistic data; the caller can await the
|
|
79
94
|
// real result separately if it needs to reconcile.
|
|
80
|
-
req.then((real) => { if (!real.ok) bus.emit('post:optimistic-fail', { url, error: real.error }); });
|
|
95
|
+
req.then((real) => { if (!real.ok) bus.emit('post:optimistic-fail', { url: requestUrl, error: real.error }); });
|
|
81
96
|
return { ok: true, data: optimistic, error: null, status: 202, realResponse: req };
|
|
82
97
|
}
|
|
83
98
|
return req;
|
|
@@ -86,6 +101,8 @@ export async function post(url, body, opts = {}) {
|
|
|
86
101
|
/** Invalidate a cached URL so the next api() call fetches fresh. */
|
|
87
102
|
export function bustCache(url) {
|
|
88
103
|
_cache.delete(url);
|
|
104
|
+
const scoped = _scopedUrl(url);
|
|
105
|
+
if (scoped !== url) _cache.delete(scoped);
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
/** Clear the entire cache (e.g. after an SSE reconnect). */
|
|
@@ -122,6 +139,15 @@ export function notifyNotReady(payloads) {
|
|
|
122
139
|
for (const p of payloads) {
|
|
123
140
|
if (!p || typeof p !== 'object' || !p.notReady) continue;
|
|
124
141
|
const code = String(p.pillar || 'pillar') + '-not-ready';
|
|
125
|
-
|
|
142
|
+
const reason = String(p.reason || '');
|
|
143
|
+
const optionalIdleRuntime = /not attached|not initiali[sz]ed|deferred|lazy/i.test(reason)
|
|
144
|
+
&& !p.error
|
|
145
|
+
&& !p.fatal
|
|
146
|
+
&& !p.severity;
|
|
147
|
+
if (optionalIdleRuntime) {
|
|
148
|
+
clearSystemError(code);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
pushSystemError(code, reason || 'runtime not attached', undefined);
|
|
126
152
|
}
|
|
127
153
|
}
|
|
@@ -185,6 +185,10 @@ if(location.protocol==='file:'){
|
|
|
185
185
|
<div class="shd">Promotion history</div>
|
|
186
186
|
<div class="card" style="margin-bottom:14px"><div id="mem-promotion-ticker" class="mem-promotion-ticker"></div></div>
|
|
187
187
|
|
|
188
|
+
<div class="memory-toolbar">
|
|
189
|
+
<button class="btn btn-sm" id="mem-graph-max-btn">Maximize graph</button>
|
|
190
|
+
<button class="btn btn-sm" id="mem-browse-btn">Browse memories</button>
|
|
191
|
+
</div>
|
|
188
192
|
<div id="graph-container" class="card">
|
|
189
193
|
<svg id="graph-svg"></svg>
|
|
190
194
|
<div class="graph-legend">
|
|
@@ -208,6 +212,7 @@ if(location.protocol==='file:'){
|
|
|
208
212
|
<div class="card">
|
|
209
213
|
<div class="mem-search-row">
|
|
210
214
|
<input id="mem-search" type="text" placeholder="Search memories…" aria-label="Search memories">
|
|
215
|
+
<button class="btn btn-sm" id="mem-list-browse-btn">Browse</button>
|
|
211
216
|
</div>
|
|
212
217
|
<div id="mem-list"></div>
|
|
213
218
|
</div>
|