total-recall-brain 3.6.4 → 3.6.5

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.
@@ -15,7 +15,7 @@ import ApiKeysPage from './pages/ApiKeysPage'
15
15
  import IntegrationsPage from './pages/IntegrationsPage'
16
16
  import BrainSelector from './components/BrainSelector'
17
17
  import AutomationsPage from './pages/AutomationsPage'
18
- import DeploymentsPage from './pages/DeploymentsPage'
18
+ import ModelsPage from './pages/ModelsPage'
19
19
  import SkillsPage from './pages/SkillsPage'
20
20
  import UsagePage from './pages/UsagePage'
21
21
  import CollabPage from './pages/CollabPage'
@@ -99,14 +99,11 @@ function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProp
99
99
  </svg>
100
100
  Sandbox
101
101
  </NavLink>
102
- <NavLink to="/deployments" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-deployments">
102
+ <NavLink to="/models" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-models">
103
103
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
104
- <rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
105
- <rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
106
- <line x1="6" y1="6" x2="6.01" y2="6" />
107
- <line x1="6" y1="18" x2="6.01" y2="18" />
104
+ <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line>
108
105
  </svg>
109
- Deployments
106
+ Models & Agents
110
107
  </NavLink>
111
108
  <NavLink to="/health" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-health">
112
109
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
@@ -312,7 +309,7 @@ function MainContent({ activeBrainId, onBrainChange }: { activeBrainId: string;
312
309
  <Route path="/automations" element={<AutomationsPage />} />
313
310
  <Route path="/files" element={<FilesPage />} />
314
311
  <Route path="/sandbox" element={<SandboxPage />} />
315
- <Route path="/deployments" element={<DeploymentsPage />} />
312
+ <Route path="/models" element={<ModelsPage />} />
316
313
  <Route path="/health" element={<HealthPage />} />
317
314
  <Route path="/usage" element={<UsagePage />} />
318
315
  <Route path="/settings" element={<SettingsPage />} />
@@ -181,9 +181,14 @@ export default function BrainSelector({ activeBrainId, onBrainChange }: BrainSel
181
181
  {brains.map(brain => {
182
182
  const isSelected = selectedIds.includes(brain.id);
183
183
  return (
184
- <button
184
+ <div
185
185
  key={brain.id}
186
- onClick={(e) => handleRowClick(brain.id, e)}
186
+ onClick={(e) => {
187
+ if (brain.exists) {
188
+ handleRowClick(brain.id, e);
189
+ setExpanded(false); // Close dropdown on single selection
190
+ }
191
+ }}
187
192
  style={{
188
193
  width: '100%',
189
194
  padding: '8px 10px',
@@ -201,7 +206,8 @@ export default function BrainSelector({ activeBrainId, onBrainChange }: BrainSel
201
206
  }}
202
207
  onMouseEnter={e => { if (brain.exists) (e.currentTarget as HTMLElement).style.background = 'var(--bg-tertiary)' }}
203
208
  onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = isSelected ? 'var(--accent-faint)' : 'transparent' }}
204
- disabled={!brain.exists}
209
+ role="button"
210
+ tabIndex={0}
205
211
  >
206
212
  <input
207
213
  type="checkbox"
@@ -247,7 +253,7 @@ export default function BrainSelector({ activeBrainId, onBrainChange }: BrainSel
247
253
  }}>
248
254
  {brain.layer}
249
255
  </span>
250
- </button>
256
+ </div>
251
257
  );
252
258
  })}
253
259
  </div>
@@ -271,6 +271,7 @@ code, pre { font-family: var(--font-mono); }
271
271
  /* ─── Chat ───────────────────────────────────────────────────────────────────── */
272
272
 
273
273
  .chat-container {
274
+ flex: 1;
274
275
  display: flex;
275
276
  flex-direction: column;
276
277
  height: calc(100vh - 1px);
@@ -718,6 +718,8 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
718
718
  flexDirection: 'column',
719
719
  gap: '16px',
720
720
  width: '100%',
721
+ maxWidth: 800,
722
+ margin: '0 auto',
721
723
  pointerEvents: 'auto'
722
724
  }}>
