total-recall-brain 3.13.2 → 3.14.4
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/bin/total-recall.mjs +3 -0
- package/frontend/src/App.tsx +8 -7
- package/frontend/src/api.ts +25 -0
- package/frontend/src/components/UsageChart.tsx +1 -1
- package/frontend/src/pages/ApiKeysPage.tsx +118 -42
- package/frontend/src/pages/ChatPage.tsx +1 -1
- package/frontend/src/pages/DesignDocsPage.tsx +28 -21
- package/frontend/src/pages/FilesPage.tsx +6 -1
- package/frontend/src/pages/GraphPage.tsx +5 -1
- package/frontend/src/pages/HealthPage.tsx +1 -1
- package/frontend/src/pages/InboxPage.tsx +2 -1
- package/frontend/src/pages/InstructionsPage.tsx +26 -26
- package/frontend/src/pages/IntegrationsPage.tsx +11 -5
- package/frontend/src/pages/MemoryPage.tsx +27 -118
- package/frontend/src/pages/ModelsPage.tsx +2 -50
- package/frontend/src/pages/OnboardingPage.tsx +5 -10
- package/frontend/src/pages/OpenWikiPage.tsx +1 -0
- package/frontend/src/pages/SettingsPage.tsx +136 -131
- package/frontend/src/pages/SkillsPage.tsx +102 -46
- package/frontend/src/pages/TasksPage.tsx +60 -25
- package/frontend/src/pages/UsagePage.tsx +206 -95
- package/frontend/src/types.ts +17 -11
- package/frontend/src/utils/onboarding.ts +10 -0
- package/package.json +3 -2
- package/scaffold/.agent/skills/ssss/SKILL.md +235 -0
- package/scaffold/.agent/skills/ssss/evals/evals.json +37 -0
- package/scaffold/.agent/skills/ssss/references/contract-reference.md +95 -0
- package/scaffold/.agent/skills/ssss/scripts/validate-schema.mjs +215 -0
- package/scaffold/.agent/skills/ssss/subagents/audit-ssss-contract.md +22 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +1 -0
- package/src/cli/backup.mjs +3 -3
- package/src/cli/chat.mjs +2 -2
- package/src/cli/config.mjs +2 -9
- package/src/cli/connect.mjs +1 -1
- package/src/cli/deploy-ui.mjs +1 -1
- package/src/cli/deploy.mjs +2 -2
- package/src/cli/doctor.mjs +1 -1
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +19 -1
- package/src/cli/key.mjs +150 -0
- package/src/cli/secret.mjs +30 -1
- package/src/cli/skill.mjs +0 -2
- package/src/cli/sync.mjs +19 -0
- package/src/cli/upgrade.mjs +1 -1
- package/src/cli/wizard.html +5 -5
- package/src/core/crons.mjs +11 -0
- package/src/core/daemon-loop.mjs +11 -0
- package/src/core/import-rules.mjs +4 -3
- package/src/core/okf-adapter.mjs +10 -10
- package/src/core/runtime.mjs +0 -2
- package/src/core/secrets-store.mjs +16 -0
- package/src/core/skills-registry.mjs +38 -0
- package/src/core/surface.mjs +1 -1
- package/src/core/task_runner.mjs +6 -0
- package/src/core/usage-tracker.mjs +16 -3
- package/src/core/vault-watcher.mjs +92 -0
- package/src/core/vault.mjs +0 -2
- package/src/server/api.mjs +1 -1
- package/src/server/rest.mjs +69 -57
- package/src/server/route-manifest.json +8 -0
- package/src/server/routes/_shared.mjs +1 -1
- package/src/server/routes/secrets.mjs +4 -4
- package/src/server/routes/skills.mjs +92 -10
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
installRegistrySkill,
|
|
11
11
|
runSandbox,
|
|
12
12
|
listResearch,
|
|
13
|
-
createResearch
|
|
13
|
+
createResearch,
|
|
14
|
+
toggleSkillRepo,
|
|
15
|
+
auditSkill
|
|
14
16
|
} from '../api';
|
|
15
17
|
import type { ResearchItem } from '../types';
|
|
16
18
|
|
|
@@ -64,11 +66,6 @@ interface SubFileEntry {
|
|
|
64
66
|
|
|
65
67
|
// ─── Automation tab types ───────────────────────────────────────────────────────
|
|
66
68
|
|
|
67
|
-
interface ImprovementSuggestion {
|
|
68
|
-
skillName: string;
|
|
69
|
-
type: 'eval_gap' | 'missing_reference' | 'stale_script';
|
|
70
|
-
description: string;
|
|
71
|
-
}
|
|
72
69
|
|
|
73
70
|
type TabId = 'active' | 'registry' | 'lifecycle' | 'network' | 'automation';
|
|
74
71
|
|
|
@@ -150,30 +147,13 @@ function computeHealthChecks(skill: SkillItem, content: string): HealthCheck[] {
|
|
|
150
147
|
return [hasSkillMd, ...dirChecks, hasEvals, descCheck];
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
function generateSuggestions(skills: SkillItem[]): ImprovementSuggestion[] {
|
|
156
|
-
const suggestions: ImprovementSuggestion[] = [];
|
|
157
|
-
skills.forEach(s => {
|
|
158
|
-
const subs = (s.subSkills || []).map(x => x.toLowerCase());
|
|
159
|
-
if (!subs.includes('evals')) {
|
|
160
|
-
suggestions.push({ skillName: s.name, type: 'eval_gap', description: `Add evals directory with assertion tests for "${s.name}"` });
|
|
161
|
-
}
|
|
162
|
-
if (!subs.includes('references')) {
|
|
163
|
-
suggestions.push({ skillName: s.name, type: 'missing_reference', description: `Add reference documentation for "${s.name}"` });
|
|
164
|
-
}
|
|
165
|
-
if (!subs.includes('scripts')) {
|
|
166
|
-
suggestions.push({ skillName: s.name, type: 'stale_script', description: `Add automation scripts for "${s.name}"` });
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
return suggestions.slice(0, 15); // Cap at 15
|
|
170
|
-
}
|
|
150
|
+
// Removed generateSuggestions helper since we now use the backend AI audit
|
|
171
151
|
|
|
172
152
|
// ─── Helper: format file sizes ──────────────────────────────────────────────────
|
|
173
153
|
|
|
174
154
|
|
|
175
155
|
|
|
176
|
-
export default function SkillsPage() {
|
|
156
|
+
export default function SkillsPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
177
157
|
const [skills, setSkills] = useState<SkillItem[]>([]);
|
|
178
158
|
const [repoGroups, setRepoGroups] = useState<RepoSkillsGroup[]>([]);
|
|
179
159
|
const [selectedSkill, setSelectedSkill] = useState<SkillItem | null>(null);
|
|
@@ -216,12 +196,45 @@ export default function SkillsPage() {
|
|
|
216
196
|
const [researchItems, setResearchItems] = useState<ResearchItem[]>([]);
|
|
217
197
|
const [researchLoading, setResearchLoading] = useState(false);
|
|
218
198
|
const [checkingUpdates, setCheckingUpdates] = useState(false);
|
|
219
|
-
const
|
|
199
|
+
const [aiSuggestions, setAiSuggestions] = useState<{skillName: string, description: string, problematic: boolean}[]>([]);
|
|
200
|
+
const [auditing, setAuditing] = useState(false);
|
|
201
|
+
|
|
202
|
+
const handleRunAudit = async () => {
|
|
203
|
+
if (auditing || skills.length === 0) return;
|
|
204
|
+
setAuditing(true);
|
|
205
|
+
setAiSuggestions([]);
|
|
206
|
+
try {
|
|
207
|
+
// Limit to max 5 to prevent long waits
|
|
208
|
+
const toAudit = skills.slice(0, 5);
|
|
209
|
+
const results = [];
|
|
210
|
+
for (const skill of toAudit) {
|
|
211
|
+
try {
|
|
212
|
+
const res = await auditSkill(skill.name, skill.repo || activeBrainId);
|
|
213
|
+
if (res.audit.outdated || res.audit.problematic) {
|
|
214
|
+
results.push({
|
|
215
|
+
skillName: skill.name,
|
|
216
|
+
description: res.audit.proposed_changes,
|
|
217
|
+
problematic: res.audit.problematic
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error('Audit error for', skill.name, e);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
setAiSuggestions(results);
|
|
225
|
+
if (results.length === 0) {
|
|
226
|
+
setSuccess('AI Audit complete: All checked skills are well-configured.');
|
|
227
|
+
setTimeout(() => setSuccess(null), 3000);
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
setAuditing(false);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
220
233
|
|
|
221
234
|
/**
|
|
222
235
|
* Syncs project skills by repo (excludes total-recall / tr-* system skills).
|
|
223
236
|
*/
|
|
224
|
-
const fetchSkillsList = async (selectName?: string, selectRepo?: string) => {
|
|
237
|
+
const fetchSkillsList = useCallback(async (selectName?: string, selectRepo?: string) => {
|
|
225
238
|
setLoading(true);
|
|
226
239
|
setError(null);
|
|
227
240
|
try {
|
|
@@ -249,6 +262,11 @@ export default function SkillsPage() {
|
|
|
249
262
|
groups = (data.repos || []) as RepoSkillsGroup[];
|
|
250
263
|
}
|
|
251
264
|
|
|
265
|
+
if (activeBrainId) {
|
|
266
|
+
groups = groups.filter(g => g.repo === 'Global' || g.repo === activeBrainId);
|
|
267
|
+
list = groups.flatMap(g => g.skills || []);
|
|
268
|
+
}
|
|
269
|
+
|
|
252
270
|
setSkills(list);
|
|
253
271
|
setRepoGroups(groups);
|
|
254
272
|
|
|
@@ -278,12 +296,12 @@ export default function SkillsPage() {
|
|
|
278
296
|
} finally {
|
|
279
297
|
setLoading(false);
|
|
280
298
|
}
|
|
281
|
-
};
|
|
299
|
+
}, [activeBrainId]);
|
|
282
300
|
|
|
283
301
|
useEffect(() => {
|
|
284
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
302
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
285
303
|
void fetchSkillsList();
|
|
286
|
-
}, []);
|
|
304
|
+
}, [fetchSkillsList]);
|
|
287
305
|
|
|
288
306
|
|
|
289
307
|
|
|
@@ -466,8 +484,9 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
466
484
|
try {
|
|
467
485
|
const files = await fetchSkillFiles(selectedSkill.name, dir, selectedSkill.repo);
|
|
468
486
|
setSubDirFiles(prev => ({ ...prev, [dir]: files }));
|
|
469
|
-
} catch (err) {
|
|
487
|
+
} catch (err: unknown) {
|
|
470
488
|
console.error('Failed to fetch skill files', err);
|
|
489
|
+
setError((err as Error).message || 'Failed to fetch skill files.');
|
|
471
490
|
setSubDirFiles(prev => ({ ...prev, [dir]: [] }));
|
|
472
491
|
}
|
|
473
492
|
}
|
|
@@ -497,8 +516,8 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
497
516
|
try {
|
|
498
517
|
const res = await listResearch();
|
|
499
518
|
setResearchItems(res.items || []);
|
|
500
|
-
} catch {
|
|
501
|
-
|
|
519
|
+
} catch (err: unknown) {
|
|
520
|
+
setError((err as Error).message || 'Failed to fetch research items.');
|
|
502
521
|
} finally {
|
|
503
522
|
setResearchLoading(false);
|
|
504
523
|
}
|
|
@@ -506,6 +525,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
506
525
|
|
|
507
526
|
useEffect(() => {
|
|
508
527
|
if (activeTab === 'automation') {
|
|
528
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
509
529
|
void handleFetchResearch();
|
|
510
530
|
}
|
|
511
531
|
}, [activeTab, handleFetchResearch]);
|
|
@@ -632,7 +652,26 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
632
652
|
>
|
|
633
653
|
{skill.name}
|
|
634
654
|
</span>
|
|
635
|
-
<
|
|
655
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
656
|
+
{activeBrainId && activeBrainId !== 'Global' && group.repo === 'Global' && (
|
|
657
|
+
<input
|
|
658
|
+
type="checkbox"
|
|
659
|
+
checked={!!repoGroups.find(g => g.repo === activeBrainId)?.skills?.some(s => s.name === skill.name)}
|
|
660
|
+
onClick={(e) => e.stopPropagation()}
|
|
661
|
+
onChange={async (e) => {
|
|
662
|
+
e.stopPropagation();
|
|
663
|
+
try {
|
|
664
|
+
await toggleSkillRepo(skill.name, activeBrainId, e.target.checked);
|
|
665
|
+
void fetchSkillsList();
|
|
666
|
+
} catch (err) {
|
|
667
|
+
setError((err as Error).message);
|
|
668
|
+
}
|
|
669
|
+
}}
|
|
670
|
+
style={{ cursor: 'pointer' }}
|
|
671
|
+
title="Enable/Disable skill for this repo"
|
|
672
|
+
/>
|
|
673
|
+
)}
|
|
674
|
+
<button
|
|
636
675
|
type="button"
|
|
637
676
|
onClick={(e) => {
|
|
638
677
|
e.stopPropagation();
|
|
@@ -650,6 +689,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
650
689
|
>
|
|
651
690
|
✖
|
|
652
691
|
</button>
|
|
692
|
+
</div>
|
|
653
693
|
</div>
|
|
654
694
|
{skill.source && (
|
|
655
695
|
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
@@ -1353,13 +1393,32 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
1353
1393
|
|
|
1354
1394
|
{/* Skill Improvement Queue */}
|
|
1355
1395
|
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1356
|
-
<
|
|
1357
|
-
|
|
1358
|
-
|
|
1396
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
1397
|
+
<h3 id="automation-improvements-heading" style={{ margin: 0, fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
1398
|
+
💡 AI Skill Improvement Queue
|
|
1399
|
+
</h3>
|
|
1400
|
+
<button
|
|
1401
|
+
onClick={handleRunAudit}
|
|
1402
|
+
disabled={auditing}
|
|
1403
|
+
style={{
|
|
1404
|
+
background: auditing ? 'var(--bg-secondary)' : 'linear-gradient(135deg, #10b981, #059669)',
|
|
1405
|
+
color: '#fff',
|
|
1406
|
+
border: 'none',
|
|
1407
|
+
borderRadius: 6,
|
|
1408
|
+
padding: '6px 14px',
|
|
1409
|
+
fontSize: 11,
|
|
1410
|
+
fontWeight: 600,
|
|
1411
|
+
cursor: auditing ? 'not-allowed' : 'pointer',
|
|
1412
|
+
flexShrink: 0,
|
|
1413
|
+
}}
|
|
1414
|
+
>
|
|
1415
|
+
{auditing ? '⏳ Auditing Skills...' : '🤖 Run AI Audit'}
|
|
1416
|
+
</button>
|
|
1417
|
+
</div>
|
|
1359
1418
|
|
|
1360
|
-
{
|
|
1419
|
+
{aiSuggestions.length > 0 ? (
|
|
1361
1420
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1362
|
-
{
|
|
1421
|
+
{aiSuggestions.map((suggestion, idx) => (
|
|
1363
1422
|
<div
|
|
1364
1423
|
key={idx}
|
|
1365
1424
|
id={`automation-suggestion-${idx}`}
|
|
@@ -1384,14 +1443,11 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
1384
1443
|
borderRadius: 10,
|
|
1385
1444
|
textTransform: 'uppercase',
|
|
1386
1445
|
letterSpacing: 0.3,
|
|
1387
|
-
background: suggestion.
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
suggestion.type === 'missing_reference' ? '#60a5fa' : '#8b5cf6',
|
|
1391
|
-
border: `1px solid ${suggestion.type === 'eval_gap' ? 'rgba(245, 158, 11, 0.2)' :
|
|
1392
|
-
suggestion.type === 'missing_reference' ? 'rgba(59, 130, 246, 0.2)' : 'rgba(139, 92, 246, 0.2)'}`,
|
|
1446
|
+
background: suggestion.problematic ? 'rgba(239, 68, 68, 0.1)' : 'rgba(245, 158, 11, 0.1)',
|
|
1447
|
+
color: suggestion.problematic ? '#f87171' : '#f59e0b',
|
|
1448
|
+
border: `1px solid ${suggestion.problematic ? 'rgba(239, 68, 68, 0.2)' : 'rgba(245, 158, 11, 0.2)'}`,
|
|
1393
1449
|
}}>
|
|
1394
|
-
{suggestion.
|
|
1450
|
+
{suggestion.problematic ? 'Problematic' : 'Outdated'}
|
|
1395
1451
|
</span>
|
|
1396
1452
|
</div>
|
|
1397
1453
|
<div style={{ fontSize: 11, color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
@@ -14,7 +14,7 @@ import ResearchAgendaTab from '../components/ResearchAgendaTab'
|
|
|
14
14
|
import type { ResearchItem } from '../components/ResearchAgendaTab'
|
|
15
15
|
import DaemonLogsTab from '../components/DaemonLogsTab'
|
|
16
16
|
|
|
17
|
-
export default function TasksPage() {
|
|
17
|
+
export default function TasksPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
18
18
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
19
19
|
const [researchItems, setResearchItems] = useState<ResearchItem[]>([])
|
|
20
20
|
const [researchCounts, setResearchCounts] = useState({ pending: 0, in_progress: 0, done: 0, failed: 0 })
|
|
@@ -46,6 +46,7 @@ export default function TasksPage() {
|
|
|
46
46
|
setLoadedDiscoveries(prev => ({ ...prev, [item.node_slug!]: node }))
|
|
47
47
|
} catch (e) {
|
|
48
48
|
console.error('Failed to load memory node for research', e)
|
|
49
|
+
setError(`Failed to load memory node: ${(e as Error).message}`)
|
|
49
50
|
} finally {
|
|
50
51
|
setLoadingNodeSlugs(prev => ({ ...prev, [item.node_slug!]: false }))
|
|
51
52
|
}
|
|
@@ -63,6 +64,7 @@ export default function TasksPage() {
|
|
|
63
64
|
const [showResearchForm, setShowResearchForm] = useState(false)
|
|
64
65
|
const [researchTopic, setResearchTopic] = useState('')
|
|
65
66
|
const [researchPriority, setResearchPriority] = useState<'low' | 'medium' | 'high' | 'critical'>('medium')
|
|
67
|
+
|
|
66
68
|
const [researchNotes, setResearchNotes] = useState('')
|
|
67
69
|
const [researchSubmitting, setResearchSubmitting] = useState(false)
|
|
68
70
|
|
|
@@ -73,68 +75,101 @@ export default function TasksPage() {
|
|
|
73
75
|
setTasks(data || [])
|
|
74
76
|
setError('')
|
|
75
77
|
} catch (e) {
|
|
76
|
-
|
|
78
|
+
console.error(e)
|
|
79
|
+
setError(`Failed to fetch tasks: ${(e as Error).message}`)
|
|
77
80
|
}
|
|
78
81
|
}, [])
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
const fetchResearch = useCallback(async () => {
|
|
83
|
+
const fetchResearchItems = useCallback(async () => {
|
|
82
84
|
try {
|
|
83
|
-
const res = await listResearch(
|
|
85
|
+
const res = await listResearch()
|
|
84
86
|
setResearchItems(res.items || [])
|
|
85
|
-
|
|
87
|
+
const counts = { pending: 0, in_progress: 0, done: 0, failed: 0 }
|
|
88
|
+
for (const item of (res.items || [])) {
|
|
89
|
+
if (counts[item.status as keyof typeof counts] !== undefined) {
|
|
90
|
+
counts[item.status as keyof typeof counts]++
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
setResearchCounts(counts)
|
|
86
94
|
setError('')
|
|
87
95
|
} catch (e) {
|
|
88
|
-
|
|
96
|
+
console.error(e)
|
|
97
|
+
setError(`Failed to fetch research: ${(e as Error).message}`)
|
|
89
98
|
}
|
|
90
99
|
}, [])
|
|
91
100
|
|
|
92
101
|
// Fetch Daemon (Cognitive Engine) Logs
|
|
93
102
|
const fetchDaemonLogs = useCallback(async () => {
|
|
103
|
+
if (activeTab !== 'logs') return
|
|
94
104
|
try {
|
|
95
|
-
const data = await fetchLogs(
|
|
105
|
+
const data = await fetchLogs("server")
|
|
96
106
|
setLogs(data.content || '(no logs streamed yet)')
|
|
107
|
+
return true
|
|
97
108
|
} catch (e) {
|
|
98
109
|
setLogs(`Error connecting to cognitive logs: ${(e as Error).message}`)
|
|
110
|
+
return false
|
|
99
111
|
}
|
|
100
|
-
}, [])
|
|
112
|
+
}, [activeTab])
|
|
101
113
|
|
|
102
114
|
// Combined Refresh
|
|
103
115
|
const refreshAll = useCallback(async () => {
|
|
104
116
|
setLoading(true)
|
|
105
|
-
await Promise.all([fetchTasks(),
|
|
117
|
+
await Promise.all([fetchTasks(), fetchResearchItems(), fetchDaemonLogs()])
|
|
106
118
|
setLoading(false)
|
|
107
|
-
}, [fetchTasks,
|
|
119
|
+
}, [fetchTasks, fetchResearchItems, fetchDaemonLogs])
|
|
108
120
|
|
|
109
121
|
// Polling loop
|
|
110
122
|
useEffect(() => {
|
|
123
|
+
let timeoutId: ReturnType<typeof setTimeout>
|
|
124
|
+
let active = true
|
|
125
|
+
let failCount = 0
|
|
126
|
+
|
|
127
|
+
const poll = async () => {
|
|
128
|
+
if (!active) return
|
|
129
|
+
|
|
130
|
+
const results = await Promise.all([fetchTasks(), fetchResearchItems(), fetchDaemonLogs()])
|
|
131
|
+
const hasError = results.includes(false)
|
|
132
|
+
|
|
133
|
+
if (hasError) {
|
|
134
|
+
failCount++
|
|
135
|
+
} else {
|
|
136
|
+
failCount = 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (active) {
|
|
140
|
+
const delay = Math.min(3000 * Math.pow(2, failCount), 60000)
|
|
141
|
+
timeoutId = setTimeout(poll, delay)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
111
145
|
const init = async () => {
|
|
112
|
-
await Promise.all([fetchTasks(),
|
|
146
|
+
await Promise.all([fetchTasks(), fetchResearchItems(), fetchDaemonLogs()])
|
|
113
147
|
setLoading(false)
|
|
148
|
+
if (active) {
|
|
149
|
+
timeoutId = setTimeout(poll, 3000)
|
|
150
|
+
}
|
|
114
151
|
}
|
|
115
152
|
void init()
|
|
116
153
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return () => clearInterval(interval)
|
|
124
|
-
}, [fetchTasks, fetchResearch, fetchDaemonLogs])
|
|
154
|
+
return () => {
|
|
155
|
+
active = false
|
|
156
|
+
clearTimeout(timeoutId)
|
|
157
|
+
}
|
|
158
|
+
}, [fetchTasks, fetchResearchItems, fetchDaemonLogs])
|
|
125
159
|
|
|
126
160
|
// Trigger Brain Recompilation
|
|
127
161
|
const handleRecompile = async () => {
|
|
162
|
+
if (recompiling) return
|
|
128
163
|
setRecompiling(true)
|
|
129
164
|
setError('')
|
|
130
165
|
setActionSuccess('')
|
|
131
166
|
try {
|
|
132
|
-
|
|
133
|
-
setActionSuccess(
|
|
167
|
+
await triggerRecompile(activeBrainId)
|
|
168
|
+
setActionSuccess('Surface projections recompiled successfully')
|
|
134
169
|
setTimeout(() => setActionSuccess(''), 4000)
|
|
135
170
|
void refreshAll()
|
|
136
171
|
} catch (e) {
|
|
137
|
-
setError((e as Error).message)
|
|
172
|
+
setError(`Recompile failed: ${(e as Error).message}`)
|
|
138
173
|
} finally {
|
|
139
174
|
setRecompiling(false)
|
|
140
175
|
}
|
|
@@ -219,7 +254,7 @@ export default function TasksPage() {
|
|
|
219
254
|
setShowResearchForm(false)
|
|
220
255
|
setActionSuccess('Research project enqueued successfully!')
|
|
221
256
|
setTimeout(() => setActionSuccess(''), 3000)
|
|
222
|
-
void
|
|
257
|
+
void fetchResearchItems()
|
|
223
258
|
} catch (err) {
|
|
224
259
|
setError((err as Error).message)
|
|
225
260
|
} finally {
|
|
@@ -380,7 +415,7 @@ export default function TasksPage() {
|
|
|
380
415
|
handleToggleExpand={handleToggleExpand}
|
|
381
416
|
loadedDiscoveries={loadedDiscoveries}
|
|
382
417
|
loadingNodeSlugs={loadingNodeSlugs}
|
|
383
|
-
refreshResearch={
|
|
418
|
+
refreshResearch={fetchResearchItems}
|
|
384
419
|
/>
|
|
385
420
|
)}
|
|
386
421
|
|