total-recall-brain 3.13.0 → 3.13.2

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 (152) hide show
  1. package/README.md +91 -108
  2. package/bin/antigravity.mjs +81 -52
  3. package/bin/total-recall.mjs +32 -57
  4. package/extension/background.js +1 -1
  5. package/extension/options/options.js +9 -3
  6. package/extension/sidepanel/sidepanel.js +0 -2
  7. package/frontend/index.html +4 -3
  8. package/frontend/package.json +1 -0
  9. package/frontend/src/App.tsx +109 -152
  10. package/frontend/src/api.ts +605 -23
  11. package/frontend/src/components/BrainSelector.tsx +211 -199
  12. package/frontend/src/components/DocumentEditorModal.tsx +57 -0
  13. package/frontend/src/components/DocumentTable.tsx +47 -0
  14. package/frontend/src/components/Graph3D.tsx +120 -71
  15. package/frontend/src/components/UsageChart.tsx +3 -3
  16. package/frontend/src/components/brand/BrandMark.tsx +95 -0
  17. package/frontend/src/index.css +286 -86
  18. package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
  19. package/frontend/src/pages/ChatPage.tsx +103 -45
  20. package/frontend/src/pages/DesignDocsPage.tsx +5 -5
  21. package/frontend/src/pages/FilesPage.tsx +10 -6
  22. package/frontend/src/pages/GraphPage.tsx +5 -3
  23. package/frontend/src/pages/HelpPage.tsx +2 -2
  24. package/frontend/src/pages/InboxPage.tsx +82 -0
  25. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  26. package/frontend/src/pages/IntegrationsPage.tsx +21 -10
  27. package/frontend/src/pages/LoginPage.tsx +95 -77
  28. package/frontend/src/pages/MemoryPage.tsx +31 -8
  29. package/frontend/src/pages/OnboardingPage.tsx +690 -0
  30. package/frontend/src/pages/OpenWikiPage.tsx +412 -323
  31. package/frontend/src/pages/SettingsPage.tsx +825 -922
  32. package/frontend/src/pages/SkillsPage.tsx +272 -147
  33. package/frontend/src/pages/UsagePage.tsx +23 -31
  34. package/frontend/src/pages/VaultPage.tsx +223 -0
  35. package/frontend/src/types.ts +13 -0
  36. package/package.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
  38. package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
  39. package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
  40. package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
  41. package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
  42. package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
  43. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
  44. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
  46. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
  47. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
  48. package/src/cli/brain.mjs +63 -5
  49. package/src/cli/connect.mjs +11 -19
  50. package/src/cli/daemon.mjs +5 -4
  51. package/src/cli/deploy-ui.mjs +8 -3
  52. package/src/cli/deploy.mjs +84 -27
  53. package/src/cli/ingest/index.mjs +1 -1
  54. package/src/cli/init.mjs +63 -34
  55. package/src/cli/remember.mjs +3 -3
  56. package/src/cli/secret.mjs +812 -0
  57. package/src/cli/setup.mjs +34 -7
  58. package/src/cli/share.mjs +2 -2
  59. package/src/cli/skill.mjs +396 -35
  60. package/src/cli/sync.mjs +4 -4
  61. package/src/cli/task.mjs +287 -0
  62. package/src/core/clarity-rewriter.mjs +5 -5
  63. package/src/core/config.mjs +44 -2
  64. package/src/core/conflict-detector.mjs +5 -5
  65. package/src/core/crons.mjs +54 -0
  66. package/src/core/daemon-loop.mjs +87 -235
  67. package/src/core/dream.mjs +170 -10
  68. package/src/core/env-import.mjs +456 -0
  69. package/src/core/fact-seeker.mjs +14 -14
  70. package/src/core/import-rules.mjs +20 -7
  71. package/src/core/inference-engine.mjs +3 -3
  72. package/src/core/migrate.mjs +14 -8
  73. package/src/core/okf-adapter.mjs +2 -2
  74. package/src/core/operation-validator.mjs +39 -331
  75. package/src/core/optimizer.mjs +7 -0
  76. package/src/core/post-mortem.mjs +2 -2
  77. package/src/core/project-brain.mjs +349 -0
  78. package/src/core/provider-catalog.mjs +296 -0
  79. package/src/core/quick-capture.mjs +1 -1
  80. package/src/core/remote-vault-sync.mjs +194 -0
  81. package/src/core/research-queue.mjs +7 -11
  82. package/src/core/runtime.mjs +33 -9
  83. package/src/core/scheduler.mjs +59 -5
  84. package/src/core/schema.mjs +14 -0
  85. package/src/core/search.mjs +1 -1
  86. package/src/core/secrets-env-export.mjs +323 -0
  87. package/src/core/secrets-rotate.mjs +245 -0
  88. package/src/core/secrets-store.mjs +645 -0
  89. package/src/core/semantic-index.mjs +3 -3
  90. package/src/core/skills-registry.mjs +1062 -0
  91. package/src/core/source-adapters.mjs +4 -1
  92. package/src/core/ssss-host-extension.mjs +94 -0
  93. package/src/core/ssss-kernel-bridge.mjs +530 -0
  94. package/src/core/surface.mjs +14 -0
  95. package/src/core/task-envelope.mjs +298 -0
  96. package/src/core/task-executors.mjs +600 -0
  97. package/src/core/validated-write.mjs +124 -56
  98. package/src/core/vault.mjs +21 -34
  99. package/src/core/webauthn-store.mjs +272 -0
  100. package/src/server/api.mjs +26 -16
  101. package/src/server/auth.mjs +51 -0
  102. package/src/server/index.mjs +3 -3
  103. package/src/server/keys.mjs +30 -4
  104. package/src/server/rest.mjs +198 -25
  105. package/src/server/route-manifest.json +156 -0
  106. package/src/server/routes/_shared.mjs +40 -1
  107. package/src/server/routes/docs.mjs +260 -0
  108. package/src/server/routes/memory.mjs +145 -32
  109. package/src/server/routes/research.mjs +30 -0
  110. package/src/server/routes/secrets.mjs +470 -0
  111. package/src/server/routes/share.mjs +2 -2
  112. package/src/server/routes/skills.mjs +452 -54
  113. package/src/server/routes/sync.mjs +48 -0
  114. package/src/server/routes/webauthn.mjs +184 -0
  115. package/src/server/update-route-manifest.mjs +39 -0
  116. package/templates/onboarding-interview.md +32 -22
  117. package/templates/openwiki/README.md +10 -0
  118. package/templates/openwiki/architecture.md +5 -0
  119. package/templates/openwiki/ide.md +10 -0
  120. package/templates/openwiki/memory.md +11 -0
  121. package/templates/openwiki/secrets.md +7 -0
  122. package/templates/openwiki/skills.md +7 -0
  123. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
  124. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
  125. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
  126. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
  127. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
  128. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
  129. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
  130. package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
  131. package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
  132. package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
  133. package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
  134. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
  135. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
  136. package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
  137. package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
  138. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
  139. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
  140. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
  141. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
  142. package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
  143. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
  144. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
  145. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
  146. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
  147. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
  148. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
  149. /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
  150. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
  151. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
  152. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
