@theihtisham/agent-shadow-brain 6.0.2 → 6.0.3

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 (51) hide show
  1. package/CHANGELOG.md +372 -0
  2. package/README.md +366 -455
  3. package/dist/brain/auto-update.js +1 -1
  4. package/dist/brain/brain-portability.js +3 -3
  5. package/dist/brain/brain-portability.js.map +1 -1
  6. package/dist/brain/context-continuity.d.ts +78 -0
  7. package/dist/brain/context-continuity.d.ts.map +1 -0
  8. package/dist/brain/context-continuity.js +341 -0
  9. package/dist/brain/context-continuity.js.map +1 -0
  10. package/dist/brain/global-brain.d.ts.map +1 -1
  11. package/dist/brain/global-brain.js +35 -9
  12. package/dist/brain/global-brain.js.map +1 -1
  13. package/dist/brain/live-brain.d.ts +119 -0
  14. package/dist/brain/live-brain.d.ts.map +1 -0
  15. package/dist/brain/live-brain.js +405 -0
  16. package/dist/brain/live-brain.js.map +1 -0
  17. package/dist/brain/mcp-server.d.ts +4 -0
  18. package/dist/brain/mcp-server.d.ts.map +1 -1
  19. package/dist/brain/mcp-server.js +81 -5
  20. package/dist/brain/mcp-server.js.map +1 -1
  21. package/dist/brain/memory-seeder.d.ts +78 -0
  22. package/dist/brain/memory-seeder.d.ts.map +1 -0
  23. package/dist/brain/memory-seeder.js +326 -0
  24. package/dist/brain/memory-seeder.js.map +1 -0
  25. package/dist/brain/orchestrator.js +1 -1
  26. package/dist/brain/token-economy.d.ts +2 -0
  27. package/dist/brain/token-economy.d.ts.map +1 -1
  28. package/dist/brain/token-economy.js +80 -24
  29. package/dist/brain/token-economy.js.map +1 -1
  30. package/dist/cli.js +240 -31
  31. package/dist/cli.js.map +1 -1
  32. package/dist/dashboard/server.d.ts +5 -0
  33. package/dist/dashboard/server.d.ts.map +1 -1
  34. package/dist/dashboard/server.js +355 -2
  35. package/dist/dashboard/server.js.map +1 -1
  36. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  37. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  38. package/dist/dashboard/v6-dashboard-html.js +755 -85
  39. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +4 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/types.d.ts +17 -12
  45. package/dist/types.d.ts.map +1 -1
  46. package/docs/launch/README.md +61 -0
  47. package/docs/launch/RELEASE_CHECKLIST_v6.0.3.md +102 -0
  48. package/docs/launch/RELEASE_NOTES_v6.0.3.md +111 -0
  49. package/package.json +25 -13
  50. package/tools/export-resume-pack.mjs +56 -0
  51. package/tools/record-global-memory.mjs +72 -0
