nexus-prime 7.9.30 → 7.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +9 -9
  2. package/dist/agents/adapters/mcp/handlers/orchestration.js +6 -4
  3. package/dist/agents/adapters/mcp/handlers/runtime.js +3 -3
  4. package/dist/agents/adapters/mcp/stdio-buffer.js +10 -0
  5. package/dist/agents/adapters/mcp.js +3 -1
  6. package/dist/build-assets.d.ts +1 -0
  7. package/dist/build-assets.js +39 -0
  8. package/dist/cli.js +1 -1
  9. package/dist/dashboard/app/index.html +17 -12
  10. package/dist/dashboard/app/main.js +52 -0
  11. package/dist/dashboard/app/state.js +5 -0
  12. package/dist/dashboard/app/styles/board.css +174 -4
  13. package/dist/dashboard/app/styles/governance.css +93 -0
  14. package/dist/dashboard/app/styles/memory.css +81 -1
  15. package/dist/dashboard/app/styles/runtime.css +2 -2
  16. package/dist/dashboard/app/styles/shell.css +19 -1
  17. package/dist/dashboard/app/styles/workforce.css +189 -3
  18. package/dist/dashboard/app/views/board.js +369 -23
  19. package/dist/dashboard/app/views/governance.js +19 -11
  20. package/dist/dashboard/app/views/learning.js +42 -2
  21. package/dist/dashboard/app/views/license.js +3 -3
  22. package/dist/dashboard/app/views/memory.js +220 -20
  23. package/dist/dashboard/app/views/runtime.js +93 -3
  24. package/dist/dashboard/app/views/workforce.js +461 -32
  25. package/dist/dashboard/routes/governance.js +6 -1
  26. package/dist/dashboard/routes/graph.js +19 -5
  27. package/dist/dashboard/routes/runtime.js +78 -0
  28. package/dist/dashboard/routes/workforce.d.ts +1 -0
  29. package/dist/dashboard/routes/workforce.js +40 -0
  30. package/dist/dashboard/selectors/operate-selector.js +219 -2
  31. package/dist/dashboard/types.d.ts +1 -0
  32. package/dist/engines/dispatch/context-pack.d.ts +3 -1
  33. package/dist/engines/dispatch/context-pack.js +2 -0
  34. package/dist/engines/dispatch/event-mapper.js +17 -0
  35. package/dist/engines/dispatch/push-dispatch.d.ts +3 -0
  36. package/dist/engines/dispatch/push-dispatch.js +66 -7
  37. package/dist/engines/event-bus.d.ts +15 -0
  38. package/dist/engines/middleware-pipeline.js +3 -3
  39. package/dist/engines/model-pricing.d.ts +74 -0
  40. package/dist/engines/model-pricing.js +438 -0
  41. package/dist/engines/orchestrator/decision-spine.d.ts +65 -0
  42. package/dist/engines/orchestrator/decision-spine.js +368 -44
  43. package/dist/engines/providers/registry.js +18 -15
  44. package/dist/engines/providers/types.d.ts +9 -1
  45. package/dist/engines/specialist-cost-estimator.d.ts +2 -5
  46. package/dist/engines/specialist-cost-estimator.js +16 -10
  47. package/dist/engines/token-analytics.d.ts +15 -0
  48. package/dist/engines/token-analytics.js +32 -0
  49. package/dist/index.js +36 -19
  50. package/dist/invokers/aider.js +26 -4
  51. package/dist/invokers/base.d.ts +3 -0
  52. package/dist/invokers/base.js +41 -0
  53. package/dist/invokers/claude-code.js +24 -7
  54. package/dist/invokers/codex.js +24 -5
  55. package/dist/invokers/cursor.js +25 -4
  56. package/dist/invokers/types.d.ts +25 -0
  57. package/dist/licensing/enforcement.js +11 -12
  58. package/dist/licensing/license-manager.d.ts +4 -5
  59. package/dist/licensing/license-manager.js +22 -24
  60. package/dist/licensing/types.d.ts +1 -1
  61. package/dist/licensing/upgrade-prompts.d.ts +3 -4
  62. package/dist/licensing/upgrade-prompts.js +15 -18
  63. package/dist/licensing/web-auth.d.ts +2 -2
  64. package/dist/licensing/web-auth.js +4 -4
  65. package/dist/phantom/runtime/diff-preview.js +12 -0
  66. package/dist/phantom/runtime.d.ts +24 -1
  67. package/dist/phantom/runtime.js +125 -18
  68. package/dist/postinstall/cleanup.js +1 -1
  69. package/dist/postinstall-bootstrap.js +4 -4
  70. package/dist/postinstall-runner.d.ts +1 -0
  71. package/dist/postinstall-runner.js +18 -0
  72. package/package.json +4 -3
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  <p>
17
17
  <img src="https://img.shields.io/badge/Protocol-MCP-4285F4?style=for-the-badge" alt="MCP Protocol">
