nexus-prime 6.0.0 → 6.0.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.
Files changed (134) hide show
  1. package/dist/agents/adapters/ide-compat.d.ts +3 -0
  2. package/dist/agents/adapters/ide-compat.js +54 -1
  3. package/dist/agents/adapters/mcp/envelope.d.ts +50 -0
  4. package/dist/agents/adapters/mcp/envelope.js +45 -0
  5. package/dist/agents/adapters/mcp/handlers/discovery.js +4 -0
  6. package/dist/agents/adapters/mcp/handlers/governance.js +32 -0
  7. package/dist/agents/adapters/mcp/handlers/memory.js +8 -2
  8. package/dist/agents/adapters/mcp/handlers/orchestration.js +33 -8
  9. package/dist/agents/adapters/mcp/handlers/runtime.js +5 -1
  10. package/dist/agents/adapters/mcp/runHandler.d.ts +7 -34
  11. package/dist/agents/adapters/mcp/runHandler.js +46 -35
  12. package/dist/agents/adapters/mcp/util/detect-caller.d.ts +14 -6
  13. package/dist/agents/adapters/mcp/util/detect-caller.js +74 -11
  14. package/dist/agents/adapters/mcp/util/require-runtime.d.ts +24 -0
  15. package/dist/agents/adapters/mcp/util/require-runtime.js +28 -0
  16. package/dist/agents/adapters/mcp.js +60 -57
  17. package/dist/architects/db/schema.js +3 -0
  18. package/dist/architects/sentinel/patrol.d.ts +19 -0
  19. package/dist/architects/sentinel/patrol.js +22 -0
  20. package/dist/cli/install-wizard.d.ts +16 -0
  21. package/dist/cli/install-wizard.js +256 -15
  22. package/dist/cli/reset.d.ts +30 -0
  23. package/dist/cli/reset.js +132 -0
  24. package/dist/cli.js +102 -13
  25. package/dist/daemon/lock.d.ts +19 -0
  26. package/dist/daemon/lock.js +70 -1
  27. package/dist/daemon/preflight.d.ts +23 -0
  28. package/dist/daemon/preflight.js +121 -0
  29. package/dist/daemon/server.d.ts +1 -0
  30. package/dist/daemon/server.js +50 -1
  31. package/dist/dashboard/app/api.js +75 -23
  32. package/dist/dashboard/app/index.html +63 -0
  33. package/dist/dashboard/app/main.js +34 -4
  34. package/dist/dashboard/app/sse.js +6 -0
  35. package/dist/dashboard/app/state.js +6 -0
  36. package/dist/dashboard/app/styles/animation.css +10 -0
  37. package/dist/dashboard/app/styles/authoring.css +199 -0
  38. package/dist/dashboard/app/styles/governance.css +121 -0
  39. package/dist/dashboard/app/styles/tokens.css +62 -5
  40. package/dist/dashboard/app/styles/trust.css +356 -0
  41. package/dist/dashboard/app/views/authoring.js +295 -0
  42. package/dist/dashboard/app/views/federation.js +216 -0
  43. package/dist/dashboard/app/views/governance.js +317 -0
  44. package/dist/dashboard/app/views/knowledge.js +103 -2
  45. package/dist/dashboard/app/views/license.js +336 -0
  46. package/dist/dashboard/app/views/memory.js +30 -5
  47. package/dist/dashboard/app/views/repo.js +262 -0
  48. package/dist/dashboard/app/views/trust.js +296 -0
  49. package/dist/dashboard/app/views/workforce.js +191 -14
  50. package/dist/dashboard/app/widgets/error-banner.js +83 -0
  51. package/dist/dashboard/app/widgets/runtime-badge.js +46 -0
  52. package/dist/dashboard/routes/architects.d.ts +2 -0
  53. package/dist/dashboard/routes/architects.js +46 -0
  54. package/dist/dashboard/routes/authoring.d.ts +2 -0
  55. package/dist/dashboard/routes/authoring.js +193 -0
  56. package/dist/dashboard/routes/governance.js +93 -2
  57. package/dist/dashboard/routes/graph.d.ts +13 -0
  58. package/dist/dashboard/routes/graph.js +94 -0
  59. package/dist/dashboard/routes/runtime.js +24 -50
  60. package/dist/dashboard/server.d.ts +7 -3
  61. package/dist/dashboard/server.js +122 -81
  62. package/dist/dashboard/stream/sse-broker.js +11 -0
  63. package/dist/dashboard/types.d.ts +15 -2
  64. package/dist/engines/benchmark-extended.js +2 -2
  65. package/dist/engines/byzantine-consensus.js +22 -0
  66. package/dist/engines/client-registry.d.ts +2 -0
  67. package/dist/engines/client-registry.js +25 -5
  68. package/dist/engines/code-review-graph-client.d.ts +13 -0
  69. package/dist/engines/code-review-graph-client.js +60 -0
  70. package/dist/engines/event-bus.d.ts +75 -1
  71. package/dist/engines/instruction-gateway.js +14 -14
  72. package/dist/engines/knowledge-fabric.d.ts +1 -1
  73. package/dist/engines/knowledge-fabric.js +4 -4
  74. package/dist/engines/machine-efficiency/pressure-detector.js +10 -4
  75. package/dist/engines/memory/entropy-decay.d.ts +19 -0
  76. package/dist/engines/memory/entropy-decay.js +19 -0
  77. package/dist/engines/memory/recall.d.ts +8 -0
  78. package/dist/engines/memory/recall.js +36 -0
  79. package/dist/engines/memory/schema.d.ts +21 -0
  80. package/dist/engines/memory/schema.js +127 -0
  81. package/dist/engines/memory/store.d.ts +18 -0
  82. package/dist/engines/memory/store.js +86 -0
  83. package/dist/engines/memory/tier-promotion.d.ts +21 -0
  84. package/dist/engines/memory/tier-promotion.js +14 -0
  85. package/dist/engines/memory-bridge.d.ts +1 -1
  86. package/dist/engines/memory-bridge.js +2 -2
  87. package/dist/engines/memory.d.ts +10 -1
  88. package/dist/engines/memory.js +45 -194
  89. package/dist/engines/middleware-pipeline.d.ts +3 -0
  90. package/dist/engines/middleware-pipeline.js +20 -0
  91. package/dist/engines/nexus-layer.d.ts +1 -1
  92. package/dist/engines/orchestrator/funnel.d.ts +49 -0
  93. package/dist/engines/orchestrator/funnel.js +82 -0
  94. package/dist/engines/orchestrator.js +144 -61
  95. package/dist/engines/perf-history.d.ts +33 -0
  96. package/dist/engines/perf-history.js +93 -0
  97. package/dist/engines/runtime-backends.d.ts +3 -3
  98. package/dist/engines/runtime-backends.js +3 -3
  99. package/dist/engines/schema/integrity.d.ts +23 -0
  100. package/dist/engines/schema/integrity.js +111 -0
  101. package/dist/engines/schema/migrate.d.ts +27 -0
  102. package/dist/engines/schema/migrate.js +90 -0
  103. package/dist/engines/telemetry.d.ts +28 -0
  104. package/dist/engines/telemetry.js +79 -0
  105. package/dist/engines/token-supremacy.d.ts +6 -11
  106. package/dist/engines/token-supremacy.js +49 -53
  107. package/dist/index.d.ts +11 -3
  108. package/dist/index.js +23 -4
  109. package/dist/invokers/cursor.d.ts +27 -0
  110. package/dist/invokers/cursor.js +163 -0
  111. package/dist/invokers/registry.js +3 -0
  112. package/dist/invokers/types.d.ts +1 -1
  113. package/dist/migrations/architects/v001_baseline.sql +4 -0
  114. package/dist/migrations/memory/v001_baseline.sql +4 -0
  115. package/dist/migrations/synapse/v001_baseline.sql +4 -0
  116. package/dist/phantom/index.js +1 -1
  117. package/dist/phantom/runtime/diff-preview.d.ts +9 -0
  118. package/dist/phantom/runtime/diff-preview.js +72 -0
  119. package/dist/phantom/runtime/ghost-pass.d.ts +5 -0
  120. package/dist/phantom/runtime/ghost-pass.js +7 -0
  121. package/dist/phantom/runtime/index.d.ts +5 -0
  122. package/dist/phantom/runtime/index.js +5 -0
  123. package/dist/phantom/runtime/worker.d.ts +57 -0
  124. package/dist/phantom/runtime/worker.js +193 -0
  125. package/dist/phantom/runtime/worktree.d.ts +69 -0
  126. package/dist/phantom/runtime/worktree.js +227 -0
  127. package/dist/phantom/runtime.d.ts +10 -12
  128. package/dist/phantom/runtime.js +66 -536
  129. package/dist/postinstall/cleanup.d.ts +16 -0
  130. package/dist/postinstall/cleanup.js +85 -0
  131. package/dist/synapse/db/schema.js +3 -0
  132. package/dist/verify-token-scoring.js +1 -1
  133. package/package.json +6 -6
  134. package/dist/dashboard/index.html +0 -3678
