total-recall-brain 3.2.3 → 3.4.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/extension/background.js +14 -2
- package/extension/content-script.js +18 -1
- package/extension/lib/brain-client.js +86 -52
- package/extension/lib/preconfigured.js +5 -0
- package/extension/manifest.json +1 -1
- package/extension/options/options.html +1 -0
- package/extension/options/options.js +12 -6
- package/extension/popup/popup.html +1 -0
- package/extension/popup/popup.js +29 -4
- package/extension/sidepanel/sidepanel.html +1 -0
- package/extension/sidepanel/sidepanel.js +24 -6
- package/frontend/index.html +8 -0
- package/frontend/src/api.ts +1 -2
- package/frontend/src/pages/ChatPage.tsx +9 -6
- package/frontend/src/pages/IntegrationsPage.tsx +144 -3
- 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/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/init.mjs +30 -0
- package/src/cli/map.mjs +2 -2
- package/src/cli/rebuild.mjs +1 -1
- package/src/cli/remember.mjs +21 -11
- package/src/cli/setup.mjs +10 -1
- 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 +2 -2
- package/src/core/sandbox.mjs +56 -26
- package/src/core/scheduler.mjs +6 -5
- package/src/core/schema.mjs +2 -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 +238 -29
- package/src/server/route-manifest.json +344 -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/extension/background.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Total Recall — Background Service Worker
|
|
2
2
|
// Handles context menus, message routing, and passive browsing buffer.
|
|
3
3
|
|
|
4
|
-
importScripts('lib/brain-client.js');
|
|
4
|
+
importScripts('lib/preconfigured.js', 'lib/brain-client.js');
|
|
5
5
|
|
|
6
6
|
// Destructure for convenience after importScripts populates self.BrainClient
|
|
7
7
|
const { share, search, healthCheck } = self.BrainClient;
|
|
@@ -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
|
|
|
@@ -2,64 +2,98 @@
|
|
|
2
2
|
// Shared by background.js, popup, sidepanel, and options page.
|
|
3
3
|
// Exposed as `self.BrainClient` namespace (no ES-module bundler required).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
(function () {
|
|
6
|
+
const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
|
|
6
7
|
|
|
7
|
-
async function getConfig() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
async function getConfig() {
|
|
9
|
+
const [localConfig, syncConfig] = await Promise.all([
|
|
10
|
+
chrome.storage.local.get(['brainUrl', 'pat']),
|
|
11
|
+
chrome.storage.sync.get(['brainUrl', 'pat'])
|
|
12
|
+
]);
|
|
13
|
+
const pre = self.PreConfigured || {};
|
|
14
|
+
const config = {
|
|
15
|
+
brainUrl: localConfig.brainUrl || syncConfig.brainUrl,
|
|
16
|
+
pat: localConfig.pat || syncConfig.pat
|
|
17
|
+
};
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (pat) headers['Authorization'] = `Bearer ${pat}`;
|
|
19
|
+
if (syncConfig.pat && !localConfig.pat) {
|
|
20
|
+
await chrome.storage.local.set({ pat: syncConfig.pat });
|
|
21
|
+
await chrome.storage.sync.remove('pat');
|
|
22
|
+
}
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
throw new Error('Authentication failed — check your PAT token');
|
|
24
|
+
return {
|
|
25
|
+
brainUrl: config.brainUrl || pre.brainUrl || DEFAULT_BRAIN_URL,
|
|
26
|
+
pat: config.pat || pre.pat || ''
|
|
27
|
+
};
|
|
26
28
|
}
|
|
27
|
-
if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
|
|
28
|
-
return res.json();
|
|
29
|
-
}
|
|
30
29
|
|
|
31
|
-
async function
|
|
32
|
-
|
|
33
|
-
}
|
|
30
|
+
async function brainFetch(path, options = {}) {
|
|
31
|
+
const { brainUrl, pat } = await getConfig();
|
|
32
|
+
const url = `${brainUrl}${path}`;
|
|
33
|
+
const headers = { 'Content-Type': 'application/json', ...options.headers };
|
|
34
|
+
if (pat) headers['Authorization'] = `Bearer ${pat}`;
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
36
|
+
const res = await fetch(url, { ...options, headers });
|
|
37
|
+
if (res.status === 401) {
|
|
38
|
+
chrome.action.setBadgeText({ text: '!' });
|
|
39
|
+
chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
|
|
40
|
+
throw new Error('Authentication failed — check your PAT token');
|
|
41
|
+
}
|
|
42
|
+
if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
|
|
43
|
+
return res.json();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function share(data) {
|
|
47
|
+
return brainFetch('/api/share', { method: 'POST', body: JSON.stringify(data) });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function search(query, topK = 3) {
|
|
51
|
+
return brainFetch('/api/memory/search/semantic', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: JSON.stringify({ query, top_k: topK })
|
|
54
|
+
});
|
|
55
|
+
}
|
|
41
56
|
|
|
42
|
-
async function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
async function healthCheck() {
|
|
58
|
+
return brainFetch('/health');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function chat(messages, options = {}) {
|
|
62
|
+
const { brainUrl, pat } = await getConfig();
|
|
63
|
+
const url = `${brainUrl}/v1/chat/completions`;
|
|
64
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
65
|
+
if (pat) headers['Authorization'] = `Bearer ${pat}`;
|
|
66
|
+
if (options.sessionId) headers['x-session-id'] = options.sessionId;
|
|
48
67
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
68
|
+
const res = await fetch(url, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers,
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
messages,
|
|
73
|
+
model: options.model || 'gemini',
|
|
74
|
+
brainId: options.brainId || 'global',
|
|
75
|
+
groundingNodes: options.groundingNodes || []
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
if (res.status === 401) throw new Error('Authentication failed — check your PAT token');
|
|
79
|
+
if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
|
|
80
|
+
return res.json();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Self-heal stale storage: if preconfigured values exist, write them to extension-local storage.
|
|
84
|
+
try {
|
|
85
|
+
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
|
86
|
+
chrome.storage.local.get(['brainUrl', 'pat']).then((config) => {
|
|
87
|
+
const pre = self.PreConfigured || {};
|
|
88
|
+
if (pre.pat && (!config.pat || config.pat !== pre.pat)) {
|
|
89
|
+
chrome.storage.local.set({ brainUrl: pre.brainUrl, pat: pre.pat });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// Ignore in contexts where storage sync isn't available
|
|
95
|
+
}
|
|
63
96
|
|
|
64
|
-
// Expose as a global namespace for non-module contexts (service worker, popup, etc.)
|
|
65
|
-
self.BrainClient = { getConfig, brainFetch, share, search, healthCheck, chat };
|
|
97
|
+
// Expose as a global namespace for non-module contexts (service worker, popup, etc.)
|
|
98
|
+
self.BrainClient = { getConfig, brainFetch, share, search, healthCheck, chat };
|
|
99
|
+
})();
|
package/extension/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Total Recall — Brain Extension",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"description": "Contextual memory overlay, Share-to-Brain, and passive browsing context for Total Recall",
|
|
6
|
-
"permissions": ["activeTab", "contextMenus", "sidePanel", "storage"],
|
|
6
|
+
"permissions": ["activeTab", "contextMenus", "sidePanel", "storage", "tabs"],
|
|
7
7
|
"optional_permissions": ["history", "webNavigation"],
|
|
8
8
|
"host_permissions": ["http://127.0.0.1:*/*", "http://localhost:*/*"],
|
|
9
9
|
"background": {
|
|
@@ -13,15 +13,18 @@
|
|
|
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
|
-
|
|
24
|
-
|
|
25
|
+
const pre = self.PreConfigured || {};
|
|
26
|
+
brainUrlInput.value = settings.brainUrl || pre.brainUrl || 'http://127.0.0.1:3000';
|
|
27
|
+
patInput.value = settings.pat || pre.pat || '';
|
|
25
28
|
|
|
26
29
|
// Blocklist: stored as array, displayed one per line
|
|
27
30
|
if (Array.isArray(settings.blocklist)) {
|
|
@@ -47,9 +50,12 @@
|
|
|
47
50
|
const granularityRadio = document.querySelector('input[name="granularity"]:checked');
|
|
48
51
|
const captureGranularity = granularityRadio ? granularityRadio.value : 'minimal';
|
|
49
52
|
|
|
50
|
-
await chrome.storage.
|
|
53
|
+
await chrome.storage.local.set({
|
|
51
54
|
brainUrl: brainUrlInput.value.trim() || 'http://127.0.0.1:3000',
|
|
52
55
|
pat: patInput.value.trim(),
|
|
56
|
+
});
|
|
57
|
+
await chrome.storage.sync.remove('pat');
|
|
58
|
+
await chrome.storage.sync.set({
|
|
53
59
|
blocklist,
|
|
54
60
|
captureGranularity
|
|
55
61
|
});
|
package/extension/popup/popup.js
CHANGED
|
@@ -59,14 +59,27 @@
|
|
|
59
59
|
btnRemember.classList.add('loading');
|
|
60
60
|
try {
|
|
61
61
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
62
|
+
const note = noteInput.value.trim();
|
|
63
|
+
const targetUrl = tab?.url || urlInput.value || '';
|
|
64
|
+
|
|
65
|
+
// If we don't have a URL, we must have a note/excerpt
|
|
66
|
+
if (!targetUrl && !note) {
|
|
67
|
+
throw new Error('Please enter a note or browse to a webpage to remember.');
|
|
68
|
+
}
|
|
69
|
+
if (targetUrl && (targetUrl.startsWith('chrome://') || targetUrl.startsWith('about:') || targetUrl.startsWith('chrome-extension://'))) {
|
|
70
|
+
if (!note) {
|
|
71
|
+
throw new Error('Cannot capture system pages. Please enter a note to save.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
const payload = {
|
|
63
|
-
url:
|
|
76
|
+
url: targetUrl && !targetUrl.startsWith('chrome://') && !targetUrl.startsWith('about:') && !targetUrl.startsWith('chrome-extension://') ? targetUrl : undefined,
|
|
64
77
|
title: tab?.title || '',
|
|
65
78
|
action: 'remember',
|
|
66
79
|
source: 'chrome-extension-popup'
|
|
67
80
|
};
|
|
68
|
-
const note = noteInput.value.trim();
|
|
69
81
|
if (note) payload.excerpt = note;
|
|
82
|
+
|
|
70
83
|
await sendShare(payload);
|
|
71
84
|
showStatus('Page remembered!');
|
|
72
85
|
noteInput.value = '';
|
|
@@ -88,14 +101,26 @@
|
|
|
88
101
|
btnResearch.classList.add('loading');
|
|
89
102
|
try {
|
|
90
103
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
104
|
+
const note = noteInput.value.trim();
|
|
105
|
+
const targetUrl = tab?.url || urlInput.value || '';
|
|
106
|
+
|
|
107
|
+
if (!targetUrl && !note) {
|
|
108
|
+
throw new Error('Please enter research notes or browse to a webpage to queue research.');
|
|
109
|
+
}
|
|
110
|
+
if (targetUrl && (targetUrl.startsWith('chrome://') || targetUrl.startsWith('about:') || targetUrl.startsWith('chrome-extension://'))) {
|
|
111
|
+
if (!note) {
|
|
112
|
+
throw new Error('Cannot research system pages. Please enter research notes.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
91
116
|
const payload = {
|
|
92
|
-
url:
|
|
117
|
+
url: targetUrl && !targetUrl.startsWith('chrome://') && !targetUrl.startsWith('about:') && !targetUrl.startsWith('chrome-extension://') ? targetUrl : undefined,
|
|
93
118
|
title: tab?.title || '',
|
|
94
119
|
action: 'research',
|
|
95
120
|
source: 'chrome-extension-popup'
|
|
96
121
|
};
|
|
97
|
-
const note = noteInput.value.trim();
|
|
98
122
|
if (note) payload.excerpt = note;
|
|
123
|
+
|
|
99
124
|
await sendShare(payload);
|
|
100
125
|
showStatus('Research queued!');
|
|
101
126
|
noteInput.value = '';
|
|
@@ -183,9 +183,15 @@
|
|
|
183
183
|
btnRemember.classList.add('loading');
|
|
184
184
|
try {
|
|
185
185
|
const tab = await getCurrentTab();
|
|
186
|
+
if (!tab || !tab.url) {
|
|
187
|
+
throw new Error('No active webpage found to capture. Make sure you are on a webpage tab.');
|
|
188
|
+
}
|
|
189
|
+
if (tab.url.startsWith('chrome://') || tab.url.startsWith('about:') || tab.url.startsWith('chrome-extension://')) {
|
|
190
|
+
throw new Error('Cannot capture system pages.');
|
|
191
|
+
}
|
|
186
192
|
await sendShare({
|
|
187
193
|
url: tab.url,
|
|
188
|
-
title: tab.title,
|
|
194
|
+
title: tab.title || 'Untitled Page',
|
|
189
195
|
action: 'remember',
|
|
190
196
|
source: 'chrome-extension-sidepanel'
|
|
191
197
|
});
|
|
@@ -208,9 +214,15 @@
|
|
|
208
214
|
btnResearch.classList.add('loading');
|
|
209
215
|
try {
|
|
210
216
|
const tab = await getCurrentTab();
|
|
217
|
+
if (!tab || !tab.url) {
|
|
218
|
+
throw new Error('No active webpage found to capture. Make sure you are on a webpage tab.');
|
|
219
|
+
}
|
|
220
|
+
if (tab.url.startsWith('chrome://') || tab.url.startsWith('about:') || tab.url.startsWith('chrome-extension://')) {
|
|
221
|
+
throw new Error('Cannot capture system pages.');
|
|
222
|
+
}
|
|
211
223
|
await sendShare({
|
|
212
224
|
url: tab.url,
|
|
213
|
-
title: tab.title,
|
|
225
|
+
title: tab.title || 'Untitled Page',
|
|
214
226
|
action: 'research',
|
|
215
227
|
source: 'chrome-extension-sidepanel'
|
|
216
228
|
});
|
|
@@ -245,13 +257,19 @@
|
|
|
245
257
|
btnSaveNote.disabled = true;
|
|
246
258
|
btnSaveNote.classList.add('loading');
|
|
247
259
|
try {
|
|
248
|
-
|
|
260
|
+
let tab = null;
|
|
261
|
+
try {
|
|
262
|
+
tab = await getCurrentTab();
|
|
263
|
+
} catch {
|
|
264
|
+
// ignore
|
|
265
|
+
}
|
|
266
|
+
const hasValidUrl = tab && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('about:') && !tab.url.startsWith('chrome-extension://');
|
|
249
267
|
await sendShare({
|
|
250
|
-
url: tab.url,
|
|
251
|
-
title: `Note: ${text.slice(0, 50)}
|
|
268
|
+
url: hasValidUrl ? tab.url : undefined,
|
|
269
|
+
title: hasValidUrl ? `Note: ${text.slice(0, 50)}` : undefined,
|
|
252
270
|
excerpt: text,
|
|
253
271
|
action: 'remember',
|
|
254
|
-
category: '
|
|
272
|
+
category: 'facts',
|
|
255
273
|
source: 'chrome-extension-sidepanel'
|
|
256
274
|
});
|
|
257
275
|
noteInput.value = '';
|
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
|
})
|
|
@@ -685,4 +685,3 @@ export async function fetchExtensionStatus(): Promise<{ available: boolean; conn
|
|
|
685
685
|
if (!res.ok) return { available: false, connected: false }
|
|
686
686
|
return res.json()
|
|
687
687
|
}
|
|
688
|
-
|
|
@@ -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
|
|