18
18
  <img src="https://img.shields.io/badge/license-Commercial-6f42c1?style=for-the-badge" alt="Commercial License">
19
- <a href="https://nexus-prime.cfd/pricing"><img src="https://img.shields.io/badge/3--day-grace%20then%20license-00ff88?style=for-the-badge" alt="3-day grace then license"></a>
19
+ <a href="https://nexus-prime.cfd/pricing"><img src="https://img.shields.io/badge/15--day-trial%20then%20license-00ff88?style=for-the-badge" alt="15-day trial then license"></a>
20
20
  <img src="https://img.shields.io/badge/licenses-reviewed%20within%2024h-ff69b4?style=for-the-badge" alt="Licenses reviewed within 24 hours">
21
21
  <img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-444?style=for-the-badge" alt="Cross-platform">
22
22
  </p>
@@ -317,7 +317,7 @@ If any of that sounds like you — keep reading.
317
317
 
318
318
  <div align="center">
319
319
 
320
- **3-day no-license grace. Request a license before day 4. Manual approvals within 24 hours.**
320
+ **15-day local trial. Activate or upgrade before expiry; runtime work locks after day 15.**
321
321
 
322
322
  </div>
323
323
 
@@ -329,10 +329,10 @@ If any of that sounds like you — keep reading.
329
329
  <th>Caps</th>
330
330
  </tr>
331
331
  <tr>
332
- <td><b>🎁 Local Grace</b></td>
332
+ <td><b>🎁 Local Trial</b></td>
333
333
  <td>Everyone — install and verify locally</td>
334
- <td>3 days without a license</td>
335
- <td>Runtime available during grace; license required after day 3</td>
334
+ <td>15 days without a license</td>
335
+ <td>Runtime available during trial; license required after day 15</td>
336
336
  </tr>
337
337
  <tr>
338
338
  <td><b>💎 Independent Creators</b></td>
@@ -390,9 +390,9 @@ No — it supercharges them. Keep the agents you already love. Nexus Prime adds
390
390
  </details>
391
391
 
392
392
  <details>
393
- <summary><b>What happens after the 30-day trial ends?</b></summary>
393
+ <summary><b>What happens after the 15-day trial ends?</b></summary>
394
394
  <br>
395
- The local trial window ends and paid-tier work requires an active license. Your local memory and data stay on your machine; activate a license or request renewal from the account page to continue.
395
+ The local trial window ends and Nexus Prime runtime work is locked until an active license is activated. Your local memory and data stay on your machine; upgrade, buy, or request renewal from the account page to continue.
396
396
  </details>
397
397
 
398
398
  <details>
@@ -445,7 +445,7 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
445
445
 
446
446
  - 🏠 **100% local.** All data lives on your disk, in your home directory.
447
447
  - 🚫 **No cloud sync.** Your code, your memory, your logs — never uploaded.
448
- - 🔐 **Short no-account grace.** You can run locally for 3 days without signing up; a license is required after that grace period.
448
+ - 🔐 **Bounded local trial.** You can run locally for 15 days without signing up; a license is required after the trial expires.
449
449
  - 📊 **Telemetry is opt-in.** Off by default. When on, only aggregate, anonymous event counts are sent — never source code, never memory contents.
450
450
  - 🗑️ **Uninstall is clean.** Everything lives in one directory you can delete.
451
451
 
@@ -473,7 +473,7 @@ Nexus Prime was designed privacy-first, because the code on your machine is your
473
473
 
474
474
  Nexus Prime is a **commercial product** with manual license issuance:
475
475
 
476
- - ✅ 3-day local no-license grace, for everyone
476
+ - ✅ 15-day local trial, for everyone
477
477
  - ✅ Manual creator, pilot, team, and enterprise license review
478
478
  - ✅ License and upgrade requests reviewed within 24 hours
479
479
  - 💳 Paid plans for professional, team, and enterprise usage
@@ -21,6 +21,7 @@ import { getSharedTelemetry } from '../../../../engines/telemetry-remote.js';
21
21
  import { requireRuntime } from '../util/require-runtime.js';
