apteva 0.4.20 → 0.4.29
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/dist/ActivityPage.41nbye4r.js +3 -0
- package/dist/{ApiDocsPage.kf6bbwkk.js → ApiDocsPage.4smnt8m3.js} +2 -2
- package/dist/{App.jfx3der4.js → App.0sbax9et.js} +3 -3
- package/dist/App.0ws427h8.js +4 -0
- package/dist/App.4ehxpt48.js +4 -0
- package/dist/App.6q6bar8b.js +4 -0
- package/dist/App.ca1rz1ph.js +4 -0
- package/dist/{App.7v1w3ys9.js → App.ensa6z0r.js} +3 -3
- package/dist/{App.n4jb3c22.js → App.f8g7tych.js} +3 -3
- package/dist/App.kh7d2xj3.js +267 -0
- package/dist/App.mvtqv6qc.js +20 -0
- package/dist/{App.c90t3dxg.js → App.ncgc9cxy.js} +3 -3
- package/dist/{App.039re6cf.js → App.p0fb1pds.js} +3 -3
- package/dist/App.pmaq48sj.js +4 -0
- package/dist/{App.2yy66bnp.js → App.yv87t9m5.js} +3 -3
- package/dist/App.zjmfm8p6.js +4 -0
- package/dist/ConnectionsPage.anb3rv9a.js +3 -0
- package/dist/McpPage.y396h6fy.js +3 -0
- package/dist/SettingsPage.5k6vp396.js +3 -0
- package/dist/SkillsPage.yj3xdsay.js +3 -0
- package/dist/TasksPage.sjv0khtv.js +3 -0
- package/dist/TelemetryPage.2qm4w16r.js +3 -0
- package/dist/TestsPage.zzs4qfj8.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/channels/telegram.ts +5 -0
- package/src/crypto.ts +13 -4
- package/src/db.ts +25 -2
- package/src/integrations/agentdojo.ts +1 -1
- package/src/providers.ts +46 -0
- package/src/routes/api/agent-utils.ts +64 -9
- package/src/routes/api/agents.ts +41 -13
- package/src/routes/api/integrations.ts +16 -6
- package/src/routes/api/mcp.ts +7 -0
- package/src/routes/api/triggers.ts +45 -5
- package/src/web/App.tsx +1 -0
- package/src/web/components/activity/ActivityPage.tsx +349 -214
- package/src/web/components/agents/AgentCard.tsx +37 -8
- package/src/web/components/agents/AgentPanel.tsx +268 -23
- package/src/web/components/connections/IntegrationsTab.tsx +57 -31
- package/src/web/components/connections/TriggersTab.tsx +336 -159
- package/src/web/components/dashboard/Dashboard.tsx +39 -7
- package/src/web/components/layout/Header.tsx +0 -34
- package/src/web/components/layout/Sidebar.tsx +43 -3
- package/src/web/components/mcp/McpPage.tsx +16 -5
- package/src/web/components/settings/SettingsPage.tsx +279 -30
- package/src/web/components/tasks/TasksPage.tsx +32 -6
- package/src/web/context/ProjectContext.tsx +5 -0
- package/src/web/context/TelemetryContext.tsx +14 -0
- package/src/web/types.ts +20 -2
- package/dist/ActivityPage.h769ek3a.js +0 -3
- package/dist/App.2jmkqm8c.js +0 -4
- package/dist/App.3515wsb4.js +0 -4
- package/dist/App.edwahsvz.js +0 -4
- package/dist/App.q3bpx15d.js +0 -20
- package/dist/App.r0a2nmqs.js +0 -267
- package/dist/App.s2yrcz15.js +0 -4
- package/dist/App.s5j82a5j.js +0 -4
- package/dist/App.tg1b94tx.js +0 -4
- package/dist/ConnectionsPage.a67fjgbf.js +0 -3
- package/dist/McpPage.d4p3xvtk.js +0 -3
- package/dist/SettingsPage.46sqpe39.js +0 -3
- package/dist/SkillsPage.j9hkqm99.js +0 -3
- package/dist/TasksPage.6pvkb7s7.js +0 -3
- package/dist/TelemetryPage.5zq9msb5.js +0 -3
- package/dist/TestsPage.24432yqt.js +0 -3
|
@@ -5,16 +5,17 @@ import { Select } from "../common/Select";
|
|
|
5
5
|
import { useProjects, useAuth, type Project } from "../../context";
|
|
6
6
|
import type { Provider } from "../../types";
|
|
7
7
|
|
|
8
|
-
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data";
|
|
8
|
+
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data" | "assistant";
|
|
9
9
|
|
|
10
10
|
export function SettingsPage() {
|
|
11
|
-
const { projectsEnabled } = useProjects();
|
|
11
|
+
const { projectsEnabled, metaAgentEnabled } = useProjects();
|
|
12
12
|
const [activeTab, setActiveTab] = useState<SettingsTab>("general");
|
|
13
13
|
|
|
14
14
|
const tabs: { key: SettingsTab; label: string }[] = [
|
|
15
15
|
{ key: "general", label: "General" },
|
|
16
16
|
{ key: "providers", label: "Providers" },
|
|
17
17
|
...(projectsEnabled ? [{ key: "projects" as SettingsTab, label: "Projects" }] : []),
|
|
18
|
+
...(metaAgentEnabled ? [{ key: "assistant" as SettingsTab, label: "Assistant" }] : []),
|
|
18
19
|
{ key: "channels", label: "Channels" },
|
|
19
20
|
{ key: "api-keys", label: "API Keys" },
|
|
20
21
|
{ key: "account", label: "Account" },
|
|
@@ -68,6 +69,7 @@ export function SettingsPage() {
|
|
|
68
69
|
{activeTab === "account" && <AccountSettings />}
|
|
69
70
|
{activeTab === "updates" && <UpdatesSettings />}
|
|
70
71
|
{activeTab === "data" && <DataSettings />}
|
|
72
|
+
{activeTab === "assistant" && metaAgentEnabled && <AssistantSettings />}
|
|
71
73
|
</div>
|
|
72
74
|
</div>
|
|
73
75
|
);
|
|
@@ -273,8 +275,10 @@ function ProvidersSettings() {
|
|
|
273
275
|
|
|
274
276
|
const llmProviders = providers.filter(p => p.type === "llm");
|
|
275
277
|
const integrations = providers.filter(p => p.type === "integration");
|
|
278
|
+
const browserProviders = providers.filter(p => p.type === "browser");
|
|
276
279
|
const llmConfiguredCount = llmProviders.filter(p => p.hasKey).length;
|
|
277
280
|
const intConfiguredCount = integrations.filter(p => p.hasKey).length;
|
|
281
|
+
const browserConfiguredCount = browserProviders.filter(p => p.hasKey).length;
|
|
278
282
|
|
|
279
283
|
// Auto-dismiss success message after 5 seconds
|
|
280
284
|
useEffect(() => {
|
|
@@ -382,6 +386,44 @@ function ProvidersSettings() {
|
|
|
382
386
|
))}
|
|
383
387
|
</div>
|
|
384
388
|
</div>
|
|
389
|
+
|
|
390
|
+
{/* Browser Providers Section */}
|
|
391
|
+
<div>
|
|
392
|
+
<div className="mb-6">
|
|
393
|
+
<h2 className="text-xl font-semibold mb-1">Browser Providers</h2>
|
|
394
|
+
<p className="text-[#666]">
|
|
395
|
+
Configure browser environments for operator mode (computer use). {browserConfiguredCount} of {browserProviders.length} configured.
|
|
396
|
+
</p>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
400
|
+
{browserProviders.map(provider => (
|
|
401
|
+
<ProviderKeyCard
|
|
402
|
+
key={provider.id}
|
|
403
|
+
provider={provider}
|
|
404
|
+
isEditing={selectedProvider === provider.id}
|
|
405
|
+
apiKey={apiKey}
|
|
406
|
+
saving={saving}
|
|
407
|
+
testing={testing}
|
|
408
|
+
error={selectedProvider === provider.id ? error : null}
|
|
409
|
+
success={selectedProvider === provider.id ? success : null}
|
|
410
|
+
onStartEdit={() => {
|
|
411
|
+
setSelectedProvider(provider.id);
|
|
412
|
+
setError(null);
|
|
413
|
+
setSuccess(null);
|
|
414
|
+
}}
|
|
415
|
+
onCancelEdit={() => {
|
|
416
|
+
setSelectedProvider(null);
|
|
417
|
+
setApiKey("");
|
|
418
|
+
setError(null);
|
|
419
|
+
}}
|
|
420
|
+
onApiKeyChange={setApiKey}
|
|
421
|
+
onSave={saveKey}
|
|
422
|
+
onDelete={() => deleteKey(provider.id)}
|
|
423
|
+
/>
|
|
424
|
+
))}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
385
427
|
</div>
|
|
386
428
|
</>
|
|
387
429
|
);
|
|
@@ -899,6 +941,8 @@ function ProviderKeyCard({
|
|
|
899
941
|
onDelete,
|
|
900
942
|
}: ProviderKeyCardProps) {
|
|
901
943
|
const isOllama = provider.id === "ollama";
|
|
944
|
+
const isUrlBased = isOllama || provider.id === "browserengine" || provider.id === "chrome";
|
|
945
|
+
const isBrowser = provider.type === "browser";
|
|
902
946
|
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
|
|
903
947
|
|
|
904
948
|
// Check Ollama status when configured
|
|
@@ -919,11 +963,13 @@ function ProviderKeyCard({
|
|
|
919
963
|
<div className="min-w-0">
|
|
920
964
|
<h3 className="font-medium">{provider.name}</h3>
|
|
921
965
|
<p className="text-sm text-[#666] truncate">
|
|
922
|
-
{
|
|
923
|
-
? (provider.description || "
|
|
924
|
-
:
|
|
925
|
-
?
|
|
926
|
-
:
|
|
966
|
+
{isBrowser
|
|
967
|
+
? (provider.description || "Browser automation")
|
|
968
|
+
: provider.type === "integration"
|
|
969
|
+
? (provider.description || "MCP integration")
|
|
970
|
+
: isOllama
|
|
971
|
+
? "Run models locally"
|
|
972
|
+
: `${provider.models.length} models`}
|
|
927
973
|
</p>
|
|
928
974
|
</div>
|
|
929
975
|
{provider.hasKey ? (
|
|
@@ -940,6 +986,8 @@ function ProviderKeyCard({
|
|
|
940
986
|
) : (
|
|
941
987
|
<>Not running</>
|
|
942
988
|
)
|
|
989
|
+
) : isUrlBased ? (
|
|
990
|
+
<><CheckIcon className="w-3 h-3" />Configured</>
|
|
943
991
|
) : (
|
|
944
992
|
<><CheckIcon className="w-3 h-3" />{provider.keyHint}</>
|
|
945
993
|
)}
|
|
@@ -955,18 +1003,26 @@ function ProviderKeyCard({
|
|
|
955
1003
|
{isEditing ? (
|
|
956
1004
|
<div className="space-y-3">
|
|
957
1005
|
<input
|
|
958
|
-
type={
|
|
1006
|
+
type={isUrlBased ? "text" : "password"}
|
|
959
1007
|
value={apiKey}
|
|
960
1008
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
961
1009
|
placeholder={isOllama
|
|
962
1010
|
? "http://localhost:11434"
|
|
963
|
-
: provider.
|
|
1011
|
+
: provider.id === "browserengine"
|
|
1012
|
+
? "http://localhost:8098"
|
|
1013
|
+
: provider.id === "chrome"
|
|
1014
|
+
? "http://localhost:9222"
|
|
1015
|
+
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
964
1016
|
autoFocus
|
|
965
1017
|
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
966
1018
|
/>
|
|
967
|
-
{
|
|
1019
|
+
{isUrlBased && (
|
|
968
1020
|
<p className="text-xs text-[#666]">
|
|
969
|
-
|
|
1021
|
+
{isOllama
|
|
1022
|
+
? "Enter your Ollama server URL. Default is http://localhost:11434"
|
|
1023
|
+
: provider.id === "browserengine"
|
|
1024
|
+
? "Enter your BrowserEngine service URL (e.g., http://localhost:8098)"
|
|
1025
|
+
: "Enter your Chrome DevTools URL (e.g., http://localhost:9222)"}
|
|
970
1026
|
</p>
|
|
971
1027
|
)}
|
|
972
1028
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
@@ -983,26 +1039,30 @@ function ProviderKeyCard({
|
|
|
983
1039
|
disabled={!apiKey || saving}
|
|
984
1040
|
className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
985
1041
|
>
|
|
986
|
-
{testing ? "Validating..." : saving ? "Saving..." :
|
|
1042
|
+
{testing ? "Validating..." : saving ? "Saving..." : isUrlBased ? "Connect" : "Save"}
|
|
987
1043
|
</button>
|
|
988
1044
|
</div>
|
|
989
1045
|
</div>
|
|
990
1046
|
) : provider.hasKey ? (
|
|
991
1047
|
<div className="flex items-center justify-between">
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1048
|
+
{provider.docsUrl ? (
|
|
1049
|
+
<a
|
|
1050
|
+
href={provider.docsUrl}
|
|
1051
|
+
target="_blank"
|
|
1052
|
+
rel="noopener noreferrer"
|
|
1053
|
+
className="text-sm text-[#3b82f6] hover:underline"
|
|
1054
|
+
>
|
|
1055
|
+
{isOllama ? "Download Ollama" : "View docs"}
|
|
1056
|
+
</a>
|
|
1057
|
+
) : (
|
|
1058
|
+
<span />
|
|
1059
|
+
)}
|
|
1000
1060
|
<div className="flex items-center gap-3">
|
|
1001
1061
|
<button
|
|
1002
1062
|
onClick={onStartEdit}
|
|
1003
1063
|
className="text-sm text-[#888] hover:text-[#e0e0e0]"
|
|
1004
1064
|
>
|
|
1005
|
-
{
|
|
1065
|
+
{isUrlBased ? "Change URL" : "Update key"}
|
|
1006
1066
|
</button>
|
|
1007
1067
|
<button
|
|
1008
1068
|
onClick={onDelete}
|
|
@@ -1014,19 +1074,23 @@ function ProviderKeyCard({
|
|
|
1014
1074
|
</div>
|
|
1015
1075
|
) : (
|
|
1016
1076
|
<div className="flex items-center justify-between">
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1077
|
+
{provider.docsUrl ? (
|
|
1078
|
+
<a
|
|
1079
|
+
href={provider.docsUrl}
|
|
1080
|
+
target="_blank"
|
|
1081
|
+
rel="noopener noreferrer"
|
|
1082
|
+
className="text-sm text-[#3b82f6] hover:underline"
|
|
1083
|
+
>
|
|
1084
|
+
{isOllama ? "Download Ollama" : isBrowser ? "View docs" : "Get API key"}
|
|
1085
|
+
</a>
|
|
1086
|
+
) : (
|
|
1087
|
+
<span />
|
|
1088
|
+
)}
|
|
1025
1089
|
<button
|
|
1026
1090
|
onClick={onStartEdit}
|
|
1027
1091
|
className="text-sm text-[#f97316] hover:text-[#fb923c]"
|
|
1028
1092
|
>
|
|
1029
|
-
{
|
|
1093
|
+
{isUrlBased ? "Configure" : "+ Add key"}
|
|
1030
1094
|
</button>
|
|
1031
1095
|
</div>
|
|
1032
1096
|
)}
|
|
@@ -2152,3 +2216,188 @@ function ChannelsSettings() {
|
|
|
2152
2216
|
</>
|
|
2153
2217
|
);
|
|
2154
2218
|
}
|
|
2219
|
+
|
|
2220
|
+
function AssistantSettings() {
|
|
2221
|
+
const { authFetch } = useAuth();
|
|
2222
|
+
const [providers, setProviders] = useState<Provider[]>([]);
|
|
2223
|
+
const [provider, setProvider] = useState("");
|
|
2224
|
+
const [model, setModel] = useState("");
|
|
2225
|
+
const [systemPrompt, setSystemPrompt] = useState("");
|
|
2226
|
+
const [status, setStatus] = useState<"running" | "stopped" | "unknown">("unknown");
|
|
2227
|
+
const [loading, setLoading] = useState(true);
|
|
2228
|
+
const [saving, setSaving] = useState(false);
|
|
2229
|
+
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
|
2230
|
+
const [starting, setStarting] = useState(false);
|
|
2231
|
+
|
|
2232
|
+
// Original values for change detection
|
|
2233
|
+
const [original, setOriginal] = useState({ provider: "", model: "", systemPrompt: "" });
|
|
2234
|
+
|
|
2235
|
+
useEffect(() => {
|
|
2236
|
+
const fetchData = async () => {
|
|
2237
|
+
try {
|
|
2238
|
+
const [statusRes, providersRes] = await Promise.all([
|
|
2239
|
+
authFetch("/api/meta-agent/status"),
|
|
2240
|
+
authFetch("/api/providers"),
|
|
2241
|
+
]);
|
|
2242
|
+
const statusData = await statusRes.json();
|
|
2243
|
+
const providersData = await providersRes.json();
|
|
2244
|
+
setProviders((providersData.providers || []).filter((p: Provider) => p.type === "llm" && p.hasKey));
|
|
2245
|
+
|
|
2246
|
+
if (statusData.agent) {
|
|
2247
|
+
const a = statusData.agent;
|
|
2248
|
+
setProvider(a.provider || "");
|
|
2249
|
+
setModel(a.model || "");
|
|
2250
|
+
setSystemPrompt(a.systemPrompt || "");
|
|
2251
|
+
setStatus(a.status || "stopped");
|
|
2252
|
+
setOriginal({ provider: a.provider || "", model: a.model || "", systemPrompt: a.systemPrompt || "" });
|
|
2253
|
+
}
|
|
2254
|
+
} catch {
|
|
2255
|
+
setMessage({ type: "error", text: "Failed to load assistant config" });
|
|
2256
|
+
} finally {
|
|
2257
|
+
setLoading(false);
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
fetchData();
|
|
2261
|
+
}, [authFetch]);
|
|
2262
|
+
|
|
2263
|
+
const selectedProvider = providers.find(p => p.id === provider);
|
|
2264
|
+
const models = selectedProvider?.models || [];
|
|
2265
|
+
|
|
2266
|
+
const handleProviderChange = (newProvider: string) => {
|
|
2267
|
+
setProvider(newProvider);
|
|
2268
|
+
const p = providers.find(pr => pr.id === newProvider);
|
|
2269
|
+
const defaultModel = p?.models.find(m => m.recommended)?.value || p?.models[0]?.value || "";
|
|
2270
|
+
setModel(defaultModel);
|
|
2271
|
+
};
|
|
2272
|
+
|
|
2273
|
+
const hasChanges = provider !== original.provider || model !== original.model || systemPrompt !== original.systemPrompt;
|
|
2274
|
+
|
|
2275
|
+
const handleSave = async () => {
|
|
2276
|
+
setSaving(true);
|
|
2277
|
+
setMessage(null);
|
|
2278
|
+
try {
|
|
2279
|
+
const res = await authFetch("/api/agents/apteva-assistant", {
|
|
2280
|
+
method: "PUT",
|
|
2281
|
+
headers: { "Content-Type": "application/json" },
|
|
2282
|
+
body: JSON.stringify({ provider, model, systemPrompt }),
|
|
2283
|
+
});
|
|
2284
|
+
if (res.ok) {
|
|
2285
|
+
setOriginal({ provider, model, systemPrompt });
|
|
2286
|
+
setMessage({ type: "success", text: "Assistant settings saved" });
|
|
2287
|
+
setTimeout(() => setMessage(null), 3000);
|
|
2288
|
+
} else {
|
|
2289
|
+
const data = await res.json().catch(() => ({}));
|
|
2290
|
+
setMessage({ type: "error", text: data.error || "Failed to save" });
|
|
2291
|
+
}
|
|
2292
|
+
} catch {
|
|
2293
|
+
setMessage({ type: "error", text: "Failed to save settings" });
|
|
2294
|
+
} finally {
|
|
2295
|
+
setSaving(false);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2299
|
+
const handleToggle = async () => {
|
|
2300
|
+
setStarting(true);
|
|
2301
|
+
setMessage(null);
|
|
2302
|
+
try {
|
|
2303
|
+
const endpoint = status === "running" ? "/api/meta-agent/stop" : "/api/meta-agent/start";
|
|
2304
|
+
const res = await authFetch(endpoint, { method: "POST" });
|
|
2305
|
+
if (res.ok) {
|
|
2306
|
+
setStatus(status === "running" ? "stopped" : "running");
|
|
2307
|
+
} else {
|
|
2308
|
+
const data = await res.json().catch(() => ({}));
|
|
2309
|
+
setMessage({ type: "error", text: data.error || "Failed to toggle assistant" });
|
|
2310
|
+
}
|
|
2311
|
+
} catch {
|
|
2312
|
+
setMessage({ type: "error", text: "Failed to toggle assistant" });
|
|
2313
|
+
} finally {
|
|
2314
|
+
setStarting(false);
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
if (loading) {
|
|
2319
|
+
return <div className="text-[#666]">Loading assistant settings...</div>;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
return (
|
|
2323
|
+
<div className="max-w-2xl">
|
|
2324
|
+
<h2 className="text-lg font-medium mb-1">Apteva Assistant</h2>
|
|
2325
|
+
<p className="text-sm text-[#666] mb-6">Configure the built-in AI assistant that manages your agents and platform.</p>
|
|
2326
|
+
|
|
2327
|
+
{message && (
|
|
2328
|
+
<div className={`mb-4 px-3 py-2 rounded text-sm ${
|
|
2329
|
+
message.type === "success" ? "bg-green-500/10 text-green-400" : "bg-red-500/10 text-red-400"
|
|
2330
|
+
}`}>
|
|
2331
|
+
{message.text}
|
|
2332
|
+
</div>
|
|
2333
|
+
)}
|
|
2334
|
+
|
|
2335
|
+
{/* Status */}
|
|
2336
|
+
<div className="mb-6 flex items-center gap-3">
|
|
2337
|
+
<span className="text-sm text-[#666]">Status:</span>
|
|
2338
|
+
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
|
2339
|
+
status === "running" ? "bg-[#3b82f6]/20 text-[#3b82f6]" : "bg-[#333] text-[#666]"
|
|
2340
|
+
}`}>
|
|
2341
|
+
{status}
|
|
2342
|
+
</span>
|
|
2343
|
+
<button
|
|
2344
|
+
onClick={handleToggle}
|
|
2345
|
+
disabled={starting}
|
|
2346
|
+
className={`px-3 py-1.5 rounded text-sm font-medium transition ${
|
|
2347
|
+
status === "running"
|
|
2348
|
+
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
2349
|
+
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
2350
|
+
} disabled:opacity-50`}
|
|
2351
|
+
>
|
|
2352
|
+
{starting ? "..." : status === "running" ? "Stop" : "Start"}
|
|
2353
|
+
</button>
|
|
2354
|
+
</div>
|
|
2355
|
+
|
|
2356
|
+
{/* Provider */}
|
|
2357
|
+
<div className="mb-4">
|
|
2358
|
+
<label className="block text-sm text-[#666] mb-1">Provider</label>
|
|
2359
|
+
<Select
|
|
2360
|
+
value={provider}
|
|
2361
|
+
onChange={handleProviderChange}
|
|
2362
|
+
options={providers.map(p => ({ value: p.id, label: p.name }))}
|
|
2363
|
+
placeholder="Select provider..."
|
|
2364
|
+
/>
|
|
2365
|
+
</div>
|
|
2366
|
+
|
|
2367
|
+
{/* Model */}
|
|
2368
|
+
<div className="mb-4">
|
|
2369
|
+
<label className="block text-sm text-[#666] mb-1">Model</label>
|
|
2370
|
+
<Select
|
|
2371
|
+
value={model}
|
|
2372
|
+
onChange={setModel}
|
|
2373
|
+
options={models.map(m => ({ value: m.value, label: m.label, recommended: m.recommended }))}
|
|
2374
|
+
placeholder="Select model..."
|
|
2375
|
+
/>
|
|
2376
|
+
</div>
|
|
2377
|
+
|
|
2378
|
+
{/* System Prompt */}
|
|
2379
|
+
<div className="mb-6">
|
|
2380
|
+
<label className="block text-sm text-[#666] mb-1">System Prompt</label>
|
|
2381
|
+
<textarea
|
|
2382
|
+
value={systemPrompt}
|
|
2383
|
+
onChange={e => setSystemPrompt(e.target.value)}
|
|
2384
|
+
rows={12}
|
|
2385
|
+
className="w-full bg-[#111] border border-[#1a1a1a] rounded px-3 py-2 text-sm font-mono focus:outline-none focus:border-[#f97316] resize-y"
|
|
2386
|
+
/>
|
|
2387
|
+
</div>
|
|
2388
|
+
|
|
2389
|
+
{/* Save */}
|
|
2390
|
+
<button
|
|
2391
|
+
onClick={handleSave}
|
|
2392
|
+
disabled={!hasChanges || saving}
|
|
2393
|
+
className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
|
|
2394
|
+
>
|
|
2395
|
+
{saving ? "Saving..." : "Save Changes"}
|
|
2396
|
+
</button>
|
|
2397
|
+
|
|
2398
|
+
{status === "running" && hasChanges && (
|
|
2399
|
+
<p className="text-xs text-[#666] mt-2">Changes will be applied to the running assistant</p>
|
|
2400
|
+
)}
|
|
2401
|
+
</div>
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
2
2
|
import { TasksIcon, CloseIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
|
|
3
3
|
import { useAuth, useProjects } from "../../context";
|
|
4
4
|
import { useTelemetry } from "../../context/TelemetryContext";
|
|
@@ -87,6 +87,32 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
87
87
|
}
|
|
88
88
|
}, [authFetch]);
|
|
89
89
|
|
|
90
|
+
// Sort tasks: running first, then pending by next execution (soonest first), then completed/failed by date
|
|
91
|
+
const sortedTasks = useMemo(() => {
|
|
92
|
+
return [...tasks].sort((a, b) => {
|
|
93
|
+
// Running tasks first
|
|
94
|
+
if (a.status === "running" && b.status !== "running") return -1;
|
|
95
|
+
if (b.status === "running" && a.status !== "running") return 1;
|
|
96
|
+
// Pending tasks next
|
|
97
|
+
const aIsPending = a.status === "pending";
|
|
98
|
+
const bIsPending = b.status === "pending";
|
|
99
|
+
if (aIsPending && !bIsPending) return -1;
|
|
100
|
+
if (bIsPending && !aIsPending) return 1;
|
|
101
|
+
// For running/pending: sort by next execution time (soonest first)
|
|
102
|
+
if (aIsPending && bIsPending || a.status === "running" && b.status === "running") {
|
|
103
|
+
const aTime = a.next_run || a.execute_at || null;
|
|
104
|
+
const bTime = b.next_run || b.execute_at || null;
|
|
105
|
+
const aTs = aTime ? new Date(aTime).getTime() : Infinity;
|
|
106
|
+
const bTs = bTime ? new Date(bTime).getTime() : Infinity;
|
|
107
|
+
return aTs - bTs;
|
|
108
|
+
}
|
|
109
|
+
// For completed/failed: most recent first
|
|
110
|
+
const aDate = a.completed_at || a.executed_at || a.created_at;
|
|
111
|
+
const bDate = b.completed_at || b.executed_at || b.created_at;
|
|
112
|
+
return new Date(bDate).getTime() - new Date(aDate).getTime();
|
|
113
|
+
});
|
|
114
|
+
}, [tasks]);
|
|
115
|
+
|
|
90
116
|
const statusColors: Record<string, string> = {
|
|
91
117
|
pending: "bg-yellow-500/20 text-yellow-400",
|
|
92
118
|
running: "bg-blue-500/20 text-blue-400",
|
|
@@ -134,7 +160,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
134
160
|
|
|
135
161
|
{loading ? (
|
|
136
162
|
<div className="text-center py-12 text-[#666]">Loading tasks...</div>
|
|
137
|
-
) :
|
|
163
|
+
) : sortedTasks.length === 0 ? (
|
|
138
164
|
<div className="text-center py-12">
|
|
139
165
|
<TasksIcon className="w-12 h-12 mx-auto mb-4 text-[#333]" />
|
|
140
166
|
<p className="text-[#666]">No tasks found</p>
|
|
@@ -144,7 +170,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
144
170
|
</div>
|
|
145
171
|
) : (
|
|
146
172
|
<div className="space-y-3">
|
|
147
|
-
{
|
|
173
|
+
{sortedTasks.map(task => (
|
|
148
174
|
<div
|
|
149
175
|
key={`${task.agentId}-${task.id}`}
|
|
150
176
|
onClick={() => selectTask(task)}
|
|
@@ -210,7 +236,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
210
236
|
);
|
|
211
237
|
}
|
|
212
238
|
|
|
213
|
-
interface TaskDetailPanelProps {
|
|
239
|
+
export interface TaskDetailPanelProps {
|
|
214
240
|
task: Task;
|
|
215
241
|
statusColors: Record<string, string>;
|
|
216
242
|
onClose: () => void;
|
|
@@ -218,7 +244,7 @@ interface TaskDetailPanelProps {
|
|
|
218
244
|
loading?: boolean;
|
|
219
245
|
}
|
|
220
246
|
|
|
221
|
-
function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }: TaskDetailPanelProps) {
|
|
247
|
+
export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }: TaskDetailPanelProps) {
|
|
222
248
|
return (
|
|
223
249
|
<div className="w-full md:w-1/2 lg:w-1/3 border-l border-[#1a1a1a] bg-[#0a0a0a] flex flex-col overflow-hidden">
|
|
224
250
|
{/* Header */}
|
|
@@ -352,7 +378,7 @@ function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }
|
|
|
352
378
|
);
|
|
353
379
|
}
|
|
354
380
|
|
|
355
|
-
function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[] }) {
|
|
381
|
+
export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[] }) {
|
|
356
382
|
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
357
383
|
|
|
358
384
|
const toggleStep = (id: string) => {
|
|
@@ -19,6 +19,7 @@ interface ProjectContextValue {
|
|
|
19
19
|
error: string | null;
|
|
20
20
|
unassignedCount: number;
|
|
21
21
|
projectsEnabled: boolean; // Feature flag
|
|
22
|
+
metaAgentEnabled: boolean; // Feature flag
|
|
22
23
|
setCurrentProjectId: (id: string | null) => void;
|
|
23
24
|
createProject: (data: { name: string; description?: string; color?: string }) => Promise<Project | null>;
|
|
24
25
|
updateProject: (id: string, data: { name?: string; description?: string; color?: string }) => Promise<Project | null>;
|
|
@@ -56,6 +57,7 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
|
|
|
56
57
|
const [error, setError] = useState<string | null>(null);
|
|
57
58
|
const [unassignedCount, setUnassignedCount] = useState(0);
|
|
58
59
|
const [projectsEnabled, setProjectsEnabled] = useState(false);
|
|
60
|
+
const [metaAgentEnabled, setMetaAgentEnabled] = useState(false);
|
|
59
61
|
|
|
60
62
|
// Fetch feature flags on mount
|
|
61
63
|
useEffect(() => {
|
|
@@ -63,9 +65,11 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
|
|
|
63
65
|
.then(res => res.json())
|
|
64
66
|
.then(data => {
|
|
65
67
|
setProjectsEnabled(data.projects === true);
|
|
68
|
+
setMetaAgentEnabled(data.metaAgent === true);
|
|
66
69
|
})
|
|
67
70
|
.catch(() => {
|
|
68
71
|
setProjectsEnabled(false);
|
|
72
|
+
setMetaAgentEnabled(false);
|
|
69
73
|
});
|
|
70
74
|
}, []);
|
|
71
75
|
|
|
@@ -193,6 +197,7 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
|
|
|
193
197
|
error,
|
|
194
198
|
unassignedCount,
|
|
195
199
|
projectsEnabled,
|
|
200
|
+
metaAgentEnabled,
|
|
196
201
|
setCurrentProjectId,
|
|
197
202
|
createProject,
|
|
198
203
|
updateProject,
|
|
@@ -233,6 +233,19 @@ export function useTelemetry(filter?: {
|
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// Map raw telemetry event types to user-friendly labels
|
|
237
|
+
export function getActivityLabel(type: string): string {
|
|
238
|
+
switch (type) {
|
|
239
|
+
case "llm_request": return "Thinking";
|
|
240
|
+
case "tool_invocation": return "Using tools";
|
|
241
|
+
case "tool_result": return "Using tools";
|
|
242
|
+
case "thread_activity": return "Working";
|
|
243
|
+
case "agent_started": return "Starting";
|
|
244
|
+
case "agent_stopped": return "Stopped";
|
|
245
|
+
default: return "Working";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
// Hook for agent activity indicator - uses context-level tracking
|
|
237
250
|
export function useAgentActivity(agentId: string) {
|
|
238
251
|
const { activeAgents } = useTelemetryContext();
|
|
@@ -241,6 +254,7 @@ export function useAgentActivity(agentId: string) {
|
|
|
241
254
|
return {
|
|
242
255
|
isActive: !!activity,
|
|
243
256
|
type: activity?.type,
|
|
257
|
+
label: activity ? getActivityLabel(activity.type) : undefined,
|
|
244
258
|
};
|
|
245
259
|
}
|
|
246
260
|
|
package/src/web/types.ts
CHANGED
|
@@ -13,11 +13,19 @@ export interface AgentBuiltinTools {
|
|
|
13
13
|
webFetch: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface OperatorConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
browser_provider?: string; // "browserbase" | "steel" | "browserengine" | "chrome"
|
|
19
|
+
display_width?: number;
|
|
20
|
+
display_height?: number;
|
|
21
|
+
max_actions_per_turn?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
export interface AgentFeatures {
|
|
17
25
|
memory: boolean;
|
|
18
26
|
tasks: boolean;
|
|
19
27
|
vision: boolean;
|
|
20
|
-
operator: boolean;
|
|
28
|
+
operator: boolean | OperatorConfig; // Can be boolean for backwards compat or full config
|
|
21
29
|
mcp: boolean;
|
|
22
30
|
realtime: boolean;
|
|
23
31
|
files: boolean;
|
|
@@ -37,6 +45,15 @@ export const DEFAULT_FEATURES: AgentFeatures = {
|
|
|
37
45
|
builtinTools: { webSearch: false, webFetch: false },
|
|
38
46
|
};
|
|
39
47
|
|
|
48
|
+
// Helper to normalize operator feature to OperatorConfig
|
|
49
|
+
export function getOperatorConfig(features: AgentFeatures): OperatorConfig {
|
|
50
|
+
const op = features.operator;
|
|
51
|
+
if (typeof op === "boolean") {
|
|
52
|
+
return { enabled: op };
|
|
53
|
+
}
|
|
54
|
+
return op;
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
// Helper to normalize agents feature to MultiAgentConfig
|
|
41
58
|
export function getMultiAgentConfig(features: AgentFeatures, projectId?: string | null): MultiAgentConfig {
|
|
42
59
|
const agents = features.agents;
|
|
@@ -127,7 +144,7 @@ export interface ProviderModel {
|
|
|
127
144
|
export interface Provider {
|
|
128
145
|
id: string;
|
|
129
146
|
name: string;
|
|
130
|
-
type: "llm" | "integration";
|
|
147
|
+
type: "llm" | "integration" | "browser";
|
|
131
148
|
docsUrl: string;
|
|
132
149
|
description?: string;
|
|
133
150
|
models: ProviderModel[];
|
|
@@ -135,6 +152,7 @@ export interface Provider {
|
|
|
135
152
|
keyHint: string | null;
|
|
136
153
|
isValid: boolean | null;
|
|
137
154
|
configured?: boolean; // for backwards compatibility
|
|
155
|
+
isLocal?: boolean; // Uses URL instead of API key (ollama, browserengine, chrome)
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
export interface OnboardingStatus {
|