total-recall-brain 3.18.2 → 3.18.3
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-CGqvdwHT.js +5742 -0
- 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 +12 -1
- 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/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 +206 -85
- package/package.json +1 -1
- package/src/cli/agent-dir.mjs +2 -1
- package/src/cli/secret.mjs +180 -3
- package/src/core/daemon-loop.mjs +31 -5
- 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/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 +278 -14
- package/src/core/secrets-sync.mjs +1 -1
- package/src/core/source-adapters.mjs +247 -22
- package/src/core/task-executors.mjs +161 -14
- package/src/core/webhook-handlers.mjs +123 -14
- package/src/server/auth.mjs +6 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-manifest.json +24 -0
- package/src/server/routes/config.mjs +64 -23
- 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/system.mjs +20 -1
- package/src/server/routes/update.mjs +11 -4
- package/frontend/dist/assets/index-D5xRw5uq.js +0 -5750
|
@@ -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);
|
|
@@ -75,6 +118,14 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
75
118
|
try {
|
|
76
119
|
const res = await runUpdate();
|
|
77
120
|
setUpdateMessage(res.success ? 'Update complete. Restarting...' : 'Update failed: ' + res.message);
|
|
121
|
+
const refreshed = await checkUpdate().catch(() => null);
|
|
122
|
+
if (refreshed) {
|
|
123
|
+
setUpdateInfo({
|
|
124
|
+
updateAvailable: !!refreshed.updateAvailable,
|
|
125
|
+
latestVersion: refreshed.latestVersion,
|
|
126
|
+
currentVersion: refreshed.currentVersion,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
78
129
|
} catch (err: unknown) {
|
|
79
130
|
setUpdateMessage('Update error: ' + (err as Error).message);
|
|
80
131
|
} finally {
|
|
@@ -86,7 +137,18 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
86
137
|
if (!configData) return;
|
|
87
138
|
setSaveStatus('saving');
|
|
88
139
|
try {
|
|
89
|
-
|
|
140
|
+
// Do not persist masked secret placeholders back into the store.
|
|
141
|
+
const payload = { ...configData } as ConfigJson & { secrets?: Record<string, string> };
|
|
142
|
+
if (payload.secrets) {
|
|
143
|
+
const cleaned: Record<string, string> = {};
|
|
144
|
+
for (const [k, v] of Object.entries(payload.secrets)) {
|
|
145
|
+
if (typeof v === 'string' && v && !v.includes('•') && v !== '[redacted]') {
|
|
146
|
+
cleaned[k] = v;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
payload.secrets = cleaned;
|
|
150
|
+
}
|
|
151
|
+
await saveConfigJson(payload);
|
|
90
152
|
setSaveStatus('saved');
|
|
91
153
|
setTimeout(() => setSaveStatus('idle'), 2500);
|
|
92
154
|
} catch (err) {
|
|
@@ -96,18 +158,39 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
96
158
|
}
|
|
97
159
|
};
|
|
98
160
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
161
|
+
const handleRebuildIndex = async () => {
|
|
162
|
+
setRebuilding(true);
|
|
163
|
+
setSandboxLog('[Running] Rebuilding instruction surfaces…');
|
|
164
|
+
try {
|
|
165
|
+
const res = await triggerRecompile();
|
|
166
|
+
setSandboxLog(
|
|
167
|
+
`[Finished] Rebuilding instruction surfaces\n\n${res.message || (res.success ? 'Compile complete.' : 'Compile finished with warnings.')}`,
|
|
168
|
+
);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
setSandboxLog(`[Failed] Rebuild\n\n${err instanceof Error ? err.message : String(err)}`);
|
|
171
|
+
} finally {
|
|
172
|
+
setRebuilding(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleDownloadExtension = async () => {
|
|
177
|
+
try {
|
|
178
|
+
const res = await apiFetch(`${getApiBase()}/api/extension/download`);
|
|
179
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
180
|
+
const blob = await res.blob();
|
|
181
|
+
const url = URL.createObjectURL(blob);
|
|
182
|
+
const a = document.createElement('a');
|
|
183
|
+
a.href = url;
|
|
184
|
+
a.download = 'total-recall-extension.zip';
|
|
185
|
+
document.body.appendChild(a);
|
|
186
|
+
a.click();
|
|
187
|
+
a.remove();
|
|
188
|
+
URL.revokeObjectURL(url);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
setSandboxLog(
|
|
191
|
+
`[Failed] Extension download\n\n${err instanceof Error ? err.message : String(err)}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
111
194
|
};
|
|
112
195
|
|
|
113
196
|
// State Update Helpers
|
|
@@ -153,12 +236,16 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
153
236
|
|
|
154
237
|
// Instead of parsing it back and forth on every keystroke,
|
|
155
238
|
// 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
|
-
|
|
239
|
+
const getCsv = (arr: unknown) => Array.isArray(arr) ? arr.filter(Boolean).join(', ') : '';
|
|
240
|
+
const setCsv = (str: string) =>
|
|
241
|
+
str
|
|
242
|
+
.split(',')
|
|
243
|
+
.map((s) => s.trim())
|
|
244
|
+
.filter((s) => s.length > 0);
|
|
245
|
+
|
|
246
|
+
if (loading && !configData) {
|
|
160
247
|
return (
|
|
161
|
-
<div style={{ padding: 40, color: 'var(--text-secondary)' }}>
|
|
248
|
+
<div style={{ padding: 40, color: 'var(--text-secondary)' }} data-testid="settings-loading">
|
|
162
249
|
<div className="glass-card" style={{ display: 'inline-block', padding: '12px 24px' }}>
|
|
163
250
|
Loading Settings...
|
|
164
251
|
</div>
|
|
@@ -166,8 +253,38 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
166
253
|
);
|
|
167
254
|
}
|
|
168
255
|
|
|
256
|
+
if (loadError && !configData) {
|
|
257
|
+
return (
|
|
258
|
+
<div style={{ padding: 40 }} data-testid="settings-error" role="alert">
|
|
259
|
+
<div
|
|
260
|
+
className="glass-card"
|
|
261
|
+
style={{
|
|
262
|
+
display: 'inline-block',
|
|
263
|
+
padding: '16px 24px',
|
|
264
|
+
border: '1px solid #ef4444',
|
|
265
|
+
color: '#ef4444',
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
Failed to load settings: {loadError}
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
className="btn-primary"
|
|
272
|
+
style={{ marginLeft: 16 }}
|
|
273
|
+
onClick={() => loadConfig()}
|
|
274
|
+
>
|
|
275
|
+
Retry
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!configData) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
169
286
|
return (
|
|
170
|
-
<div className="settings-page-wrapper">
|
|
287
|
+
<div className="settings-page-wrapper" data-testid="settings-page">
|
|
171
288
|
<style>{`
|
|
172
289
|
.settings-page-wrapper {
|
|
173
290
|
padding: 32px 40px 100px;
|
|
@@ -627,7 +744,12 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
627
744
|
step="0.01"
|
|
628
745
|
className="settings-input"
|
|
629
746
|
value={configData.budget.budget?.daily_cap_usd ?? 5.0}
|
|
630
|
-
onChange={(e) =>
|
|
747
|
+
onChange={(e) =>
|
|
748
|
+
updateBudgetProp(
|
|
749
|
+
'daily_cap_usd',
|
|
750
|
+
e.target.value === '' ? null : Number(e.target.value),
|
|
751
|
+
)
|
|
752
|
+
}
|
|
631
753
|
/>
|
|
632
754
|
</div>
|
|
633
755
|
<div className="field-col" style={{ flex: 1 }}>
|
|
@@ -638,7 +760,12 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
638
760
|
step="0.01"
|
|
639
761
|
className="settings-input"
|
|
640
762
|
value={configData.budget.budget?.weekly_cap_usd ?? 20.0}
|
|
641
|
-
onChange={(e) =>
|
|
763
|
+
onChange={(e) =>
|
|
764
|
+
updateBudgetProp(
|
|
765
|
+
'weekly_cap_usd',
|
|
766
|
+
e.target.value === '' ? null : Number(e.target.value),
|
|
767
|
+
)
|
|
768
|
+
}
|
|
642
769
|
/>
|
|
643
770
|
</div>
|
|
644
771
|
</div>
|
|
@@ -654,28 +781,10 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
654
781
|
</div>
|
|
655
782
|
</div>
|
|
656
783
|
|
|
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>
|
|
784
|
+
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '0 0 12px', lineHeight: 1.5 }}>
|
|
785
|
+
Active vault layer is chosen in the <strong>sidebar brain selector</strong> (bottom left).
|
|
786
|
+
Fields below edit this install’s <code>brain.json</code> identity — not the active layer.
|
|
787
|
+
</p>
|
|
679
788
|
|
|
680
789
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
681
790
|
<div className="field-col" style={{ flex: 1 }}>
|
|
@@ -765,7 +874,7 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
765
874
|
<option value="antigravity">Antigravity (Google Deepmind)</option>
|
|
766
875
|
<option value="gemini">Gemini (Google)</option>
|
|
767
876
|
<option value="claude">Claude (Anthropic)</option>
|
|
768
|
-
<option value="
|
|
877
|
+
<option value="codex">Codex (OpenAI)</option>
|
|
769
878
|
<option value="grok">Grok (xAI)</option>
|
|
770
879
|
</select>
|
|
771
880
|
</div>
|
|
@@ -788,22 +897,30 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
788
897
|
<p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
|
|
789
898
|
Capture context directly from your browser. Install from the Chrome Web Store or load unpacked from <code>/extension</code> directory.
|
|
790
899
|
</p>
|
|
791
|
-
<
|
|
900
|
+
<button
|
|
901
|
+
type="button"
|
|
902
|
+
className="btn-primary"
|
|
903
|
+
style={{ display: 'block', width: '100%', boxSizing: 'border-box' }}
|
|
904
|
+
onClick={handleDownloadExtension}
|
|
905
|
+
>
|
|
906
|
+
Download Extension
|
|
907
|
+
</button>
|
|
792
908
|
</div>
|
|
793
909
|
|
|
794
910
|
<div style={{ background: 'rgba(255,255,255,0.03)', padding: 20, borderRadius: 12, border: '1px solid rgba(255,255,255,0.06)' }}>
|
|
795
911
|
<div style={{ fontSize: 24, marginBottom: 12 }}>🛠️</div>
|
|
796
912
|
<h4 style={{ margin: '0 0 8px', fontSize: 15 }}>Developer Utilities</h4>
|
|
797
913
|
<p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
|
|
798
|
-
|
|
914
|
+
Rebuild compiled instruction surfaces (vault compile) without a shell sandbox.
|
|
799
915
|
</p>
|
|
800
916
|
<div style={{ display: 'flex', gap: 12 }}>
|
|
801
917
|
<button
|
|
802
918
|
className="btn-secondary"
|
|
803
919
|
style={{ flex: 1 }}
|
|
804
|
-
|
|
920
|
+
disabled={rebuilding}
|
|
921
|
+
onClick={handleRebuildIndex}
|
|
805
922
|
>
|
|
806
|
-
Rebuild Index
|
|
923
|
+
{rebuilding ? 'Rebuilding…' : 'Rebuild Index'}
|
|
807
924
|
</button>
|
|
808
925
|
</div>
|
|
809
926
|
</div>
|
|
@@ -826,13 +943,19 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
826
943
|
</div>
|
|
827
944
|
</div>
|
|
828
945
|
|
|
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)' }}>
|
|
946
|
+
<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
947
|
<div>
|
|
831
948
|
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
832
|
-
{updateInfo?.updateAvailable
|
|
949
|
+
{updateInfo?.updateAvailable
|
|
950
|
+
? `Update available (latest v${updateInfo.latestVersion || '?'})`
|
|
951
|
+
: 'Host package is up to date'}
|
|
833
952
|
</div>
|
|
834
953
|
<div style={{ fontSize: 12, color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
835
|
-
{updateInfo?.
|
|
954
|
+
Running v{updateInfo?.currentVersion || health?.version || '—'}
|
|
955
|
+
{updateInfo?.latestVersion ? ` · npm latest v${updateInfo.latestVersion}` : ''}
|
|
956
|
+
{updateInfo?.updateAvailable
|
|
957
|
+
? ' · registered projects or host package can be upgraded'
|
|
958
|
+
: ''}
|
|
836
959
|
</div>
|
|
837
960
|
{updateMessage && (
|
|
838
961
|
<div style={{ fontSize: 12, color: updateMessage.includes('error') || updateMessage.includes('failed') ? '#ef4444' : '#10b981', marginTop: 8 }}>
|
|
@@ -840,16 +963,14 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
840
963
|
</div>
|
|
841
964
|
)}
|
|
842
965
|
</div>
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
</button>
|
|
852
|
-
)}
|
|
966
|
+
<button
|
|
967
|
+
className="btn-primary"
|
|
968
|
+
onClick={handleRunUpdate}
|
|
969
|
+
disabled={updating}
|
|
970
|
+
style={{ background: updateInfo?.updateAvailable ? '#3fb950' : undefined, border: 'none' }}
|
|
971
|
+
>
|
|
972
|
+
{updating ? 'Updating…' : updateInfo?.updateAvailable ? 'Apply Updates' : 'Check & Sync Projects'}
|
|
973
|
+
</button>
|
|
853
974
|
</div>
|
|
854
975
|
</div>
|
|
855
976
|
|
|
@@ -868,7 +989,7 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
868
989
|
|
|
869
990
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
870
991
|
{AGENTS_LIST.map((a) => {
|
|
871
|
-
const activeAgent =
|
|
992
|
+
const activeAgent = availableAgents.includes(a.id);
|
|
872
993
|
return (
|
|
873
994
|
<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
995
|
<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
|
}
|