nexus-prime 7.9.27 → 7.9.29
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/README.md +12 -10
- package/dist/agents/adapters/mcp/definitions.js +3 -2
- package/dist/agents/adapters/mcp/dispatch.js +9 -1
- package/dist/agents/adapters/mcp/envelope.js +2 -0
- package/dist/agents/adapters/mcp/handlers/orchestration.js +60 -18
- package/dist/agents/adapters/mcp/helpers.js +2 -2
- package/dist/agents/adapters/mcp/types.d.ts +1 -1
- package/dist/dashboard/app/api.js +30 -11
- package/dist/dashboard/app/index.html +28 -3
- package/dist/dashboard/app/main.js +34 -20
- package/dist/dashboard/app/state.js +4 -0
- package/dist/dashboard/app/styles/board.css +75 -0
- package/dist/dashboard/app/styles/context-log.css +154 -3
- package/dist/dashboard/app/styles/learning.css +204 -0
- package/dist/dashboard/app/styles/memory.css +169 -1
- package/dist/dashboard/app/styles/tokens.css +5 -0
- package/dist/dashboard/app/views/board.js +91 -23
- package/dist/dashboard/app/views/context-log.js +130 -7
- package/dist/dashboard/app/views/learning.js +200 -0
- package/dist/dashboard/app/views/memory.js +139 -26
- package/dist/dashboard/app/views/repo.js +168 -5
- package/dist/dashboard/routes/events.js +71 -3
- package/dist/dashboard/routes/graph.js +50 -0
- package/dist/dashboard/routes/learning.d.ts +2 -0
- package/dist/dashboard/routes/learning.js +213 -0
- package/dist/dashboard/routes/run-artifacts.d.ts +5 -0
- package/dist/dashboard/routes/run-artifacts.js +107 -0
- package/dist/dashboard/routes/runtime.js +98 -38
- package/dist/dashboard/routes/surfaces.js +2 -1
- package/dist/dashboard/selectors/operate-selector.js +105 -12
- package/dist/dashboard/server.js +2 -0
- package/dist/dashboard/types.d.ts +4 -1
- package/dist/engines/index.d.ts +2 -0
- package/dist/engines/index.js +1 -0
- package/dist/engines/learning/index.d.ts +1 -0
- package/dist/engines/learning/index.js +1 -0
- package/dist/engines/learning-network.d.ts +150 -0
- package/dist/engines/learning-network.js +452 -0
- package/dist/engines/orchestrator/decision-spine.d.ts +30 -3
- package/dist/engines/orchestrator/decision-spine.js +195 -23
- package/dist/index.js +3 -1
- package/dist/licensing/enforcement.js +18 -1
- package/dist/phantom/runtime.d.ts +15 -1
- package/dist/phantom/runtime.js +144 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
<a href="https://www.producthunt.com/products/nexus-prime?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-nexus-prime" target="_blank" rel="noopener noreferrer"><img alt="Nexus-Prime — Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1096831&theme=dark&t=1773345508816"></a>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
|
-
> **
|
|
41
|
+
> **Stop restarting your coding agents from zero.**
|
|
42
42
|
>
|
|
43
|
-
>
|
|
43
|
+
> Nexus Prime gives Claude Code, Codex, Cursor, Windsurf, OpenCode, Aider, and the rest of your local agent stack one shared memory, one proof trail, and one dashboard for what actually happened.
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -74,14 +74,15 @@ Agents should start with `nexus_session_bootstrap`, then route the raw request t
|
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## Why teams install it
|
|
78
78
|
|
|
79
|
-
Nexus Prime
|
|
79
|
+
Nexus Prime is the local-first control plane for serious AI-assisted coding.
|
|
80
80
|
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
81
|
+
- **Shared memory for every agent.** Your tools stop acting like strangers and start carrying useful context across sessions.
|
|
82
|
+
- **Lower token waste.** Nexus routes agents toward the files and facts that matter instead of rereading the repo every turn.
|
|
83
|
+
- **Runtime truth you can inspect.** The dashboard shows runs, memory, token savings, and agent activity instead of hiding the messy parts.
|
|
84
|
+
- **Safer multi-file work.** Planning, verification, and handoff state stay attached to the repo, so interrupted work is easier to resume and review.
|
|
85
|
+
- **Local by default.** Your code and agent memory stay on your machine unless you explicitly opt into something else.
|
|
85
86
|
|
|
86
87
|
---
|
|
87
88
|
|
|
@@ -95,7 +96,7 @@ One tracked session was measured at **100,000,000 tokens**. Ninety-nine point fo
|
|
|
95
96
|
|
|
96
97
|
This is **agent amnesia** — and it's the quiet tax on every hour you spend with AI-assisted coding.
|
|
97
98
|
|
|
98
|
-
**Nexus Prime
|
|
99
|
+
**Nexus Prime turns that cold start into a remembered workspace.**
|
|
99
100
|
|
|
100
101
|
---
|
|
101
102
|
|
|
@@ -458,7 +459,8 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
|
|
|
458
459
|
|---------|-------------------|
|
|
459
460
|
| 💬 [**Discord**](https://discord.gg/tByGZgk5gS) | Real-time help, show-and-tell, feature ideas |
|
|
460
461
|
| 🔴 [**Reddit — r/Nexus_Prime**](https://www.reddit.com/r/Nexus_Prime/) | Long-form posts, releases, community wins |
|
|
461
|
-
| 🐦 [**X / Twitter**](https://x.com/
|
|
462
|
+
| 🐦 [**X / Twitter**](https://x.com/getnexusprime) | Launch announcements, tips, updates |
|
|
463
|
+
| 📸 [**Instagram**](https://www.instagram.com/nexus_prime.cfd/) | Product clips, launch updates, behind-the-scenes |
|
|
462
464
|
| 📧 [**adarsh@nexus-prime.cfd**](mailto:adarsh@nexus-prime.cfd) | License requests, upgrades, pilots, enterprise |
|
|
463
465
|
| 📧 [**hello@nexus-prime.cfd**](mailto:hello@nexus-prime.cfd) | General support, press, community |
|
|
464
466
|
| 🌐 [**nexus-prime.cfd**](https://nexus-prime.cfd) | Product site, demos, pricing, setup guides |
|
|
@@ -238,7 +238,7 @@ export function buildMcpToolDefinitions() {
|
|
|
238
238
|
properties: {
|
|
239
239
|
goal: { type: 'string', description: 'Raw goal or task description for this session' },
|
|
240
240
|
files: { type: 'array', items: { type: 'string' }, description: 'Optional candidate file constraints' },
|
|
241
|
-
detailLevel: { type: 'string', enum: ['compact', 'standard', 'debug'], description: 'Response verbosity; defaults to compact for MCP callers' },
|
|
241
|
+
detailLevel: { type: 'string', enum: ['compact', 'standard', 'debug', 'full'], description: 'Response verbosity; defaults to compact for MCP callers' },
|
|
242
242
|
depth: { type: 'string', enum: ['fast', 'deep'], description: 'Optional bootstrap depth override; defaults to fast unless debug mode is requested' },
|
|
243
243
|
include: { type: 'array', items: { type: 'string' }, description: 'Optional rich payload sections to include for advanced clients' },
|
|
244
244
|
intent: { type: 'string', enum: ['inspect', 'plan', 'mutate'], description: 'Requested orchestration stance for the bootstrap summary' },
|
|
@@ -268,7 +268,7 @@ export function buildMcpToolDefinitions() {
|
|
|
268
268
|
properties: {
|
|
269
269
|
prompt: { type: 'string', description: 'Raw prompt or objective to orchestrate end-to-end' },
|
|
270
270
|
files: { type: 'array', items: { type: 'string' }, description: 'Optional hard constraints for candidate files' },
|
|
271
|
-
detailLevel: { type: 'string', enum: ['compact', 'standard', 'debug'], description: 'Response verbosity; defaults to compact for MCP callers' },
|
|
271
|
+
detailLevel: { type: 'string', enum: ['compact', 'standard', 'debug', 'full'], description: 'Response verbosity; defaults to compact for MCP callers' },
|
|
272
272
|
intent: { type: 'string', enum: ['inspect', 'plan', 'mutate'], description: 'Requested orchestration stance; inspect/plan should stay read-only' },
|
|
273
273
|
topology: { type: 'string', enum: ['auto', 'manager-tools', 'handoff', 'dag-pool', 'worktree-swarm'], description: 'Requested orchestration topology hint' },
|
|
274
274
|
workers: { type: 'number', description: 'Optional worker override' },
|
|
@@ -282,6 +282,7 @@ export function buildMcpToolDefinitions() {
|
|
|
282
282
|
executionPreset: { type: 'string', enum: ['fast', 'balanced', 'deep', 'release'], description: 'Optional execution preset that maps orchestration depth, verification strictness, and backend routing' },
|
|
283
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
|
+
wait: { type: 'boolean', description: 'Alias for inline/sync execution. Waits up to the MCP-safe bounded window and returns the final result when it completes in time.' },
|
|
285
286
|
waitMs: { type: 'number', description: 'Advanced/debug bounded wait for inline execution. Clamped to 45 seconds; normal orchestrate calls return a queued preflight immediately.' },
|
|
286
287
|
inline: { type: 'boolean', description: 'Advanced/debug only: wait for inline orchestrate output instead of the default fast queued preflight.' }
|
|
287
288
|
},
|
|
@@ -101,7 +101,10 @@ function shouldReturnQueuedReceipt(toolName, args) {
|
|
|
101
101
|
if (!SLOW_TOOLS.has(toolName))
|
|
102
102
|
return false;
|
|
103
103
|
if (toolName === 'nexus_orchestrate') {
|
|
104
|
-
return !isTruthyFlag(args.inline)
|
|
104
|
+
return !isTruthyFlag(args.inline)
|
|
105
|
+
&& !isTruthyFlag(args.sync)
|
|
106
|
+
&& !isTruthyFlag(args.blocking)
|
|
107
|
+
&& !isTruthyFlag(args.wait);
|
|
105
108
|
}
|
|
106
109
|
return isTruthyFlag(args.background)
|
|
107
110
|
|| isTruthyFlag(args.async)
|
|
@@ -109,8 +112,13 @@ function shouldReturnQueuedReceipt(toolName, args) {
|
|
|
109
112
|
|| isTruthyFlag(args.detach);
|
|
110
113
|
}
|
|
111
114
|
function resolveMaxSyncMs(toolName, args) {
|
|
115
|
+
const explicitWait = isTruthyFlag(args.wait)
|
|
116
|
+
|| isTruthyFlag(args.inline)
|
|
117
|
+
|| isTruthyFlag(args.sync)
|
|
118
|
+
|| isTruthyFlag(args.blocking);
|
|
112
119
|
return coerceBoundedWaitMs(args.waitMs)
|
|
113
120
|
?? coerceBoundedWaitMs(args.maxSyncMs)
|
|
121
|
+
?? (toolName === 'nexus_orchestrate' && explicitWait ? MAX_CLIENT_SYNC_WAIT_MS : undefined)
|
|
114
122
|
?? (toolName === 'nexus_orchestrate' ? ORCHESTRATE_DEFAULT_MAX_SYNC_MS : DEFAULT_MAX_SYNC_MS);
|
|
115
123
|
}
|
|
116
124
|
function asStringList(value) {
|
|
@@ -22,6 +22,8 @@ function renderTable(rows, maxRows = 20) {
|
|
|
22
22
|
}
|
|
23
23
|
/** Build a compact timing + savings footer line for tool responses. */
|
|
24
24
|
function buildMcpFooter(meta) {
|
|
25
|
+
if (process.env.NEXUS_MCP_RESPONSE_FOOTER !== '1')
|
|
26
|
+
return '';
|
|
25
27
|
const ms = meta.durationMs;
|
|
26
28
|
const timing = ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
27
29
|
return `\n\n─── nexus-prime · ${meta.toolName} · ${timing} · dashboard: http://localhost:${DASH_PORT}/#runtime ───`;
|
|
@@ -275,7 +275,8 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
275
275
|
const timings = {
|
|
276
276
|
totalMs: Date.now() - callStartedAt,
|
|
277
277
|
};
|
|
278
|
-
const
|
|
278
|
+
const fullBootstrapDetails = detailLevel === 'debug' || detailLevel === 'full';
|
|
279
|
+
const payload = fullBootstrapDetails
|
|
279
280
|
? {
|
|
280
281
|
depth: bootstrap.depth,
|
|
281
282
|
workspace,
|
|
@@ -390,7 +391,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
390
391
|
return '';
|
|
391
392
|
}
|
|
392
393
|
})(),
|
|
393
|
-
...(
|
|
394
|
+
...(fullBootstrapDetails ? [
|
|
394
395
|
`Bootstrap depth: ${bootstrap.depth || bootstrapDepth}`,
|
|
395
396
|
`Execution mode: ${bootstrap.recommendedExecutionMode || 'autonomous'}`,
|
|
396
397
|
`Shortlist: ${bootstrap.shortlist?.skills?.slice(0, 3).join(', ') || 'none'} (skills), ${bootstrap.shortlist?.specialists?.slice(0, 3).join(', ') || 'none'} (specialists)`,
|
|
@@ -410,7 +411,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
410
411
|
`Bootstrap status: ${bootstrap.clientBootstrapStatus?.clients?.length || 0} client manifests tracked`,
|
|
411
412
|
] : []),
|
|
412
413
|
]),
|
|
413
|
-
|
|
414
|
+
fullBootstrapDetails && bootstrap.tokenOptimization?.autoApplied && bootstrap.tokenOptimization?.plan
|
|
414
415
|
? `Auto token plan\n\`\`\`txt\n${bootstrap.tokenOptimization.plan}\n\`\`\``
|
|
415
416
|
: '',
|
|
416
417
|
formatJsonDetails('Structured details', payload),
|
|
@@ -547,7 +548,36 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
547
548
|
intent: requestedIntent,
|
|
548
549
|
topology: requestedTopology,
|
|
549
550
|
};
|
|
551
|
+
const verboseDetails = detailLevel === 'debug' || detailLevel === 'full';
|
|
550
552
|
const compactPayload = {
|
|
553
|
+
workspace,
|
|
554
|
+
runId: execution.runId,
|
|
555
|
+
state: execution.state,
|
|
556
|
+
summary: summarizeExecution(execution),
|
|
557
|
+
resultPreview: truncateText(execution.result || summarizeExecution(execution), 700),
|
|
558
|
+
artifactsPath: execution.artifactsPath,
|
|
559
|
+
planner: execution.plannerState
|
|
560
|
+
? {
|
|
561
|
+
selectedCrew: execution.plannerState.selectedCrew?.name,
|
|
562
|
+
selectedSpecialists: execution.plannerState.selectedSpecialists.slice(0, 4).map((specialist) => specialist.name),
|
|
563
|
+
selectedWorkflows: execution.plannerState.selectedWorkflows.slice(0, 4),
|
|
564
|
+
}
|
|
565
|
+
: undefined,
|
|
566
|
+
worktreeHealth: runtimeUsage.worktreeHealth
|
|
567
|
+
? {
|
|
568
|
+
repoRoot: runtimeUsage.worktreeHealth.repoRoot,
|
|
569
|
+
overall: runtimeUsage.worktreeHealth.overall,
|
|
570
|
+
brokenEntries: runtimeUsage.worktreeHealth.brokenEntries,
|
|
571
|
+
repairedEntries: runtimeUsage.worktreeHealth.repairedEntries,
|
|
572
|
+
issues: runtimeUsage.worktreeHealth.issues,
|
|
573
|
+
}
|
|
574
|
+
: undefined,
|
|
575
|
+
modelRoute: selectionSummary.modelRoute,
|
|
576
|
+
verifiedWorkers,
|
|
577
|
+
continuationChildren: execution.continuationChildren.length,
|
|
578
|
+
timings,
|
|
579
|
+
};
|
|
580
|
+
const standardPayload = {
|
|
551
581
|
workspace,
|
|
552
582
|
runId: execution.runId,
|
|
553
583
|
state: execution.state,
|
|
@@ -616,31 +646,43 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
616
646
|
timings,
|
|
617
647
|
payloadRef,
|
|
618
648
|
};
|
|
619
|
-
const payload =
|
|
649
|
+
const payload = verboseDetails
|
|
650
|
+
? debugPayload
|
|
651
|
+
: detailLevel === 'standard'
|
|
652
|
+
? standardPayload
|
|
653
|
+
: compactPayload;
|
|
654
|
+
const verboseResponse = detailLevel !== 'compact';
|
|
655
|
+
const stateLine = execution.state === 'inspected'
|
|
656
|
+
? 'Orchestrated run inspected (read-only advisory; no diff expected).'
|
|
657
|
+
: `Orchestrated run ${execution.state}.`;
|
|
620
658
|
return {
|
|
621
659
|
content: [{
|
|
622
660
|
type: 'text',
|
|
623
661
|
text: [
|
|
624
662
|
upfrontTokenNote ? `[Token plan] ${upfrontTokenNote}` : '',
|
|
625
|
-
|
|
663
|
+
stateLine,
|
|
626
664
|
formatBullets([
|
|
627
665
|
`Workspace: ${workspace.repoName} (${workspace.workspaceSource})`,
|
|
628
666
|
`Run ID: ${execution.runId}`,
|
|
629
667
|
`Summary: ${summarizeExecution(execution)}`,
|
|
630
668
|
`Crew: ${execution.plannerState?.selectedCrew?.name || 'baseline path'}`,
|
|
631
|
-
`Decomposition: ${selectionSummary.phaseCount} phase(s), ${selectionSummary.workerLaneCount} worker lane(s), ${selectionSummary.mode}`,
|
|
632
669
|
`Hired/selected: crew ${selectionSummary.crew}; specialists ${formatSelectionList(selectionSummary.specialists)}; workflows ${formatSelectionList(selectionSummary.workflows)}; skills ${formatSelectionList(selectionSummary.skills)}`,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
670
|
+
...(verboseResponse ? [
|
|
671
|
+
`Decomposition: ${selectionSummary.phaseCount} phase(s), ${selectionSummary.workerLaneCount} worker lane(s), ${selectionSummary.mode}`,
|
|
672
|
+
`Model route: ${formatModelRoute(selectionSummary.modelRoute)}`,
|
|
673
|
+
`Budget route: ${selectionSummary.budgetRoute || 'budget pending'}`,
|
|
674
|
+
selectionSummary.agentFlow?.stages?.length
|
|
675
|
+
? `AgentFlow gates: ${selectionSummary.agentFlow.stages.map((stage) => `${stage.stage}:${stage.ownerRole}`).join(' -> ')}`
|
|
676
|
+
: null,
|
|
677
|
+
`Selection audit: ${selectionSummary.auditSelected} selected, ${selectionSummary.auditRejected} rejected`,
|
|
678
|
+
] : [
|
|
679
|
+
`Model route: ${formatModelRoute(selectionSummary.modelRoute)}`,
|
|
680
|
+
]),
|
|
639
681
|
`Verification: ${verifiedWorkers}/${execution.workerResults.length} worker(s) verified`,
|
|
640
|
-
`Tokens: saved ${Number(execution.tokenTelemetry?.savedTokens || 0).toLocaleString()} · compression ${Number(execution.tokenTelemetry?.compressionPct || 0)}
|
|
682
|
+
verboseResponse ? `Tokens: saved ${Number(execution.tokenTelemetry?.savedTokens || 0).toLocaleString()} · compression ${Number(execution.tokenTelemetry?.compressionPct || 0)}%` : null,
|
|
641
683
|
autoTokenApplyNote || null,
|
|
642
|
-
`Payload ref: ${workspace.stateKey} · ${detailLevel}
|
|
643
|
-
...(
|
|
684
|
+
verboseResponse ? `Payload ref: ${workspace.stateKey} · ${detailLevel}` : null,
|
|
685
|
+
...(verboseDetails ? [
|
|
644
686
|
`Specialists: ${execution.plannerState?.selectedSpecialists.map((specialist) => specialist.name).slice(0, 4).join(', ') || 'none selected'}`,
|
|
645
687
|
`Assets: ${(execution.activeSkills || []).length} skills · ${(execution.activeWorkflows || []).length} workflows · ${(runtimeUsage.artifactSelectionAudit?.selected?.length || 0)} audited selections`,
|
|
646
688
|
`Task graph: ${runtimeUsage.taskGraph?.phases?.length || execution.taskGraph?.phases?.length || 0} phases · ${runtimeUsage.workerPlan?.totalWorkers || execution.workerPlan?.totalWorkers || 0} workers`,
|
|
@@ -652,9 +694,9 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
652
694
|
`Payload ref: ${workspace.stateKey} · ${detailLevel}`,
|
|
653
695
|
] : []),
|
|
654
696
|
]),
|
|
655
|
-
|
|
697
|
+
verboseDetails && execution.result ? `Result\n\`\`\`\n${execution.result}\n\`\`\`` : `Result preview\n\`\`\`\n${truncateText(execution.result || summarizeExecution(execution), 900)}\n\`\`\``,
|
|
656
698
|
formatJsonDetails('Structured details', payload),
|
|
657
|
-
hctx.formatRemainingProtocolSteps(),
|
|
699
|
+
verboseResponse ? hctx.formatRemainingProtocolSteps() : '',
|
|
658
700
|
].filter(Boolean).join('\n\n'),
|
|
659
701
|
}],
|
|
660
702
|
};
|
|
@@ -684,7 +726,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
684
726
|
runtimeUsage?.skipReasons?.length ? `Skipped stages: ${runtimeUsage.skipReasons.join(' · ')}` : 'Skipped stages: not recorded',
|
|
685
727
|
]),
|
|
686
728
|
formatJsonDetails('Structured details', payload),
|
|
687
|
-
hctx.formatRemainingProtocolSteps(),
|
|
729
|
+
detailLevel !== 'compact' ? hctx.formatRemainingProtocolSteps() : '',
|
|
688
730
|
].join('\n\n'),
|
|
689
731
|
}],
|
|
690
732
|
};
|
|
@@ -90,7 +90,7 @@ export function formatJsonDetails(label, value) {
|
|
|
90
90
|
}
|
|
91
91
|
export function normalizeDetailLevel(value) {
|
|
92
92
|
const normalized = String(value ?? 'compact').toLowerCase();
|
|
93
|
-
if (normalized === 'standard' || normalized === 'debug')
|
|
93
|
+
if (normalized === 'standard' || normalized === 'debug' || normalized === 'full')
|
|
94
94
|
return normalized;
|
|
95
95
|
return 'compact';
|
|
96
96
|
}
|
|
@@ -100,7 +100,7 @@ export function normalizeBootstrapDepth(value, detailLevel) {
|
|
|
100
100
|
return 'deep';
|
|
101
101
|
if (normalized === 'fast')
|
|
102
102
|
return 'fast';
|
|
103
|
-
return detailLevel === 'debug' ? 'deep' : 'fast';
|
|
103
|
+
return detailLevel === 'debug' || detailLevel === 'full' ? 'deep' : 'fast';
|
|
104
104
|
}
|
|
105
105
|
export function normalizeResponseIntent(value) {
|
|
106
106
|
const normalized = String(value ?? 'inspect').toLowerCase();
|
|
@@ -14,7 +14,7 @@ import type { SessionDNAManager } from '../../../engines/session-dna.js';
|
|
|
14
14
|
import type { NgramIndex } from '../../../engines/ngram-index.js';
|
|
15
15
|
export type McpToolProfile = 'autonomous' | 'full';
|
|
16
16
|
export type LifecyclePhase = 'pre-bootstrap' | 'bootstrapped' | 'orchestrated' | 'working' | 'closing';
|
|
17
|
-
export type ResponseDetailLevel = 'compact' | 'standard' | 'debug';
|
|
17
|
+
export type ResponseDetailLevel = 'compact' | 'standard' | 'debug' | 'full';
|
|
18
18
|
export type ResponseIntent = 'inspect' | 'plan' | 'mutate';
|
|
19
19
|
export type ResponseTopology = 'auto' | 'manager-tools' | 'handoff' | 'dag-pool' | 'worktree-swarm';
|
|
20
20
|
/** Session-level telemetry tracker. Moved to types.ts so handler files can
|
|
@@ -8,6 +8,18 @@
|
|
|
8
8
|
import { CACHE_TTL, S, bus } from './state.js';
|
|
9
9
|
|
|
10
10
|
const _cache = new Map();
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
12
|
+
|
|
13
|
+
function _timeoutFor(url, override) {
|
|
14
|
+
if (Number.isFinite(Number(override))) return Number(override);
|
|
15
|
+
if (url.includes('/api/tokens/')) return 1_800;
|
|
16
|
+
if (url.includes('/api/license')) return 2_500;
|
|
17
|
+
if (url.includes('/api/runtimes')) return 3_000;
|
|
18
|
+
if (url.includes('/api/dashboard/surface/')) return 4_000;
|
|
19
|
+
if (url.includes('/api/dashboard/summary')) return 4_500;
|
|
20
|
+
if (url.includes('/api/knowledge-topology')) return 7_000;
|
|
21
|
+
return DEFAULT_TIMEOUT_MS;
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
function _scopedUrl(url) {
|
|
13
25
|
if (typeof url !== 'string' || !url.startsWith('/api/')) return url;
|
|
@@ -27,32 +39,39 @@ function _scopedUrl(url) {
|
|
|
27
39
|
* then kick off a background refresh. Callers get the fast synchronous hit
|
|
28
40
|
* for p50 <5ms; the next render cycle gets the fresh value.
|
|
29
41
|
*/
|
|
30
|
-
export async function api(url, ttl = CACHE_TTL) {
|
|
42
|
+
export async function api(url, ttl = CACHE_TTL, opts = {}) {
|
|
31
43
|
const requestUrl = _scopedUrl(url);
|
|
32
44
|
const hit = _cache.get(requestUrl);
|
|
33
45
|
const fresh = !hit || (Date.now() - hit.ts >= ttl);
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
if (hit) {
|
|
48
|
+
if (fresh) {
|
|
49
|
+
_fetch(requestUrl, opts).catch(() => {});
|
|
50
|
+
bus.emit('api:stale', { url: requestUrl, ageMs: Date.now() - hit.ts });
|
|
51
|
+
} else {
|
|
52
|
+
_fetch(requestUrl, opts).catch(() => {});
|
|
53
|
+
}
|
|
54
|
+
return hit.data;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
return
|
|
57
|
+
return _fetch(requestUrl, opts);
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
async function _fetch(url) {
|
|
60
|
+
async function _fetch(url, opts = {}) {
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const timeout = setTimeout(() => controller.abort(), _timeoutFor(url, opts.timeoutMs));
|
|
47
63
|
try {
|
|
48
|
-
const r = await fetch(url);
|
|
64
|
+
const r = await fetch(url, { signal: controller.signal });
|
|
49
65
|
if (!r.ok) return null;
|
|
50
66
|
const d = await r.json();
|
|
51
67
|
_cache.set(url, { data: d, ts: Date.now() });
|
|
52
68
|
bus.emit('cache:updated', url);
|
|
53
69
|
return d;
|
|
54
|
-
} catch {
|
|
70
|
+
} catch (err) {
|
|
71
|
+
bus.emit('api:timeout', { url, error: err?.name === 'AbortError' ? 'timeout' : String(err?.message || err) });
|
|
55
72
|
return null;
|
|
73
|
+
} finally {
|
|
74
|
+
clearTimeout(timeout);
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
<link rel="stylesheet" href="./styles/workforce.css">
|
|
13
13
|
<link rel="stylesheet" href="./styles/memory.css">
|
|
14
14
|
<link rel="stylesheet" href="./styles/context-log.css">
|
|
15
|
+
<link rel="stylesheet" href="./styles/learning.css">
|
|
15
16
|
<link rel="stylesheet" href="./styles/governance.css">
|
|
16
17
|
<link rel="stylesheet" href="./styles/trust.css">
|
|
17
18
|
<link rel="stylesheet" href="./styles/animation.css">
|
|
@@ -56,6 +57,7 @@ if(location.protocol==='file:'){
|
|
|
56
57
|
<a class="nav-item" data-nav="runtime" href="#runtime">⚡ Runtime</a>
|
|
57
58
|
<a class="nav-item" data-nav="workforce" href="#workforce">Workforce</a>
|
|
58
59
|
<a class="nav-item" data-nav="memory" href="#memory">Memory</a>
|
|
60
|
+
<a class="nav-item" data-nav="learning" href="#learning">Learning</a>
|
|
59
61
|
<a class="nav-item" data-nav="context-log" href="#context-log">Context Log</a>
|
|
60
62
|
<a class="nav-item" data-nav="repo" href="#repo">Repo</a>
|
|
61
63
|
<a class="nav-item" data-nav="knowledge" href="#knowledge">Knowledge</a>
|
|
@@ -111,6 +113,8 @@ if(location.protocol==='file:'){
|
|
|
111
113
|
<div class="hero-stat"><div class="metric-val" id="m-ops">—</div><div class="metric-lbl">Operatives</div><canvas id="spark-ops" class="sparkline" width="80" height="24" aria-hidden="true"></canvas></div>
|
|
112
114
|
</div>
|
|
113
115
|
|
|
116
|
+
<div id="model-router-panel" class="model-router-panel card" aria-label="Model and context router"></div>
|
|
117
|
+
|
|
114
118
|
<!-- Live strip + kanban -->
|
|
115
119
|
<div id="agents-live-strip"></div>
|
|
116
120
|
<div id="kanban-board">
|
|
@@ -189,6 +193,7 @@ if(location.protocol==='file:'){
|
|
|
189
193
|
|
|
190
194
|
<div class="memory-toolbar">
|
|
191
195
|
<button class="btn btn-sm" id="mem-graph-max-btn">Maximize graph</button>
|
|
196
|
+
<button class="btn btn-sm" id="mem-graph-focus-btn">Focus selected</button>
|
|
192
197
|
<button class="btn btn-sm" id="mem-browse-btn">Browse memories</button>
|
|
193
198
|
</div>
|
|
194
199
|
<div id="graph-container" class="card">
|
|
@@ -228,13 +233,33 @@ if(location.protocol==='file:'){
|
|
|
228
233
|
<div id="context-log-view"></div>
|
|
229
234
|
</section>
|
|
230
235
|
|
|
236
|
+
<!-- LEARNING ───────────────────────────────────────────────── -->
|
|
237
|
+
<section class="view-panel" data-view="learning" aria-label="Learning network">
|
|
238
|
+
<div id="learning-view"></div>
|
|
239
|
+
</section>
|
|
240
|
+
|
|
231
241
|
<!-- REPO ───────────────────────────────────────────────────── -->
|
|
232
242
|
<section class="view-panel" data-view="repo" aria-label="Repo graph">
|
|
233
243
|
<div class="shd">Repo knowledge graph</div>
|
|
234
|
-
<div id="repo-graph-header"
|
|
244
|
+
<div id="repo-graph-header" class="repo-graph-header">
|
|
235
245
|
<span id="repo-graph-meta">—</span>
|
|
236
|
-
<
|
|
237
|
-
|
|
246
|
+
<div class="repo-graph-actions">
|
|
247
|
+
<button id="repo-build-btn" class="btn btn-sm">Build graph</button>
|
|
248
|
+
<button id="repo-memory-focus-btn" class="btn btn-sm">Memory overlay</button>
|
|
249
|
+
<input id="repo-search-input" class="repo-graph-search" type="text" placeholder="Search nodes…" autocomplete="off">
|
|
250
|
+
<div class="repo-graph-controls" aria-label="Repo graph navigation controls">
|
|
251
|
+
<button id="repo-zoom-out-btn" class="repo-graph-control" title="Zoom out">-</button>
|
|
252
|
+
<button id="repo-zoom-in-btn" class="repo-graph-control" title="Zoom in">+</button>
|
|
253
|
+
<button id="repo-fit-btn" class="repo-graph-control repo-graph-control-wide" title="Fit graph">Fit</button>
|
|
254
|
+
<button id="repo-reset-btn" class="repo-graph-control repo-graph-control-wide" title="Reset view">Reset</button>
|
|
255
|
+
<button id="repo-pan-left-btn" class="repo-graph-control" title="Pan left"><</button>
|
|
256
|
+
<button id="repo-pan-right-btn" class="repo-graph-control" title="Pan right">></button>
|
|
257
|
+
<button id="repo-pan-up-btn" class="repo-graph-control" title="Pan up">^</button>
|
|
258
|
+
<button id="repo-pan-down-btn" class="repo-graph-control" title="Pan down">v</button>
|
|
259
|
+
<button id="repo-graph-max-btn" class="repo-graph-control repo-graph-control-wide" title="Maximize graph">Max</button>
|
|
260
|
+
<span id="repo-viewport-badge" class="repo-viewport-badge">100%</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
238
263
|
</div>
|
|
239
264
|
<div class="card" style="position:relative;overflow:hidden">
|
|
240
265
|
<div id="repo-graph-container" style="width:100%;height:480px;position:relative">
|
|
@@ -15,6 +15,7 @@ import { init as initCmdBar } from './widgets/command-bar.js';
|
|
|
15
15
|
import * as Board from './views/board.js';
|
|
16
16
|
import * as Workforce from './views/workforce.js';
|
|
17
17
|
import * as Memory from './views/memory.js';
|
|
18
|
+
import * as Learning from './views/learning.js';
|
|
18
19
|
import * as ContextLog from './views/context-log.js';
|
|
19
20
|
import * as Knowledge from './views/knowledge.js';
|
|
20
21
|
import * as Repo from './views/repo.js';
|
|
@@ -54,6 +55,7 @@ navRegister('board', Board.load);
|
|
|
54
55
|
navRegister('runtime', Runtime.load);
|
|
55
56
|
navRegister('workforce', Workforce.load);
|
|
56
57
|
navRegister('memory', Memory.load);
|
|
58
|
+
navRegister('learning', Learning.load);
|
|
57
59
|
navRegister('context-log', ContextLog.load);
|
|
58
60
|
navRegister('repo', Repo.load);
|
|
59
61
|
navRegister('knowledge', Knowledge.load);
|
|
@@ -89,6 +91,10 @@ setOnEvent(evt => {
|
|
|
89
91
|
if (tab === 'memory' && evt.category === 'memory') {
|
|
90
92
|
_refreshMemorySoon();
|
|
91
93
|
}
|
|
94
|
+
if (tab === 'learning' && (evt.category === 'memory' || String(evt.type||'').startsWith('orchestration.'))) {
|
|
95
|
+
bustCache('/api/learning/packets?limit=24');
|
|
96
|
+
Learning.load();
|
|
97
|
+
}
|
|
92
98
|
if (tab === 'governance' && evt.category === 'darwin') {
|
|
93
99
|
Governance.load();
|
|
94
100
|
}
|
|
@@ -123,6 +129,9 @@ setOnEvent(evt => {
|
|
|
123
129
|
if (tab === 'context-log') {
|
|
124
130
|
ContextLog.load();
|
|
125
131
|
}
|
|
132
|
+
if (tab === 'learning') {
|
|
133
|
+
Learning.load();
|
|
134
|
+
}
|
|
126
135
|
}
|
|
127
136
|
// Workspace root promoted (e.g. from bootstrap hint) — refresh header immediately.
|
|
128
137
|
if (String(evt.type||'') === 'workspace.changed') {
|
|
@@ -184,8 +193,9 @@ async function loadProjects() {
|
|
|
184
193
|
]);
|
|
185
194
|
S.projects = Array.isArray(projectsD) ? projectsD : (projectsD?.projects||[]);
|
|
186
195
|
S.runtimes = Array.isArray(runtimesD) ? runtimesD : (runtimesD?.runtimes||[]);
|
|
187
|
-
_selectDefaultRuntime();
|
|
196
|
+
const scopeChanged = _selectDefaultRuntime();
|
|
188
197
|
_renderProjectSelectors();
|
|
198
|
+
return scopeChanged;
|
|
189
199
|
}
|
|
190
200
|
|
|
191
201
|
async function loadCompanies() {
|
|
@@ -210,7 +220,7 @@ function _renderProjectSelectors() {
|
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
function _selectDefaultRuntime() {
|
|
213
|
-
if (S.scope.runtimeId || !(S.runtimes||[]).length) return;
|
|
223
|
+
if (S.scope.runtimeId || !(S.runtimes||[]).length) return false;
|
|
214
224
|
const current = S.workspace?.repoRoot || S.workspace?.workspaceRoot || '';
|
|
215
225
|
const candidates = (S.runtimes||[]).filter(r => r?.runtimeId);
|
|
216
226
|
const exact = current ? candidates.find(r => r.repoIdentity?.repoRoot === current) : null;
|
|
@@ -228,7 +238,10 @@ function _selectDefaultRuntime() {
|
|
|
228
238
|
return root && root !== '/';
|
|
229
239
|
});
|
|
230
240
|
const selected = (exact && !currentLooksLikeHome ? exact : null) || remoteRepo || deepestRepo || candidates[0];
|
|
231
|
-
|
|
241
|
+
const nextRuntimeId = selected?.runtimeId || null;
|
|
242
|
+
const changed = nextRuntimeId !== S.scope.runtimeId;
|
|
243
|
+
S.scope.runtimeId = nextRuntimeId;
|
|
244
|
+
return changed;
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
/* ─────────────────── Header / ticker ─────────────────── */
|
|
@@ -344,29 +357,30 @@ async function bootstrap() {
|
|
|
344
357
|
_renderHeader(false);
|
|
345
358
|
|
|
346
359
|
await loadWorkspace();
|
|
347
|
-
await loadProjects();
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
loadCompanies(),
|
|
353
|
-
loadRepoTree(),
|
|
354
|
-
api('/api/license', 60000),
|
|
355
|
-
]);
|
|
356
|
-
S.license = lic;
|
|
357
|
-
|
|
358
|
-
// Populate agent selector
|
|
359
|
-
const agentSel = $('ctx-agent');
|
|
360
|
-
if (agentSel && S.synapseHealth.length) {
|
|
361
|
-
agentSel.innerHTML = '<option value="">All agents</option>' +
|
|
362
|
-
S.synapseHealth.map(o=>`<option value="${esc(o.id||o.operativeId)}">${esc(o.role||o.name||o.id||'agent')}</option>`).join('');
|
|
360
|
+
const scopeChanged = await loadProjects();
|
|
361
|
+
if (scopeChanged) {
|
|
362
|
+
bustCache('/api/workspace');
|
|
363
|
+
bustCache('/api/repo-tree');
|
|
364
|
+
await loadWorkspace();
|
|
363
365
|
}
|
|
364
366
|
|
|
365
|
-
// Wire router
|
|
367
|
+
// Wire router before slow side panels. Each view owns progressive loading.
|
|
366
368
|
routerStart();
|
|
367
369
|
|
|
368
370
|
// Connect SSE stream — SSE drives all live updates; no polling needed.
|
|
369
371
|
connectSSE();
|
|
372
|
+
|
|
373
|
+
Promise.allSettled([
|
|
374
|
+
loadCompanies(),
|
|
375
|
+
loadRepoTree(),
|
|
376
|
+
api('/api/license', 60000, { timeoutMs: 2500 }).then(lic => { S.license = lic; }),
|
|
377
|
+
]).then(() => {
|
|
378
|
+
const agentSel = $('ctx-agent');
|
|
379
|
+
if (agentSel && S.synapseHealth.length) {
|
|
380
|
+
agentSel.innerHTML = '<option value="">All agents</option>' +
|
|
381
|
+
S.synapseHealth.map(o=>`<option value="${esc(o.id||o.operativeId)}">${esc(o.role||o.name||o.id||'agent')}</option>`).join('');
|
|
382
|
+
}
|
|
383
|
+
});
|
|
370
384
|
}
|
|
371
385
|
|
|
372
386
|
// Start when DOM is ready
|
|
@@ -86,6 +86,10 @@ export const S = {
|
|
|
86
86
|
contextLogRuns: [],
|
|
87
87
|
contextLogSelectedRunId: null,
|
|
88
88
|
contextLogSpine: null,
|
|
89
|
+
contextLogLearning: null,
|
|
90
|
+
learningSurface: null,
|
|
91
|
+
learningGraph: null,
|
|
92
|
+
learningSelectedPacketId: null,
|
|
89
93
|
|
|
90
94
|
// Neural Stream HUD ring buffer (latest SSE events, restored from v3.8.0)
|
|
91
95
|
neuralStream: [],
|