@@ -110,20 +110,24 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
110
110
  .then(health => {
111
111
  const agents = health.cli_agents && health.cli_agents.length > 0
112
112
  ? health.cli_agents
113
- : ['antigravity', 'claude', 'codex'];
113
+ : ['antigravity', 'grok', 'claude', 'codex'];
114
114
  setAvailableModels(agents);
115
115
 
116
116
  const saved = localStorage.getItem('selectedModel');
117
117
  if (saved && agents.includes(saved)) {
118
118
  setSelectedModel(saved);
119
119
  } else {
120
- const defaultAgent = agents.includes('antigravity') ? 'antigravity' : agents[0];
120
+ const defaultAgent = agents.includes('antigravity')
121
+ ? 'antigravity'
122
+ : agents.includes('grok')
123
+ ? 'grok'
124
+ : agents[0];
121
125
  setSelectedModel(defaultAgent);
122
126
  localStorage.setItem('selectedModel', defaultAgent);
123
127
  }
124
128
  })
125
129
  .catch(() => {
126
- const agents = ['antigravity', 'claude', 'codex'];
130
+ const agents = ['antigravity', 'grok', 'claude', 'codex'];
127
131
  setAvailableModels(agents);
128
132
  const saved = localStorage.getItem('selectedModel');
129
133
  if (saved && agents.includes(saved)) {
@@ -170,11 +174,51 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
170
174
  }
171
175
  }, [])