22
22
  import { ensureCrGraphBuilt } from '../../../../engines/code-review-graph-client.js';
23
23
  import { recordFirstBootstrap } from '../../../../engines/telemetry.js';
24
+ import { estimateModelCostUsd } from '../../../../engines/model-pricing.js';
24
25
  function escapeMarkdownTableCell(value) {
25
26
  return String(value ?? '')
26
27
  .replace(/\r?\n/g, ' ')
@@ -122,6 +123,8 @@ function buildSelectionSummary(execution, runtimeUsage) {
122
123
  automations: asStringList(selected.automations, 8),
123
124
  workers,
124
125
  modelRoute: selectionPlan.modelRoute ?? execution.requestBrief?.modelPolicy,
126
+ preferencePolicy: selectionPlan.preferencePolicy ?? execution.requestBrief?.preferencePolicy,
127
+ podNetwork: selectionPlan.podNetwork,
125
128
  budgetRoute: formatBudgetRoute(selectionPlan),
126
129
  agentFlow: selectionPlan.executionPolicy?.agentFlow,
127
130
  phaseCount: Array.isArray(taskGraph.phases) ? taskGraph.phases.length : 0,
@@ -802,7 +805,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
802
805
  tokensForwarded: plan.totalEstimatedTokens,
803
806
  compressionRatio: grossInput > 0 ? plan.totalEstimatedTokens / grossInput : 0,
804
807
  fileCount: filePaths.length,
805
- usdValueSaved: plan.savings / 1000 * 0.0006,
808
+ usdValueSaved: estimateModelCostUsd('claude-sonnet-4-6', plan.savings, 0),
806
809
  });
807
810
  }
808
811
  }
@@ -811,9 +814,8 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
811
814
  const fullReads = plan.files.filter((a) => a.action === 'full').length;
812
815
  const nudge = hctx.telemetry.planningNudge('optimize', { fullReads });
813
816
  // Per-call receipt: honest token + cost accounting (dedup shown separately)
814
- const COST_PER_1K_IN = 0.003; // Sonnet-class baseline $/1K tokens
815
- const estimatedCostUsd = (plan.totalEstimatedTokens / 1_000) * COST_PER_1K_IN;
816
- const savedCostUsd = (plan.savings / 1_000) * COST_PER_1K_IN;
817
+ const estimatedCostUsd = estimateModelCostUsd('claude-sonnet-4-6', plan.totalEstimatedTokens, 0);
818
+ const savedCostUsd = estimateModelCostUsd('claude-sonnet-4-6', plan.savings, 0);
817
819
  const dedupLine = dedupTokens > 0
818
820
  ? `\n ${dedupTokens.toLocaleString()} tok deduped (${delta.unchanged.length} unchanged files)`
819
821
  : '';
@@ -44,10 +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**: License activation is required for paid-tier work; keep license/status tools available.`
47
+ ? `- **Agent motion**: 15-day trial is active; ask the user to activate or upgrade before expiry.`
48
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
+ ? `- **Agent motion**: Trial remains active; ask the user to request or activate a license soon, then keep working until expiry.`
50
+ : `- **Agent motion**: Trial remains active; activate before the 15-day window ends.`,
51
51
  ].filter((line) => Boolean(line)) : [];
52
52
  const lines = [
53
53
  `## License Status`,
@@ -4,16 +4,24 @@ let primedStdioInputStarted = false;
4
4
  let primedStdioInputReady = false;
5
5
  let primedStdioInputEnded = false;
6
6
  const primedStdioChunks = [];
