apteva 0.4.26 → 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 (54) hide show
  1. package/dist/ActivityPage.41nbye4r.js +3 -0
  2. package/dist/{ApiDocsPage.3q5x9hhg.js → ApiDocsPage.4smnt8m3.js} +1 -1
  3. package/dist/{App.9bzz8dqh.js → App.0sbax9et.js} +1 -1
  4. package/dist/{App.a7h91mxr.js → App.0ws427h8.js} +1 -1
  5. package/dist/App.4ehxpt48.js +4 -0
  6. package/dist/App.6q6bar8b.js +4 -0
  7. package/dist/{App.wnap3h7r.js → App.ca1rz1ph.js} +1 -1
  8. package/dist/{App.e54ynjf2.js → App.ensa6z0r.js} +1 -1
  9. package/dist/{App.sb2fg71h.js → App.f8g7tych.js} +1 -1
  10. package/dist/App.kh7d2xj3.js +267 -0
  11. package/dist/App.mvtqv6qc.js +20 -0
  12. package/dist/{App.2prdcxgq.js → App.ncgc9cxy.js} +1 -1
  13. package/dist/{App.r2c5nw36.js → App.p0fb1pds.js} +1 -1
  14. package/dist/{App.6ftxk387.js → App.pmaq48sj.js} +1 -1
  15. package/dist/{App.40azyqz6.js → App.yv87t9m5.js} +1 -1
  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.65dcf4vw.js → TasksPage.sjv0khtv.js} +1 -1
  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 +1 -1
  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/providers.ts +44 -0
  31. package/src/routes/api/agent-utils.ts +64 -9
  32. package/src/routes/api/agents.ts +41 -13
  33. package/src/routes/api/integrations.ts +16 -6
  34. package/src/routes/api/mcp.ts +7 -0
  35. package/src/web/components/activity/ActivityPage.tsx +3 -3
  36. package/src/web/components/agents/AgentCard.tsx +5 -5
  37. package/src/web/components/agents/AgentPanel.tsx +81 -20
  38. package/src/web/components/mcp/McpPage.tsx +16 -5
  39. package/src/web/components/settings/SettingsPage.tsx +279 -30
  40. package/src/web/context/ProjectContext.tsx +5 -0
  41. package/src/web/context/TelemetryContext.tsx +14 -0
  42. package/src/web/types.ts +20 -2
  43. package/dist/ActivityPage.cycn14ck.js +0 -3
  44. package/dist/App.0wwyytz2.js +0 -4
  45. package/dist/App.fq11mvc7.js +0 -4
  46. package/dist/App.h6k4j1w9.js +0 -4
  47. package/dist/App.jq5tmjws.js +0 -267
  48. package/dist/App.k377qek6.js +0 -20
  49. package/dist/ConnectionsPage.6fyhqfhz.js +0 -3
  50. package/dist/McpPage.hk2qt1qt.js +0 -3
  51. package/dist/SettingsPage.gwpx9v7v.js +0 -3
  52. package/dist/SkillsPage.j5zech2z.js +0 -3
  53. package/dist/TelemetryPage.07xrbd7k.js +0 -3
  54. package/dist/TestsPage.q6zfephf.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
