@theihtisham/agent-shadow-brain 6.0.2 → 6.2.0
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/CHANGELOG.md +372 -0
- package/README.md +330 -358
- package/dist/brain/auto-update.js +1 -1
- package/dist/brain/brain-bus.d.ts +40 -0
- package/dist/brain/brain-bus.d.ts.map +1 -0
- package/dist/brain/brain-bus.js +94 -0
- package/dist/brain/brain-bus.js.map +1 -0
- package/dist/brain/brain-portability.js +3 -3
- package/dist/brain/brain-portability.js.map +1 -1
- package/dist/brain/collision-detective.d.ts.map +1 -1
- package/dist/brain/collision-detective.js +16 -0
- package/dist/brain/collision-detective.js.map +1 -1
- package/dist/brain/context-continuity.d.ts +78 -0
- package/dist/brain/context-continuity.d.ts.map +1 -0
- package/dist/brain/context-continuity.js +341 -0
- package/dist/brain/context-continuity.js.map +1 -0
- package/dist/brain/dream-engine.d.ts.map +1 -1
- package/dist/brain/dream-engine.js +12 -0
- package/dist/brain/dream-engine.js.map +1 -1
- package/dist/brain/forgetting-curve.d.ts.map +1 -1
- package/dist/brain/forgetting-curve.js +19 -0
- package/dist/brain/forgetting-curve.js.map +1 -1
- package/dist/brain/global-brain.d.ts +4 -0
- package/dist/brain/global-brain.d.ts.map +1 -1
- package/dist/brain/global-brain.js +61 -9
- package/dist/brain/global-brain.js.map +1 -1
- package/dist/brain/live-brain.d.ts +119 -0
- package/dist/brain/live-brain.d.ts.map +1 -0
- package/dist/brain/live-brain.js +405 -0
- package/dist/brain/live-brain.js.map +1 -0
- package/dist/brain/mcp-server.d.ts +4 -0
- package/dist/brain/mcp-server.d.ts.map +1 -1
- package/dist/brain/mcp-server.js +81 -5
- package/dist/brain/mcp-server.js.map +1 -1
- package/dist/brain/memory-graph.d.ts +51 -0
- package/dist/brain/memory-graph.d.ts.map +1 -0
- package/dist/brain/memory-graph.js +267 -0
- package/dist/brain/memory-graph.js.map +1 -0
- package/dist/brain/memory-seeder.d.ts +78 -0
- package/dist/brain/memory-seeder.d.ts.map +1 -0
- package/dist/brain/memory-seeder.js +349 -0
- package/dist/brain/memory-seeder.js.map +1 -0
- package/dist/brain/orchestrator.js +1 -1
- package/dist/brain/subagent-bridge.d.ts.map +1 -1
- package/dist/brain/subagent-bridge.js +12 -0
- package/dist/brain/subagent-bridge.js.map +1 -1
- package/dist/brain/token-economy.d.ts +2 -0
- package/dist/brain/token-economy.d.ts.map +1 -1
- package/dist/brain/token-economy.js +80 -24
- package/dist/brain/token-economy.js.map +1 -1
- package/dist/cli.js +240 -31
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/server.d.ts +24 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +524 -7
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
- package/dist/dashboard/v6-dashboard-html.js +2070 -213
- package/dist/dashboard/v6-dashboard-html.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +17 -12
- package/dist/types.d.ts.map +1 -1
- package/docs/launch/README.md +61 -0
- package/docs/launch/RELEASE_CHECKLIST_v6.0.3.md +102 -0
- package/docs/launch/RELEASE_NOTES_v6.0.3.md +111 -0
- package/docs/launch/RELEASE_NOTES_v6.1.0.md +38 -0
- package/docs/launch/RELEASE_NOTES_v6.2.0.md +54 -0
- package/package.json +18 -4
- package/tools/export-resume-pack.mjs +56 -0
- package/tools/record-global-memory.mjs +72 -0
|
@@ -10,7 +10,26 @@ export declare class DashboardServer {
|
|
|
10
10
|
private clients;
|
|
11
11
|
private orchestrator;
|
|
12
12
|
private options;
|
|
13
|
+
private brainBusUnsub;
|
|
14
|
+
private brainWatcher;
|
|
15
|
+
private brainDbPath;
|
|
16
|
+
private seenEntryIds;
|
|
17
|
+
private brainFileDebounce;
|
|
13
18
|
constructor(orchestrator: Orchestrator, options?: DashboardOptions);
|
|
19
|
+
/** Forward every realtime brain-bus event straight to all WebSocket clients. */
|
|
20
|
+
private subscribeToBrainBus;
|
|
21
|
+
/**
|
|
22
|
+
* Cross-process realtime: an agent in another terminal writes to the shared
|
|
23
|
+
* ~/.shadow-brain/global.json. That write lands in a different process's
|
|
24
|
+
* GlobalBrain instance, so the in-process bus never sees it. We watch the
|
|
25
|
+
* file and republish any entry id we have not already broadcast. The bus
|
|
26
|
+
* dedups by composite id, so our own in-process memory-writes are ignored.
|
|
27
|
+
*/
|
|
28
|
+
private startBrainFileWatcher;
|
|
29
|
+
private primeSeenEntryIds;
|
|
30
|
+
/** Re-read the on-disk brain and publish any entries we haven't seen. */
|
|
31
|
+
private scanBrainFile;
|
|
32
|
+
private toEpoch;
|
|
14
33
|
private handleRequest;
|
|
15
34
|
private handleV6Request;
|
|
16
35
|
/** Aggregate recent activity from all v6 modules into a single unified stream. */
|
|
@@ -18,9 +37,14 @@ export declare class DashboardServer {
|
|
|
18
37
|
private readJsonBody;
|
|
19
38
|
private getModulesData;
|
|
20
39
|
private getMemoryData;
|
|
40
|
+
private getRuntimeContext;
|
|
21
41
|
private getToolsData;
|
|
22
42
|
private getTimelineData;
|
|
23
43
|
private getFirewallDemo;
|
|
44
|
+
private getDefaultWorkflowProjectDir;
|
|
45
|
+
private pathExists;
|
|
46
|
+
private getWorkflowAgentStatus;
|
|
47
|
+
private getWorkflowWarnings;
|
|
24
48
|
private setupWebSocket;
|
|
25
49
|
private subscribeToOrchestrator;
|
|
26
50
|
private send;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAQxD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAmYD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,YAAY,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB;IAmBtE,gFAAgF;IAChF,OAAO,CAAC,mBAAmB;IAM3B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,iBAAiB;IAQzB,yEAAyE;IACzE,OAAO,CAAC,aAAa;IAuDrB,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,aAAa;YAmDP,eAAe;IAuvB7B,kFAAkF;YACpE,kBAAkB;YAkFlB,YAAY;IAY1B,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,iBAAiB;IAkDzB,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,4BAA4B;YAQtB,UAAU;YASV,sBAAsB;IAuCpC,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,uBAAuB;IA2C/B,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,SAAS;IASX,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAUxB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
// v5.0.1 — Infinite Intelligence Edition
|
|
3
3
|
// 8-panel layout: Health, Insights, AI Tools, Memory Tiers, Modules, Fixes, Controls, Activity
|
|
4
4
|
import * as http from 'http';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import chokidar from 'chokidar';
|
|
5
7
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
6
8
|
import { AgentFirewall } from '../brain/agent-firewall.js';
|
|
9
|
+
import { analyzeMemoryQuality, generateContextResumePack } from '../brain/context-continuity.js';
|
|
7
10
|
import { getGlobalBrain, GlobalBrain } from '../brain/global-brain.js';
|
|
11
|
+
import { getBrainBus } from '../brain/brain-bus.js';
|
|
8
12
|
import { V6_DASHBOARD_HTML } from './v6-dashboard-html.js';
|
|
9
13
|
// ── Embedded HTML Client ──────────────────────────────────────────────────────
|
|
10
14
|
const HTML_CLIENT = `<!DOCTYPE html>
|
|
@@ -212,11 +216,6 @@ function ts(){return new Date().toLocaleTimeString('en-US',{hour12:false})}
|
|
|
212
216
|
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
|
213
217
|
function sc(v){return v>=85?'var(--green)':v>=70?'var(--yellow)':v>=50?'var(--orange)':'var(--red)'}
|
|
214
218
|
|
|
215
|
-
// Log to insights panel feedback via activity
|
|
216
|
-
function logActivity(msg,type){
|
|
217
|
-
// We no longer have a dedicated log panel, so we use a minimal approach
|
|
218
|
-
}
|
|
219
|
-
|
|
220
219
|
function renderHealth(s){
|
|
221
220
|
if(!s)return;
|
|
222
221
|
const c=sc(s.overall);
|
|
@@ -398,6 +397,11 @@ connect();
|
|
|
398
397
|
export class DashboardServer {
|
|
399
398
|
constructor(orchestrator, options = {}) {
|
|
400
399
|
this.clients = new Set();
|
|
400
|
+
this.brainBusUnsub = null;
|
|
401
|
+
this.brainWatcher = null;
|
|
402
|
+
this.brainDbPath = '';
|
|
403
|
+
this.seenEntryIds = new Set();
|
|
404
|
+
this.brainFileDebounce = null;
|
|
401
405
|
this.orchestrator = orchestrator;
|
|
402
406
|
this.options = {
|
|
403
407
|
port: options.port ?? 7341,
|
|
@@ -410,6 +414,120 @@ export class DashboardServer {
|
|
|
410
414
|
this.wss = new WebSocketServer({ server: this.server });
|
|
411
415
|
this.setupWebSocket();
|
|
412
416
|
this.subscribeToOrchestrator();
|
|
417
|
+
this.subscribeToBrainBus();
|
|
418
|
+
this.startBrainFileWatcher();
|
|
419
|
+
}
|
|
420
|
+
/** Forward every realtime brain-bus event straight to all WebSocket clients. */
|
|
421
|
+
subscribeToBrainBus() {
|
|
422
|
+
this.brainBusUnsub = getBrainBus().subscribe((event) => {
|
|
423
|
+
this.broadcast({ type: 'activity', event });
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Cross-process realtime: an agent in another terminal writes to the shared
|
|
428
|
+
* ~/.shadow-brain/global.json. That write lands in a different process's
|
|
429
|
+
* GlobalBrain instance, so the in-process bus never sees it. We watch the
|
|
430
|
+
* file and republish any entry id we have not already broadcast. The bus
|
|
431
|
+
* dedups by composite id, so our own in-process memory-writes are ignored.
|
|
432
|
+
*/
|
|
433
|
+
startBrainFileWatcher() {
|
|
434
|
+
try {
|
|
435
|
+
const brain = getGlobalBrain();
|
|
436
|
+
this.brainDbPath = brain.dbPath;
|
|
437
|
+
// Seed the seen-set with current contents so we never replay history.
|
|
438
|
+
this.primeSeenEntryIds();
|
|
439
|
+
this.brainWatcher = chokidar.watch(this.brainDbPath, {
|
|
440
|
+
persistent: true,
|
|
441
|
+
ignoreInitial: true,
|
|
442
|
+
awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
|
|
443
|
+
});
|
|
444
|
+
const onChange = () => {
|
|
445
|
+
if (this.brainFileDebounce)
|
|
446
|
+
clearTimeout(this.brainFileDebounce);
|
|
447
|
+
this.brainFileDebounce = setTimeout(() => this.scanBrainFile(), 400);
|
|
448
|
+
};
|
|
449
|
+
this.brainWatcher.on('add', onChange).on('change', onChange);
|
|
450
|
+
}
|
|
451
|
+
catch { /* watcher is best-effort; in-process events still flow */ }
|
|
452
|
+
}
|
|
453
|
+
primeSeenEntryIds() {
|
|
454
|
+
try {
|
|
455
|
+
const raw = fs.readFileSync(this.brainDbPath, 'utf-8');
|
|
456
|
+
const parsed = JSON.parse(raw);
|
|
457
|
+
for (const e of parsed.entries ?? [])
|
|
458
|
+
this.seenEntryIds.add(e.id);
|
|
459
|
+
}
|
|
460
|
+
catch { /* file may not exist yet */ }
|
|
461
|
+
}
|
|
462
|
+
/** Re-read the on-disk brain and publish any entries we haven't seen. */
|
|
463
|
+
scanBrainFile() {
|
|
464
|
+
let parsed;
|
|
465
|
+
let raw = '';
|
|
466
|
+
try {
|
|
467
|
+
raw = fs.readFileSync(this.brainDbPath, 'utf-8');
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Very large stores: skip the per-entry diff, emit one generic pulse.
|
|
473
|
+
if (raw.length > 8000000) {
|
|
474
|
+
getBrainBus().publish({
|
|
475
|
+
id: `brain-updated:${Date.now()}`,
|
|
476
|
+
type: 'brain-updated',
|
|
477
|
+
source: 'global-brain',
|
|
478
|
+
detail: 'brain store updated (large store — details suppressed)',
|
|
479
|
+
});
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
parsed = JSON.parse(raw);
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const entries = parsed.entries ?? [];
|
|
489
|
+
let fresh = 0;
|
|
490
|
+
for (const e of entries) {
|
|
491
|
+
const id = String(e.id ?? '');
|
|
492
|
+
if (!id || this.seenEntryIds.has(id))
|
|
493
|
+
continue;
|
|
494
|
+
this.seenEntryIds.add(id);
|
|
495
|
+
fresh++;
|
|
496
|
+
// Cap how many we replay in a single burst to avoid flooding the UI.
|
|
497
|
+
if (fresh > 100)
|
|
498
|
+
continue;
|
|
499
|
+
getBrainBus().publish({
|
|
500
|
+
id: `memory-write:${id}`,
|
|
501
|
+
ts: this.toEpoch(e.createdAt),
|
|
502
|
+
type: 'memory-write',
|
|
503
|
+
source: String(e.agentTool ?? 'agent'),
|
|
504
|
+
detail: `[${String(e.category ?? '')}] ${String(e.content ?? '').slice(0, 220)}`,
|
|
505
|
+
meta: {
|
|
506
|
+
category: e.category,
|
|
507
|
+
importance: e.importance,
|
|
508
|
+
projectName: e.projectName,
|
|
509
|
+
crossProcess: true,
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (fresh > 100) {
|
|
514
|
+
getBrainBus().publish({
|
|
515
|
+
id: `brain-updated:${Date.now()}`,
|
|
516
|
+
type: 'brain-updated',
|
|
517
|
+
source: 'global-brain',
|
|
518
|
+
detail: `${fresh} memories written (showing first 100)`,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
toEpoch(v) {
|
|
523
|
+
if (typeof v === 'number')
|
|
524
|
+
return v;
|
|
525
|
+
if (typeof v === 'string') {
|
|
526
|
+
const t = Date.parse(v);
|
|
527
|
+
if (!Number.isNaN(t))
|
|
528
|
+
return t;
|
|
529
|
+
}
|
|
530
|
+
return Date.now();
|
|
413
531
|
}
|
|
414
532
|
handleRequest(req, res) {
|
|
415
533
|
const url = req.url || '/';
|
|
@@ -508,9 +626,11 @@ export class DashboardServer {
|
|
|
508
626
|
const brain = getGlobalBrain();
|
|
509
627
|
await brain.init();
|
|
510
628
|
const tokenReport = await tokens.report();
|
|
629
|
+
const runtime = this.getRuntimeContext();
|
|
511
630
|
return send({
|
|
512
|
-
version: '6.0.
|
|
631
|
+
version: '6.0.3',
|
|
513
632
|
localFirst: true,
|
|
633
|
+
runtime,
|
|
514
634
|
totalAgentsConnected: brain.getStats().totalAgents,
|
|
515
635
|
totalMemoriesStored: brain.getStats().totalEntries,
|
|
516
636
|
modules: {
|
|
@@ -526,6 +646,171 @@ export class DashboardServer {
|
|
|
526
646
|
generatedAt: new Date(),
|
|
527
647
|
});
|
|
528
648
|
}
|
|
649
|
+
case '/api/v6/ww2br-ops': {
|
|
650
|
+
const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
|
|
651
|
+
const brain = getGlobalBrain();
|
|
652
|
+
await brain.init();
|
|
653
|
+
const stats = brain.getStats();
|
|
654
|
+
const projectId = GlobalBrain.projectIdFor(projectDir);
|
|
655
|
+
let timeline = brain.timeline({ projectId, limit: 18 });
|
|
656
|
+
let scoped = true;
|
|
657
|
+
if (!timeline.length) {
|
|
658
|
+
scoped = false;
|
|
659
|
+
timeline = brain.timeline({ limit: 18 });
|
|
660
|
+
}
|
|
661
|
+
const memoryEntries = brain.recall({
|
|
662
|
+
projectId: scoped ? projectId : undefined,
|
|
663
|
+
minImportance: 0,
|
|
664
|
+
limit: 500,
|
|
665
|
+
});
|
|
666
|
+
const quality = analyzeMemoryQuality(memoryEntries);
|
|
667
|
+
const resumePack = generateContextResumePack({
|
|
668
|
+
projectDir,
|
|
669
|
+
projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
|
|
670
|
+
projectId,
|
|
671
|
+
entries: memoryEntries,
|
|
672
|
+
maxMemories: 12,
|
|
673
|
+
});
|
|
674
|
+
const agents = await this.getWorkflowAgentStatus(projectDir);
|
|
675
|
+
const categories = {};
|
|
676
|
+
for (const event of timeline)
|
|
677
|
+
categories[event.category] = (categories[event.category] ?? 0) + 1;
|
|
678
|
+
return send({
|
|
679
|
+
projectDir,
|
|
680
|
+
projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
|
|
681
|
+
projectExists: await this.pathExists(projectDir),
|
|
682
|
+
projectScoped: scoped,
|
|
683
|
+
stats,
|
|
684
|
+
agents,
|
|
685
|
+
warnings: this.getWorkflowWarnings(projectDir, agents, timeline, scoped),
|
|
686
|
+
recent: timeline,
|
|
687
|
+
categories,
|
|
688
|
+
quality,
|
|
689
|
+
resumePack: {
|
|
690
|
+
generatedAt: resumePack.generatedAt,
|
|
691
|
+
stats: resumePack.stats,
|
|
692
|
+
warnings: resumePack.warnings,
|
|
693
|
+
suggestedRecallQueries: resumePack.suggestedRecallQueries,
|
|
694
|
+
blockers: resumePack.blockers.slice(0, 5),
|
|
695
|
+
topMemories: resumePack.topMemories.slice(0, 5),
|
|
696
|
+
},
|
|
697
|
+
generatedAt: new Date(),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
case '/api/v6/ww2br-recall': {
|
|
701
|
+
const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
|
|
702
|
+
const query = String(qs.get('q') || qs.get('query') || (body.query ?? '')).trim();
|
|
703
|
+
const brain = getGlobalBrain();
|
|
704
|
+
await brain.init();
|
|
705
|
+
const projectId = GlobalBrain.projectIdFor(projectDir);
|
|
706
|
+
const common = {
|
|
707
|
+
agentTool: qs.get('agent') || body.agent || undefined,
|
|
708
|
+
category: qs.get('category') || body.category || undefined,
|
|
709
|
+
keywords: query ? query.split(/\s+/).filter(Boolean) : undefined,
|
|
710
|
+
minImportance: Number(qs.get('minImportance') ?? body.minImportance ?? 0),
|
|
711
|
+
limit: Number(qs.get('limit') ?? body.limit ?? 12),
|
|
712
|
+
};
|
|
713
|
+
const projectResults = brain.recall({ projectId, ...common });
|
|
714
|
+
const results = projectResults.length ? projectResults : brain.recall(common);
|
|
715
|
+
return send({
|
|
716
|
+
projectDir,
|
|
717
|
+
projectScoped: projectResults.length > 0,
|
|
718
|
+
query,
|
|
719
|
+
results,
|
|
720
|
+
generatedAt: new Date(),
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
case '/api/v6/resume-pack': {
|
|
724
|
+
const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
|
|
725
|
+
const format = String(qs.get('format') || body.format || 'json').toLowerCase();
|
|
726
|
+
const limit = Number(qs.get('limit') ?? body.limit ?? 12);
|
|
727
|
+
const brain = getGlobalBrain();
|
|
728
|
+
await brain.init();
|
|
729
|
+
const projectId = GlobalBrain.projectIdFor(projectDir);
|
|
730
|
+
let entries = brain.recall({ projectId, minImportance: 0, limit: 500 });
|
|
731
|
+
let projectScoped = true;
|
|
732
|
+
if (!entries.length) {
|
|
733
|
+
projectScoped = false;
|
|
734
|
+
entries = brain.recall({ minImportance: 0, limit: 500 });
|
|
735
|
+
}
|
|
736
|
+
const pack = generateContextResumePack({
|
|
737
|
+
projectDir,
|
|
738
|
+
projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
|
|
739
|
+
projectId,
|
|
740
|
+
entries,
|
|
741
|
+
maxMemories: Number.isFinite(limit) ? Math.max(1, Math.min(30, limit)) : 12,
|
|
742
|
+
});
|
|
743
|
+
if (format === 'markdown' || format === 'md')
|
|
744
|
+
return send(pack.markdown, 200, 'text/markdown; charset=utf-8');
|
|
745
|
+
return send({ ...pack, projectScoped });
|
|
746
|
+
}
|
|
747
|
+
case '/api/v6/live-brain': {
|
|
748
|
+
const { getLiveBrain } = await import('../brain/live-brain.js');
|
|
749
|
+
const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
|
|
750
|
+
const format = String(qs.get('format') || body.format || 'json').toLowerCase();
|
|
751
|
+
const queriesRaw = String(qs.get('queries') || body.queries || '').trim();
|
|
752
|
+
const queries = queriesRaw ? queriesRaw.split('|').map(q => q.trim()).filter(Boolean) : undefined;
|
|
753
|
+
const report = await getLiveBrain().report({
|
|
754
|
+
projectDir,
|
|
755
|
+
maxMemories: Number(qs.get('limit') ?? body.limit ?? 12),
|
|
756
|
+
queries,
|
|
757
|
+
globalFallback: body.globalFallback !== false && qs.get('globalFallback') !== 'false',
|
|
758
|
+
});
|
|
759
|
+
if (format === 'markdown' || format === 'md')
|
|
760
|
+
return send(report.agentBriefing, 200, 'text/markdown; charset=utf-8');
|
|
761
|
+
return send(report);
|
|
762
|
+
}
|
|
763
|
+
case '/api/v6/seed-drafts': {
|
|
764
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
765
|
+
const limit = Number(qs.get('limit') ?? body.limit ?? 30);
|
|
766
|
+
const seeder = getMemorySeeder();
|
|
767
|
+
await seeder.init();
|
|
768
|
+
return send(seeder.list(limit));
|
|
769
|
+
}
|
|
770
|
+
case '/api/v6/seed-draft': {
|
|
771
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
772
|
+
const seeder = getMemorySeeder();
|
|
773
|
+
return send(await seeder.createDraft({
|
|
774
|
+
topic: body.topic,
|
|
775
|
+
objective: body.objective,
|
|
776
|
+
projectDir: body.projectDir || this.getDefaultWorkflowProjectDir(),
|
|
777
|
+
category: body.category,
|
|
778
|
+
tags: body.tags,
|
|
779
|
+
importance: body.importance,
|
|
780
|
+
researchAgent: body.researchAgent,
|
|
781
|
+
approverAgent: body.approverAgent,
|
|
782
|
+
approvalMode: body.approvalMode,
|
|
783
|
+
sources: body.sources,
|
|
784
|
+
content: body.content,
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
case '/api/v6/seed-approve': {
|
|
788
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
789
|
+
return send(await getMemorySeeder().approve(body.id, body.approvedBy || 'user', body.note));
|
|
790
|
+
}
|
|
791
|
+
case '/api/v6/seed-reject': {
|
|
792
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
793
|
+
return send(await getMemorySeeder().reject(body.id, body.rejectedBy || 'user', body.reason || 'Rejected from dashboard'));
|
|
794
|
+
}
|
|
795
|
+
case '/api/v6/seed-commit': {
|
|
796
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
797
|
+
return send(await getMemorySeeder().seed(body.id, body.seededBy || 'user'));
|
|
798
|
+
}
|
|
799
|
+
case '/api/v6/agent-handoff': {
|
|
800
|
+
const { AgentHandoff } = await import('../brain/agent-handoff.js');
|
|
801
|
+
const projectDir = String(body.projectDir || qs.get('projectDir') || this.getDefaultWorkflowProjectDir());
|
|
802
|
+
const packet = await new AgentHandoff().generate({
|
|
803
|
+
fromAgent: (body.fromAgent || 'user'),
|
|
804
|
+
toAgent: (body.toAgent || 'claude-code'),
|
|
805
|
+
projectDir,
|
|
806
|
+
task: body.task,
|
|
807
|
+
limit: body.limit ?? 12,
|
|
808
|
+
});
|
|
809
|
+
const format = String(body.format || qs.get('format') || 'json').toLowerCase();
|
|
810
|
+
if (format === 'markdown' || format === 'md')
|
|
811
|
+
return send(packet.markdown, 200, 'text/markdown; charset=utf-8');
|
|
812
|
+
return send(packet);
|
|
813
|
+
}
|
|
529
814
|
case '/api/v6/subagent-sliver': {
|
|
530
815
|
const { getSubAgentBridge } = await import('../brain/subagent-bridge.js');
|
|
531
816
|
const b = getSubAgentBridge();
|
|
@@ -620,7 +905,8 @@ export class DashboardServer {
|
|
|
620
905
|
}
|
|
621
906
|
case '/api/v6/tokens-report': {
|
|
622
907
|
const { getTokenEconomy } = await import('../brain/token-economy.js');
|
|
623
|
-
|
|
908
|
+
const report = await getTokenEconomy().report();
|
|
909
|
+
return send({ ...report, runtime: this.getRuntimeContext() });
|
|
624
910
|
}
|
|
625
911
|
case '/api/v6/forget-consolidate': {
|
|
626
912
|
const { getForgettingCurve } = await import('../brain/forgetting-curve.js');
|
|
@@ -968,6 +1254,75 @@ export class DashboardServer {
|
|
|
968
1254
|
return send({});
|
|
969
1255
|
}
|
|
970
1256
|
}
|
|
1257
|
+
case '/api/v6/memory-intelligence': {
|
|
1258
|
+
const brain = getGlobalBrain();
|
|
1259
|
+
await brain.init();
|
|
1260
|
+
const limit = Number(qs.get('limit') ?? body.limit ?? 500);
|
|
1261
|
+
const events = brain.timeline({ limit: Number.isFinite(limit) ? Math.max(50, Math.min(2000, limit)) : 500 });
|
|
1262
|
+
const now = Date.now();
|
|
1263
|
+
const byCategory = {};
|
|
1264
|
+
const byAgent = {};
|
|
1265
|
+
const topTags = {};
|
|
1266
|
+
let sourceBacked = 0;
|
|
1267
|
+
let recent24h = 0;
|
|
1268
|
+
let recent7d = 0;
|
|
1269
|
+
let stale30d = 0;
|
|
1270
|
+
let highImportance = 0;
|
|
1271
|
+
for (const event of events) {
|
|
1272
|
+
const createdAt = new Date(event.createdAt).getTime();
|
|
1273
|
+
const ageMs = now - createdAt;
|
|
1274
|
+
byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
|
|
1275
|
+
byAgent[event.agentTool] = (byAgent[event.agentTool] ?? 0) + 1;
|
|
1276
|
+
if (event.importance >= 0.75)
|
|
1277
|
+
highImportance++;
|
|
1278
|
+
if (ageMs <= 24 * 60 * 60 * 1000)
|
|
1279
|
+
recent24h++;
|
|
1280
|
+
if (ageMs <= 7 * 24 * 60 * 60 * 1000)
|
|
1281
|
+
recent7d++;
|
|
1282
|
+
if (ageMs > 30 * 24 * 60 * 60 * 1000)
|
|
1283
|
+
stale30d++;
|
|
1284
|
+
const sources = Array.isArray(event.metadata?.sources) ? event.metadata.sources : [];
|
|
1285
|
+
if (sources.length > 0)
|
|
1286
|
+
sourceBacked++;
|
|
1287
|
+
const tags = Array.isArray(event.metadata?.tags) ? event.metadata.tags : [];
|
|
1288
|
+
for (const tag of tags) {
|
|
1289
|
+
const key = String(tag).trim();
|
|
1290
|
+
if (key)
|
|
1291
|
+
topTags[key] = (topTags[key] ?? 0) + 1;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const sortRecord = (record) => Object.entries(record)
|
|
1295
|
+
.sort((a, b) => b[1] - a[1])
|
|
1296
|
+
.slice(0, 12)
|
|
1297
|
+
.map(([name, count]) => ({ name, count }));
|
|
1298
|
+
let seedDrafts = [];
|
|
1299
|
+
try {
|
|
1300
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
1301
|
+
const seeder = getMemorySeeder();
|
|
1302
|
+
await seeder.init();
|
|
1303
|
+
seedDrafts = seeder.list(100);
|
|
1304
|
+
}
|
|
1305
|
+
catch {
|
|
1306
|
+
seedDrafts = [];
|
|
1307
|
+
}
|
|
1308
|
+
return send({
|
|
1309
|
+
totalEntries: brain.getStats().totalEntries,
|
|
1310
|
+
sampledEntries: events.length,
|
|
1311
|
+
sourceBacked,
|
|
1312
|
+
sourceCoverage: events.length ? sourceBacked / events.length : 0,
|
|
1313
|
+
recent24h,
|
|
1314
|
+
recent7d,
|
|
1315
|
+
stale30d,
|
|
1316
|
+
highImportance,
|
|
1317
|
+
byCategory: sortRecord(byCategory),
|
|
1318
|
+
byAgent: sortRecord(byAgent),
|
|
1319
|
+
topTags: sortRecord(topTags),
|
|
1320
|
+
openSeedDrafts: seedDrafts.filter(d => d.status === 'draft' || d.status === 'approved').length,
|
|
1321
|
+
pendingApprovals: seedDrafts.filter(d => d.status === 'draft').length,
|
|
1322
|
+
recent: events.slice(0, 12),
|
|
1323
|
+
generatedAt: new Date(),
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
971
1326
|
case '/api/v6/memory-browser': {
|
|
972
1327
|
const brain = getGlobalBrain();
|
|
973
1328
|
await brain.init();
|
|
@@ -990,6 +1345,21 @@ export class DashboardServer {
|
|
|
990
1345
|
}));
|
|
991
1346
|
return send(results);
|
|
992
1347
|
}
|
|
1348
|
+
case '/api/v6/memory-graph': {
|
|
1349
|
+
const { getMemoryGraph } = await import('../brain/memory-graph.js');
|
|
1350
|
+
const projectDirRaw = String(qs.get('projectDir') || body.projectDir || '').trim();
|
|
1351
|
+
const limit = Number(qs.get('limit') ?? body.limit ?? 400);
|
|
1352
|
+
const maxNodes = Number(qs.get('maxNodes') ?? body.maxNodes ?? 180);
|
|
1353
|
+
const minImportance = Number(qs.get('minImportance') ?? body.minImportance ?? 0);
|
|
1354
|
+
const projectId = projectDirRaw ? GlobalBrain.projectIdFor(projectDirRaw) : undefined;
|
|
1355
|
+
const snapshot = await getMemoryGraph().build({
|
|
1356
|
+
projectId,
|
|
1357
|
+
limit: Number.isFinite(limit) ? limit : 400,
|
|
1358
|
+
maxNodes: Number.isFinite(maxNodes) ? maxNodes : 180,
|
|
1359
|
+
minImportance: Number.isFinite(minImportance) ? minImportance : 0,
|
|
1360
|
+
});
|
|
1361
|
+
return send(snapshot);
|
|
1362
|
+
}
|
|
993
1363
|
case '/api/v6/topology': {
|
|
994
1364
|
// Assemble a real-time topology snapshot: agents + sub-agents + recent signal events
|
|
995
1365
|
const projectDir = body.projectDir || process.cwd();
|
|
@@ -1082,6 +1452,20 @@ export class DashboardServer {
|
|
|
1082
1452
|
}
|
|
1083
1453
|
}
|
|
1084
1454
|
catch { /* empty */ }
|
|
1455
|
+
try {
|
|
1456
|
+
const { getMemorySeeder } = await import('../brain/memory-seeder.js');
|
|
1457
|
+
const seeder = getMemorySeeder();
|
|
1458
|
+
await seeder.init();
|
|
1459
|
+
for (const draft of seeder.list(30)) {
|
|
1460
|
+
events.push({
|
|
1461
|
+
timestamp: new Date(draft.updatedAt || draft.createdAt),
|
|
1462
|
+
source: `seed:${draft.researchAgent}`,
|
|
1463
|
+
type: draft.status === 'seeded' ? 'memory-seeded' : 'seed-draft',
|
|
1464
|
+
detail: `[${draft.status}] ${draft.topic} | approval: ${draft.approvalMode}`,
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
catch { /* empty */ }
|
|
1085
1469
|
try {
|
|
1086
1470
|
const { getSubAgentBridge } = await import('../brain/subagent-bridge.js');
|
|
1087
1471
|
const bridge = getSubAgentBridge();
|
|
@@ -1212,6 +1596,48 @@ export class DashboardServer {
|
|
|
1212
1596
|
return { raw: 0, summary: 0, pattern: 0, principle: 0, turboCompression: 0 };
|
|
1213
1597
|
}
|
|
1214
1598
|
}
|
|
1599
|
+
getRuntimeContext() {
|
|
1600
|
+
const status = this.orchestrator.getStatus();
|
|
1601
|
+
const agents = (status.agents || []).map(a => a.toLowerCase());
|
|
1602
|
+
let currentAgentTool = null;
|
|
1603
|
+
let source = 'orchestrator';
|
|
1604
|
+
if (process.env.CODEX_SHELL || process.env.CODEX_THREAD_ID || process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE) {
|
|
1605
|
+
currentAgentTool = 'codex';
|
|
1606
|
+
source = 'environment';
|
|
1607
|
+
}
|
|
1608
|
+
else if (agents.some(a => a.includes('codex'))) {
|
|
1609
|
+
currentAgentTool = 'codex';
|
|
1610
|
+
}
|
|
1611
|
+
else if (agents.some(a => a.includes('claude'))) {
|
|
1612
|
+
currentAgentTool = 'claude-code';
|
|
1613
|
+
}
|
|
1614
|
+
else if (agents.some(a => a.includes('cursor'))) {
|
|
1615
|
+
currentAgentTool = 'cursor';
|
|
1616
|
+
}
|
|
1617
|
+
const labels = {
|
|
1618
|
+
codex: 'Codex',
|
|
1619
|
+
'claude-code': 'Claude Code',
|
|
1620
|
+
cursor: 'Cursor',
|
|
1621
|
+
cline: 'Cline',
|
|
1622
|
+
copilot: 'Copilot',
|
|
1623
|
+
windsurf: 'Windsurf',
|
|
1624
|
+
'kilo-code': 'Kilo Code',
|
|
1625
|
+
'roo-code': 'Roo Code',
|
|
1626
|
+
opencode: 'OpenCode',
|
|
1627
|
+
aider: 'Aider',
|
|
1628
|
+
};
|
|
1629
|
+
const statusProvider = typeof status.provider === 'string' ? status.provider : '';
|
|
1630
|
+
const provider = statusProvider || (process.env.OPENAI_BASE_URL ? 'openai-compatible' : 'ollama');
|
|
1631
|
+
const model = typeof status.model === 'string' && status.model.trim() ? status.model : null;
|
|
1632
|
+
return {
|
|
1633
|
+
currentAgentTool,
|
|
1634
|
+
currentAgentDisplay: currentAgentTool ? labels[currentAgentTool] ?? currentAgentTool : 'Unknown agent',
|
|
1635
|
+
provider,
|
|
1636
|
+
model,
|
|
1637
|
+
source,
|
|
1638
|
+
note: 'Current agent is runtime context. Token spend rows are historical records and may include other agents or models.',
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1215
1641
|
getToolsData() {
|
|
1216
1642
|
try {
|
|
1217
1643
|
const status = this.orchestrator.getStatus();
|
|
@@ -1262,6 +1688,80 @@ export class DashboardServer {
|
|
|
1262
1688
|
return { allowed: true, findings: [], riskScore: 0, summary: 'Firewall unavailable' };
|
|
1263
1689
|
}
|
|
1264
1690
|
}
|
|
1691
|
+
getDefaultWorkflowProjectDir() {
|
|
1692
|
+
const status = this.orchestrator.getStatus();
|
|
1693
|
+
const configured = String(status.projectDir || '').trim();
|
|
1694
|
+
const ww2br = 'D:\\New Game';
|
|
1695
|
+
if (configured)
|
|
1696
|
+
return configured;
|
|
1697
|
+
return ww2br;
|
|
1698
|
+
}
|
|
1699
|
+
async pathExists(pathToCheck) {
|
|
1700
|
+
try {
|
|
1701
|
+
const fs = await import('node:fs');
|
|
1702
|
+
return fs.existsSync(pathToCheck);
|
|
1703
|
+
}
|
|
1704
|
+
catch {
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
async getWorkflowAgentStatus(projectDir) {
|
|
1709
|
+
const knownAgents = [
|
|
1710
|
+
{ name: 'claude-code', displayName: 'Claude Code' },
|
|
1711
|
+
{ name: 'cursor', displayName: 'Cursor' },
|
|
1712
|
+
{ name: 'cline', displayName: 'Cline' },
|
|
1713
|
+
{ name: 'codex', displayName: 'Codex' },
|
|
1714
|
+
{ name: 'copilot', displayName: 'GitHub Copilot' },
|
|
1715
|
+
{ name: 'windsurf', displayName: 'Windsurf' },
|
|
1716
|
+
{ name: 'kilo-code', displayName: 'Kilo Code' },
|
|
1717
|
+
{ name: 'roo-code', displayName: 'Roo Code' },
|
|
1718
|
+
{ name: 'opencode', displayName: 'OpenCode' },
|
|
1719
|
+
{ name: 'aider', displayName: 'Aider' },
|
|
1720
|
+
];
|
|
1721
|
+
let detected = [];
|
|
1722
|
+
let hooks = [];
|
|
1723
|
+
try {
|
|
1724
|
+
const { detectRunningAgents } = await import('../adapters/index.js');
|
|
1725
|
+
detected = await detectRunningAgents(projectDir);
|
|
1726
|
+
}
|
|
1727
|
+
catch {
|
|
1728
|
+
detected = [];
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
const { getHookInstaller } = await import('../brain/session-hooks.js');
|
|
1732
|
+
hooks = await getHookInstaller().audit(projectDir);
|
|
1733
|
+
}
|
|
1734
|
+
catch {
|
|
1735
|
+
hooks = [];
|
|
1736
|
+
}
|
|
1737
|
+
return knownAgents.map(agent => {
|
|
1738
|
+
const det = detected.find((d) => d.name === agent.name);
|
|
1739
|
+
const hook = hooks.find((h) => h.agent === agent.name);
|
|
1740
|
+
return {
|
|
1741
|
+
...agent,
|
|
1742
|
+
detected: !!det,
|
|
1743
|
+
hookInstalled: !!hook,
|
|
1744
|
+
hookPath: hook?.installPath,
|
|
1745
|
+
};
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
getWorkflowWarnings(projectDir, agents, timeline, projectScoped) {
|
|
1749
|
+
const warnings = [];
|
|
1750
|
+
if (!projectDir)
|
|
1751
|
+
warnings.push('No active project path is configured for the workflow view.');
|
|
1752
|
+
if (!agents.some(a => a.detected || a.hookInstalled)) {
|
|
1753
|
+
warnings.push('No agent integrations are detected or hooked for this project path.');
|
|
1754
|
+
}
|
|
1755
|
+
else if (!agents.some(a => a.hookInstalled)) {
|
|
1756
|
+
warnings.push('Agent tools may be present, but Shadow Brain hooks are not installed for this project path.');
|
|
1757
|
+
}
|
|
1758
|
+
if (!projectScoped && timeline.length > 0) {
|
|
1759
|
+
warnings.push('No project-scoped memories found; showing the latest global brain memories instead.');
|
|
1760
|
+
}
|
|
1761
|
+
if (!timeline.length)
|
|
1762
|
+
warnings.push('No memories are available yet. Run agents with Shadow Brain attached to build the timeline.');
|
|
1763
|
+
return warnings;
|
|
1764
|
+
}
|
|
1265
1765
|
setupWebSocket() {
|
|
1266
1766
|
this.wss.on('connection', (ws) => {
|
|
1267
1767
|
this.clients.add(ws);
|
|
@@ -1293,6 +1793,9 @@ export class DashboardServer {
|
|
|
1293
1793
|
evolutionGen: status.evolutionGeneration ?? 0,
|
|
1294
1794
|
swarmConv: status.swarmConvergence ?? 0,
|
|
1295
1795
|
});
|
|
1796
|
+
// Replay recent realtime activity so a late-connecting client's live
|
|
1797
|
+
// feed is populated immediately instead of waiting for the next event.
|
|
1798
|
+
this.send(ws, { type: 'activity-backlog', events: getBrainBus().recent(50) });
|
|
1296
1799
|
ws.on('close', () => { this.clients.delete(ws); });
|
|
1297
1800
|
ws.on('error', () => { this.clients.delete(ws); });
|
|
1298
1801
|
});
|
|
@@ -1356,6 +1859,20 @@ export class DashboardServer {
|
|
|
1356
1859
|
}
|
|
1357
1860
|
async stop() {
|
|
1358
1861
|
return new Promise((resolve) => {
|
|
1862
|
+
try {
|
|
1863
|
+
this.brainBusUnsub?.();
|
|
1864
|
+
}
|
|
1865
|
+
catch { /* ignore */ }
|
|
1866
|
+
this.brainBusUnsub = null;
|
|
1867
|
+
if (this.brainFileDebounce) {
|
|
1868
|
+
clearTimeout(this.brainFileDebounce);
|
|
1869
|
+
this.brainFileDebounce = null;
|
|
1870
|
+
}
|
|
1871
|
+
try {
|
|
1872
|
+
this.brainWatcher?.close();
|
|
1873
|
+
}
|
|
1874
|
+
catch { /* ignore */ }
|
|
1875
|
+
this.brainWatcher = null;
|
|
1359
1876
|
for (const client of this.clients) {
|
|
1360
1877
|
try {
|
|
1361
1878
|
client.close();
|