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.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/extension/background.js +14 -2
  3. package/extension/content-script.js +18 -1
  4. package/extension/lib/brain-client.js +86 -52
  5. package/extension/lib/preconfigured.js +5 -0
  6. package/extension/manifest.json +1 -1
  7. package/extension/options/options.html +1 -0
  8. package/extension/options/options.js +12 -6
  9. package/extension/popup/popup.html +1 -0
  10. package/extension/popup/popup.js +29 -4
  11. package/extension/sidepanel/sidepanel.html +1 -0
  12. package/extension/sidepanel/sidepanel.js +24 -6
  13. package/frontend/index.html +8 -0
  14. package/frontend/src/api.ts +1 -2
  15. package/frontend/src/pages/ChatPage.tsx +9 -6
  16. package/frontend/src/pages/IntegrationsPage.tsx +144 -3
  17. package/package.json +2 -2
  18. package/scaffold/.agent/skills/total-recall/SKILL.md +55 -6
  19. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -2
  20. package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -6
  21. package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +19 -0
  22. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +33 -5
  23. package/src/cli/forget.mjs +18 -12
  24. package/src/cli/ingest/index.mjs +3 -2
  25. package/src/cli/init.mjs +30 -0
  26. package/src/cli/map.mjs +2 -2
  27. package/src/cli/rebuild.mjs +1 -1
  28. package/src/cli/remember.mjs +21 -11
  29. package/src/cli/setup.mjs +10 -1
  30. package/src/cli/share.mjs +7 -2
  31. package/src/core/append-log.mjs +131 -0
  32. package/src/core/clarity-rewriter.mjs +10 -6
  33. package/src/core/conclusion-writer.mjs +3 -2
  34. package/src/core/config.mjs +6 -0
  35. package/src/core/conflict-detector.mjs +4 -2
  36. package/src/core/context-compiler.mjs +412 -0
  37. package/src/core/daemon-loop.mjs +15 -0
  38. package/src/core/dream.mjs +4 -4
  39. package/src/core/drift-detector.mjs +3 -3
  40. package/src/core/embeddings.mjs +60 -10
  41. package/src/core/fact-seeker.mjs +70 -44
  42. package/src/core/import-rules.mjs +3 -2
  43. package/src/core/inference-engine.mjs +5 -4
  44. package/src/core/migrate.mjs +4 -3
  45. package/src/core/optimizer.mjs +6 -5
  46. package/src/core/parallel-context.mjs +288 -0
  47. package/src/core/post-mortem.mjs +4 -3
  48. package/src/core/research-queue.mjs +4 -3
  49. package/src/core/runtime.mjs +2 -2
  50. package/src/core/sandbox.mjs +56 -26
  51. package/src/core/scheduler.mjs +6 -5
  52. package/src/core/schema.mjs +2 -2
  53. package/src/core/search.mjs +167 -101
  54. package/src/core/surface.mjs +203 -53
  55. package/src/core/validated-write.mjs +100 -0
  56. package/src/core/vault-cache.mjs +46 -18
  57. package/src/core/vault.mjs +17 -29
  58. package/src/core/vector-field.mjs +659 -0
  59. package/src/core/vector-store.mjs +85 -0
  60. package/src/server/api.mjs +4 -3
  61. package/src/server/index.mjs +13 -61
  62. package/src/server/rest.mjs +238 -29
  63. package/src/server/route-manifest.json +344 -77
  64. package/src/server/routes/memory.mjs +85 -39
  65. package/src/server/routes/sessions.mjs +6 -2
  66. 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