@@ -18,9 +18,14 @@ export declare class DashboardServer {
18
18
  private readJsonBody;
19
19
  private getModulesData;
20
20
  private getMemoryData;
21
+ private getRuntimeContext;
21
22
  private getToolsData;
22
23
  private getTimelineData;
23
24
  private getFirewallDemo;
25
+ private getDefaultWorkflowProjectDir;
26
+ private pathExists;
27
+ private getWorkflowAgentStatus;
28
+ private getWorkflowWarnings;
24
29
  private setupWebSocket;
25
30
  private subscribeToOrchestrator;
26
31
  private send;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAMxD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAwYD,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;gBAEhC,YAAY,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB;IAiBtE,OAAO,CAAC,aAAa;YAmDP,eAAe;IAwgB7B,kFAAkF;YACpE,kBAAkB;YAoElB,YAAY;IAY1B,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,cAAc;IAwCtB,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;IAY3B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAOxD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAwYD,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;gBAEhC,YAAY,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB;IAiBtE,OAAO,CAAC,aAAa;YAmDP,eAAe;IA0tB7B,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;IAwCtB,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;IAY3B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -4,6 +4,7 @@
4
4
  import * as http from 'http';
5
5
  import { WebSocketServer, WebSocket } from 'ws';
6
6
  import { AgentFirewall } from '../brain/agent-firewall.js';
7
+ import { analyzeMemoryQuality, generateContextResumePack } from '../brain/context-continuity.js';
7
8
  import { getGlobalBrain, GlobalBrain } from '../brain/global-brain.js';
8
9
  import { V6_DASHBOARD_HTML } from './v6-dashboard-html.js';
9
10
  // ── Embedded HTML Client ──────────────────────────────────────────────────────
@@ -508,9 +509,11 @@ export class DashboardServer {
508
509
  const brain = getGlobalBrain();
509
510
  await brain.init();
510
511
  const tokenReport = await tokens.report();
512
+ const runtime = this.getRuntimeContext();
511
513
  return send({
512
- version: '6.0.0',
514
+ version: '6.0.3',
513
515
  localFirst: true,
516
+ runtime,
514
517
  totalAgentsConnected: brain.getStats().totalAgents,
515
518
  totalMemoriesStored: brain.getStats().totalEntries,
516
519
  modules: {
@@ -526,6 +529,156 @@ export class DashboardServer {
526
529
  generatedAt: new Date(),
527
530
  });
528
531
  }
532
+ case '/api/v6/ww2br-ops': {
533
+ const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
534
+ const brain = getGlobalBrain();
535
+ await brain.init();
536
+ const stats = brain.getStats();
537
+ const projectId = GlobalBrain.projectIdFor(projectDir);
538
+ let timeline = brain.timeline({ projectId, limit: 18 });
539
+ let scoped = true;
540
+ if (!timeline.length) {
541
+ scoped = false;
542
+ timeline = brain.timeline({ limit: 18 });
543
+ }
544
+ const memoryEntries = brain.recall({
545
+ projectId: scoped ? projectId : undefined,
546
+ minImportance: 0,
547
+ limit: 500,
548
+ });
549
+ const quality = analyzeMemoryQuality(memoryEntries);
550
+ const resumePack = generateContextResumePack({
551
+ projectDir,
552
+ projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
553
+ projectId,
554
+ entries: memoryEntries,
555
+ maxMemories: 12,
556
+ });
557
+ const agents = await this.getWorkflowAgentStatus(projectDir);
558
+ const categories = {};
559
+ for (const event of timeline)
560
+ categories[event.category] = (categories[event.category] ?? 0) + 1;
561
+ return send({
562
+ projectDir,
563
+ projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
564
+ projectExists: await this.pathExists(projectDir),
565
+ projectScoped: scoped,
566
+ stats,
567
+ agents,
568
+ warnings: this.getWorkflowWarnings(projectDir, agents, timeline, scoped),
569
+ recent: timeline,
570
+ categories,
571
+ quality,
572
+ resumePack: {
573
+ generatedAt: resumePack.generatedAt,
574
+ stats: resumePack.stats,
575
+ warnings: resumePack.warnings,
576
+ suggestedRecallQueries: resumePack.suggestedRecallQueries,
577
+ blockers: resumePack.blockers.slice(0, 5),
578
+ topMemories: resumePack.topMemories.slice(0, 5),
579
+ },
580
+ generatedAt: new Date(),
581
+ });
582
+ }
583
+ case '/api/v6/ww2br-recall': {
584
+ const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
585
+ const query = String(qs.get('q') || qs.get('query') || (body.query ?? '')).trim();
586
+ const brain = getGlobalBrain();
587
+ await brain.init();
588
+ const projectId = GlobalBrain.projectIdFor(projectDir);
589
+ const common = {
590
+ agentTool: qs.get('agent') || body.agent || undefined,
591
+ category: qs.get('category') || body.category || undefined,
592
+ keywords: query ? query.split(/\s+/).filter(Boolean) : undefined,
593
+ minImportance: Number(qs.get('minImportance') ?? body.minImportance ?? 0),
594
+ limit: Number(qs.get('limit') ?? body.limit ?? 12),
595
+ };
596
+ const projectResults = brain.recall({ projectId, ...common });
597
+ const results = projectResults.length ? projectResults : brain.recall(common);
598
+ return send({
599
+ projectDir,
600
+ projectScoped: projectResults.length > 0,
601
+ query,
602
+ results,
603
+ generatedAt: new Date(),
604
+ });
605
+ }
606
+ case '/api/v6/resume-pack': {
607
+ const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
608
+ const format = String(qs.get('format') || body.format || 'json').toLowerCase();
609
+ const limit = Number(qs.get('limit') ?? body.limit ?? 12);
610
+ const brain = getGlobalBrain();
611
+ await brain.init();
612
+ const projectId = GlobalBrain.projectIdFor(projectDir);
613
+ let entries = brain.recall({ projectId, minImportance: 0, limit: 500 });
614
+ let projectScoped = true;
615
+ if (!entries.length) {
616
+ projectScoped = false;
617
+ entries = brain.recall({ minImportance: 0, limit: 500 });
618
+ }
619
+ const pack = generateContextResumePack({
620
+ projectDir,
621
+ projectName: projectDir.split(/[\\/]/).filter(Boolean).pop() ?? projectDir,
622
+ projectId,
623
+ entries,
624
+ maxMemories: Number.isFinite(limit) ? Math.max(1, Math.min(30, limit)) : 12,
625
+ });
626
+ if (format === 'markdown' || format === 'md')
627
+ return send(pack.markdown, 200, 'text/markdown; charset=utf-8');
628
+ return send({ ...pack, projectScoped });
629
+ }
630
+ case '/api/v6/live-brain': {
631
+ const { getLiveBrain } = await import('../brain/live-brain.js');
632
+ const projectDir = String(qs.get('projectDir') || body.projectDir || this.getDefaultWorkflowProjectDir());
633
+ const format = String(qs.get('format') || body.format || 'json').toLowerCase();
634
+ const queriesRaw = String(qs.get('queries') || body.queries || '').trim();
635
+ const queries = queriesRaw ? queriesRaw.split('|').map(q => q.trim()).filter(Boolean) : undefined;
636
+ const report = await getLiveBrain().report({
637
+ projectDir,
638
+ maxMemories: Number(qs.get('limit') ?? body.limit ?? 12),
639
+ queries,
640
+ globalFallback: body.globalFallback !== false && qs.get('globalFallback') !== 'false',
641
+ });
642
+ if (format === 'markdown' || format === 'md')
643
+ return send(report.agentBriefing, 200, 'text/markdown; charset=utf-8');
644
+ return send(report);
645
+ }
646
+ case '/api/v6/seed-drafts': {
647
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
648
+ const limit = Number(qs.get('limit') ?? body.limit ?? 30);
649
+ const seeder = getMemorySeeder();
650
+ await seeder.init();
651
+ return send(seeder.list(limit));
652
+ }
653
+ case '/api/v6/seed-draft': {
654
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
655
+ const seeder = getMemorySeeder();
656
+ return send(await seeder.createDraft({
657
+ topic: body.topic,
658
+ objective: body.objective,
659
+ projectDir: body.projectDir || this.getDefaultWorkflowProjectDir(),
660
+ category: body.category,
661
+ tags: body.tags,
662
+ importance: body.importance,
663
+ researchAgent: body.researchAgent,
664
+ approverAgent: body.approverAgent,
665
+ approvalMode: body.approvalMode,
666
+ sources: body.sources,
667
+ content: body.content,
668
+ }));
669
+ }
670
+ case '/api/v6/seed-approve': {
671
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
672
+ return send(await getMemorySeeder().approve(body.id, body.approvedBy || 'user', body.note));
673
+ }
674
+ case '/api/v6/seed-reject': {
675
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
676
+ return send(await getMemorySeeder().reject(body.id, body.rejectedBy || 'user', body.reason || 'Rejected from dashboard'));
677
+ }
678
+ case '/api/v6/seed-commit': {
679
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
680
+ return send(await getMemorySeeder().seed(body.id, body.seededBy || 'user'));
681
+ }
529
682
  case '/api/v6/subagent-sliver': {
530
683
  const { getSubAgentBridge } = await import('../brain/subagent-bridge.js');
531
684
  const b = getSubAgentBridge();
@@ -620,7 +773,8 @@ export class DashboardServer {
620
773
  }
621
774
  case '/api/v6/tokens-report': {
622
775
  const { getTokenEconomy } = await import('../brain/token-economy.js');
623
- return send(await getTokenEconomy().report());
776
+ const report = await getTokenEconomy().report();
777
+ return send({ ...report, runtime: this.getRuntimeContext() });
624
778
  }
625
779
  case '/api/v6/forget-consolidate': {
626
780
  const { getForgettingCurve } = await import('../brain/forgetting-curve.js');
@@ -968,6 +1122,75 @@ export class DashboardServer {
968
1122
  return send({});
969
1123
  }
970
1124
  }
1125
+ case '/api/v6/memory-intelligence': {
1126
+ const brain = getGlobalBrain();
1127
+ await brain.init();
1128
+ const limit = Number(qs.get('limit') ?? body.limit ?? 500);
1129
+ const events = brain.timeline({ limit: Number.isFinite(limit) ? Math.max(50, Math.min(2000, limit)) : 500 });
1130
+ const now = Date.now();
1131
+ const byCategory = {};
1132
+ const byAgent = {};
1133
+ const topTags = {};
1134
+ let sourceBacked = 0;
1135
+ let recent24h = 0;
1136
+ let recent7d = 0;
1137
+ let stale30d = 0;
1138
+ let highImportance = 0;
1139
+ for (const event of events) {
1140
+ const createdAt = new Date(event.createdAt).getTime();
1141
+ const ageMs = now - createdAt;
1142
+ byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
1143
+ byAgent[event.agentTool] = (byAgent[event.agentTool] ?? 0) + 1;
1144
+ if (event.importance >= 0.75)
1145
+ highImportance++;
1146
+ if (ageMs <= 24 * 60 * 60 * 1000)
1147
+ recent24h++;
1148
+ if (ageMs <= 7 * 24 * 60 * 60 * 1000)
1149
+ recent7d++;
1150
+ if (ageMs > 30 * 24 * 60 * 60 * 1000)
1151
+ stale30d++;
1152
+ const sources = Array.isArray(event.metadata?.sources) ? event.metadata.sources : [];
1153
+ if (sources.length > 0)
1154
+ sourceBacked++;
1155
+ const tags = Array.isArray(event.metadata?.tags) ? event.metadata.tags : [];
1156
+ for (const tag of tags) {
1157
+ const key = String(tag).trim();
1158
+ if (key)
1159
+ topTags[key] = (topTags[key] ?? 0) + 1;
1160
+ }
1161
+ }
1162
+ const sortRecord = (record) => Object.entries(record)
1163
+ .sort((a, b) => b[1] - a[1])
1164
+ .slice(0, 12)
1165
+ .map(([name, count]) => ({ name, count }));
1166
+ let seedDrafts = [];
1167
+ try {
1168
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
1169
+ const seeder = getMemorySeeder();
1170
+ await seeder.init();
1171
+ seedDrafts = seeder.list(100);
1172
+ }
1173
+ catch {
1174
+ seedDrafts = [];
1175
+ }
1176
+ return send({
1177
+ totalEntries: brain.getStats().totalEntries,
1178
+ sampledEntries: events.length,
1179
+ sourceBacked,
1180
+ sourceCoverage: events.length ? sourceBacked / events.length : 0,
1181
+ recent24h,
1182
+ recent7d,
1183
+ stale30d,
1184
+ highImportance,
1185
+ byCategory: sortRecord(byCategory),
1186
+ byAgent: sortRecord(byAgent),
1187
+ topTags: sortRecord(topTags),
1188
+ openSeedDrafts: seedDrafts.filter(d => d.status === 'draft' || d.status === 'approved').length,
1189
+ pendingApprovals: seedDrafts.filter(d => d.status === 'draft').length,
1190
+ recent: events.slice(0, 12),
1191
+ generatedAt: new Date(),
1192
+ });
1193
+ }
971
1194
  case '/api/v6/memory-browser': {
972
1195
  const brain = getGlobalBrain();
973
1196
  await brain.init();
@@ -1082,6 +1305,20 @@ export class DashboardServer {
1082
1305
  }
1083
1306
  }
1084
1307
  catch { /* empty */ }
1308
+ try {
1309
+ const { getMemorySeeder } = await import('../brain/memory-seeder.js');
1310
+ const seeder = getMemorySeeder();
1311
+ await seeder.init();
1312
+ for (const draft of seeder.list(30)) {
1313
+ events.push({
1314
+ timestamp: new Date(draft.updatedAt || draft.createdAt),
1315
+ source: `seed:${draft.researchAgent}`,
1316
+ type: draft.status === 'seeded' ? 'memory-seeded' : 'seed-draft',
1317
+ detail: `[${draft.status}] ${draft.topic} | approval: ${draft.approvalMode}`,
1318
+ });
1319
+ }
1320
+ }
1321
+ catch { /* empty */ }
1085
1322
  try {
1086
1323
  const { getSubAgentBridge } = await import('../brain/subagent-bridge.js');
1087
1324
  const bridge = getSubAgentBridge();
@@ -1212,6 +1449,48 @@ export class DashboardServer {
1212
1449
  return { raw: 0, summary: 0, pattern: 0, principle: 0, turboCompression: 0 };
1213
1450
  }
1214
1451
  }
1452
+ getRuntimeContext() {
1453
+ const status = this.orchestrator.getStatus();
1454
+ const agents = (status.agents || []).map(a => a.toLowerCase());
1455
+ let currentAgentTool = null;
1456
+ let source = 'orchestrator';
1457
+ if (process.env.CODEX_SHELL || process.env.CODEX_THREAD_ID || process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE) {
1458
+ currentAgentTool = 'codex';
1459
+ source = 'environment';
1460
+ }
1461
+ else if (agents.some(a => a.includes('codex'))) {
1462
+ currentAgentTool = 'codex';
1463
+ }
1464
+ else if (agents.some(a => a.includes('claude'))) {
1465
+ currentAgentTool = 'claude-code';
1466
+ }
1467
+ else if (agents.some(a => a.includes('cursor'))) {
1468
+ currentAgentTool = 'cursor';
1469
+ }
1470
+ const labels = {
1471
+ codex: 'Codex',
1472
+ 'claude-code': 'Claude Code',
1473
+ cursor: 'Cursor',
1474
+ cline: 'Cline',
1475
+ copilot: 'Copilot',
1476
+ windsurf: 'Windsurf',
1477
+ 'kilo-code': 'Kilo Code',
1478
+ 'roo-code': 'Roo Code',
1479
+ opencode: 'OpenCode',
1480
+ aider: 'Aider',
1481
+ };
1482
+ const statusProvider = typeof status.provider === 'string' ? status.provider : '';
1483
+ const provider = statusProvider || (process.env.OPENAI_BASE_URL ? 'openai-compatible' : 'ollama');
1484
+ const model = typeof status.model === 'string' && status.model.trim() ? status.model : null;
1485
+ return {
1486
+ currentAgentTool,
1487
+ currentAgentDisplay: currentAgentTool ? labels[currentAgentTool] ?? currentAgentTool : 'Unknown agent',
1488
+ provider,
1489
+ model,
1490
+ source,
1491
+ note: 'Current agent is runtime context. Token spend rows are historical records and may include other agents or models.',
1492
+ };
1493
+ }
1215
1494
  getToolsData() {
1216
1495
  try {
1217
1496
  const status = this.orchestrator.getStatus();
@@ -1262,6 +1541,80 @@ export class DashboardServer {
1262
1541
  return { allowed: true, findings: [], riskScore: 0, summary: 'Firewall unavailable' };
1263
1542
  }
1264
1543
  }
1544
+ getDefaultWorkflowProjectDir() {
1545
+ const status = this.orchestrator.getStatus();
1546
+ const configured = String(status.projectDir || '').trim();
1547
+ const ww2br = 'D:\\New Game';
1548
+ if (configured)
1549
+ return configured;
1550
+ return ww2br;
1551
+ }
1552
+ async pathExists(pathToCheck) {
1553
+ try {
1554
+ const fs = await import('node:fs');
1555
+ return fs.existsSync(pathToCheck);
1556
+ }
1557
+ catch {
1558
+ return false;
1559
+ }
1560
+ }
1561
+ async getWorkflowAgentStatus(projectDir) {
1562
+ const knownAgents = [
1563
+ { name: 'claude-code', displayName: 'Claude Code' },
1564
+ { name: 'cursor', displayName: 'Cursor' },
1565
+ { name: 'cline', displayName: 'Cline' },
1566
+ { name: 'codex', displayName: 'Codex' },
1567
+ { name: 'copilot', displayName: 'GitHub Copilot' },
1568
+ { name: 'windsurf', displayName: 'Windsurf' },
1569
+ { name: 'kilo-code', displayName: 'Kilo Code' },
1570
+ { name: 'roo-code', displayName: 'Roo Code' },
1571
+ { name: 'opencode', displayName: 'OpenCode' },
1572
+ { name: 'aider', displayName: 'Aider' },
1573
+ ];
1574
+ let detected = [];
1575
+ let hooks = [];
1576
+ try {
1577
+ const { detectRunningAgents } = await import('../adapters/index.js');
1578
+ detected = await detectRunningAgents(projectDir);
1579
+ }
1580
+ catch {
1581
+ detected = [];
1582
+ }
1583
+ try {
1584
+ const { getHookInstaller } = await import('../brain/session-hooks.js');
1585
+ hooks = await getHookInstaller().audit(projectDir);
1586
+ }
1587
+ catch {
1588
+ hooks = [];
1589
+ }
1590
+ return knownAgents.map(agent => {
1591
+ const det = detected.find((d) => d.name === agent.name);
1592
+ const hook = hooks.find((h) => h.agent === agent.name);
1593
+ return {
1594
+ ...agent,
1595
+ detected: !!det,
1596
+ hookInstalled: !!hook,
1597
+ hookPath: hook?.installPath,
1598
+ };
1599
+ });
1600
+ }
1601
+ getWorkflowWarnings(projectDir, agents, timeline, projectScoped) {
1602
+ const warnings = [];
1603
+ if (!projectDir)
1604
+ warnings.push('No active project path is configured for the workflow view.');
1605
+ if (!agents.some(a => a.detected || a.hookInstalled)) {
1606
+ warnings.push('No agent integrations are detected or hooked for this project path.');
1607
+ }
1608
+ else if (!agents.some(a => a.hookInstalled)) {
1609
+ warnings.push('Agent tools may be present, but Shadow Brain hooks are not installed for this project path.');
1610
+ }
1611
+ if (!projectScoped && timeline.length > 0) {
1612
+ warnings.push('No project-scoped memories found; showing the latest global brain memories instead.');
1613
+ }
1614
+ if (!timeline.length)
1615
+ warnings.push('No memories are available yet. Run agents with Shadow Brain attached to build the timeline.');
1616
+ return warnings;
1617
+ }
1265
1618
  setupWebSocket() {
1266
1619
  this.wss.on('connection', (ws) => {
1267
1620
  this.clients.add(ws);