total-recall-brain 3.18.2 ā 3.19.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/frontend/dist/assets/{index-D5xRw5uq.js ā index-B6AfuKv9.js} +126 -134
- package/frontend/dist/index.html +1 -1
- package/frontend/src/api/config.ts +16 -2
- package/frontend/src/api/keys.ts +111 -0
- package/frontend/src/api/notifications.ts +54 -6
- package/frontend/src/api/update.ts +41 -5
- package/frontend/src/components/BrainSelector.spec.tsx +71 -47
- package/frontend/src/components/BrainSelector.tsx +112 -35
- package/frontend/src/components/MobilePairing.spec.tsx +44 -14
- package/frontend/src/components/MobilePairing.tsx +229 -22
- package/frontend/src/pages/HealthPage.tsx +12 -16
- package/frontend/src/pages/NotificationsPage.tsx +6 -3
- package/frontend/src/pages/RulesPage.spec.tsx +46 -15
- package/frontend/src/pages/RulesPage.tsx +41 -9
- package/frontend/src/pages/SecretsPage.tsx +316 -3
- package/frontend/src/pages/SettingsPage.spec.tsx +30 -6
- package/frontend/src/pages/SettingsPage.tsx +228 -86
- package/package.json +1 -1
- package/src/cli/agent-dir.mjs +17 -2
- package/src/cli/chat.mjs +1 -1
- package/src/cli/daemon.mjs +2 -1
- package/src/cli/dream.mjs +14 -10
- package/src/cli/forget.mjs +14 -2
- package/src/cli/friction.mjs +1 -1
- package/src/cli/import-rules.mjs +8 -3
- package/src/cli/ingest.mjs +3 -2
- package/src/cli/lint.mjs +9 -6
- package/src/cli/rebuild.mjs +9 -4
- package/src/cli/remember.mjs +21 -15
- package/src/cli/secret.mjs +184 -4
- package/src/cli/share.mjs +5 -3
- package/src/cli/skill-projection.mjs +21 -2
- package/src/cli/snapshot.mjs +10 -8
- package/src/cli/update.mjs +2 -1
- package/src/core/clarity-rewriter.mjs +31 -12
- package/src/core/config.mjs +10 -4
- package/src/core/daemon-loop.mjs +33 -6
- package/src/core/embeddings.mjs +51 -3
- package/src/core/memory-title.mjs +70 -0
- package/src/core/notifications.mjs +204 -34
- package/src/core/package-auto-update.mjs +99 -3
- package/src/core/pairing.mjs +168 -0
- package/src/core/provider-account-sync.mjs +1145 -0
- package/src/core/provider-catalog.mjs +11 -4
- package/src/core/research.mjs +98 -24
- package/src/core/runtime.mjs +11 -1
- package/src/core/scheduler.mjs +31 -9
- package/src/core/schema.mjs +23 -0
- package/src/core/secret-integration-research.mjs +325 -0
- package/src/core/secrets-store.mjs +284 -14
- package/src/core/secrets-sync.mjs +1 -1
- package/src/core/skills-registry.mjs +12 -0
- package/src/core/source-adapters.mjs +247 -22
- package/src/core/surface.mjs +4 -4
- package/src/core/task-executors.mjs +161 -14
- package/src/core/throttled-fetch.mjs +5 -0
- package/src/core/vault.mjs +7 -1
- package/src/core/webhook-handlers.mjs +123 -14
- package/src/server/auth.mjs +6 -0
- package/src/server/index.mjs +56 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-manifest.json +40 -0
- package/src/server/routes/_shared.mjs +33 -1
- package/src/server/routes/config.mjs +64 -23
- package/src/server/routes/context.mjs +14 -9
- package/src/server/routes/dashboard.mjs +39 -18
- package/src/server/routes/docs.mjs +27 -13
- package/src/server/routes/dream.mjs +10 -11
- package/src/server/routes/export.mjs +43 -12
- package/src/server/routes/field.mjs +11 -9
- package/src/server/routes/graph.mjs +31 -13
- package/src/server/routes/import.mjs +35 -16
- package/src/server/routes/instructions.mjs +41 -5
- package/src/server/routes/memory.mjs +61 -21
- package/src/server/routes/mesh.mjs +2 -1
- package/src/server/routes/notifications.mjs +103 -0
- package/src/server/routes/rules.mjs +75 -20
- package/src/server/routes/secrets.mjs +110 -4
- package/src/server/routes/share.mjs +2 -1
- package/src/server/routes/ssss.mjs +11 -3
- package/src/server/routes/system.mjs +20 -1
- package/src/server/routes/update.mjs +33 -7
- package/src/server/routes/vault.mjs +53 -28
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fetchConfigJson,
|
|
4
|
+
saveConfigJson,
|
|
5
|
+
fetchHealth,
|
|
6
|
+
runAgentDiagnostics,
|
|
7
|
+
checkUpdate,
|
|
8
|
+
runUpdate,
|
|
9
|
+
triggerRecompile,
|
|
10
|
+
apiFetch,
|
|
11
|
+
getApiBase,
|
|
12
|
+
} from '../api';
|
|
3
13
|
import type { HealthData } from '../types';
|
|
4
14
|
import type { ConfigJson } from '../types';
|
|
5
15
|
import { MobilePairing } from '../components/MobilePairing';
|
|
6
16
|
|
|
17
|
+
function normalizeConfigJson(data: ConfigJson): ConfigJson {
|
|
18
|
+
const next = { ...data } as ConfigJson & Record<string, any>;
|
|
19
|
+
if (!next.security) next.security = {};
|
|
20
|
+
if (!next.security.dashboard) next.security.dashboard = {};
|
|
21
|
+
if (!next.security.api) next.security.api = {};
|
|
22
|
+
if (!next.security.network) next.security.network = {};
|
|
23
|
+
if (!next.security.bind) next.security.bind = {};
|
|
24
|
+
if (!next.security.rate_limits) next.security.rate_limits = {};
|
|
25
|
+
if (!next.security.sandbox) next.security.sandbox = {};
|
|
26
|
+
if (!next.security.privacy) next.security.privacy = {};
|
|
27
|
+
if (!next.budget) next.budget = { budget: {} };
|
|
28
|
+
if (!next.budget.budget) next.budget.budget = {};
|
|
29
|
+
if (!next.brain) next.brain = {};
|
|
30
|
+
if (!next.secrets) next.secrets = {};
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
|
|
7
34
|
export default function SettingsPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
8
35
|
const [configData, setConfigData] = useState<ConfigJson | null>(null);
|
|
36
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
37
|
+
const [loading, setLoading] = useState(true);
|
|
9
38
|
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
|
|
10
39
|
const [sandboxLog, setSandboxLog] = useState('');
|
|
11
40
|
|
|
@@ -14,35 +43,43 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
14
43
|
const [runningDiagnostics, setRunningDiagnostics] = useState(false);
|
|
15
44
|
const [diagnosticLogs, setDiagnosticLogs] = useState<string | null>(null);
|
|
16
45
|
|
|
17
|
-
const [updateInfo, setUpdateInfo] = useState<{
|
|
46
|
+
const [updateInfo, setUpdateInfo] = useState<{
|
|
47
|
+
updateAvailable: boolean;
|
|
48
|
+
latestVersion?: string;
|
|
49
|
+
currentVersion?: string;
|
|
50
|
+
} | null>(null);
|
|
18
51
|
const [updating, setUpdating] = useState(false);
|
|
19
52
|
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
|
|
53
|
+
const [rebuilding, setRebuilding] = useState(false);
|
|
20
54
|
|
|
21
|
-
const
|
|
22
|
-
|
|
55
|
+
const loadConfig = async () => {
|
|
56
|
+
setLoading(true);
|
|
57
|
+
setLoadError(null);
|
|
58
|
+
try {
|
|
59
|
+
const data = await fetchConfigJson();
|
|
60
|
+
setConfigData(normalizeConfigJson(data));
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(err);
|
|
63
|
+
setLoadError(err instanceof Error ? err.message : 'Failed to load settings');
|
|
64
|
+
setConfigData(null);
|
|
65
|
+
} finally {
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
23
69
|
|
|
24
70
|
useEffect(() => {
|
|
25
71
|
fetchHealth().then(setHealth).catch(console.error);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!data.security.bind) data.security.bind = {};
|
|
36
|
-
if (!data.security.rate_limits) data.security.rate_limits = {};
|
|
37
|
-
if (!data.security.sandbox) data.security.sandbox = {};
|
|
38
|
-
if (!data.security.privacy) data.security.privacy = {};
|
|
39
|
-
if (!data.budget) data.budget = { budget: {} };
|
|
40
|
-
if (!data.budget.budget) data.budget.budget = {};
|
|
41
|
-
if (!data.brain) data.brain = {};
|
|
42
|
-
if (!data.secrets) data.secrets = {};
|
|
43
|
-
setConfigData(data);
|
|
44
|
-
})
|
|
72
|
+
// Update check must never block config load (server used to spawnSync npm view).
|
|
73
|
+
checkUpdate()
|
|
74
|
+
.then((u) =>
|
|
75
|
+
setUpdateInfo({
|
|
76
|
+
updateAvailable: !!u.updateAvailable,
|
|
77
|
+
latestVersion: u.latestVersion,
|
|
78
|
+
currentVersion: u.currentVersion,
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
45
81
|
.catch(console.error);
|
|
82
|
+
loadConfig();
|
|
46
83
|
}, [activeBrainId]);
|
|
47
84
|
|
|
48
85
|
|
|
@@ -54,6 +91,12 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
54
91
|
{ id: 'grok', name: 'Grok CLI', desc: 'xAI developer binary integration' }
|
|
55
92
|
];
|
|
56
93
|
|
|
94
|
+
const availableAgents: string[] = Array.isArray(health?.cli_agents)
|
|
95
|
+
? health.cli_agents
|
|
96
|
+
: Array.isArray((health as unknown as { agents?: string[] } | null)?.agents)
|
|
97
|
+
? ((health as unknown as { agents: string[] }).agents)
|
|
98
|
+
: [];
|
|
99
|
+
|
|
57
100
|
const handleRunDiagnostics = async (e: React.FormEvent) => {
|
|
58
101
|
e.preventDefault();
|
|
59
102
|
setRunningDiagnostics(true);
|
|
@@ -74,7 +117,36 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
74
117
|
setUpdateMessage(null);
|
|
75
118
|
try {
|
|
76
119
|
const res = await runUpdate();
|
|
77
|
-
|
|
120
|
+
if (res.success) {
|
|
121
|
+
const s = res.summary;
|
|
122
|
+
const bits = [
|
|
123
|
+
s?.updated != null ? `${s.updated} updated` : null,
|
|
124
|
+
s?.up_to_date != null ? `${s.up_to_date} current` : null,
|
|
125
|
+
s?.latest ? `latest ${s.latest}` : null,
|
|
126
|
+
].filter(Boolean);
|
|
127
|
+
setUpdateMessage(
|
|
128
|
+
bits.length
|
|
129
|
+
? `Update complete ā ${bits.join(' Ā· ')}. Restart daemon/server if this host still runs old code.`
|
|
130
|
+
: res.message || 'Update complete.',
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
const failDetail = (res.summary?.results || [])
|
|
134
|
+
.filter((r) => r.status === 'failed')
|
|
135
|
+
.map((r) => `${r.name}: ${r.error || 'failed'}`)
|
|
136
|
+
.slice(0, 3)
|
|
137
|
+
.join('; ');
|
|
138
|
+
setUpdateMessage(
|
|
139
|
+
`Update failed: ${res.message}${failDetail ? ` (${failDetail})` : ''}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const refreshed = await checkUpdate().catch(() => null);
|
|
143
|
+
if (refreshed) {
|
|
144
|
+
setUpdateInfo({
|
|
145
|
+
updateAvailable: !!refreshed.updateAvailable,
|
|
146
|
+
latestVersion: refreshed.latestVersion,
|
|
147
|
+
currentVersion: refreshed.currentVersion,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
78
150
|
} catch (err: unknown) {
|
|
79
151
|
setUpdateMessage('Update error: ' + (err as Error).message);
|
|
80
152
|
} finally {
|
|
@@ -86,7 +158,18 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
86
158
|
if (!configData) return;
|
|
87
159
|
setSaveStatus('saving');
|
|
88
160
|
try {
|
|
89
|
-
|
|
161
|
+
// Do not persist masked secret placeholders back into the store.
|
|
162
|
+
const payload = { ...configData } as ConfigJson & { secrets?: Record<string, string> };
|
|
163
|
+
if (payload.secrets) {
|
|
164
|
+
const cleaned: Record<string, string> = {};
|
|
165
|
+
for (const [k, v] of Object.entries(payload.secrets)) {
|
|
166
|
+
if (typeof v === 'string' && v && !v.includes('ā¢') && v !== '[redacted]') {
|
|
167
|
+
cleaned[k] = v;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
payload.secrets = cleaned;
|
|
171
|
+
}
|
|
172
|
+
await saveConfigJson(payload);
|
|
90
173
|
setSaveStatus('saved');
|
|
91
174
|
setTimeout(() => setSaveStatus('idle'), 2500);
|
|
92
175
|
} catch (err) {
|
|
@@ -96,18 +179,39 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
96
179
|
}
|
|
97
180
|
};
|
|
98
181
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
182
|
+
const handleRebuildIndex = async () => {
|
|
183
|
+
setRebuilding(true);
|
|
184
|
+
setSandboxLog('[Running] Rebuilding instruction surfacesā¦');
|
|
185
|
+
try {
|
|
186
|
+
const res = await triggerRecompile();
|
|
187
|
+
setSandboxLog(
|
|
188
|
+
`[Finished] Rebuilding instruction surfaces\n\n${res.message || (res.success ? 'Compile complete.' : 'Compile finished with warnings.')}`,
|
|
189
|
+
);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
setSandboxLog(`[Failed] Rebuild\n\n${err instanceof Error ? err.message : String(err)}`);
|
|
192
|
+
} finally {
|
|
193
|
+
setRebuilding(false);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const handleDownloadExtension = async () => {
|
|
198
|
+
try {
|
|
199
|
+
const res = await apiFetch(`${getApiBase()}/api/extension/download`);
|
|
200
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
201
|
+
const blob = await res.blob();
|
|
202
|
+
const url = URL.createObjectURL(blob);
|
|
203
|
+
const a = document.createElement('a');
|
|
204
|
+
a.href = url;
|
|
205
|
+
a.download = 'total-recall-extension.zip';
|
|
206
|
+
document.body.appendChild(a);
|
|
207
|
+
a.click();
|
|
208
|
+
a.remove();
|
|
209
|
+
URL.revokeObjectURL(url);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
setSandboxLog(
|
|
212
|
+
`[Failed] Extension download\n\n${err instanceof Error ? err.message : String(err)}`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
111
215
|
};
|
|
112
216
|
|
|
113
217
|
// State Update Helpers
|
|
@@ -153,12 +257,16 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
153
257
|
|
|
154
258
|
// Instead of parsing it back and forth on every keystroke,
|
|
155
259
|
// we just use simple helpers that don't aggressively trim trailing commas.
|
|
156
|
-
const getCsv = (arr: unknown) => Array.isArray(arr) ? arr.join(', ') : '';
|
|
157
|
-
const setCsv = (str: string) =>
|
|
158
|
-
|
|
159
|
-
|
|
260
|
+
const getCsv = (arr: unknown) => Array.isArray(arr) ? arr.filter(Boolean).join(', ') : '';
|
|
261
|
+
const setCsv = (str: string) =>
|
|
262
|
+
str
|
|
263
|
+
.split(',')
|
|
264
|
+
.map((s) => s.trim())
|
|
265
|
+
.filter((s) => s.length > 0);
|
|
266
|
+
|
|
267
|
+
if (loading && !configData) {
|
|
160
268
|
return (
|
|
161
|
-
<div style={{ padding: 40, color: 'var(--text-secondary)' }}>
|
|
269
|
+
<div style={{ padding: 40, color: 'var(--text-secondary)' }} data-testid="settings-loading">
|
|
162
270
|
<div className="glass-card" style={{ display: 'inline-block', padding: '12px 24px' }}>
|
|
163
271
|
Loading Settings...
|
|
164
272
|
</div>
|
|
@@ -166,8 +274,38 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
166
274
|
);
|
|
167
275
|
}
|
|
168
276
|
|
|
277
|
+
if (loadError && !configData) {
|
|
278
|
+
return (
|
|
279
|
+
<div style={{ padding: 40 }} data-testid="settings-error" role="alert">
|
|
280
|
+
<div
|
|
281
|
+
className="glass-card"
|
|
282
|
+
style={{
|
|
283
|
+
display: 'inline-block',
|
|
284
|
+
padding: '16px 24px',
|
|
285
|
+
border: '1px solid #ef4444',
|
|
286
|
+
color: '#ef4444',
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
Failed to load settings: {loadError}
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
className="btn-primary"
|
|
293
|
+
style={{ marginLeft: 16 }}
|
|
294
|
+
onClick={() => loadConfig()}
|
|
295
|
+
>
|
|
296
|
+
Retry
|
|
297
|
+
</button>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!configData) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
169
307
|
return (
|
|
170
|
-
<div className="settings-page-wrapper">
|
|
308
|
+
<div className="settings-page-wrapper" data-testid="settings-page">
|
|
171
309
|
<style>{`
|
|
172
310
|
.settings-page-wrapper {
|
|
173
311
|
padding: 32px 40px 100px;
|
|
@@ -627,7 +765,12 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
627
765
|
step="0.01"
|
|
628
766
|
className="settings-input"
|
|
629
767
|
value={configData.budget.budget?.daily_cap_usd ?? 5.0}
|
|
630
|
-
onChange={(e) =>
|
|
768
|
+
onChange={(e) =>
|
|
769
|
+
updateBudgetProp(
|
|
770
|
+
'daily_cap_usd',
|
|
771
|
+
e.target.value === '' ? null : Number(e.target.value),
|
|
772
|
+
)
|
|
773
|
+
}
|
|
631
774
|
/>
|
|
632
775
|
</div>
|
|
633
776
|
<div className="field-col" style={{ flex: 1 }}>
|
|
@@ -638,7 +781,12 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
638
781
|
step="0.01"
|
|
639
782
|
className="settings-input"
|
|
640
783
|
value={configData.budget.budget?.weekly_cap_usd ?? 20.0}
|
|
641
|
-
onChange={(e) =>
|
|
784
|
+
onChange={(e) =>
|
|
785
|
+
updateBudgetProp(
|
|
786
|
+
'weekly_cap_usd',
|
|
787
|
+
e.target.value === '' ? null : Number(e.target.value),
|
|
788
|
+
)
|
|
789
|
+
}
|
|
642
790
|
/>
|
|
643
791
|
</div>
|
|
644
792
|
</div>
|
|
@@ -654,28 +802,10 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
654
802
|
</div>
|
|
655
803
|
</div>
|
|
656
804
|
|
|
657
|
-
<
|
|
658
|
-
|
|
659
|
-
<
|
|
660
|
-
|
|
661
|
-
style={{ border: '1px solid var(--border-accent)', background: 'rgba(59, 130, 246, 0.05)' }}
|
|
662
|
-
value={activeBrain}
|
|
663
|
-
onChange={(e) => {
|
|
664
|
-
const val = e.target.value;
|
|
665
|
-
if (val) {
|
|
666
|
-
localStorage.setItem('total-recall-active-brain', val);
|
|
667
|
-
} else {
|
|
668
|
-
localStorage.removeItem('total-recall-active-brain');
|
|
669
|
-
}
|
|
670
|
-
window.location.reload();
|
|
671
|
-
}}
|
|
672
|
-
>
|
|
673
|
-
<option value="">Global Brain (Root)</option>
|
|
674
|
-
{brains.map((b: any) => (
|
|
675
|
-
<option key={b.name} value={b.name}>{b.name} ({b.role} - {b.nodes} nodes)</option>
|
|
676
|
-
))}
|
|
677
|
-
</select>
|
|
678
|
-
</div>
|
|
805
|
+
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '0 0 12px', lineHeight: 1.5 }}>
|
|
806
|
+
Active vault layer is chosen in the <strong>sidebar brain selector</strong> (bottom left).
|
|
807
|
+
Fields below edit this installās <code>brain.json</code> identity ā not the active layer.
|
|
808
|
+
</p>
|
|
679
809
|
|
|
680
810
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
681
811
|
<div className="field-col" style={{ flex: 1 }}>
|
|
@@ -765,7 +895,7 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
765
895
|
<option value="antigravity">Antigravity (Google Deepmind)</option>
|
|
766
896
|
<option value="gemini">Gemini (Google)</option>
|
|
767
897
|
<option value="claude">Claude (Anthropic)</option>
|
|
768
|
-
<option value="
|
|
898
|
+
<option value="codex">Codex (OpenAI)</option>
|
|
769
899
|
<option value="grok">Grok (xAI)</option>
|
|
770
900
|
</select>
|
|
771
901
|
</div>
|
|
@@ -788,22 +918,30 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
788
918
|
<p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
|
|
789
919
|
Capture context directly from your browser. Install from the Chrome Web Store or load unpacked from <code>/extension</code> directory.
|
|
790
920
|
</p>
|
|
791
|
-
<
|
|
921
|
+
<button
|
|
922
|
+
type="button"
|
|
923
|
+
className="btn-primary"
|
|
924
|
+
style={{ display: 'block', width: '100%', boxSizing: 'border-box' }}
|
|
925
|
+
onClick={handleDownloadExtension}
|
|
926
|
+
>
|
|
927
|
+
Download Extension
|
|
928
|
+
</button>
|
|
792
929
|
</div>
|
|
793
930
|
|
|
794
931
|
<div style={{ background: 'rgba(255,255,255,0.03)', padding: 20, borderRadius: 12, border: '1px solid rgba(255,255,255,0.06)' }}>
|
|
795
932
|
<div style={{ fontSize: 24, marginBottom: 12 }}>š ļø</div>
|
|
796
933
|
<h4 style={{ margin: '0 0 8px', fontSize: 15 }}>Developer Utilities</h4>
|
|
797
934
|
<p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
|
|
798
|
-
|
|
935
|
+
Rebuild compiled instruction surfaces (vault compile) without a shell sandbox.
|
|
799
936
|
</p>
|
|
800
937
|
<div style={{ display: 'flex', gap: 12 }}>
|
|
801
938
|
<button
|
|
802
939
|
className="btn-secondary"
|
|
803
940
|
style={{ flex: 1 }}
|
|
804
|
-
|
|
941
|
+
disabled={rebuilding}
|
|
942
|
+
onClick={handleRebuildIndex}
|
|
805
943
|
>
|
|
806
|
-
Rebuild Index
|
|
944
|
+
{rebuilding ? 'Rebuildingā¦' : 'Rebuild Index'}
|
|
807
945
|
</button>
|
|
808
946
|
</div>
|
|
809
947
|
</div>
|
|
@@ -826,13 +964,19 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
826
964
|
</div>
|
|
827
965
|
</div>
|
|
828
966
|
|
|
829
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 24, padding: '16px 20px', background: 'rgba(0,0,0,0.2)', borderRadius: 12, border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
967
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 24, padding: '16px 20px', background: 'rgba(0,0,0,0.2)', borderRadius: 12, border: '1px solid rgba(255,255,255,0.05)', gap: 16, flexWrap: 'wrap' }}>
|
|
830
968
|
<div>
|
|
831
969
|
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
832
|
-
{updateInfo?.updateAvailable
|
|
970
|
+
{updateInfo?.updateAvailable
|
|
971
|
+
? `Update available (latest v${updateInfo.latestVersion || '?'})`
|
|
972
|
+
: 'Host package is up to date'}
|
|
833
973
|
</div>
|
|
834
974
|
<div style={{ fontSize: 12, color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
835
|
-
{updateInfo?.
|
|
975
|
+
Running v{updateInfo?.currentVersion || health?.version || 'ā'}
|
|
976
|
+
{updateInfo?.latestVersion ? ` Ā· npm latest v${updateInfo.latestVersion}` : ''}
|
|
977
|
+
{updateInfo?.updateAvailable
|
|
978
|
+
? ' Ā· registered projects or host package can be upgraded'
|
|
979
|
+
: ''}
|
|
836
980
|
</div>
|
|
837
981
|
{updateMessage && (
|
|
838
982
|
<div style={{ fontSize: 12, color: updateMessage.includes('error') || updateMessage.includes('failed') ? '#ef4444' : '#10b981', marginTop: 8 }}>
|
|
@@ -840,16 +984,14 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
840
984
|
</div>
|
|
841
985
|
)}
|
|
842
986
|
</div>
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
</button>
|
|
852
|
-
)}
|
|
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>
|
|
853
995
|
</div>
|
|
854
996
|
</div>
|
|
855
997
|
|
|
@@ -868,7 +1010,7 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
868
1010
|
|
|
869
1011
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
870
1012
|
{AGENTS_LIST.map((a) => {
|
|
871
|
-
const activeAgent =
|
|
1013
|
+
const activeAgent = availableAgents.includes(a.id);
|
|
872
1014
|
return (
|
|
873
1015
|
<div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 8, border: '1px solid var(--border)' }}>
|
|
874
1016
|
<div style={{ flex: 1 }}>
|
package/package.json
CHANGED
package/src/cli/agent-dir.mjs
CHANGED
|
@@ -56,7 +56,8 @@ export function detectProjectBrain(startDir = process.cwd()) {
|
|
|
56
56
|
if (fs.existsSync(path.join(c.candidate, 'SKILL.md')) || fs.existsSync(path.join(c.candidate, 'memory-vault'))) {
|
|
57
57
|
return {
|
|
58
58
|
agentDir: c.dir,
|
|
59
|
-
|
|
59
|
+
// Project brain lives at the detected candidate ā never alias to global.
|
|
60
|
+
brainDir: c.candidate,
|
|
60
61
|
projectRoot: dir,
|
|
61
62
|
};
|
|
62
63
|
}
|
|
@@ -90,7 +91,21 @@ export function resolveAgentDir(layer = 'auto', targetPath = process.cwd()) {
|
|
|
90
91
|
* @param {'global' | 'project' | 'auto'} [layer='auto']
|
|
91
92
|
*/
|
|
92
93
|
export function resolveBrainDir(layer = 'auto', targetPath = process.cwd()) {
|
|
93
|
-
|
|
94
|
+
// Match resolveAgentDir: AGENT_DIR override pins the agent root ā brain under it.
|
|
95
|
+
if (process.env.AGENT_DIR) {
|
|
96
|
+
return path.join(process.env.AGENT_DIR, 'skills', 'total-recall');
|
|
97
|
+
}
|
|
98
|
+
if (layer === 'global') return getGlobalBrainDir();
|
|
99
|
+
if (layer === 'project') {
|
|
100
|
+
const project = detectProjectBrain(targetPath);
|
|
101
|
+
if (!project) {
|
|
102
|
+
throw new Error('No project brain found. Run `npx total-recall init --project` to create one.');
|
|
103
|
+
}
|
|
104
|
+
return project.brainDir;
|
|
105
|
+
}
|
|
106
|
+
// Auto: project-local wins if a project brain exists
|
|
107
|
+
const project = detectProjectBrain(targetPath);
|
|
108
|
+
return project ? project.brainDir : getGlobalBrainDir();
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
/**
|
package/src/cli/chat.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { callLocalRuntime, loadRuntimeConfig, checkRuntimeHealth } from '../core
|
|
|
10
10
|
export default async function runChat(args) {
|
|
11
11
|
console.log('š¤ Total Recall CLI Chat initialized. Type "exit" to quit.\n');
|
|
12
12
|
|
|
13
|
-
const configDir = path.join(resolveBrainDir(), 'config');
|
|
13
|
+
const configDir = path.join(resolveBrainDir('global'), 'config');
|
|
14
14
|
|
|
15
15
|
let config;
|
|
16
16
|
let caller;
|
package/src/cli/daemon.mjs
CHANGED
|
@@ -22,7 +22,8 @@ import { startDaemon, stopDaemon, readPid } from '../core/daemon-control.mjs';
|
|
|
22
22
|
|
|
23
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const ROOT = path.join(__dirname, '..', '..');
|
|
25
|
-
|
|
25
|
+
// System daemon always targets the global brain ā never the cwd project vault.
|
|
26
|
+
const BRAIN_DIR = resolveBrainDir('global');
|
|
26
27
|
const LOG_FILE = path.join(BRAIN_DIR, 'logs', 'daemon.log');
|
|
27
28
|
|
|
28
29
|
function commandExists(cmd) {
|
package/src/cli/dream.mjs
CHANGED
|
@@ -13,11 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
|
|
16
|
+
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const AGENT_DIR = resolveAgentDir();
|
|
20
|
-
const BRAIN_DIR = resolveBrainDir();
|
|
21
19
|
|
|
22
20
|
function parseArgs(args) {
|
|
23
21
|
const opts = { vault: null, help: false };
|
|
@@ -42,20 +40,26 @@ function printHelp() {
|
|
|
42
40
|
Usage: total-recall dream [options]
|
|
43
41
|
|
|
44
42
|
Options:
|
|
45
|
-
--vault <path> Override vault directory
|
|
43
|
+
--vault <path> Override vault directory
|
|
44
|
+
--global Dream the global brain (default)
|
|
45
|
+
--project Dream the project brain for the current repo
|
|
46
46
|
--help, -h Show this help
|
|
47
47
|
`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export default async function dream(args) {
|
|
51
|
-
const
|
|
51
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
52
|
+
const effectiveLayer = layer === 'auto' ? 'global' : layer;
|
|
53
|
+
const opts = parseArgs(remainingArgs);
|
|
52
54
|
if (opts.help) { printHelp(); return; }
|
|
53
55
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
56
|
+
const agentDir = resolveAgentDir(effectiveLayer);
|
|
57
|
+
const brainDir = resolveBrainDir(effectiveLayer);
|
|
58
|
+
const vaultDir = opts.vault || path.join(brainDir, 'memory-vault');
|
|
59
|
+
const skillsDir = path.join(agentDir, 'skills');
|
|
60
|
+
const derivedDir = path.join(brainDir, 'memory-derived');
|
|
61
|
+
const conflictsDir = path.join(brainDir, 'memory-inbox', 'conflicts');
|
|
62
|
+
const instructionsFile = path.join(agentDir, 'INSTRUCTIONS.md');
|
|
59
63
|
|
|
60
64
|
console.error('\n š Starting dream cycle...\n');
|
|
61
65
|
const startTime = Date.now();
|
package/src/cli/forget.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { resolveAgentDir, resolveBrainDir, parseLayerFlag, getBothBrains } from './agent-dir.mjs';
|
|
4
4
|
import { compileSurface } from '../core/surface.mjs';
|
|
5
5
|
import { deleteNode } from '../core/vault.mjs';
|
|
6
|
+
import { invalidate } from '../core/vault-cache.mjs';
|
|
6
7
|
|
|
7
8
|
function printHelp() {
|
|
8
9
|
console.log(`
|
|
@@ -65,12 +66,16 @@ export default async function forget(args) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
console.log(` ā
Soft-deleted ${count} nodes for project "${projectName}" (moved to .trash/)`);
|
|
69
|
+
|
|
70
|
+
// Files moved under .trash still invalidate the vault cache (watcher may lag)
|
|
71
|
+
invalidate(globalVaultDir);
|
|
68
72
|
|
|
69
73
|
if (!remainingArgs.includes('--no-compile')) {
|
|
70
74
|
console.log(' ā³ Recompiling active memory surfaces and indexes in the background...');
|
|
71
75
|
try {
|
|
72
76
|
const { spawn } = await import('node:child_process');
|
|
73
|
-
|
|
77
|
+
// Soft-delete from global vault tags ā recompile global surfaces
|
|
78
|
+
const child = spawn(process.argv[0], [process.argv[1], 'compile', '--global'], {
|
|
74
79
|
detached: true,
|
|
75
80
|
stdio: 'ignore'
|
|
76
81
|
});
|
|
@@ -130,6 +135,9 @@ export default async function forget(args) {
|
|
|
130
135
|
process.exit(1);
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
// Drop cached nodes immediately (fs.watch may lag or miss same-process deletes)
|
|
139
|
+
invalidate(vaultDir);
|
|
140
|
+
|
|
133
141
|
console.log(` ā
Deleted memory node "${slug}" from ${layerLabel} vault.`);
|
|
134
142
|
|
|
135
143
|
// Recompile unless --no-compile
|
|
@@ -137,7 +145,11 @@ export default async function forget(args) {
|
|
|
137
145
|
console.log(' ā³ Recompiling active memory surfaces and indexes in the background...');
|
|
138
146
|
try {
|
|
139
147
|
const { spawn } = await import('node:child_process');
|
|
140
|
-
|
|
148
|
+
// Pass layer so project forget recompiles the project brain, not auto/wrong vault
|
|
149
|
+
const compileArgs = [process.argv[1], 'compile'];
|
|
150
|
+
if (layer === 'project') compileArgs.push('--project');
|
|
151
|
+
else if (layer === 'global') compileArgs.push('--global');
|
|
152
|
+
const child = spawn(process.argv[0], compileArgs, {
|
|
141
153
|
detached: true,
|
|
142
154
|
stdio: 'ignore'
|
|
143
155
|
});
|
package/src/cli/friction.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export default async function runFriction(args) {
|
|
|
9
9
|
console.log(`[Friction] š Analyzing logs for workflow bottlenecks...`);
|
|
10
10
|
|
|
11
11
|
// Default path for watchdog logs
|
|
12
|
-
const logPath = args[0] || path.join(resolveBrainDir(), 'logs', 'watchdog.jsonl');
|
|
12
|
+
const logPath = args[0] || path.join(resolveBrainDir('global'), 'logs', 'watchdog.jsonl');
|
|
13
13
|
|
|
14
14
|
const result = await detectFriction(logPath);
|
|
15
15
|
if (!result) {
|
package/src/cli/import-rules.mjs
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { detectAndImport } from '../core/import-rules.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
12
12
|
|
|
13
13
|
export async function run(argv = []) {
|
|
14
|
-
const
|
|
14
|
+
const rawArgs = argv.slice(3); // strip node, script, 'import'
|
|
15
|
+
const { layer, remainingArgs: args } = parseLayerFlag(rawArgs);
|
|
15
16
|
|
|
16
17
|
const dirs = [];
|
|
17
18
|
let force = false;
|
|
@@ -33,7 +34,8 @@ export async function run(argv = []) {
|
|
|
33
34
|
// Default: search cwd
|
|
34
35
|
if (dirs.length === 0) dirs.push(process.cwd());
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// auto ā project vault when present (import rules for this repo)
|
|
38
|
+
const brainDir = resolveBrainDir(layer);
|
|
37
39
|
const vaultDir = path.join(brainDir, 'memory-vault');
|
|
38
40
|
|
|
39
41
|
console.log(`\nš Scanning for rule files in: ${dirs.join(', ')}\n`);
|
|
@@ -90,12 +92,15 @@ all future AI sessions.
|
|
|
90
92
|
|
|
91
93
|
Options:
|
|
92
94
|
--dir <path> Directory to search (default: current directory)
|
|
95
|
+
--global Import into the global vault
|
|
96
|
+
--project Import into the project vault (default: auto when present)
|
|
93
97
|
--force Re-import files that were previously imported
|
|
94
98
|
--dry-run Show what would be imported without writing anything
|
|
95
99
|
--help Show this help
|
|
96
100
|
|
|
97
101
|
Examples:
|
|
98
102
|
npx total-recall import
|
|
103
|
+
npx total-recall import --project
|
|
99
104
|
npx total-recall import --dir ~/my-project
|
|
100
105
|
npx total-recall import --force --dry-run
|
|
101
106
|
`);
|