- [![Version](https://img.shields.io/badge/version-3.0.0-indigo.svg)](package.json)
3
+ [![Version](https://img.shields.io/badge/version-3.3.0-indigo.svg)](package.json)
4
4
  [![License](https://img.shields.io/badge/license-MIT-emerald.svg)](LICENSE)
5
5
  [![Continuous Integration](https://img.shields.io/badge/CI-passing-teal.svg)](.github/workflows/test.yml)
6
6
 
@@ -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, _sender, sendResponse) => {
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
- const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
5
+ (function () {
6
+ const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
6
7
 
7
- async function getConfig() {
8
- const config = await chrome.storage.sync.get(['brainUrl', 'pat']);
9
- return {
10
- brainUrl: config.brainUrl || DEFAULT_BRAIN_URL,
11
- pat: config.pat || ''
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
- async function brainFetch(path, options = {}) {
16
- const { brainUrl, pat } = await getConfig();
17
- const url = `${brainUrl}${path}`;
18
- const headers = { 'Content-Type': 'application/json', ...options.headers };
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
- const res = await fetch(url, { ...options, headers });
22
- if (res.status === 401) {
23
- chrome.action.setBadgeText({ text: '!' });
24
- chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
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 share(data) {
32
- return brainFetch('/api/share', { method: 'POST', body: JSON.stringify(data) });
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
- async function search(query, topK = 3) {
36
- return brainFetch('/api/memory/search/semantic', {
37
- method: 'POST',
38
- body: JSON.stringify({ query, top_k: topK })
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 chat(messages, options = {}) {
43
- const { brainUrl, pat } = await getConfig();
44
- const url = `${brainUrl}/v1/chat/completions`;
45
- const headers = { 'Content-Type': 'application/json' };
46
- if (pat) headers['Authorization'] = `Bearer ${pat}`;
47
- if (options.sessionId) headers['x-session-id'] = options.sessionId;
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
- const res = await fetch(url, {
50
- method: 'POST',
51
- headers,
52
- body: JSON.stringify({
53
- messages,
54
- model: options.model || 'gemini',
55
- brainId: options.brainId || 'global',
56
- groundingNodes: options.groundingNodes || []
57
- })
58
- });
59
- if (res.status === 401) throw new Error('Authentication failed — check your PAT token');
60
- if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
61
- return res.json();
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
+ })();
@@ -0,0 +1,5 @@
1
+ // Exposes default configuration dynamically injected by the server
2
+ self.PreConfigured = {
3
+ brainUrl: '',
4
+ pat: ''
5
+ };
@@ -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": {
@@ -66,6 +66,7 @@
66
66
  </div>
67
67
  </div>
68
68
 
69
+ <script src="../lib/preconfigured.js"></script>
69
70
  <script src="../lib/brain-client.js"></script>
70
71
  <script src="options.js"></script>
71
72
  </body>
@@ -13,15 +13,18 @@
13
13
 
14
14
  // ---- Load saved settings ----
15
15
  async function loadSettings() {
16
- const settings = await chrome.storage.sync.get([
17
- 'brainUrl',
18
- 'pat',
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
- brainUrlInput.value = settings.brainUrl || 'http://127.0.0.1:3000';
24
- patInput.value = settings.pat || '';
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.sync.set({
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
  });
@@ -38,6 +38,7 @@
38
38
  <div id="status-msg" class="status-msg hidden"></div>
39
39
  </div>
40
40
 
41
+ <script src="../lib/preconfigured.js"></script>
41
42
  <script src="../lib/brain-client.js"></script>
42
43
  <script src="popup.js"></script>
43
44
  </body>
@@ -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: tab?.url || urlInput.value,
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: tab?.url || urlInput.value,
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 = '';
@@ -130,6 +130,7 @@
130
130
 
131
131
  </div>
132
132
  </div>
133
+ <script src="../lib/preconfigured.js"></script>
133
134
  <script src="../lib/brain-client.js"></script>
134
135
  <script src="sidepanel.js"></script>
135
136
  </body>
@@ -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
- const tab = await getCurrentTab();
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: 'fact',
272
+ category: 'facts',
255
273
  source: 'chrome-extension-sidepanel'
256
274
  });
257
275
  noteInput.value = '';
@@ -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>
@@ -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: "PUT",
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>(() => localStorage.getItem('selectedModel') || '')
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', 'gemini', 'claude', 'codex'];
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('gemini') ? 'gemini' : agents[0];
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', 'gemini', 'claude', 'codex'];
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('gemini');
127
- localStorage.setItem('selectedModel', 'gemini');
129
+ setSelectedModel('antigravity');
130
+ localStorage.setItem('selectedModel', 'antigravity');
128
131
  }
129
132
  })
130
133