total-recall-brain 3.1.2 → 3.2.1

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 (60) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +806 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
@@ -0,0 +1,707 @@
1
+ import React, { Fragment, useState, useRef } from 'react'
2
+ import { renderMarkdown, extractSources } from './MarkdownUtils'
3
+ import type { MemoryNode } from '../types'
4
+ import { patchResearch, deleteResearch, shareToApi } from '../api'
5
+
6
+ export interface ResearchItem {
7
+ id: string
8
+ topic: string
9
+ status: 'pending' | 'in_progress' | 'done' | 'failed' | 'paused'
10
+ priority: 'low' | 'medium' | 'high' | 'critical'
11
+ notes: string | null
12
+ node_slug?: string | null
13
+ research_phase?: string
14
+ created_at: string
15
+ updated_at: string
16
+ }
17
+
18
+ interface ResearchAgendaTabProps {
19
+ researchItems: ResearchItem[]
20
+ loading: boolean
21
+ showResearchForm: boolean
22
+ setShowResearchForm: (v: boolean) => void
23
+ researchTopic: string
24
+ setResearchTopic: (v: string) => void
25
+ researchPriority: 'low' | 'medium' | 'high' | 'critical'
26
+ setResearchPriority: (v: 'low' | 'medium' | 'high' | 'critical') => void
27
+ researchNotes: string
28
+ setResearchNotes: (v: string) => void
29
+ researchSubmitting: boolean
30
+ handleCreateResearch: (e: React.FormEvent) => void
31
+ expandedResearchId: string | null
32
+ handleToggleExpand: (item: ResearchItem) => void
33
+ loadedDiscoveries: Record<string, MemoryNode | null>
34
+ loadingNodeSlugs: Record<string, boolean>
35
+ refreshResearch?: () => void
36
+ }
37
+
38
+ export default function ResearchAgendaTab(props: ResearchAgendaTabProps) {
39
+ const {
40
+ researchItems,
41
+ loading,
42
+ showResearchForm,
43
+ setShowResearchForm,
44
+ researchTopic,
45
+ setResearchTopic,
46
+ researchPriority,
47
+ setResearchPriority,
48
+ researchNotes,
49
+ setResearchNotes,
50
+ researchSubmitting,
51
+ handleCreateResearch,
52
+ expandedResearchId,
53
+ handleToggleExpand,
54
+ loadedDiscoveries,
55
+ loadingNodeSlugs,
56
+ refreshResearch,
57
+ } = props
58
+
59
+ const [steerTarget, setSteerTarget] = useState<string | null>(null)
60
+ const steerRef = useRef<HTMLTextAreaElement>(null)
61
+ const [expandedReports, setExpandedReports] = useState<Record<string, boolean>>({})
62
+
63
+ const handlePatch = async (id: string, updates: Record<string, unknown>) => {
64
+ try {
65
+ await patchResearch(id, updates)
66
+ refreshResearch?.()
67
+ } catch (err) {
68
+ console.error('Failed to update research:', err)
69
+ }
70
+ }
71
+
72
+ const handleDelete = async (id: string) => {
73
+ if (!confirm('Cancel this research project?')) return
74
+ try {
75
+ await deleteResearch(id)
76
+ refreshResearch?.()
77
+ } catch (err) {
78
+ console.error('Failed to delete research:', err)
79
+ }
80
+ }
81
+
82
+ const handleSteerSubmit = async () => {
83
+ if (!steerTarget || !steerRef.current) return
84
+ const direction = steerRef.current.value.trim()
85
+ if (!direction) return
86
+ try {
87
+ await patchResearch(steerTarget, { notes: direction })
88
+ setSteerTarget(null)
89
+ refreshResearch?.()
90
+ } catch (err) {
91
+ console.error('Failed to steer research:', err)
92
+ }
93
+ }
94
+
95
+
96
+ return (
97
+ <div>
98
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
99
+ <h3 style={{ margin: 0 }}>Vector Research agenda</h3>
100
+ <button
101
+ className="btn btn-sm btn-ghost"
102
+ onClick={() => setShowResearchForm(!showResearchForm)}
103
+ style={{ borderColor: 'var(--border)' }}
104
+ >
105
+ {showResearchForm ? 'Cancel' : '+ Queue Research Topic'}
106
+ </button>
107
+ </div>
108
+
109
+ {/* Collapsible Research Form */}
110
+ {showResearchForm && (
111
+ <form onSubmit={handleCreateResearch} style={{
112
+ background: 'var(--bg-secondary)',
113
+ border: '1px solid var(--border)',
114
+ borderRadius: 12,
115
+ padding: 20,
116
+ marginBottom: 20,
117
+ display: 'flex',
118
+ flexDirection: 'column',
119
+ gap: 12
120
+ }}>
121
+ <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 16 }}>
122
+ <label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
123
+ Research Query / Topic
124
+ <input
125
+ type="text"
126
+ required
127
+ placeholder="e.g. Bun vs Node.js HTTP clustering performance benchmarks"
128
+ value={researchTopic}
129
+ onChange={e => setResearchTopic(e.target.value)}
130
+ style={{
131
+ padding: '8px 10px',
132
+ borderRadius: 8,
133
+ background: 'var(--bg-primary)',
134
+ color: 'var(--text-primary)',
135
+ border: '1px solid var(--border)',
136
+ outline: 'none'
137
+ }}
138
+ />
139
+ </label>
140
+ <label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
141
+ Agenda Priority
142
+ <select
143
+ value={researchPriority}
144
+ onChange={e => setResearchPriority(e.target.value as 'low' | 'medium' | 'high' | 'critical')}
145
+ style={{
146
+ padding: '8px 10px',
147
+ borderRadius: 8,
148
+ background: 'var(--bg-primary)',
149
+ color: 'var(--text-primary)',
150
+ border: '1px solid var(--border)',
151
+ outline: 'none'
152
+ }}
153
+ >
154
+ <option value="low">Low</option>
155
+ <option value="medium">Medium</option>
156
+ <option value="high">High</option>
157
+ <option value="critical">Critical</option>
158
+ </select>
159
+ </label>
160
+ </div>
161
+ <label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
162
+ Target Gaps / Reference Notes
163
+ <textarea
164
+ placeholder="Identify specific APIs, libraries or cutoff dates..."
165
+ value={researchNotes}
166
+ onChange={e => setResearchNotes(e.target.value)}
167
+ style={{
168
+ padding: '10px',
169
+ borderRadius: 8,
170
+ background: 'var(--bg-primary)',
171
+ color: 'var(--text-primary)',
172
+ border: '1px solid var(--border)',
173
+ outline: 'none',
174
+ minHeight: 80,
175
+ resize: 'vertical'
176
+ }}
177
+ />
178
+ </label>
179
+ <button
180
+ type="submit"
181
+ className="btn btn-primary"
182
+ disabled={researchSubmitting || !researchTopic.trim()}
183
+ style={{ alignSelf: 'flex-end', minWidth: 120 }}
184
+ >
185
+ {researchSubmitting ? 'Queueing...' : 'Add to Agenda'}
186
+ </button>
187
+ </form>
188
+ )}
189
+
190
+ {/* Research Agenda Table */}
191
+ <div style={{ overflowX: 'auto', border: '1px solid var(--border)', borderRadius: 12, background: 'var(--bg-secondary)' }}>
192
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
193
+ <thead>
194
+ <tr style={{ borderBottom: '1px solid var(--border)', color: 'var(--text-secondary)', textAlign: 'left', background: 'rgba(255, 255, 255, 0.02)' }}>
195
+ <th style={{ padding: '12px 16px', width: 40, textAlign: 'center' }}></th>
196
+ <th style={{ padding: '12px 16px' }}>Research Topic</th>
197
+ <th style={{ padding: '12px 16px', width: 100 }}>Priority</th>
198
+ <th style={{ padding: '12px 16px', width: 120 }}>Status</th>
199
+ <th style={{ padding: '12px 16px', width: 160 }}>Updated At</th>
200
+ </tr>
201
+ </thead>
202
+ <tbody>
203
+ {loading && researchItems.length === 0 ? (
204
+ <tr>
205
+ <td colSpan={5} style={{ textAlign: 'center', padding: 24, color: 'var(--text-tertiary)' }}>Loading agenda...</td>
206
+ </tr>
207
+ ) : researchItems.length === 0 ? (
208
+ <tr>
209
+ <td colSpan={5} style={{ textAlign: 'center', padding: 40, color: 'var(--text-tertiary)' }}>
210
+ No active research topics queued.
211
+ </td>
212
+ </tr>
213
+ ) : (
214
+ researchItems.map(item => {
215
+ const isExpanded = expandedResearchId === item.id;
216
+ const node = item.node_slug ? loadedDiscoveries[item.node_slug] : null;
217
+ const isNodeLoading = item.node_slug ? loadingNodeSlugs[item.node_slug] : false;
218
+
219
+ return (
220
+ <Fragment key={item.id}>
221
+ <tr
222
+ onClick={() => handleToggleExpand(item)}
223
+ style={{
224
+ borderBottom: isExpanded ? 'none' : '1px solid var(--border)',
225
+ cursor: 'pointer',
226
+ background: isExpanded ? 'rgba(255, 255, 255, 0.015)' : 'transparent',
227
+ transition: 'background 0.2s ease',
228
+ }}
229
+ >
230
+ <td style={{ padding: '14px 16px', textAlign: 'center', color: 'var(--text-tertiary)' }}>
231
+ <span style={{
232
+ display: 'inline-block',
233
+ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
234
+ transition: 'transform 0.2s',
235
+ fontSize: 10
236
+ }}>
237
+ ▶
238
+ </span>
239
+ </td>
240
+ <td style={{ padding: '14px 16px' }}>
241
+ <div style={{ fontWeight: 600, color: 'var(--text-primary)' }}>{item.topic}</div>
242
+ {item.notes && (
243
+ <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 }}>
244
+ {item.notes}
245
+ </div>
246
+ )}
247
+ </td>
248
+ <td style={{ padding: '14px 16px' }}>
249
+ <span className={`badge ${
250
+ item.priority === 'critical' ? 'badge-error' :
251
+ item.priority === 'high' ? 'badge-accent' :
252
+ item.priority === 'medium' ? 'badge-warning' :
253
+ 'badge-ghost'
254
+ }`} style={{ fontSize: 11, textTransform: 'capitalize' }}>
255
+ {item.priority}
256
+ </span>
257
+ </td>
258
+ <td style={{ padding: '14px 16px' }}>
259
+ <span className={`badge ${
260
+ (item.node_slug && (item.status === 'pending' || item.status === 'in_progress')) || item.status === 'done' ? 'badge-success' :
261
+ item.status === 'in_progress' ? 'badge-accent' :
262
+ item.status === 'failed' ? 'badge-error' :
263
+ 'badge-warning'
264
+ }`} style={{ textTransform: 'capitalize', fontSize: 11 }}>
265
+ {item.node_slug && (item.status === 'pending' || item.status === 'in_progress') ? 'Researched' : item.status.replace('_', ' ')}
266
+ </span>
267
+ </td>
268
+ <td style={{ padding: '14px 16px', color: 'var(--text-tertiary)', fontSize: 12 }}>
269
+ {new Date(item.updated_at).toLocaleString([], { dateStyle: 'short', timeStyle: 'short' })}
270
+ </td>
271
+ </tr>
272
+ {isExpanded && (
273
+ <tr style={{ borderBottom: '1px solid var(--border)', background: 'rgba(255, 255, 255, 0.015)' }}>
274
+ <td colSpan={5} style={{ padding: '0 24px 20px 24px' }}>
275
+ <div style={{
276
+ border: '1px solid rgba(255, 255, 255, 0.06)',
277
+ borderRadius: 12,
278
+ background: 'rgba(10, 10, 15, 0.4)',
279
+ padding: 20,
280
+ boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 4px 20px rgba(0, 0, 0, 0.3)',
281
+ }}>
282
+ <style dangerouslySetInnerHTML={{__html: `
283
+ @keyframes pulse {
284
+ 0% { transform: scale(0.95); opacity: 0.5; }
285
+ 50% { transform: scale(1.05); opacity: 1; }
286
+ 100% { transform: scale(0.95); opacity: 0.5; }
287
+ }
288
+ `}} />
289
+
290
+ {/* Cognitive Stepper */}
291
+ <div style={{ marginBottom: 20 }}>
292
+ <div style={{ fontSize: 11, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, marginBottom: 12 }}>
293
+ Cognitive Research Engine Progress
294
+ </div>
295
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
296
+ {[
297
+ {
298
+ title: 'Data Acquisition',
299
+ phase: 'acquisition',
300
+ icon: '🔍',
301
+ desc: 'Deep multi-source crawling & LLM synthesis'
302
+ },
303
+ {
304
+ title: 'Cognitive Deliberation',
305
+ phase: 'deliberation',
306
+ icon: '🧠',
307
+ desc: 'System 2 context evaluation & insights'
308
+ },
309
+ {
310
+ title: 'Clarity Auditing',
311
+ phase: 'improvement',
312
+ icon: '✨',
313
+ desc: 'Markdown polish & premium layout restructuring'
314
+ },
315
+ {
316
+ title: 'Source Monitoring',
317
+ phase: 'monitoring',
318
+ icon: '📡',
319
+ desc: 'Ongoing newsletter, RSS & release tracking'
320
+ },
321
+ {
322
+ title: 'Tangent Spawning',
323
+ phase: 'expansion',
324
+ icon: '🚀',
325
+ desc: 'Autonomously brainstorming & enqueuing tangents'
326
+ }
327
+ ].map((step, idx) => {
328
+ const phases = ['acquisition', 'deliberation', 'improvement', 'monitoring', 'expansion']
329
+ const activePhase = item.research_phase || 'acquisition'
330
+ const activeIndex = phases.indexOf(activePhase)
331
+
332
+ let state: 'completed' | 'active' | 'waiting' | 'failed' | 'upcoming' = 'upcoming'
333
+ if (idx < activeIndex) {
334
+ state = 'completed'
335
+ } else if (idx === activeIndex) {
336
+ if (item.status === 'in_progress') state = 'active'
337
+ else if (item.status === 'pending') state = 'waiting'
338
+ else if (item.status === 'failed') state = 'failed'
339
+ else state = 'waiting'
340
+ }
341
+
342
+ let borderColor = 'rgba(255, 255, 255, 0.04)'
343
+ let bgColor = 'rgba(255, 255, 255, 0.01)'
344
+ let titleColor = 'var(--text-secondary)'
345
+ let badgeBg = 'rgba(255, 255, 255, 0.02)'
346
+ let statusText = 'Locked'
347
+ let statusColor = 'var(--text-tertiary)'
348
+ let isPulse = false
349
+
350
+ if (state === 'completed') {
351
+ borderColor = 'rgba(46, 204, 113, 0.25)'
352
+ bgColor = 'rgba(46, 204, 113, 0.02)'
353
+ titleColor = '#fff'
354
+ badgeBg = 'rgba(46, 204, 113, 0.12)'
355
+ statusText = '✓ Completed'
356
+ statusColor = '#2ecc71'
357
+ } else if (state === 'active') {
358
+ borderColor = 'rgba(108, 92, 231, 0.45)'
359
+ bgColor = 'rgba(108, 92, 231, 0.05)'
360
+ titleColor = '#fff'
361
+ badgeBg = 'rgba(108, 92, 231, 0.18)'
362
+ statusText = '⚡ Running'
363
+ statusColor = 'var(--accent)'
364
+ isPulse = true
365
+ } else if (state === 'waiting') {
366
+ borderColor = 'rgba(241, 196, 15, 0.25)'
367
+ bgColor = 'rgba(241, 196, 15, 0.02)'
368
+ titleColor = 'var(--text-primary)'
369
+ badgeBg = 'rgba(241, 196, 15, 0.1)'
370
+ statusText = '⏳ Queued'
371
+ statusColor = '#f1c40f'
372
+ } else if (state === 'failed') {
373
+ borderColor = 'rgba(231, 76, 60, 0.25)'
374
+ bgColor = 'rgba(231, 76, 60, 0.02)'
375
+ titleColor = 'var(--text-primary)'
376
+ badgeBg = 'rgba(231, 76, 60, 0.1)'
377
+ statusText = '⚠️ Failed'
378
+ statusColor = '#e74c3c'
379
+ }
380
+
381
+ return (
382
+ <div key={idx} style={{
383
+ border: `1px solid ${borderColor}`,
384
+ background: bgColor,
385
+ borderRadius: 10,
386
+ padding: 12,
387
+ display: 'flex',
388
+ flexDirection: 'column',
389
+ gap: 6,
390
+ transition: 'all 0.3s ease',
391
+ position: 'relative'
392
+ }}>
393
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
394
+ <span style={{
395
+ display: 'inline-flex',
396
+ alignItems: 'center',
397
+ justifyContent: 'center',
398
+ width: 24,
399
+ height: 24,
400
+ borderRadius: '50%',
401
+ background: badgeBg,
402
+ fontSize: 12
403
+ }}>
404
+ {step.icon}
405
+ </span>
406
+ <span style={{
407
+ fontSize: 10,
408
+ fontWeight: 700,
409
+ color: statusColor,
410
+ display: 'inline-flex',
411
+ alignItems: 'center',
412
+ gap: 4
413
+ }}>
414
+ {isPulse && (
415
+ <span style={{
416
+ width: 6,
417
+ height: 6,
418
+ borderRadius: '50%',
419
+ background: 'var(--accent)',
420
+ animation: 'pulse 1.5s infinite ease-in-out'
421
+ }} />
422
+ )}
423
+ {statusText}
424
+ </span>
425
+ </div>
426
+ <div style={{ fontSize: 12, fontWeight: 600, color: titleColor }}>
427
+ {idx + 1}. {step.title}
428
+ </div>
429
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', lineHeight: 1.3 }}>
430
+ {step.desc}
431
+ </div>
432
+ </div>
433
+ )
434
+ })}
435
+ </div>
436
+ </div>
437
+
438
+ <hr style={{ border: 'none', borderTop: '1px solid rgba(255, 255, 255, 0.06)', margin: '20px 0' }} />
439
+
440
+ {/* Lifecycle Control Buttons */}
441
+ <div style={{ display: 'flex', gap: '8px', padding: '0 0 16px', flexWrap: 'wrap' }}>
442
+ {item.status === 'in_progress' && (
443
+ <button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'paused' }) }}>⏸ Pause</button>
444
+ )}
445
+ {(item.status === 'paused' || item.status === 'failed') && (
446
+ <button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'pending' }) }}>▶️ Resume</button>
447
+ )}
448
+ {item.status === 'done' && (
449
+ <button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'pending', research_phase: 'acquisition' }) }}>🔄 Re-run</button>
450
+ )}
451
+ <button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); setSteerTarget(item.id) }}>🎯 Steer</button>
452
+ {item.status !== 'done' && (
453
+ <button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'done' }) }}>✅ Conclude</button>
454
+ )}
455
+ <button className="btn btn-sm" style={{ borderColor: 'var(--error)', color: 'var(--error)' }} onClick={(e) => { e.stopPropagation(); handleDelete(item.id) }}>❌ Cancel</button>
456
+ </div>
457
+
458
+ {item.node_slug ? (
459
+ <>
460
+ {isNodeLoading ? (
461
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, color: 'var(--text-secondary)' }}>
462
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite' }}>
463
+ <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
464
+ </svg>
465
+ <span>Reading memory node and cataloging discoveries...</span>
466
+ </div>
467
+ ) : node ? (
468
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
469
+ {item.status === 'in_progress' && (
470
+ <div style={{
471
+ display: 'flex',
472
+ alignItems: 'center',
473
+ gap: 8,
474
+ background: 'rgba(108, 92, 231, 0.1)',
475
+ border: '1px solid rgba(108, 92, 231, 0.2)',
476
+ borderRadius: 8,
477
+ padding: '8px 12px',
478
+ fontSize: 12,
479
+ color: 'var(--accent)',
480
+ marginBottom: 4
481
+ }}>
482
+ <span className="pulse" style={{ background: 'var(--accent)', width: 8, height: 8, borderRadius: '50%', display: 'inline-block' }} />
483
+ <span>Fact-seeker agents are currently updating this topic in the background. Previous discoveries are displayed below.</span>
484
+ </div>
485
+ )}
486
+ {item.status === 'pending' && (
487
+ <div style={{
488
+ display: 'flex',
489
+ alignItems: 'center',
490
+ gap: 8,
491
+ background: 'rgba(241, 196, 15, 0.1)',
492
+ border: '1px solid rgba(241, 196, 15, 0.2)',
493
+ borderRadius: 8,
494
+ padding: '8px 12px',
495
+ fontSize: 12,
496
+ color: 'var(--warning)',
497
+ marginBottom: 4
498
+ }}>
499
+ <span>⏳</span>
500
+ <span>Scheduled for automatic refresh. Previous discoveries are displayed below.</span>
501
+ </div>
502
+ )}
503
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 12, borderBottom: '1px solid rgba(255, 255, 255, 0.06)', paddingBottom: 12 }}>
504
+ <div>
505
+ <h4 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: '#fff' }}>{node.title}</h4>
506
+ <div style={{ display: 'flex', gap: 8, marginTop: 6, alignItems: 'center' }}>
507
+ <span className="badge badge-accent" style={{ fontSize: 10, padding: '2px 8px' }}>{node.category}</span>
508
+ {node.modality && <span className="badge" style={{ fontSize: 10, padding: '2px 8px', background: 'rgba(255,255,255,0.06)', color: 'var(--text-secondary)' }}>{node.modality}</span>}
509
+ </div>
510
+ </div>
511
+
512
+ <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
513
+ {node.importance !== undefined && (
514
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4, background: 'rgba(255, 255, 255, 0.03)', padding: '4px 8px', borderRadius: 6, fontSize: 11, color: 'var(--text-secondary)', border: '1px solid rgba(255,255,255,0.04)' }}>
515
+ <span>⭐ Importance:</span>
516
+ <span style={{ fontWeight: 600, color: '#fff' }}>{node.importance}/5</span>
517
+ </div>
518
+ )}
519
+ {node.confidence !== undefined && (
520
+ <div style={{
521
+ display: 'flex',
522
+ alignItems: 'center',
523
+ gap: 4,
524
+ background: node.confidence >= 0.9 ? 'rgba(0, 206, 201, 0.08)' : 'rgba(108, 92, 231, 0.08)',
525
+ border: `1px solid ${node.confidence >= 0.9 ? 'rgba(0, 206, 201, 0.3)' : 'rgba(108, 92, 231, 0.3)'}`,
526
+ borderRadius: 6,
527
+ padding: '4px 8px',
528
+ fontSize: 11,
529
+ color: node.confidence >= 0.9 ? 'var(--success)' : 'var(--accent)'
530
+ }}>
531
+ <span>✓ Confidence:</span>
532
+ <span style={{ fontWeight: 600 }}>{(node.confidence * 100).toFixed(0)}%</span>
533
+ </div>
534
+ )}
535
+ </div>
536
+ </div>
537
+
538
+ <div style={{ position: 'relative' }}>
539
+ <div style={{
540
+ maxHeight: expandedReports[item.id] ? 'none' : 500,
541
+ overflow: 'hidden',
542
+ paddingRight: 8,
543
+ transition: 'max-height 0.3s ease',
544
+ }}>
545
+ <div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
546
+ {renderMarkdown(node.content || node.body || '')}
547
+ </div>
548
+ </div>
549
+ {!expandedReports[item.id] && (node.content || node.body || '').length > 800 && (
550
+ <div style={{
551
+ position: 'absolute',
552
+ bottom: 0,
553
+ left: 0,
554
+ right: 0,
555
+ height: 80,
556
+ background: 'linear-gradient(transparent, rgba(10, 10, 15, 0.95))',
557
+ pointerEvents: 'none',
558
+ }} />
559
+ )}
560
+ </div>
561
+ {(node.content || node.body || '').length > 800 && (
562
+ <button
563
+ className="btn btn-sm btn-ghost"
564
+ onClick={(e) => {
565
+ e.stopPropagation()
566
+ setExpandedReports(prev => ({ ...prev, [item.id]: !prev[item.id] }))
567
+ }}
568
+ style={{ alignSelf: 'flex-start', marginTop: 8, fontSize: 12, color: 'var(--accent)' }}
569
+ >
570
+ {expandedReports[item.id] ? '▲ Show less' : '▼ Show more'}
571
+ </button>
572
+ )}
573
+
574
+ {extractSources(node.content || node.body).length > 0 && (
575
+ <div style={{ borderTop: '1px solid rgba(255, 255, 255, 0.06)', paddingTop: 12 }}>
576
+ <div style={{ fontSize: 11, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 600, marginBottom: 8 }}>Sources & Citations</div>
577
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
578
+ {extractSources(node.content || node.body).map((src, idx) => {
579
+ const domain = (() => { try { return new URL(src.url).hostname.replace(/^www\./, '') } catch { return src.url } })()
580
+ return (
581
+ <div
582
+ key={idx}
583
+ style={{
584
+ display: 'flex',
585
+ alignItems: 'center',
586
+ gap: 10,
587
+ background: 'rgba(108, 92, 231, 0.06)',
588
+ border: '1px solid rgba(108, 92, 231, 0.2)',
589
+ borderRadius: 10,
590
+ padding: '8px 14px',
591
+ fontSize: 12,
592
+ transition: 'all 0.2s',
593
+ }}
594
+ >
595
+ <img
596
+ src={`https://www.google.com/s2/favicons?domain=${domain}&sz=16`}
597
+ width={16}
598
+ height={16}
599
+ alt=""
600
+ style={{ borderRadius: 2, flexShrink: 0 }}
601
+ />
602
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 2, flex: 1, minWidth: 0 }}>
603
+ <span style={{ fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{src.text}</span>
604
+ <span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{domain}</span>
605
+ </div>
606
+ <a
607
+ href={src.url}
608
+ target="_blank"
609
+ rel="noopener noreferrer"
610
+ className="btn btn-sm"
611
+ style={{ fontSize: 11, padding: '3px 10px', textDecoration: 'none', flexShrink: 0 }}
612
+ onClick={(e) => e.stopPropagation()}
613
+ >
614
+ Open
615
+ </a>
616
+ <button
617
+ className="btn btn-sm"
618
+ style={{ fontSize: 11, padding: '3px 10px', flexShrink: 0 }}
619
+ onClick={(e) => {
620
+ e.stopPropagation()
621
+ shareToApi({ url: src.url, action: 'research', title: src.text }).catch(console.error)
622
+ }}
623
+ >
624
+ 🔬 Research deeper
625
+ </button>
626
+ </div>
627
+ )
628
+ })}
629
+ </div>
630
+ </div>
631
+ )}
632
+ </div>
633
+ ) : (
634
+ <div style={{ color: 'var(--text-tertiary)', fontSize: 13 }}>
635
+ No memory synthesis node found for slug: <code>{item.node_slug}</code>
636
+ </div>
637
+ )}
638
+ </>
639
+ ) : (
640
+ <>
641
+ {item.status === 'pending' && (
642
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
643
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--warning)', fontWeight: 600, fontSize: 14 }}>
644
+ <span>⏳</span> Queued for Ingestion
645
+ </div>
646
+ <p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
647
+ This research topic is enqueued in the Cognitive Scheduler. The background fact-seeker daemon will systematically synthesize findings and catalog discoveries when processing background gaps.
648
+ </p>
649
+ </div>
650
+ )}
651
+
652
+ {item.status === 'in_progress' && (
653
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
654
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--accent)', fontWeight: 600, fontSize: 14 }}>
655
+ <span className="pulse" style={{ background: 'var(--accent)' }} />
656
+ Synthesizing Deep Discoveries...
657
+ </div>
658
+ <p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
659
+ Fact-seeker agents are actively searching vector indexes, crawling target APIs, and structuring new domain schemas. Results will be verified by the System 2 consensus writer.
660
+ </p>
661
+ </div>
662
+ )}
663
+
664
+ {item.status === 'failed' && (
665
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
666
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--error)', fontWeight: 600, fontSize: 14 }}>
667
+ <span>⚠️</span> Ingestion Boundary Encountered
668
+ </div>
669
+ <p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
670
+ The research pipeline failed to produce high-confidence consensus conclusions or hit an instruction boundary. Check live cognitive console logs for execution specifics.
671
+ </p>
672
+ </div>
673
+ )}
674
+ </>
675
+ )}
676
+ </div>
677
+ </td>
678
+ </tr>
679
+ )}
680
+ </Fragment>
681
+ )
682
+ })
683
+ )}
684
+ </tbody>
685
+ </table>
686
+ </div>
687
+
688
+ {/* Steer Modal Overlay */}
689
+ {steerTarget && (
690
+ <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
691
+ <div style={{ background: 'var(--bg-secondary, #1e1e2e)', borderRadius: '12px', padding: '24px', maxWidth: '500px', width: '90%', border: '1px solid var(--border)' }}>
692
+ <h3 style={{ margin: '0 0 12px' }}>🎯 Steer Research</h3>
693
+ <textarea
694
+ ref={steerRef}
695
+ placeholder="Add direction... e.g. 'Focus more on pricing comparisons'"
696
+ style={{ width: '100%', minHeight: '100px', borderRadius: '8px', padding: '12px', background: 'var(--bg-primary, #11111b)', color: 'inherit', border: '1px solid var(--border, #333)', outline: 'none', resize: 'vertical', boxSizing: 'border-box' }}
697
+ />
698
+ <div style={{ display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'flex-end' }}>
699
+ <button className="btn btn-sm btn-ghost" onClick={() => setSteerTarget(null)}>Cancel</button>
700
+ <button className="btn btn-sm btn-primary" onClick={handleSteerSubmit}>Submit</button>
701
+ </div>
702
+ </div>
703
+ </div>
704
+ )}
705
+ </div>
706
+ )
707
+ }