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.
Files changed (67) hide show
  1. package/dist/ActivityPage.41nbye4r.js +3 -0
  2. package/dist/{ApiDocsPage.kf6bbwkk.js → ApiDocsPage.4smnt8m3.js} +2 -2
  3. package/dist/{App.jfx3der4.js → App.0sbax9et.js} +3 -3
  4. package/dist/App.0ws427h8.js +4 -0
  5. package/dist/App.4ehxpt48.js +4 -0
  6. package/dist/App.6q6bar8b.js +4 -0
  7. package/dist/App.ca1rz1ph.js +4 -0
  8. package/dist/{App.7v1w3ys9.js → App.ensa6z0r.js} +3 -3
  9. package/dist/{App.n4jb3c22.js → App.f8g7tych.js} +3 -3
  10. package/dist/App.kh7d2xj3.js +267 -0
  11. package/dist/App.mvtqv6qc.js +20 -0
  12. package/dist/{App.c90t3dxg.js → App.ncgc9cxy.js} +3 -3
  13. package/dist/{App.039re6cf.js → App.p0fb1pds.js} +3 -3
  14. package/dist/App.pmaq48sj.js +4 -0
  15. package/dist/{App.2yy66bnp.js → App.yv87t9m5.js} +3 -3
  16. package/dist/App.zjmfm8p6.js +4 -0
  17. package/dist/ConnectionsPage.anb3rv9a.js +3 -0
  18. package/dist/McpPage.y396h6fy.js +3 -0
  19. package/dist/SettingsPage.5k6vp396.js +3 -0
  20. package/dist/SkillsPage.yj3xdsay.js +3 -0
  21. package/dist/TasksPage.sjv0khtv.js +3 -0
  22. package/dist/TelemetryPage.2qm4w16r.js +3 -0
  23. package/dist/TestsPage.zzs4qfj8.js +3 -0
  24. package/dist/index.html +1 -1
  25. package/dist/styles.css +1 -1
  26. package/package.json +2 -2
  27. package/src/channels/telegram.ts +5 -0
  28. package/src/crypto.ts +13 -4
  29. package/src/db.ts +25 -2
  30. package/src/integrations/agentdojo.ts +1 -1
  31. package/src/providers.ts +46 -0
  32. package/src/routes/api/agent-utils.ts +64 -9
  33. package/src/routes/api/agents.ts +41 -13
  34. package/src/routes/api/integrations.ts +16 -6
  35. package/src/routes/api/mcp.ts +7 -0
  36. package/src/routes/api/triggers.ts +45 -5
  37. package/src/web/App.tsx +1 -0
  38. package/src/web/components/activity/ActivityPage.tsx +349 -214
  39. package/src/web/components/agents/AgentCard.tsx +37 -8
  40. package/src/web/components/agents/AgentPanel.tsx +268 -23
  41. package/src/web/components/connections/IntegrationsTab.tsx +57 -31
  42. package/src/web/components/connections/TriggersTab.tsx +336 -159
  43. package/src/web/components/dashboard/Dashboard.tsx +39 -7
  44. package/src/web/components/layout/Header.tsx +0 -34
  45. package/src/web/components/layout/Sidebar.tsx +43 -3
  46. package/src/web/components/mcp/McpPage.tsx +16 -5
  47. package/src/web/components/settings/SettingsPage.tsx +279 -30
  48. package/src/web/components/tasks/TasksPage.tsx +32 -6
  49. package/src/web/context/ProjectContext.tsx +5 -0
  50. package/src/web/context/TelemetryContext.tsx +14 -0
  51. package/src/web/types.ts +20 -2
  52. package/dist/ActivityPage.h769ek3a.js +0 -3
  53. package/dist/App.2jmkqm8c.js +0 -4
  54. package/dist/App.3515wsb4.js +0 -4
  55. package/dist/App.edwahsvz.js +0 -4
  56. package/dist/App.q3bpx15d.js +0 -20
  57. package/dist/App.r0a2nmqs.js +0 -267
  58. package/dist/App.s2yrcz15.js +0 -4
  59. package/dist/App.s5j82a5j.js +0 -4
  60. package/dist/App.tg1b94tx.js +0 -4
  61. package/dist/ConnectionsPage.a67fjgbf.js +0 -3
  62. package/dist/McpPage.d4p3xvtk.js +0 -3
  63. package/dist/SettingsPage.46sqpe39.js +0 -3
  64. package/dist/SkillsPage.j9hkqm99.js +0 -3
  65. package/dist/TasksPage.6pvkb7s7.js +0 -3
  66. package/dist/TelemetryPage.5zq9msb5.js +0 -3
  67. 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
- {provider.type === "integration"
923
- ? (provider.description || "MCP integration")
924
- : isOllama
925
- ? "Run models locally"
926
- : `${provider.models.length} models`}
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={isOllama ? "text" : "password"}
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.hasKey ? "Enter new API key..." : "Enter API key..."}
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
- {isOllama && (
1019
+ {isUrlBased && (
968
1020
  <p className="text-xs text-[#666]">
969
- Enter your Ollama server URL. Default is http://localhost:11434
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..." : isOllama ? "Connect" : "Save"}
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
- <a
993
- href={provider.docsUrl}
994
- target="_blank"
995
- rel="noopener noreferrer"
996
- className="text-sm text-[#3b82f6] hover:underline"
997
- >
998
- {isOllama ? "Download Ollama" : "View docs"}
999
- </a>
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
- {isOllama ? "Change URL" : "Update key"}
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
- <a
1018
- href={provider.docsUrl}
1019
- target="_blank"
1020
- rel="noopener noreferrer"
1021
- className="text-sm text-[#3b82f6] hover:underline"
1022
- >
1023
- {isOllama ? "Download Ollama" : "Get API key"}
1024
- </a>
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
- {isOllama ? "Configure" : "+ Add key"}
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
- ) : tasks.length === 0 ? (
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
- {tasks.map(task => (
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 {
@@ -1,3 +0,0 @@
1
- import{d as a}from"./App.s5j82a5j.js";import"./App.c90t3dxg.js";import"./App.q3bpx15d.js";export{a as ActivityPage};
2
-
3
- //# debugId=A836402CCF3683F564756E2164756E21