@stackbilt/aegis-core 0.1.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.
Files changed (148) hide show
  1. package/package.json +96 -0
  2. package/schema.sql +586 -0
  3. package/src/adapters/voice/cloudflare-agent.ts +34 -0
  4. package/src/auth.ts +124 -0
  5. package/src/bluesky.ts +464 -0
  6. package/src/claude-tools/content.ts +188 -0
  7. package/src/claude-tools/email.ts +69 -0
  8. package/src/claude-tools/github.ts +440 -0
  9. package/src/claude-tools/goals.ts +116 -0
  10. package/src/claude-tools/index.ts +353 -0
  11. package/src/claude-tools/web.ts +59 -0
  12. package/src/claude.ts +406 -0
  13. package/src/codebeast.ts +200 -0
  14. package/src/composite.ts +715 -0
  15. package/src/content/column.ts +80 -0
  16. package/src/content/hero-image.ts +47 -0
  17. package/src/content/index.ts +27 -0
  18. package/src/content/journal.ts +91 -0
  19. package/src/content/roundtable.ts +163 -0
  20. package/src/core.ts +309 -0
  21. package/src/dashboard.ts +620 -0
  22. package/src/decision-docs.ts +284 -0
  23. package/src/dispatch.ts +13 -0
  24. package/src/edge-env.ts +58 -0
  25. package/src/email.ts +850 -0
  26. package/src/exports.ts +156 -0
  27. package/src/github-projects.ts +312 -0
  28. package/src/github.ts +670 -0
  29. package/src/groq.ts +247 -0
  30. package/src/health-page.ts +578 -0
  31. package/src/index.ts +89 -0
  32. package/src/kernel/argus-actions.ts +397 -0
  33. package/src/kernel/argus-correlation.ts +639 -0
  34. package/src/kernel/board.ts +91 -0
  35. package/src/kernel/briefing.ts +177 -0
  36. package/src/kernel/classify-memory-topic.ts +166 -0
  37. package/src/kernel/cognition.ts +377 -0
  38. package/src/kernel/court-cards.ts +163 -0
  39. package/src/kernel/dispatch.ts +587 -0
  40. package/src/kernel/domain.ts +50 -0
  41. package/src/kernel/dynamic-tools.ts +322 -0
  42. package/src/kernel/executor-port.ts +45 -0
  43. package/src/kernel/executors/claude.ts +73 -0
  44. package/src/kernel/executors/direct.ts +237 -0
  45. package/src/kernel/executors/groq.ts +18 -0
  46. package/src/kernel/executors/index.ts +87 -0
  47. package/src/kernel/executors/tarotscript.ts +104 -0
  48. package/src/kernel/executors/workers-ai.ts +54 -0
  49. package/src/kernel/insight-cache.ts +76 -0
  50. package/src/kernel/memory/agenda.ts +200 -0
  51. package/src/kernel/memory/blocks.ts +188 -0
  52. package/src/kernel/memory/consolidation.ts +194 -0
  53. package/src/kernel/memory/episodic.ts +241 -0
  54. package/src/kernel/memory/goals.ts +156 -0
  55. package/src/kernel/memory/graph.ts +290 -0
  56. package/src/kernel/memory/index.ts +11 -0
  57. package/src/kernel/memory/insights.ts +316 -0
  58. package/src/kernel/memory/procedural.ts +467 -0
  59. package/src/kernel/memory/pruning.ts +67 -0
  60. package/src/kernel/memory/recall.ts +367 -0
  61. package/src/kernel/memory/semantic.ts +315 -0
  62. package/src/kernel/memory/synthesis.ts +161 -0
  63. package/src/kernel/memory-adapter.ts +369 -0
  64. package/src/kernel/memory-guardrails.ts +76 -0
  65. package/src/kernel/port.ts +23 -0
  66. package/src/kernel/resilience.ts +322 -0
  67. package/src/kernel/router.ts +471 -0
  68. package/src/kernel/scheduled/agent-dispatch.ts +252 -0
  69. package/src/kernel/scheduled/argus-analytics.ts +247 -0
  70. package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
  71. package/src/kernel/scheduled/argus-notify.ts +348 -0
  72. package/src/kernel/scheduled/board-sync.ts +110 -0
  73. package/src/kernel/scheduled/ci-watcher.ts +125 -0
  74. package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
  75. package/src/kernel/scheduled/consolidation.ts +229 -0
  76. package/src/kernel/scheduled/content-drip.ts +47 -0
  77. package/src/kernel/scheduled/content.ts +6 -0
  78. package/src/kernel/scheduled/conversation-facts.ts +204 -0
  79. package/src/kernel/scheduled/cost-report.ts +84 -0
  80. package/src/kernel/scheduled/curiosity.ts +219 -0
  81. package/src/kernel/scheduled/dev-activity.ts +44 -0
  82. package/src/kernel/scheduled/digest.ts +317 -0
  83. package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
  84. package/src/kernel/scheduled/dreaming/facts.ts +239 -0
  85. package/src/kernel/scheduled/dreaming/index.ts +8 -0
  86. package/src/kernel/scheduled/dreaming/llm.ts +33 -0
  87. package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
  88. package/src/kernel/scheduled/dreaming/persona.ts +75 -0
  89. package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
  90. package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
  91. package/src/kernel/scheduled/dreaming.ts +66 -0
  92. package/src/kernel/scheduled/entropy.ts +149 -0
  93. package/src/kernel/scheduled/escalation.ts +192 -0
  94. package/src/kernel/scheduled/feed-watcher.ts +206 -0
  95. package/src/kernel/scheduled/goals.ts +214 -0
  96. package/src/kernel/scheduled/governance.ts +41 -0
  97. package/src/kernel/scheduled/heartbeat.ts +220 -0
  98. package/src/kernel/scheduled/inbox-processor.ts +174 -0
  99. package/src/kernel/scheduled/index.ts +245 -0
  100. package/src/kernel/scheduled/issue-proposer.ts +478 -0
  101. package/src/kernel/scheduled/issue-watcher.ts +128 -0
  102. package/src/kernel/scheduled/pr-automerge.ts +213 -0
  103. package/src/kernel/scheduled/product-health.ts +107 -0
  104. package/src/kernel/scheduled/reflection.ts +373 -0
  105. package/src/kernel/scheduled/self-improvement.ts +114 -0
  106. package/src/kernel/scheduled/social-engage.ts +175 -0
  107. package/src/kernel/scheduled/task-audit.ts +60 -0
  108. package/src/kernel/symbolic.ts +156 -0
  109. package/src/kernel/types.ts +145 -0
  110. package/src/landing.ts +1190 -0
  111. package/src/lib/audit-chain/chain.ts +28 -0
  112. package/src/lib/audit-chain/types.ts +12 -0
  113. package/src/lib/observability/errors.ts +55 -0
  114. package/src/markdown.ts +164 -0
  115. package/src/mcp/handlers.ts +647 -0
  116. package/src/mcp/server.ts +184 -0
  117. package/src/mcp/tools.ts +316 -0
  118. package/src/mcp-client.ts +275 -0
  119. package/src/mcp-server.ts +2 -0
  120. package/src/operator/config.example.ts +60 -0
  121. package/src/operator/config.ts +60 -0
  122. package/src/operator/index.ts +46 -0
  123. package/src/operator/persona.example.ts +34 -0
  124. package/src/operator/persona.ts +34 -0
  125. package/src/operator/prompt-builder.ts +190 -0
  126. package/src/operator/types.ts +43 -0
  127. package/src/pulse.ts +1179 -0
  128. package/src/routes/bluesky.ts +116 -0
  129. package/src/routes/cc-tasks.ts +328 -0
  130. package/src/routes/codebeast.ts +1 -0
  131. package/src/routes/content.ts +194 -0
  132. package/src/routes/conversations.ts +25 -0
  133. package/src/routes/dynamic-tools.ts +111 -0
  134. package/src/routes/feedback.ts +192 -0
  135. package/src/routes/health.ts +147 -0
  136. package/src/routes/messages.ts +228 -0
  137. package/src/routes/observability.ts +82 -0
  138. package/src/routes/operator-logs.ts +42 -0
  139. package/src/routes/pages.ts +96 -0
  140. package/src/routes/sessions.ts +54 -0
  141. package/src/sanitize.ts +73 -0
  142. package/src/schema-enums.ts +155 -0
  143. package/src/search.ts +112 -0
  144. package/src/task-intelligence.ts +497 -0
  145. package/src/types.ts +194 -0
  146. package/src/ui.ts +5 -0
  147. package/src/version.ts +3 -0
  148. package/src/workers-ai-chat.ts +333 -0
