total-recall-brain 3.24.0 → 3.24.1
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/frontend/dist/assets/index-BQL-opnK.js +5742 -0
- package/frontend/dist/favicon.ico +0 -0
- package/frontend/dist/favicon.png +0 -0
- package/frontend/dist/favicon.svg +3 -6
- package/frontend/dist/icon-192.png +0 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/api/api.spec.ts +2 -1
- package/frontend/src/api/system.ts +6 -0
- package/frontend/src/api/update.ts +1 -4
- package/frontend/src/components/MobilePairing.tsx +25 -18
- package/frontend/src/pages/ChatPage.tsx +25 -1
- package/frontend/src/pages/HealthPage.tsx +52 -3
- package/frontend/src/pages/SettingsPage.tsx +35 -8
- package/package.json +1 -1
- package/src/core/package-auto-update.mjs +13 -9
- package/src/core/surface.mjs +51 -22
- package/src/server/routes/system.mjs +15 -0
- package/src/server/routes/update.mjs +1 -1
- package/frontend/dist/assets/index-CtCDaZYZ.js +0 -5742
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"
|
|
2
|
-
<
|
|
3
|
-
|
|
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
|
package/frontend/dist/index.html
CHANGED
|
@@ -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-
|
|
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:
|
|
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
|
-
<
|
|
206
|
-
{selected.reachable_hint}
|
|
207
|
-
</
|
|
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
|
-
<
|
|
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
|
-
|
|
216
|
-
</
|
|
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
|
-
<
|
|
223
|
+
<div
|
|
224
224
|
data-testid="pairing-warnings"
|
|
225
225
|
style={{
|
|
226
226
|
margin: '16px 0 0',
|
|
227
|
-
|
|
227
|
+
padding: '10px 14px',
|
|
228
228
|
fontSize: 12,
|
|
229
|
-
color: '#
|
|
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
|
-
{
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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
|
@@ -169,20 +169,24 @@ export function inspectProjectPackage(projectRoot) {
|
|
|
169
169
|
} catch {
|
|
170
170
|
return { declared: null, installed: null, isSourceTree: false, packageName: null };
|
|
171
171
|
}
|
|
172
|
+
const isSourceTree =
|
|
173
|
+
pkg.name === PACKAGE_NAME &&
|
|
174
|
+
fs.existsSync(path.join(projectRoot, 'src', 'server', 'index.mjs'));
|
|
172
175
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
173
176
|
const declared = deps[PACKAGE_NAME] || null;
|
|
174
177
|
let installed = null;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
if (isSourceTree) {
|
|
179
|
+
installed = pkg.version || null;
|
|
180
|
+
} else {
|
|
181
|
+
const nm = path.join(projectRoot, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
182
|
+
if (fs.existsSync(nm)) {
|
|
183
|
+
try {
|
|
184
|
+
installed = JSON.parse(fs.readFileSync(nm, 'utf8')).version || null;
|
|
185
|
+
} catch {
|
|
186
|
+
installed = null;
|
|
187
|
+
}
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
|
-
const isSourceTree =
|
|
184
|
-
pkg.name === PACKAGE_NAME &&
|
|
185
|
-
fs.existsSync(path.join(projectRoot, 'src', 'server', 'index.mjs'));
|
|
186
190
|
return { declared, installed, isSourceTree, packageName: pkg.name || null };
|
|
187
191
|
}
|
|
188
192
|
|
package/src/core/surface.mjs
CHANGED
|
@@ -321,38 +321,67 @@ export async function buildRulesBlock(skillsDir, nodes = [], { consumer = 'ide',
|
|
|
321
321
|
// Note: Only invariants, preferences, and anti-patterns are included in instructions,
|
|
322
322
|
// enforcing Category Partitioning (concepts, decisions, facts are search-only).
|
|
323
323
|
const isImportant = (n) => n.importance === undefined || n.importance >= 3;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
324
|
+
|
|
325
|
+
// Determine current project/repo context to prevent cross-repo pollution
|
|
326
|
+
const currentRepoName = path.basename(process.cwd()).toLowerCase();
|
|
327
|
+
|
|
328
|
+
const isRelevantToRepo = (n) => {
|
|
329
|
+
// If explicitly marked global or no repo restriction, it applies everywhere
|
|
330
|
+
if (n.scope === 'global' || n._layer === 'global') {
|
|
331
|
+
// If the node text explicitly binds to specific other repos, skip it
|
|
332
|
+
if (Array.isArray(n.repos) && n.repos.length > 0) {
|
|
333
|
+
return n.repos.some(r => r.toLowerCase() === currentRepoName);
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
// If tagged with specific repos or project name
|
|
338
|
+
if (Array.isArray(n.repos) && n.repos.length > 0) {
|
|
339
|
+
return n.repos.some(r => r.toLowerCase() === currentRepoName);
|
|
340
|
+
}
|
|
341
|
+
if (n.project && typeof n.project === 'string') {
|
|
342
|
+
return n.project.toLowerCase() === currentRepoName;
|
|
343
|
+
}
|
|
344
|
+
return true;
|
|
345
|
+
};
|
|
327
346
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
347
|
+
const rawInvariants = nodes.filter(n => n.category === 'invariants' && n.status === 'active' && !isExpired(n) && isImportant(n) && isRelevantToRepo(n));
|
|
348
|
+
const rawPreferences = nodes.filter(n => n.category === 'preferences' && n.status === 'active' && !isExpired(n) && isImportant(n) && isRelevantToRepo(n));
|
|
349
|
+
const rawCorrections = nodes.filter(n => n.category === 'anti-patterns' && n.status === 'active' && !isExpired(n) && isImportant(n) && isRelevantToRepo(n));
|
|
350
|
+
|
|
351
|
+
// Deduplicate nodes by normalized content key and rank by priority/importance
|
|
352
|
+
const deduplicateAndRankNodes = (list, maxCount = 15) => {
|
|
331
353
|
const seen = new Map();
|
|
332
354
|
const deduped = [];
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
355
|
+
|
|
356
|
+
// Sort by importance (descending) and priority (absolute first)
|
|
357
|
+
const sorted = [...list].sort((a, b) => {
|
|
358
|
+
const aAbs = a.priority === 'absolute' ? 10 : 0;
|
|
359
|
+
const bAbs = b.priority === 'absolute' ? 10 : 0;
|
|
360
|
+
const aScore = (a.importance || 3) + aAbs;
|
|
361
|
+
const bScore = (b.importance || 3) + bAbs;
|
|
362
|
+
return bScore - aScore;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
for (const n of sorted) {
|
|
366
|
+
// Normalize content to catch paraphrased/duplicate directives
|
|
367
|
+
const rawText = (n.body || n.content || n.title || '').trim().toLowerCase();
|
|
368
|
+
const normKey = rawText.replace(/[^a-z0-9]/g, '').substring(0, 120);
|
|
369
|
+
|
|
370
|
+
if (!seen.has(normKey)) {
|
|
371
|
+
seen.set(normKey, n);
|
|
346
372
|
deduped.push(n);
|
|
347
373
|
}
|
|
348
374
|
}
|
|
349
|
-
return deduped;
|
|
375
|
+
return deduped.slice(0, maxCount);
|
|
350
376
|
};
|
|
351
377
|
|
|
378
|
+
const invariants = deduplicateAndRankNodes(rawInvariants, 15);
|
|
379
|
+
const preferences = deduplicateAndRankNodes(rawPreferences, 10);
|
|
380
|
+
const corrections = deduplicateAndRankNodes(rawCorrections, 15);
|
|
381
|
+
|
|
352
382
|
const formatNodes = async (list) => {
|
|
353
|
-
const unique = deduplicateNodes(list);
|
|
354
383
|
const formatted = [];
|
|
355
|
-
for (const n of
|
|
384
|
+
for (const n of list) {
|
|
356
385
|
const snippet = await compactNode(n, derivedDir, force);
|
|
357
386
|
formatted.push(snippet.startsWith('-') ? snippet : `- ${snippet}`);
|
|
358
387
|
}
|
|
@@ -202,5 +202,20 @@ router.get('/api/usage/providers', requireAuth, async (req, res) => {
|
|
|
202
202
|
} catch (err) { serverError(res, err); }
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* POST /api/daemon/restart
|
|
207
|
+
* Restarts the Active Intelligence background daemon process.
|
|
208
|
+
*/
|
|
209
|
+
router.post('/api/daemon/restart', requireAuth, requireScope('config:write'), async (_req, res) => {
|
|
210
|
+
try {
|
|
211
|
+
const { stopDaemon, startDaemon } = await import('../../core/daemon-control.mjs');
|
|
212
|
+
stopDaemon();
|
|
213
|
+
const pid = startDaemon();
|
|
214
|
+
res.json({ success: true, message: `Daemon restarted successfully (PID ${pid})`, pid });
|
|
215
|
+
} catch (err) {
|
|
216
|
+
serverError(res, err);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
205
220
|
export default router;
|
|
206
221
|
export { router as systemRouter };
|
|
@@ -45,7 +45,7 @@ router.get('/api/update/check', requireAuth, async (_req, res) => {
|
|
|
45
45
|
|
|
46
46
|
const consumersBehind = projects.filter((p) => p.update_available).length;
|
|
47
47
|
const updateAvailable =
|
|
48
|
-
Boolean(latest && current && current
|
|
48
|
+
Boolean(latest && current && needsUpdate(current, latest)) || consumersBehind > 0;
|
|
49
49
|
|
|
50
50
|
res.json({
|
|
51
51
|
package: PACKAGE_NAME,
|