172
176
 
173
- // Reload memory nodes when active selected brain changes
177
+ // Reload memory nodes when active selected brain changes or background SSE triggers
174
178
  useEffect(() => {
175
- listMemory(activeBrainId)
176
- .then(setAllMemoryNodes)
177
- .catch(console.error)
179
+ let active = true
180
+
181
+ const loadMemoryNodes = () => {
182
+ listMemory(activeBrainId)
183
+ .then(nodes => {
184
+ if (!active) return
185
+ setAllMemoryNodes(nodes)
186
+ // Ensure selected grounding nodes stay in sync if a node is deleted in another tab/process
187
+ setSelectedGroundingNodes(prev => prev.filter(slug => nodes.some(n => n.slug === slug)))
188
+ })
189
+ .catch(console.error)
190
+ }
191
+
192
+ // Initial load
193
+ loadMemoryNodes()
194
+
195
+ // Listen to Cognitive Daemon SSE for memory surface rebuilds
196
+ const sseUrl = "http://localhost:9111/stream"
197
+ const eventSource = new EventSource(sseUrl)
198
+
199
+ eventSource.onmessage = (event) => {
200
+ if (!active) return
201
+ try {
202
+ const logEntry = JSON.parse(event.data)
203
+ if (
204
+ logEntry.sender === 'conflict-detector' ||
205
+ logEntry.sender === 'memory-ingestion' ||
206
+ logEntry.message?.includes('Surface compilation') ||
207
+ logEntry.message?.includes('Brain embeddings rebuilt') ||
208
+ logEntry.message?.includes('Graph indices rebuilt') ||
209
+ logEntry.message?.includes('deleted')
210
+ ) {
211
+ loadMemoryNodes()
212
+ }
213
+ } catch (e) {
214
+ // ignore parsing errors from stream
215
+ }
216
+ }
217
+
218
+ return () => {
219
+ active = false
220
+ eventSource.close()
221
+ }
178
222
  }, [activeBrainId])
179
223
 
180
224
  // Click outside listener for grounding node selector popover and model selector dropdown
@@ -501,8 +545,36 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
501
545
  </aside>
502
546
 
503
547
  {/* Main Chat Area */}
504
- <div className="chat-container">
505
- <header className="chat-header animate-fade-in">
548
+ <div className="chat-container" style={{ position: 'relative', overflow: 'hidden' }}>
549
+ {/* Decorative graph only — never intercepts clicks (was blocking model/provider UI) */}
550
+ <div
551
+ aria-hidden
552
+ style={{
553
+ position: 'absolute',
554
+ top: 0,
555
+ left: 0,
556
+ right: 0,
557
+ bottom: 0,
558
+ zIndex: 0,
559
+ pointerEvents: 'none',
560
+ opacity: messages.length > 0 ? 0.22 : 0.55,
561
+ transition: 'opacity 0.3s ease',
562
+ }}
563
+ >
564
+ <Graph3D
565
+ threads={threads}
566
+ memoryNodes={allMemoryNodes}
567
+ researchItems={researchItems}
568
+ onOpenThread={() => {}}
569
+ onGroundMemoryNode={() => {}}
570
+ selectedGroundingNodes={selectedGroundingNodes}
571
+ interactive={false}
572
+ />
573
+ </div>
574
+ <header
575
+ className="chat-header animate-fade-in"
576
+ style={{ position: 'relative', zIndex: 40, overflow: 'visible' }}
577
+ >
506
578
  <div className="chat-header-title">
