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
|
@@ -62,7 +62,7 @@ const PRESETS: Preset[] = [
|
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
|
|
65
|
-
export default function IntegrationsPage() {
|
|
65
|
+
export default function IntegrationsPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
66
66
|
const [copied, setCopied] = useState<string | null>(null)
|
|
67
67
|
const [connecting, setConnecting] = useState<string | null>(null)
|
|
68
68
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
|
@@ -82,7 +82,9 @@ export default function IntegrationsPage() {
|
|
|
82
82
|
.then(res => {
|
|
83
83
|
if (active) setKeys(res.keys || [])
|
|
84
84
|
})
|
|
85
|
-
.catch(
|
|
85
|
+
.catch(e => {
|
|
86
|
+
if (active) setMessage({ type: 'error', text: `Failed to list API keys: ${(e as Error).message || 'Unknown error'}` })
|
|
87
|
+
})
|
|
86
88
|
|
|
87
89
|
fetchActiveIntegrations()
|
|
88
90
|
.then(res => {
|
|
@@ -90,16 +92,20 @@ export default function IntegrationsPage() {
|
|
|
90
92
|
setActiveIdes(res.active)
|
|
91
93
|
}
|
|
92
94
|
})
|
|
93
|
-
.catch(
|
|
95
|
+
.catch(e => {
|
|
96
|
+
if (active) setMessage({ type: 'error', text: `Failed to fetch active integrations: ${(e as Error).message || 'Unknown error'}` })
|
|
97
|
+
})
|
|
94
98
|
|
|
95
99
|
fetchExtensionStatus()
|
|
96
100
|
.then(res => {
|
|
97
101
|
if (active) setExtStatus(res)
|
|
98
102
|
})
|
|
99
|
-
.catch(
|
|
103
|
+
.catch(e => {
|
|
104
|
+
if (active) setMessage({ type: 'error', text: `Failed to fetch extension status: ${(e as Error).message || 'Unknown error'}` })
|
|
105
|
+
})
|
|
100
106
|
|
|
101
107
|
return () => { active = false }
|
|
102
|
-
}, [])
|
|
108
|
+
}, [activeBrainId])
|
|
103
109
|
|
|
104
110
|
const filteredPresets = useMemo(() => {
|
|
105
111
|
if (activeIdes.length === 0) return PRESETS
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback
|
|
1
|
+
import { useState, useEffect, useCallback } from "react"
|
|
2
2
|
import { useSearchParams } from "react-router-dom"
|
|
3
3
|
import { listMemory, searchMemory, readMemory, fetchConflicts, resolveConflict, saveMemory, createMemory, deleteMemory, fetchSessions, deleteSession } from "../api"
|
|
4
4
|
import type { MemoryNode, Conflict } from "../types"
|
|
@@ -41,70 +41,6 @@ function renderMarkdown(md: string) {
|
|
|
41
41
|
return <div dangerouslySetInnerHTML={{ __html: html }} className="markdown-preview-block" />;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function markdownToHtml(md: string): string {
|
|
45
|
-
if (!md) return "";
|
|
46
|
-
let html = md
|
|
47
|
-
.replace(/&/g, "&")
|
|
48
|
-
.replace(/</g, "<")
|
|
49
|
-
.replace(/>/g, ">");
|
|
50
|
-
|
|
51
|
-
// Headings
|
|
52
|
-
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
53
|
-
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
54
|
-
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
55
|
-
|
|
56
|
-
// Bold & Italic
|
|
57
|
-
html = html.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
|
58
|
-
html = html.replace(/\*(.*?)\*/g, "<em>$1</em>");
|
|
59
|
-
|
|
60
|
-
// Blockquotes
|
|
61
|
-
html = html.replace(/^> (.*$)/gim, "<blockquote>$1</blockquote>");
|
|
62
|
-
|
|
63
|
-
// Lists
|
|
64
|
-
html = html.replace(/^- (.*$)/gim, "<li>$1</li>");
|
|
65
|
-
|
|
66
|
-
// Newlines
|
|
67
|
-
html = html.replace(/\n/g, "<br />");
|
|
68
|
-
|
|
69
|
-
return html;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function htmlToMarkdown(html: string): string {
|
|
73
|
-
if (!html) return "";
|
|
74
|
-
let md = html;
|
|
75
|
-
|
|
76
|
-
// Replace <br> and <br /> with newline
|
|
77
|
-
md = md.replace(/<br\s*\/?>/gi, "\n");
|
|
78
|
-
|
|
79
|
-
// Headings
|
|
80
|
-
md = md.replace(/<h1>(.*?)<\/h1>/gi, "# $1\n");
|
|
81
|
-
md = md.replace(/<h2>(.*?)<\/h2>/gi, "## $1\n");
|
|
82
|
-
md = md.replace(/<h3>(.*?)<\/h3>/gi, "### $1\n");
|
|
83
|
-
|
|
84
|
-
// Bold & Italic
|
|
85
|
-
md = md.replace(/<strong>(.*?)<\/strong>/gi, "**$1**");
|
|
86
|
-
md = md.replace(/<b>(.*?)<\/b>/gi, "**$1**");
|
|
87
|
-
md = md.replace(/<em>(.*?)<\/em>/gi, "*$1*");
|
|
88
|
-
md = md.replace(/<i>(.*?)<\/i>/gi, "*$1*");
|
|
89
|
-
|
|
90
|
-
// Blockquotes
|
|
91
|
-
md = md.replace(/<blockquote>(.*?)<\/blockquote>/gi, "> $1\n");
|
|
92
|
-
|
|
93
|
-
// Lists
|
|
94
|
-
md = md.replace(/<li>(.*?)<\/li>/gi, "- $1\n");
|
|
95
|
-
|
|
96
|
-
// Clean up any remaining HTML tags safely
|
|
97
|
-
md = md.replace(/<[^>]+>/g, "");
|
|
98
|
-
|
|
99
|
-
// Decode HTML entities
|
|
100
|
-
md = md
|
|
101
|
-
.replace(/&/g, "&")
|
|
102
|
-
.replace(/</g, "<")
|
|
103
|
-
.replace(/>/g, ">");
|
|
104
|
-
|
|
105
|
-
return md.trim();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
44
|
export default function MemoryPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
109
45
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
110
46
|
const urlSlug = searchParams.get("slug")
|
|
@@ -143,23 +79,6 @@ export default function MemoryPage({ activeBrainId }: { activeBrainId?: string }
|
|
|
143
79
|
const [editContradicts, setEditContradicts] = useState("")
|
|
144
80
|
const [editSupersedes, setEditSupersedes] = useState("")
|
|
145
81
|
|
|
146
|
-
const editorRef = useRef<HTMLDivElement>(null)
|
|
147
|
-
|
|
148
|
-
const execCommand = (command: string, value: string = "") => {
|
|
149
|
-
document.execCommand(command, false, value)
|
|
150
|
-
editorRef.current?.focus()
|
|
151
|
-
if (editorRef.current) {
|
|
152
|
-
setEditContent(htmlToMarkdown(editorRef.current.innerHTML))
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
useEffect(() => {
|
|
157
|
-
if ((isEditing || isCreating) && editorRef.current) {
|
|
158
|
-
editorRef.current.innerHTML = markdownToHtml(editContent)
|
|
159
|
-
}
|
|
160
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- only sync editor HTML on toggle edit/create
|
|
161
|
-
}, [isEditing, isCreating])
|
|
162
|
-
|
|
163
82
|
const fetchNodes = useCallback(async () => {
|
|
164
83
|
setLoading(true)
|
|
165
84
|
setError("")
|
|
@@ -584,42 +503,28 @@ export default function MemoryPage({ activeBrainId }: { activeBrainId?: string }
|
|
|
584
503
|
</div>
|
|
585
504
|
</div>
|
|
586
505
|
|
|
587
|
-
{/*
|
|
506
|
+
{/* Markdown Content Editor */}
|
|
588
507
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
589
|
-
<label style={{ fontSize: 12, color: "var(--text-secondary)" }}>
|
|
590
|
-
<
|
|
591
|
-
{
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
}}
|
|
611
|
-
style={{
|
|
612
|
-
minHeight: 300,
|
|
613
|
-
padding: 16,
|
|
614
|
-
color: "#fff",
|
|
615
|
-
background: "#090d16",
|
|
616
|
-
outline: "none",
|
|
617
|
-
fontSize: 14,
|
|
618
|
-
lineHeight: 1.6,
|
|
619
|
-
overflowY: "auto"
|
|
620
|
-
}}
|
|
621
|
-
/>
|
|
622
|
-
</div>
|
|
508
|
+
<label style={{ fontSize: 12, color: "var(--text-secondary)" }}>Memory Content</label>
|
|
509
|
+
<textarea
|
|
510
|
+
value={editContent}
|
|
511
|
+
onChange={(e) => setEditContent(e.target.value)}
|
|
512
|
+
placeholder="Write markdown here..."
|
|
513
|
+
style={{
|
|
514
|
+
minHeight: 300,
|
|
515
|
+
padding: 16,
|
|
516
|
+
color: "#fff",
|
|
517
|
+
background: "#090d16",
|
|
518
|
+
border: "1px solid var(--border)",
|
|
519
|
+
borderRadius: 8,
|
|
520
|
+
outline: "none",
|
|
521
|
+
fontSize: 14,
|
|
522
|
+
lineHeight: 1.6,
|
|
523
|
+
resize: "vertical",
|
|
524
|
+
width: "100%",
|
|
525
|
+
boxSizing: "border-box"
|
|
526
|
+
}}
|
|
527
|
+
/>
|
|
623
528
|
</div>
|
|
624
529
|
|
|
625
530
|
{/* Action Buttons */}
|
|
@@ -934,7 +839,11 @@ export default function MemoryPage({ activeBrainId }: { activeBrainId?: string }
|
|
|
934
839
|
<div className="memory-list" style={{ display: "grid", gridTemplateColumns: "1fr", gap: 12 }}>
|
|
935
840
|
{nodes.length === 0 && !loading && (
|
|
936
841
|
<div style={{ textAlign: "center", padding: 40, color: "var(--text-tertiary)" }}>
|
|
937
|
-
{query
|
|
842
|
+
{query
|
|
843
|
+
? "No matching nodes found."
|
|
844
|
+
: category !== "all"
|
|
845
|
+
? `No memory nodes found in category "${category}".`
|
|
846
|
+
: "No memory nodes loaded. Is the backend running?"}
|
|
938
847
|
</div>
|
|
939
848
|
)}
|
|
940
849
|
{nodes.map(n => {
|
|
@@ -126,7 +126,8 @@ export default function ModelsPage() {
|
|
|
126
126
|
{ id: 'antigravity', name: 'Antigravity (Gemini SDK)', desc: 'Primary core developer agent' },
|
|
127
127
|
{ id: 'gemini', name: 'Gemini CLI', desc: 'Direct Gemini assistant binary' },
|
|
128
128
|
{ id: 'claude', name: 'Claude Code', desc: 'Anthropic developer CLI wrapper' },
|
|
129
|
-
{ id: 'codex', name: 'Codex CLI', desc: 'OpenAI agent binary integration' }
|
|
129
|
+
{ id: 'codex', name: 'Codex CLI', desc: 'OpenAI agent binary integration' },
|
|
130
|
+
{ id: 'grok', name: 'Grok CLI', desc: 'xAI developer binary integration' }
|
|
130
131
|
];
|
|
131
132
|
|
|
132
133
|
|
|
@@ -177,55 +178,6 @@ export default function ModelsPage() {
|
|
|
177
178
|
|
|
178
179
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
|
|
179
180
|
|
|
180
|
-
{/* Ollama Panel */}
|
|
181
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
182
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
|
|
183
|
-
<span style={{ fontSize: 20 }}>🦙</span>
|
|
184
|
-
<div>
|
|
185
|
-
<h3 style={{ fontSize: 14, fontWeight: 600 }}>Local Provider</h3>
|
|
186
|
-
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Connect Ollama, LM Studio, vLLM, or Llama.cpp</p>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
191
|
-
<label htmlFor="local_endpoint" style={{ fontSize: 13, fontWeight: 500 }}>Provider Base URL</label>
|
|
192
|
-
<input
|
|
193
|
-
id="local_endpoint"
|
|
194
|
-
type="text"
|
|
195
|
-
placeholder="http://127.0.0.1:11434 or http://127.0.0.1:1234/v1"
|
|
196
|
-
value={configData?.brain?.local_endpoint || ''}
|
|
197
|
-
onChange={(e) => updateBrainProp('local_endpoint', e.target.value)}
|
|
198
|
-
style={{
|
|
199
|
-
background: 'var(--bg-tertiary)',
|
|
200
|
-
color: 'var(--text-primary)',
|
|
201
|
-
border: '1px solid var(--border)',
|
|
202
|
-
padding: '8px 12px',
|
|
203
|
-
borderRadius: 6,
|
|
204
|
-
outline: 'none',
|
|
205
|
-
fontSize: 13
|
|
206
|
-
}}
|
|
207
|
-
/>
|
|
208
|
-
<span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Leave blank to use the default localhost endpoint.</span>
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 8 }}>
|
|
212
|
-
<span className="badge" style={{
|
|
213
|
-
background: health?.ollama === 'running' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
|
214
|
-
color: health?.ollama === 'running' ? '#10b981' : '#ef4444',
|
|
215
|
-
border: health?.ollama === 'running' ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)',
|
|
216
|
-
fontSize: 10,
|
|
217
|
-
padding: '4px 8px'
|
|
218
|
-
}}>
|
|
219
|
-
{health?.ollama === 'running' ? 'Connected' : 'Offline'}
|
|
220
|
-
</span>
|
|
221
|
-
{health?.ollama === 'running' && health?.ollama_models && (
|
|
222
|
-
<span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>
|
|
223
|
-
{health.ollama_models.length} local models available
|
|
224
|
-
</span>
|
|
225
|
-
)}
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
181
|
{/* Cloud API Keys Panel */}
|
|
230
182
|
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
231
183
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
|
|
@@ -67,14 +67,7 @@ function saveProgress(p: { step: number; fact?: string; complete?: boolean }) {
|
|
|
67
67
|
if (p.complete) localStorage.setItem('tr-onboarding-complete', '1')
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
return localStorage.getItem('tr-onboarding-complete') === '1'
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function resetOnboarding() {
|
|
75
|
-
localStorage.removeItem(STORAGE_KEY)
|
|
76
|
-
localStorage.removeItem('tr-onboarding-complete')
|
|
77
|
-
}
|
|
70
|
+
import { isOnboardingComplete } from '../utils/onboarding'
|
|
78
71
|
|
|
79
72
|
/** Client-side .env parse so we can re-send pairs on import without server holding paste. */
|
|
80
73
|
function parseEnvClient(text: string): Record<string, string> {
|
|
@@ -140,8 +133,10 @@ export default function OnboardingPage() {
|
|
|
140
133
|
useEffect(() => {
|
|
141
134
|
if (step.id !== 'import-env' || importMode !== 'scan') return
|
|
142
135
|
let cancelled = false
|
|
143
|
-
|
|
144
|
-
|
|
136
|
+
Promise.resolve().then(() => {
|
|
137
|
+
setScanLoading(true)
|
|
138
|
+
setScanError('')
|
|
139
|
+
})
|
|
145
140
|
scanEnvSecrets()
|
|
146
141
|
.then((data) => {
|
|
147
142
|
if (cancelled) return
|
|
@@ -94,6 +94,7 @@ export default function OpenWikiPage({ activeBrainId = 'global' }: { activeBrain
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
useEffect(() => {
|
|
97
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
97
98
|
void fetchNodes(activeBrainId || 'global');
|
|
98
99
|
void fetchHealthData();
|
|
99
100
|
const interval = setInterval(fetchHealthData, 15000);
|