nexus-prime 7.9.24 → 7.9.26

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.
@@ -280,9 +280,10 @@ export function buildMcpToolDefinitions() {
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
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.' },
283
+ background: { type: 'boolean', description: 'Compatibility flag; nexus_orchestrate already returns a queued hiring preflight by default and continues in the async gate.' },
284
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.' }
285
+ waitMs: { type: 'number', description: 'Advanced/debug bounded wait for inline execution. Clamped to 45 seconds; normal orchestrate calls return a queued preflight immediately.' },
286
+ inline: { type: 'boolean', description: 'Advanced/debug only: wait for inline orchestrate output instead of the default fast queued preflight.' }
286
287
  },
287
288
  required: ['prompt'],
288
289
  },
@@ -100,6 +100,9 @@ function coerceBoundedWaitMs(value) {
100
100
  function shouldReturnQueuedReceipt(toolName, args) {
101
101
  if (!SLOW_TOOLS.has(toolName))
102
102
  return false;
103
+ if (toolName === 'nexus_orchestrate') {
104
+ return !isTruthyFlag(args.inline) && !isTruthyFlag(args.sync) && !isTruthyFlag(args.blocking);
105
+ }
103
106
  return isTruthyFlag(args.background)
104
107
  || isTruthyFlag(args.async)
105
108
  || isTruthyFlag(args.queue)
@@ -157,29 +160,35 @@ function inferQueuedOrchestratePreview(args, runId) {
157
160
  ...asStringList(args.skillNames),
158
161
  ...extractLinkedSelectors(prompt, '$'),
159
162
  ], 20);
163
+ const isRuntimeControlPlane = /orchestrat|synapse|architect|runtime|mcp|dispatch|route|routing|queue|queued|scheduler|model routing|worker|hiring|workflow selection|token|budget|memory|lifecycle/.test(lower);
164
+ const wantsResearch = /research|paper|literature|deep-research|source-grounded|cited/.test(lower);
165
+ const wantsMemory = /memory|recall|learning|decay|graph|mcp/.test(lower);
166
+ const wantsPerf = /fast|faster|millisecond|latency|performance|budget|token|optim/i.test(lower);
160
167
  const workflows = uniqueList([
161
168
  ...asStringList(args.workflows),
162
169
  ...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,
170
+ isRuntimeControlPlane ? 'orchestration-execution-loop' : undefined,
171
+ isRuntimeControlPlane ? 'backend-execution-loop' : undefined,
172
+ isRuntimeControlPlane ? 'testing-execution-loop' : undefined,
173
+ isRuntimeControlPlane && !wantsResearch ? 'typescript-execution-loop' : undefined,
174
+ wantsResearch ? 'research-and-implement' : undefined,
175
+ lower.match(/dashboard|board|ui|ux/) && !isRuntimeControlPlane ? 'frontend-execution-loop' : undefined,
168
176
  ], 8);
169
177
  const specialists = uniqueList([
170
178
  ...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,
179
+ isRuntimeControlPlane ? 'Agents Orchestrator' : undefined,
180
+ isRuntimeControlPlane ? 'Backend Architect' : undefined,
181
+ isRuntimeControlPlane ? 'Workflow Optimizer' : undefined,
182
+ wantsPerf ? 'Performance Benchmarker' : undefined,
183
+ wantsMemory && !wantsPerf ? 'Backend Architect' : undefined,
184
+ lower.match(/dashboard|board|ui|ux/) && !isRuntimeControlPlane ? 'Frontend Developer' : undefined,
176
185
  lower.match(/test|verify|qa|release|publish/) ? 'Verification Engineer' : undefined,
177
186
  ], 8);
178
187
  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'));
188
+ ? 'Implementation Crew'
189
+ : lower.match(/research|paper|literature/)
190
+ ? 'Research Crew'
191
+ : 'Implementation Crew'));
183
192
  const risk = lower.match(/fix|broken|bug|doesn.?t|failed|queued|runtime|synapse|orchestrat/) ? 'high' : 'medium';
184
193
  const task = {
185
194
  goal: prompt,
@@ -479,6 +479,38 @@
479
479
  color: var(--text-main); margin-bottom: 4px;
480
480
  }
481
481
  .frh-sub { font-size: var(--text-sm); color: var(--text-dim); }
