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.
Files changed (54) hide show
  1. package/dist/agents/adapters/ide-compat.d.ts +3 -3
  2. package/dist/agents/adapters/ide-compat.js +51 -1
  3. package/dist/agents/adapters/mcp/definitions.js +4 -1
  4. package/dist/agents/adapters/mcp/dispatch.js +217 -6
  5. package/dist/agents/adapters/mcp/handlers/orchestration.js +91 -0
  6. package/dist/agents/adapters/mcp/runHandler.js +3 -0
  7. package/dist/agents/adapters/mcp/util/detect-caller.js +21 -0
  8. package/dist/agents/adapters/mcp.js +1 -1
  9. package/dist/agents/adapters.d.ts +10 -1
  10. package/dist/agents/adapters.js +21 -0
  11. package/dist/agents/core/types.d.ts +1 -1
  12. package/dist/cli/hook.d.ts +4 -6
  13. package/dist/cli/hook.js +6 -8
  14. package/dist/cli/install-wizard.js +5 -1
  15. package/dist/cli.js +181 -15
  16. package/dist/core/types.d.ts +1 -1
  17. package/dist/dashboard/app/styles/board.css +85 -1
  18. package/dist/dashboard/app/views/board.js +56 -0
  19. package/dist/dashboard/app/views/memory.js +71 -10
  20. package/dist/dashboard/routes/events.js +3 -0
  21. package/dist/dashboard/selectors/operate-selector.js +5 -0
  22. package/dist/dashboard/selectors/runs-selector.js +5 -0
  23. package/dist/dashboard/server.js +6 -0
  24. package/dist/dashboard/types.d.ts +4 -0
  25. package/dist/engines/client-bootstrap.d.ts +5 -1
  26. package/dist/engines/client-bootstrap.js +105 -10
  27. package/dist/engines/client-registry.js +51 -0
  28. package/dist/engines/event-bus.d.ts +16 -2
  29. package/dist/engines/feature-registry.js +1 -0
  30. package/dist/engines/instruction-gateway.d.ts +9 -0
  31. package/dist/engines/instruction-gateway.js +113 -4
  32. package/dist/engines/memory/types.d.ts +28 -0
  33. package/dist/engines/memory-bridge.d.ts +1 -1
  34. package/dist/engines/memory-bridge.js +1 -1
  35. package/dist/engines/memory.d.ts +5 -0
  36. package/dist/engines/memory.js +144 -12
  37. package/dist/engines/orchestrator/decision-spine.d.ts +26 -0
  38. package/dist/engines/orchestrator/decision-spine.js +145 -6
  39. package/dist/engines/orchestrator/funnel.js +8 -1
  40. package/dist/engines/orchestrator/scoring.d.ts +1 -1
  41. package/dist/engines/orchestrator/scoring.js +24 -2
  42. package/dist/engines/orchestrator.d.ts +3 -0
  43. package/dist/engines/orchestrator.js +73 -13
  44. package/dist/engines/peer-connectors.d.ts +1 -1
  45. package/dist/engines/peer-connectors.js +9 -2
  46. package/dist/engines/runtime-registry.d.ts +9 -0
  47. package/dist/index.js +9 -0
  48. package/dist/install/state-locator.d.ts +1 -1
  49. package/dist/install/state-locator.js +3 -0
  50. package/dist/synapse/bootstrap.js +3 -0
  51. package/dist/synapse/mandate/pipeline.js +52 -5
  52. package/dist/synapse/sorties/runner.js +32 -0
  53. package/dist/synapse/types.d.ts +27 -0
  54. 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: 2000,
317
+ maxSyncMs: resolveMaxSyncMs(toolName, args),
131
318
  etaMs: TOOL_ETA_MS[toolName],
132
- alwaysQueue: toolName === 'nexus_orchestrate',
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: JSON.stringify({ queued: true, runId: gated.runId, etaMs: gated.etaMs }, null, 2)
174
- + '\n\nCall nexus_run_status(runId) to check progress.',
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,
@@ -21,6 +21,9 @@ const CLIENT_TIMEOUTS = {
21
21
  'claude-code': 60_000,
22
22
  'cursor': 30_000,
23
23
  'openclaw': 90_000,
24
+ 'hermes': 90_000,
25
+ 'nanoclaw': 90_000,
26
+ 'picoclaw': 90_000,
24
27
  'opencode': 90_000,
25
28
  'windsurf': 90_000,
26
29
  'antigravity': 90_000,
@@ -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;
@@ -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>;