@@ -5,6 +5,7 @@
5
5
  * Supported: VS Code (Claude Code ext), JetBrains, Cursor, Windsurf, Zed, Continue.dev, Aider, Cline, Codex
6
6
  */
7
7
  export type IDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'cline' | 'zed' | 'codex';
8
+ export type CallerIDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'zed' | 'codex';
8
9
  export interface McpConfigOutput {
9
10
  /** Absolute path where the config should be written */
10
11
  configPath: string | null;
@@ -13,6 +14,8 @@ export interface McpConfigOutput {
13
14
  /** If true, attempt to merge into existing file rather than overwrite */
14
15
  merge: boolean;
15
16
  }
17
+ export declare function resetDetectedCallerIDECache(): void;
18
+ export declare function detectCallerIDE(): CallerIDEId | null;
16
19
  /** Detect which IDEs are present in the workspace or home directory. */
17
20
  export declare function detectInstalledIDEs(workspaceRoot: string): IDEId[];
18
21
  /** Get the MCP config content and target path for a given IDE. */
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import { existsSync } from 'fs';
8
8
  import { homedir } from 'os';
9
- import { join } from 'path';
9
+ import { dirname, join, resolve } from 'path';
10
+ let callerIDECache;
10
11
  /** Nexus Prime MCP server entry (stdio transport). */
11
12
  function nexusMcpServerEntry(workspaceRoot) {
12
13
  return {
@@ -15,6 +16,58 @@ function nexusMcpServerEntry(workspaceRoot) {
15
16
  env: {},
16
17
  };
17
18
  }
19
+ function hasAnyEnvPrefix(prefix) {
20
+ return Object.keys(process.env).some((key) => key.startsWith(prefix));
21
+ }
22
+ function detectCallerIDEFromEnv() {
23
+ if (process.env.CLAUDECODE || process.env.CLAUDE_CODE || process.env.CLAUDE_SESSION_ID || process.env.CLAUDE_PROJECT_DIR) {
24
+ return 'claude-code';
25
+ }
26
+ if (process.env.CURSOR_TRACE_ID || hasAnyEnvPrefix('CURSOR_')) {
27
+ return 'cursor';
28
+ }
29
+ if (process.env.CODEX_HOME || hasAnyEnvPrefix('CODEX_')) {
30
+ return 'codex';
31
+ }
32
+ if (hasAnyEnvPrefix('WINDSURF_')) {
33
+ return 'windsurf';
34
+ }
35
+ if (hasAnyEnvPrefix('CONTINUE_')) {
36
+ return 'continue';
37
+ }
38
+ if (hasAnyEnvPrefix('ZED_')) {
39
+ return 'zed';
40
+ }
41
+ return null;
42
+ }
43
+ function detectCallerIDEFromFilesystem(startDir) {
44
+ let current = resolve(startDir);
45
+ for (;;) {
46
+ if (existsSync(join(current, '.cursor')))
47
+ return 'cursor';
48
+ if (existsSync(join(current, '.continue')))
49
+ return 'continue';
50
+ if (existsSync(join(current, '.windsurf')))
51
+ return 'windsurf';
52
+ if (existsSync(join(current, '.zed')))
53
+ return 'zed';
54
+ const parent = dirname(current);
55
+ if (parent === current) {
56
+ return null;
57
+ }
58
+ current = parent;
59
+ }
60
+ }
61
+ export function resetDetectedCallerIDECache() {
62
+ callerIDECache = undefined;
63
+ }
64
+ export function detectCallerIDE() {
65
+ if (callerIDECache !== undefined) {
66
+ return callerIDECache;
67
+ }
68
+ callerIDECache = detectCallerIDEFromEnv() ?? detectCallerIDEFromFilesystem(process.cwd());
69
+ return callerIDECache;
70
+ }
18
71
  /** Detect which IDEs are present in the workspace or home directory. */
19
72
  export function detectInstalledIDEs(workspaceRoot) {
20
73
  const detected = [];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * HandlerEnvelope — unified success/error shape for all MCP tool handlers.
3
+ *
4
+ * Lifted out of runHandler.ts so any module (middleware, handlers, tests,
5
+ * dashboard routes) can import the type without pulling in the full
6
+ * runHandler runtime. runHandler.ts re-exports these for back-compat.
7
+ */
8
+ export type HandlerErrorCode = 'timeout' | 'sqlite-busy' | 'runtime-cold' | 'input-invalid' | 'nested-dispatch-refused' | 'internal';
9
+ export interface HandlerEnvelope<T = unknown> {
10
+ ok: boolean;
11
+ data?: T;
12
+ error?: {
13
+ code: HandlerErrorCode;
14
+ message: string;
15
+ retriable: boolean;
16
+ hint?: string;
17
+ };
18
+ meta: {
19
+ toolName: string;
20
+ durationMs: number;
21
+ attempts: number;
22
+ runtimeHotAt: number | null;
23
+ };
24
+ }
25
+ export interface RunHandlerOptions {
26
+ /** Override the per-client default timeout. */
27
+ timeoutMs?: number;
28
+ /** Max retries for transient errors (SQLite-busy, runtime-cold). Default 1. */
29
+ retries?: number;
30
+ /** Caller name for per-client timeout selection. */
31
+ callerName?: string;
32
+ /** Timestamp (ms) when the runtime became hot. Passed through into meta. */
33
+ runtimeHotAt?: number | null;
34
+ }
35
+ /**
36
+ * Convert a HandlerEnvelope into the MCP tool-call response shape.
37
+ *
38
+ * On success, if `envelope.data` is already a ToolResult
39
+ * (`{ content: Array<...> }`), it is returned directly so we never
40
+ * double-wrap handler output. Any other value is JSON-stringified into a
41
+ * single text content block.
42
+ *
43
+ * On error, a structured JSON error block is produced.
44
+ */
45
+ export declare function envelopeToMcpResponse<T>(envelope: HandlerEnvelope<T>): {
46
+ content: Array<{
47
+ type: 'text';
48
+ text: string;
49
+ }>;
50
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * HandlerEnvelope — unified success/error shape for all MCP tool handlers.
3
+ *
4
+ * Lifted out of runHandler.ts so any module (middleware, handlers, tests,
5
+ * dashboard routes) can import the type without pulling in the full
6
+ * runHandler runtime. runHandler.ts re-exports these for back-compat.
7
+ */
8
+ // ─── Serialisation helper ─────────────────────────────────────────────────────
9
+ /**
10
+ * Convert a HandlerEnvelope into the MCP tool-call response shape.
11
+ *
12
+ * On success, if `envelope.data` is already a ToolResult
13
+ * (`{ content: Array<...> }`), it is returned directly so we never
14
+ * double-wrap handler output. Any other value is JSON-stringified into a
15
+ * single text content block.
16
+ *
17
+ * On error, a structured JSON error block is produced.
18
+ */
19
+ export function envelopeToMcpResponse(envelope) {
20
+ if (envelope.ok) {
21
+ const data = envelope.data;
22
+ // Pass-through: handler already returned a ToolResult-shaped object.
23
+ if (data !== null &&
24
+ data !== undefined &&
25
+ typeof data === 'object' &&
26
+ Array.isArray(data.content)) {
27
+ return data;
28
+ }
29
+ const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
30
+ return { content: [{ type: 'text', text }] };
31
+ }
32
+ return {
33
+ content: [{
34
+ type: 'text',
35
+ text: JSON.stringify({
36
+ status: 'error',
37
+ code: envelope.error.code,
38
+ message: envelope.error.message,
39
+ retriable: envelope.error.retriable,
40
+ hint: envelope.error.hint,
41
+ meta: envelope.meta,
42
+ }, null, 2),
43
+ }],
44
+ };
45
+ }
@@ -5,8 +5,12 @@
5
5
  */
6
6
  import { formatBullets, formatJsonDetails, collectFolderFiles, } from '../helpers.js';
7
7
  import { listExecutionPresets } from '../../../../engines/execution-presets.js';
8
+ import { requireRuntime } from '../util/require-runtime.js';
8
9
  import * as path from 'path';
9
10
  export async function handleDiscoveryGroup(toolName, hctx, request, args, ctx) {
11
+ const runtimeError = requireRuntime(hctx);
12
+ if (runtimeError)
13
+ return runtimeError;
10
14
  switch (toolName) {
11
15
  case 'nexus_list_execution_presets': {
12
16
  const presets = listExecutionPresets();
@@ -17,9 +17,15 @@ import { ensureBootstrap } from '../../../../engines/client-bootstrap.js';
17
17
  import { HybridRetriever } from '../../../../engines/hybrid-retriever.js';
18
18
  import { GraphTraversalEngine } from '../../../../engines/graph-traversal.js';
19
19
  import { fileURLToPath } from 'url';
20
+ import { requireRuntime } from '../util/require-runtime.js';
21
+ import { getAllPerfStats } from '../../../../engines/perf-history.js';
22
+ import { getTTVSnapshot } from '../../../../engines/telemetry.js';
20
23
  const _gfile = fileURLToPath(import.meta.url);
21
24
  const PROJECT_ROOT = path.resolve(path.dirname(_gfile), '..', '..', '..', '..', '..');
22
25
  export async function handleGovernanceGroup(toolName, hctx, request, args, ctx) {
26
+ const runtimeError = requireRuntime(hctx);
27
+ if (runtimeError)
28
+ return runtimeError;
23
29
  switch (toolName) {
24
30
  case 'nexus_runtime_health': {
25
31
  const envelope = hctx.nexusRef.getRuntimeHealthEnvelope();
@@ -78,6 +84,30 @@ export async function handleGovernanceGroup(toolName, hctx, request, args, ctx)
78
84
  : []),
79
85
  ];
80
86
  const allLive = deadSubsystems.length === 0;
87
+ // ── perfProfile: p50/p90/p99 per tool + TTV snapshot ─────────
88
+ const [perfStats, ttvSnap] = await Promise.all([
89
+ getAllPerfStats().catch(() => []),
90
+ Promise.resolve(getTTVSnapshot()),
91
+ ]);
92
+ const perfProfile = {
93
+ tools: perfStats.map((s) => ({
94
+ tool: s.tool,
95
+ count: s.count,
96
+ p50ms: s.p50,
97
+ p90ms: s.p90,
98
+ p99ms: s.p99,
99
+ budget: s.budget ?? null,
100
+ slow: s.budget ? s.p99 > s.budget.fail : false,
101
+ })),
102
+ ttv: {
103
+ daemonStartAt: ttvSnap.daemonStartAt,
104
+ firstBootstrapMs: ttvSnap.firstBootstrapAt != null ? ttvSnap.firstBootstrapAt - ttvSnap.daemonStartAt : null,
105
+ firstMemoryMs: ttvSnap.firstMemoryAt != null ? ttvSnap.firstMemoryAt - ttvSnap.daemonStartAt : null,
106
+ firstInteractionMs: ttvSnap.firstInteractionAt != null ? ttvSnap.firstInteractionAt - ttvSnap.daemonStartAt : null,
107
+ firstMissionMs: ttvSnap.firstMissionCompleteAt != null ? ttvSnap.firstMissionCompleteAt - ttvSnap.daemonStartAt : null,
108
+ },
109
+ };
110
+ const slowTools = perfProfile.tools.filter((t) => t.slow).map((t) => t.tool);
81
111
  return {
82
112
  content: [{
83
113
  type: 'text',
@@ -94,6 +124,7 @@ export async function handleGovernanceGroup(toolName, hctx, request, args, ctx)
94
124
  `Workspace: ${envelope.workspace.source}`,
95
125
  `Issues: ${issues.length}`,
96
126
  `Ngram index: ${ngramOversized ? 'oversized' : 'ok'} (${Math.round(ngramSizeBytes / 1024 / 1024)}MB)`,
127
+ ...(slowTools.length > 0 ? [`\u26A0\uFE0F Slow tools (p99 > budget): ${slowTools.join(', ')}`] : []),
97
128
  ]),
98
129
  issues.length > 0 ? formatBullets(issues.map((issue) => `\u26A0\uFE0F ${issue}`)) : '',
99
130
  (() => {
@@ -115,6 +146,7 @@ export async function handleGovernanceGroup(toolName, hctx, request, args, ctx)
115
146
  oversized: ngramOversized,
116
147
  },
117
148
  securityAudit: getSharedAuditLog().getSummary(),
149
+ perfProfile,
118
150
  }),
119
151
  ].filter(Boolean).join('\n\n'),
120
152
  }],
@@ -8,7 +8,12 @@ import { formatBullets, formatJsonDetails } from '../helpers.js';
8
8
  import { nexusEventBus } from '../../../../engines/event-bus.js';
9
9
  import { getOperativeJournal } from '../../../../engines/operative-journal.js';
10
10
  import { storeToolSummary } from '../util/auto-memory.js';
11
+ import { requireRuntime } from '../util/require-runtime.js';
12
+ import { recordFirstMemory } from '../../../../engines/telemetry.js';
11
13
  export async function handleMemoryGroup(toolName, hctx, request, args, ctx) {
14
+ const runtimeError = requireRuntime(hctx);
15
+ if (runtimeError)
16
+ return runtimeError;
12
17
  switch (toolName) {
13
18
  case 'nexus_store_memory': {
14
19
  const content = String(request.params.arguments?.content ?? '');
@@ -19,6 +24,7 @@ export async function handleMemoryGroup(toolName, hctx, request, args, ctx) {
19
24
  const verbatim = Boolean(request.params.arguments?.verbatim ?? false);
20
25
  const id = hctx.nexusRef.storeMemory(content, priority, tags, undefined, undefined, { verbatim });
21
26
  hctx.telemetry.recordStore();
27
+ void recordFirstMemory().catch(() => { });
22
28
  hctx.sessionDNA.recordMemoryStore();
23
29
  const nudge = hctx.telemetry.planningNudge('store', { priority });
24
30
  if (ctx) {
@@ -170,7 +176,7 @@ export async function handleMemoryGroup(toolName, hctx, request, args, ctx) {
170
176
  };
171
177
  }
172
178
  case 'nexus_memory_import': {
173
- const result = hctx.nexusRef.importMemoryBundle({
179
+ const result = await hctx.nexusRef.importMemoryBundle({
174
180
  path: request.params.arguments?.path ? String(request.params.arguments.path) : undefined,
175
181
  bundle: request.params.arguments?.bundle,
176
182
  });
@@ -232,7 +238,7 @@ export async function handleMemoryGroup(toolName, hctx, request, args, ctx) {
232
238
  ]);
233
239
  }
234
240
  else if (action === 'syncFrom') {
235
- const result = bridge.syncFrom(sourceDir || bridge.getBridgeDir());
241
+ const result = await bridge.syncFrom(sourceDir || bridge.getBridgeDir());
236
242
  detail = formatBullets([
237
243
  `Success: ${result.success}`,
238
244
  `Imported: ${result.imported}`,
@@ -12,12 +12,18 @@ import { formatReadingPlan } from '../../../../engines/token-supremacy.js';
12
12
  import { GhostPass, summarizeExecution } from '../../../../phantom/index.js';
13
13
  import { applyExecutionPreset, resolveExecutionPreset } from '../../../../engines/execution-presets.js';
14
14
  import { pLimit } from '../../../../engines/util/p-limit.js';
15
- import { promises as fsPromises, statSync } from 'fs';
15
+ import { promises as fsPromises } from 'fs';
16
16
  import { getSharedLicenseManager } from '../../../../licensing/index.js';
17
17
  import { SessionDNAManager } from '../../../../engines/session-dna.js';
18
18
  import { getSharedTelemetry } from '../../../../engines/telemetry-remote.js';
19
19
  import { storeToolSummary } from '../util/auto-memory.js';
20
+ import { requireRuntime } from '../util/require-runtime.js';
21
+ import { ensureCrGraphBuilt } from '../../../../engines/code-review-graph-client.js';
22
+ import { recordFirstBootstrap } from '../../../../engines/telemetry.js';
20
23
  export async function handleOrchestrationGroup(toolName, hctx, request, args, ctx) {
24
+ const runtimeError = requireRuntime(hctx);
25
+ if (runtimeError)
26
+ return runtimeError;
21
27
  // Variables computed in the original handleToolCall preamble and used by these cases
22
28
  const detailLevel = normalizeDetailLevel(args.detailLevel);
23
29
  const requestedIntent = normalizeResponseIntent(args.intent);
@@ -117,7 +123,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
117
123
  remoteTelemetry.trackTokenSavings(Number(bootstrap.tokenOptimization.planMetrics.savings));
118
124
  }
119
125
  }
120
- catch { /* best-effort telemetry */ }
126
+ catch (err) {
127
+ nexusEventBus.emit('mcp.handler.best-effort-failed', { toolName, stage: 'track-session-start', error: err instanceof Error ? err.message : String(err) });
128
+ }
121
129
  if (ctx) {
122
130
  ctx.meta.tokenSavings = Number(bootstrap.tokenOptimization?.planMetrics?.savings ?? 0);
123
131
  ctx.meta.projectMemoryBootstrapCount = Number(bootstrap.projectMemoryBootstrap?.count ?? 0);
@@ -133,7 +141,20 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
133
141
  try {
134
142
  storeToolSummary(hctx, 'nexus_session_bootstrap', `Session bootstrap: ${bootstrapGoal || 'interactive session'}. Memory: prefrontal=${bootstrap.memoryStats?.prefrontal ?? 0}, hippocampus=${bootstrap.memoryStats?.hippocampus ?? 0}, cortex=${bootstrap.memoryStats?.cortex ?? 0}.`);
135
143
  }
144
+ catch (err) {
145
+ nexusEventBus.emit('mcp.handler.best-effort-failed', { toolName, stage: 'persist-session-summary', error: err instanceof Error ? err.message : String(err) });
146
+ }
147
+ // Non-blocking: ensure cr-graph is fresh after bootstrap. Fires after the handler
148
+ // returns so the response is not delayed. Any failure is silently swallowed.
149
+ try {
150
+ const crRepoRoot = hctx.nexusRef.getWorkspaceContext().repoRoot;
151
+ if (crRepoRoot) {
152
+ setImmediate(() => { void ensureCrGraphBuilt(crRepoRoot); });
153
+ }
154
+ }
136
155
  catch { /* best-effort */ }
156
+ // TTV telemetry: record first successful bootstrap (fire-and-forget).
157
+ void recordFirstBootstrap().catch(() => { });
137
158
  const timings = {
138
159
  totalMs: Date.now() - callStartedAt,
139
160
  };
@@ -350,7 +371,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
350
371
  const modifiedFiles = execution.workerResults.flatMap((w) => w.modifiedFiles);
351
372
  hctx.nexusRef.storeMemory(`Run ${execution.runId} ${execution.state}: ${summarizeExecution(execution)}. Workers: ${verifiedWorkers}/${execution.workerResults.length} verified. Files modified: ${modifiedFiles.slice(0, 5).join(', ') || 'none'}.`, 0.34, ['#run-summary', '#auto', '#system-hidden']);
352
373
  }
353
- catch { /* best-effort */ }
374
+ catch (err) {
375
+ nexusEventBus.emit('mcp.handler.best-effort-failed', { toolName, stage: 'auto-memory-store', error: err instanceof Error ? err.message : String(err) });
376
+ }
354
377
  execution.activeSkills.forEach((skill) => hctx.sessionDNA.recordSkill(skill.name));
355
378
  execution.workerResults.forEach((result) => {
356
379
  result.modifiedFiles.forEach((file) => hctx.sessionDNA.recordFileModified(file));
@@ -509,7 +532,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
509
532
  return { path: resolved, sizeBytes: 0 };
510
533
  }
511
534
  })));
512
- const plan = getTokenEngine().plan(task, files);
535
+ const plan = await getTokenEngine().plan(task, files);
513
536
  const formatted = formatReadingPlan(plan);
514
537
  // Persist the decision
515
538
  hctx.nexusRef.storeMemory(`Token plan for "${task.slice(0, 80)}": ` +
@@ -557,16 +580,16 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
557
580
  const rawFiles = Array.isArray(request.params.arguments?.files)
558
581
  ? request.params.arguments.files.map(String)
559
582
  : [];
560
- const files = rawFiles.map(p => {
583
+ const files = await Promise.all(rawFiles.map(async (p) => {
561
584
  const resolved = hctx.resolveToolPath(p, request.params.arguments ?? {});
562
585
  try {
563
- const stat = statSync(resolved);
586
+ const stat = await fsPromises.stat(resolved);
564
587
  return { path: resolved, sizeBytes: stat.size, lastModified: stat.mtimeMs };
565
588
  }
566
589
  catch {
567
590
  return { path: resolved, sizeBytes: 0 };
568
591
  }
569
- });
592
+ }));
570
593
  const ghost = new GhostPass(hctx.getWorkspace(request.params.arguments ?? {}).repoRoot);
571
594
  const report = await ghost.analyze(goal, files);
572
595
  hctx.nexusRef.storeMemory(`Ghost pass for "${goal.slice(0, 80)}": ${report.riskAreas.length} risks identified.`, 0.6, ['#ghost-pass']);
@@ -650,7 +673,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
650
673
  try {
651
674
  storeToolSummary(hctx, 'nexus_spawn_workers', `Worker plan: "${goal.slice(0, 100)}" — ${workersCount} workers, ${rawFiles.length} files.`);
652
675
  }
653
- catch { /* best-effort */ }
676
+ catch (err) {
677
+ nexusEventBus.emit('mcp.handler.best-effort-failed', { toolName, stage: 'worker-plan-summary', error: err instanceof Error ? err.message : String(err) });
678
+ }
654
679
  const execution = await hctx.getRuntime().run(applyExecutionPreset({
655
680
  goal,
656
681
  files: rawFiles,
@@ -8,7 +8,11 @@ import { formatBullets, formatJsonDetails, } 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';
11
+ import { requireRuntime } from '../util/require-runtime.js';
11
12
  export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
13
+ const runtimeError = requireRuntime(hctx);
14
+ if (runtimeError)
15
+ return runtimeError;
12
16
  switch (toolName) {
13
17
  case 'nexus_license_usage': {
14
18
  const fmt = String(request.params.arguments?.format ?? 'summary');
@@ -220,7 +224,7 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
220
224
  }
221
225
  case 'nexus_run_status': {
222
226
  const runId = String(request.params.arguments?.runId ?? '');
223
- const run = hctx.getRuntime().getRun(runId);
227
+ const run = await hctx.getRuntime().getRun(runId);
224
228
  if (!run) {
225
229
  return { content: [{ type: 'text', text: `❌ Run not found: ${runId}` }] };
226
230
  }
@@ -8,34 +8,14 @@
8
8
  *
9
9
  * Per-client timeouts (AI Lead refinement):
10
10
  * Codex 90 s · Claude Code 60 s · Cursor 30 s · default 25 s
11
+ *
12
+ * Types and `envelopeToMcpResponse` are canonical in ./envelope.ts;
13
+ * re-exported from here for backward compatibility.
11
14
  */
12
- export type HandlerErrorCode = 'timeout' | 'sqlite-busy' | 'runtime-cold' | 'input-invalid' | 'nested-dispatch-refused' | 'internal';
13
- export interface HandlerEnvelope<T = unknown> {
14
- ok: boolean;
15
- data?: T;
16
- error?: {
17
- code: HandlerErrorCode;
18
- message: string;
19
- retriable: boolean;
20
- hint?: string;
21
- };
22
- meta: {
23
- toolName: string;
24
- durationMs: number;
25
- attempts: number;
26
- runtimeHotAt: number | null;
27
- };
28
- }
29
- export interface RunHandlerOptions {
30
- /** Override the per-client default timeout. */
31
- timeoutMs?: number;
32
- /** Max retries for transient errors (SQLite-busy, runtime-cold). Default 1. */
33
- retries?: number;
34
- /** Caller name for per-client timeout selection. */
35
- callerName?: string;
36
- /** Timestamp (ms) when the runtime became hot. Passed through into meta. */
37
- runtimeHotAt?: number | null;
38
- }
15
+ import type { RunHandlerOptions } from './envelope.js';
16
+ import type { HandlerEnvelope } from './envelope.js';
17
+ export type { HandlerErrorCode, HandlerEnvelope, RunHandlerOptions } from './envelope.js';
18
+ export { envelopeToMcpResponse } from './envelope.js';
39
19
  /**
40
20
  * Run a handler function with timeout, retry, and structured envelope.
41
21
  *
@@ -46,10 +26,3 @@ export interface RunHandlerOptions {
46
26
  * }, { callerName: hctx.adapterName });
47
27
  */
48
28
  export declare function runHandler<T>(toolName: string, fn: () => Promise<T>, opts?: RunHandlerOptions): Promise<HandlerEnvelope<T>>;
49
- /** Convert a HandlerEnvelope into the MCP tool-call response shape. */
50
- export declare function envelopeToMcpResponse<T>(envelope: HandlerEnvelope<T>): {
51
- content: Array<{
52
- type: 'text';
53
- text: string;
54
- }>;
55
- };
@@ -8,8 +8,13 @@
8
8
  *
9
9
  * Per-client timeouts (AI Lead refinement):
10
10
  * Codex 90 s · Claude Code 60 s · Cursor 30 s · default 25 s
11
+ *
12
+ * Types and `envelopeToMcpResponse` are canonical in ./envelope.ts;
13
+ * re-exported from here for backward compatibility.
11
14
  */
12
15
  import { nexusEventBus } from '../../../engines/event-bus.js';
16
+ import { recordPerf } from '../../../engines/perf-history.js';
17
+ export { envelopeToMcpResponse } from './envelope.js';
13
18
  // ─── Per-client timeout map ───────────────────────────────────────────────────
14
19
  const CLIENT_TIMEOUTS = {
15
20
  'codex': 90_000,
@@ -25,12 +30,7 @@ function resolveTimeout(opts) {
25
30
  }
26
31
  return DEFAULT_TIMEOUT;
27
32
  }
28
- // ─── Retriable error detection ────────────────────────────────────────────────
29
- const RETRIABLE_RE = /SQLITE_BUSY|SQLITE_LOCKED|synapse runtime not yet|runtime not yet ready/i;
30
- function isRetriable(err) {
31
- const msg = err instanceof Error ? err.message : String(err);
32
- return RETRIABLE_RE.test(msg);
33
- }
33
+ // ─── Error classification ─────────────────────────────────────────────────────
34
34
  function classifyError(err) {
35
35
  const msg = err instanceof Error ? err.message : String(err);
36
36
  if (/SQLITE_BUSY|SQLITE_LOCKED/i.test(msg))
@@ -41,6 +41,28 @@ function classifyError(err) {
41
41
  return 'input-invalid';
42
42
  return 'internal';
43
43
  }
44
+ const RETRY_POLICIES = {
45
+ // SQLite lock: 3 retries with exponential backoff (100 → 300 → 900 ms).
46
+ 'sqlite-busy': {
47
+ maxRetries: 3,
48
+ backoffMs: (attempt) => 100 * Math.pow(3, attempt - 1),
49
+ },
50
+ // Cold runtime: 1 immediate retry (the runtime may warm up in milliseconds).
51
+ 'runtime-cold': {
52
+ maxRetries: 1,
53
+ backoffMs: () => 0,
54
+ },
55
+ // Bad input and timeouts are never retried — retrying won't fix them.
56
+ 'input-invalid': { maxRetries: 0, backoffMs: () => 0 },
57
+ 'timeout': { maxRetries: 0, backoffMs: () => 0 },
58
+ };
59
+ const DEFAULT_RETRY_POLICY = {
60
+ maxRetries: 1,
61
+ backoffMs: () => 200,
62
+ };
63
+ function getRetryPolicy(code) {
64
+ return RETRY_POLICIES[code] ?? DEFAULT_RETRY_POLICY;
65
+ }
44
66
  // ─── Core wrapper ─────────────────────────────────────────────────────────────
45
67
  /**
46
68
  * Run a handler function with timeout, retry, and structured envelope.
@@ -53,7 +75,6 @@ function classifyError(err) {
53
75
  */
54
76
  export async function runHandler(toolName, fn, opts = {}) {
55
77
  const timeoutMs = resolveTimeout(opts);
56
- const maxRetries = opts.retries ?? 1;
57
78
  const startMs = Date.now();
58
79
  let attempts = 0;
59
80
  // eslint-disable-next-line no-constant-condition -- retry loop exits via return/break
@@ -77,22 +98,34 @@ export async function runHandler(toolName, fn, opts = {}) {
77
98
  },
78
99
  };
79
100
  nexusEventBus.emit('mcp.handler.complete', { toolName, attempts, durationMs: envelope.meta.durationMs });
101
+ recordPerf(toolName, envelope.meta.durationMs);
80
102
  return envelope;
81
103
  }
82
104
  catch (err) {
83
105
  clearTimeout(timeoutHandle);
84
- const isTimeout = err instanceof Error && err.message === '__nexus_timeout__';
85
- const canRetry = !isTimeout && attempts <= maxRetries && isRetriable(err);
106
+ const isTimeout = err instanceof Error && (err.message === '__nexus_timeout__' ||
107
+ err.message === '__nexus_pipeline_timeout__');
108
+ const code = isTimeout ? 'timeout' : classifyError(err);
109
+ const policy = getRetryPolicy(code);
110
+ // opts.retries overrides the policy maxRetries if provided explicitly.
111
+ const maxRetries = opts.retries !== undefined ? opts.retries : policy.maxRetries;
112
+ const canRetry = attempts <= maxRetries;
86
113
  if (canRetry) {
87
- // One retry after a short backoff
88
- await new Promise(r => setTimeout(r, 200));
114
+ const delay = policy.backoffMs(attempts);
115
+ nexusEventBus.emit('mcp.handler.retry', {
116
+ toolName,
117
+ code,
118
+ attempt: attempts,
119
+ delayMs: delay,
120
+ });
121
+ if (delay > 0)
122
+ await new Promise(r => setTimeout(r, delay));
89
123
  continue;
90
124
  }
91
- const code = isTimeout ? 'timeout' : classifyError(err);
92
125
  const message = isTimeout
93
126
  ? `Handler timed out after ${timeoutMs}ms`
94
127
  : (err instanceof Error ? err.message : String(err));
95
- const retriable = !isTimeout && isRetriable(err);
128
+ const retriable = policy.maxRetries > 0 && !isTimeout;
96
129
  const envelope = {
97
130
  ok: false,
98
131
  error: {
@@ -115,25 +148,3 @@ export async function runHandler(toolName, fn, opts = {}) {
115
148
  }
116
149
  }
117
150
  }
118
- /** Convert a HandlerEnvelope into the MCP tool-call response shape. */
119
- export function envelopeToMcpResponse(envelope) {
120
- if (envelope.ok) {
121
- const text = typeof envelope.data === 'string'
122
- ? envelope.data
123
- : JSON.stringify(envelope.data, null, 2);
124
- return { content: [{ type: 'text', text }] };
125
- }
126
- return {
127
- content: [{
128
- type: 'text',
129
- text: JSON.stringify({
130
- status: 'error',
131
- code: envelope.error.code,
132
- message: envelope.error.message,
133
- retriable: envelope.error.retriable,
134
- hint: envelope.error.hint,
135
- meta: envelope.meta,
136
- }, null, 2),
137
- }],
138
- };
139
- }
@@ -1,14 +1,22 @@
1
1
  /**
2
2
  * Detect which MCP client is invoking nexus-prime.
3
3
  *
4
- * Two tiers:
5
- * 1. detectCallerFromEnv() sync, instant, covers ~99% of real clients
6
- * 2. detectCallerAsync() async ps(1) fallback for edge cases (no env vars)
4
+ * Three tiers (highest-priority first):
5
+ * 1. setMcpClientName() from MCP initialize handshake clientInfo.name (most reliable)
6
+ * 2. detectCallerFromEnv() sync env-var detection, covers ~99% of real clients
7
+ * 3. detectCallerAsync() — async ps(1) fallback for edge cases (no env vars)
7
8
  *
8
- * The MCP adapter constructor uses tier-1 for immediate init, then calls tier-2
9
- * via setImmediate to optionally refine this.name before the first tool call.
9
+ * The MCP adapter constructor uses tier-2 for immediate init, then the Server's
10
+ * oninitialized callback calls setMcpClientName() (tier-1) once the handshake
11
+ * completes. detectCallerAsync() falls through only when tier-1 and tier-2 miss.
10
12
  */
13
+ /**
14
+ * Store the caller identity obtained from the MCP `initialize` handshake
15
+ * `clientInfo.name` field. Pass an empty string to clear.
16
+ * Called by the MCP adapter's `oninitialized` callback.
17
+ */
18
+ export declare function setMcpClientName(rawName: string): void;
11
19
  /** Instant env-variable detection. Safe to call in a constructor. */
12
20
  export declare function detectCallerFromEnv(): string;
13
- /** Full detection including async ps(1) fallback. Never call in a constructor. */
21
+ /** Full detection: MCP handshake name → env vars → ps(1) fallback. */
14
22
  export declare function detectCallerAsync(): Promise<string>;