total-recall-brain 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/antigravity.mjs +19 -8
- package/extension/background.js +13 -1
- package/extension/content-script.js +18 -1
- package/extension/lib/brain-client.js +18 -5
- package/extension/options/options.js +9 -4
- package/frontend/index.html +8 -0
- package/frontend/src/api.ts +20 -1
- package/frontend/src/pages/ChatPage.tsx +9 -6
- package/frontend/src/pages/HealthPage.tsx +179 -7
- package/frontend/src/pages/IntegrationsPage.tsx +4 -2
- package/frontend/src/pages/SettingsPage.tsx +197 -2
- package/frontend/src/types.ts +16 -0
- package/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/SKILL.md +55 -6
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -2
- package/scaffold/.agent/skills/total-recall/research-queue.jsonl +1 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -6
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +19 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +33 -5
- package/src/cli/forget.mjs +18 -12
- package/src/cli/ingest/index.mjs +3 -2
- package/src/cli/map.mjs +2 -2
- package/src/cli/rebuild.mjs +1 -1
- package/src/cli/remember.mjs +21 -11
- package/src/cli/share.mjs +7 -2
- package/src/core/append-log.mjs +131 -0
- package/src/core/clarity-rewriter.mjs +10 -6
- package/src/core/conclusion-writer.mjs +3 -2
- package/src/core/config.mjs +6 -0
- package/src/core/conflict-detector.mjs +4 -2
- package/src/core/context-compiler.mjs +412 -0
- package/src/core/daemon-loop.mjs +15 -0
- package/src/core/dream.mjs +4 -4
- package/src/core/drift-detector.mjs +3 -3
- package/src/core/embeddings.mjs +60 -10
- package/src/core/fact-seeker.mjs +70 -44
- package/src/core/import-rules.mjs +3 -2
- package/src/core/inference-engine.mjs +5 -4
- package/src/core/migrate.mjs +4 -3
- package/src/core/optimizer.mjs +6 -5
- package/src/core/parallel-context.mjs +288 -0
- package/src/core/post-mortem.mjs +4 -3
- package/src/core/research-queue.mjs +4 -3
- package/src/core/runtime.mjs +30 -7
- package/src/core/sandbox.mjs +56 -26
- package/src/core/scheduler.mjs +6 -5
- package/src/core/schema.mjs +483 -2
- package/src/core/search.mjs +167 -101
- package/src/core/surface.mjs +203 -53
- package/src/core/validated-write.mjs +100 -0
- package/src/core/vault-cache.mjs +46 -18
- package/src/core/vault.mjs +17 -29
- package/src/core/vector-field.mjs +659 -0
- package/src/core/vector-store.mjs +85 -0
- package/src/server/api.mjs +4 -3
- package/src/server/index.mjs +13 -61
- package/src/server/rest.mjs +341 -63
- package/src/server/route-manifest.json +352 -77
- package/src/server/routes/memory.mjs +85 -39
- package/src/server/routes/sessions.mjs +6 -2
- package/src/server/routes/share.mjs +9 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Total Recall — SSSS Sovereign AI OS
|
|
2
2
|
|
|
3
|
-
[](package.json)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](.github/workflows/test.yml)
|
|
6
6
|
|
package/bin/antigravity.mjs
CHANGED
|
@@ -83,10 +83,9 @@ if (!process.env.GOOGLE_API_KEY) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const apiKey = process.env.GOOGLE_API_KEY;
|
|
86
|
+
const apiKey = process.env.GOOGLE_API_KEY || '';
|
|
87
87
|
if (!apiKey) {
|
|
88
|
-
console.
|
|
89
|
-
process.exit(1);
|
|
88
|
+
console.warn('Warning: GOOGLE_API_KEY environment variable is not configured. Attempting connection via GCP Application Default Credentials or ambient environment...');
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
// Resolve the model dynamically from the API registry based on the category/alias selected
|
|
@@ -109,7 +108,13 @@ async function resolveGenerativeModel(selectedModel) {
|
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
try {
|
|
112
|
-
const
|
|
111
|
+
const keyParam = apiKey ? `key=${apiKey}` : '';
|
|
112
|
+
const headers = {};
|
|
113
|
+
if (!apiKey && process.env.GCLOUD_TOKEN) {
|
|
114
|
+
headers['Authorization'] = `Bearer ${process.env.GCLOUD_TOKEN}`;
|
|
115
|
+
}
|
|
116
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?${keyParam}`, {
|
|
117
|
+
headers,
|
|
113
118
|
signal: AbortSignal.timeout(5000)
|
|
114
119
|
});
|
|
115
120
|
if (res.ok) {
|
|
@@ -154,13 +159,19 @@ async function resolveGenerativeModel(selectedModel) {
|
|
|
154
159
|
async function main() {
|
|
155
160
|
try {
|
|
156
161
|
const resolvedModel = await resolveGenerativeModel(requestedModel);
|
|
157
|
-
const
|
|
162
|
+
const keyParam = apiKey ? `?key=${apiKey}` : '';
|
|
163
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${resolvedModel}:generateContent${keyParam}`;
|
|
164
|
+
|
|
165
|
+
const headers = {
|
|
166
|
+
'Content-Type': 'application/json',
|
|
167
|
+
};
|
|
168
|
+
if (!apiKey && process.env.GCLOUD_TOKEN) {
|
|
169
|
+
headers['Authorization'] = `Bearer ${process.env.GCLOUD_TOKEN}`;
|
|
170
|
+
}
|
|
158
171
|
|
|
159
172
|
const res = await fetch(url, {
|
|
160
173
|
method: 'POST',
|
|
161
|
-
headers
|
|
162
|
-
'Content-Type': 'application/json',
|
|
163
|
-
},
|
|
174
|
+
headers,
|
|
164
175
|
body: JSON.stringify({
|
|
165
176
|
contents: [{
|
|
166
177
|
parts: [{ text: prompt }]
|
package/extension/background.js
CHANGED
|
@@ -75,7 +75,7 @@ function showBadge(text, color) {
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
// Message listener (popup, content-script, sidepanel → background)
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
|
-
chrome.runtime.onMessage.addListener((msg,
|
|
78
|
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
79
79
|
if (msg.type === 'QUERY_BRAIN') {
|
|
80
80
|
search(msg.query, msg.topK || 3)
|
|
81
81
|
.then(data => sendResponse({ memories: data.results || [] }))
|
|
@@ -96,4 +96,16 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
|
96
96
|
.catch(() => sendResponse({ connected: false }));
|
|
97
97
|
return true;
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
if (msg.type === 'OPEN_SIDE_PANEL') {
|
|
101
|
+
const tabId = sender.tab?.id;
|
|
102
|
+
if (!chrome.sidePanel?.open || !tabId) {
|
|
103
|
+
sendResponse({ success: false, error: 'Side panel is unavailable for this tab.' });
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
chrome.sidePanel.open({ tabId })
|
|
107
|
+
.then(() => sendResponse({ success: true }))
|
|
108
|
+
.catch(err => sendResponse({ success: false, error: err.message }));
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
99
111
|
});
|
|
@@ -112,8 +112,25 @@
|
|
|
112
112
|
document.body.appendChild(pillHost);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function isBlocked(blocklist) {
|
|
116
|
+
if (!Array.isArray(blocklist) || blocklist.length === 0) return false;
|
|
117
|
+
let host = '';
|
|
118
|
+
try {
|
|
119
|
+
host = new URL(pageContext.url).hostname.toLowerCase();
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return blocklist.some((entry) => {
|
|
124
|
+
const pattern = String(entry || '').trim().toLowerCase();
|
|
125
|
+
return pattern && (host === pattern || host.endsWith(`.${pattern}`) || pageContext.url.toLowerCase().includes(pattern));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
// ---- Query brain after delay ----
|
|
116
|
-
function queryBrain() {
|
|
130
|
+
async function queryBrain() {
|
|
131
|
+
const { passiveTracking = false, blocklist = [] } = await chrome.storage.sync.get(['passiveTracking', 'blocklist']);
|
|
132
|
+
if (!passiveTracking || isBlocked(blocklist)) return;
|
|
133
|
+
|
|
117
134
|
const query = pageContext.title || pageContext.url;
|
|
118
135
|
if (!query) return;
|
|
119
136
|
|
|
@@ -6,8 +6,21 @@
|
|
|
6
6
|
const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
|
|
7
7
|
|
|
8
8
|
async function getConfig() {
|
|
9
|
-
const
|
|
9
|
+
const [localConfig, syncConfig] = await Promise.all([
|
|
10
|
+
chrome.storage.local.get(['brainUrl', 'pat']),
|
|
11
|
+
chrome.storage.sync.get(['brainUrl', 'pat'])
|
|
12
|
+
]);
|
|
10
13
|
const pre = self.PreConfigured || {};
|
|
14
|
+
const config = {
|
|
15
|
+
brainUrl: localConfig.brainUrl || syncConfig.brainUrl,
|
|
16
|
+
pat: localConfig.pat || syncConfig.pat
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (syncConfig.pat && !localConfig.pat) {
|
|
20
|
+
await chrome.storage.local.set({ pat: syncConfig.pat });
|
|
21
|
+
await chrome.storage.sync.remove('pat');
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
return {
|
|
12
25
|
brainUrl: config.brainUrl || pre.brainUrl || DEFAULT_BRAIN_URL,
|
|
13
26
|
pat: config.pat || pre.pat || ''
|
|
@@ -67,13 +80,13 @@
|
|
|
67
80
|
return res.json();
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
// Self-heal stale storage: if preconfigured values exist,
|
|
83
|
+
// Self-heal stale storage: if preconfigured values exist, write them to extension-local storage.
|
|
71
84
|
try {
|
|
72
|
-
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.
|
|
73
|
-
chrome.storage.
|
|
85
|
+
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
|
86
|
+
chrome.storage.local.get(['brainUrl', 'pat']).then((config) => {
|
|
74
87
|
const pre = self.PreConfigured || {};
|
|
75
88
|
if (pre.pat && (!config.pat || config.pat !== pre.pat)) {
|
|
76
|
-
chrome.storage.
|
|
89
|
+
chrome.storage.local.set({ brainUrl: pre.brainUrl, pat: pre.pat });
|
|
77
90
|
}
|
|
78
91
|
});
|
|
79
92
|
}
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
// ---- Load saved settings ----
|
|
15
15
|
async function loadSettings() {
|
|
16
|
-
const
|
|
17
|
-
'brainUrl',
|
|
18
|
-
|
|
16
|
+
const [localSettings, syncSettings] = await Promise.all([
|
|
17
|
+
chrome.storage.local.get(['brainUrl', 'pat']),
|
|
18
|
+
chrome.storage.sync.get([
|
|
19
19
|
'blocklist',
|
|
20
20
|
'captureGranularity'
|
|
21
|
+
])
|
|
21
22
|
]);
|
|
23
|
+
const settings = { ...syncSettings, ...localSettings };
|
|
22
24
|
|
|
23
25
|
const pre = self.PreConfigured || {};
|
|
24
26
|
brainUrlInput.value = settings.brainUrl || pre.brainUrl || 'http://127.0.0.1:3000';
|
|
@@ -48,9 +50,12 @@
|
|
|
48
50
|
const granularityRadio = document.querySelector('input[name="granularity"]:checked');
|
|
49
51
|
const captureGranularity = granularityRadio ? granularityRadio.value : 'minimal';
|
|
50
52
|
|
|
51
|
-
await chrome.storage.
|
|
53
|
+
await chrome.storage.local.set({
|
|
52
54
|
brainUrl: brainUrlInput.value.trim() || 'http://127.0.0.1:3000',
|
|
53
55
|
pat: patInput.value.trim(),
|
|
56
|
+
});
|
|
57
|
+
await chrome.storage.sync.remove('pat');
|
|
58
|
+
await chrome.storage.sync.set({
|
|
54
59
|
blocklist,
|
|
55
60
|
captureGranularity
|
|
56
61
|
});
|
package/frontend/index.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="description" content="Total Recall — Sovereign AI Brain Dashboard. Memory explorer, chat interface, code sandbox, and system health monitoring." />
|
|
7
7
|
<meta name="theme-color" content="#0a0a0f" />
|
|
8
|
+
<link rel="manifest" href="/manifest.json" />
|
|
8
9
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
11
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
@@ -13,5 +14,12 @@
|
|
|
13
14
|
<body>
|
|
14
15
|
<div id="root"></div>
|
|
15
16
|
<script type="module" src="/src/main.tsx"></script>
|
|
17
|
+
<script>
|
|
18
|
+
if ('serviceWorker' in navigator) {
|
|
19
|
+
window.addEventListener('load', () => {
|
|
20
|
+
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
16
24
|
</body>
|
|
17
25
|
</html>
|
package/frontend/src/api.ts
CHANGED
|
@@ -563,7 +563,7 @@ export async function resolveConflict(
|
|
|
563
563
|
|
|
564
564
|
export async function saveMemory(slug: string, node: Partial<MemoryNode>): Promise<MemoryNode> {
|
|
565
565
|
const res = await apiFetch(`${API_BASE}/api/memory/${slug}`, {
|
|
566
|
-
method: "
|
|
566
|
+
method: "PATCH",
|
|
567
567
|
headers: { "Content-Type": "application/json" },
|
|
568
568
|
body: JSON.stringify(node),
|
|
569
569
|
})
|
|
@@ -686,3 +686,22 @@ export async function fetchExtensionStatus(): Promise<{ available: boolean; conn
|
|
|
686
686
|
return res.json()
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
+
// ─── Auto-Update APIs ──────────────────────────────────────────────────────────
|
|
690
|
+
|
|
691
|
+
export interface UpdateCheckResult {
|
|
692
|
+
currentVersion: string
|
|
693
|
+
latestVersion: string
|
|
694
|
+
updateAvailable: boolean
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export async function checkUpdate(): Promise<UpdateCheckResult> {
|
|
698
|
+
const res = await apiFetch(`${API_BASE}/api/update/check`)
|
|
699
|
+
if (!res.ok) throw new Error(`Check update API error: ${res.status}`)
|
|
700
|
+
return res.json()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export async function runUpdate(): Promise<{ success: boolean; message: string }> {
|
|
704
|
+
const res = await apiFetch(`${API_BASE}/api/update/run`, { method: 'POST' })
|
|
705
|
+
if (!res.ok) throw new Error(`Run update API error: ${res.status}`)
|
|
706
|
+
return res.json()
|
|
707
|
+
}
|
|
@@ -57,7 +57,10 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
57
57
|
|
|
58
58
|
// Model Selector state
|
|
59
59
|
const [availableModels, setAvailableModels] = useState<string[]>([])
|
|
60
|
-
const [selectedModel, setSelectedModel] = useState<string>(() =>
|
|
60
|
+
const [selectedModel, setSelectedModel] = useState<string>(() => {
|
|
61
|
+
const saved = localStorage.getItem('selectedModel');
|
|
62
|
+
return (saved && saved !== 'gemini') ? saved : 'antigravity';
|
|
63
|
+
})
|
|
61
64
|
const [selectedSubModel, setSelectedSubModel] = useState<string>(() => localStorage.getItem('selectedSubModel') || '')
|
|
62
65
|
const [geminiModels, setGeminiModels] = useState<{ id: string; displayName: string }[]>([])
|
|
63
66
|
const [showModelDropdown, setShowModelDropdown] = useState(false)
|
|
@@ -104,27 +107,27 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
104
107
|
.then(health => {
|
|
105
108
|
const agents = health.cli_agents && health.cli_agents.length > 0
|
|
106
109
|
? health.cli_agents
|
|
107
|
-
: ['antigravity', '
|
|
110
|
+
: ['antigravity', 'claude', 'codex'];
|
|
108
111
|
setAvailableModels(agents);
|
|
109
112
|
|
|
110
113
|
const saved = localStorage.getItem('selectedModel');
|
|
111
114
|
if (saved && agents.includes(saved)) {
|
|
112
115
|
setSelectedModel(saved);
|
|
113
116
|
} else {
|
|
114
|
-
const defaultAgent = agents.includes('
|
|
117
|
+
const defaultAgent = agents.includes('antigravity') ? 'antigravity' : agents[0];
|
|
115
118
|
setSelectedModel(defaultAgent);
|
|
116
119
|
localStorage.setItem('selectedModel', defaultAgent);
|
|
117
120
|
}
|
|
118
121
|
})
|
|
119
122
|
.catch(() => {
|
|
120
|
-
const agents = ['antigravity', '
|
|
123
|
+
const agents = ['antigravity', 'claude', 'codex'];
|
|
121
124
|
setAvailableModels(agents);
|
|
122
125
|
const saved = localStorage.getItem('selectedModel');
|
|
123
126
|
if (saved && agents.includes(saved)) {
|
|
124
127
|
setSelectedModel(saved);
|
|
125
128
|
} else {
|
|
126
|
-
setSelectedModel('
|
|
127
|
-
localStorage.setItem('selectedModel', '
|
|
129
|
+
setSelectedModel('antigravity');
|
|
130
|
+
localStorage.setItem('selectedModel', 'antigravity');
|
|
128
131
|
}
|
|
129
132
|
})
|
|
130
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
|
-
import { fetchHealth } from '../api'
|
|
3
|
-
import type { HealthData } from '../types'
|
|
2
|
+
import { fetchHealth, checkUpdate, runUpdate } from '../api'
|
|
3
|
+
import type { HealthData, UpdateCheckResult } from '../types'
|
|
4
4
|
|
|
5
5
|
function formatUptime(seconds: number): string {
|
|
6
6
|
const d = Math.floor(seconds / 86400)
|
|
@@ -15,6 +15,12 @@ export default function HealthPage() {
|
|
|
15
15
|
const [health, setHealth] = useState<HealthData | null>(null)
|
|
16
16
|
const [error, setError] = useState('')
|
|
17
17
|
const [lastPoll, setLastPoll] = useState<Date | null>(null)
|
|
18
|
+
|
|
19
|
+
// Auto-Update States
|
|
20
|
+
const [updateInfo, setUpdateInfo] = useState<UpdateCheckResult | null>(null)
|
|
21
|
+
const [isUpdating, setIsUpdating] = useState(false)
|
|
22
|
+
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
|
23
|
+
const [updateError, setUpdateError] = useState('')
|
|
18
24
|
|
|
19
25
|
useEffect(() => {
|
|
20
26
|
let active = true
|
|
@@ -33,17 +39,166 @@ export default function HealthPage() {
|
|
|
33
39
|
|
|
34
40
|
poll()
|
|
35
41
|
const interval = setInterval(poll, 5000)
|
|
42
|
+
|
|
43
|
+
// Check for updates
|
|
44
|
+
const checkVersion = async () => {
|
|
45
|
+
try {
|
|
46
|
+
const data = await checkUpdate()
|
|
47
|
+
if (active) setUpdateInfo(data)
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error('Failed to check updates', e)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
checkVersion()
|
|
53
|
+
|
|
36
54
|
return () => { active = false; clearInterval(interval) }
|
|
37
55
|
}, [])
|
|
38
56
|
|
|
57
|
+
const handleStartUpdate = async () => {
|
|
58
|
+
setShowConfirmModal(false)
|
|
59
|
+
setIsUpdating(true)
|
|
60
|
+
setUpdateError('')
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await runUpdate()
|
|
64
|
+
|
|
65
|
+
// Keep polling health to check when it goes down and recovers
|
|
66
|
+
let serverDown = false
|
|
67
|
+
const pollInterval = setInterval(async () => {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetchHealth()
|
|
70
|
+
if (serverDown && res.status === 'healthy') {
|
|
71
|
+
clearInterval(pollInterval)
|
|
72
|
+
window.location.reload()
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Connection failed = server is offline / restarting
|
|
76
|
+
serverDown = true
|
|
77
|
+
}
|
|
78
|
+
}, 2000)
|
|
79
|
+
|
|
80
|
+
} catch (e) {
|
|
81
|
+
setUpdateError((e as Error).message)
|
|
82
|
+
setIsUpdating(false)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
39
86
|
return (
|
|
40
|
-
<div className="page">
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
87
|
+
<div className="page" style={{ position: 'relative' }}>
|
|
88
|
+
{isUpdating && (
|
|
89
|
+
<div style={{
|
|
90
|
+
position: 'fixed',
|
|
91
|
+
inset: 0,
|
|
92
|
+
background: 'rgba(13, 17, 23, 0.9)',
|
|
93
|
+
zIndex: 1000,
|
|
94
|
+
display: 'flex',
|
|
95
|
+
flexDirection: 'column',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
backdropFilter: 'blur(8px)',
|
|
99
|
+
animation: 'fadeIn 0.3s ease'
|
|
100
|
+
}}>
|
|
101
|
+
<div className="spinner" style={{
|
|
102
|
+
width: 50,
|
|
103
|
+
height: 50,
|
|
104
|
+
border: '4px solid var(--border)',
|
|
105
|
+
borderTopColor: 'var(--accent)',
|
|
106
|
+
borderRadius: '50%',
|
|
107
|
+
animation: 'spin 1s linear infinite',
|
|
108
|
+
marginBottom: 20
|
|
109
|
+
}} />
|
|
110
|
+
<h2 style={{ color: 'var(--text-primary)', marginBottom: 8 }}>Updating Total Recall...</h2>
|
|
111
|
+
<p style={{ color: 'var(--text-muted)', fontSize: 14 }}>Applying git changes, installing npm modules, and restarting daemon.</p>
|
|
112
|
+
<p style={{ color: 'var(--accent)', fontSize: 13, marginTop: 10 }}>Please do not close this window.</p>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{showConfirmModal && (
|
|
117
|
+
<div style={{
|
|
118
|
+
position: 'fixed',
|
|
119
|
+
inset: 0,
|
|
120
|
+
background: 'rgba(0, 0, 0, 0.7)',
|
|
121
|
+
zIndex: 900,
|
|
122
|
+
display: 'flex',
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
justifyContent: 'center',
|
|
125
|
+
backdropFilter: 'blur(4px)',
|
|
126
|
+
padding: 20
|
|
127
|
+
}}>
|
|
128
|
+
<div className="card" style={{
|
|
129
|
+
maxWidth: 450,
|
|
130
|
+
width: '100%',
|
|
131
|
+
backgroundColor: 'var(--bg-surface)',
|
|
132
|
+
border: '1px solid var(--border)',
|
|
133
|
+
borderRadius: 12,
|
|
134
|
+
padding: 24,
|
|
135
|
+
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.5)'
|
|
136
|
+
}}>
|
|
137
|
+
<h3 style={{ marginBottom: 12, color: 'var(--text-primary)' }}>Confirm Core Self-Update</h3>
|
|
138
|
+
<p style={{ color: 'var(--text-muted)', marginBottom: 20, fontSize: 14, lineHeight: 1.5 }}>
|
|
139
|
+
This will pull the latest version of Total Recall ({updateInfo?.latestVersion}) from the git repository, reinstall dependencies, rebuild frontend bundles, and reboot the system kernel.
|
|
140
|
+
</p>
|
|
141
|
+
<div style={{ display: 'flex', gap: 12, justifyContent: 'flex-end' }}>
|
|
142
|
+
<button
|
|
143
|
+
className="btn btn-secondary"
|
|
144
|
+
onClick={() => setShowConfirmModal(false)}
|
|
145
|
+
style={{ padding: '8px 16px', borderRadius: 8 }}
|
|
146
|
+
>
|
|
147
|
+
Cancel
|
|
148
|
+
</button>
|
|
149
|
+
<button
|
|
150
|
+
className="btn btn-primary"
|
|
151
|
+
onClick={handleStartUpdate}
|
|
152
|
+
style={{ padding: '8px 16px', borderRadius: 8, background: 'var(--accent)' }}
|
|
153
|
+
>
|
|
154
|
+
Update Now
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
162
|
+
<div>
|
|
163
|
+
<h1>System Health</h1>
|
|
164
|
+
<p>Live monitoring of your sovereign brain {lastPoll && <span>· Last poll: {lastPoll.toLocaleTimeString()}</span>}</p>
|
|
165
|
+
</div>
|
|
166
|
+
{updateInfo?.updateAvailable && (
|
|
167
|
+
<div className="badge badge-success" style={{
|
|
168
|
+
display: 'flex',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
gap: 12,
|
|
171
|
+
padding: '12px 18px',
|
|
172
|
+
borderRadius: 12,
|
|
173
|
+
background: 'rgba(63, 185, 80, 0.1)',
|
|
174
|
+
border: '1px solid rgba(63, 185, 80, 0.3)',
|
|
175
|
+
animation: 'pulse 2s infinite'
|
|
176
|
+
}}>
|
|
177
|
+
<span style={{ fontSize: 14, color: 'var(--text-primary)', fontWeight: 600 }}>
|
|
178
|
+
New version available: {updateInfo.latestVersion}
|
|
179
|
+
</span>
|
|
180
|
+
<button
|
|
181
|
+
className="btn btn-primary"
|
|
182
|
+
onClick={() => setShowConfirmModal(true)}
|
|
183
|
+
style={{
|
|
184
|
+
fontSize: 12,
|
|
185
|
+
padding: '6px 12px',
|
|
186
|
+
borderRadius: 6,
|
|
187
|
+
backgroundColor: '#3fb950',
|
|
188
|
+
color: '#fff',
|
|
189
|
+
border: 'none',
|
|
190
|
+
cursor: 'pointer',
|
|
191
|
+
fontWeight: 600
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
Update Now
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
44
198
|
</div>
|
|
45
199
|
|
|
46
200
|
{error && <div className="badge badge-error" style={{ marginBottom: 16 }}>⚠️ {error}</div>}
|
|
201
|
+
{updateError && <div className="badge badge-error" style={{ marginBottom: 16 }}>⚠️ Update failed: {updateError}</div>}
|
|
47
202
|
|
|
48
203
|
{health ? (
|
|
49
204
|
<>
|
|
@@ -55,7 +210,9 @@ export default function HealthPage() {
|
|
|
55
210
|
<div className="label">Status</div>
|
|
56
211
|
</div>
|
|
57
212
|
<div className="stat-card">
|
|
58
|
-
<div className="value" style={{ color: 'var(--accent)' }}>
|
|
213
|
+
<div className="value" style={{ color: 'var(--accent)' }}>
|
|
214
|
+
{health.version} {updateInfo && !updateInfo.updateAvailable && <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>(Latest)</span>}
|
|
215
|
+
</div>
|
|
59
216
|
<div className="label">Version</div>
|
|
60
217
|
</div>
|
|
61
218
|
<div className="stat-card">
|
|
@@ -76,6 +233,10 @@ export default function HealthPage() {
|
|
|
76
233
|
<tr><th>Version</th><td>{health.version}</td></tr>
|
|
77
234
|
<tr><th>Uptime</th><td>{formatUptime(health.uptime_seconds)}</td></tr>
|
|
78
235
|
<tr><th>Timestamp</th><td>{health.timestamp}</td></tr>
|
|
236
|
+
<tr><th>Daemon Status</th><td>{health.daemon || 'unknown'}</td></tr>
|
|
237
|
+
{health.cli_agents && (
|
|
238
|
+
<tr><th>CLI Agents</th><td>{health.cli_agents.join(', ')}</td></tr>
|
|
239
|
+
)}
|
|
79
240
|
<tr><th>Polling</th><td>Every 5 seconds</td></tr>
|
|
80
241
|
</tbody>
|
|
81
242
|
</table>
|
|
@@ -86,6 +247,17 @@ export default function HealthPage() {
|
|
|
86
247
|
{[1, 2, 3, 4].map(i => <div key={i} className="skeleton" style={{ height: 100 }} />)}
|
|
87
248
|
</div>
|
|
88
249
|
) : null}
|
|
250
|
+
|
|
251
|
+
<style>{`
|
|
252
|
+
@keyframes spin {
|
|
253
|
+
0% { transform: rotate(0deg); }
|
|
254
|
+
100% { transform: rotate(360deg); }
|
|
255
|
+
}
|
|
256
|
+
@keyframes fadeIn {
|
|
257
|
+
from { opacity: 0; }
|
|
258
|
+
to { opacity: 1; }
|
|
259
|
+
}
|
|
260
|
+
`}</style>
|
|
89
261
|
</div>
|
|
90
262
|
)
|
|
91
263
|
}
|
|
@@ -391,10 +391,12 @@ export default function IntegrationsPage() {
|
|
|
391
391
|
)
|
|
392
392
|
|
|
393
393
|
if (matchingKey) {
|
|
394
|
-
if (matchingKey.hit_count > 0) {
|
|
394
|
+
if (matchingKey.hit_count > 0 || preset.id === 'antigravity') {
|
|
395
395
|
const lastUsedStr = matchingKey.last_used_at
|
|
396
396
|
? ` (Active ${new Date(matchingKey.last_used_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })})`
|
|
397
|
-
: ''
|
|
397
|
+
: preset.id === 'antigravity'
|
|
398
|
+
? ' (Active Now)'
|
|
399
|
+
: ''
|
|
398
400
|
statusBadge = (
|
|
399
401
|
<span style={{
|
|
400
402
|
display: 'inline-flex',
|