482
+ .frh-steps {
483
+ display: grid;
484
+ grid-template-columns: repeat(3, minmax(0, 1fr));
485
+ gap: 8px;
486
+ margin-bottom: 14px;
487
+ }
488
+ .frh-step {
489
+ display: grid;
490
+ grid-template-columns: auto 1fr;
491
+ grid-template-rows: auto auto;
492
+ gap: 1px 8px;
493
+ align-items: center;
494
+ padding: 9px 10px;
495
+ border: 1px solid color-mix(in oklch, var(--accent) 24%, var(--border));
496
+ border-radius: var(--radius);
497
+ background: color-mix(in oklch, var(--accent) 4%, var(--bg-panel));
498
+ min-width: 0;
499
+ }
500
+ .frh-step span {
501
+ grid-row: 1 / span 2;
502
+ display: inline-grid;
503
+ place-items: center;
504
+ width: 22px;
505
+ height: 22px;
506
+ border-radius: 50%;
507
+ background: color-mix(in oklch, var(--accent) 18%, transparent);
508
+ color: var(--accent);
509
+ font-family: var(--font-mono);
510
+ font-size: 0.72rem;
511
+ }
512
+ .frh-step strong { font-size: 0.78rem; color: var(--text-main); min-width: 0; }
513
+ .frh-step small { font-size: 0.72rem; color: var(--text-dim); min-width: 0; }
482
514
  .frh-command {
483
515
  display: grid;
484
516
  grid-template-columns: minmax(220px, 1fr) auto auto;
@@ -492,6 +524,7 @@
492
524
  transition: border-color 0.15s;
493
525
  }
494
526
  .frh-pick:hover { border-color: var(--accent); }
527
+ .frh-pick-loading { border-style: dashed; }
495
528
  .frh-pick-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-main); }
496
529
  .frh-pick-desc { font-size: 0.78rem; color: var(--text-dim); flex: 1; }
497
530
  .frh-pick-cost { font-family: var(--font-mono); font-size: 0.75rem; color: var(--accent); margin-top: 2px; }
@@ -507,6 +540,7 @@
507
540
  }
508
541
  @media (max-width: 768px) {
509
542
  #kanban-board { grid-template-columns: repeat(2, 1fr); }
543
+ .frh-steps,
510
544
  .frh-command,
511
545
  .frh-picks { grid-template-columns: 1fr; }
512
546
  }