7
+ function debugStdioBuffer(message) {
8
+ if (process.env.NEXUS_DEBUG_STDIO_BUFFER === '1') {
9
+ console.error(`[MCP stdio buffer] ${message}`);
10
+ }
11
+ }
7
12
  export function primeMcpStdioInput() {
8
13
  if (primedStdioInputStarted || process.stdin.isTTY)
9
14
  return;
10
15
  primedStdioInputStarted = true;
16
+ debugStdioBuffer('primed');
11
17
  process.stdin.on('data', (chunk) => {
12
18
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
13
19
  if (primedStdioInput && primedStdioInputReady) {
20
+ debugStdioBuffer(`forward ${buffer.length} bytes`);
14
21
  primedStdioInput.write(buffer);
15
22
  return;
16
23
  }
24
+ debugStdioBuffer(`capture ${buffer.length} bytes`);
17
25
  primedStdioChunks.push(buffer);
18
26
  });
19
27
  process.stdin.on('end', () => {
@@ -27,6 +35,7 @@ export function primeMcpStdioInput() {
27
35
  export function getMcpStdioInput() {
28
36
  if (primedStdioInputStarted && !primedStdioInput) {
29
37
  primedStdioInput = new PassThrough();
38
+ debugStdioBuffer('created passthrough');
30
39
  }
31
40
  return primedStdioInput ?? process.stdin;
32
41
  }
@@ -34,6 +43,7 @@ export function flushPrimedMcpStdioInput() {
34
43
  if (!primedStdioInput)
35
44
  return;
36
45
  primedStdioInputReady = true;
46
+ debugStdioBuffer(`flush ${primedStdioChunks.length} chunks`);
37
47
  for (const chunk of primedStdioChunks.splice(0)) {
38
48
  primedStdioInput.write(chunk);
39
49
  }
@@ -30,7 +30,7 @@ import { getFallbackRuntime, getFallbackOrchestrator, getGuardrailEngine, normal
30
30
  import { dispatchMcpToolCall } from './mcp/dispatch.js';
31
31
  import { LifecyclePolicy } from '../../engines/lifecycle-policy.js';
32
32
  import { startWatchdog } from './mcp/watchdog.js';
33
- import { getMcpStdioInput } from './mcp/stdio-buffer.js';
33
+ import { flushPrimedMcpStdioInput, getMcpStdioInput } from './mcp/stdio-buffer.js';
34
34
  // Derive project root from this file's location (dist/agents/adapters/mcp.js → project root)
35
35
  const __filename = fileURLToPath(import.meta.url);
36
36
  const PROJECT_ROOT = path.resolve(path.dirname(__filename), '..', '..', '..');
@@ -1170,6 +1170,8 @@ export class MCPAdapter {
1170
1170
  const transport = new StdioServerTransport(getMcpStdioInput(), process.stdout);
1171
1171
  await this.server.connect(transport);
1172
1172
  this.connected = true;
1173
+ flushPrimedMcpStdioInput();
1174
+ await new Promise((resolve) => setImmediate(resolve));
1173
1175
  startWatchdog();
1174
1176
  console.error('[MCP Adapter] Connected — runtime tools active');
1175
1177
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ const root = process.cwd();
4
+ const dist = path.join(root, 'dist');
5
+ function ensureDir(dir) {
6
+ fs.mkdirSync(dir, { recursive: true });
7
+ }
8
+ function copyDir(from, to) {
9
+ if (!fs.existsSync(from))
10
+ return;
11
+ fs.rmSync(to, { recursive: true, force: true });
12
+ ensureDir(path.dirname(to));
13
+ fs.cpSync(from, to, { recursive: true });
14
+ }
15
+ function copyFile(from, to) {
16
+ if (!fs.existsSync(from))
17
+ return;
18
+ ensureDir(path.dirname(to));
19
+ fs.copyFileSync(from, to);
20
+ }
21
+ function copyJsonFiles(fromDir, toDir) {
22
+ if (!fs.existsSync(fromDir))
23
+ return;
24
+ ensureDir(toDir);
25
+ for (const entry of fs.readdirSync(fromDir)) {
26
+ if (entry.endsWith('.json'))
27
+ copyFile(path.join(fromDir, entry), path.join(toDir, entry));
28
+ }
29
+ }
30
+ copyDir(path.join(root, 'src', 'dashboard', 'app'), path.join(dist, 'dashboard', 'app'));
31
+ copyFile(path.join(root, 'src', 'dashboard', 'welcome.html'), path.join(dist, 'dashboard', 'welcome.html'));
32
+ copyJsonFiles(path.join(root, 'src', 'engines', 'data'), path.join(dist, 'engines', 'data'));
33
+ copyDir(path.join(root, 'src', 'migrations'), path.join(dist, 'migrations'));
34
+ try {
35
+ fs.chmodSync(path.join(dist, 'cli.js'), 0o755);
36
+ }
37
+ catch {
38
+ // Windows/global npm shims do not require POSIX executable bits.
39
+ }
package/dist/cli.js CHANGED
@@ -1126,9 +1126,9 @@ program
1126
1126
  throw new Error('MCP adapter did not become ready before the startup deadline.');
1127
1127
  }
1128
1128
  flushPrimedMcpStdioInput();
1129
- await startup;
1130
1129
  console.error('Nexus Prime MCP Server running on stdio (standalone)');
1131
1130
  console.error('Memory persistence: active (~/.nexus-prime/memory.db)');
1131
+ await startup;
1132
1132
  const shutdown = async (signal) => {
1133
1133
  nexusEventBus.emit('nexus.shutdown', { signal });
1134
1134
  nexus?.getOrchestrator().dispose();
@@ -40,6 +40,9 @@ if(location.protocol==='file:'){
40
40
  <div class="sidebar-brand">
41
41
  <div class="brand-mark" aria-hidden="true"></div>
42
42
  <span class="brand-name">Nexus Prime</span>
43
+ <button id="sidebar-collapse-btn" class="sidebar-collapse-btn" type="button" aria-label="Collapse navigation" aria-expanded="true" title="Collapse navigation">
44
+ <span class="sidebar-collapse-icon" aria-hidden="true">&lt;</span>
45
+ </button>
43
46
  </div>
44
47
  <div class="sidebar-workspace" id="workspace-badge" title="Active workspace">
45
48
  <span class="ws-repo" id="workspace-repo">…</span>
@@ -53,18 +56,18 @@ if(location.protocol==='file:'){
53
56
  </div>
54
57
 
55
58
  <nav id="tab-nav" aria-label="Primary navigation">
56
- <a class="nav-item active" data-nav="board" href="#board">Board</a>
57
- <a class="nav-item" data-nav="runtime" href="#runtime">⚡ Runtime</a>
58
- <a class="nav-item" data-nav="workforce" href="#workforce">Workforce</a>
59
- <a class="nav-item" data-nav="memory" href="#memory">Memory</a>
60
- <a class="nav-item" data-nav="learning" href="#learning">Learning</a>
61
- <a class="nav-item" data-nav="context-log" href="#context-log">Context Log</a>
62
- <a class="nav-item" data-nav="repo" href="#repo">Repo</a>
63
- <a class="nav-item" data-nav="knowledge" href="#knowledge">Knowledge</a>
64
- <a class="nav-item" data-nav="governance" href="#governance">Governance</a>
65
- <a class="nav-item" data-nav="trust" href="#trust">Trust</a>
66
- <a class="nav-item" data-nav="license" href="#license">License</a>
67
- <a class="nav-item" data-nav="federation" href="#federation">Federation</a>
59
+ <a class="nav-item active" data-nav="board" data-short="B" title="Board" href="#board">Board</a>
60
+ <a class="nav-item" data-nav="runtime" data-short="R" title="Runtime" href="#runtime">⚡ Runtime</a>
61
+ <a class="nav-item" data-nav="workforce" data-short="W" title="Workforce" href="#workforce">Workforce</a>
62
+ <a class="nav-item" data-nav="memory" data-short="M" title="Memory" href="#memory">Memory</a>
63
+ <a class="nav-item" data-nav="learning" data-short="L" title="Learning" href="#learning">Learning</a>
64
+ <a class="nav-item" data-nav="context-log" data-short="C" title="Context Log" href="#context-log">Context Log</a>
65
+ <a class="nav-item" data-nav="repo" data-short="P" title="Repo" href="#repo">Repo</a>
66
+ <a class="nav-item" data-nav="knowledge" data-short="K" title="Knowledge" href="#knowledge">Knowledge</a>
67
+ <a class="nav-item" data-nav="governance" data-short="G" title="Governance" href="#governance">Governance</a>
68
+ <a class="nav-item" data-nav="trust" data-short="T" title="Trust" href="#trust">Trust</a>
69
+ <a class="nav-item" data-nav="license" data-short="$" title="License" href="#license">License</a>
70
+ <a class="nav-item" data-nav="federation" data-short="F" title="Federation" href="#federation">Federation</a>
68
71
  </nav>
69
72
 
70
73
  <div class="sidebar-foot">
@@ -166,6 +169,8 @@ if(location.protocol==='file:'){
166
169
  <section class="view-panel" data-view="workforce" aria-label="Workforce">
167
170
  <div class="shd">Operative org chart</div>
168
171
  <div id="org-container" class="card"></div>
172
+ <div class="shd">Execution network</div>
173
+ <div id="workforce-execution-network" class="card execution-network"></div>
169
174
  <div class="workforce-bottom">
170
175
  <div><div class="shd">Active missions</div><div class="card" id="mission-list"></div></div>
171
176
  <div><div class="shd">Dispatch</div><div class="card" id="dispatch-panel"></div></div>
@@ -29,7 +29,9 @@ import { mount as mountRuntimeBadges } from './widgets/runtime-badge.js';
29
29
 
30
30
  const $ = id => document.getElementById(id);
31
31
  const esc = s => s==null?'':String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
32
+ const SIDEBAR_COLLAPSED_KEY = 'nexus.sidebarCollapsed';
32
33
  let memoryReloadTimer = null;
34
+ let workforceReloadTimer = null;
33
35
 
34
36
  function _memoryApiUrl() {
35
37
  const params = new URLSearchParams({ includeInactive: 'true', limit: '100' });
@@ -50,6 +52,47 @@ function _refreshMemorySoon() {
50
52
  }, 150);
51
53
  }
52
54
 
55
+ function _refreshWorkforceSoon(forceLoad = false) {
56
+ bustCache('/api/workforce/kanban');
57
+ bustCache('/api/synapse/health');
58
+ bustCache('/api/dashboard/surface/operate');
59
+ if (S.tab === 'workforce') Workforce.render();
60
+ if (!forceLoad || S.tab !== 'workforce') return;
61
+ clearTimeout(workforceReloadTimer);
62
+ workforceReloadTimer = setTimeout(() => {
63
+ workforceReloadTimer = null;
64
+ Workforce.load();
65
+ }, 300);
66
+ }
67
+
68
+ function _applySidebarCollapsed(collapsed) {
69
+ document.body.classList.toggle('sidebar-collapsed', collapsed);
70
+ const btn = $('sidebar-collapse-btn');
71
+ if (!btn) return;
72
+ const expanded = !collapsed;
73
+ btn.setAttribute('aria-expanded', expanded ? 'true' : 'false');
74
+ btn.setAttribute('aria-label', expanded ? 'Collapse navigation' : 'Expand navigation');
75
+ btn.title = expanded ? 'Collapse navigation' : 'Expand navigation';
76
+ const icon = btn.querySelector('.sidebar-collapse-icon');
77
+ if (icon) icon.textContent = expanded ? '<' : '>';
78
+ }
79
+
80
+ function _initSidebarCollapse() {
81
+ document.querySelectorAll('#tab-nav .nav-item').forEach(item => {
82
+ if (!item.title) item.title = item.textContent.trim();
83
+ if (!item.dataset.short) item.dataset.short = (item.textContent.trim()[0] || '?').toUpperCase();
84
+ });
85
+ let stored = null;
86
+ try { stored = localStorage.getItem(SIDEBAR_COLLAPSED_KEY); } catch { stored = null; }
87
+ const defaultCollapsed = stored == null && window.matchMedia?.('(max-width: 760px)').matches;
88
+ _applySidebarCollapsed(stored === '1' || defaultCollapsed);
89
+ $('sidebar-collapse-btn')?.addEventListener('click', () => {
90
+ const collapsed = !document.body.classList.contains('sidebar-collapsed');
91
+ try { localStorage.setItem(SIDEBAR_COLLAPSED_KEY, collapsed ? '1' : '0'); } catch { /* ignore private-mode storage failures */ }
92
+ _applySidebarCollapsed(collapsed);
93
+ });
94
+ }
95
+
53
96
  /* ─────────────────── Router ─────────────────── */
54
97
  navRegister('board', Board.load);
55
98
  navRegister('runtime', Runtime.load);
@@ -114,6 +157,12 @@ setOnEvent(evt => {
114
157
  // Route push-mode dispatch events regardless of active tab
115
158
  if (String(evt.type||'').startsWith('dispatch.')) {
116
159
  Workforce.handleDispatchEvent(evt);
160
+ const payload = evt.payload ?? evt.data ?? {};
161
+ const kind = String(payload.kind || '');
162
+ const shouldLoad = ['dispatch.started','dispatch.complete','dispatch.failed','dispatch.cancelled'].includes(String(evt.type||''))
163
+ || ['done','error','file-change'].includes(kind);
164
+ _refreshWorkforceSoon(shouldLoad);
165
+ if (tab === 'board') Board.render();
117
166
  }
118
167
 
119
168
  // Orchestration pipeline events — keep board fresh as decomposition / completion fires.
@@ -282,6 +331,9 @@ function _attachHandlers() {
282
331
  // Command bar
283
332
  initCmdBar();
284
333
 
334
+ // Collapsible navigation rail
335
+ _initSidebarCollapse();
336
+
285
337
  // Memory view init
286
338
  Memory.init();
287
339
 
@@ -31,6 +31,8 @@ export const S = {
31
31
  // Board / cockpit
32
32
  tokensSummary: null,
33
33
  tokensLifetime: null,
34
+ tokenEconomics: null,
35
+ providerEconomics: null,
34
36
  operateSurface: null,
35
37
  synapseHealth: [],
36
38
  synapseHealthRaw: null,
@@ -68,6 +70,9 @@ export const S = {
68
70
  selectedMemoryIds: [],
69
71
  memorySurface: null,
70
72
  topology: null,
73
+ memoryLoading: false,
74
+ memoryError: null,
75
+ memoryLoadedAt:null,
71
76
 
72
77
  // Knowledge
73
78
  ragCollections: [],
@@ -43,12 +43,15 @@
43
43
  }
44
44
  .model-router-lanes {
45
45
  display: grid;
46
- grid-template-columns: repeat(3, minmax(0, 1fr));
46
+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
47
47
  gap: 10px;
48
48
  }
49
49
  .model-lane {
50
- min-height: 92px;
50
+ min-height: 124px;
51
51
  padding: 12px;
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 3px;
52
55
  text-align: left;
53
56
  color: var(--text-muted);
54
57
  background: rgba(0,0,0,0.2);
@@ -69,10 +72,17 @@
69
72
  color: var(--text-main);
70
73
  font-family: var(--font-mono);
71
74
  font-size: 1rem;
75
+ overflow-wrap: anywhere;
72
76
  }
73
77
  .model-lane small {
78
+ display: block;
74
79
  color: var(--text-dim);
75
80
  font-family: var(--font-mono);
81
+ line-height: 1.35;
82
+ overflow-wrap: anywhere;
83
+ }
84
+ .model-lane-purpose {
85
+ color: var(--text-muted);
76
86
  }
77
87
  .model-router-reason {
78
88
  margin-top: 10px;
@@ -81,10 +91,154 @@
81
91
  line-height: 1.45;
82
92
  }
83
93
 
94
+ .token-economics-panel {
95
+ margin: 0 0 16px;
96
+ padding: 14px;
97
+ background: var(--bg-elevated);
98
+ }
99
+ .token-econ-head {
100
+ display: flex;
101
+ justify-content: space-between;
102
+ align-items: flex-start;
103
+ gap: 12px;
104
+ margin-bottom: 12px;
105
+ }
106
+ .token-econ-head span,
107
+ .token-econ-subhead {
108
+ display: block;
109
+ color: var(--text-dim);
110
+ font-family: var(--font-mono);
111
+ font-size: 0.68rem;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.06em;
114
+ }
115
+ .token-econ-head strong {
116
+ display: block;
117
+ margin-top: 3px;
118
+ color: var(--text-main);
119
+ font: var(--title);
120
+ }
121
+ .token-econ-proof {
122
+ color: var(--text-muted);
123
+ font-family: var(--font-mono);
124
+ font-size: 0.72rem;
125
+ text-align: right;
126
+ }
127
+ .token-econ-metrics {
128
+ display: grid;
129
+ grid-template-columns: repeat(4, minmax(0, 1fr));
130
+ gap: 1px;
131
+ margin-bottom: 12px;
132
+ background: var(--border);
133
+ border: 1px solid var(--border);
134
+ border-radius: var(--radius);
135
+ overflow: hidden;
136
+ }
137
+ .token-econ-metrics div {
138
+ padding: 10px 12px;
139
+ background: rgba(0,0,0,0.22);
140
+ }
141
+ .token-econ-metrics span {
142
+ display: block;
143
+ color: var(--text-dim);
144
+ font-family: var(--font-mono);
145
+ font-size: 0.68rem;
146
+ text-transform: uppercase;
147
+ letter-spacing: 0.05em;
148
+ }
149
+ .token-econ-metrics strong {
150
+ display: block;
151
+ margin-top: 4px;
152
+ color: var(--text-main);
153
+ font-family: var(--font-mono);
154
+ font-size: 1rem;
155
+ }
156
+ .token-econ-grid {
157
+ display: grid;
158
+ grid-template-columns: repeat(3, minmax(0, 1fr));
159
+ gap: 10px;
160
+ }
161
+ .token-econ-subhead { margin-bottom: 6px; }
162
+ .token-econ-rows { display: flex; flex-direction: column; gap: 6px; }
163
+ .econ-row {
164
+ display: grid;
165
+ grid-template-columns: minmax(0, 1fr) auto auto;
166
+ gap: 8px;
167
+ align-items: center;
168
+ min-height: 54px;
169
+ padding: 9px 10px;
170
+ border: 1px solid var(--border);
171
+ border-left: 2px solid rgba(0,255,136,0.32);
172
+ border-radius: var(--radius);
173
+ background: rgba(0,0,0,0.18);
174
+ }
175
+ .econ-row.live { border-left-color: rgba(0,212,255,0.36); }
176
+ .econ-row.verify.pass { border-left-color: rgba(0,255,136,0.46); }
177
+ .econ-row.verify.fail { border-left-color: rgba(255,95,87,0.46); }
178
+ .econ-row strong {
179
+ display: block;
180
+ color: var(--text-main);
181
+ font-family: var(--font-mono);
182
+ font-size: 0.78rem;
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ }
187
+ .econ-row span,
188
+ .econ-row > div:nth-child(2),
189
+ .econ-row > div:nth-child(3) {
190
+ color: var(--text-dim);
191
+ font-family: var(--font-mono);
192
+ font-size: 0.72rem;
193
+ }
194
+ .econ-row > div:nth-child(2),
195
+ .econ-row > div:nth-child(3) {
196
+ color: var(--text-muted);
197
+ white-space: nowrap;
198
+ }
199
+ .econ-empty {
200
+ min-height: 54px;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ padding: 10px;
205
+ color: var(--text-dim);
206
+ font-family: var(--font-mono);
207
+ font-size: 0.74rem;
208
+ border: 1px dashed var(--border);
209
+ border-radius: var(--radius);
210
+ }
211
+
84
212
  @media (max-width: 900px) {
85
213
  #hero-stats,
86
- .model-router-lanes {
214
+ .model-router-lanes,
215
+ .token-econ-metrics,
216
+ .token-econ-grid {
217
+ grid-template-columns: 1fr;
218
+ }
219
+ .token-econ-head { flex-direction: column; }
220
+ .token-econ-proof { text-align: left; }
221
+ .token-economics-panel,
222
+ .token-econ-grid > div,
223
+ .token-econ-rows {
224
+ min-width: 0;
225
+ overflow-x: hidden;
226
+ }
227
+ .econ-row {
87
228
  grid-template-columns: 1fr;
229
+ align-items: flex-start;
230
+ box-sizing: border-box;
231
+ min-width: 0;
232
+ width: 100%;
233
+ }
234
+ .econ-row strong,
235
+ .econ-row span {
236
+ white-space: normal;
237
+ overflow-wrap: anywhere;
238
+ }
239
+ .econ-row > div:nth-child(2),
240
+ .econ-row > div:nth-child(3) {
241
+ white-space: normal;
88
242
  }
89
243
  }
90
244
 
@@ -132,7 +286,21 @@
132
286
  .kcard.dragging { opacity: 0.35; transform: scale(0.96); cursor: grabbing; pointer-events: none; }
133
287
 
134
288
  /* ── Agents Live Strip ── */
135
- #agents-live-strip { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
289
+ #agents-live-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
290
+ .agent-live-summary {
291
+ display: inline-flex;
292
+ align-items: center;
293
+ gap: 5px;
294
+ padding: 3px 8px;
295
+ border: 1px solid rgba(0,255,136,0.18);
296
+ border-radius: 20px;
297
+ background: rgba(0,255,136,0.05);
298
+ color: var(--text-dim);
299
+ font-family: var(--font-mono);
300
+ font-size: 0.7rem;
301
+ }
302
+ .agent-live-summary strong { color: var(--accent); }
303
+ .agent-live-summary .agent-live-warn { color: var(--warning); }
136
304
  .agent-live-pill {
137
305
  display: inline-flex; align-items: center; gap: 5px;
138
306
  font-family: var(--font-mono); font-size: 0.78rem;
@@ -143,6 +311,8 @@
143
311
  .agent-live-pill .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-dim); flex-shrink: 0; }
144
312
  .agent-live-pill.active .dot { background: var(--accent); box-shadow: 0 0 4px rgba(0,255,136,0.6); }
145
313
  .agent-live-pill.active { border-color: rgba(0,255,136,0.25); }
314
+ .agent-live-pill.idle .dot { background: var(--text-dim); opacity: 0.8; }
315
+ .agent-live-pill.idle { border-color: rgba(255,255,255,0.08); opacity: 0.78; }
146
316
  .agent-live-pill.blocked .dot { background: var(--warning); }
147
317
  .agent-live-pill.blocked { border-color: rgba(255,95,87,0.25); }
148
318
  .agent-inline-badge {