507
579
  <h2>Chat Session</h2>
508
580
  {activeThreadId && (
@@ -511,13 +583,17 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
511
583
  </span>
512
584
  )}
513
585
  </div>
514
- <div className="chat-header-model-selector" ref={dropdownRef}>
515
- <span className="selector-label">Model:</span>
516
- <div className="model-selector-dropdown-wrapper">
586
+ <div className="chat-header-model-selector" ref={dropdownRef} style={{ position: 'relative', zIndex: 50 }}>
587
+ <span className="selector-label">Model / provider:</span>
588
+ <div className="model-selector-dropdown-wrapper" style={{ position: 'relative', zIndex: 50 }}>
517
589
  <button
590
+ type="button"
518
591
  className={`model-selector-dropdown-btn ${showModelDropdown ? 'active' : ''}`}
519
- onClick={() => setShowModelDropdown(!showModelDropdown)}
520
- title="Select CLI agent model"
592
+ onClick={(e) => {
593
+ e.stopPropagation()
594
+ setShowModelDropdown((v) => !v)
595
+ }}
596
+ title="Select CLI agent / provider"
521
597
  >
522
598
  <span style={{ textTransform: 'capitalize' }}>{selectedModel || 'Select Model'}</span>
523
599
  <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
@@ -525,12 +601,19 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
525
601
  </svg>
526
602
  </button>