@@ -376,7 +376,6 @@ function renderFirstRunHero() {
376
376
  if (hasOps || (alreadySeen && hasRuns)) return;
377
377
 
378
378
  const specs = (S.curatedSpecialists || []).slice(0, 3);
379
- if (!specs.length) return; // Still loading — will re-render when prefetch resolves
380
379
  const readiness = getHireReadiness();
381
380
  const noticesHtml = readiness.notes.length
382
381
  ? `<div class="frh-notices" style="display:flex;flex-direction:column;gap:8px;margin-bottom:12px">
@@ -389,23 +388,33 @@ function renderFirstRunHero() {
389
388
  card.className = 'first-run-hero card';
390
389
  card.innerHTML = `
391
390
  <div class="frh-header">
392
- <div class="frh-title">${hasRuns ? 'Your next hire' : 'Start the first real run'}</div>
393
- <div class="frh-sub">${hasRuns ? 'Hire a specialist to start running tasks autonomously.' : 'Run a goal from the dashboard or hire a specialist. The run will appear in Board and Context Log.'}</div>
391
+ <div class="frh-title">${hasRuns ? 'Choose the next worker' : 'Start the first real run'}</div>
392
+ <div class="frh-sub">${hasRuns ? 'Hire a specialist or queue a goal. Nexus will show the route, budget, workers, and verification here.' : 'Queue one goal from the dashboard. Nexus will route it, show who gets hired, and write the run trail into Board and Context Log.'}</div>
394
393
  </div>
395
394
  ${noticesHtml}
395
+ <div class="frh-steps" aria-label="Onboarding steps">
396
+ <div class="frh-step"><span>1</span><strong>Describe goal</strong><small>Run or hire from here.</small></div>
397
+ <div class="frh-step"><span>2</span><strong>Watch route</strong><small>Board shows workers and budget.</small></div>
398
+ <div class="frh-step"><span>3</span><strong>Verify proof</strong><small>Context Log keeps artifacts.</small></div>
399
+ </div>
396
400
  <div class="frh-command">
397
401
  <input id="frh-goal-input" class="form-input" type="text" placeholder="Inspect this repo and suggest the next fix" autocomplete="off">
398
- <button class="btn btn-primary btn-sm" id="frh-run-btn">Run goal</button>
402
+ <button class="btn btn-primary btn-sm" id="frh-run-btn">Run first goal</button>
399
403
  <button class="btn btn-sm" id="frh-context-btn">Open context</button>
400
404
  </div>
401
405
  <div class="frh-picks">
402
- ${specs.map(s => `
406
+ ${specs.length ? specs.map(s => `
403
407
  <div class="frh-pick" data-specid="${esc(s.specialistId)}" data-specname="${esc(s.name)}">
404
408
  <div class="frh-pick-name">${esc(s.name)}</div>
405
409
  <div class="frh-pick-desc">${esc((s.description||'').slice(0, 72))}${(s.description||'').length > 72 ? '…' : ''}</div>
406
410
  <div class="frh-pick-cost">~$${esc(String(s.pricing?.typical ?? '?'))}/sortie</div>
407
411
  <button class="btn btn-primary btn-sm frh-hire-btn" data-specid="${esc(s.specialistId)}" data-specname="${esc(s.name)}" ${readiness.unavailable ? 'disabled title="Synapse is not ready"' : ''}>Hire</button>
408
- </div>`).join('')}
412
+ </div>`).join('') : `
413
+ <div class="frh-pick frh-pick-loading">
414
+ <div class="frh-pick-name">Specialists loading</div>
415
+ <div class="frh-pick-desc">You can run a goal immediately. Hiring picks will appear when the catalog responds.</div>
416
+ <div class="frh-pick-cost">route first, hire second</div>
417
+ </div>`}
409
418
  </div>
410
419
  <div id="frh-status" style="display:none;margin-top:12px;font-size:var(--text-sm)"></div>
411
420
  <button class="btn btn-ghost btn-sm frh-dismiss" style="margin-top:var(--space-3)">Dismiss</button>`;
@@ -424,6 +433,7 @@ function renderFirstRunHero() {
424
433
  const result = await post('/api/orchestrate', { goal, source: 'dashboard-onboarding' });
425
434
  if (result.ok) {
426
435
  setFirstRunStatus('Run queued. Board and Context Log will update as Nexus writes artifacts.');
436
+ try { localStorage.setItem(FIRST_RUN_KEY, '1'); } catch { /* ignore */ }
427
437
  bustCache('/api/runs?limit=12');
428
438
  bustCache('/api/events');
429
439
  setTimeout(load, 900);
@@ -431,7 +441,7 @@ function renderFirstRunHero() {
431
441
  setFirstRunStatus(result.error || 'Run failed to queue.', 'bad');
432
442
  if (button) {
433
443
  button.disabled = false;
434
- button.textContent = 'Run goal';
444
+ button.textContent = 'Run first goal';
435
445
  }
436
446
  }
437
447
  });
@@ -508,6 +518,21 @@ function buildKanbanCols() {
508
518
  if (sg) cols[sg].push({id:w.id||w.workerId||w.goal,goal:w.goal||w.task||w.approach||'(worker)',status:st,tokens:w.tokensUsed||w.budget,time:w.startedAt||w.createdAt,role:w.role});
509
519
  }
510
520
  }
521
+ const ghost = S.lastDecomposition?.autoGhostPass || S.lastCompletion?.autoGhostPass || op?.orchestration?.autoGhostPass || op?.autoGhostPass;
522
+ if (ghost && (ghost.applied || ghost.policy?.reason)) {
523
+ const risks = Array.isArray(ghost.riskAreas) ? ghost.riskAreas.length : 0;
524
+ const reason = ghost.policy?.reason ? ` · ${ghost.policy.reason}` : '';
525
+ cols.ghostpass.push({
526
+ id: 'auto-ghostpass',
527
+ goal: ghost.applied
528
+ ? `Auto Ghost Pass: ${risks} risk area${risks === 1 ? '' : 's'}, ${ghost.workerApproaches || 0} approach${ghost.workerApproaches === 1 ? '' : 'es'}`
529
+ : `Auto Ghost Pass skipped${reason}`,
530
+ status: ghost.applied ? 'reviewing' : 'skipped',
531
+ tokens: ghost.estimatedTokens,
532
+ time: S.lastDecomposition?.ts || S.lastCompletion?.ts,
533
+ role: 'ghost-pass',
534
+ });
535
+ }
511
536
  for (const r of (S.runs||[]).slice(0,8)) {
512
537
  const runId = r.runId || r.id;
513
538
  if (!runId) continue;
@@ -874,11 +899,16 @@ function renderOrchestrationPipeline() {
874
899
  const more = arr.length > 6 ? ` <span style="color:var(--muted)">+${arr.length - 6}</span>` : '';
875
900
  return `<div style="margin:4px 0"><span style="color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:0.5px">${esc(label)}</span> <span style="font-family:var(--font-mono);font-size:12px">${head}${more}</span></div>`;
876
901
  };
902
+ const ghostChip = (ghost) => ghost
903
+ ? chip('ghost-pass', ghost.applied
904
+ ? `${ghost.riskAreas?.length ?? 0} risks · ${ghost.workerApproaches ?? 0} approaches`
905
+ : `skipped${ghost.policy?.reason ? ` · ${ghost.policy.reason}` : ''}`)
906
+ : '';
877
907
  const decBlock = dec ? `
878
908
  <div style="border-left:3px solid var(--accent);padding:8px 12px;margin-bottom:8px">
879
909
  <div style="font-size:13px;font-weight:600;margin-bottom:4px">Decomposition · run ${esc((dec.runId || '').slice(-8))}</div>
880
910
  <div style="font-size:12px;color:var(--muted);margin-bottom:6px">${esc(dec.goal || '')}</div>
881
- <div>${chip('intent', dec.intent || 'auto')}${chip('crew', dec.crew || 'baseline')}${chip('workers', dec.workers ?? 0)}${chip('phases', dec.phases ?? 0)}</div>
911
+ <div>${chip('intent', dec.intent || 'auto')}${chip('crew', dec.crew || 'baseline')}${chip('workers', dec.workers ?? 0)}${chip('phases', dec.phases ?? 0)}${ghostChip(dec.autoGhostPass)}</div>
882
912
  ${chipList('specialists', dec.specialists)}
883
913
  ${chipList('skills', dec.skills)}
884
914
  ${chipList('files', dec.files)}
@@ -888,7 +918,7 @@ function renderOrchestrationPipeline() {
888
918
  <div style="border-left:3px solid ${stateColor(cmpState)};padding:8px 12px">
889
919
  <div style="font-size:13px;font-weight:600;margin-bottom:4px">Completion · run ${esc((cmp.runId || '').slice(-8))} · <span style="color:${stateColor(cmpState)}">${esc(cmpState || '')}</span></div>
890
920
  <div style="font-size:12px;color:var(--muted);margin-bottom:6px">${esc(cmp.result || '')}</div>
891
- <div>${chip('verified', `${cmp.verifiedWorkers ?? 0}/${cmp.totalWorkers ?? 0}`)}${chip('saved', `${fmtNum(cmp.savedTokens ?? 0)} t`)}${chip('compression', `${cmp.compressionPct ?? 0}%`)}${chip('duration', `${Math.round((cmp.durationMs ?? 0) / 100) / 10}s`)}</div>
921
+ <div>${chip('verified', `${cmp.verifiedWorkers ?? 0}/${cmp.totalWorkers ?? 0}`)}${chip('saved', `${fmtNum(cmp.savedTokens ?? 0)} t`)}${chip('compression', `${cmp.compressionPct ?? 0}%`)}${chip('duration', `${Math.round((cmp.durationMs ?? 0) / 100) / 10}s`)}${ghostChip(cmp.autoGhostPass)}</div>
892
922
  </div>` : '';
893
923
  const spineBlock = spine ? _buildDecisionSpineMiniHtml(spine) : '';
894
924
  const headerNote = same ? '' : (dec && cmp ? '<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Showing latest decomposition + most recent completion (different runs)</div>' : '');
@@ -93,6 +93,16 @@ export interface NexusEventPayloads {
93
93
  files: string[];
94
94
  workers: number;
95
95
  phases: number;
96
+ autoGhostPass?: {
97
+ applied: boolean;
98
+ riskAreas: string[];
99
+ workerApproaches: number;
100
+ estimatedTokens: number;
101
+ policy?: {
102
+ enabled?: boolean;
103
+ reason?: string;
104
+ };
105
+ };
96
106
  };
97
107
  'orchestration.completed': {
98
108
  runId: string;
@@ -104,6 +114,12 @@ export interface NexusEventPayloads {
104
114
  compressionPct: number;
105
115
  durationMs: number;
106
116
  result: string;
117
+ autoGhostPass?: {
118
+ applied: boolean;
119
+ riskAreas: string[];
120
+ workerApproaches: number;
121
+ estimatedTokens: number;
122
+ };
107
123
  };
108
124
  'session.summaryBootstrap': {
109
125
  originalTokens: number;
@@ -183,6 +183,8 @@ export declare class OrchestratorEngine {
183
183
  private searchIndexedRepoCandidates;
184
184
  private toFileRef;
185
185
  private resolveSelections;
186
+ private inferRuntimeCatalogHints;
187
+ private filterExistingCatalogSelectors;
186
188
  private getSkillCatalogItems;
187
189
  private getWorkflowCatalogItems;
188
190
  private getHookCatalogItems;
@@ -1036,7 +1036,7 @@ export class OrchestratorEngine {
1036
1036
  // Non-fatal: bootstrap receipt is a safety net, not a hard dependency
1037
1037
  }
1038
1038
  const [army, prepared] = await this.runParallelPhases(this.induce(task), this._prepareExecution(task, options));
1039
- const { intent, phases, primaryClient, bootstrapManifest, latestDNA, memoryMatches, memoryStats, candidateFiles, knowledgeFabric, plannedFiles, planner, selections, catalogHealth, tokenBudget, workerCount, mode, taskGraph, workerPlan, autoGhostPass, crSignals, } = prepared;
1039
+ const { intent, phases, primaryClient, bootstrapManifest, latestDNA, memoryMatches, memoryStats, candidateFiles, knowledgeFabric, plannedFiles, planner, selections, catalogHealth, tokenBudget, workerCount, mode, taskGraph, workerPlan, autoGhostPass, autoGhostPassDecision, crSignals, } = prepared;
1040
1040
  this.runtime.recordClientToolCall('nexus_orchestrate', {
1041
1041
  orchestrateCalled: true,
1042
1042
  plannerCalled: true,
@@ -1314,6 +1314,8 @@ export class OrchestratorEngine {
1314
1314
  instructionPacket,
1315
1315
  executionLedger: ledger,
1316
1316
  knowledgeFabric,
1317
+ autoGhostPass,
1318
+ autoGhostPassDecision,
1317
1319
  });
1318
1320
  this.executionDedupeStore.set(fingerprint, {
1319
1321
  id: ledger.runId,
@@ -2382,10 +2384,16 @@ export class OrchestratorEngine {
2382
2384
  limit: 5,
2383
2385
  selector: 'name',
2384
2386
  });
2385
- const workflowSelection = this.resolveCatalogVotes('workflow', task, intent, options.workflowSelectors?.length ? allWorkflowItems : workflowItems, {
2387
+ const runtimeCatalogHints = this.inferRuntimeCatalogHints(task, intent, {
2388
+ workflows: allWorkflowItems,
2389
+ specialists: allSpecialistItems,
2390
+ crews: allCrewItems,
2391
+ });
2392
+ const workflowSelection = this.resolveCatalogVotes('workflow', task, intent, options.workflowSelectors?.length || runtimeCatalogHints.workflows.length ? allWorkflowItems : workflowItems, {
2386
2393
  explicit: options.workflowSelectors,
2387
2394
  planner: planner.selectedWorkflows,
2388
2395
  knowledge: knowledgeFabric.recommendations.workflows,
2396
+ runtime: runtimeCatalogHints.workflows,
2389
2397
  scorerLimit: 4,
2390
2398
  limit: 4,
2391
2399
  selector: 'name',
@@ -2423,18 +2431,20 @@ export class OrchestratorEngine {
2423
2431
  limit: intent.taskType === 'release' || this.sessionState.repeatedFailures > 0 ? 3 : 2,
2424
2432
  selector: 'name',
2425
2433
  });
2426
- const specialistSelection = this.resolveCatalogVotes('specialist', task, intent, options.specialistSelectors?.length ? allSpecialistItems : specialistItems, {
2434
+ const specialistSelection = this.resolveCatalogVotes('specialist', task, intent, options.specialistSelectors?.length || runtimeCatalogHints.specialists.length ? allSpecialistItems : specialistItems, {
2427
2435
  explicit: options.specialistSelectors,
2428
2436
  planner: planner.selectedSpecialists.map((specialist) => specialist.specialistId),
2429
2437
  knowledge: knowledgeFabric.recommendations.specialists,
2438
+ runtime: runtimeCatalogHints.specialists,
2430
2439
  scorerLimit: 4,
2431
2440
  limit: 4,
2432
2441
  selector: 'id',
2433
2442
  });
2434
- const crewSelection = this.resolveCatalogVotes('crew', task, intent, options.crewSelectors?.length ? allCrewItems : crewItems, {
2443
+ const crewSelection = this.resolveCatalogVotes('crew', task, intent, options.crewSelectors?.length || runtimeCatalogHints.crews.length ? allCrewItems : crewItems, {
2435
2444
  explicit: options.crewSelectors,
2436
2445
  planner: planner.selectedCrew ? [planner.selectedCrew.crewId] : [],
2437
2446
  knowledge: knowledgeFabric.recommendations.crews,
2447
+ runtime: runtimeCatalogHints.crews,
2438
2448
  scorerLimit: 2,
2439
2449
  limit: 1,
2440
2450
  selector: 'id',
@@ -2504,6 +2514,47 @@ export class OrchestratorEngine {
2504
2514
  },
2505
2515
  };
2506
2516
  }
2517
+ inferRuntimeCatalogHints(task, intent, catalog) {
2518
+ const lower = task.toLowerCase();
2519
+ const isControlPlane = /\b(orchestrat(?:e|or|ion)|synapse|architects?|mcp|dispatch|routing|route|queued?|queue|scheduler|runtime|control plane|agentflow|worker|hiring|specialist|crew|workflow selection|model routing|token budget|memory hook|memory graph|lifecycle)\b/.test(lower);
2520
+ if (!isControlPlane) {
2521
+ return { workflows: [], specialists: [], crews: [] };
2522
+ }
2523
+ const wantsResearch = /\b(research|papers?|literature|source-grounded|cited|deep-research)\b/.test(lower);
2524
+ const wantsMemory = /\b(memory|recall|learning|decay|graph|mcp)\b/.test(lower);
2525
+ const wantsPerf = /\b(fast|faster|milliseconds?|latency|performance|budget|token|optim(?:i|is|iz))\b/.test(lower);
2526
+ const readOnlyResearch = intent.taskType === 'research'
2527
+ && !/\b(fix|patch|implement|refactor|change|add|wire|ship|deploy|release|mutate|improve)\b/.test(lower);
2528
+ const workflowSelectors = [
2529
+ 'orchestration-execution-loop',
2530
+ 'backend-execution-loop',
2531
+ 'testing-execution-loop',
2532
+ wantsResearch ? 'research-and-implement' : 'typescript-execution-loop',
2533
+ ];
2534
+ const specialistSelectors = [
2535
+ 'specialist_specialized-agents-orchestrator',
2536
+ 'specialist_engineering-engineering-backend-architect',
2537
+ 'specialist_testing-testing-workflow-optimizer',
2538
+ wantsPerf
2539
+ ? 'specialist_testing-testing-performance-benchmarker'
2540
+ : wantsMemory
2541
+ ? 'specialist_integrations-mcp-memory-backend-architect-with-memory'
2542
+ : undefined,
2543
+ ];
2544
+ const crewSelectors = [
2545
+ readOnlyResearch ? 'crew_research' : 'crew_implementation',
2546
+ ];
2547
+ return {
2548
+ workflows: this.filterExistingCatalogSelectors(catalog.workflows, workflowSelectors, 'name'),
2549
+ specialists: this.filterExistingCatalogSelectors(catalog.specialists, specialistSelectors, 'id'),
2550
+ crews: this.filterExistingCatalogSelectors(catalog.crews, crewSelectors, 'id'),
2551
+ };
2552
+ }
2553
+ filterExistingCatalogSelectors(items, selectors, selector) {
2554
+ const existing = new Set(items.map((item) => selector === 'id' ? item.id : item.name));
2555
+ return dedupeStrings(selectors.filter((value) => Boolean(value)))
2556
+ .filter((value) => existing.has(value));
2557
+ }
2507
2558
  getSkillCatalogItems() {
2508
2559
  const skills = this.runtime.listSkillsForSelection();
2509
2560
  const signature = skills
@@ -2657,7 +2708,7 @@ export class OrchestratorEngine {
2657
2708
  applyVote(value, 'knowledge-fabric', 0.76, 'medium', 'Knowledge Fabric recommended this artifact from cross-source evidence.');
2658
2709
  });
2659
2710
  (input.runtime ?? []).forEach((value) => {
2660
- applyVote(value, 'runtime-resolver', 0.84, 'high', 'Runtime resolver matched this artifact against the registered catalog for the active goal.');
2711
+ applyVote(value, 'runtime-resolver', 2.0, 'high', 'Runtime resolver matched this artifact against the registered catalog for the active goal.');
2661
2712
  });
2662
2713
  this.pickCatalogEntries(task, intent, items, input.scorerLimit).forEach((entry) => {
2663
2714
  applyVote(input.selector === 'id' ? entry.item.id : entry.item.name, 'scorer', Math.min(0.7, entry.score / 10), entry.score >= 9 ? 'medium' : 'low', `Keyword scorer matched the task with score ${entry.score}.`);
@@ -24,6 +24,10 @@ export function getNexusHookSpec() {
24
24
  matcher: 'Edit|Write|MultiEdit',
25
25
  hooks: [{ type: 'command', command: 'nexus-prime hook mindkit', timeout: 10 }],
26
26
  },
27
+ {
28
+ matcher: 'MultiEdit',
29
+ hooks: [{ type: 'command', command: 'nexus-prime hook ghost-pass', timeout: 15 }],
30
+ },
27
31
  ],
28
32
  PostToolUse: [
29
33
  {
@@ -101,6 +101,12 @@ export interface ExecutionTask {
101
101
  instructionPacket?: InstructionPacket;
102
102
  executionLedger?: ExecutionLedger;
103
103
  knowledgeFabric?: KnowledgeFabricBundle;
104
+ autoGhostPass?: GhostReport;
105
+ autoGhostPassDecision?: {
106
+ enabled: boolean;
107
+ reason: string;
108
+ contextHash?: string;
109
+ };
104
110
  }
105
111
  export interface WorkerSkillOverlay {
106
112
  base: string[];
@@ -225,6 +225,16 @@ export class SubAgentRuntime {
225
225
  files: (task.files ?? []).slice(0, 24),
226
226
  workers: planner.plannerState.selectedSpecialists.length || 1,
227
227
  phases: planner.plannerState.ledger.length,
228
+ autoGhostPass: {
229
+ applied: Boolean(task.autoGhostPass),
230
+ riskAreas: task.autoGhostPass?.riskAreas ?? [],
231
+ workerApproaches: task.autoGhostPass?.workerAssignments?.length ?? 0,
232
+ estimatedTokens: task.autoGhostPass?.totalEstimatedTokens ?? 0,
233
+ policy: task.autoGhostPassDecision ? {
234
+ enabled: task.autoGhostPassDecision.enabled,
235
+ reason: task.autoGhostPassDecision.reason,
236
+ } : undefined,
237
+ },
228
238
  });
229
239
  }
230
240
  catch { /* best-effort */ }
@@ -765,6 +775,12 @@ export class SubAgentRuntime {
765
775
  compressionPct: Number(tt?.compressionPct ?? 0),
766
776
  durationMs: Date.now() - runStartedAt,
767
777
  result: String(applied.summary ?? '').slice(0, 280),
778
+ autoGhostPass: {
779
+ applied: Boolean(task.autoGhostPass),
780
+ riskAreas: task.autoGhostPass?.riskAreas ?? [],
781
+ workerApproaches: task.autoGhostPass?.workerAssignments?.length ?? 0,
782
+ estimatedTokens: task.autoGhostPass?.totalEstimatedTokens ?? 0,
783
+ },
768
784
  });
769
785
  }
770
786
  catch { /* best-effort */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.9.24",
3
+ "version": "7.9.26",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",