723
725
  {messages.map((m, index) => (
@@ -1,11 +1,12 @@
1
- import { useState, useEffect, useRef } from 'react';
2
- import { fetchHealth, fetchLogs, runAgentDiagnostics } from '../api';
3
- import type { HealthData } from '../types';
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { fetchHealth, runAgentDiagnostics, fetchConfigJson, saveConfigJson } from '../api';
3
+ import type { HealthData, ConfigJson } from '../types';
4
4
 
5
- export default function DeploymentsPage() {
5
+ export default function ModelsPage() {
6
6
  const [health, setHealth] = useState<HealthData | null>(null);
7
- const [logs, setLogs] = useState('(loading live cognitive activity console streams...)');
7
+ const [configData, setConfigData] = useState<ConfigJson | null>(null);
8
8
  const [loading, setLoading] = useState(true);
9
+ const [saving, setSaving] = useState(false);
9
10
  const [error, setError] = useState<string | null>(null);
10
11
  const [success, setSuccess] = useState<string | null>(null);
11
12
 
@@ -13,36 +14,44 @@ export default function DeploymentsPage() {
13
14
  const [runningDiagnostics, setRunningDiagnostics] = useState(false);
14
15
  const [diagnosticLogs, setDiagnosticLogs] = useState<string | null>(null);
15
16
 
16
- const consoleEndRef = useRef<HTMLDivElement>(null);
17
-
18
- const fetchSystemData = async () => {
17
+ const fetchSystemData = useCallback(async () => {
19
18
  try {
20
19
  const systemHealth = await fetchHealth();
21
20
  setHealth(systemHealth);
22
21
 
23
- const daemonLogs = await fetchLogs('daemon');
24
- setLogs(daemonLogs.content || '(no console activity streamed yet)');
22
+ const data = await fetchConfigJson();
23
+ if (!data.secrets) data.secrets = {};
24
+ if (!data.brain) data.brain = {};
25
+ setConfigData(data);
26
+
25
27
  setError(null);
26
28
  } catch (err: unknown) {
27
- setError((err as Error).message || 'Failed to sync system health.');
29
+ setError((err as Error).message || 'Failed to sync system data.');
28
30
  } finally {
29
31
  setLoading(false);
30
32
  }
31
- };
33
+ }, []);
32
34
 
33
35
  useEffect(() => {
34
36
  // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate metrics polling on mount
35
37
  void fetchSystemData();
36
- const interval = setInterval(fetchSystemData, 4000);
37
- return () => clearInterval(interval);
38
- }, []);
38
+ }, [fetchSystemData]);
39
39
 
40
- useEffect(() => {
41
- // Auto-scroll the retro console log to bottom
42
- if (consoleEndRef.current) {
43
- consoleEndRef.current.scrollIntoView({ behavior: 'smooth' });
40
+ const handleSaveConfig = async () => {
41
+ if (!configData) return;
42
+ setSaving(true);
43
+ setError(null);
44
+ setSuccess(null);
45
+ try {
46
+ await saveConfigJson(configData);
47
+ setSuccess('Configuration saved successfully. Kernel will hot-reload settings.');
48
+ setTimeout(() => setSuccess(null), 4000);
49
+ } catch (err: unknown) {
50
+ setError((err as Error).message || 'Failed to save configuration.');
51
+ } finally {
52
+ setSaving(false);
44
53
  }
45
- }, [logs]);
54
+ };
46
55
 
47
56
  const handleRunDiagnostics = async (e: React.FormEvent) => {
48
57
  e.preventDefault();
@@ -66,6 +75,28 @@ export default function DeploymentsPage() {
66
75
  }
67
76
  };
68
77
 
78
+ const updateSecretsProp = (prop: string, value: string) => {
79
+ if (!configData) return;
80
+ setConfigData({
81
+ ...configData,
82
+ secrets: {
83
+ ...configData.secrets,
84
+ [prop]: value
85
+ }
86
+ });
87
+ };
88
+
89
+ const updateBrainProp = (prop: string, value: string) => {
90
+ if (!configData) return;
91
+ setConfigData({
92
+ ...configData,
93
+ brain: {
94
+ ...configData.brain,
95
+ [prop]: value
96
+ }
97
+ });
98
+ };
99
+
69
100
  const AGENTS_LIST = [
70
101
  { id: 'antigravity', name: 'Antigravity (Gemini SDK)', desc: 'Primary core developer agent' },
71
102
  { id: 'gemini', name: 'Gemini CLI', desc: 'Direct Gemini assistant binary' },
@@ -76,9 +107,21 @@ export default function DeploymentsPage() {
76
107
 
77
108
  return (
78
109
  <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
79
- <div className="page-header" style={{ marginBottom: 24 }}>
80
- <h1>Sovereign Stack Deployments</h1>
81
- <p>Monitor your machine services, pull kernel intelligence models, and stream activity logs</p>
110
+ <div className="page-header" style={{ marginBottom: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
111
+ <div>
112
+ <h1>Models & Agents</h1>
113
+ <p>Configure Bring Your Own Model (BYOM) settings and manage active reasoning agents</p>
114
+ </div>
115
+ {configData && (
116
+ <button
117
+ onClick={handleSaveConfig}
118
+ disabled={saving}
119
+ className="btn btn-primary"
120
+ style={{ minWidth: 120 }}
121
+ >
122
+ {saving ? 'Saving...' : 'Save Configuration'}
123
+ </button>
124
+ )}
82
125
  </div>
83
126
 
84
127
  {error && (
@@ -97,63 +140,143 @@ export default function DeploymentsPage() {
97
140
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite', marginBottom: 12 }}>
98
141
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
99
142
  </svg>
100
- <div>Checking deployment gateways...</div>
143
+ <div>Loading models and agents...</div>
101
144
  </div>
102
145
  ) : (
103
146
  <div style={{ display: 'flex', flexDirection: 'column', gap: 24, flex: 1, minHeight: 0 }}>
104
147
 
105
- {/* Services Status Panels Grid */}
106
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 20 }}>
148
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
107
149
 
108
- {/* Service 1: Cognitive Daemon */}
109
- <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
110
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
111
- <span style={{ fontSize: 24 }}>⚙️</span>
150
+ {/* Ollama Panel */}
151
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
152
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
153
+ <span style={{ fontSize: 20 }}>🦙</span>
112
154
  <div>
113
- <h4 style={{ fontSize: 13, fontWeight: 600 }}>Reasoning Daemon</h4>
114
- <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>System 2 scheduler loop</p>
155
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>Local Provider</h3>
156
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Connect Ollama, LM Studio, vLLM, or Llama.cpp</p>
115
157
  </div>
116
158
  </div>
117
- <span className="badge" style={{
118
- background: health?.daemon === 'running' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
119
- color: health?.daemon === 'running' ? '#10b981' : '#ef4444',
120
- border: health?.daemon === 'running' ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)'
121
- }}>
122
- {health?.daemon || 'dead'}
123
- </span>
124
- </div>
125
159
 
126
- {/* Service 2: Caddy SSL Tunnel */}
127
- <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
128
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
129
- <span style={{ fontSize: 24 }}>🔒</span>
130
- <div>
131
- <h4 style={{ fontSize: 13, fontWeight: 600 }}>Caddy SSL Proxy</h4>
132
- <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Reverse proxy tunnel gateway</p>
133
- </div>
160
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
161
+ <label htmlFor="local_endpoint" style={{ fontSize: 13, fontWeight: 500 }}>Provider Base URL</label>
162
+ <input
163
+ id="local_endpoint"
164
+ type="text"
165
+ placeholder="http://127.0.0.1:11434 or http://127.0.0.1:1234/v1"
166
+ value={configData?.brain?.local_endpoint || ''}
167
+ onChange={(e) => updateBrainProp('local_endpoint', e.target.value)}
168
+ style={{
169
+ background: 'var(--bg-tertiary)',
170
+ color: 'var(--text-primary)',
171
+ border: '1px solid var(--border)',
172
+ padding: '8px 12px',
173
+ borderRadius: 6,
174
+ outline: 'none',
175
+ fontSize: 13
176
+ }}
177
+ />
178
+ <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Leave blank to use the default localhost endpoint.</span>
179
+ </div>
180
+
181
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 8 }}>
182
+ <span className="badge" style={{
183
+ background: health?.ollama === 'running' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
184
+ color: health?.ollama === 'running' ? '#10b981' : '#ef4444',
185
+ border: health?.ollama === 'running' ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)',
186
+ fontSize: 10,
187
+ padding: '4px 8px'
188
+ }}>
189
+ {health?.ollama === 'running' ? 'Connected' : 'Offline'}
190
+ </span>
191
+ {health?.ollama === 'running' && health?.ollama_models && (
192
+ <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>
193
+ {health.ollama_models.length} local models available
194
+ </span>
195
+ )}
134
196
  </div>
135
- <span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#10b981', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
136
- active
137
- </span>
138
197
  </div>
139
198
 
140
- {/* Service 3: Cloudflare Secure Tunnel */}
141
- <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
142
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
143
- <span style={{ fontSize: 24 }}>☁️</span>
199
+ {/* Cloud API Keys Panel */}
200
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
201
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
202
+ <span style={{ fontSize: 20 }}>🔑</span>
144
203
  <div>
145
- <h4 style={{ fontSize: 13, fontWeight: 600 }}>Cloudflare Secure Tunnel</h4>
146
- <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Expose tunnel behind SSL</p>
204
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>Cloud Models (API Keys)</h3>
205
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Bring your own cloud models</p>
147
206
  </div>
148
207
  </div>
149
- <span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#10b981', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
150
- active
208
+
209
+ {/* Google Gemini API Key */}
210
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
211
+ <label htmlFor="google_api_key" style={{ fontSize: 13, fontWeight: 500 }}>Google Gemini API Key</label>
212
+ <input
213
+ id="google_api_key"
214
+ type="password"
215
+ placeholder="AIzaSy..."
216
+ value={configData?.secrets?.google_api_key || ''}
217
+ onChange={(e) => updateSecretsProp('google_api_key', e.target.value)}
218
+ style={{
219
+ background: 'var(--bg-tertiary)',
220
+ color: 'var(--text-primary)',
221
+ border: '1px solid var(--border)',
222
+ padding: '8px 12px',
223
+ borderRadius: 6,
224
+ outline: 'none',
225
+ fontSize: 13
226
+ }}
227
+ />
228
+ </div>
229
+
230
+ {/* Anthropic API Key */}
231
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
232
+ <label htmlFor="anthropic_api_key" style={{ fontSize: 13, fontWeight: 500 }}>Anthropic API Key (Claude)</label>
233
+ <input
234
+ id="anthropic_api_key"
235
+ type="password"
236
+ placeholder="sk-ant-..."
237
+ value={configData?.secrets?.anthropic_api_key || ''}
238
+ onChange={(e) => updateSecretsProp('anthropic_api_key', e.target.value)}
239
+ style={{
240
+ background: 'var(--bg-tertiary)',
241
+ color: 'var(--text-primary)',
242
+ border: '1px solid var(--border)',
243
+ padding: '8px 12px',
244
+ borderRadius: 6,
245
+ outline: 'none',
246
+ fontSize: 13
247
+ }}
248
+ />
249
+ </div>
250
+
251
+ {/* OpenAI API Key */}
252
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
253
+ <label htmlFor="openai_api_key" style={{ fontSize: 13, fontWeight: 500 }}>OpenAI API Key (Codex)</label>
254
+ <input
255
+ id="openai_api_key"
256
+ type="password"
257
+ placeholder="sk-proj-..."
258
+ value={configData?.secrets?.openai_api_key || ''}
259
+ onChange={(e) => updateSecretsProp('openai_api_key', e.target.value)}
260
+ style={{
261
+ background: 'var(--bg-tertiary)',
262
+ color: 'var(--text-primary)',
263
+ border: '1px solid var(--border)',
264
+ padding: '8px 12px',
265
+ borderRadius: 6,
266
+ outline: 'none',
267
+ fontSize: 13
268
+ }}
269
+ />
270
+ </div>
271
+
272
+ <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>
273
+ These keys are stored locally and injected into the CLI reasoning agents on dispatch.
151
274
  </span>
152
275
  </div>
153
276
 
154
277
  </div>
155
278
 
156
- {/* CLI Agents Catalog & Diagnostics */}
279
+ {/* CLI Agents Catalog & Diagnostics */}
157
280
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
158
281
 
159
282
  {/* Catalog */}
@@ -249,33 +372,6 @@ export default function DeploymentsPage() {
249
372
 
250
373
  </div>
251
374
 
252
- {/* Active Terminal Console Logs */}
253
- <div className="card" style={{ flex: 1, minHeight: 300, background: '#07070a', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', borderRadius: 10, overflow: 'hidden' }}>
254
- <div style={{ background: '#0e0e15', padding: '10px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
255
- <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ff6b6b' }} />
256
- <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#fdcb6e' }} />
257
- <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00cec9' }} />
258
- <span style={{ fontSize: 11, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)', marginLeft: 8 }}>bash — daemon-activity.log — 80x24</span>
259
- </div>
260
-
261
- <pre style={{
262
- flex: 1,
263
- overflowY: 'auto',
264
- padding: 16,
265
- margin: 0,
266
- fontFamily: 'var(--font-mono)',
267
- fontSize: 12,
268
- color: '#34d399',
269
- background: '#07070a',
270
- lineHeight: 1.5,
271
- whiteSpace: 'pre-wrap',
272
- wordBreak: 'break-all'
273
- }}>
274
- {logs}
275
- <div ref={consoleEndRef} />
276
- </pre>
277
- </div>
278
-
279
375
  </div>
280
376
  )}
281
377
 
@@ -47,6 +47,8 @@ export interface HealthData {
47
47
  ollama_models?: string[]
48
48
  cli_agents?: string[]
49
49
  daemon?: string
50
+ caddy?: string
51
+ cloudflare?: string
50
52
  emergency_alerts?: string | null
51
53
  }
52
54
 
@@ -158,6 +160,7 @@ export interface ConfigJson {
158
160
  }
159
161
  brain?: {
160
162
  preferred_agent?: string
163
+ local_endpoint?: string
161
164
  }
162
165
  secrets?: {
163
166
  google_api_key?: string
@@ -8,15 +8,15 @@ export default defineConfig({
8
8
  port: 5173,
9
9
  proxy: {
10
10
  '/v1': {
11
- target: 'http://localhost:3000',
11
+ target: 'http://127.0.0.1:3000',
12
12
  changeOrigin: true,
13
13
  },
14
14
  '/health': {
15
- target: 'http://localhost:3000',
15
+ target: 'http://127.0.0.1:3000',
16
16
  changeOrigin: true,
17
17
  },
18
18
  '/api': {
19
- target: 'http://localhost:3000',
19
+ target: 'http://127.0.0.1:3000',
20
20
  changeOrigin: true,
21
21
  },
22
22
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.6.4",
3
+ "version": "3.6.5",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -68,11 +68,15 @@ function requiresLLM(task) {
68
68
  return false;
69
69
  }
70
70
 
71
- /**
72
- * Dispatch a task to the appropriate engine.
73
- * Only deterministic (non-LLM) tasks are executed.
74
- */
75
71
  async function dispatchTask(task) {
72
+ if (requiresLLM(task)) {
73
+ logger.info({
74
+ subsystem: 'daemon-loop',
75
+ message: `Skipping task (requires LLM): ${task.slug} [${task.category}]`,
76
+ });
77
+ return { success: true, skippedLLM: true, output: 'Skipped — handled by IDE agents' };
78
+ }
79
+
76
80
  const category = task.category;
77
81
 
78
82
  try {
@@ -85,13 +89,11 @@ async function dispatchTask(task) {
85
89
  return await runResearchTask(task);
86
90
 
87
91
  default:
88
- // All other categories (research, deliberation, inference, etc.)
89
- // require LLM and are handled by IDE agents, not the daemon.
90
92
  logger.info({
91
93
  subsystem: 'daemon-loop',
92
94
  message: `Skipping task (not deterministic): ${task.slug} [${category}]`,
93
95
  });
94
- return { success: true, output: 'Skipped — handled by IDE agents' };
96
+ return { success: true, skippedLLM: true, output: 'Skipped — handled by IDE agents' };
95
97
  }
96
98
  } catch (err) {
97
99
  logger.info({
@@ -309,7 +311,11 @@ async function main() {
309
311
  // Mark complete
310
312
  if ((source === 'explicit' || source === 'idle') && task._filepath) {
311
313
  try {
312
- updateTaskStatus(task, result.success ? 'completed' : 'failed', QUEUE_DIR);
314
+ if (result.skippedLLM) {
315
+ updateTaskStatus(task, 'pending', QUEUE_DIR);
316
+ } else {
317
+ updateTaskStatus(task, result.success ? 'completed' : 'failed', QUEUE_DIR);
318
+ }
313
319
  } catch (statusErr) {
314
320
  logger.info({
315
321
  subsystem: 'daemon-loop',
@@ -321,15 +327,22 @@ async function main() {
321
327
  try {
322
328
  const patch = {
323
329
  status: result.success ? 'pending' : 'failed',
324
- notes: result.output || result.error || null,
330
+ // Append the output rather than completely overwriting the original rationale
331
+ notes: result.output ? `Phase output: ${result.output}` : (result.error || null),
325
332
  };
326
333
  if (result.factSlug) {
327
334
  patch.node_slug = result.factSlug;
328
335
  }
329
- // Only advance through acquisition phase — skip LLM phases
330
- if (result.success && task._research_phase === 'acquisition' && result.factSlug) {
331
- // Research stays in acquisition — no LLM phases to advance to
332
- patch.status = 'done';
336
+ // If the task was skipped because it requires an LLM, DO NOT complete the research project.
337
+ // Wait for an IDE agent to run it.
338
+ if (result.skippedLLM) {
339
+ patch.status = 'pending';
340
+ } else if (result.success && task._research_phase === 'acquisition' && result.factSlug) {
341
+ // Acquisition is done, advance to deliberation phase for LLM processing!
342
+ patch.status = 'pending';
343
+ patch.research_phase = 'deliberation';
344
+ // Also notify the user/UI that phase 1 is complete!
345
+ patch.completed_at = new Date().toISOString();
333
346
  }
334
347
  updateQueueItem(task._research_id, patch);
335
348
  } catch (err) {
@@ -138,6 +138,23 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
138
138
  // Determine overall status
139
139
  const hasCriticalIssue = emergencyAlerts.length > 0 || daemonStatus === 'dead' || cliAgents.length === 0;
140
140
 
141
+ // Check Caddy and Cloudflare status
142
+ let caddyStatus = 'inactive';
143
+ let cloudflareStatus = 'inactive';
144
+ try {
145
+ const { exec } = await import('node:child_process');
146
+ const util = await import('node:util');
147
+ const execAsync = util.promisify(exec);
148
+ try {
149
+ await execAsync('pgrep caddy');
150
+ caddyStatus = 'active';
151
+ } catch { }
152
+ try {
153
+ await execAsync('pgrep cloudflared');
154
+ cloudflareStatus = 'active';
155
+ } catch { }
156
+ } catch { }
157
+
141
158
  res.json({
142
159
  status: hasCriticalIssue ? 'degraded' : 'healthy',
143
160
  version: PACKAGE_VERSION,
@@ -146,6 +163,8 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
146
163
  disk,
147
164
  cli_agents: cliAgents,
148
165
  daemon: daemonStatus,
166
+ caddy: caddyStatus,
167
+ cloudflare: cloudflareStatus,
149
168
  emergency_alerts: emergencyAlerts || null,
150
169
  vfs: {
151
170
  exists: vaultExists,