package/src/pulse.ts ADDED
@@ -0,0 +1,1179 @@
1
+ /**
2
+ * /pulse — Live neural activity feed for AEGIS
3
+ * A real-time window into the system's autonomous operations.
4
+ * Public-facing, no sensitive data exposed.
5
+ */
6
+
7
+ export interface PulseEvent {
8
+ ts: string;
9
+ type: 'task' | 'heartbeat' | 'goal' | 'memory' | 'cc_task' | 'agenda' | 'reflection' | 'operator';
10
+ icon: string;
11
+ title: string;
12
+ detail: string;
13
+ status: 'ok' | 'warn' | 'error' | 'info' | 'active';
14
+ }
15
+
16
+ export interface PulseData {
17
+ version: string;
18
+ events: PulseEvent[];
19
+ summary: {
20
+ taskRuns24h: number;
21
+ taskErrors24h: number;
22
+ memoriesTotal: number;
23
+ goalsActive: number;
24
+ ccTasksCompleted: number;
25
+ ccTasksPending: number;
26
+ agendaActive: number;
27
+ lastHeartbeat: string | null;
28
+ lastDreaming: string | null;
29
+ };
30
+ kernel: { learned: number; learning: number; degraded: number; broken: number };
31
+ }
32
+
33
+ export async function getPulseData(db: D1Database, version = '0.0.0', memoryBinding?: import('./types.js').MemoryServiceBinding): Promise<PulseData> {
34
+ const events: PulseEvent[] = [];
35
+
36
+ // Parallel queries for all data sources
37
+ const [
38
+ taskRuns,
39
+ heartbeats,
40
+ goalActions,
41
+ recentMemories,
42
+ ccTasks,
43
+ agendaChanges,
44
+ reflections,
45
+ operatorLogs,
46
+ taskStats,
47
+ memoryCount,
48
+ goalCount,
49
+ ccStats,
50
+ agendaCount,
51
+ procedures,
52
+ ] = await Promise.all([
53
+ // Recent task runs (last 12h)
54
+ db.prepare(`
55
+ SELECT task_name, status, duration_ms, created_at FROM task_runs
56
+ WHERE created_at > datetime('now', '-12 hours')
57
+ ORDER BY created_at DESC LIMIT 60
58
+ `).all<{ task_name: string; status: string; duration_ms: number; created_at: string }>().catch(() => ({ results: [] })),
59
+
60
+ // Recent heartbeats
61
+ db.prepare(`
62
+ SELECT severity, summary, created_at FROM heartbeat_results
63
+ WHERE created_at > datetime('now', '-24 hours')
64
+ ORDER BY created_at DESC LIMIT 10
65
+ `).all<{ severity: string; summary: string; created_at: string }>().catch(() => ({ results: [] })),
66
+
67
+ // Recent goal actions
68
+ db.prepare(`
69
+ SELECT a.action_type, a.outcome, a.description, a.created_at, g.title as goal_title
70
+ FROM agent_actions a LEFT JOIN agent_goals g ON a.goal_id = g.id
71
+ WHERE a.created_at > datetime('now', '-24 hours')
72
+ ORDER BY a.created_at DESC LIMIT 20
73
+ `).all<{ action_type: string; outcome: string; description: string; created_at: string; goal_title: string }>().catch(() => ({ results: [] })),
74
+
75
+ // Recent memory writes — shadow_writes tracks all write activity (topic + timestamp)
76
+ db.prepare(`
77
+ SELECT topic, 1.0 as confidence, 'memory_worker' as source, created_at FROM shadow_writes
78
+ WHERE created_at > datetime('now', '-24 hours') AND worker_ok = 1
79
+ ORDER BY created_at DESC LIMIT 15
80
+ `).all<{ topic: string; confidence: number; source: string; created_at: string }>().catch(() => ({ results: [] })),
81
+
82
+ // Recent cc_tasks activity
83
+ db.prepare(`
84
+ SELECT title, repo, status, category, authority, pr_url, created_at, completed_at FROM cc_tasks
85
+ WHERE created_at > datetime('now', '-48 hours') OR completed_at > datetime('now', '-48 hours')
86
+ ORDER BY COALESCE(completed_at, created_at) DESC LIMIT 15
87
+ `).all<{ title: string; repo: string; status: string; category: string; authority: string; pr_url: string | null; created_at: string; completed_at: string | null }>().catch(() => ({ results: [] })),
88
+
89
+ // Recent agenda changes
90
+ db.prepare(`
91
+ SELECT item, priority, status, created_at, resolved_at FROM agent_agenda
92
+ WHERE created_at > datetime('now', '-48 hours') OR resolved_at > datetime('now', '-48 hours')
93
+ ORDER BY COALESCE(resolved_at, created_at) DESC LIMIT 10
94
+ `).all<{ item: string; priority: string; status: string; created_at: string; resolved_at: string | null }>().catch(() => ({ results: [] })),
95
+
96
+ // Recent reflections
97
+ db.prepare(`
98
+ SELECT content, memory_count, topics_covered, created_at FROM reflections
99
+ WHERE created_at > datetime('now', '-48 hours')
100
+ ORDER BY created_at DESC LIMIT 5
101
+ `).all<{ content: string; memory_count: number; topics_covered: number; created_at: string }>().catch(() => ({ results: [] })),
102
+
103
+ // Recent operator logs
104
+ db.prepare(`
105
+ SELECT episodes_count, goals_run, tasks_completed, tasks_failed, prs_created, created_at FROM operator_log
106
+ WHERE created_at > datetime('now', '-48 hours')
107
+ ORDER BY created_at DESC LIMIT 5
108
+ `).all<{ episodes_count: number; goals_run: number; tasks_completed: number; tasks_failed: number; prs_created: number; created_at: string }>().catch(() => ({ results: [] })),
109
+
110
+ // Summary stats
111
+ db.prepare(`
112
+ SELECT COUNT(*) as runs, SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors
113
+ FROM task_runs WHERE created_at > datetime('now', '-24 hours')
114
+ `).first<{ runs: number; errors: number }>().catch(() => ({ runs: 0, errors: 0 })),
115
+
116
+ // Memory count from Memory Worker (sole store)
117
+ memoryBinding
118
+ ? memoryBinding.health().then(h => ({ c: h.active_fragments })).catch(() => ({ c: 0 }))
119
+ : Promise.resolve({ c: 0 }),
120
+
121
+ db.prepare("SELECT COUNT(*) as c FROM agent_goals WHERE status = 'active'").first<{ c: number }>().catch(() => ({ c: 0 })),
122
+
123
+ db.prepare(`
124
+ SELECT
125
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
126
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
127
+ FROM cc_tasks
128
+ `).first<{ completed: number; pending: number }>().catch(() => ({ completed: 0, pending: 0 })),
129
+
130
+ db.prepare("SELECT COUNT(*) as c FROM agent_agenda WHERE status = 'active'").first<{ c: number }>().catch(() => ({ c: 0 })),
131
+
132
+ db.prepare(`
133
+ SELECT status, COUNT(*) as c FROM procedural_memory GROUP BY status
134
+ `).all<{ status: string; c: number }>().catch(() => ({ results: [] })),
135
+ ]);
136
+
137
+ // Build kernel from procedures
138
+ const kernel = { learned: 0, learning: 0, degraded: 0, broken: 0 };
139
+ for (const p of procedures.results) {
140
+ if (p.status in kernel) kernel[p.status as keyof typeof kernel] = p.c;
141
+ }
142
+
143
+ // Transform task runs into events
144
+ for (const t of taskRuns.results) {
145
+ events.push({
146
+ ts: t.created_at,
147
+ type: 'task',
148
+ icon: t.status === 'ok' ? '\u2713' : t.status === 'error' ? '\u2717' : '\u2014',
149
+ title: `System task`,
150
+ detail: `${t.status} in ${t.duration_ms}ms`,
151
+ status: t.status === 'ok' ? 'ok' : t.status === 'error' ? 'error' : 'info',
152
+ });
153
+ }
154
+
155
+ // Heartbeats
156
+ for (const h of heartbeats.results) {
157
+ const sev = h.severity || 'none';
158
+ events.push({
159
+ ts: h.created_at,
160
+ type: 'heartbeat',
161
+ icon: '\u2665',
162
+ title: `Heartbeat: ${sev}`,
163
+ detail: 'System check complete',
164
+ status: sev === 'none' || sev === 'low' ? 'ok' : sev === 'medium' ? 'warn' : 'error',
165
+ });
166
+ }
167
+
168
+ // Goal actions
169
+ for (const g of goalActions.results) {
170
+ events.push({
171
+ ts: g.created_at,
172
+ type: 'goal',
173
+ icon: '\u25C9',
174
+ title: `Goal activity`,
175
+ detail: `${g.action_type} \u2192 ${g.outcome}`,
176
+ status: g.outcome === 'success' ? 'ok' : g.outcome === 'failure' ? 'error' : 'info',
177
+ });
178
+ }
179
+
180
+ // Memory writes
181
+ for (const m of recentMemories.results) {
182
+ events.push({
183
+ ts: m.created_at,
184
+ type: 'memory',
185
+ icon: '\u25C6',
186
+ title: `Memory write`,
187
+ detail: `confidence ${(m.confidence * 100).toFixed(0)}%`,
188
+ status: 'info',
189
+ });
190
+ }
191
+
192
+ // CC tasks
193
+ for (const t of ccTasks.results) {
194
+ const ts = t.completed_at || t.created_at;
195
+ const statusMap: Record<string, PulseEvent['status']> = {
196
+ completed: 'ok', failed: 'error', running: 'active', pending: 'info', cancelled: 'warn',
197
+ };
198
+ events.push({
199
+ ts,
200
+ type: 'cc_task',
201
+ icon: t.status === 'completed' ? '\u2713' : t.status === 'running' ? '\u25B6' : '\u25CB',
202
+ title: `Pipeline: ${t.category}`,
203
+ detail: `${t.status}${t.pr_url ? ' \u2192 PR' : ''}`,
204
+ status: statusMap[t.status] || 'info',
205
+ });
206
+ }
207
+
208
+ // Agenda changes
209
+ for (const a of agendaChanges.results) {
210
+ const ts = a.resolved_at || a.created_at;
211
+ events.push({
212
+ ts,
213
+ type: 'agenda',
214
+ icon: a.status === 'done' ? '\u2611' : a.status === 'dismissed' ? '\u2612' : '\u2610',
215
+ title: `Agenda item`,
216
+ detail: `${a.priority} / ${a.status}`,
217
+ status: a.status === 'done' ? 'ok' : a.status === 'active' ? 'active' : 'info',
218
+ });
219
+ }
220
+
221
+ // Reflections
222
+ for (const r of reflections.results) {
223
+ events.push({
224
+ ts: r.created_at,
225
+ type: 'reflection',
226
+ icon: '\u2727',
227
+ title: `Reflection cycle`,
228
+ detail: `${r.memory_count} memories consolidated, ${r.topics_covered} topics covered`,
229
+ status: 'info',
230
+ });
231
+ }
232
+
233
+ // Operator logs
234
+ for (const o of operatorLogs.results) {
235
+ events.push({
236
+ ts: o.created_at,
237
+ type: 'operator',
238
+ icon: '\u2318',
239
+ title: `Operator worklog`,
240
+ detail: `${o.episodes_count} episodes, ${o.goals_run} goals, ${o.tasks_completed} tasks, ${o.prs_created} PRs`,
241
+ status: o.tasks_failed > 0 ? 'warn' : 'ok',
242
+ });
243
+ }
244
+
245
+ // Sort by timestamp descending
246
+ events.sort((a, b) => b.ts.localeCompare(a.ts));
247
+
248
+ // Find last heartbeat and dreaming timestamps
249
+ const lastHeartbeat = taskRuns.results.find(t => t.task_name === 'heartbeat')?.created_at ?? null;
250
+ const lastDreaming = taskRuns.results.find(t => t.task_name === 'dreaming')?.created_at ?? null;
251
+
252
+ return {
253
+ version,
254
+ events: events.slice(0, 100), // Cap at 100 events
255
+ summary: {
256
+ taskRuns24h: taskStats?.runs ?? 0,
257
+ taskErrors24h: taskStats?.errors ?? 0,
258
+ memoriesTotal: memoryCount?.c ?? 0,
259
+ goalsActive: goalCount?.c ?? 0,
260
+ ccTasksCompleted: ccStats?.completed ?? 0,
261
+ ccTasksPending: ccStats?.pending ?? 0,
262
+ agendaActive: agendaCount?.c ?? 0,
263
+ lastHeartbeat,
264
+ lastDreaming,
265
+ },
266
+ kernel,
267
+ };
268
+ }
269
+
270
+ export function pulsePage(data: PulseData): string {
271
+ const statusOk = data.summary.taskErrors24h === 0;
272
+
273
+ const eventRows = data.events.map((e, i) => {
274
+ const ts = e.ts ? new Date(e.ts.endsWith('Z') ? e.ts : e.ts + 'Z') : new Date();
275
+ const timeStr = ts.toISOString().replace('T', ' ').slice(0, 19);
276
+ const relTime = getRelativeTime(ts);
277
+ const delay = Math.min(i * 30, 1500);
278
+ return `
279
+ <div class="ev ev-${e.status} ev-${e.type}" style="animation-delay:${delay}ms">
280
+ <div class="ev-time">
281
+ <span class="ev-rel">${esc(relTime)}</span>
282
+ <span class="ev-abs">${timeStr}</span>
283
+ </div>
284
+ <div class="ev-icon">${e.icon}</div>
285
+ <div class="ev-body">
286
+ <div class="ev-title">${esc(e.title)}</div>
287
+ <div class="ev-detail">${esc(e.detail)}</div>
288
+ </div>
289
+ <div class="ev-tag ev-tag-${e.type}">${e.type}</div>
290
+ </div>`;
291
+ }).join('');
292
+
293
+ const kernelTotal = data.kernel.learned + data.kernel.learning + data.kernel.degraded + data.kernel.broken;
294
+
295
+ return `<!DOCTYPE html>
296
+ <html lang="en">
297
+ <head>
298
+ <meta charset="utf-8">
299
+ <meta name="viewport" content="width=device-width, initial-scale=1">
300
+ <title>AEGIS Neural Pulse</title>
301
+ <meta name="description" content="Live neural activity feed for AEGIS — watch an autonomous AI agent think, learn, and ship code in real-time.">
302
+ <meta name="theme-color" content="#04040a">
303
+ <link rel="preconnect" href="https://fonts.googleapis.com">
304
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
305
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=Syne:wght@700;800&display=swap" rel="stylesheet">
306
+ <style>
307
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
308
+
309
+ :root {
310
+ --bg: #04040a;
311
+ --bg-card: #080812;
312
+ --bg-hover: #0c0c1a;
313
+ --border: rgba(123, 123, 223, 0.06);
314
+ --border-bright: rgba(123, 123, 223, 0.12);
315
+ --accent: #7b7bdf;
316
+ --accent-glow: #8b8bff;
317
+ --teal: #3dd6c8;
318
+ --green: #2dd4a0;
319
+ --amber: #f5a623;
320
+ --red: #ef4444;
321
+ --text: #c8c8d8;
322
+ --text-sec: #7a7a90;
323
+ --text-dim: #3e3e50;
324
+ --mono: 'JetBrains Mono', monospace;
325
+ --display: 'Syne', sans-serif;
326
+ }
327
+
328
+ html { -webkit-font-smoothing: antialiased; scroll-behavior: smooth; }
329
+
330
+ body {
331
+ background: var(--bg);
332
+ color: var(--text);
333
+ font-family: var(--mono);
334
+ font-size: 12px;
335
+ line-height: 1.5;
336
+ min-height: 100vh;
337
+ overflow-x: hidden;
338
+ }
339
+
340
+ /* Noise */
341
+ body::before {
342
+ content: '';
343
+ position: fixed;
344
+ inset: 0;
345
+ opacity: 0.018;
346
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
347
+ background-size: 256px 256px;
348
+ pointer-events: none;
349
+ z-index: 1;
350
+ }
351
+
352
+ .ambient {
353
+ position: fixed;
354
+ top: -30%;
355
+ left: 50%;
356
+ transform: translateX(-50%);
357
+ width: 120%;
358
+ height: 60%;
359
+ background: radial-gradient(ellipse at center, rgba(61,214,200,0.025) 0%, rgba(123,123,223,0.015) 40%, transparent 70%);
360
+ pointer-events: none;
361
+ z-index: 0;
362
+ animation: breathe 12s ease-in-out infinite;
363
+ }
364
+
365
+ @keyframes breathe {
366
+ 0%, 100% { opacity: 1; }
367
+ 50% { opacity: 0.25; }
368
+ }
369
+
370
+ /* Scan line */
371
+ .scan {
372
+ position: fixed;
373
+ left: 0; right: 0;
374
+ height: 1px;
375
+ background: linear-gradient(90deg, transparent 10%, rgba(61,214,200,0.06) 50%, transparent 90%);
376
+ pointer-events: none;
377
+ z-index: 50;
378
+ animation: scan 8s linear infinite;
379
+ }
380
+
381
+ @keyframes scan {
382
+ 0% { top: -1px; }
383
+ 100% { top: 100vh; }
384
+ }
385
+
386
+ .wrap {
387
+ position: relative;
388
+ z-index: 2;
389
+ max-width: 1060px;
390
+ margin: 0 auto;
391
+ padding: 1.5rem 1.25rem 4rem;
392
+ }
393
+
394
+ /* ── Header ─────────────────────────── */
395
+ .head {
396
+ display: flex;
397
+ align-items: center;
398
+ justify-content: space-between;
399
+ padding-bottom: 1.25rem;
400
+ border-bottom: 1px solid var(--border);
401
+ margin-bottom: 1.25rem;
402
+ }
403
+
404
+ .head-left {
405
+ display: flex;
406
+ align-items: center;
407
+ gap: 0.75rem;
408
+ }
409
+
410
+ .head-logo {
411
+ width: 32px;
412
+ height: 32px;
413
+ border-radius: 6px;
414
+ background: linear-gradient(135deg, rgba(61,214,200,0.12), rgba(123,123,223,0.08));
415
+ border: 1px solid rgba(61,214,200,0.2);
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: center;
419
+ font-family: var(--display);
420
+ font-weight: 800;
421
+ font-size: 13px;
422
+ color: var(--teal);
423
+ }
424
+
425
+ .head-info h1 {
426
+ font-family: var(--display);
427
+ font-size: 15px;
428
+ font-weight: 800;
429
+ letter-spacing: -0.01em;
430
+ }
431
+
432
+ .head-info span {
433
+ font-size: 10px;
434
+ color: var(--text-dim);
435
+ letter-spacing: 0.06em;
436
+ text-transform: uppercase;
437
+ }
438
+
439
+ .head-right {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 0.75rem;
443
+ }
444
+
445
+ .head-ver {
446
+ font-size: 10px;
447
+ padding: 3px 8px;
448
+ border-radius: 3px;
449
+ background: rgba(123,123,223,0.06);
450
+ border: 1px solid var(--border-bright);
451
+ color: var(--accent);
452
+ }
453
+
454
+ .head-live {
455
+ display: flex;
456
+ align-items: center;
457
+ gap: 6px;
458
+ font-size: 10px;
459
+ color: var(--green);
460
+ text-transform: uppercase;
461
+ letter-spacing: 0.08em;
462
+ }
463
+
464
+ .head-live-dot {
465
+ width: 6px;
466
+ height: 6px;
467
+ border-radius: 50%;
468
+ background: var(--green);
469
+ box-shadow: 0 0 6px rgba(45,212,160,0.6);
470
+ animation: pulse 2s ease-in-out infinite;
471
+ }
472
+
473
+ @keyframes pulse {
474
+ 0%, 100% { opacity: 1; transform: scale(1); }
475
+ 50% { opacity: 0.4; transform: scale(1.3); }
476
+ }
477
+
478
+ /* ── Stats Bar ──────────────────────── */
479
+ .stats {
480
+ display: grid;
481
+ grid-template-columns: repeat(7, 1fr);
482
+ gap: 0.5rem;
483
+ margin-bottom: 1.25rem;
484
+ }
485
+
486
+ .stat-card {
487
+ background: var(--bg-card);
488
+ border: 1px solid var(--border);
489
+ border-radius: 6px;
490
+ padding: 0.75rem;
491
+ text-align: center;
492
+ transition: border-color 0.2s;
493
+ }
494
+
495
+ .stat-card:hover { border-color: var(--border-bright); }
496
+
497
+ .stat-val {
498
+ font-size: 18px;
499
+ font-weight: 700;
500
+ line-height: 1;
501
+ }
502
+
503
+ .stat-val.teal { color: var(--teal); }
504
+ .stat-val.green { color: var(--green); }
505
+ .stat-val.accent { color: var(--accent-glow); }
506
+ .stat-val.amber { color: var(--amber); }
507
+
508
+ .stat-lbl {
509
+ font-size: 9px;
510
+ color: var(--text-dim);
511
+ text-transform: uppercase;
512
+ letter-spacing: 0.06em;
513
+ margin-top: 4px;
514
+ }
515
+
516
+ /* ── Layout ─────────────────────────── */
517
+ .main {
518
+ display: grid;
519
+ grid-template-columns: 1fr 260px;
520
+ gap: 1rem;
521
+ }
522
+
523
+ /* ── Event Feed ─────────────────────── */
524
+ .feed-head {
525
+ display: flex;
526
+ align-items: center;
527
+ justify-content: space-between;
528
+ margin-bottom: 0.75rem;
529
+ }
530
+
531
+ .feed-title {
532
+ font-size: 10px;
533
+ color: var(--text-dim);
534
+ text-transform: uppercase;
535
+ letter-spacing: 0.1em;
536
+ }
537
+
538
+ .feed-count {
539
+ font-size: 10px;
540
+ color: var(--text-dim);
541
+ }
542
+
543
+ .feed-filters {
544
+ display: flex;
545
+ gap: 4px;
546
+ margin-bottom: 0.75rem;
547
+ flex-wrap: wrap;
548
+ }
549
+
550
+ .feed-filter {
551
+ font-family: var(--mono);
552
+ font-size: 10px;
553
+ padding: 3px 8px;
554
+ border-radius: 3px;
555
+ background: rgba(255,255,255,0.02);
556
+ border: 1px solid var(--border);
557
+ color: var(--text-dim);
558
+ cursor: pointer;
559
+ transition: all 0.15s;
560
+ }
561
+
562
+ .feed-filter:hover, .feed-filter.active {
563
+ border-color: var(--teal);
564
+ color: var(--teal);
565
+ background: rgba(61,214,200,0.06);
566
+ }
567
+
568
+ .feed {
569
+ display: flex;
570
+ flex-direction: column;
571
+ gap: 2px;
572
+ }
573
+
574
+ .ev {
575
+ display: grid;
576
+ grid-template-columns: 120px 24px 1fr auto;
577
+ align-items: center;
578
+ gap: 0.5rem;
579
+ padding: 6px 10px;
580
+ border-radius: 4px;
581
+ background: transparent;
582
+ transition: background 0.15s;
583
+ opacity: 0;
584
+ animation: fadeIn 0.3s ease forwards;
585
+ border-left: 2px solid transparent;
586
+ }
587
+
588
+ @keyframes fadeIn {
589
+ to { opacity: 1; }
590
+ }
591
+
592
+ .ev:hover { background: var(--bg-hover); }
593
+
594
+ .ev-ok { border-left-color: rgba(45,212,160,0.3); }
595
+ .ev-warn { border-left-color: rgba(245,166,35,0.3); }
596
+ .ev-error { border-left-color: rgba(239,68,68,0.3); }
597
+ .ev-info { border-left-color: rgba(123,123,223,0.2); }
598
+ .ev-active { border-left-color: rgba(61,214,200,0.4); }
599
+
600
+ .ev-time {
601
+ display: flex;
602
+ flex-direction: column;
603
+ gap: 1px;
604
+ }
605
+
606
+ .ev-rel {
607
+ font-size: 10px;
608
+ color: var(--text-sec);
609
+ }
610
+
611
+ .ev-abs {
612
+ font-size: 9px;
613
+ color: var(--text-dim);
614
+ }
615
+
616
+ .ev-icon {
617
+ font-size: 12px;
618
+ text-align: center;
619
+ width: 20px;
620
+ }
621
+
622
+ .ev-ok .ev-icon { color: var(--green); }
623
+ .ev-warn .ev-icon { color: var(--amber); }
624
+ .ev-error .ev-icon { color: var(--red); }
625
+ .ev-info .ev-icon { color: var(--accent); }
626
+ .ev-active .ev-icon { color: var(--teal); }
627
+
628
+ .ev-body {
629
+ min-width: 0;
630
+ }
631
+
632
+ .ev-title {
633
+ font-size: 11px;
634
+ font-weight: 500;
635
+ color: var(--text);
636
+ white-space: nowrap;
637
+ overflow: hidden;
638
+ text-overflow: ellipsis;
639
+ }
640
+
641
+ .ev-detail {
642
+ font-size: 10px;
643
+ color: var(--text-dim);
644
+ white-space: nowrap;
645
+ overflow: hidden;
646
+ text-overflow: ellipsis;
647
+ }
648
+
649
+ .ev-tag {
650
+ font-size: 9px;
651
+ padding: 2px 6px;
652
+ border-radius: 3px;
653
+ text-transform: uppercase;
654
+ letter-spacing: 0.04em;
655
+ white-space: nowrap;
656
+ }
657
+
658
+ .ev-tag-task { background: rgba(45,212,160,0.08); color: var(--green); }
659
+ .ev-tag-heartbeat { background: rgba(239,68,68,0.08); color: #f87171; }
660
+ .ev-tag-goal { background: rgba(123,123,223,0.08); color: var(--accent); }
661
+ .ev-tag-memory { background: rgba(61,214,200,0.08); color: var(--teal); }
662
+ .ev-tag-cc_task { background: rgba(245,166,35,0.08); color: var(--amber); }
663
+ .ev-tag-agenda { background: rgba(255,255,255,0.04); color: var(--text-sec); }
664
+ .ev-tag-reflection { background: rgba(168,85,247,0.08); color: #a78bfa; }
665
+ .ev-tag-operator { background: rgba(45,212,160,0.06); color: var(--green); }
666
+
667
+ /* ── Sidebar ────────────────────────── */
668
+ .sidebar {
669
+ display: flex;
670
+ flex-direction: column;
671
+ gap: 0.75rem;
672
+ }
673
+
674
+ .side-card {
675
+ background: var(--bg-card);
676
+ border: 1px solid var(--border);
677
+ border-radius: 6px;
678
+ padding: 0.85rem;
679
+ }
680
+
681
+ .side-label {
682
+ font-size: 9px;
683
+ color: var(--text-dim);
684
+ text-transform: uppercase;
685
+ letter-spacing: 0.08em;
686
+ margin-bottom: 0.5rem;
687
+ }
688
+
689
+ /* Kernel bar */
690
+ .kern-bar {
691
+ display: flex;
692
+ height: 4px;
693
+ border-radius: 2px;
694
+ overflow: hidden;
695
+ background: rgba(255,255,255,0.02);
696
+ margin-bottom: 0.5rem;
697
+ }
698
+
699
+ .kern-bar .seg-l { background: var(--green); }
700
+ .kern-bar .seg-n { background: var(--teal); }
701
+ .kern-bar .seg-d { background: var(--amber); }
702
+ .kern-bar .seg-b { background: var(--red); }
703
+
704
+ .kern-legend {
705
+ display: flex;
706
+ flex-direction: column;
707
+ gap: 3px;
708
+ }
709
+
710
+ .kern-row {
711
+ display: flex;
712
+ justify-content: space-between;
713
+ align-items: center;
714
+ font-size: 10px;
715
+ }
716
+
717
+ .kern-row-left {
718
+ display: flex;
719
+ align-items: center;
720
+ gap: 5px;
721
+ color: var(--text-dim);
722
+ }
723
+
724
+ .kern-dot {
725
+ width: 5px;
726
+ height: 5px;
727
+ border-radius: 50%;
728
+ }
729
+
730
+ .kern-dot.l { background: var(--green); }
731
+ .kern-dot.n { background: var(--teal); }
732
+ .kern-dot.d { background: var(--amber); }
733
+ .kern-dot.b { background: var(--red); }
734
+
735
+ .kern-row-val {
736
+ font-weight: 500;
737
+ color: var(--text);
738
+ }
739
+
740
+ /* Timeline markers */
741
+ .timeline {
742
+ display: flex;
743
+ flex-direction: column;
744
+ gap: 6px;
745
+ }
746
+
747
+ .tl-item {
748
+ display: flex;
749
+ align-items: center;
750
+ gap: 6px;
751
+ font-size: 10px;
752
+ }
753
+
754
+ .tl-dot {
755
+ width: 4px;
756
+ height: 4px;
757
+ border-radius: 50%;
758
+ background: var(--text-dim);
759
+ flex-shrink: 0;
760
+ }
761
+
762
+ .tl-dot.active { background: var(--green); box-shadow: 0 0 4px rgba(45,212,160,0.4); }
763
+
764
+ .tl-label { color: var(--text-dim); }
765
+ .tl-val { color: var(--text-sec); margin-left: auto; }
766
+
767
+ /* ── Footer ─────────────────────────── */
768
+ .foot {
769
+ display: flex;
770
+ align-items: center;
771
+ justify-content: space-between;
772
+ padding-top: 1.25rem;
773
+ margin-top: 1.25rem;
774
+ border-top: 1px solid var(--border);
775
+ font-size: 10px;
776
+ color: var(--text-dim);
777
+ }
778
+
779
+ .foot a {
780
+ color: var(--text-dim);
781
+ text-decoration: none;
782
+ padding: 3px 8px;
783
+ border-radius: 3px;
784
+ border: 1px solid var(--border);
785
+ transition: all 0.15s;
786
+ }
787
+
788
+ .foot a:hover { border-color: var(--accent); color: var(--accent); }
789
+
790
+ .foot-links { display: flex; gap: 0.5rem; }
791
+
792
+ /* Auto-refresh indicator */
793
+ .refresh-bar {
794
+ position: fixed;
795
+ top: 0;
796
+ left: 0;
797
+ height: 2px;
798
+ background: linear-gradient(90deg, var(--teal), var(--accent));
799
+ z-index: 100;
800
+ animation: refresh-progress 30s linear infinite;
801
+ opacity: 0.6;
802
+ }
803
+
804
+ @keyframes refresh-progress {
805
+ 0% { width: 0; }
806
+ 100% { width: 100%; }
807
+ }
808
+
809
+ /* ── Neural Viz ─────────────────────── */
810
+ .pulse-viz {
811
+ position: relative;
812
+ width: 100%;
813
+ height: 180px;
814
+ margin-bottom: 1.25rem;
815
+ border-radius: 6px;
816
+ background: var(--bg-card);
817
+ border: 1px solid var(--border);
818
+ overflow: hidden;
819
+ }
820
+
821
+ .pulse-viz canvas {
822
+ display: block;
823
+ width: 100%;
824
+ height: 100%;
825
+ }
826
+
827
+ /* ── Responsive ─────────────────────── */
828
+ @media (max-width: 768px) {
829
+ .stats { grid-template-columns: repeat(4, 1fr); }
830
+ .main { grid-template-columns: 1fr; }
831
+ .sidebar { display: grid; grid-template-columns: repeat(2, 1fr); }
832
+ .ev { grid-template-columns: 80px 20px 1fr auto; }
833
+ .ev-abs { display: none; }
834
+ }
835
+
836
+ @media (max-width: 480px) {
837
+ .stats { grid-template-columns: repeat(2, 1fr); }
838
+ .sidebar { grid-template-columns: 1fr; }
839
+ .ev-tag { display: none; }
840
+ }
841
+ </style>
842
+ </head>
843
+ <body>
844
+ <div class="ambient"></div>
845
+ <div class="scan"></div>
846
+ <div class="refresh-bar"></div>
847
+
848
+ <div class="wrap">
849
+ <header class="head">
850
+ <div class="head-left">
851
+ <div class="head-logo">A</div>
852
+ <div class="head-info">
853
+ <h1>Neural Pulse</h1>
854
+ <span>Autonomous Activity Feed</span>
855
+ </div>
856
+ </div>
857
+ <div class="head-right">
858
+ <span class="head-ver">v${esc(data.version)}</span>
859
+ <div class="head-live">
860
+ <span class="head-live-dot"></span>
861
+ Live
862
+ </div>
863
+ </div>
864
+ </header>
865
+
866
+ <div class="stats">
867
+ <div class="stat-card">
868
+ <div class="stat-val green">${data.summary.taskRuns24h}</div>
869
+ <div class="stat-lbl">Tasks 24h</div>
870
+ </div>
871
+ <div class="stat-card">
872
+ <div class="stat-val ${data.summary.taskErrors24h === 0 ? 'green' : 'amber'}">${data.summary.taskErrors24h === 0 ? '0' : data.summary.taskErrors24h}</div>
873
+ <div class="stat-lbl">Errors</div>
874
+ </div>
875
+ <div class="stat-card">
876
+ <div class="stat-val teal">${data.summary.memoriesTotal}</div>
877
+ <div class="stat-lbl">Memories</div>
878
+ </div>
879
+ <div class="stat-card">
880
+ <div class="stat-val accent">${data.summary.goalsActive}</div>
881
+ <div class="stat-lbl">Goals</div>
882
+ </div>
883
+ <div class="stat-card">
884
+ <div class="stat-val green">${data.summary.ccTasksCompleted}</div>
885
+ <div class="stat-lbl">Tasks Done</div>
886
+ </div>
887
+ <div class="stat-card">
888
+ <div class="stat-val amber">${data.summary.ccTasksPending}</div>
889
+ <div class="stat-lbl">Queued</div>
890
+ </div>
891
+ <div class="stat-card">
892
+ <div class="stat-val accent">${data.summary.agendaActive}</div>
893
+ <div class="stat-lbl">Agenda</div>
894
+ </div>
895
+ </div>
896
+
897
+ <div class="pulse-viz">
898
+ <canvas id="neural-pulse" data-tasks="${data.summary.taskRuns24h}" data-errors="${data.summary.taskErrors24h}" data-memories="${data.summary.memoriesTotal}" data-goals="${data.summary.goalsActive}" data-learned="${data.kernel.learned}" data-learning="${data.kernel.learning}"></canvas>
899
+ </div>
900
+
901
+ <div class="main">
902
+ <div class="feed-col">
903
+ <div class="feed-head">
904
+ <span class="feed-title">Activity Stream</span>
905
+ <span class="feed-count">${data.events.length} events</span>
906
+ </div>
907
+ <div class="feed-filters">
908
+ <button class="feed-filter active" data-type="all">All</button>
909
+ <button class="feed-filter" data-type="task">Tasks</button>
910
+ <button class="feed-filter" data-type="heartbeat">Heartbeat</button>
911
+ <button class="feed-filter" data-type="goal">Goals</button>
912
+ <button class="feed-filter" data-type="memory">Memory</button>
913
+ <button class="feed-filter" data-type="cc_task">Pipeline</button>
914
+ <button class="feed-filter" data-type="reflection">Reflection</button>
915
+ </div>
916
+ <div class="feed" id="feed">
917
+ ${eventRows}
918
+ <div id="feed-empty" style="display:none;padding:2rem;text-align:center;color:var(--text-dim);font-size:11px">No events matching this filter in the current window.</div>
919
+ </div>
920
+ </div>
921
+
922
+ <aside class="sidebar">
923
+ <div class="side-card">
924
+ <div class="side-label">Procedural Kernel</div>
925
+ <div class="kern-bar">
926
+ ${kernelTotal > 0 ? `
927
+ <div class="seg-l" style="width:${(data.kernel.learned / kernelTotal * 100).toFixed(1)}%"></div>
928
+ <div class="seg-n" style="width:${(data.kernel.learning / kernelTotal * 100).toFixed(1)}%"></div>
929
+ <div class="seg-d" style="width:${(data.kernel.degraded / kernelTotal * 100).toFixed(1)}%"></div>
930
+ <div class="seg-b" style="width:${(data.kernel.broken / kernelTotal * 100).toFixed(1)}%"></div>
931
+ ` : '<div class="seg-l" style="width:100%"></div>'}
932
+ </div>
933
+ <div class="kern-legend">
934
+ <div class="kern-row"><span class="kern-row-left"><span class="kern-dot l"></span> Learned</span><span class="kern-row-val">${data.kernel.learned}</span></div>
935
+ <div class="kern-row"><span class="kern-row-left"><span class="kern-dot n"></span> Learning</span><span class="kern-row-val">${data.kernel.learning}</span></div>
936
+ <div class="kern-row"><span class="kern-row-left"><span class="kern-dot d"></span> Degraded</span><span class="kern-row-val">${data.kernel.degraded}</span></div>
937
+ <div class="kern-row"><span class="kern-row-left"><span class="kern-dot b"></span> Broken</span><span class="kern-row-val">${data.kernel.broken}</span></div>
938
+ </div>
939
+ </div>
940
+
941
+ <div class="side-card">
942
+ <div class="side-label">Last Activity</div>
943
+ <div class="timeline">
944
+ <div class="tl-item">
945
+ <span class="tl-dot ${data.summary.lastHeartbeat ? 'active' : ''}"></span>
946
+ <span class="tl-label">Heartbeat</span>
947
+ <span class="tl-val">${data.summary.lastHeartbeat ? getRelativeTime(new Date((data.summary.lastHeartbeat.endsWith('Z') ? data.summary.lastHeartbeat : data.summary.lastHeartbeat + 'Z'))) : 'n/a'}</span>
948
+ </div>
949
+ <div class="tl-item">
950
+ <span class="tl-dot ${data.summary.lastDreaming ? 'active' : ''}"></span>
951
+ <span class="tl-label">Dreaming</span>
952
+ <span class="tl-val">${data.summary.lastDreaming ? getRelativeTime(new Date((data.summary.lastDreaming.endsWith('Z') ? data.summary.lastDreaming : data.summary.lastDreaming + 'Z'))) : 'n/a'}</span>
953
+ </div>
954
+ </div>
955
+ </div>
956
+
957
+ <div class="side-card">
958
+ <div class="side-label">System</div>
959
+ <div class="timeline">
960
+ <div class="tl-item">
961
+ <span class="tl-dot active"></span>
962
+ <span class="tl-label">Mode</span>
963
+ <span class="tl-val">edge-native</span>
964
+ </div>
965
+ <div class="tl-item">
966
+ <span class="tl-dot active"></span>
967
+ <span class="tl-label">Runtime</span>
968
+ <span class="tl-val">CF Workers</span>
969
+ </div>
970
+ <div class="tl-item">
971
+ <span class="tl-dot active"></span>
972
+ <span class="tl-label">Memory</span>
973
+ <span class="tl-val">Vectorize</span>
974
+ </div>
975
+ <div class="tl-item">
976
+ <span class="tl-dot active"></span>
977
+ <span class="tl-label">Refresh</span>
978
+ <span class="tl-val">30s</span>
979
+ </div>
980
+ </div>
981
+ </div>
982
+ </aside>
983
+ </div>
984
+
985
+ <footer class="foot">
986
+ <span>AEGIS v${esc(data.version)} &middot; auto-refresh 30s</span>
987
+ <div class="foot-links">
988
+ <a href="/">Landing</a>
989
+ <a href="/health">Health</a>
990
+ <a href="/health?format=json">JSON</a>
991
+ </div>
992
+ </footer>
993
+ </div>
994
+
995
+ <script>
996
+ // Restore filter from URL hash
997
+ var activeFilter = location.hash.replace('#', '') || 'all';
998
+
999
+ function applyFilter(type) {
1000
+ document.querySelectorAll('.feed-filter').forEach(function(b) { b.classList.remove('active'); });
1001
+ var target = document.querySelector('.feed-filter[data-type="' + type + '"]');
1002
+ if (target) target.classList.add('active');
1003
+
1004
+ var visible = 0;
1005
+ document.querySelectorAll('.ev').forEach(function(ev) {
1006
+ if (type === 'all' || ev.classList.contains('ev-' + type)) {
1007
+ ev.style.display = '';
1008
+ visible++;
1009
+ } else {
1010
+ ev.style.display = 'none';
1011
+ }
1012
+ });
1013
+
1014
+ // Show empty state
1015
+ var empty = document.getElementById('feed-empty');
1016
+ if (empty) empty.style.display = visible === 0 ? '' : 'none';
1017
+ }
1018
+
1019
+ // Filter buttons
1020
+ document.querySelectorAll('.feed-filter').forEach(function(btn) {
1021
+ btn.addEventListener('click', function() {
1022
+ var type = btn.getAttribute('data-type');
1023
+ location.hash = type === 'all' ? '' : type;
1024
+ applyFilter(type);
1025
+ });
1026
+ });
1027
+
1028
+ // Apply saved filter on load
1029
+ if (activeFilter !== 'all') applyFilter(activeFilter);
1030
+
1031
+ // Auto-refresh preserving filter (uses hash)
1032
+ setTimeout(function() { location.reload(); }, 30000);
1033
+
1034
+ // ── Neural Pulse Visualization ──
1035
+ (function() {
1036
+ var c = document.getElementById('neural-pulse');
1037
+ if (!c || !c.getContext) return;
1038
+ var ctx = c.getContext('2d');
1039
+ var dpr = window.devicePixelRatio || 1;
1040
+ var W, H;
1041
+
1042
+ function resize() {
1043
+ var rect = c.parentElement.getBoundingClientRect();
1044
+ W = rect.width;
1045
+ H = rect.height;
1046
+ c.width = W * dpr;
1047
+ c.height = H * dpr;
1048
+ ctx.scale(dpr, dpr);
1049
+ }
1050
+ resize();
1051
+ window.addEventListener('resize', resize);
1052
+
1053
+ var tasks = parseInt(c.dataset.tasks) || 0;
1054
+ var errors = parseInt(c.dataset.errors) || 0;
1055
+ var memories = parseInt(c.dataset.memories) || 0;
1056
+ var goals = parseInt(c.dataset.goals) || 0;
1057
+ var learned = parseInt(c.dataset.learned) || 0;
1058
+ var learning = parseInt(c.dataset.learning) || 0;
1059
+
1060
+ // Nodes representing system components
1061
+ var nodes = [
1062
+ { label: 'KERNEL', x: 0.5, y: 0.35, r: 18, color: '#2dd4a0', pulse: learned / Math.max(learned + learning, 1) },
1063
+ { label: 'MEMORY', x: 0.22, y: 0.3, r: 14, color: '#3dd6c8', pulse: Math.min(memories / 500, 1) },
1064
+ { label: 'TASKS', x: 0.78, y: 0.3, r: 14, color: '#2dd4a0', pulse: Math.min(tasks / 30, 1) },
1065
+ { label: 'GOALS', x: 0.35, y: 0.7, r: 12, color: '#7b7bdf', pulse: Math.min(goals / 5, 1) },
1066
+ { label: 'PIPELINE', x: 0.65, y: 0.7, r: 12, color: '#f5a623', pulse: 0.5 },
1067
+ { label: 'DREAMING', x: 0.12, y: 0.6, r: 10, color: '#a78bfa', pulse: 0.6 },
1068
+ { label: 'ARGUS', x: 0.88, y: 0.6, r: 10, color: '#ef4444', pulse: errors > 0 ? 1 : 0.3 },
1069
+ { label: 'DISPATCH', x: 0.5, y: 0.85, r: 10, color: '#8b8bff', pulse: 0.7 },
1070
+ ];
1071
+
1072
+ // Connections between nodes (index pairs)
1073
+ var edges = [
1074
+ [0,1],[0,2],[0,3],[0,4],[1,3],[1,5],[2,4],[2,6],[3,7],[4,7],[5,3],[6,4]
1075
+ ];
1076
+
1077
+ // Particles traveling along edges
1078
+ var particles = [];
1079
+ for (var i = 0; i < 12; i++) {
1080
+ var ei = i % edges.length;
1081
+ particles.push({ edge: ei, t: Math.random(), speed: 0.002 + Math.random() * 0.004 });
1082
+ }
1083
+
1084
+ var frame = 0;
1085
+
1086
+ function draw() {
1087
+ frame++;
1088
+ ctx.clearRect(0, 0, W, H);
1089
+
1090
+ // Resolve node positions
1091
+ var ns = nodes.map(function(n) {
1092
+ return { x: n.x * W, y: n.y * H, r: n.r, color: n.color, pulse: n.pulse, label: n.label };
1093
+ });
1094
+
1095
+ // Draw edges
1096
+ edges.forEach(function(e) {
1097
+ var a = ns[e[0]], b = ns[e[1]];
1098
+ ctx.beginPath();
1099
+ ctx.moveTo(a.x, a.y);
1100
+ ctx.lineTo(b.x, b.y);
1101
+ ctx.strokeStyle = 'rgba(123,123,223,0.07)';
1102
+ ctx.lineWidth = 1;
1103
+ ctx.stroke();
1104
+ });
1105
+
1106
+ // Draw particles
1107
+ particles.forEach(function(p) {
1108
+ p.t += p.speed;
1109
+ if (p.t > 1) { p.t -= 1; p.edge = Math.floor(Math.random() * edges.length); }
1110
+ var e = edges[p.edge];
1111
+ var a = ns[e[0]], b = ns[e[1]];
1112
+ var px = a.x + (b.x - a.x) * p.t;
1113
+ var py = a.y + (b.y - a.y) * p.t;
1114
+ ctx.beginPath();
1115
+ ctx.arc(px, py, 1.5, 0, Math.PI * 2);
1116
+ ctx.fillStyle = 'rgba(61,214,200,0.4)';
1117
+ ctx.fill();
1118
+ });
1119
+
1120
+ // Draw nodes
1121
+ ns.forEach(function(n) {
1122
+ var glowSize = n.r + 8 + Math.sin(frame * 0.03 * (0.5 + n.pulse)) * 4 * n.pulse;
1123
+
1124
+ // Outer glow
1125
+ var grad = ctx.createRadialGradient(n.x, n.y, n.r * 0.5, n.x, n.y, glowSize);
1126
+ grad.addColorStop(0, n.color + '18');
1127
+ grad.addColorStop(1, 'transparent');
1128
+ ctx.beginPath();
1129
+ ctx.arc(n.x, n.y, glowSize, 0, Math.PI * 2);
1130
+ ctx.fillStyle = grad;
1131
+ ctx.fill();
1132
+
1133
+ // Core
1134
+ ctx.beginPath();
1135
+ ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
1136
+ ctx.fillStyle = n.color + '12';
1137
+ ctx.strokeStyle = n.color + '40';
1138
+ ctx.lineWidth = 1;
1139
+ ctx.fill();
1140
+ ctx.stroke();
1141
+
1142
+ // Center dot
1143
+ ctx.beginPath();
1144
+ ctx.arc(n.x, n.y, 2.5, 0, Math.PI * 2);
1145
+ ctx.fillStyle = n.color;
1146
+ ctx.fill();
1147
+
1148
+ // Label
1149
+ ctx.font = '500 8px "JetBrains Mono", monospace';
1150
+ ctx.fillStyle = n.color + '90';
1151
+ ctx.textAlign = 'center';
1152
+ ctx.fillText(n.label, n.x, n.y + n.r + 12);
1153
+ });
1154
+
1155
+ requestAnimationFrame(draw);
1156
+ }
1157
+ draw();
1158
+ })();
1159
+ </script>
1160
+ </body>
1161
+ </html>`;
1162
+ }
1163
+
1164
+ function getRelativeTime(date: Date): string {
1165
+ const now = Date.now();
1166
+ const diff = now - date.getTime();
1167
+ if (diff < 0) return 'just now';
1168
+ const mins = Math.floor(diff / 60000);
1169
+ if (mins < 1) return 'just now';
1170
+ if (mins < 60) return mins + 'm ago';
1171
+ const hours = Math.floor(mins / 60);
1172
+ if (hours < 24) return hours + 'h ago';
1173
+ const days = Math.floor(hours / 24);
1174
+ return days + 'd ago';
1175
+ }
1176
+
1177
+ function esc(s: string): string {
1178
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1179
+ }