nexus-prime 7.9.16 → 7.9.18
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/mcp/definitions.js +3 -2
- package/dist/agents/adapters/mcp/handlers/orchestration.js +85 -13
- package/dist/agents/adapters/mcp/handlers/runtime.js +4 -2
- package/dist/agents/adapters/mcp/types.js +9 -0
- package/dist/agents/adapters/mcp.js +2 -0
- package/dist/cli/hook.js +3 -2
- package/dist/cli/install-wizard.js +12 -4
- package/dist/cli.js +21 -9
- package/dist/dashboard/app/views/board.js +9 -3
- package/dist/dashboard/app/views/workforce.js +56 -18
- package/dist/dashboard/routes/architects.js +9 -1
- package/dist/dashboard/routes/governance.js +69 -19
- package/dist/dashboard/routes/runtime.js +36 -2
- package/dist/dashboard/routes/workforce.d.ts +34 -1
- package/dist/dashboard/routes/workforce.js +171 -0
- package/dist/dashboard/server.js +13 -4
- package/dist/engines/client-bootstrap.d.ts +1 -0
- package/dist/engines/client-bootstrap.js +16 -5
- package/dist/engines/event-bus.d.ts +2 -0
- package/dist/engines/mcp-entrypoint.d.ts +1 -1
- package/dist/engines/mcp-entrypoint.js +42 -4
- package/dist/engines/orchestrator.js +11 -0
- package/dist/index.js +1 -0
- package/dist/licensing/enforcement.js +1 -4
- package/dist/licensing/license-manager.js +4 -17
- package/dist/licensing/types.d.ts +1 -1
- package/dist/licensing/upgrade-prompts.js +13 -4
- package/dist/utils/ascii-art.js +2 -0
- package/package.json +2 -2
|
@@ -292,8 +292,9 @@ export function buildMcpToolDefinitions() {
|
|
|
292
292
|
properties: {
|
|
293
293
|
goal: { type: 'string', description: 'What you are trying to accomplish' },
|
|
294
294
|
task: { type: 'string', description: 'Alias for goal (deprecated, use goal)' },
|
|
295
|
-
files: { type: 'array', items: { type: 'string' }, description: 'File paths to analyze. If omitted, auto-scans
|
|
296
|
-
budget: { type: 'number', description: 'Token budget override' }
|
|
295
|
+
files: { type: 'array', items: { type: 'string' }, description: 'File paths to analyze. If omitted, auto-scans a capped source shortlist instead of the whole repo.' },
|
|
296
|
+
budget: { type: 'number', description: 'Token budget override' },
|
|
297
|
+
maxFiles: { type: 'number', description: 'Maximum files to include when files are omitted (default 80, max 200)' }
|
|
297
298
|
},
|
|
298
299
|
required: [],
|
|
299
300
|
},
|
|
@@ -14,12 +14,28 @@ import { GhostPass, summarizeExecution } from '../../../../phantom/index.js';
|
|
|
14
14
|
import { applyExecutionPreset, resolveExecutionPreset } from '../../../../engines/execution-presets.js';
|
|
15
15
|
import { pLimit } from '../../../../engines/util/p-limit.js';
|
|
16
16
|
import { promises as fsPromises } from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
17
18
|
import { getSharedLicenseManager } from '../../../../licensing/index.js';
|
|
18
19
|
import { SessionDNAManager } from '../../../../engines/session-dna.js';
|
|
19
20
|
import { getSharedTelemetry } from '../../../../engines/telemetry-remote.js';
|
|
20
21
|
import { requireRuntime } from '../util/require-runtime.js';
|
|
21
22
|
import { ensureCrGraphBuilt } from '../../../../engines/code-review-graph-client.js';
|
|
22
23
|
import { recordFirstBootstrap } from '../../../../engines/telemetry.js';
|
|
24
|
+
function escapeMarkdownTableCell(value) {
|
|
25
|
+
return String(value ?? '')
|
|
26
|
+
.replace(/\r?\n/g, ' ')
|
|
27
|
+
.replace(/\|/g, '\\|')
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
function markdownTable(headers, rows) {
|
|
31
|
+
if (rows.length === 0)
|
|
32
|
+
return '';
|
|
33
|
+
return [
|
|
34
|
+
`| ${headers.map(escapeMarkdownTableCell).join(' | ')} |`,
|
|
35
|
+
`| ${headers.map(() => '---').join(' | ')} |`,
|
|
36
|
+
...rows.map(row => `| ${row.map(escapeMarkdownTableCell).join(' | ')} |`),
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
23
39
|
export function extractSkillSelectorsFromPrompt(prompt) {
|
|
24
40
|
const selectors = new Set();
|
|
25
41
|
const add = (value) => {
|
|
@@ -282,6 +298,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
282
298
|
`Memory stats: prefrontal ${bootstrap.memoryStats?.prefrontal ?? 0} · hippocampus ${bootstrap.memoryStats?.hippocampus ?? 0} · cortex ${bootstrap.memoryStats?.cortex ?? 0}`,
|
|
283
299
|
`Recommended next step: ${bootstrap.recommendedNextStep || 'nexus_orchestrate'}`,
|
|
284
300
|
`Token optimization: ${bootstrap.tokenOptimization?.autoApplied ? `auto-applied — saved ${Number(bootstrap.tokenOptimization?.planMetrics?.savings ?? 0).toLocaleString()} tokens (${bootstrap.tokenOptimization?.planMetrics?.pct ?? 0}% reduction)` : (bootstrap.tokenOptimization?.required ? 'required before broad reading' : 'not required yet')}`,
|
|
301
|
+
bootstrap.tokenOptimization?.planMetrics
|
|
302
|
+
? `Token budget: original ${Number(bootstrap.tokenOptimization.planMetrics.originalTokens ?? 0).toLocaleString()} → planned ${Number(bootstrap.tokenOptimization.planMetrics.compressedTokens ?? 0).toLocaleString()} across ${Number(bootstrap.tokenOptimization.planMetrics.files ?? 0).toLocaleString()} file action(s)`
|
|
303
|
+
: '',
|
|
285
304
|
`Catalog health: ${bootstrap.catalogHealth?.overall || 'unknown'} · selected ${bootstrap.artifactSelectionAudit?.selected?.length || 0}`,
|
|
286
305
|
(() => {
|
|
287
306
|
try {
|
|
@@ -585,8 +604,13 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
585
604
|
const rawFiles = Array.isArray(request.params.arguments?.files)
|
|
586
605
|
? request.params.arguments.files.map(String)
|
|
587
606
|
: null;
|
|
588
|
-
const
|
|
589
|
-
|
|
607
|
+
const workspaceRoot = hctx.getWorkspace(request.params.arguments ?? {}).repoRoot;
|
|
608
|
+
const scannedFilePaths = rawFiles
|
|
609
|
+
?? await hctx.scanSourceFiles(workspaceRoot);
|
|
610
|
+
const requestedMaxFiles = Number(request.params.arguments?.maxFiles ?? request.params.arguments?.limit ?? 80);
|
|
611
|
+
const maxFiles = Math.max(1, Math.min(Number.isFinite(requestedMaxFiles) ? Math.floor(requestedMaxFiles) : 80, 200));
|
|
612
|
+
const filePaths = rawFiles ? scannedFilePaths : scannedFilePaths.slice(0, maxFiles);
|
|
613
|
+
const omittedFiles = rawFiles ? 0 : Math.max(0, scannedFilePaths.length - filePaths.length);
|
|
590
614
|
const statLimit = pLimit(8);
|
|
591
615
|
const files = await Promise.all(filePaths.map((p) => statLimit(async () => {
|
|
592
616
|
const resolved = hctx.resolveToolPath(p, request.params.arguments ?? {});
|
|
@@ -660,10 +684,14 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
660
684
|
const dedupLine = dedupTokens > 0
|
|
661
685
|
? `\n ${dedupTokens.toLocaleString()} tok deduped (${delta.unchanged.length} unchanged files)`
|
|
662
686
|
: '';
|
|
687
|
+
const scanLine = rawFiles
|
|
688
|
+
? `\n explicit file set: ${filePaths.length.toLocaleString()} file(s)`
|
|
689
|
+
: `\n auto-scan capped: ${filePaths.length.toLocaleString()}/${scannedFilePaths.length.toLocaleString()} file(s)${omittedFiles ? ` (${omittedFiles.toLocaleString()} omitted; pass files or maxFiles to override)` : ''}`;
|
|
663
690
|
const receipt = [
|
|
664
691
|
'',
|
|
665
692
|
`▸ Receipt ${plan.totalEstimatedTokens.toLocaleString()} tok in`,
|
|
666
693
|
` ${plan.savings.toLocaleString()} tok saved (${pct}% vs full read)${dedupLine}`,
|
|
694
|
+
scanLine.trimStart(),
|
|
667
695
|
` ~$${estimatedCostUsd.toFixed(4)} estimated · ~$${savedCostUsd.toFixed(4)} saved`,
|
|
668
696
|
].join('\n');
|
|
669
697
|
return { content: [{ type: 'text', text: formatted + receipt + notification + nudge }] };
|
|
@@ -673,7 +701,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
673
701
|
const ctx = {
|
|
674
702
|
action: String(args?.action ?? ''),
|
|
675
703
|
tokenCount: args?.tokenCount,
|
|
676
|
-
filesToModify: args?.filesToModify
|
|
704
|
+
filesToModify: Array.isArray(args?.filesToModify)
|
|
705
|
+
? args.filesToModify.map(String)
|
|
706
|
+
: (Array.isArray(args?.files) ? args.files.map(String) : undefined),
|
|
677
707
|
isDestructive: args?.isDestructive,
|
|
678
708
|
};
|
|
679
709
|
const result = getGuardrailEngine().check(ctx);
|
|
@@ -685,15 +715,47 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
685
715
|
const nudge = result.passed
|
|
686
716
|
? hctx.telemetry.planningNudge('high_call_count', {})
|
|
687
717
|
: hctx.telemetry.planningNudge('mindkit_fail', {});
|
|
718
|
+
const verdict = result.passed ? 'PASS' : 'FAIL';
|
|
719
|
+
const violationRows = result.violations.map((v) => {
|
|
720
|
+
const item = v;
|
|
721
|
+
return [
|
|
722
|
+
item.id ?? 'violation',
|
|
723
|
+
item.severity ?? 'high',
|
|
724
|
+
item.message ?? item.description ?? 'blocked by guardrail',
|
|
725
|
+
];
|
|
726
|
+
});
|
|
727
|
+
const warningRows = result.warnings.map((w) => {
|
|
728
|
+
const item = w;
|
|
729
|
+
return [
|
|
730
|
+
item.id ?? 'warning',
|
|
731
|
+
item.severity ?? 'warn',
|
|
732
|
+
item.message ?? item.description ?? 'review recommended',
|
|
733
|
+
];
|
|
734
|
+
});
|
|
735
|
+
const detailJson = JSON.stringify({
|
|
736
|
+
passed: result.passed,
|
|
737
|
+
score: Math.round(result.score * 100),
|
|
738
|
+
filesToModify: ctx.filesToModify ?? [],
|
|
739
|
+
violations: result.violations,
|
|
740
|
+
warnings: result.warnings,
|
|
741
|
+
summary: getGuardrailEngine().format(result)
|
|
742
|
+
}, null, 2);
|
|
688
743
|
return {
|
|
689
744
|
content: [{
|
|
690
|
-
type: 'text',
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
745
|
+
type: 'text',
|
|
746
|
+
text: [
|
|
747
|
+
`Mindkit check: ${verdict}`,
|
|
748
|
+
formatBullets([
|
|
749
|
+
`Score: ${Math.round(result.score * 100)}/100`,
|
|
750
|
+
`Files: ${ctx.filesToModify?.length ? ctx.filesToModify.join(', ') : 'none declared'}`,
|
|
751
|
+
`Decision: ${result.passed ? 'proceed' : 'stop and resolve guardrail violations'}`,
|
|
752
|
+
`Summary: ${getGuardrailEngine().format(result)}`,
|
|
753
|
+
]),
|
|
754
|
+
violationRows.length ? `Violations\n${markdownTable(['Rule', 'Severity', 'Message'], violationRows)}` : '',
|
|
755
|
+
warningRows.length ? `Warnings\n${markdownTable(['Rule', 'Severity', 'Message'], warningRows)}` : '',
|
|
756
|
+
`Details\n\`\`\`json\n${detailJson}\n\`\`\``,
|
|
757
|
+
nudge,
|
|
758
|
+
].filter(Boolean).join('\n\n')
|
|
697
759
|
}]
|
|
698
760
|
};
|
|
699
761
|
}
|
|
@@ -717,6 +779,17 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
717
779
|
hctx.nexusRef.storeMemory(`Ghost pass for "${goal.slice(0, 80)}": ${report.riskAreas.length} risks identified.`, 0.6, ['#ghost-pass']);
|
|
718
780
|
nexusEventBus.emit('ghost.pass', { task: goal, risks: report.riskAreas.length, workers: report.workerAssignments.length });
|
|
719
781
|
const ghostNudge = hctx.telemetry.planningNudge('ghost_pass', { risks: report.riskAreas.length });
|
|
782
|
+
const workerTable = markdownTable(['Worker', 'Approach', 'Token Budget', 'Files'], report.workerAssignments.map((worker, index) => [
|
|
783
|
+
`Worker ${index + 1}`,
|
|
784
|
+
worker.approach,
|
|
785
|
+
worker.tokenBudget.toLocaleString(),
|
|
786
|
+
worker.files.map(file => path.basename(file.path)).slice(0, 4).join(', ') || 'n/a',
|
|
787
|
+
]));
|
|
788
|
+
const riskTable = markdownTable(['Risk', 'Severity', 'Mitigation'], report.riskAreas.map((risk) => [
|
|
789
|
+
risk,
|
|
790
|
+
'review',
|
|
791
|
+
'Use the reading plan and isolate edits before mutation',
|
|
792
|
+
]));
|
|
720
793
|
// Console ASCII UI
|
|
721
794
|
const rCount = report.riskAreas.length;
|
|
722
795
|
hctx.box('👻 GHOST PASS PRE-FLIGHT', [
|
|
@@ -735,10 +808,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
735
808
|
`Risks: ${report.riskAreas.length ? report.riskAreas.join(' | ') : 'none detected'}`,
|
|
736
809
|
`Worker approaches: ${report.workerAssignments.length}`,
|
|
737
810
|
]),
|
|
811
|
+
riskTable ? `Risk table\n${riskTable}` : 'Risk table\nNo risks detected.',
|
|
812
|
+
workerTable ? `Worker table\n${workerTable}` : '',
|
|
738
813
|
'Reading plan\n```txt\n' + formatReadingPlan(report.readingPlan) + '\n```',
|
|
739
|
-
report.workerAssignments.length
|
|
740
|
-
? formatBullets(report.workerAssignments.map((worker, index) => `Worker ${index + 1}: ${worker.approach} · budget ${worker.tokenBudget.toLocaleString()} tokens`))
|
|
741
|
-
: '',
|
|
742
814
|
ghostNudge,
|
|
743
815
|
].filter(Boolean).join('\n\n'),
|
|
744
816
|
}],
|
|
@@ -44,8 +44,10 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
|
44
44
|
? `- **Trial remaining**: ${licStatus.trialDaysRemaining} day${licStatus.trialDaysRemaining === 1 ? '' : 's'}`
|
|
45
45
|
: '',
|
|
46
46
|
licStatus.activationRequired
|
|
47
|
-
? `- **Agent motion**:
|
|
48
|
-
:
|
|
47
|
+
? `- **Agent motion**: License activation is required for paid-tier work; keep license/status tools available.`
|
|
48
|
+
: licStatus.trialPhase === 'activation'
|
|
49
|
+
? `- **Agent motion**: Trial remains active; ask the user to request or activate a license soon, then keep working.`
|
|
50
|
+
: `- **Agent motion**: No license is mandatory during the first 3 days; ask the user to request a license soon.`,
|
|
49
51
|
].filter((line) => Boolean(line)) : [];
|
|
50
52
|
const lines = [
|
|
51
53
|
`## License Status`,
|
|
@@ -120,6 +120,13 @@ export class SessionTelemetry {
|
|
|
120
120
|
this.noteFileIntent(args.files);
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
|
+
if (toolName === 'nexus_spawn_workers') {
|
|
124
|
+
if (this.lifecyclePhase === 'bootstrapped' || this.lifecyclePhase === 'orchestrated') {
|
|
125
|
+
this.advancePhase('working');
|
|
126
|
+
}
|
|
127
|
+
this.noteFileIntent(args.files);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
123
130
|
if (toolName === 'nexus_store_memory') {
|
|
124
131
|
if (this.isPostOrchestratePhase()) {
|
|
125
132
|
this.storeMemoryCalledPostOrchestrate = true;
|
|
@@ -154,6 +161,8 @@ export class SessionTelemetry {
|
|
|
154
161
|
needsOptimizeTokens(currentToolName) {
|
|
155
162
|
if (currentToolName === 'nexus_optimize_tokens')
|
|
156
163
|
return false;
|
|
164
|
+
if (currentToolName === 'nexus_mindkit_check')
|
|
165
|
+
return false;
|
|
157
166
|
if (this.lifecyclePhase === 'pre-bootstrap')
|
|
158
167
|
return false;
|
|
159
168
|
if (this.tokenAutoApplied)
|
|
@@ -136,6 +136,7 @@ const PRE_ORCHESTRATE_ALLOWED_TOOLS = new Set([
|
|
|
136
136
|
'nexus_optimize_tokens',
|
|
137
137
|
'nexus_mindkit_check',
|
|
138
138
|
'nexus_ghost_pass',
|
|
139
|
+
'nexus_spawn_workers',
|
|
139
140
|
'nexus_token_report',
|
|
140
141
|
'nexus_session_dna',
|
|
141
142
|
'nexus_run_status',
|
|
@@ -545,6 +546,7 @@ export class MCPAdapter {
|
|
|
545
546
|
code: 'orchestration-required',
|
|
546
547
|
reason: 'nexus_orchestrate has not been called for this session',
|
|
547
548
|
action: 'Call nexus_orchestrate(prompt="<your task>") before invoking mutation, worker, kernel, hook, automation, or low-level execution tools.',
|
|
549
|
+
nextTool: 'nexus_orchestrate',
|
|
548
550
|
hint: 'Nexus Prime must own planning, token budgeting, memory recall, hooks, and review gates before work starts.',
|
|
549
551
|
}, null, 2),
|
|
550
552
|
}],
|
package/dist/cli/hook.js
CHANGED
|
@@ -209,8 +209,9 @@ export async function runHookMindkit() {
|
|
|
209
209
|
if (!lock)
|
|
210
210
|
return; // no daemon — skip rather than block
|
|
211
211
|
const resp = await callDaemonTool(lock, 'nexus_mindkit_check', {
|
|
212
|
-
action: toolName,
|
|
213
|
-
files,
|
|
212
|
+
action: `${toolName}: ${files.join(', ')}`,
|
|
213
|
+
filesToModify: files,
|
|
214
|
+
isDestructive: ['Write', 'Edit', 'MultiEdit'].includes(toolName),
|
|
214
215
|
}, 6_000);
|
|
215
216
|
// Parse the tool result text to check for a block verdict
|
|
216
217
|
const text = extractResultText(resp);
|
|
@@ -39,6 +39,13 @@ const SETUP_CLIENT_BY_IDE = {
|
|
|
39
39
|
cline: 'cline',
|
|
40
40
|
codex: 'codex',
|
|
41
41
|
};
|
|
42
|
+
function resolveSetupWorkspaceRoot(explicitWorkspaceRoot) {
|
|
43
|
+
return resolveWorkspaceContext({
|
|
44
|
+
workspaceRoot: explicitWorkspaceRoot,
|
|
45
|
+
cwd: process.cwd(),
|
|
46
|
+
env: process.env,
|
|
47
|
+
}).workspaceRoot;
|
|
48
|
+
}
|
|
42
49
|
function isSetupMarker(value) {
|
|
43
50
|
if (!value || typeof value !== 'object') {
|
|
44
51
|
return false;
|
|
@@ -89,7 +96,7 @@ export function writeSetupMarker(marker, markerPath = SETUP_MARKER_PATH) {
|
|
|
89
96
|
return nextMarker;
|
|
90
97
|
}
|
|
91
98
|
export async function configureIDE(ide, opts = {}) {
|
|
92
|
-
const workspaceRoot =
|
|
99
|
+
const workspaceRoot = resolveSetupWorkspaceRoot(opts.workspaceRoot);
|
|
93
100
|
const mappedClient = SETUP_CLIENT_BY_IDE[ide];
|
|
94
101
|
if (mappedClient) {
|
|
95
102
|
const definition = getSetupDefinition(mappedClient, {
|
|
@@ -285,7 +292,7 @@ export function runArchitectureUpgrade(opts = {}) {
|
|
|
285
292
|
}
|
|
286
293
|
/** Run the install wizard: detect IDEs and write MCP configs. */
|
|
287
294
|
export async function runInstallWizard(opts = {}) {
|
|
288
|
-
const workspaceRoot =
|
|
295
|
+
const workspaceRoot = resolveSetupWorkspaceRoot(opts.workspaceRoot);
|
|
289
296
|
const verbose = opts.verbose ?? true;
|
|
290
297
|
const dryRun = opts.dryRun ?? false;
|
|
291
298
|
const setupMarker = readSetupMarker();
|
|
@@ -414,6 +421,7 @@ export async function cliSetup(opts = []) {
|
|
|
414
421
|
const port = process.env.NEXUS_DASHBOARD_PORT ?? '3377';
|
|
415
422
|
const dashUrl = `http://localhost:${port}`;
|
|
416
423
|
const isNew = options.isNewUser ?? false;
|
|
424
|
+
const workspaceRoot = resolveSetupWorkspaceRoot();
|
|
417
425
|
const ANSI = { cyan: '\x1b[36m', green: '\x1b[32m', yellow: '\x1b[33m', dim: '\x1b[2m', reset: '\x1b[0m' };
|
|
418
426
|
const isTTY = process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
419
427
|
const c = (s, k) => isTTY ? `${ANSI[k]}${s}${ANSI.reset}` : s;
|
|
@@ -438,7 +446,7 @@ export async function cliSetup(opts = []) {
|
|
|
438
446
|
}
|
|
439
447
|
catch { /* non-fatal */ }
|
|
440
448
|
};
|
|
441
|
-
const result = await runInstallWizard({ dryRun, verbose: false });
|
|
449
|
+
const result = await runInstallWizard({ workspaceRoot, dryRun, verbose: false });
|
|
442
450
|
const allIDEs = [...result.configured, ...result.skipped, ...result.errors.map(e => e.ide)];
|
|
443
451
|
if (allIDEs.length > 0) {
|
|
444
452
|
for (const ide of allIDEs) {
|
|
@@ -499,7 +507,7 @@ export async function cliSetup(opts = []) {
|
|
|
499
507
|
let daemonReady = false;
|
|
500
508
|
if (!dryRun) {
|
|
501
509
|
try {
|
|
502
|
-
const workspace = resolveWorkspaceContext({ workspaceRoot: process.cwd() });
|
|
510
|
+
const workspace = resolveWorkspaceContext({ workspaceRoot, cwd: process.cwd(), env: process.env });
|
|
503
511
|
await withSpinner('Connecting to daemon', ensureDaemonReady(workspace, { timeoutMs: DEFAULT_DAEMON_READY_TIMEOUT_MS, entrypoint: process.argv[1] }));
|
|
504
512
|
daemonReady = true;
|
|
505
513
|
log(` ${c('✓', 'green')} Dashboard: ${c(dashUrl, 'cyan')}`);
|
package/dist/cli.js
CHANGED
|
@@ -454,11 +454,13 @@ function resolveClaudeDesktopConfigPath() {
|
|
|
454
454
|
return join(homedir(), '.claude', 'claude_desktop_config.json');
|
|
455
455
|
}
|
|
456
456
|
function getSetupDefinition(clientId) {
|
|
457
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
457
458
|
const instructionFiles = buildInstructionFiles(clientId);
|
|
458
459
|
if (clientId === 'codex') {
|
|
459
460
|
return {
|
|
460
461
|
id: clientId,
|
|
461
462
|
label: 'Codex',
|
|
463
|
+
workspaceRoot,
|
|
462
464
|
configPath: resolveCodexConfigPath(),
|
|
463
465
|
instructionFiles,
|
|
464
466
|
};
|
|
@@ -467,6 +469,7 @@ function getSetupDefinition(clientId) {
|
|
|
467
469
|
return {
|
|
468
470
|
id: clientId,
|
|
469
471
|
label: 'Cursor',
|
|
472
|
+
workspaceRoot,
|
|
470
473
|
configPath: join(homedir(), '.cursor', 'mcp.json'),
|
|
471
474
|
instructionFiles,
|
|
472
475
|
};
|
|
@@ -475,7 +478,8 @@ function getSetupDefinition(clientId) {
|
|
|
475
478
|
return {
|
|
476
479
|
id: clientId,
|
|
477
480
|
label: 'Claude Code',
|
|
478
|
-
|
|
481
|
+
workspaceRoot,
|
|
482
|
+
configPath: join(workspaceRoot, '.mcp.json'),
|
|
479
483
|
instructionFiles,
|
|
480
484
|
};
|
|
481
485
|
}
|
|
@@ -483,6 +487,7 @@ function getSetupDefinition(clientId) {
|
|
|
483
487
|
return {
|
|
484
488
|
id: clientId,
|
|
485
489
|
label: 'Claude Desktop',
|
|
490
|
+
workspaceRoot,
|
|
486
491
|
configPath: resolveClaudeDesktopConfigPath(),
|
|
487
492
|
instructionFiles,
|
|
488
493
|
};
|
|
@@ -491,6 +496,7 @@ function getSetupDefinition(clientId) {
|
|
|
491
496
|
return {
|
|
492
497
|
id: clientId,
|
|
493
498
|
label: 'Opencode',
|
|
499
|
+
workspaceRoot,
|
|
494
500
|
configPath: join(homedir(), '.config', 'opencode', 'opencode.json'),
|
|
495
501
|
instructionFiles,
|
|
496
502
|
};
|
|
@@ -499,6 +505,7 @@ function getSetupDefinition(clientId) {
|
|
|
499
505
|
return {
|
|
500
506
|
id: clientId,
|
|
501
507
|
label: 'Windsurf',
|
|
508
|
+
workspaceRoot,
|
|
502
509
|
configPath: join(homedir(), '.windsurf', 'mcp.json'),
|
|
503
510
|
instructionFiles,
|
|
504
511
|
};
|
|
@@ -507,6 +514,7 @@ function getSetupDefinition(clientId) {
|
|
|
507
514
|
return {
|
|
508
515
|
id: clientId,
|
|
509
516
|
label: 'Antigravity / OpenClaw',
|
|
517
|
+
workspaceRoot,
|
|
510
518
|
configPath: join(homedir(), '.antigravity', 'mcp.json'),
|
|
511
519
|
instructionFiles,
|
|
512
520
|
};
|
|
@@ -515,6 +523,7 @@ function getSetupDefinition(clientId) {
|
|
|
515
523
|
return {
|
|
516
524
|
id: clientId,
|
|
517
525
|
label: 'Aider',
|
|
526
|
+
workspaceRoot,
|
|
518
527
|
configPath: join(homedir(), '.aider', 'mcp.json'),
|
|
519
528
|
instructionFiles,
|
|
520
529
|
};
|
|
@@ -523,6 +532,7 @@ function getSetupDefinition(clientId) {
|
|
|
523
532
|
return {
|
|
524
533
|
id: clientId,
|
|
525
534
|
label: 'Continue.dev',
|
|
535
|
+
workspaceRoot,
|
|
526
536
|
configPath: join(homedir(), '.continue', 'config.json'),
|
|
527
537
|
instructionFiles,
|
|
528
538
|
};
|
|
@@ -531,6 +541,7 @@ function getSetupDefinition(clientId) {
|
|
|
531
541
|
return {
|
|
532
542
|
id: clientId,
|
|
533
543
|
label: 'OpenClaw',
|
|
544
|
+
workspaceRoot,
|
|
534
545
|
configPath: join(homedir(), '.openclaw', 'openclaw.json'),
|
|
535
546
|
instructionFiles,
|
|
536
547
|
};
|
|
@@ -538,6 +549,7 @@ function getSetupDefinition(clientId) {
|
|
|
538
549
|
return {
|
|
539
550
|
id: clientId,
|
|
540
551
|
label: 'Cline',
|
|
552
|
+
workspaceRoot,
|
|
541
553
|
configPath: join(homedir(), '.vscode', 'cline-mcp.json'),
|
|
542
554
|
instructionFiles,
|
|
543
555
|
};
|
|
@@ -548,10 +560,10 @@ function installSetup(definition) {
|
|
|
548
560
|
writeCodexMcpConfig(definition.configPath);
|
|
549
561
|
}
|
|
550
562
|
else if (definition.id === 'opencode') {
|
|
551
|
-
writeOpencodeConfig(definition.configPath,
|
|
563
|
+
writeOpencodeConfig(definition.configPath, definition.workspaceRoot);
|
|
552
564
|
}
|
|
553
565
|
else {
|
|
554
|
-
writeStandardMcpConfig(definition.configPath,
|
|
566
|
+
writeStandardMcpConfig(definition.configPath, definition.workspaceRoot);
|
|
555
567
|
}
|
|
556
568
|
}
|
|
557
569
|
for (const file of definition.instructionFiles) {
|
|
@@ -585,12 +597,12 @@ function printSetupPreview(definition) {
|
|
|
585
597
|
console.log(JSON.stringify(definition.id === 'opencode'
|
|
586
598
|
? {
|
|
587
599
|
mcp: {
|
|
588
|
-
servers: [{ id: 'nexus-prime', ...buildStandardMcpServerConfig(
|
|
600
|
+
servers: [{ id: 'nexus-prime', ...buildStandardMcpServerConfig(definition.workspaceRoot) }]
|
|
589
601
|
}
|
|
590
602
|
}
|
|
591
603
|
: {
|
|
592
604
|
mcpServers: {
|
|
593
|
-
'nexus-prime': buildStandardMcpServerConfig(
|
|
605
|
+
'nexus-prime': buildStandardMcpServerConfig(definition.workspaceRoot)
|
|
594
606
|
}
|
|
595
607
|
}, null, 2));
|
|
596
608
|
}
|
|
@@ -612,10 +624,10 @@ function hasExpectedConfig(definition) {
|
|
|
612
624
|
return false;
|
|
613
625
|
if (definition.id === 'opencode') {
|
|
614
626
|
const server = parsed?.mcp?.['nexus-prime'];
|
|
615
|
-
return Boolean(server && isStableNexusMcpServerConfig(server, 'environment'));
|
|
627
|
+
return Boolean(server && isStableNexusMcpServerConfig(server, 'environment', definition.workspaceRoot));
|
|
616
628
|
}
|
|
617
629
|
const server = parsed?.mcpServers?.['nexus-prime'];
|
|
618
|
-
return Boolean(server && isStableNexusMcpServerConfig(server));
|
|
630
|
+
return Boolean(server && isStableNexusMcpServerConfig(server, 'env', definition.workspaceRoot));
|
|
619
631
|
}
|
|
620
632
|
catch {
|
|
621
633
|
return false;
|
|
@@ -705,11 +717,11 @@ program
|
|
|
705
717
|
.description('Start Nexus Prime daemon')
|
|
706
718
|
.option('--force', 'Force restart if already running')
|
|
707
719
|
.action(async (options) => {
|
|
708
|
-
const workspaceContext = resolveWorkspaceContext({ workspaceRoot: getWorkspaceRoot() });
|
|
709
720
|
try {
|
|
710
721
|
const record = await ensureDaemonManaged({ force: options.force });
|
|
722
|
+
const dashboardPort = process.env.NEXUS_DASHBOARD_PORT ?? '3377';
|
|
711
723
|
console.log(`Nexus Prime daemon running (pid ${record.pid}, ${formatDaemonAddress(record)})`);
|
|
712
|
-
console.log(`Dashboard: http://localhost
|
|
724
|
+
console.log(`Dashboard: http://localhost:${dashboardPort}`);
|
|
713
725
|
}
|
|
714
726
|
catch (err) {
|
|
715
727
|
console.error(`Failed to start daemon: ${err.message}`);
|
|
@@ -274,6 +274,7 @@ function renderHero() {
|
|
|
274
274
|
?? lt?.totalSaved
|
|
275
275
|
?? lt?.lifetime?.saved
|
|
276
276
|
?? op?.tokenOptimization?.savedTokens
|
|
277
|
+
?? op?.tokenOptimization?.estimatedSavings
|
|
277
278
|
?? t?.savedTokens
|
|
278
279
|
?? t?.saved
|
|
279
280
|
?? 0,
|
|
@@ -317,14 +318,18 @@ function getHireReadiness() {
|
|
|
317
318
|
const synapseState = S.healthData?.runtimeEnvelope?.engines?.synapse ?? {};
|
|
318
319
|
const memoryStorage = S.healthData?.memory?.storage ?? {};
|
|
319
320
|
const rawReason = S.synapseHealthRaw?.reason;
|
|
321
|
+
const deferred = synapseState.deferred === true || /deferred\s*\(lazy mode\)/i.test(`${rawReason || ''} ${synapseState.reason || ''}`);
|
|
320
322
|
const unavailable = Boolean(
|
|
321
|
-
S.synapseHealthRaw?.notReady
|
|
322
|
-
|| synapseState.ready === false
|
|
323
|
-
|| synapseState.available === false,
|
|
323
|
+
(S.synapseHealthRaw?.notReady && !deferred)
|
|
324
|
+
|| (synapseState.ready === false && !deferred)
|
|
325
|
+
|| (synapseState.available === false && !deferred),
|
|
324
326
|
);
|
|
325
327
|
if (unavailable) {
|
|
326
328
|
notes.push({ tone: 'bad', text: rawReason || synapseState.reason || 'Synapse is not ready for hires in this dashboard session.' });
|
|
327
329
|
}
|
|
330
|
+
if (deferred && !unavailable) {
|
|
331
|
+
notes.push({ tone: 'warn', text: 'Synapse is in lazy mode and will warm up on the first hire.' });
|
|
332
|
+
}
|
|
328
333
|
if (synapseState.fallbackApplied) {
|
|
329
334
|
notes.push({ tone: 'warn', text: synapseState.reason || 'Synapse is running in fallback storage mode.' });
|
|
330
335
|
}
|
|
@@ -401,6 +406,7 @@ function renderFirstRunHero() {
|
|
|
401
406
|
specialistId: btn.dataset.specid,
|
|
402
407
|
name: btn.dataset.specname,
|
|
403
408
|
budgetCapUsd: 2,
|
|
409
|
+
fireFirstSortie: true,
|
|
404
410
|
});
|
|
405
411
|
if (result.ok) {
|
|
406
412
|
setFirstRunStatus(readiness.notes.some(note => note.tone === 'warn')
|
|
@@ -514,6 +514,7 @@ function _showHireSheet(specialistId, name) {
|
|
|
514
514
|
budgetCapUsd: budget,
|
|
515
515
|
reportsToOperativeId: reportsToVal,
|
|
516
516
|
strikeTeamId: teamVal,
|
|
517
|
+
fireFirstSortie: true,
|
|
517
518
|
}, { optimistic: optimisticRecord });
|
|
518
519
|
|
|
519
520
|
// Immediately open drawer with pending state.
|
|
@@ -547,10 +548,10 @@ function _showHireSheet(specialistId, name) {
|
|
|
547
548
|
}
|
|
548
549
|
bustCache('/api/synapse/health');
|
|
549
550
|
setTimeout(load, 800);
|
|
550
|
-
//
|
|
551
|
+
// The backend materializes this hire as a Workforce agent and
|
|
552
|
+
// starts the first sortie when fireFirstSortie is true.
|
|
551
553
|
const operativeId = real.data?.operative?.id || real.data?.operative?.operativeId;
|
|
552
554
|
if (operativeId) {
|
|
553
|
-
// Insert dispatch-strip placeholder into the already-open drawer
|
|
554
555
|
const drawerBody = document.getElementById('drawer-body');
|
|
555
556
|
if (drawerBody) {
|
|
556
557
|
let stripDiv = drawerBody.querySelector('[data-dispatch-strip]');
|
|
@@ -563,22 +564,12 @@ function _showHireSheet(specialistId, name) {
|
|
|
563
564
|
_dispatches.set('__warmup__', warmupRun);
|
|
564
565
|
stripDiv.innerHTML = _buildDispatchStrip(warmupRun);
|
|
565
566
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
specialistId: specId,
|
|
573
|
-
budgetCapUsd: 0.5,
|
|
574
|
-
}),
|
|
575
|
-
}).then(r => r.json()).then(dr => {
|
|
576
|
-
_dispatches.delete('__warmup__');
|
|
577
|
-
if (dr?.runId) {
|
|
578
|
-
_dispatches.set(dr.runId, { runId: dr.runId, operativeId, status: 'queued', tokens: 0, costUsd: 0, messages: [], filesChanged: [] });
|
|
579
|
-
}
|
|
580
|
-
_refreshDrawerForOp(operativeId);
|
|
581
|
-
}).catch(() => { _dispatches.delete('__warmup__'); _refreshDrawerForOp(operativeId); });
|
|
567
|
+
const fd = real.data?.firstDispatch;
|
|
568
|
+
_dispatches.delete('__warmup__');
|
|
569
|
+
if (fd?.runId) {
|
|
570
|
+
_dispatches.set(fd.runId, { runId: fd.runId, operativeId, status: 'queued', tokens: 0, costUsd: 0, messages: [], filesChanged: [] });
|
|
571
|
+
}
|
|
572
|
+
_refreshDrawerForOp(operativeId);
|
|
582
573
|
}
|
|
583
574
|
} else {
|
|
584
575
|
if (msg) {
|
|
@@ -609,6 +600,15 @@ function _openOpDrawer(id, name) {
|
|
|
609
600
|
['Budget alloc',op.budget!=null?fmtNum(op.budget):'—'],
|
|
610
601
|
['Team',op.team||op.strikeName||'—']
|
|
611
602
|
])}</div>
|
|
603
|
+
<div class="dsec">
|
|
604
|
+
<div class="dsec-title">Agent control</div>
|
|
605
|
+
<textarea id="agent-control-input" rows="4" placeholder="Ask this agent to inspect, plan, or run a focused task"
|
|
606
|
+
style="width:100%;resize:vertical;background:var(--bg-panel);border:1px solid var(--border);border-radius:var(--radius);padding:8px 10px;color:var(--text-main);font:var(--text-sm) var(--font-sans);margin-bottom:var(--space-3)"></textarea>
|
|
607
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
608
|
+
<button class="btn btn-primary btn-sm" id="agent-control-send">Send to agent</button>
|
|
609
|
+
<span id="agent-control-status" style="font-size:var(--text-sm);color:var(--text-muted)"></span>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
612
|
<div data-dispatch-strip>${stripInner}</div>` });
|
|
613
613
|
// Attach stop button handlers for any already-rendered strips
|
|
614
614
|
const drawerBody = document.getElementById('drawer-body');
|
|
@@ -621,6 +621,44 @@ function _openOpDrawer(id, name) {
|
|
|
621
621
|
});
|
|
622
622
|
});
|
|
623
623
|
}
|
|
624
|
+
const sendBtn = document.getElementById('agent-control-send');
|
|
625
|
+
const input = document.getElementById('agent-control-input');
|
|
626
|
+
const status = document.getElementById('agent-control-status');
|
|
627
|
+
sendBtn?.addEventListener('click', async () => {
|
|
628
|
+
const goal = input?.value?.trim();
|
|
629
|
+
if (!goal) {
|
|
630
|
+
if (status) status.textContent = 'Enter a task first.';
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
sendBtn.disabled = true;
|
|
634
|
+
sendBtn.textContent = 'Sending…';
|
|
635
|
+
if (status) status.textContent = 'Dispatching to agent…';
|
|
636
|
+
const response = await fetch(`/api/workforce/agents/${encodeURIComponent(opId)}/chat`, {
|
|
637
|
+
method: 'POST',
|
|
638
|
+
headers: { 'Content-Type': 'application/json' },
|
|
639
|
+
body: JSON.stringify({
|
|
640
|
+
goal,
|
|
641
|
+
message: goal,
|
|
642
|
+
specialistId: op.specialistId || op.role || undefined,
|
|
643
|
+
budgetCapUsd: op.budget || 2,
|
|
644
|
+
}),
|
|
645
|
+
}).then(r => r.json().then(data => ({ ok: r.ok, data }))).catch(error => ({ ok: false, data: { error: error?.message || String(error) } }));
|
|
646
|
+
if (response.ok) {
|
|
647
|
+
if (input) input.value = '';
|
|
648
|
+
const runId = response.data?.runId;
|
|
649
|
+
if (runId) _dispatches.set(runId, { runId, operativeId: opId, status: 'queued', tokens: 0, costUsd: 0, messages: [response.data?.message || 'Agent command queued.'], filesChanged: [] });
|
|
650
|
+
if (status) status.textContent = response.data?.mode === 'orchestrator-fallback'
|
|
651
|
+
? 'Queued through Nexus orchestrator.'
|
|
652
|
+
: 'Queued to agent runtime.';
|
|
653
|
+
_refreshDrawerForOp(opId);
|
|
654
|
+
setTimeout(load, 800);
|
|
655
|
+
} else if (status) {
|
|
656
|
+
status.textContent = response.data?.hint || response.data?.error || 'Agent command failed.';
|
|
657
|
+
status.style.color = 'var(--bad)';
|
|
658
|
+
}
|
|
659
|
+
sendBtn.disabled = false;
|
|
660
|
+
sendBtn.textContent = 'Send to agent';
|
|
661
|
+
});
|
|
624
662
|
}
|
|
625
663
|
|
|
626
664
|
function _openMissionDrawer(id) {
|
|
@@ -27,7 +27,15 @@ export const handleArchitectsRoutes = async (ctx, req, res, url) => {
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
const dispatch = architects.getDispatchStatus?.() ?? null;
|
|
30
|
-
ctx.
|
|
30
|
+
const preferred = ctx.getPreferredArchitectsWorklist(url, url.searchParams.get('worklistId'), url.searchParams.get('strikeTeamId'));
|
|
31
|
+
ctx.respondJson(res, {
|
|
32
|
+
dispatch,
|
|
33
|
+
worklist: Array.isArray(preferred?.items) ? preferred.items : [],
|
|
34
|
+
worklistId: preferred?.worklistId ?? preferred?.worklist?.id ?? null,
|
|
35
|
+
activeLocks: [],
|
|
36
|
+
idle: Boolean(preferred?.idle),
|
|
37
|
+
reason: preferred?.reason ?? '',
|
|
38
|
+
});
|
|
31
39
|
return true;
|
|
32
40
|
}
|
|
33
41
|
/* ── POST /api/architects/wards/:wardId/resolve ──────────────────────── */
|