+ }
@@ -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.0wwyytz2.js";import"./App.2prdcxgq.js";import"./App.k377qek6.js";export{a as ActivityPage};
2
-
3
- //# debugId=A836402CCF3683F564756E2164756E21
@@ -1,4 +0,0 @@
1
- import{H as u,I as c,J as E}from"./App.2prdcxgq.js";import{S as A,V as a,W as h,Y as V,Z as x,_ as D,ca as T,fa as j}from"./App.k377qek6.js";var X=A(a(),1);var q=A(h(),1);function Qq({agents:B,loading:Z,onNavigate:L}){let{authFetch:G}=T(),{currentProjectId:J}=j(),{events:$,statusChangeCounter:K}=V(),[H,Q]=X.useState(null),[W,U]=X.useState([]),[O,P]=X.useState([]),I=X.useRef(null),{events:R}=x({category:"TASK"}),N=X.useMemo(()=>{if(J===null)return B;if(J==="unassigned")return B.filter((z)=>!z.projectId);return B.filter((z)=>z.projectId===J)},[B,J]),b=X.useMemo(()=>{return[...N].sort((z,Y)=>{if(z.status==="running"&&Y.status!=="running")return-1;if(Y.status==="running"&&z.status!=="running")return 1;return z.name.localeCompare(Y.name)})},[N]),g=X.useMemo(()=>N.filter((z)=>z.status==="running").length,[N]),f=X.useMemo(()=>new Set(N.map((z)=>z.id)),[N]),d=X.useMemo(()=>{let z=new Map;return N.forEach((Y)=>z.set(Y.id,Y.name)),z},[N]),M=X.useCallback(async()=>{let z=J?`&project_id=${encodeURIComponent(J)}`:"",[Y,C]=await Promise.all([G(`/api/tasks?status=all${z}`).catch(()=>null),G(`/api/telemetry/events?type=thread_activity&limit=50${z}`).catch(()=>null)]);if(Y?.ok){let _=(await Y.json()).tasks||[];_.sort((F,w)=>{let S=F.status==="running"?0:F.status==="pending"?1:F.status==="completed"?2:3,v=w.status==="running"?0:w.status==="pending"?1:w.status==="completed"?2:3;if(S!==v)return S-v;if(S<=1){let r=F.next_run||F.execute_at?new Date(F.next_run||F.execute_at).getTime():1/0,n=w.next_run||w.execute_at?new Date(w.next_run||w.execute_at).getTime():1/0;return r-n}let o=F.completed_at||F.executed_at||F.created_at,i=w.completed_at||w.executed_at||w.created_at;return new Date(i).getTime()-new Date(o).getTime()}),U(_)}if(C?.ok){let p=await C.json();P(p.events||[])}},[G,J]);X.useEffect(()=>{M()},[M,K]),X.useEffect(()=>{if(!R.length)return;let z=R[0];if(!z||z.id===I.current)return;let Y=z.type;if(Y==="task_created"||Y==="task_updated"||Y==="task_deleted")I.current=z.id,M()},[R,M]);let y=X.useMemo(()=>{let z=$.filter((_)=>_.type==="thread_activity"),Y=new Set(z.map((_)=>_.id)),C=[...z];for(let _ of O)if(!Y.has(_.id))C.push(_),Y.add(_.id);let p=C.filter((_)=>f.has(_.agent_id));return p.sort((_,F)=>new Date(F.timestamp).getTime()-new Date(_.timestamp).getTime()),p.slice(0,50)},[$,O,f]);if(Z)return q.jsxDEV("div",{className:"flex-1 flex items-center justify-center text-[#666]",children:"Loading..."},void 0,!1,void 0,this);return q.jsxDEV("div",{className:"flex-1 flex flex-col overflow-hidden",children:[q.jsxDEV("div",{className:"px-6 pt-6 pb-4 shrink-0",children:q.jsxDEV("div",{className:"flex items-center justify-between",children:[q.jsxDEV("h1",{className:"text-xl font-semibold",children:"Activity"},void 0,!1,void 0,this),q.jsxDEV("span",{className:"text-sm text-[#666]",children:[g," of ",N.length," agents running"]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)},void 0,!1,void 0,this),q.jsxDEV("div",{className:"flex-1 flex min-h-0 overflow-hidden",children:[q.jsxDEV("div",{className:"flex-[2] flex flex-col overflow-hidden border-r border-[#1a1a1a]",children:[q.jsxDEV("div",{className:"px-4 py-2.5 border-b border-[#1a1a1a] shrink-0",children:q.jsxDEV("h3",{className:"text-xs font-semibold text-[#666] uppercase tracking-wider",children:"Agents"},void 0,!1,void 0,this)},void 0,!1,void 0,this),q.jsxDEV("div",{className:"flex-1 overflow-auto px-3 py-2",children:[b.length===0?q.jsxDEV("p",{className:"text-sm text-[#555] px-2 py-4 text-center",children:"No agents found"},void 0,!1,void 0,this):q.jsxDEV("div",{className:"space-y-1",children:b.map((z)=>q.jsxDEV(s,{agent:z,selected:H===z.id,onSelect:()=>Q(H===z.id?null:z.id)},z.id,!1,void 0,this))},void 0,!1,void 0,this),H&&q.jsxDEV(t,{agent:N.find((z)=>z.id===H)||null},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex-[3] flex flex-col min-h-0 overflow-hidden border-r border-[#1a1a1a]",children:[q.jsxDEV("div",{className:"px-4 py-2.5 border-b border-[#1a1a1a] flex items-center justify-between shrink-0",children:[q.jsxDEV("h3",{className:"text-xs font-semibold text-[#666] uppercase tracking-wider",children:"Activity Feed"},void 0,!1,void 0,this),q.jsxDEV("span",{className:"text-xs text-[#555]",children:y.length},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex-1 overflow-auto",children:y.length===0?q.jsxDEV("div",{className:"p-6 text-center text-[#555] text-sm",children:"No activity yet. Agent activity will appear here in real-time."},void 0,!1,void 0,this):q.jsxDEV("div",{className:"divide-y divide-[#1a1a1a]",children:y.map((z)=>q.jsxDEV("div",{className:"px-4 py-2.5 hover:bg-[#111]/50 transition",children:[q.jsxDEV("p",{className:"text-sm truncate",children:z.data?.activity||"Working..."},void 0,!1,void 0,this),q.jsxDEV("div",{className:"flex items-center gap-2 text-[10px] text-[#555] mt-0.5",children:[q.jsxDEV("span",{className:"text-[#666]",children:d.get(z.agent_id)||z.agent_id},void 0,!1,void 0,this),q.jsxDEV("span",{className:"text-[#444]",children:"·"},void 0,!1,void 0,this),q.jsxDEV("span",{children:zq(z.timestamp)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},z.id,!0,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex-[3] flex flex-col overflow-hidden",children:[q.jsxDEV("div",{className:"px-4 py-2.5 border-b border-[#1a1a1a] flex items-center justify-between shrink-0",children:[q.jsxDEV("h3",{className:"text-xs font-semibold text-[#666] uppercase tracking-wider",children:"Tasks"},void 0,!1,void 0,this),L&&q.jsxDEV("button",{onClick:()=>L("tasks"),className:"text-xs text-[#3b82f6] hover:text-[#60a5fa]",children:"View All"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex-1 overflow-auto px-3 py-3",children:W.length===0?q.jsxDEV("p",{className:"text-sm text-[#555] px-2 py-4 text-center",children:"No tasks yet"},void 0,!1,void 0,this):q.jsxDEV("div",{className:"space-y-2.5",children:W.map((z)=>q.jsxDEV(e,{task:z},`${z.agentId}-${z.id}`,!1,void 0,this))},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function s({agent:B,selected:Z,onSelect:L}){let{isActive:G,type:J}=D(B.id),$=B.status==="running";return q.jsxDEV("button",{onClick:L,className:`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition ${Z?"bg-[#f97316]/10 border border-[#f97316]/30":"hover:bg-[#1a1a1a] border border-transparent"}`,children:[q.jsxDEV("span",{className:`w-2.5 h-2.5 rounded-full shrink-0 ${$&&G?"bg-green-400 animate-pulse":$?"bg-[#3b82f6]":"bg-[#444]"}`},void 0,!1,void 0,this),q.jsxDEV("div",{className:"flex-1 min-w-0",children:[q.jsxDEV("div",{className:"flex items-center gap-2",children:[q.jsxDEV("span",{className:`text-sm font-medium truncate ${$?"":"text-[#666]"}`,children:B.name},void 0,!1,void 0,this),q.jsxDEV("span",{className:"text-[10px] text-[#555] shrink-0",children:B.provider},void 0,!1,void 0,this)]},void 0,!0,void 0,this),G&&J?q.jsxDEV("p",{className:"text-[11px] text-green-400 truncate",children:J},void 0,!1,void 0,this):q.jsxDEV("p",{className:`text-[11px] ${$?"text-[#555]":"text-[#444]"}`,children:$?"idle":"stopped"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function t({agent:B}){let{authFetch:Z}=T(),[L,G]=X.useState(""),[J,$]=X.useState(!1),[K,H]=X.useState(null);if(X.useEffect(()=>{G(""),H(null)},[B?.id]),!B)return null;let Q=B.status==="running",W=async()=>{if(!L.trim()||J)return;if(!Q){H("Agent is not running"),setTimeout(()=>H(null),3000);return}$(!0);try{let U=await Z(`/api/agents/${B.id}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:L,agent_id:B.id})});if(U.ok)H("Sent"),G("");else{let O=await U.json().catch(()=>({}));H(O.error||"Failed")}}catch{H("Failed to send")}finally{$(!1),setTimeout(()=>H(null),3000)}};return q.jsxDEV("div",{className:"mt-2 bg-[#0a0a0a] border border-[#1a1a1a] rounded-lg p-2.5",children:[q.jsxDEV("div",{className:"flex items-center justify-between mb-1.5",children:[q.jsxDEV("span",{className:"text-[10px] text-[#666]",children:["Send to ",q.jsxDEV("span",{className:"text-[#888]",children:B.name},void 0,!1,void 0,this)]},void 0,!0,void 0,this),K&&q.jsxDEV("span",{className:`text-[10px] px-1.5 py-0.5 rounded ${K==="Sent"?"bg-green-500/10 text-green-400":"bg-red-500/10 text-red-400"}`,children:K},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex gap-1.5",children:[q.jsxDEV("input",{type:"text",value:L,onChange:(U)=>G(U.target.value),onKeyDown:(U)=>U.key==="Enter"&&W(),placeholder:Q?"Command...":"Not running",disabled:J||!Q,autoFocus:!0,className:"flex-1 bg-[#111] border border-[#1a1a1a] rounded px-2 py-1.5 text-xs focus:outline-none focus:border-[#f97316] placeholder-[#444] disabled:opacity-50"},void 0,!1,void 0,this),q.jsxDEV("button",{onClick:W,disabled:J||!L.trim()||!Q,className:"px-2.5 py-1.5 bg-[#f97316]/20 text-[#f97316] rounded text-xs font-medium hover:bg-[#f97316]/30 transition disabled:opacity-30",children:J?"...":"Send"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}var l={pending:"bg-yellow-500/20 text-yellow-400",running:"bg-blue-500/20 text-blue-400",completed:"bg-green-500/20 text-green-400",failed:"bg-red-500/20 text-red-400",cancelled:"bg-gray-500/20 text-gray-400"},m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];function e({task:B}){return q.jsxDEV("div",{className:"bg-[#111] border border-[#1a1a1a] rounded-lg p-3 hover:border-[#333] transition",children:[q.jsxDEV("div",{className:"flex items-start justify-between mb-1.5",children:[q.jsxDEV("div",{className:"flex-1 min-w-0",children:[q.jsxDEV("h4",{className:"text-sm font-medium truncate",children:B.title},void 0,!1,void 0,this),q.jsxDEV("p",{className:"text-xs text-[#666]",children:B.agentName},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("span",{className:`px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ml-2 ${l[B.status]||l.pending}`,children:B.status},void 0,!1,void 0,this)]},void 0,!0,void 0,this),q.jsxDEV("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-[#555]",children:[q.jsxDEV("span",{className:"flex items-center gap-1",children:[B.type==="recurring"?q.jsxDEV(u,{className:"w-3 h-3"},void 0,!1,void 0,this):B.execute_at?q.jsxDEV(c,{className:"w-3 h-3"},void 0,!1,void 0,this):q.jsxDEV(E,{className:"w-3 h-3"},void 0,!1,void 0,this),B.type==="recurring"&&B.recurrence?qq(B.recurrence):B.type]},void 0,!0,void 0,this),B.next_run&&q.jsxDEV("span",{className:"text-[#f97316]",children:k(B.next_run)},void 0,!1,void 0,this),!B.next_run&&B.execute_at&&q.jsxDEV("span",{className:"text-[#f97316]",children:k(B.execute_at)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function qq(B){try{let Z=B.trim().split(/\s+/);if(Z.length!==5)return B;let[L,G,J,$,K]=Z;if(L.startsWith("*/")&&G==="*"&&J==="*"&&$==="*"&&K==="*"){let Q=parseInt(L.slice(2));return Q===1?"Every min":`Every ${Q}min`}if(L!=="*"&&!L.includes("/")&&G==="*"&&J==="*"&&$==="*"&&K==="*")return"Hourly";if(G.startsWith("*/")&&J==="*"&&$==="*"&&K==="*"){let Q=parseInt(G.slice(2));return Q===1?"Hourly":`Every ${Q}h`}let H=(Q,W)=>{let U=parseInt(Q),O=parseInt(W);if(isNaN(U))return"";let P=U>=12?"PM":"AM";return`${U===0?12:U>12?U-12:U}:${O.toString().padStart(2,"0")} ${P}`};if(G!=="*"&&!G.includes("/")&&J==="*"&&$==="*"){let Q=H(G,L);if(K==="*")return`Daily ${Q}`;let W=K.split(",").map((U)=>m[parseInt(U.trim())]||U);if(W.length===1)return`${W[0]} ${Q}`;return`${W.join(", ")} ${Q}`}return B}catch{return B}}function k(B){let Z=new Date(B),L=new Date,G=Z.getTime()-L.getTime(),J=G>0,$=Math.abs(G),K=Math.floor($/60000),H=Math.floor($/3600000),Q=Z.toLocaleTimeString([],{hour:"numeric",minute:"2-digit"}),W=Z.toDateString()===L.toDateString(),U=new Date(L);U.setDate(U.getDate()+1);let O=Z.toDateString()===U.toDateString();if(W){if(K<1)return"now";if(K<60)return J?`in ${K}m`:`${K}m ago`;return J?`in ${H}h (${Q})`:`${H}h ago`}if(O)return`Tomorrow ${Q}`;return`${m[Z.getDay()]} ${Q}`}function zq(B){let Z=Math.floor((Date.now()-new Date(B).getTime())/1000);if(Z<5)return"just now";if(Z<60)return`${Z}s ago`;let L=Math.floor(Z/60);if(L<60)return`${L}m ago`;let G=Math.floor(L/60);if(G<24)return`${G}h ago`;return`${Math.floor(G/24)}d ago`}
2
- export{Qq as d};
3
-
4
- //# debugId=FD9DB9A2941A85B564756E2164756E21