total-recall-brain 3.26.0 → 3.28.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/bin/total-recall.mjs +31 -1
- package/docs/ARCHITECTURE.md +76 -9
- package/frontend/dist/assets/index-U7o-qU99.js +5742 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +39 -1
- package/frontend/src/api/index.ts +1 -0
- package/frontend/src/api/plugins.ts +167 -0
- package/frontend/src/pages/ChatPage.tsx +44 -17
- package/frontend/src/pages/IntegrationsPage.tsx +7 -0
- package/frontend/src/pages/PluginsPage.spec.tsx +17 -0
- package/frontend/src/pages/PluginsPage.tsx +1331 -0
- package/package.json +1 -1
- package/scaffold/.agent/plugins/.gitkeep +0 -0
- package/scaffold/.agent/plugins/code-quality/README.md +16 -0
- package/scaffold/.agent/plugins/code-quality/cli.mjs +15 -0
- package/scaffold/.agent/plugins/code-quality/plugin.json +34 -0
- package/scaffold/.agent/plugins/git-sentinel/README.md +16 -0
- package/scaffold/.agent/plugins/git-sentinel/cli.mjs +86 -0
- package/scaffold/.agent/plugins/git-sentinel/plugin.json +34 -0
- package/scaffold/.agent/plugins/meta-harness/README.md +16 -0
- package/scaffold/.agent/plugins/meta-harness/cli.mjs +34 -0
- package/scaffold/.agent/plugins/meta-harness/plugin.json +34 -0
- package/scaffold/.agent/plugins/system-monitor/README.md +16 -0
- package/scaffold/.agent/plugins/system-monitor/cli.mjs +79 -0
- package/scaffold/.agent/plugins/system-monitor/generator.mjs +20 -0
- package/scaffold/.agent/plugins/system-monitor/plugin.json +38 -0
- package/scaffold/.agent/skills/meta-harness/SKILL.md +113 -0
- package/scaffold/.agent/skills/total-recall/references/cli-reference.md +23 -0
- package/src/cli/agent.mjs +173 -0
- package/src/cli/connect.mjs +89 -4
- package/src/cli/deploy-ui.mjs +6 -0
- package/src/cli/harness.mjs +135 -0
- package/src/cli/ingest-openwiki.mjs +71 -2
- package/src/cli/init.mjs +1 -0
- package/src/cli/lint.mjs +47 -3
- package/src/cli/mesh.mjs +286 -0
- package/src/cli/plugin/create.mjs +186 -0
- package/src/cli/plugin/index.mjs +139 -0
- package/src/cli/plugin/install.mjs +141 -0
- package/src/cli/plugin/list.mjs +83 -0
- package/src/cli/plugin/remove.mjs +51 -0
- package/src/cli/plugin/search.mjs +37 -0
- package/src/cli/skill-projection.mjs +14 -0
- package/src/cli/wizard.html +9 -0
- package/src/core/agent-manager.mjs +290 -0
- package/src/core/cache-prune.mjs +341 -0
- package/src/core/context-cache.mjs +130 -0
- package/src/core/daemon-loop.mjs +47 -1
- package/src/core/evolving-context.mjs +171 -0
- package/src/core/mesh-activity.mjs +140 -0
- package/src/core/mesh.mjs +174 -1
- package/src/core/meta-harness.mjs +251 -0
- package/src/core/plugin-context.mjs +206 -0
- package/src/core/plugin-loader.mjs +210 -0
- package/src/core/runtime.mjs +65 -13
- package/src/core/surface.mjs +32 -10
- package/src/server/api.mjs +18 -2
- package/src/server/index.mjs +8 -2
- package/src/server/rest.mjs +4 -0
- package/src/server/routes/integrations.mjs +5 -1
- package/src/server/routes/mesh-presence.mjs +143 -0
- package/src/server/routes/models.mjs +42 -30
- package/src/server/routes/plugins.mjs +517 -0
- package/src/server/tools.mjs +266 -0
- package/frontend/dist/assets/index-Bl3I11B0.js +0 -5742
package/frontend/dist/index.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
18
18
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
19
19
|
<title>Total Recall — Portable Memory</title>
|
|
20
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-U7o-qU99.js"></script>
|
|
21
21
|
<link rel="stylesheet" crossorigin href="/assets/index-C7_Xpk27.css">
|
|
22
22
|
</head>
|
|
23
23
|
<body>
|
package/frontend/src/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { BrowserRouter, Routes, Route, NavLink, Navigate, useLocation } from 'react-router-dom'
|
|
3
3
|
import './App.css'
|
|
4
|
-
import { getApiBase, setApiBase, checkSession, logout, registerUnauthedCallback, fetchHealth } from './api'
|
|
4
|
+
import { getApiBase, setApiBase, checkSession, logout, registerUnauthedCallback, fetchHealth, fetchPlugins, type PluginInfo } from './api'
|
|
5
5
|
import type { HealthData } from './types'
|
|
6
6
|
import LoginPage from './pages/LoginPage'
|
|
7
7
|
import ChatPage from './pages/ChatPage'
|
|
@@ -19,6 +19,7 @@ import RulesPage from './pages/RulesPage'
|
|
|
19
19
|
import HelpPage from './pages/HelpPage'
|
|
20
20
|
import GraphPage from './pages/GraphPage'
|
|
21
21
|
import OpenWikiPage from './pages/OpenWikiPage'
|
|
22
|
+
import PluginsPage from './pages/PluginsPage'
|
|
22
23
|
import OnboardingPage from './pages/OnboardingPage'
|
|
23
24
|
import { NotificationsPage } from './pages/NotificationsPage'
|
|
24
25
|
import { isOnboardingComplete } from './utils/onboarding'
|
|
@@ -86,6 +87,12 @@ interface SidebarProps {
|
|
|
86
87
|
const getNavLinkClass = ({ isActive }: { isActive: boolean }) => `nav-link ${isActive ? 'active' : ''}`;
|
|
87
88
|
|
|
88
89
|
function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProps) {
|
|
90
|
+
const [plugins, setPlugins] = useState<PluginInfo[]>([])
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
fetchPlugins().then(setPlugins).catch(() => {})
|
|
94
|
+
}, [])
|
|
95
|
+
|
|
89
96
|
return (
|
|
90
97
|
<aside className="sidebar">
|
|
91
98
|
<div className="sidebar-brand">
|
|
@@ -158,6 +165,36 @@ function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProp
|
|
|
158
165
|
</svg>
|
|
159
166
|
OpenWiki
|
|
160
167
|
</NavLink>
|
|
168
|
+
<NavLink to="/plugins" className={getNavLinkClass} id="nav-plugins">
|
|
169
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
170
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
171
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
172
|
+
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
173
|
+
</svg>
|
|
174
|
+
Plugins
|
|
175
|
+
</NavLink>
|
|
176
|
+
|
|
177
|
+
{plugins.length > 0 && (
|
|
178
|
+
<>
|
|
179
|
+
<div className="nav-section-label">Plugins</div>
|
|
180
|
+
{plugins.map((p) => (
|
|
181
|
+
<NavLink
|
|
182
|
+
key={p.id}
|
|
183
|
+
to={`/plugins?id=${encodeURIComponent(p.id)}`}
|
|
184
|
+
className={getNavLinkClass}
|
|
185
|
+
id={`nav-plugin-${p.id}`}
|
|
186
|
+
title={p.description}
|
|
187
|
+
>
|
|
188
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
189
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
190
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
191
|
+
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
192
|
+
</svg>
|
|
193
|
+
{p.name}
|
|
194
|
+
</NavLink>
|
|
195
|
+
))}
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
161
198
|
|
|
162
199
|
<div className="nav-section-label">Account</div>
|
|
163
200
|
<NavLink to="/network" className={getNavLinkClass} id="nav-network">
|
|
@@ -396,6 +433,7 @@ function MainContent({ activeBrainId, onBrainChange }: { activeBrainId: string;
|
|
|
396
433
|
<Route path="/tasks" element={<TasksPage />} />
|
|
397
434
|
<Route path="/skills" element={<SkillsPage activeBrainId={activeBrainId} />} />
|
|
398
435
|
<Route path="/openwiki" element={<OpenWikiPage activeBrainId={activeBrainId} />} />
|
|
436
|
+
<Route path="/plugins" element={<PluginsPage activeBrainId={activeBrainId} />} />
|
|
399
437
|
<Route path="/integrations" element={<IntegrationsPage activeBrainId={activeBrainId} />} />
|
|
400
438
|
<Route path="/keys" element={<Navigate to="/secrets" replace />} />
|
|
401
439
|
<Route path="/settings" element={<SettingsPage activeBrainId={activeBrainId} />} />
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// ─── Plugins domain ───────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import { apiFetch, API_BASE } from "./_base"
|
|
4
|
+
|
|
5
|
+
export interface PluginInfo {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
version: string
|
|
9
|
+
description: string
|
|
10
|
+
valid: boolean
|
|
11
|
+
errors: string[]
|
|
12
|
+
dir: string
|
|
13
|
+
rating: number
|
|
14
|
+
reviewCount: number
|
|
15
|
+
installCount: string
|
|
16
|
+
userRating?: number | null
|
|
17
|
+
userReview?: string | null
|
|
18
|
+
verified?: boolean
|
|
19
|
+
categories: Array<{ name: string; description?: string }>
|
|
20
|
+
tasks: Array<{ intent: string; schedule: string }>
|
|
21
|
+
openwiki_hubs: Array<{ title: string; path: string }>
|
|
22
|
+
tools: Array<{ name: string; description?: string }>
|
|
23
|
+
cli?: { command: string; handler: string } | null
|
|
24
|
+
manifest?: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CatalogPlugin {
|
|
28
|
+
id: string
|
|
29
|
+
name: string
|
|
30
|
+
version: string
|
|
31
|
+
description: string
|
|
32
|
+
tags: string[]
|
|
33
|
+
author: string
|
|
34
|
+
sourceUrl: string
|
|
35
|
+
rating: number
|
|
36
|
+
reviewCount: number
|
|
37
|
+
installCount: string
|
|
38
|
+
userRating?: number | null
|
|
39
|
+
verified?: boolean
|
|
40
|
+
isInstalled: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PluginsResponse {
|
|
44
|
+
success: boolean
|
|
45
|
+
count: number
|
|
46
|
+
plugins: PluginInfo[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CatalogResponse {
|
|
50
|
+
success: boolean
|
|
51
|
+
catalog: CatalogPlugin[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function fetchPlugins(): Promise<PluginInfo[]> {
|
|
55
|
+
try {
|
|
56
|
+
const res = await apiFetch(`${API_BASE}/api/plugins`)
|
|
57
|
+
if (!res.ok) return []
|
|
58
|
+
const data: PluginsResponse = await res.json()
|
|
59
|
+
return Array.isArray(data.plugins) ? data.plugins : []
|
|
60
|
+
} catch {
|
|
61
|
+
return []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function fetchPlugin(id: string): Promise<PluginInfo | null> {
|
|
66
|
+
try {
|
|
67
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(id)}`)
|
|
68
|
+
if (!res.ok) return null
|
|
69
|
+
const data = await res.json()
|
|
70
|
+
return data.plugin || null
|
|
71
|
+
} catch {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function fetchPluginCatalog(): Promise<CatalogPlugin[]> {
|
|
77
|
+
try {
|
|
78
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/catalog`)
|
|
79
|
+
if (!res.ok) return []
|
|
80
|
+
const data: CatalogResponse = await res.json()
|
|
81
|
+
return Array.isArray(data.catalog) ? data.catalog : []
|
|
82
|
+
} catch {
|
|
83
|
+
return []
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function installPlugin(options: { source: string; link?: boolean; global?: boolean }): Promise<{ success: boolean; message?: string; error?: string }> {
|
|
88
|
+
try {
|
|
89
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/install`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/json" },
|
|
92
|
+
body: JSON.stringify(options)
|
|
93
|
+
})
|
|
94
|
+
const data = await res.json()
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
return { success: false, error: data.error || data.message || "Failed to install plugin" }
|
|
97
|
+
}
|
|
98
|
+
return { success: true, message: data.message }
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
return { success: false, error: err.message || "Network error" }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function removePlugin(id: string, global = false): Promise<{ success: boolean; message?: string; error?: string }> {
|
|
105
|
+
try {
|
|
106
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(id)}?global=${global ? "true" : "false"}`, {
|
|
107
|
+
method: "DELETE"
|
|
108
|
+
})
|
|
109
|
+
const data = await res.json()
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
return { success: false, error: data.error || data.message || "Failed to remove plugin" }
|
|
112
|
+
}
|
|
113
|
+
return { success: true, message: data.message }
|
|
114
|
+
} catch (err: any) {
|
|
115
|
+
return { success: false, error: err.message || "Network error" }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function ratePlugin(id: string, rating: number, review?: string): Promise<{ success: boolean; message?: string; error?: string }> {
|
|
120
|
+
try {
|
|
121
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(id)}/rate`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({ rating, review })
|
|
125
|
+
})
|
|
126
|
+
const data = await res.json()
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
return { success: false, error: data.error || data.message || "Failed to submit rating" }
|
|
129
|
+
}
|
|
130
|
+
return { success: true, message: data.message }
|
|
131
|
+
} catch (err: any) {
|
|
132
|
+
return { success: false, error: err.message || "Network error" }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function runPluginCommand(
|
|
137
|
+
id: string,
|
|
138
|
+
subcommand = "",
|
|
139
|
+
args: string[] = []
|
|
140
|
+
): Promise<{ success: boolean; output?: string; error?: string }> {
|
|
141
|
+
try {
|
|
142
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(id)}/run`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: { "Content-Type": "application/json" },
|
|
145
|
+
body: JSON.stringify({ subcommand, args })
|
|
146
|
+
})
|
|
147
|
+
const data = await res.json()
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
return { success: false, error: data.error || data.message || "Failed to execute plugin command" }
|
|
150
|
+
}
|
|
151
|
+
return { success: true, output: data.output || "" }
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
return { success: false, error: err.message || "Network error" }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function fetchPluginReadme(id: string): Promise<string> {
|
|
158
|
+
try {
|
|
159
|
+
const res = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(id)}/readme`)
|
|
160
|
+
if (!res.ok) return ""
|
|
161
|
+
const data = await res.json()
|
|
162
|
+
return data.readme || ""
|
|
163
|
+
} catch {
|
|
164
|
+
return ""
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
@@ -62,14 +62,26 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
62
62
|
const [nodeSearchQuery, setNodeSearchQuery] = useState('')
|
|
63
63
|
const nodeSelectorRef = useRef<HTMLDivElement>(null)
|
|
64
64
|
|
|
65
|
+
const DEFAULT_GEMINI_MODELS = [
|
|
66
|
+
{ id: 'gemini-3.8-flash-high', displayName: 'Gemini 3.8 Flash (High)' },
|
|
67
|
+
{ id: 'gemini-3.8-flash-medium', displayName: 'Gemini 3.8 Flash (Medium)' },
|
|
68
|
+
{ id: 'gemini-3.8-flash-low', displayName: 'Gemini 3.8 Flash (Low)' },
|
|
69
|
+
{ id: 'gemini-3.7-flash-high', displayName: 'Gemini 3.7 Flash (High)' },
|
|
70
|
+
{ id: 'gemini-3.7-flash-medium', displayName: 'Gemini 3.7 Flash (Medium)' },
|
|
71
|
+
{ id: 'gemini-3.6-flash-high', displayName: 'Gemini 3.6 Flash (High)' },
|
|
72
|
+
{ id: 'gemini-3.1-pro-high', displayName: 'Gemini 3.1 Pro (High)' },
|
|
73
|
+
{ id: 'gemini-3.5-flash', displayName: 'Gemini 3.5 Flash' },
|
|
74
|
+
{ id: 'gemini-3.5-pro', displayName: 'Gemini 3.5 Pro' },
|
|
75
|
+
]
|
|
76
|
+
|
|
65
77
|
// Model Selector state
|
|
66
78
|
const [availableModels, setAvailableModels] = useState<string[]>([])
|
|
67
79
|
const [selectedModel, setSelectedModel] = useState<string>(() => {
|
|
68
80
|
const saved = localStorage.getItem('selectedModel');
|
|
69
81
|
return (saved && saved !== 'gemini') ? saved : 'antigravity';
|
|
70
82
|
})
|
|
71
|
-
const [selectedSubModel, setSelectedSubModel] = useState<string>(() => localStorage.getItem('selectedSubModel') || '')
|
|
72
|
-
const [geminiModels, setGeminiModels] = useState<{ id: string; displayName: string }[]>(
|
|
83
|
+
const [selectedSubModel, setSelectedSubModel] = useState<string>(() => localStorage.getItem('selectedSubModel') || 'gemini-3.8-flash-high')
|
|
84
|
+
const [geminiModels, setGeminiModels] = useState<{ id: string; displayName: string }[]>(DEFAULT_GEMINI_MODELS)
|
|
73
85
|
const [showModelDropdown, setShowModelDropdown] = useState(false)
|
|
74
86
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
75
87
|
|
|
@@ -144,19 +156,28 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
144
156
|
|
|
145
157
|
fetchGeminiModels()
|
|
146
158
|
.then(models => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
const list = models && models.length > 0 ? models : DEFAULT_GEMINI_MODELS;
|
|
160
|
+
setGeminiModels(list);
|
|
161
|
+
const savedSub = localStorage.getItem('selectedSubModel');
|
|
162
|
+
const exists = list.some(m => m.id === savedSub);
|
|
163
|
+
if (savedSub && exists) {
|
|
164
|
+
setSelectedSubModel(savedSub);
|
|
165
|
+
} else {
|
|
166
|
+
setSelectedSubModel(list[0].id);
|
|
167
|
+
localStorage.setItem('selectedSubModel', list[0].id);
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.catch(() => {
|
|
171
|
+
setGeminiModels(DEFAULT_GEMINI_MODELS);
|
|
172
|
+
const savedSub = localStorage.getItem('selectedSubModel');
|
|
173
|
+
const exists = DEFAULT_GEMINI_MODELS.some(m => m.id === savedSub);
|
|
174
|
+
if (savedSub && exists) {
|
|
175
|
+
setSelectedSubModel(savedSub);
|
|
176
|
+
} else {
|
|
177
|
+
setSelectedSubModel(DEFAULT_GEMINI_MODELS[0].id);
|
|
178
|
+
localStorage.setItem('selectedSubModel', DEFAULT_GEMINI_MODELS[0].id);
|
|
157
179
|
}
|
|
158
180
|
})
|
|
159
|
-
.catch(console.error)
|
|
160
181
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- onBrainChange is stable from parent
|
|
161
182
|
}, [])
|
|
162
183
|
|
|
@@ -380,7 +401,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
380
401
|
return;
|
|
381
402
|
}
|
|
382
403
|
|
|
383
|
-
const modelWithSubModel = ['gemini', 'antigravity'].includes(selectedModel) && selectedSubModel
|
|
404
|
+
const modelWithSubModel = ['gemini', 'antigravity', 'agy'].includes(selectedModel) && selectedSubModel
|
|
384
405
|
? `${selectedModel}:${selectedSubModel}`
|
|
385
406
|
: selectedModel
|
|
386
407
|
|
|
@@ -409,7 +430,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
409
430
|
setLoading(true)
|
|
410
431
|
abortControllerRef.current = new AbortController()
|
|
411
432
|
try {
|
|
412
|
-
const modelWithSubModel = ['gemini', 'antigravity'].includes(selectedModel) && selectedSubModel
|
|
433
|
+
const modelWithSubModel = ['gemini', 'antigravity', 'agy'].includes(selectedModel) && selectedSubModel
|
|
413
434
|
? `${selectedModel}:${selectedSubModel}`
|
|
414
435
|
: selectedModel
|
|
415
436
|
|
|
@@ -599,7 +620,12 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
599
620
|
}}
|
|
600
621
|
title="Select CLI agent / provider"
|
|
601
622
|
>
|
|
602
|
-
<span style={{ textTransform: 'capitalize' }}>
|
|
623
|
+
<span style={{ textTransform: 'capitalize' }}>
|
|
624
|
+
{selectedModel || 'Select Model'}
|
|
625
|
+
{['gemini', 'antigravity', 'agy'].includes(selectedModel) && selectedSubModel
|
|
626
|
+
? ` · ${geminiModels.find(m => m.id === selectedSubModel)?.displayName || selectedSubModel}`
|
|
627
|
+
: ''}
|
|
628
|
+
</span>
|
|
603
629
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
604
630
|
<polyline points="6 9 12 15 18 9"></polyline>
|
|
605
631
|
</svg>
|
|
@@ -629,6 +655,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
629
655
|
<span className="model-selector-item-name" style={{ textTransform: 'capitalize' }}>{modelName}</span>
|
|
630
656
|
<span className="model-selector-item-desc">
|
|
631
657
|
{modelName === 'antigravity' && 'Google Antigravity'}
|
|
658
|
+
{modelName === 'agy' && 'Google Antigravity'}
|
|
632
659
|
{modelName === 'grok' && 'xAI Grok Build CLI'}
|
|
633
660
|
{modelName === 'gemini' && 'Google Gemini CLI'}
|
|
634
661
|
{modelName === 'claude' && 'Anthropic Claude CLI'}
|
|
@@ -641,7 +668,7 @@ export default function ChatPage({ activeBrainId, onBrainChange }: { activeBrain
|
|
|
641
668
|
</div>
|
|
642
669
|
)}
|
|
643
670
|
</div>
|
|
644
|
-
{['gemini', 'antigravity'].includes(selectedModel) && geminiModels.length > 0 && (
|
|
671
|
+
{['gemini', 'antigravity', 'agy'].includes(selectedModel) && geminiModels.length > 0 && (
|
|
645
672
|
<div className="chat-header-submodel-selector" style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 16 }}>
|
|
646
673
|
<span className="selector-label" style={{ fontSize: '12px', color: 'var(--text-tertiary)', fontWeight: 500 }}>Base:</span>
|
|
647
674
|
<select
|
|
@@ -66,6 +66,13 @@ const PRESETS: Preset[] = [
|
|
|
66
66
|
command: 'npx total-recall connect hermes',
|
|
67
67
|
snippet: () => `Pushes truncated memory surface projections to autonomous Hermes.`
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
id: 'dsh',
|
|
71
|
+
name: 'DeepSeek Harness (dsh)',
|
|
72
|
+
surface: 'AGENTS.md / ~/.dsh/memory/MEMORY.md',
|
|
73
|
+
command: 'npx total-recall connect dsh',
|
|
74
|
+
snippet: () => `Integrates SSSS memory and skills into DeepSeek Harness agent runtimes.`
|
|
75
|
+
},
|
|
69
76
|
{
|
|
70
77
|
id: 'openclaw',
|
|
71
78
|
name: 'OpenClaw',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import PluginsPage from './PluginsPage';
|
|
4
|
+
|
|
5
|
+
// Mock fetch globally
|
|
6
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
7
|
+
ok: true,
|
|
8
|
+
json: async () => ({ installed: [], catalog: [] })
|
|
9
|
+
});
|
|
10
|
+
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
11
|
+
|
|
12
|
+
describe('PluginsPage', () => {
|
|
13
|
+
it('renders plugins page', () => {
|
|
14
|
+
render(<PluginsPage />);
|
|
15
|
+
expect(screen.getByText(/Plugin/i)).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
});
|