nexus-prime 7.9.22 → 7.9.24
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.d.ts +3 -3
- package/dist/agents/adapters/ide-compat.js +51 -1
- package/dist/agents/adapters/mcp/definitions.js +4 -1
- package/dist/agents/adapters/mcp/dispatch.js +217 -6
- package/dist/agents/adapters/mcp/handlers/orchestration.js +91 -0
- package/dist/agents/adapters/mcp/runHandler.js +3 -0
- package/dist/agents/adapters/mcp/util/detect-caller.js +21 -0
- package/dist/agents/adapters/mcp.js +1 -1
- package/dist/agents/adapters.d.ts +10 -1
- package/dist/agents/adapters.js +21 -0
- package/dist/agents/core/types.d.ts +1 -1
- package/dist/cli/hook.d.ts +4 -6
- package/dist/cli/hook.js +6 -8
- package/dist/cli/install-wizard.js +5 -1
- package/dist/cli.js +181 -15
- package/dist/core/types.d.ts +1 -1
- package/dist/dashboard/app/styles/board.css +85 -1
- package/dist/dashboard/app/views/board.js +56 -0
- package/dist/dashboard/app/views/memory.js +71 -10
- package/dist/dashboard/routes/events.js +3 -0
- package/dist/dashboard/selectors/operate-selector.js +5 -0
- package/dist/dashboard/selectors/runs-selector.js +5 -0
- package/dist/dashboard/server.js +6 -0
- package/dist/dashboard/types.d.ts +4 -0
- package/dist/engines/client-bootstrap.d.ts +5 -1
- package/dist/engines/client-bootstrap.js +105 -10
- package/dist/engines/client-registry.js +51 -0
- package/dist/engines/event-bus.d.ts +16 -2
- package/dist/engines/feature-registry.js +1 -0
- package/dist/engines/instruction-gateway.d.ts +9 -0
- package/dist/engines/instruction-gateway.js +113 -4
- package/dist/engines/memory/types.d.ts +28 -0
- package/dist/engines/memory-bridge.d.ts +1 -1
- package/dist/engines/memory-bridge.js +1 -1
- package/dist/engines/memory.d.ts +5 -0
- package/dist/engines/memory.js +144 -12
- package/dist/engines/orchestrator/decision-spine.d.ts +26 -0
- package/dist/engines/orchestrator/decision-spine.js +145 -6
- package/dist/engines/orchestrator/funnel.js +8 -1
- package/dist/engines/orchestrator/scoring.d.ts +1 -1
- package/dist/engines/orchestrator/scoring.js +24 -2
- package/dist/engines/orchestrator.d.ts +3 -0
- package/dist/engines/orchestrator.js +73 -13
- package/dist/engines/peer-connectors.d.ts +1 -1
- package/dist/engines/peer-connectors.js +9 -2
- package/dist/engines/runtime-registry.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/install/state-locator.d.ts +1 -1
- package/dist/install/state-locator.js +3 -0
- package/dist/synapse/bootstrap.js +3 -0
- package/dist/synapse/mandate/pipeline.js +52 -5
- package/dist/synapse/sorties/runner.js +32 -0
- package/dist/synapse/types.d.ts +27 -0
- package/package.json +1 -1
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Nexus Prime — IDE Compatibility Utilities
|
|
3
3
|
* Provides MCP config templates and detection logic for all supported IDEs.
|
|
4
4
|
*
|
|
5
|
-
* Supported: VS Code (Claude Code ext), JetBrains, Cursor, Windsurf, Zed, Continue.dev, Aider, Cline, Codex
|
|
5
|
+
* Supported: VS Code (Claude Code ext), JetBrains, Cursor, Windsurf, Zed, Continue.dev, Aider, Cline, Codex, and peer agent clients.
|
|
6
6
|
*/
|
|
7
|
-
export type IDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'cline' | 'zed' | 'codex';
|
|
8
|
-
export type CallerIDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'zed' | 'codex';
|
|
7
|
+
export type IDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'cline' | 'openclaw' | 'hermes' | 'nanoclaw' | 'picoclaw' | 'zed' | 'codex';
|
|
8
|
+
export type CallerIDEId = 'claude-code' | 'cursor' | 'windsurf' | 'continue' | 'openclaw' | 'hermes' | 'nanoclaw' | 'picoclaw' | 'zed' | 'codex';
|
|
9
9
|
export interface McpConfigOutput {
|
|
10
10
|
/** Absolute path where the config should be written */
|
|
11
11
|
configPath: string | null;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Nexus Prime — IDE Compatibility Utilities
|
|
3
3
|
* Provides MCP config templates and detection logic for all supported IDEs.
|
|
4
4
|
*
|
|
5
|
-
* Supported: VS Code (Claude Code ext), JetBrains, Cursor, Windsurf, Zed, Continue.dev, Aider, Cline, Codex
|
|
5
|
+
* Supported: VS Code (Claude Code ext), JetBrains, Cursor, Windsurf, Zed, Continue.dev, Aider, Cline, Codex, and peer agent clients.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from 'fs';
|
|
8
8
|
import { homedir } from 'os';
|
|
@@ -29,6 +29,18 @@ function detectCallerIDEFromEnv() {
|
|
|
29
29
|
if (hasAnyEnvPrefix('WINDSURF_')) {
|
|
30
30
|
return 'windsurf';
|
|
31
31
|
}
|
|
32
|
+
if (hasAnyEnvPrefix('OPENCLAW_') || hasAnyEnvPrefix('ANTIGRAVITY_')) {
|
|
33
|
+
return 'openclaw';
|
|
34
|
+
}
|
|
35
|
+
if (hasAnyEnvPrefix('HERMES_')) {
|
|
36
|
+
return 'hermes';
|
|
37
|
+
}
|
|
38
|
+
if (hasAnyEnvPrefix('NANOCLAW_')) {
|
|
39
|
+
return 'nanoclaw';
|
|
40
|
+
}
|
|
41
|
+
if (hasAnyEnvPrefix('PICOCLAW_')) {
|
|
42
|
+
return 'picoclaw';
|
|
43
|
+
}
|
|
32
44
|
if (hasAnyEnvPrefix('CONTINUE_')) {
|
|
33
45
|
return 'continue';
|
|
34
46
|
}
|
|
@@ -46,6 +58,14 @@ function detectCallerIDEFromFilesystem(startDir) {
|
|
|
46
58
|
return 'continue';
|
|
47
59
|
if (existsSync(join(current, '.windsurf')))
|
|
48
60
|
return 'windsurf';
|
|
61
|
+
if (existsSync(join(current, '.openclaw')))
|
|
62
|
+
return 'openclaw';
|
|
63
|
+
if (existsSync(join(current, '.hermes')))
|
|
64
|
+
return 'hermes';
|
|
65
|
+
if (existsSync(join(current, '.nanoclaw')))
|
|
66
|
+
return 'nanoclaw';
|
|
67
|
+
if (existsSync(join(current, '.picoclaw')))
|
|
68
|
+
return 'picoclaw';
|
|
49
69
|
if (existsSync(join(current, '.zed')))
|
|
50
70
|
return 'zed';
|
|
51
71
|
const parent = dirname(current);
|
|
@@ -84,6 +104,22 @@ export function detectInstalledIDEs(workspaceRoot) {
|
|
|
84
104
|
existsSync(join(home, '.windsurf'))) {
|
|
85
105
|
detected.push('windsurf');
|
|
86
106
|
}
|
|
107
|
+
if (existsSync(join(workspaceRoot, '.openclaw')) ||
|
|
108
|
+
existsSync(join(home, '.openclaw'))) {
|
|
109
|
+
detected.push('openclaw');
|
|
110
|
+
}
|
|
111
|
+
if (existsSync(join(workspaceRoot, '.hermes')) ||
|
|
112
|
+
existsSync(join(home, '.hermes'))) {
|
|
113
|
+
detected.push('hermes');
|
|
114
|
+
}
|
|
115
|
+
if (existsSync(join(workspaceRoot, '.nanoclaw')) ||
|
|
116
|
+
existsSync(join(home, '.nanoclaw'))) {
|
|
117
|
+
detected.push('nanoclaw');
|
|
118
|
+
}
|
|
119
|
+
if (existsSync(join(workspaceRoot, '.picoclaw')) ||
|
|
120
|
+
existsSync(join(home, '.picoclaw'))) {
|
|
121
|
+
detected.push('picoclaw');
|
|
122
|
+
}
|
|
87
123
|
// Continue.dev: .continue/ directory
|
|
88
124
|
if (existsSync(join(workspaceRoot, '.continue')) ||
|
|
89
125
|
existsSync(join(home, '.continue'))) {
|
|
@@ -140,6 +176,20 @@ export function getMcpConfigForIDE(ide, workspaceRoot) {
|
|
|
140
176
|
}, null, 2);
|
|
141
177
|
return { configPath, content, merge: true };
|
|
142
178
|
}
|
|
179
|
+
case 'openclaw':
|
|
180
|
+
case 'hermes':
|
|
181
|
+
case 'nanoclaw':
|
|
182
|
+
case 'picoclaw': {
|
|
183
|
+
const configPath = ide === 'openclaw'
|
|
184
|
+
? join(home, '.openclaw', 'openclaw.json')
|
|
185
|
+
: join(home, `.${ide}`, 'mcp.json');
|
|
186
|
+
const content = JSON.stringify({
|
|
187
|
+
mcpServers: {
|
|
188
|
+
'nexus-prime': entry,
|
|
189
|
+
},
|
|
190
|
+
}, null, 2);
|
|
191
|
+
return { configPath, content, merge: true };
|
|
192
|
+
}
|
|
143
193
|
case 'continue': {
|
|
144
194
|
// Continue.dev uses ~/.continue/config.json
|
|
145
195
|
const configPath = join(home, '.continue', 'config.json');
|
|
@@ -279,7 +279,10 @@ export function buildMcpToolDefinitions() {
|
|
|
279
279
|
crews: { type: 'array', items: { type: 'string' }, description: 'Optional hard crew selectors' },
|
|
280
280
|
specialists: { type: 'array', items: { type: 'string' }, description: 'Optional hard specialist selectors' },
|
|
281
281
|
optimizationProfile: { type: 'string', enum: ['standard', 'max'], description: 'Planner optimization profile override' },
|
|
282
|
-
executionPreset: { type: 'string', enum: ['fast', 'balanced', 'deep', 'release'], description: 'Optional execution preset that maps orchestration depth, verification strictness, and backend routing' }
|
|
282
|
+
executionPreset: { type: 'string', enum: ['fast', 'balanced', 'deep', 'release'], description: 'Optional execution preset that maps orchestration depth, verification strictness, and backend routing' },
|
|
283
|
+
background: { type: 'boolean', description: 'When true, return a queued receipt immediately and let the run continue in the async gate.' },
|
|
284
|
+
async: { type: 'boolean', description: 'Alias for background; useful for clients that prefer explicit async orchestration.' },
|
|
285
|
+
waitMs: { type: 'number', description: 'Optional bounded wait before returning a queued receipt. Clamped to 45 seconds; normal orchestrate calls default to 15 seconds.' }
|
|
283
286
|
},
|
|
284
287
|
required: ['prompt'],
|
|
285
288
|
},
|
|
@@ -16,9 +16,19 @@ import { handleMiscGroup } from './handlers/misc.js';
|
|
|
16
16
|
import { handleWorkforceGroup } from './handlers/workforce.js';
|
|
17
17
|
import { nexusEventBus } from '../../../engines/event-bus.js';
|
|
18
18
|
import { recordToolInvocation } from './tool-health.js';
|
|
19
|
-
import { withAsyncGate } from './async-gate.js';
|
|
19
|
+
import { getAsyncGate, withAsyncGate } from './async-gate.js';
|
|
20
20
|
import { getCircuitManager, checkBackpressure } from './circuit.js';
|
|
21
|
-
import { createRun } from '../../../engines/orchestrator/store.js';
|
|
21
|
+
import { completeRun, createRun, failRun, updateStage } from '../../../engines/orchestrator/store.js';
|
|
22
|
+
import { buildSelectionPlan } from '../../../engines/orchestrator/decision-spine.js';
|
|
23
|
+
function summarizeAsyncMcpResult(toolName, result) {
|
|
24
|
+
const text = result?.content
|
|
25
|
+
?.map((part) => part?.type === 'text' ? String(part.text ?? '') : '')
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('\n')
|
|
28
|
+
.trim();
|
|
29
|
+
const firstLine = text?.split('\n').find((line) => line.trim())?.trim();
|
|
30
|
+
return (firstLine ? `${toolName}: ${firstLine}` : `${toolName} completed asynchronously`).slice(0, 1024);
|
|
31
|
+
}
|
|
22
32
|
/** Tools that may exceed the MCP client timeout (~60s). Wrap in async gate. */
|
|
23
33
|
const SLOW_TOOLS = new Set([
|
|
24
34
|
'nexus_orchestrate',
|
|
@@ -69,6 +79,183 @@ const TOOL_ETA_MS = {
|
|
|
69
79
|
nexus_autofix: 45_000,
|
|
70
80
|
nexus_hypertune_max: 45_000,
|
|
71
81
|
};
|
|
82
|
+
const DEFAULT_MAX_SYNC_MS = 2000;
|
|
83
|
+
const ORCHESTRATE_DEFAULT_MAX_SYNC_MS = 15_000;
|
|
84
|
+
const MAX_CLIENT_SYNC_WAIT_MS = 45_000;
|
|
85
|
+
function isTruthyFlag(value) {
|
|
86
|
+
if (value === true)
|
|
87
|
+
return true;
|
|
88
|
+
if (typeof value === 'number')
|
|
89
|
+
return value > 0;
|
|
90
|
+
if (typeof value === 'string')
|
|
91
|
+
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function coerceBoundedWaitMs(value) {
|
|
95
|
+
const numeric = Number(value);
|
|
96
|
+
if (!Number.isFinite(numeric) || numeric < 0)
|
|
97
|
+
return undefined;
|
|
98
|
+
return Math.min(MAX_CLIENT_SYNC_WAIT_MS, Math.floor(numeric));
|
|
99
|
+
}
|
|
100
|
+
function shouldReturnQueuedReceipt(toolName, args) {
|
|
101
|
+
if (!SLOW_TOOLS.has(toolName))
|
|
102
|
+
return false;
|
|
103
|
+
return isTruthyFlag(args.background)
|
|
104
|
+
|| isTruthyFlag(args.async)
|
|
105
|
+
|| isTruthyFlag(args.queue)
|
|
106
|
+
|| isTruthyFlag(args.detach);
|
|
107
|
+
}
|
|
108
|
+
function resolveMaxSyncMs(toolName, args) {
|
|
109
|
+
return coerceBoundedWaitMs(args.waitMs)
|
|
110
|
+
?? coerceBoundedWaitMs(args.maxSyncMs)
|
|
111
|
+
?? (toolName === 'nexus_orchestrate' ? ORCHESTRATE_DEFAULT_MAX_SYNC_MS : DEFAULT_MAX_SYNC_MS);
|
|
112
|
+
}
|
|
113
|
+
function asStringList(value) {
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value
|
|
116
|
+
.map((item) => typeof item === 'string' ? item : (item && typeof item === 'object' ? String(item.name ?? item.id ?? '') : String(item ?? '')))
|
|
117
|
+
.map((item) => item.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === 'string') {
|
|
121
|
+
return value.split(',')
|
|
122
|
+
.map((item) => item.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
function uniqueList(items, max = 8) {
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
const out = [];
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const value = String(item ?? '').trim();
|
|
132
|
+
if (!value || seen.has(value))
|
|
133
|
+
continue;
|
|
134
|
+
seen.add(value);
|
|
135
|
+
out.push(value);
|
|
136
|
+
if (out.length >= max)
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
function extractLinkedSelectors(prompt, prefix) {
|
|
142
|
+
const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
143
|
+
const pattern = new RegExp(`\\[${escaped}([^\\]\\s]+)\\]`, 'g');
|
|
144
|
+
return Array.from(prompt.matchAll(pattern))
|
|
145
|
+
.map((match) => match[1]?.trim())
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
function inferQueuedOrchestratePreview(args, runId) {
|
|
149
|
+
const prompt = String(args.prompt ?? args.goal ?? '');
|
|
150
|
+
const lower = prompt.toLowerCase();
|
|
151
|
+
const files = uniqueList([
|
|
152
|
+
...asStringList(args.files),
|
|
153
|
+
...asStringList(args.filePaths),
|
|
154
|
+
], 20);
|
|
155
|
+
const skills = uniqueList([
|
|
156
|
+
...asStringList(args.skills),
|
|
157
|
+
...asStringList(args.skillNames),
|
|
158
|
+
...extractLinkedSelectors(prompt, '$'),
|
|
159
|
+
], 20);
|
|
160
|
+
const workflows = uniqueList([
|
|
161
|
+
...asStringList(args.workflows),
|
|
162
|
+
...asStringList(args.workflowSelectors),
|
|
163
|
+
lower.match(/orchestrat|route|dispatch|queue|queued/) ? 'orchestration-runtime-workflow' : undefined,
|
|
164
|
+
lower.match(/synapse|hiring|operative|sortie/) ? 'synapse-mandate-workflow' : undefined,
|
|
165
|
+
lower.match(/memory|recall|learning|decay|graph/) ? 'memory-lifecycle-workflow' : undefined,
|
|
166
|
+
lower.match(/token|budget|optim/i) ? 'token-budget-workflow' : undefined,
|
|
167
|
+
lower.match(/dashboard|board|ui|ux/) ? 'dashboard-observability-workflow' : undefined,
|
|
168
|
+
], 8);
|
|
169
|
+
const specialists = uniqueList([
|
|
170
|
+
...asStringList(args.specialists),
|
|
171
|
+
lower.match(/orchestrat|route|dispatch|queue|queued/) ? 'Orchestrator Runtime Engineer' : undefined,
|
|
172
|
+
lower.match(/synapse|hiring|operative|sortie/) ? 'Synapse Runtime Engineer' : undefined,
|
|
173
|
+
lower.match(/memory|recall|learning|decay|graph/) ? 'Memory Systems Engineer' : undefined,
|
|
174
|
+
lower.match(/token|budget|optim/i) ? 'Token Budget Engineer' : undefined,
|
|
175
|
+
lower.match(/dashboard|board|ui|ux/) ? 'Dashboard UX Engineer' : undefined,
|
|
176
|
+
lower.match(/test|verify|qa|release|publish/) ? 'Verification Engineer' : undefined,
|
|
177
|
+
], 8);
|
|
178
|
+
const crew = String(args.crew ?? args.selectedCrew ?? (lower.match(/orchestrat|synapse|runtime|mcp|dispatch|queue|queued/)
|
|
179
|
+
? 'Runtime Reliability Crew'
|
|
180
|
+
: lower.match(/memory|token|dashboard/)
|
|
181
|
+
? 'Control Plane Quality Crew'
|
|
182
|
+
: 'Nexus Implementation Crew'));
|
|
183
|
+
const risk = lower.match(/fix|broken|bug|doesn.?t|failed|queued|runtime|synapse|orchestrat/) ? 'high' : 'medium';
|
|
184
|
+
const task = {
|
|
185
|
+
goal: prompt,
|
|
186
|
+
intent: lower.match(/fix|bug|broken|doesn.?t|failed/) ? 'bugfix' : 'feature',
|
|
187
|
+
files,
|
|
188
|
+
skillNames: skills,
|
|
189
|
+
workflowSelectors: workflows,
|
|
190
|
+
};
|
|
191
|
+
const workerManifests = uniqueList([
|
|
192
|
+
specialists.length > 0 ? 'worker_coder' : undefined,
|
|
193
|
+
'worker_verifier',
|
|
194
|
+
]).map((workerId, index) => ({
|
|
195
|
+
workerId,
|
|
196
|
+
role: index === 0 ? 'coder' : 'verifier',
|
|
197
|
+
specialistName: index === 0 ? specialists[0] : 'Verification Engineer',
|
|
198
|
+
actions: index === 0 ? ['inspect', 'patch'] : ['verify'],
|
|
199
|
+
verifyCommands: index === 1 ? ['npm run lint', 'targeted tests'] : [],
|
|
200
|
+
}));
|
|
201
|
+
const run = {
|
|
202
|
+
runId,
|
|
203
|
+
requestBrief: {
|
|
204
|
+
id: `brief_${runId}`,
|
|
205
|
+
intent: task.intent,
|
|
206
|
+
risk,
|
|
207
|
+
confidence: 0.68,
|
|
208
|
+
},
|
|
209
|
+
plannerResult: {
|
|
210
|
+
selectedFiles: files,
|
|
211
|
+
selectedSkills: skills,
|
|
212
|
+
selectedWorkflows: workflows,
|
|
213
|
+
selectedSpecialists: specialists.map((name) => ({ name })),
|
|
214
|
+
selectedCrew: { name: crew },
|
|
215
|
+
},
|
|
216
|
+
workerManifests,
|
|
217
|
+
};
|
|
218
|
+
const plan = buildSelectionPlan({ run, task });
|
|
219
|
+
return { plan, crew, specialists, workflows };
|
|
220
|
+
}
|
|
221
|
+
function formatBudgetRoute(plan) {
|
|
222
|
+
const total = Number(plan.budgets?.total ?? 0);
|
|
223
|
+
const code = Number(plan.budgets?.codeBlocks ?? plan.budgets?.codeBlockPolicy?.reservedTokens ?? 0);
|
|
224
|
+
const cap = Number(plan.executionPolicy?.agentFlow?.maxTaskCostUsd ?? plan.executionPolicy?.budgetCapUsd ?? 0);
|
|
225
|
+
return `${total.toLocaleString()} tokens · ${code.toLocaleString()} code-block tokens · $${cap.toFixed(2)} task cap`;
|
|
226
|
+
}
|
|
227
|
+
function formatQueuedOrchestrateReceipt(args, receipt) {
|
|
228
|
+
const preview = inferQueuedOrchestratePreview(args, receipt.runId);
|
|
229
|
+
const plan = preview.plan;
|
|
230
|
+
const stages = Array.isArray(plan.executionPolicy?.agentFlow?.stages)
|
|
231
|
+
? plan.executionPolicy.agentFlow.stages
|
|
232
|
+
: [];
|
|
233
|
+
const selected = plan.selected ?? {};
|
|
234
|
+
const specialists = uniqueList([...(selected.specialists ?? []), ...preview.specialists], 8);
|
|
235
|
+
const workflows = uniqueList([...(selected.workflows ?? []), ...preview.workflows], 8);
|
|
236
|
+
const skills = uniqueList(selected.skills ?? [], 8);
|
|
237
|
+
const receiptJson = JSON.stringify({ queued: true, runId: receipt.runId, etaMs: receipt.etaMs }, null, 2);
|
|
238
|
+
const hiredLine = [
|
|
239
|
+
`crew ${preview.crew}`,
|
|
240
|
+
specialists.length ? `specialists ${specialists.join(', ')}` : '',
|
|
241
|
+
workflows.length ? `workflows ${workflows.join(', ')}` : '',
|
|
242
|
+
skills.length ? `skills ${skills.join(', ')}` : '',
|
|
243
|
+
].filter(Boolean).join(' · ');
|
|
244
|
+
return [
|
|
245
|
+
'Nexus orchestration queued with hiring preflight.',
|
|
246
|
+
`Run: ${receipt.runId}`,
|
|
247
|
+
`ETA: ${receipt.etaMs ? `~${Math.ceil(receipt.etaMs / 1000)}s` : 'background'}`,
|
|
248
|
+
`Hired/selected: ${hiredLine}`,
|
|
249
|
+
`Model route: ${plan.modelRoute?.workerTier ?? 'T1'}${plan.modelRoute?.reviewerTier ? ` + reviewer ${plan.modelRoute.reviewerTier}` : ''}`,
|
|
250
|
+
`Budget route: ${formatBudgetRoute(plan)}`,
|
|
251
|
+
stages.length ? `AgentFlow gates: ${stages.map((stage) => `${stage.stage}:${stage.ownerRole}`).join(' -> ')}` : '',
|
|
252
|
+
'Poll progress with nexus_run_status(runId).',
|
|
253
|
+
'',
|
|
254
|
+
'```json',
|
|
255
|
+
receiptJson,
|
|
256
|
+
'```',
|
|
257
|
+
].filter((line) => line !== '').join('\n');
|
|
258
|
+
}
|
|
72
259
|
/**
|
|
73
260
|
* Route a tool call to the appropriate handler group.
|
|
74
261
|
*
|
|
@@ -127,9 +314,9 @@ export async function dispatchMcpToolCall(hctx, request, args, ctx) {
|
|
|
127
314
|
}, {
|
|
128
315
|
tool: toolName,
|
|
129
316
|
args,
|
|
130
|
-
maxSyncMs:
|
|
317
|
+
maxSyncMs: resolveMaxSyncMs(toolName, args),
|
|
131
318
|
etaMs: TOOL_ETA_MS[toolName],
|
|
132
|
-
alwaysQueue: toolName
|
|
319
|
+
alwaysQueue: shouldReturnQueuedReceipt(toolName, args),
|
|
133
320
|
});
|
|
134
321
|
if ('queued' in gated && gated.queued) {
|
|
135
322
|
// Persist orchestration run record for durable stage tracking
|
|
@@ -138,6 +325,7 @@ export async function dispatchMcpToolCall(hctx, request, args, ctx) {
|
|
|
138
325
|
const client = hctx.getToolProfile();
|
|
139
326
|
try {
|
|
140
327
|
createRun(gated.runId, goal, client);
|
|
328
|
+
updateStage(gated.runId, 'executing', 20);
|
|
141
329
|
}
|
|
142
330
|
catch { /* non-fatal */ }
|
|
143
331
|
}
|
|
@@ -147,8 +335,24 @@ export async function dispatchMcpToolCall(hctx, request, args, ctx) {
|
|
|
147
335
|
const runId = gated.runId;
|
|
148
336
|
let unsubOk = null;
|
|
149
337
|
let unsubFail = null;
|
|
338
|
+
let finalized = false;
|
|
150
339
|
const finalize = (success) => {
|
|
340
|
+
if (finalized)
|
|
341
|
+
return;
|
|
342
|
+
finalized = true;
|
|
151
343
|
const durationMs = Date.now() - startedAt;
|
|
344
|
+
if (toolName === 'nexus_orchestrate') {
|
|
345
|
+
try {
|
|
346
|
+
const job = getAsyncGate().getJob(runId);
|
|
347
|
+
if (success) {
|
|
348
|
+
completeRun(runId, summarizeAsyncMcpResult(toolName, job?.result));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
failRun(runId, job?.error ?? `${toolName} failed asynchronously`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch { /* durable run updates are best-effort */ }
|
|
355
|
+
}
|
|
152
356
|
try {
|
|
153
357
|
circuit.recordComplete(toolName, durationMs, success);
|
|
154
358
|
}
|
|
@@ -166,12 +370,19 @@ export async function dispatchMcpToolCall(hctx, request, args, ctx) {
|
|
|
166
370
|
if (ev.runId === runId)
|
|
167
371
|
finalize(false);
|
|
168
372
|
});
|
|
373
|
+
const existingJob = getAsyncGate().getJob(runId);
|
|
374
|
+
if (existingJob?.status === 'completed')
|
|
375
|
+
finalize(true);
|
|
376
|
+
if (existingJob?.status === 'failed')
|
|
377
|
+
finalize(false);
|
|
169
378
|
// Return async receipt as MCP text so agent can poll
|
|
170
379
|
rawResult = {
|
|
171
380
|
content: [{
|
|
172
381
|
type: 'text',
|
|
173
|
-
text:
|
|
174
|
-
|
|
382
|
+
text: toolName === 'nexus_orchestrate'
|
|
383
|
+
? formatQueuedOrchestrateReceipt(args, gated)
|
|
384
|
+
: JSON.stringify({ queued: true, runId: gated.runId, etaMs: gated.etaMs }, null, 2)
|
|
385
|
+
+ '\n\nCall nexus_run_status(runId) to check progress.',
|
|
175
386
|
}],
|
|
176
387
|
};
|
|
177
388
|
}
|
|
@@ -55,6 +55,82 @@ export function extractSkillSelectorsFromPrompt(prompt) {
|
|
|
55
55
|
export function inferSpawnWorkersIntent(actions) {
|
|
56
56
|
return actions.length > 0 ? 'mutate' : 'plan';
|
|
57
57
|
}
|
|
58
|
+
function namedValue(value) {
|
|
59
|
+
if (typeof value === 'string')
|
|
60
|
+
return value.trim();
|
|
61
|
+
if (!value || typeof value !== 'object')
|
|
62
|
+
return '';
|
|
63
|
+
const record = value;
|
|
64
|
+
return String(record.name ?? record.id ?? record.specialistId ?? record.skillId ?? record.workflowId ?? record.crewId ?? record.workerId ?? '').trim();
|
|
65
|
+
}
|
|
66
|
+
function asStringList(values, limit = 8) {
|
|
67
|
+
const array = Array.isArray(values) ? values : values ? [values] : [];
|
|
68
|
+
return [...new Set(array.map(namedValue).filter(Boolean))].slice(0, limit);
|
|
69
|
+
}
|
|
70
|
+
function formatSelectionList(values, fallback = 'none') {
|
|
71
|
+
return values.length > 0 ? values.join(', ') : fallback;
|
|
72
|
+
}
|
|
73
|
+
function formatModelRoute(route) {
|
|
74
|
+
if (!route)
|
|
75
|
+
return 'default runtime route';
|
|
76
|
+
const worker = String(route.workerTier ?? 'T1');
|
|
77
|
+
const reviewer = route.reviewerTier ? ` + reviewer ${route.reviewerTier}` : '';
|
|
78
|
+
const reason = route.reason ? ` - ${route.reason}` : '';
|
|
79
|
+
return `${worker}${reviewer}${reason}`;
|
|
80
|
+
}
|
|
81
|
+
function formatBudgetRoute(plan) {
|
|
82
|
+
const budgets = plan?.budgets ?? {};
|
|
83
|
+
const policy = plan?.executionPolicy?.agentFlow ?? {};
|
|
84
|
+
const total = Number(budgets.totalTokens ?? 0);
|
|
85
|
+
const codeBlocks = Number(budgets.codeBlocks ?? budgets.codeBlockPolicy?.reservedTokens ?? 0);
|
|
86
|
+
const cap = Number(policy.maxTaskCostUsd ?? 0);
|
|
87
|
+
const parts = [
|
|
88
|
+
total > 0 ? `${total.toLocaleString()} tokens` : 'budget pending',
|
|
89
|
+
codeBlocks > 0 ? `${codeBlocks.toLocaleString()} code-block tokens` : null,
|
|
90
|
+
cap > 0 ? `$${cap.toFixed(2)} task cap` : null,
|
|
91
|
+
].filter(Boolean);
|
|
92
|
+
return parts.join(' · ');
|
|
93
|
+
}
|
|
94
|
+
function buildSelectionSummary(execution, runtimeUsage) {
|
|
95
|
+
const selectionPlan = execution.selectionPlan ?? {};
|
|
96
|
+
const selected = selectionPlan.selected ?? {};
|
|
97
|
+
const planner = execution.plannerState ?? {};
|
|
98
|
+
const taskGraph = runtimeUsage.taskGraph ?? execution.taskGraph ?? {};
|
|
99
|
+
const workerPlan = runtimeUsage.workerPlan ?? execution.workerPlan ?? {};
|
|
100
|
+
const audit = runtimeUsage.artifactSelectionAudit ?? execution.artifactSelectionAudit ?? {};
|
|
101
|
+
const crew = asStringList(selected.crews, 2)[0]
|
|
102
|
+
?? namedValue(planner.selectedCrew)
|
|
103
|
+
?? 'baseline path';
|
|
104
|
+
const specialists = asStringList(selected.specialists, 8).length > 0
|
|
105
|
+
? asStringList(selected.specialists, 8)
|
|
106
|
+
: asStringList(planner.selectedSpecialists, 8);
|
|
107
|
+
const skills = asStringList(selected.skills, 8).length > 0
|
|
108
|
+
? asStringList(selected.skills, 8)
|
|
109
|
+
: asStringList(execution.activeSkills, 8).concat(asStringList(planner.selectedSkills, 8)).slice(0, 8);
|
|
110
|
+
const workflows = asStringList(selected.workflows, 8).length > 0
|
|
111
|
+
? asStringList(selected.workflows, 8)
|
|
112
|
+
: asStringList(execution.activeWorkflows, 8).concat(asStringList(planner.selectedWorkflows, 8)).slice(0, 8);
|
|
113
|
+
const workers = asStringList(selected.workers, 8).length > 0
|
|
114
|
+
? asStringList(selected.workers, 8)
|
|
115
|
+
: asStringList(execution.workerManifests, 8);
|
|
116
|
+
return {
|
|
117
|
+
crew,
|
|
118
|
+
specialists,
|
|
119
|
+
skills: [...new Set(skills)],
|
|
120
|
+
workflows: [...new Set(workflows)],
|
|
121
|
+
hooks: asStringList(selected.hooks, 8),
|
|
122
|
+
automations: asStringList(selected.automations, 8),
|
|
123
|
+
workers,
|
|
124
|
+
modelRoute: selectionPlan.modelRoute ?? execution.requestBrief?.modelPolicy,
|
|
125
|
+
budgetRoute: formatBudgetRoute(selectionPlan),
|
|
126
|
+
agentFlow: selectionPlan.executionPolicy?.agentFlow,
|
|
127
|
+
phaseCount: Array.isArray(taskGraph.phases) ? taskGraph.phases.length : 0,
|
|
128
|
+
workerLaneCount: Number(workerPlan.totalWorkers ?? workers.length ?? execution.workerResults?.length ?? 0),
|
|
129
|
+
mode: String(workerPlan.mode ?? execution.mode ?? 'autonomous'),
|
|
130
|
+
auditSelected: Array.isArray(audit.selected) ? audit.selected.length : 0,
|
|
131
|
+
auditRejected: Array.isArray(audit.rejected) ? audit.rejected.length : 0,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
58
134
|
export async function handleOrchestrationGroup(toolName, hctx, request, args, ctx) {
|
|
59
135
|
const runtimeError = requireRuntime(hctx);
|
|
60
136
|
if (runtimeError)
|
|
@@ -457,6 +533,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
457
533
|
result.modifiedFiles.forEach((file) => hctx.sessionDNA.recordFileModified(file));
|
|
458
534
|
});
|
|
459
535
|
const runtimeUsage = hctx.getRuntime().getUsageSnapshot();
|
|
536
|
+
const selectionSummary = buildSelectionSummary(execution, runtimeUsage);
|
|
460
537
|
hctx.sessionDNA.recordDecision('Orchestrated autonomous run completed', execution.result || summarizeExecution(execution), execution.state === 'merged' ? 0.95
|
|
461
538
|
: execution.state === 'inspected' ? 0.85
|
|
462
539
|
: execution.state === 'rolled_back' ? 0.55
|
|
@@ -491,6 +568,8 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
491
568
|
worktreeHealth: runtimeUsage.worktreeHealth,
|
|
492
569
|
ragUsageSummary: runtimeUsage.ragUsageSummary ?? execution.ragUsageSummary,
|
|
493
570
|
memoryScopeUsage: runtimeUsage.memoryScopeUsage ?? execution.memoryScopeUsage,
|
|
571
|
+
selection: selectionSummary,
|
|
572
|
+
modelRoute: selectionSummary.modelRoute,
|
|
494
573
|
verifiedWorkers,
|
|
495
574
|
continuationChildren: execution.continuationChildren.length,
|
|
496
575
|
executionPreset: preset ? { id: preset.id, name: preset.name, summary: preset.summary } : undefined,
|
|
@@ -525,6 +604,10 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
525
604
|
ragUsageSummary: runtimeUsage.ragUsageSummary ?? execution.ragUsageSummary,
|
|
526
605
|
memoryScopeUsage: runtimeUsage.memoryScopeUsage ?? execution.memoryScopeUsage,
|
|
527
606
|
memoryReconciliationSummary: runtimeUsage.memoryReconciliationSummary ?? execution.memoryReconciliationSummary,
|
|
607
|
+
requestBrief: execution.requestBrief,
|
|
608
|
+
selectionPlan: execution.selectionPlan,
|
|
609
|
+
selection: selectionSummary,
|
|
610
|
+
modelRoute: selectionSummary.modelRoute,
|
|
528
611
|
tokens: execution.tokenTelemetry,
|
|
529
612
|
verifiedWorkers,
|
|
530
613
|
continuationChildren: execution.continuationChildren,
|
|
@@ -545,6 +628,14 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
545
628
|
`Run ID: ${execution.runId}`,
|
|
546
629
|
`Summary: ${summarizeExecution(execution)}`,
|
|
547
630
|
`Crew: ${execution.plannerState?.selectedCrew?.name || 'baseline path'}`,
|
|
631
|
+
`Decomposition: ${selectionSummary.phaseCount} phase(s), ${selectionSummary.workerLaneCount} worker lane(s), ${selectionSummary.mode}`,
|
|
632
|
+
`Hired/selected: crew ${selectionSummary.crew}; specialists ${formatSelectionList(selectionSummary.specialists)}; workflows ${formatSelectionList(selectionSummary.workflows)}; skills ${formatSelectionList(selectionSummary.skills)}`,
|
|
633
|
+
`Model route: ${formatModelRoute(selectionSummary.modelRoute)}`,
|
|
634
|
+
`Budget route: ${selectionSummary.budgetRoute || 'budget pending'}`,
|
|
635
|
+
selectionSummary.agentFlow?.stages?.length
|
|
636
|
+
? `AgentFlow gates: ${selectionSummary.agentFlow.stages.map((stage) => `${stage.stage}:${stage.ownerRole}`).join(' -> ')}`
|
|
637
|
+
: null,
|
|
638
|
+
`Selection audit: ${selectionSummary.auditSelected} selected, ${selectionSummary.auditRejected} rejected`,
|
|
548
639
|
`Verification: ${verifiedWorkers}/${execution.workerResults.length} worker(s) verified`,
|
|
549
640
|
`Tokens: saved ${Number(execution.tokenTelemetry?.savedTokens || 0).toLocaleString()} · compression ${Number(execution.tokenTelemetry?.compressionPct || 0)}%`,
|
|
550
641
|
autoTokenApplyNote || null,
|
|
@@ -17,6 +17,9 @@ const ENV_MAP = [
|
|
|
17
17
|
[['CURSOR_HOME', 'CURSOR_SESSION'], 'cursor'],
|
|
18
18
|
[['OPENCODE_HOME'], 'opencode'],
|
|
19
19
|
[['WINDSURF_HOME', 'WINDSURF_SESSION'], 'windsurf'],
|
|
20
|
+
[['HERMES_HOME', 'HERMES_SESSION'], 'hermes'],
|
|
21
|
+
[['NANOCLAW_HOME', 'NANOCLAW_SESSION'], 'nanoclaw'],
|
|
22
|
+
[['PICOCLAW_HOME', 'PICOCLAW_SESSION'], 'picoclaw'],
|
|
20
23
|
];
|
|
21
24
|
// Module-level store for the caller name obtained from the MCP initialize
|
|
22
25
|
// handshake. Takes precedence over env-var and ps fallback once set.
|
|
@@ -52,6 +55,18 @@ export function setMcpClientName(rawName) {
|
|
|
52
55
|
_mcpHandshakeName = 'windsurf';
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
58
|
+
if (lower.includes('hermes')) {
|
|
59
|
+
_mcpHandshakeName = 'hermes';
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (lower.includes('nanoclaw') || lower.includes('nano-claw')) {
|
|
63
|
+
_mcpHandshakeName = 'nanoclaw';
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (lower.includes('picoclaw') || lower.includes('pico-claw')) {
|
|
67
|
+
_mcpHandshakeName = 'picoclaw';
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
55
70
|
if (lower.includes('anticlaw') || lower.includes('openclaw')) {
|
|
56
71
|
_mcpHandshakeName = 'openclaw';
|
|
57
72
|
return;
|
|
@@ -95,6 +110,12 @@ export async function detectCallerAsync() {
|
|
|
95
110
|
return 'opencode';
|
|
96
111
|
if (ps.includes('windsurf'))
|
|
97
112
|
return 'windsurf';
|
|
113
|
+
if (ps.includes('hermes'))
|
|
114
|
+
return 'hermes';
|
|
115
|
+
if (ps.includes('nanoclaw') || ps.includes('nano-claw'))
|
|
116
|
+
return 'nanoclaw';
|
|
117
|
+
if (ps.includes('picoclaw') || ps.includes('pico-claw'))
|
|
118
|
+
return 'picoclaw';
|
|
98
119
|
if (ps.includes('antigravity') || ps.includes('openclaw'))
|
|
99
120
|
return 'openclaw';
|
|
100
121
|
}
|
|
@@ -719,7 +719,7 @@ export class MCPAdapter {
|
|
|
719
719
|
try {
|
|
720
720
|
this.getRuntime().recordClientInstructionStatus({
|
|
721
721
|
clientId: this.name,
|
|
722
|
-
clientFamily: this.name === 'openclaw' ? 'antigravity' : this.name,
|
|
722
|
+
clientFamily: this.name === 'openclaw' || this.name === 'antigravity' ? 'antigravity' : this.name,
|
|
723
723
|
toolProfile: profile,
|
|
724
724
|
status: profile === 'autonomous' ? 'guided' : 'manual',
|
|
725
725
|
summary: this.describeClientInstructionStatus(profile),
|
|
@@ -24,6 +24,15 @@ declare abstract class RenderedInstructionAdapter implements Adapter {
|
|
|
24
24
|
export declare class OpenClawAdapter extends RenderedInstructionAdapter {
|
|
25
25
|
constructor();
|
|
26
26
|
}
|
|
27
|
+
export declare class HermesAdapter extends RenderedInstructionAdapter {
|
|
28
|
+
constructor();
|
|
29
|
+
}
|
|
30
|
+
export declare class NanoClawAdapter extends RenderedInstructionAdapter {
|
|
31
|
+
constructor();
|
|
32
|
+
}
|
|
33
|
+
export declare class PicoClawAdapter extends RenderedInstructionAdapter {
|
|
34
|
+
constructor();
|
|
35
|
+
}
|
|
27
36
|
export declare class ClaudeCodeAdapter extends RenderedInstructionAdapter {
|
|
28
37
|
constructor();
|
|
29
38
|
}
|
|
@@ -60,5 +69,5 @@ export declare class CustomAdapter extends RenderedInstructionAdapter {
|
|
|
60
69
|
setSendHandler(handler: (message: NetworkMessage) => Promise<void>): void;
|
|
61
70
|
setReceiveHandler(handler: (message: NetworkMessage) => void): void;
|
|
62
71
|
}
|
|
63
|
-
export type AdapterType = 'openclaw' | 'claude-code' | 'ruflo' | 'langchain' | 'autogen' | 'custom' | 'mcp' | 'codex' | 'opencode' | 'cursor' | 'windsurf' | 'aider' | 'continue' | 'cline';
|
|
72
|
+
export type AdapterType = 'openclaw' | 'hermes' | 'nanoclaw' | 'picoclaw' | 'claude-code' | 'ruflo' | 'langchain' | 'autogen' | 'custom' | 'mcp' | 'codex' | 'opencode' | 'cursor' | 'windsurf' | 'aider' | 'continue' | 'cline';
|
|
64
73
|
export declare function createAdapter(type: AdapterType, customName?: string): Adapter;
|
package/dist/agents/adapters.js
CHANGED
|
@@ -78,6 +78,21 @@ export class OpenClawAdapter extends RenderedInstructionAdapter {
|
|
|
78
78
|
super('openclaw', 'openclaw');
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
export class HermesAdapter extends RenderedInstructionAdapter {
|
|
82
|
+
constructor() {
|
|
83
|
+
super('hermes', 'hermes');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export class NanoClawAdapter extends RenderedInstructionAdapter {
|
|
87
|
+
constructor() {
|
|
88
|
+
super('nanoclaw', 'nanoclaw');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export class PicoClawAdapter extends RenderedInstructionAdapter {
|
|
92
|
+
constructor() {
|
|
93
|
+
super('picoclaw', 'picoclaw');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
81
96
|
export class ClaudeCodeAdapter extends RenderedInstructionAdapter {
|
|
82
97
|
constructor() {
|
|
83
98
|
super('claude-code', 'claude-code');
|
|
@@ -158,6 +173,12 @@ export function createAdapter(type, customName) {
|
|
|
158
173
|
switch (type) {
|
|
159
174
|
case 'openclaw':
|
|
160
175
|
return new OpenClawAdapter();
|
|
176
|
+
case 'hermes':
|
|
177
|
+
return new HermesAdapter();
|
|
178
|
+
case 'nanoclaw':
|
|
179
|
+
return new NanoClawAdapter();
|
|
180
|
+
case 'picoclaw':
|
|
181
|
+
return new PicoClawAdapter();
|
|
161
182
|
case 'claude-code':
|
|
162
183
|
return new ClaudeCodeAdapter();
|
|
163
184
|
case 'ruflo':
|
|
@@ -146,7 +146,7 @@ export interface EvolutionConfig {
|
|
|
146
146
|
}
|
|
147
147
|
export interface Adapter {
|
|
148
148
|
name: string;
|
|
149
|
-
type: 'openclaw' | 'claude-code' | 'ruflo' | 'langchain' | 'autogen' | 'custom' | 'mcp' | 'codex' | 'opencode' | 'cursor' | 'windsurf' | 'aider' | 'continue' | 'cline';
|
|
149
|
+
type: 'openclaw' | 'hermes' | 'nanoclaw' | 'picoclaw' | 'claude-code' | 'ruflo' | 'langchain' | 'autogen' | 'custom' | 'mcp' | 'codex' | 'opencode' | 'cursor' | 'windsurf' | 'aider' | 'continue' | 'cline';
|
|
150
150
|
connected: boolean;
|
|
151
151
|
agents: string[];
|
|
152
152
|
connect(): Promise<void>;
|