total-recall-brain 3.24.0 → 3.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
@@ -1,6 +1,3 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
2
- <rect width="64" height="64" rx="14" fill="#0b1220"/>
3
- <path d="M18 42V28a14 14 0 0 1 28 0v14" stroke="#60a5fa" stroke-width="5" stroke-linecap="round"/>
4
- <path d="M24 42V28a8 8 0 0 1 16 0v14" stroke="#1e3a8a" stroke-width="4" stroke-linecap="round"/>
5
- <rect x="20" y="40" width="24" height="6" rx="2" fill="#93c5fd"/>
6
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <image href="data:image/png;base64,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" width="64" height="64" />
3
+ </svg>
Binary file
@@ -17,7 +17,7 @@
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
18
18
  <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" />
19
19
  <title>Total Recall — Portable Memory</title>
20
- <script type="module" crossorigin src="/assets/index-CtCDaZYZ.js"></script>
20
+ <script type="module" crossorigin src="/assets/index-BQL-opnK.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="/assets/index-C7_Xpk27.css">
22
22
  </head>
23
23
  <body>
@@ -99,6 +99,7 @@ import {
99
99
  triggerRecompile,
100
100
  triggerDream,
101
101
  runAgentDiagnostics,
102
+ restartDaemon,
102
103
  fetchBrains,
103
104
  listTasks,
104
105
  createTask,
@@ -189,7 +190,7 @@ describe('API barrel exports — all functions exist', () => {
189
190
  // research
190
191
  listResearch, createResearch, patchResearch, deleteResearch,
191
192
  // system
192
- fetchHealth, fetchUsageStats, fetchLogs, triggerRecompile, triggerDream, runAgentDiagnostics,
193
+ fetchHealth, fetchUsageStats, fetchLogs, triggerRecompile, triggerDream, runAgentDiagnostics, restartDaemon,
193
194
  fetchBrains, listTasks, createTask, listFiles, listScripts, readScript, saveScript, runScript,
194
195
  shareToApi, runOkfLint, triggerOkfExport, postDecision,
195
196
  // update
@@ -71,6 +71,12 @@ export async function runAgentDiagnostics(): Promise<{ success: boolean; output:
71
71
  return res.json()
72
72
  }
73
73
 
74
+ export async function restartDaemon(): Promise<{ success: boolean; message: string; pid?: number }> {
75
+ const res = await apiFetch(`${API_BASE}/api/daemon/restart`, { method: 'POST' })
76
+ if (!res.ok) throw new Error(`Restart daemon API error: ${res.status}`)
77
+ return res.json()
78
+ }
79
+
74
80
  export async function fetchBrains(): Promise<Record<string, unknown>[]> {
75
81
  const res = await apiFetch(`${API_BASE}/api/brains`)
76
82
  const data = await res.json()
@@ -15,13 +15,10 @@ export async function checkUpdate(): Promise<UpdateCheckResult> {
15
15
  // Normalize snake_case API fields for the Settings UI
16
16
  const currentVersion = data.currentVersion ?? data.current ?? ''
17
17
  const latestVersion = data.latestVersion ?? data.latest ?? ''
18
- // Prefer host package comparison; fall back to API flag (includes consumers behind).
19
- const hostBehind =
20
- Boolean(latestVersion && currentVersion && latestVersion !== currentVersion)
21
18
  return {
22
19
  currentVersion,
23
20
  latestVersion,
24
- updateAvailable: hostBehind || Boolean(data.updateAvailable ?? data.update_available),
21
+ updateAvailable: Boolean(data.updateAvailable ?? data.update_available),
25
22
  }
26
23
  }
27
24
 
@@ -202,43 +202,50 @@ export function MobilePairing() {
202
202
  </div>
203
203
 
204
204
  {selected && (
205
- <p style={{ fontSize: 12, color: 'var(--text-secondary)', margin: '0 0 8px' }}>
206
- {selected.reachable_hint}
207
- </p>
205
+ <div style={{ fontSize: 12, color: 'var(--text-secondary)', margin: '0 0 10px', lineHeight: 1.5, background: 'rgba(255,255,255,0.03)', padding: '8px 12px', borderRadius: 6 }}>
206
+ <strong>Connection Route:</strong> {selected.reachable_hint}
207
+ </div>
208
208
  )}
209
209
 
210
210
  {isLoopback && (
211
- <p
211
+ <div
212
212
  data-testid="pairing-loopback-warning"
213
- style={{ fontSize: 12, color: '#f59e0b', margin: 0 }}
213
+ style={{ fontSize: 12, color: '#f59e0b', margin: '0 0 8px', background: 'rgba(245, 158, 11, 0.1)', padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(245, 158, 11, 0.2)' }}
214
214
  >
215
- This address only works on this computer. Choose LAN or Tailscale for your phone.
216
- </p>
215
+ ⚠️ <strong>Loopback Only:</strong> <code>127.0.0.1</code> only works on this computer. To open on your phone, select your Mesh IP (<code>100.64.x.x</code>) or enable <strong>Allow Public Bind</strong> below to use local Wi-Fi.
216
+ </div>
217
217
  )}
218
218
  </div>
219
219
  </div>
220
220
  )}
221
221
 
222
222
  {info?.warnings && info.warnings.length > 0 && (
223
- <ul
223
+ <div
224
224
  data-testid="pairing-warnings"
225
225
  style={{
226
226
  margin: '16px 0 0',
227
- paddingLeft: 18,
227
+ padding: '10px 14px',
228
228
  fontSize: 12,
229
- color: '#f59e0b',
229
+ color: '#fbbf24',
230
+ background: 'rgba(251, 191, 36, 0.08)',
231
+ borderRadius: 8,
232
+ border: '1px solid rgba(251, 191, 36, 0.2)',
230
233
  }}
231
234
  >
232
- {info.warnings.map((w) => (
233
- <li key={w}>{w}</li>
234
- ))}
235
- </ul>
235
+ <div style={{ fontWeight: 600, marginBottom: 4 }}>Network Notice:</div>
236
+ <ul style={{ margin: 0, paddingLeft: 18 }}>
237
+ {info.warnings.map((w) => (
238
+ <li key={w}>{w}</li>
239
+ ))}
240
+ </ul>
241
+ </div>
236
242
  )}
237
243
 
238
- <p style={{ marginTop: 16, fontSize: 11, color: 'var(--text-tertiary)' }}>
239
- After scanning, log in on the phone (same password/PAT as this dashboard). Listening:{' '}
240
- {(info?.listen_hosts || []).join(', ') || 'unknown'}
241
- </p>
244
+ <div style={{ marginTop: 16, padding: '12px 14px', background: 'rgba(59, 130, 246, 0.06)', borderRadius: 8, border: '1px solid rgba(59, 130, 246, 0.15)', fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.5 }}>
245
+ <div style={{ color: 'var(--text-primary)', fontWeight: 600, marginBottom: 4 }}>📱 How to connect your phone:</div>
246
+ <div><strong>Option A (WireGuard / Mesh — Recommended):</strong> Install the standard <em>Tailscale</em> app on your phone (iOS App Store / Google Play). Log into your tailnet/headscale server. Scan the <code>100.64.x.x</code> QR code above.</div>
247
+ <div style={{ marginTop: 4 }}><strong>Option B (Local Home Wi-Fi):</strong> Check <strong>Allow Public Bind</strong> in <em>Network & Binding</em> below, restart the server, and scan your local Wi-Fi LAN QR code (e.g. <code>192.168.x.x</code>).</div>
248
+ </div>
242
249
  </div>
243
250
  );
244
251
  }
@@ -46,6 +46,9 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
46
46
  const [extensionBannerDismissed, setExtensionBannerDismissed] = useState(
47
47
  () => localStorage.getItem('tr-ext-banner-dismissed') === 'true'
48
48
  )
49
+ const [dismissedUpdateVersion, setDismissedUpdateVersion] = useState<string | null>(
50
+ () => localStorage.getItem('tr-update-banner-dismissed-version')
51
+ )
49
52
 
50
53
  // Threads listing & session control state
51
54
  const [threads, setThreads] = useState<ChatThread[]>([])
@@ -671,7 +674,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
671
674
  </header>
672
675
 
673
676
  {/* Update Available Banner */}
674
- {updateInfo?.updateAvailable && (
677
+ {updateInfo?.updateAvailable && dismissedUpdateVersion !== updateInfo.latestVersion && (
675
678
  <div className="animate-fade-in" style={{
676
679
  margin: '0 16px 12px',
677
680
  padding: '12px 16px',
@@ -710,6 +713,27 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
710
713
  >
711
714
  Go to Update
712
715
  </button>
716
+ <button
717
+ onClick={() => {
718
+ if (updateInfo?.latestVersion) {
719
+ localStorage.setItem('tr-update-banner-dismissed-version', updateInfo.latestVersion)
720
+ setDismissedUpdateVersion(updateInfo.latestVersion)
721
+ }
722
+ }}
723
+ title="Dismiss banner"
724
+ aria-label="Dismiss update banner"
725
+ style={{
726
+ background: 'transparent',
727
+ border: 'none',
728
+ color: 'var(--text-muted)',
729
+ fontSize: 16,
730
+ cursor: 'pointer',
731
+ padding: '4px 6px',
732
+ lineHeight: 1,
733
+ }}
734
+ >
735
+ ✕
736
+ </button>
713
737
  </div>
714
738
  )}
715
739
 
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect } from 'react'
2
- import { fetchHealth, checkUpdate, runUpdate } from '../api'
2
+ import { fetchHealth, checkUpdate, runUpdate, restartDaemon } from '../api'
3
3
  import type { HealthData, UpdateCheckResult } from '../types'
4
4
 
5
5
  function formatUptime(seconds: number): string {
@@ -23,6 +23,24 @@ export default function HealthPage() {
23
23
  const [updateError, setUpdateError] = useState('')
24
24
  const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
25
25
  const [checkMessage, setCheckMessage] = useState('')
26
+ const [isRestartingDaemon, setIsRestartingDaemon] = useState(false)
27
+ const [daemonMessage, setDaemonMessage] = useState('')
28
+
29
+ const handleRestartDaemon = async () => {
30
+ setIsRestartingDaemon(true)
31
+ setDaemonMessage('')
32
+ try {
33
+ const res = await restartDaemon()
34
+ setDaemonMessage(res.message || 'Daemon restarted successfully')
35
+ setTimeout(() => setDaemonMessage(''), 4000)
36
+ const data = await fetchHealth()
37
+ setHealth(data)
38
+ } catch (e) {
39
+ setDaemonMessage(`Failed to restart daemon: ${(e as Error).message}`)
40
+ } finally {
41
+ setIsRestartingDaemon(false)
42
+ }
43
+ }
26
44
 
27
45
  const handleManualCheckUpdate = async () => {
28
46
  setIsCheckingUpdate(true)
@@ -96,8 +114,14 @@ export default function HealthPage() {
96
114
  setIsUpdating(false)
97
115
  setCheckMessage(
98
116
  result.message ||
99
- 'Registered projects updated. Restart local daemon/server if you need new code in this process.',
117
+ 'Registered projects updated. Restart daemon if you need new code running.',
100
118
  )
119
+
120
+ // Refresh version check immediately so banner / badge clears
121
+ const refreshed = await checkUpdate().catch(() => null)
122
+ if (refreshed) {
123
+ setUpdateInfo(refreshed)
124
+ }
101
125
  } catch (e) {
102
126
  setUpdateError((e as Error).message)
103
127
  setIsUpdating(false)
@@ -278,7 +302,32 @@ export default function HealthPage() {
278
302
  <tr><th>Version</th><td>{health.version}</td></tr>
279
303
  <tr><th>Uptime</th><td>{formatUptime(health.uptime_seconds)}</td></tr>
280
304
  <tr><th>Timestamp</th><td>{health.timestamp}</td></tr>
281
- <tr><th>Daemon Status</th><td>{health.daemon || 'unknown'}</td></tr>
305
+ <tr>
306
+ <th>Daemon Status</th>
307
+ <td>
308
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
309
+ <span style={{ fontWeight: 600 }}>{health.daemon || 'unknown'}</span>
310
+ <button
311
+ className="btn btn-secondary"
312
+ onClick={handleRestartDaemon}
313
+ disabled={isRestartingDaemon}
314
+ style={{
315
+ fontSize: 12,
316
+ padding: '4px 12px',
317
+ borderRadius: 6,
318
+ cursor: 'pointer'
319
+ }}
320
+ >
321
+ {isRestartingDaemon ? 'Restarting…' : 'Restart Daemon'}
322
+ </button>
323
+ </div>
324
+ {daemonMessage && (
325
+ <div style={{ fontSize: 12, color: daemonMessage.includes('Failed') ? '#ef4444' : '#3fb950', marginTop: 4 }}>
326
+ {daemonMessage}
327
+ </div>
328
+ )}
329
+ </td>
330
+ </tr>
282
331
  {health.cli_agents && (
283
332
  <tr><th>CLI Agents</th><td>{health.cli_agents.join(', ')}</td></tr>
284
333
  )}
@@ -6,6 +6,7 @@ import {
6
6
  runAgentDiagnostics,
7
7
  checkUpdate,
8
8
  runUpdate,
9
+ restartDaemon,
9
10
  triggerRecompile,
10
11
  apiFetch,
11
12
  getApiBase,
@@ -50,6 +51,22 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
50
51
  } | null>(null);
51
52
  const [updating, setUpdating] = useState(false);
52
53
  const [updateMessage, setUpdateMessage] = useState<string | null>(null);
54
+ const [restartingDaemon, setRestartingDaemon] = useState(false);
55
+
56
+ const handleRestartDaemon = async () => {
57
+ setRestartingDaemon(true);
58
+ setUpdateMessage(null);
59
+ try {
60
+ const res = await restartDaemon();
61
+ setUpdateMessage(res.message || 'Daemon restarted successfully.');
62
+ const h = await fetchHealth().catch(() => null);
63
+ if (h) setHealth(h);
64
+ } catch (err: unknown) {
65
+ setUpdateMessage('Restart error: ' + (err as Error).message);
66
+ } finally {
67
+ setRestartingDaemon(false);
68
+ }
69
+ };
53
70
  const [rebuilding, setRebuilding] = useState(false);
54
71
 
55
72
  const loadConfig = async () => {
@@ -984,14 +1001,24 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
984
1001
  </div>
985
1002
  )}
986
1003
  </div>
987
- <button
988
- className="btn-primary"
989
- onClick={handleRunUpdate}
990
- disabled={updating}
991
- style={{ background: updateInfo?.updateAvailable ? '#3fb950' : undefined, border: 'none' }}
992
- >
993
- {updating ? 'Updating…' : updateInfo?.updateAvailable ? 'Apply Updates' : 'Check & Sync Projects'}
994
- </button>
1004
+ <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
1005
+ <button
1006
+ className="btn-secondary"
1007
+ onClick={handleRestartDaemon}
1008
+ disabled={restartingDaemon}
1009
+ style={{ fontSize: 13, padding: '8px 14px', borderRadius: 8, cursor: 'pointer' }}
1010
+ >
1011
+ {restartingDaemon ? 'Restarting…' : 'Restart Daemon'}
1012
+ </button>
1013
+ <button
1014
+ className="btn-primary"
1015
+ onClick={handleRunUpdate}
1016
+ disabled={updating}
1017
+ style={{ background: updateInfo?.updateAvailable ? '#3fb950' : undefined, border: 'none' }}
1018
+ >
1019
+ {updating ? 'Updating…' : updateInfo?.updateAvailable ? 'Apply Updates' : 'Check & Sync Projects'}
1020
+ </button>
1021
+ </div>
995
1022
  </div>
996
1023
  </div>
997
1024
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.24.0",
3
+ "version": "3.24.2",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -597,6 +597,19 @@ Current baseline: **288 spec files / 1493 tests, ~160 s** on the Mac Mini.
597
597
  >
598
598
  > A non-login `ssh` shell has no Homebrew on PATH — export it or use `bash -lc`,
599
599
  > otherwise the run dies with `command not found: npx` **and still exits 0**.
600
+ >
601
+ > **Never read a test result through a pipe.** `vitest run` exits 1 correctly on
602
+ > failure, but `npx vitest run | tail -20` reports *tail's* status — always 0 —
603
+ > so a red suite looks green. On 2026-08-19 this cost a full re-run and a wrong
604
+ > "the suite exits 0 on failure" claim about the repo; the defect was the
605
+ > pipeline, not vitest. Capture to a file and check the code separately:
606
+ >
607
+ > ```bash
608
+ > npx vitest run > /tmp/t.log 2>&1; echo "exit=$?"; tail -20 /tmp/t.log
609
+ > ```
610
+ >
611
+ > `check.mjs` is unaffected — it spawns without a shell and reads the real exit
612
+ > code — so prefer the gate over hand-rolled invocations.
600
613
 
601
614
  **Never skip tests** to make a release look clean, and never use `--force` on a
602
615
  checker. Audit and clean up any rows, files, or mock entries a suite leaves
@@ -3,6 +3,7 @@ type: memory
3
3
  slug: always-websearch-gap
4
4
  category: preferences
5
5
  title: "Always Perform Web Search for Cutoff Gap"
6
+ description: "Web-search to confirm any fact that falls in or past the training-cutoff gap, rather than answering from weights."
6
7
  status: active
7
8
  confidence: 1
8
9
  importance: 5
@@ -32,6 +33,7 @@ schema_version: 2
32
33
  x_temporal_context: "2026-05-25T22:36:21.376Z"
33
34
  priority: absolute
34
35
  immutable: true
36
+ timestamp: "2026-05-25T22:36:21.376Z"
35
37
  ---
36
38
 
37
39
  # Always Perform Web Search for Cutoff Gap
@@ -3,6 +3,7 @@ type: memory
3
3
  slug: topic-research-sop
4
4
  category: preferences
5
5
  title: "Standard Operating Procedure for Topic Research"
6
+ description: "How the agent handles a research request: queue it to the background daemon, keep one living scratch document, and compile so it is searchable."
6
7
  status: active
7
8
  confidence: 1
8
9
  importance: 5
@@ -32,6 +33,7 @@ schema_version: 2
32
33
  x_temporal_context: "2026-05-25T22:36:21.376Z"
33
34
  priority: absolute
34
35
  immutable: true
36
+ timestamp: "2026-05-25T22:36:21.376Z"
35
37
  ---
36
38
 
37
39
  # Standard Operating Procedure for Topic Research