527
603
  {showModelDropdown && (
528
- <div className="model-selector-menu glass">
604
+ <div
605
+ className="model-selector-menu glass"
606
+ style={{ zIndex: 200, pointerEvents: 'auto' }}
607
+ role="listbox"
608
+ >
529
609
  {availableModels.map(modelName => (
530
610
  <div
531
611
  key={modelName}
612
+ role="option"
613
+ aria-selected={selectedModel === modelName}
532
614
  className={`model-selector-item ${selectedModel === modelName ? 'selected' : ''}`}
533
- onClick={() => {
615
+ onClick={(e) => {
616
+ e.stopPropagation()
534
617
  setSelectedModel(modelName)
535
618
  localStorage.setItem('selectedModel', modelName)
536
619
  setSelectedSubModel('')
@@ -542,6 +625,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
542
625
  <span className="model-selector-item-name" style={{ textTransform: 'capitalize' }}>{modelName}</span>
543
626
  <span className="model-selector-item-desc">
544
627
  {modelName === 'antigravity' && 'Google Antigravity'}
628
+ {modelName === 'grok' && 'xAI Grok Build CLI'}
545
629
  {modelName === 'gemini' && 'Google Gemini CLI'}
546
630
  {modelName === 'claude' && 'Anthropic Claude CLI'}
547
631
  {modelName === 'codex' && 'Codex Coding Agent'}
@@ -654,7 +738,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
654
738
  style={{
655
739
  flexShrink: 0,
656
740
  padding: '6px 14px',
657
- background: 'linear-gradient(135deg, #6c5ce7, #a29bfe)',
741
+ background: 'linear-gradient(135deg, #3b82f6, #93c5fd)',
658
742
  color: '#fff',
659
743
  borderRadius: 6,
660
744
  fontWeight: 500,
@@ -685,35 +769,9 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
685
769
  </div>
686
770
  )}
687
771
 
688
- <div className="chat-messages" style={{ position: 'relative' }}>
689
- <div style={{
690
- position: 'absolute',
691
- top: 0,
692
- left: 0,
693
- right: 0,
694
- bottom: 0,
695
- zIndex: 1,
696
- pointerEvents: messages.length > 0 ? 'none' : 'auto',
697
- opacity: messages.length > 0 ? 0.35 : 1,
698
- transition: 'opacity 0.3s ease'
699
- }}>
700
- <Graph3D
701
- threads={threads}
702
- memoryNodes={allMemoryNodes}
703
- researchItems={researchItems}
704
- onOpenThread={(threadId) => setActiveThreadId(threadId)}
705
- onGroundMemoryNode={(slug) => {
706
- setSelectedGroundingNodes(prev =>
707
- prev.includes(slug) ? prev.filter(s => s !== slug) : [...prev, slug]
708
- )
709
- }}
710
- selectedGroundingNodes={selectedGroundingNodes}
711
- />
712
- </div>
713
-
772
+ <div className="chat-messages" style={{ position: 'relative', zIndex: 1, pointerEvents: 'auto' }}>
714
773
  <div style={{
715
774
  position: 'relative',
716
- zIndex: 2,
717
775
  display: 'flex',
718
776
  flexDirection: 'column',
719
777
  gap: '16px',
@@ -773,7 +831,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
773
831
  <div ref={messagesEnd} />
774
832
  </div>
775
833
  </div>
776
- <div className="chat-input-bar">
834
+ <div className="chat-input-bar" style={{ position: 'relative', zIndex: 30, pointerEvents: 'auto' }}>
777
835
  {selectedGroundingNodes.length > 0 && (
778
836
  <div className="grounding-pills">
779
837
  {selectedGroundingNodes.map(slug => {
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import { fetchDesignDocs, fetchDesignDocContent } from '../api';
3
3
 
4
4
  // ─── Types ──────────────────────────────────────────────────────────────────────
@@ -42,11 +42,11 @@ const KANBAN_COLUMNS: KanbanColumn[] = [
42
42
  {
43
43
  id: 'in-progress',
44
44
  title: 'In Progress',
45
- accent: '#6c5ce7',
45
+ accent: '#3b82f6',
46
46
  cards: [
47
47
  { id: 'auto-update', name: 'Auto Update Feature', emoji: '🔄' },
48
48
  { id: 'compaction-cache', name: 'Compaction Cache Bypass', emoji: '⚡' },
49
- { id: 'sovereign-policy', name: 'Sovereign Policy Enforcement', emoji: '🛡️' },
49
+ { id: 'memory-policy', name: 'Memory Policy Enforcement', emoji: '🛡️' },
50
50
  ],
51
51
  },
52
52
  {
@@ -70,7 +70,7 @@ const KANBAN_COLUMNS: KanbanColumn[] = [
70
70
  { id: 'okf-integration', name: 'OKF Integration', emoji: '✅' },
71
71
  { id: 'byom-architecture', name: 'BYOM Architecture', emoji: '✅' },
72
72
  { id: 'cli-performance', name: 'CLI Performance Optimization', emoji: '✅' },
73
- { id: 'ssss-sovereign', name: 'SSSS Sovereign AI OS', emoji: '✅' },
73
+ { id: 'ssss-portable-memory', name: 'SSSS Portable Memory', emoji: '✅' },
74
74
  { id: 'embedding-pipeline', name: 'Embedding Pipeline v2', emoji: '✅' },
75
75
  { id: 'surface-compiler', name: 'Surface Compiler', emoji: '✅' },
76
76
  ],
@@ -91,7 +91,7 @@ const KANBAN_COLUMNS: KanbanColumn[] = [
91
91
 
92
92
  // ─── Simple Markdown Renderer ───────────────────────────────────────────────────
93
93
 
94
- function renderMarkdown(md: string): JSX.Element {
94
+ function renderMarkdown(md: string): React.ReactNode {
95
95
  if (!md) return <></>;
96
96
 
97
97
  let html = md
@@ -35,7 +35,11 @@ export default function FilesPage() {
35
35
  listSkills()
36
36
  ])
37
37
  setFiles(filesData)
38
- setSkills(skillsData)
38
+ // Skills API returns { skills, repos } catalog — flatten for this page
39
+ const flat = Array.isArray(skillsData)
40
+ ? skillsData
41
+ : (skillsData?.skills || [])
42
+ setSkills(flat as import('../types').FileNode[])
39
43
  setError("")
40
44
  } catch (e) {
41
45
  setError((e as Error).message)
@@ -121,10 +125,10 @@ export default function FilesPage() {
121
125
  setScriptLoading(true)
122
126
  try {
123
127
  const defaultCode = name.endsWith(".py")
124
- ? "#!/usr/bin/env python3\nprint('Hello from SSSS Sovereign Automation!')\n"
128
+ ? "#!/usr/bin/env python3\nprint('Hello from Total Recall automation!')\n"
125
129
  : name.endsWith(".sh")
126
- ? "#!/bin/bash\necho 'Hello from SSSS Sovereign Automation!'\n"
127
- : "/**\n * SSSS Skills Automation Script\n */\nconsole.log(\"Hello from SSSS Sovereign Automation!\");\n"
130
+ ? "#!/bin/bash\necho 'Hello from Total Recall automation!'\n"
131
+ : "/**\n * Total Recall skills automation script\n */\nconsole.log(\"Hello from Total Recall automation!\");\n"
128
132
 
129
133
  await saveScript(name, defaultCode)
130
134
  setNewScriptName("")
@@ -143,7 +147,7 @@ export default function FilesPage() {
143
147
  <div className="page-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 20 }}>
144
148
  <div>
145
149
  <h1>Storage & Automations</h1>
146
- <p>Manage sovereign files, runtime skills, and automation scripts</p>
150
+ <p>Manage local files, runtime skills, and automation scripts</p>
147
151
  </div>
148
152
 
149
153
  {/* Navigation Tabs */}
@@ -176,7 +180,7 @@ export default function FilesPage() {
176
180
 
177
181
  {activeTab === "files" && (
178
182
  <div className="health-log" style={{ background: "rgba(15,23,42,0.45)", backdropFilter: "blur(12px)", border: "1px solid var(--border)", borderRadius: 12, padding: 24 }}>
179
- <h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>Sovereign Files (~/.agent/files/)</h2>
183
+ <h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>Local Files (~/.agent/files/)</h2>
180
184
  <table style={{ width: "100%", textAlign: "left", borderCollapse: "collapse" }}>
181
185
  <thead>
182
186
  <tr>
@@ -17,7 +17,7 @@ export default function GraphPage({ activeBrainId }: { activeBrainId?: string })
17
17
  try {
18
18
  const [nodes, researchRes, threadsRes] = await Promise.all([
19
19
  listMemory(activeBrainId),
20
- listResearch(activeBrainId).catch(() => ({ items: [] })),
20
+ listResearch().catch(() => ({ items: [] })),
21
21
  fetchChatThreads().catch(() => [])
22
22
  ])
23
23
  setMemoryNodes(nodes)
@@ -35,8 +35,10 @@ export default function GraphPage({ activeBrainId }: { activeBrainId?: string })
35
35
  return (
36
36
  <div className="page" style={{ padding: 0, height: 'calc(100vh - 1px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
37
37
  <div style={{ padding: '16px 24px', background: 'rgba(18, 18, 26, 0.45)', backdropFilter: 'blur(16px)', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
38
- <h1 style={{ fontSize: '18px', fontWeight: 700 }}>Sovereign Memory Network</h1>
39
- <p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '2px' }}>Interactive 3D constellation layout of your knowledge vault, sessions, and active thoughts.</p>
38
+ <h1 style={{ fontSize: '18px', fontWeight: 700, letterSpacing: '-0.02em' }}>Memory Graph</h1>
39
+ <p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '2px' }}>
40
+ 3D vault map — facts (blue), rules (cyan/rose), research (amber), sessions (teal).
41
+ </p>
40
42
  </div>
41
43
 
42
44
  <div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
@@ -84,8 +84,8 @@ export default function HelpPage() {
84
84
  return (
85
85
  <div className="page" style={{ padding: '24px', maxWidth: '1100px', margin: '0 auto' }}>
86
86
  <div className="page-header" style={{ marginBottom: '24px' }}>
87
- <h1>Sovereign Help System</h1>
88
- <p>Offline interactive documentation, CLI command references, and SSSS VFS specifications.</p>
87
+ <h1>Help & CLI Reference</h1>
88
+ <p>Offline docs for portable memory: CLI commands, vault layout, dream, tasks, and IDE connect.</p>
89
89
  </div>
90
90
 
91
91
  {error && <div className="badge badge-error" style={{ marginBottom: '16px' }}>⚠️ {error}</div>}
@@ -0,0 +1,82 @@
1
+ import { useState, useEffect, useCallback } from "react"
2
+ import { fetchDocs, readDoc, updateDoc, postDecision, type VaultDocument } from "../api"
3
+ import { DocumentTable } from "../components/DocumentTable"
4
+
5
+ export default function InboxPage({ activeBrainId }: { activeBrainId?: string }) {
6
+ const [items, setItems] = useState<VaultDocument[]>([])
7
+ const [loading, setLoading] = useState(true)
8
+ const [error, setError] = useState("")
9
+
10
+ const loadData = useCallback(async () => {
11
+ try {
12
+ setLoading(true)
13
+ const data = await fetchDocs(activeBrainId, { status: "pending_approval" })
14
+ setItems(data.docs)
15
+ } catch (err: unknown) {
16
+ setError(err instanceof Error ? err.message : "Failed to load the approval inbox")
17
+ } finally {
18
+ setLoading(false)
19
+ }
20
+ }, [activeBrainId])
21
+
22
+ useEffect(() => {
23
+ const timer = window.setTimeout(() => { void loadData() }, 0)
24
+ return () => window.clearTimeout(timer)
25
+ }, [loadData])
26
+
27
+ const handleDecision = async (path: string, action: string) => {
28
+ try {
29
+ const doc = await readDoc(path, activeBrainId)
30
+ // Extract proposal ID from path (e.g. proposals/p2.md -> p2)
31
+ const idMatch = path.match(/proposals\/(.+?)\.md$/)
32
+ if (idMatch) {
33
+ const id = idMatch[1]
34
+ // This hits the proxy, which forwards to droplet and runs sync
35
+ await postDecision(id, action)
36
+ } else {
37
+ // Fallback for non-proposal docs (just optimistic update)
38
+ const newBody = doc.raw.replace(/status:.*$/m, `status: ${action}`)
39
+ await updateDoc(path, newBody, activeBrainId)
40
+ }
41
+ loadData()
42
+ } catch (err: unknown) {
43
+ alert("Decision failed: " + (err instanceof Error ? err.message : "Unknown error"))
44
+ }
45
+ }
46
+
47
+ return (
48
+ <div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto', color: 'white' }}>
49
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
50
+ <h1 style={{ margin: 0, fontSize: '24px' }}>Approval Inbox</h1>
51
+ <button onClick={loadData} style={{ padding: '8px 16px', background: 'rgba(255,255,255,0.1)', border: 'none', color: 'white', cursor: 'pointer', borderRadius: '4px' }}>Refresh</button>
52
+ </div>
53
+
54
+ {loading && <p>Loading inbox items...</p>}
55
+ {error && <p style={{ color: 'red' }}>Error: {error}</p>}
56
+
57
+ {!loading && !error && (
58
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '30px' }}>
59
+ <div>
60
+ <DocumentTable
61
+ data={items}
62
+ emptyMessage="No items pending approval."
63
+ columns={[
64
+ { key: 'type', header: 'Type', render: (d) => <span style={{ opacity: 0.6 }}>{d.type}</span> },
65
+ { key: 'name', header: 'Name', render: (d) => d.name },
66
+ { key: 'status', header: 'Status', render: (d) => <span style={{ fontSize: '0.8em', background: 'rgba(234,179,8,0.15)', color: '#eab308', padding: '2px 6px', borderRadius: '4px' }}>{d.status || 'pending'}</span> },
67
+ { key: 'updated', header: 'Last Updated', render: (d) => d.updatedAt ? new Date(d.updatedAt).toLocaleDateString() : '-' },
68
+ { key: 'actions', header: 'Actions', render: (d) => (
69
+ <>
70
+ <button onClick={() => handleDecision(d.path, 'approved')} style={{ marginRight: '10px', background: '#22c55e', border: 'none', color: 'white', padding: '4px 8px', borderRadius: '4px', cursor: 'pointer' }}>Approve</button>
71
+ <button onClick={() => handleDecision(d.path, 'rejected')} style={{ background: '#e22b22', border: 'none', color: 'white', padding: '4px 8px', borderRadius: '4px', cursor: 'pointer' }}>Reject</button>
72
+ </>
73
+ )
74
+ }
75
+ ]}
76
+ />
77
+ </div>
78
+ </div>
79
+ )}
80
+ </div>
81
+ )
82
+ }
@@ -268,7 +268,7 @@ export default function InstructionsPage() {
268
268
  }
269
269
 
270
270
  useEffect(() => {
271
- // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate data fetch on mount
271
+ // eslint-disable-next-line react-hooks/set-state-in-effect, react-hooks/exhaustive-deps -- legitimate data fetch on mount
272
272
  void loadSurfaces()
273
273
  }, [])
274
274
 
@@ -45,11 +45,11 @@ const PRESETS: Preset[] = [
45
45
  snippet: baseUrl => `Read AGENTS.md from the repo root.\nDiscovery: ${baseUrl}/.well-known/total-recall.json`
46
46
  },
47
47
  {
48
- id: 'ultrachat',
49
- name: 'UltraChat',
48
+ id: 'http-api',
49
+ name: 'HTTP host app',
50
50
  surface: 'OpenAI-compatible provider',
51
51
  scopes: 'models:read, chat:write, ssss:read',
52
- command: 'npx total-recall connect ultrachat --brain <brain-url>',
52
+ command: 'npx total-recall connect http-api --brain <brain-url>',
53
53
  snippet: baseUrl => `Base URL: ${baseUrl}/v1\nModel: total-recall/gemma4\nModels: ${baseUrl}/v1/models`
54
54
  },
55
55
  {
@@ -104,7 +104,7 @@ export default function IntegrationsPage() {
104
104
  const filteredPresets = useMemo(() => {
105
105
  if (activeIdes.length === 0) return PRESETS
106
106
  return PRESETS.filter(preset => {
107
- if (['ultrachat', 'generic'].includes(preset.id)) {
107
+ if (['http-api', 'generic'].includes(preset.id)) {
108
108
  return true
109
109
  }
110
110
  return activeIdes.includes(preset.id)
@@ -364,13 +364,24 @@ export default function IntegrationsPage() {
364
364
  const snippet = preset.snippet(baseUrl)
365
365
 
366
366
  // Match keys by name (e.g. "Cursor Link", "Claude Code Link", "Codex Link")
367
+ // Guard: some legacy rows stored a non-string name object
367
368
  const matchingKey = keys.find(k => {
368
- if (preset.id === 'cursor') return k.name.toLowerCase().includes('cursor') && !k.revoked;
369
- if (preset.id === 'claude-code') return k.name.toLowerCase().includes('claude') && !k.revoked;
370
- if (preset.id === 'codex') return (k.name.toLowerCase().includes('codex') || k.name.toLowerCase().includes('agents')) && !k.revoked;
371
- if (preset.id === 'antigravity') return k.name.toLowerCase().includes('antigravity') && !k.revoked;
372
- if (preset.id === 'ultrachat') return k.name.toLowerCase().includes('ultrachat') && !k.revoked;
373
- return false;
369
+ const rawName = k?.name
370
+ const name =
371
+ typeof rawName === 'string'
372
+ ? rawName
373
+ : rawName && typeof rawName === 'object' && typeof (rawName as { name?: unknown }).name === 'string'
374
+ ? String((rawName as { name: string }).name)
375
+ : ''
376
+ if (!name || k.revoked) return false
377
+ const n = name.toLowerCase()
378
+ if (preset.id === 'cursor') return n.includes('cursor')
379
+ if (preset.id === 'claude-code') return n.includes('claude')
380
+ if (preset.id === 'codex') return n.includes('codex') || n.includes('agents')
381
+ if (preset.id === 'antigravity') return n.includes('antigravity')
382
+ if (preset.id === 'grok') return n.includes('grok') || n.includes('xai')
383
+ if (preset.id === 'http-api') return n.includes('http') || n.includes('host')
384
+ return false
374
385
  })
375
386
 
376
387
  let statusBadge = (