@synergenius/flow-weaver-pack-weaver 0.9.200 → 0.9.201
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/ai-chat-provider.js +5 -5
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/acceptance-merge.d.ts +21 -0
- package/dist/bot/acceptance-merge.d.ts.map +1 -0
- package/dist/bot/acceptance-merge.js +46 -0
- package/dist/bot/acceptance-merge.js.map +1 -0
- package/dist/bot/ai-client.d.ts +14 -2
- package/dist/bot/ai-client.d.ts.map +1 -1
- package/dist/bot/ai-client.js +71 -24
- package/dist/bot/ai-client.js.map +1 -1
- package/dist/bot/assistant-tools.js +3 -3
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/audit-logger.d.ts.map +1 -1
- package/dist/bot/audit-logger.js +34 -14
- package/dist/bot/audit-logger.js.map +1 -1
- package/dist/bot/audit-trail.d.ts +67 -0
- package/dist/bot/audit-trail.d.ts.map +1 -0
- package/dist/bot/audit-trail.js +153 -0
- package/dist/bot/audit-trail.js.map +1 -0
- package/dist/bot/behavior-defaults.d.ts +1 -1
- package/dist/bot/behavior-defaults.d.ts.map +1 -1
- package/dist/bot/behavior-defaults.js +7 -3
- package/dist/bot/behavior-defaults.js.map +1 -1
- package/dist/bot/capability-registry.d.ts +9 -0
- package/dist/bot/capability-registry.d.ts.map +1 -1
- package/dist/bot/capability-registry.js +81 -27
- package/dist/bot/capability-registry.js.map +1 -1
- package/dist/bot/capability-types.d.ts +10 -0
- package/dist/bot/capability-types.d.ts.map +1 -1
- package/dist/bot/cli-provider.d.ts.map +1 -1
- package/dist/bot/cli-provider.js +8 -7
- package/dist/bot/cli-provider.js.map +1 -1
- package/dist/bot/preflight.d.ts +48 -0
- package/dist/bot/preflight.d.ts.map +1 -0
- package/dist/bot/preflight.js +247 -0
- package/dist/bot/preflight.js.map +1 -0
- package/dist/bot/provider-shim.d.ts +74 -0
- package/dist/bot/provider-shim.d.ts.map +1 -0
- package/dist/bot/provider-shim.js +176 -0
- package/dist/bot/provider-shim.js.map +1 -0
- package/dist/bot/runner.d.ts +2 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +60 -17
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +72 -115
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +2 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +92 -20
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-create-handler.d.ts +37 -0
- package/dist/bot/task-create-handler.d.ts.map +1 -0
- package/dist/bot/task-create-handler.js +124 -0
- package/dist/bot/task-create-handler.js.map +1 -0
- package/dist/bot/task-store.d.ts +1 -0
- package/dist/bot/task-store.d.ts.map +1 -1
- package/dist/bot/task-store.js +61 -0
- package/dist/bot/task-store.js.map +1 -1
- package/dist/bot/types.d.ts +1 -1
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/bot/weaver-tools.d.ts.map +1 -1
- package/dist/bot/weaver-tools.js +7 -39
- package/dist/bot/weaver-tools.js.map +1 -1
- package/dist/node-types/agent-execute.d.ts +25 -8
- package/dist/node-types/agent-execute.d.ts.map +1 -1
- package/dist/node-types/agent-execute.js +89 -23
- package/dist/node-types/agent-execute.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +24 -3
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/plan-task.d.ts +8 -17
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +217 -256
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/node-types/review-result.js +8 -6
- package/dist/node-types/review-result.js.map +1 -1
- package/dist/palindrome.d.ts +9 -0
- package/dist/palindrome.d.ts.map +1 -0
- package/dist/palindrome.js +14 -0
- package/dist/palindrome.js.map +1 -0
- package/dist/ui/approval-card.js +91 -82
- package/dist/ui/bot-activity.js +73 -56
- package/dist/ui/bot-config.js +48 -31
- package/dist/ui/bot-dashboard.js +52 -36
- package/dist/ui/bot-panel.js +230 -228
- package/dist/ui/bot-slot-card.js +100 -90
- package/dist/ui/bot-status.js +37 -15
- package/dist/ui/budget-bar.js +57 -31
- package/dist/ui/capability-editor.js +447 -378
- package/dist/ui/chat-task-result.js +78 -71
- package/dist/ui/decision-log.js +68 -81
- package/dist/ui/genesis-block.js +86 -95
- package/dist/ui/instance-stream-view.js +722 -0
- package/dist/ui/profile-card.js +96 -221
- package/dist/ui/profile-editor.js +532 -575
- package/dist/ui/settings-section.js +41 -45
- package/dist/ui/swarm-controls.js +212 -135
- package/dist/ui/swarm-dashboard.js +3992 -2715
- package/dist/ui/task-detail-view.js +415 -521
- package/dist/ui/task-editor.js +339 -390
- package/dist/ui/task-pool-list.js +60 -55
- package/dist/workflows/src/palindrome.d.ts +11 -0
- package/dist/workflows/src/palindrome.d.ts.map +1 -0
- package/dist/workflows/src/palindrome.js +16 -0
- package/dist/workflows/src/palindrome.js.map +1 -0
- package/dist/workflows/tests/palindrome.test.d.ts +2 -0
- package/dist/workflows/tests/palindrome.test.d.ts.map +1 -0
- package/dist/workflows/tests/palindrome.test.js +41 -0
- package/dist/workflows/tests/palindrome.test.js.map +1 -0
- package/dist/workflows/weaver-bot-batch.js +1 -1
- package/dist/workflows/weaver-bot-batch.js.map +1 -1
- package/dist/workflows/weaver-bot.js +1 -1
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +1 -1
- package/package.json +8 -2
- package/src/ai-chat-provider.ts +5 -5
- package/src/bot/acceptance-merge.ts +62 -0
- package/src/bot/ai-client.ts +77 -21
- package/src/bot/assistant-tools.ts +3 -3
- package/src/bot/audit-logger.ts +42 -14
- package/src/bot/audit-trail.ts +211 -0
- package/src/bot/behavior-defaults.ts +7 -2
- package/src/bot/capability-registry.ts +84 -28
- package/src/bot/capability-types.ts +11 -0
- package/src/bot/cli-provider.ts +8 -7
- package/src/bot/preflight.ts +285 -0
- package/src/bot/provider-shim.ts +218 -0
- package/src/bot/runner.ts +68 -20
- package/src/bot/step-executor.ts +69 -127
- package/src/bot/swarm-controller.ts +94 -20
- package/src/bot/task-create-handler.ts +164 -0
- package/src/bot/task-store.ts +76 -0
- package/src/bot/types.ts +4 -1
- package/src/bot/weaver-tools.ts +7 -45
- package/src/node-types/agent-execute.ts +102 -16
- package/src/node-types/bot-report.ts +24 -3
- package/src/node-types/plan-task.ts +238 -280
- package/src/node-types/review-result.ts +8 -6
- package/src/palindrome.ts +14 -0
- package/src/ui/approval-card.tsx +78 -62
- package/src/ui/bot-activity.tsx +12 -10
- package/src/ui/bot-config.tsx +12 -10
- package/src/ui/bot-dashboard.tsx +13 -11
- package/src/ui/bot-panel.tsx +189 -171
- package/src/ui/bot-slot-card.tsx +125 -70
- package/src/ui/bot-status.tsx +4 -4
- package/src/ui/budget-bar.tsx +86 -25
- package/src/ui/capability-editor.tsx +392 -257
- package/src/ui/chat-task-result.tsx +81 -78
- package/src/ui/decision-log.tsx +76 -73
- package/src/ui/genesis-block.tsx +91 -61
- package/src/ui/instance-stream-view.tsx +861 -0
- package/src/ui/profile-card.tsx +195 -168
- package/src/ui/profile-editor.tsx +453 -370
- package/src/ui/settings-section.tsx +46 -39
- package/src/ui/swarm-controls.tsx +252 -123
- package/src/ui/swarm-dashboard.tsx +999 -466
- package/src/ui/task-detail-view.tsx +485 -428
- package/src/ui/task-editor.tsx +329 -271
- package/src/ui/task-pool-list.tsx +68 -62
- package/src/workflows/src/palindrome.ts +16 -0
- package/src/workflows/tests/palindrome.test.ts +49 -0
- package/src/workflows/weaver-bot-batch.ts +1 -1
- package/src/workflows/weaver-bot.ts +1 -1
- package/dist/ui/bot-constants.d.ts +0 -14
- package/dist/ui/bot-constants.d.ts.map +0 -1
- package/dist/ui/bot-constants.js +0 -189
- package/dist/ui/bot-constants.js.map +0 -1
- package/dist/ui/steer-api.d.ts +0 -7
- package/dist/ui/steer-api.d.ts.map +0 -1
- package/dist/ui/steer-api.js +0 -11
- package/dist/ui/steer-api.js.map +0 -1
- package/dist/ui/trace-to-timeline.d.ts +0 -91
- package/dist/ui/trace-to-timeline.d.ts.map +0 -1
- package/dist/ui/trace-to-timeline.js +0 -116
- package/dist/ui/trace-to-timeline.js.map +0 -1
- package/dist/ui/use-stream-timeline.d.ts +0 -50
- package/dist/ui/use-stream-timeline.d.ts.map +0 -1
- package/dist/ui/use-stream-timeline.js +0 -245
- package/dist/ui/use-stream-timeline.js.map +0 -1
|
@@ -5,35 +5,37 @@
|
|
|
5
5
|
* Composes: SwarmControls, BudgetBar, BotSlotCard, TaskPoolList,
|
|
6
6
|
* TaskEditor, and TaskDetailView into a unified swarm dashboard.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Three-panel navigation:
|
|
9
9
|
* - Default: full swarm dashboard (controls + budget + bots + task pool + create form)
|
|
10
10
|
* - On task click: TaskDetailView (with back button returning to dashboard)
|
|
11
|
+
* - On instance click: InstanceStreamView (live AI stream for a bot instance)
|
|
11
12
|
*
|
|
12
13
|
* Data loading:
|
|
13
14
|
* - Polls fw_weaver_swarm_status every 3s for swarm state
|
|
14
15
|
* - Polls fw_weaver_task_list every 5s for task pool
|
|
15
16
|
* - Both called immediately on mount
|
|
16
17
|
*
|
|
17
|
-
* Pattern:
|
|
18
|
-
* React.createElement throughout, module.exports at end.
|
|
18
|
+
* Pattern: ESM imports, JSX rendering.
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const {
|
|
20
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
21
|
+
import {
|
|
23
22
|
Flex, ScrollArea, EmptyState, Typography, StatusIcon, Icon, Chip, Card, Tabs, SectionTitle, Button,
|
|
24
|
-
Input, IconButton, IconPicker, ColorPicker,
|
|
25
|
-
|
|
23
|
+
Input, Slider, IconButton, IconPicker, ColorPicker, Section, Table, KeyValueRow, Field,
|
|
24
|
+
SparklineChart, BarChart, MetricCard, KanbanBoard, TaskCard, toast, usePackWorkspace,
|
|
25
|
+
} from '@fw/plugin-ui-kit';
|
|
26
26
|
|
|
27
27
|
// Local pack-specific components (bundled by esbuild)
|
|
28
28
|
import SwarmControls from './swarm-controls';
|
|
29
29
|
import BudgetBar from './budget-bar';
|
|
30
30
|
import BotSlotCard from './bot-slot-card';
|
|
31
|
-
import TaskPoolList from './task-pool-list';
|
|
31
|
+
import TaskPoolList from './task-pool-list.js';
|
|
32
32
|
import TaskDetailView from './task-detail-view';
|
|
33
33
|
import TaskEditor from './task-editor';
|
|
34
34
|
import ProfileCard from './profile-card';
|
|
35
35
|
import ProfileEditor from './profile-editor';
|
|
36
36
|
import DecisionLog from './decision-log';
|
|
37
|
+
import CapabilityEditor from './capability-editor';
|
|
38
|
+
import InstanceStreamView from './instance-stream-view';
|
|
37
39
|
import { ICON_CATALOG, BOT_COLORS } from './bot-constants';
|
|
38
40
|
|
|
39
41
|
// ---------------------------------------------------------------------------
|
|
@@ -118,12 +120,20 @@ function SwarmDashboard() {
|
|
|
118
120
|
|
|
119
121
|
// --- Navigation state ---
|
|
120
122
|
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
|
123
|
+
const [selectedInstance, setSelectedInstance] = useState<InstanceInfo | null>(null);
|
|
121
124
|
const [activeTab, setActiveTab] = useState('tasks');
|
|
122
125
|
const [editingBotId, setEditingBotId] = useState<string | null>(null);
|
|
123
126
|
const [profileEditorMode, setProfileEditorMode] = useState<'create' | 'edit' | null>(null);
|
|
124
127
|
const [editingProfileId, setEditingProfileId] = useState<string | null>(null);
|
|
128
|
+
const [taskView, setTaskView] = useState<'list' | 'board'>('list');
|
|
125
129
|
const [taskEditorMode, setTaskEditorMode] = useState<'create' | 'edit' | null>(null);
|
|
126
130
|
const [editingTaskId, setEditingTaskId] = useState<string | null>(null);
|
|
131
|
+
const [hintText, setHintText] = useState('');
|
|
132
|
+
const [hintSending, setHintSending] = useState(false);
|
|
133
|
+
const [costData, setCostData] = useState<Record<string, unknown> | null>(null);
|
|
134
|
+
const [localConcurrency, setLocalConcurrency] = useState<number | null>(null);
|
|
135
|
+
const [runHistory, setRunHistory] = useState<Array<Record<string, unknown>>>([]);
|
|
136
|
+
const concurrencyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
127
137
|
|
|
128
138
|
// --- Data state ---
|
|
129
139
|
const [swarmStatus, setSwarmStatus] = useState<SwarmStatus | null>(null);
|
|
@@ -214,10 +224,28 @@ function SwarmDashboard() {
|
|
|
214
224
|
} catch { /* non-fatal */ }
|
|
215
225
|
}, [callTool]);
|
|
216
226
|
|
|
227
|
+
const fetchCosts = useCallback(async () => {
|
|
228
|
+
try {
|
|
229
|
+
const raw = await callTool('fw_weaver_costs', {});
|
|
230
|
+
if (!mountedRef.current) return;
|
|
231
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
232
|
+
if (data && typeof data === 'object') setCostData(data as Record<string, unknown>);
|
|
233
|
+
} catch { /* non-fatal */ }
|
|
234
|
+
}, [callTool]);
|
|
235
|
+
|
|
236
|
+
const fetchRunHistory = useCallback(async () => {
|
|
237
|
+
try {
|
|
238
|
+
const raw = await callTool('fw_weaver_history', { limit: 30 });
|
|
239
|
+
if (!mountedRef.current) return;
|
|
240
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
241
|
+
if (Array.isArray(data)) setRunHistory(data);
|
|
242
|
+
} catch { /* non-fatal */ }
|
|
243
|
+
}, [callTool]);
|
|
244
|
+
|
|
217
245
|
const refreshAll = useCallback(async () => {
|
|
218
|
-
await Promise.all([fetchSwarmStatus(), fetchTaskList(), fetchBots(), fetchConfig(), fetchProfiles(), fetchOrchestratorStatus()]);
|
|
246
|
+
await Promise.all([fetchSwarmStatus(), fetchTaskList(), fetchBots(), fetchConfig(), fetchProfiles(), fetchOrchestratorStatus(), fetchCosts(), fetchRunHistory()]);
|
|
219
247
|
if (mountedRef.current) setLoading(false);
|
|
220
|
-
}, [fetchSwarmStatus, fetchTaskList, fetchBots, fetchConfig, fetchProfiles, fetchOrchestratorStatus]);
|
|
248
|
+
}, [fetchSwarmStatus, fetchTaskList, fetchBots, fetchConfig, fetchProfiles, fetchOrchestratorStatus, fetchCosts, fetchRunHistory]);
|
|
221
249
|
|
|
222
250
|
// Initial fetch on mount
|
|
223
251
|
useEffect(() => {
|
|
@@ -286,6 +314,15 @@ function SwarmDashboard() {
|
|
|
286
314
|
refreshAll();
|
|
287
315
|
}, [refreshAll]);
|
|
288
316
|
|
|
317
|
+
const handleInstanceClick = useCallback((inst: InstanceInfo) => {
|
|
318
|
+
setSelectedInstance(inst);
|
|
319
|
+
}, []);
|
|
320
|
+
|
|
321
|
+
const handleInstanceBack = useCallback(() => {
|
|
322
|
+
setSelectedInstance(null);
|
|
323
|
+
refreshAll();
|
|
324
|
+
}, [refreshAll]);
|
|
325
|
+
|
|
289
326
|
// --- Render: TaskEditor from detail view ---
|
|
290
327
|
|
|
291
328
|
const handleEditFromDetail = useCallback((tid: string) => {
|
|
@@ -294,23 +331,47 @@ function SwarmDashboard() {
|
|
|
294
331
|
}, []);
|
|
295
332
|
|
|
296
333
|
if (selectedTaskId && taskEditorMode === 'edit' && editingTaskId) {
|
|
297
|
-
return
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
334
|
+
return (
|
|
335
|
+
<TaskEditor
|
|
336
|
+
mode="edit"
|
|
337
|
+
taskId={editingTaskId}
|
|
338
|
+
onSave={() => { setTaskEditorMode(null); setEditingTaskId(null); }}
|
|
339
|
+
onCancel={() => { setTaskEditorMode(null); setEditingTaskId(null); }}
|
|
340
|
+
onDelete={() => { setTaskEditorMode(null); setEditingTaskId(null); setSelectedTaskId(null); fetchTaskList(); }}
|
|
341
|
+
/>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// --- Render: InstanceStreamView (when instance is selected) ---
|
|
346
|
+
|
|
347
|
+
if (selectedInstance) {
|
|
348
|
+
const instProfile = profiles.find((p: Record<string, unknown>) => p.id === selectedInstance.profileId);
|
|
349
|
+
const instBotId = instProfile?.botId as string | undefined;
|
|
350
|
+
const instBot = registeredBots.find((b) => b.id === instBotId);
|
|
351
|
+
return (
|
|
352
|
+
<InstanceStreamView
|
|
353
|
+
instanceId={selectedInstance.instanceId}
|
|
354
|
+
profileId={selectedInstance.profileId}
|
|
355
|
+
profileName={(instProfile?.name as string) || selectedInstance.profileId}
|
|
356
|
+
profileIcon={instBot?.icon}
|
|
357
|
+
profileColor={instBot?.color}
|
|
358
|
+
currentRunId={selectedInstance.currentRunId}
|
|
359
|
+
currentTaskId={selectedInstance.currentTaskId}
|
|
360
|
+
onBack={handleInstanceBack}
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
304
363
|
}
|
|
305
364
|
|
|
306
365
|
// --- Render: TaskDetailView (when task is selected) ---
|
|
307
366
|
|
|
308
367
|
if (selectedTaskId) {
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
368
|
+
return (
|
|
369
|
+
<TaskDetailView
|
|
370
|
+
taskId={selectedTaskId}
|
|
371
|
+
onBack={handleBack}
|
|
372
|
+
onEdit={handleEditFromDetail}
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
314
375
|
}
|
|
315
376
|
|
|
316
377
|
// --- Render: Swarm Dashboard (default view) ---
|
|
@@ -323,458 +384,930 @@ function SwarmDashboard() {
|
|
|
323
384
|
const hasBudget = !!(sessionBudget && (sessionBudget.limitTokens > 0 || sessionBudget.limitCost > 0));
|
|
324
385
|
const hasContent = hasSwarmInstances || hasRegisteredBots || tasks.length > 0 || swarmStatus !== null;
|
|
325
386
|
|
|
326
|
-
return
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
React.createElement(BudgetBar, {
|
|
348
|
-
label: 'Cost',
|
|
349
|
-
used: sessionBudget!.usedCost,
|
|
350
|
-
limit: sessionBudget!.limitCost,
|
|
351
|
-
unit: 'USD',
|
|
352
|
-
}),
|
|
353
|
-
),
|
|
354
|
-
|
|
355
|
-
// ── Bot slot cards (horizontal scrollable row) ──────────────
|
|
356
|
-
// ── Tabs ──────────────────────────────────────────────────────
|
|
357
|
-
React.createElement(Tabs, {
|
|
358
|
-
tabs: [
|
|
359
|
-
{ id: 'tasks', title: `Tasks (${tasks.length})` },
|
|
360
|
-
{ id: 'bots', title: hasSwarmInstances ? `Workers (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
|
|
361
|
-
{ id: 'profiles', title: `Profiles (${profiles.length})` },
|
|
362
|
-
{ id: 'config', title: 'Config' },
|
|
363
|
-
],
|
|
364
|
-
activeTabId: activeTab,
|
|
365
|
-
onSelectTab: (id: string) => setActiveTab(id),
|
|
366
|
-
size: 'sm',
|
|
367
|
-
}),
|
|
368
|
-
|
|
369
|
-
// ── Tab content ──────────────────────────────────────────────
|
|
370
|
-
React.createElement(Flex, {
|
|
371
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
372
|
-
style: { flex: 1, minHeight: 0, overflow: 'auto' },
|
|
373
|
-
},
|
|
374
|
-
|
|
375
|
-
// Tasks tab — when TaskEditor is open, render it INSTEAD of the list
|
|
376
|
-
activeTab === 'tasks' && taskEditorMode != null && React.createElement(TaskEditor, {
|
|
377
|
-
mode: taskEditorMode,
|
|
378
|
-
taskId: editingTaskId ?? undefined,
|
|
379
|
-
onSave: () => { setTaskEditorMode(null); setEditingTaskId(null); fetchTaskList(); },
|
|
380
|
-
onCancel: () => { setTaskEditorMode(null); setEditingTaskId(null); },
|
|
381
|
-
onDelete: taskEditorMode === 'edit'
|
|
382
|
-
? () => { setTaskEditorMode(null); setEditingTaskId(null); fetchTaskList(); }
|
|
383
|
-
: undefined,
|
|
384
|
-
}),
|
|
385
|
-
|
|
386
|
-
// Tasks tab — default list view
|
|
387
|
-
activeTab === 'tasks' && taskEditorMode == null && React.createElement(Flex, {
|
|
388
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
389
|
-
style: { flex: 1, minHeight: 0 },
|
|
390
|
-
},
|
|
391
|
-
!hasContent && !loading && React.createElement(EmptyState, {
|
|
392
|
-
icon: 'smartToy',
|
|
393
|
-
message: 'No tasks yet',
|
|
394
|
-
description: 'Create tasks below or ask the AI assistant to create them for you.',
|
|
395
|
-
}),
|
|
396
|
-
|
|
397
|
-
hasContent && React.createElement(TaskPoolList, {
|
|
398
|
-
tasks,
|
|
399
|
-
onTaskClick: handleTaskClick,
|
|
400
|
-
}),
|
|
401
|
-
|
|
402
|
-
// Task clear buttons (only shown when tasks exist)
|
|
403
|
-
tasks.length > 0 && React.createElement(Flex, {
|
|
404
|
-
variant: 'row-center-start-nowrap-8',
|
|
405
|
-
style: { padding: '4px 16px', flexShrink: 0 },
|
|
406
|
-
},
|
|
407
|
-
React.createElement(Button, {
|
|
408
|
-
size: 'xs', variant: 'outlined', color: 'secondary',
|
|
409
|
-
leftIcon: 'remove',
|
|
410
|
-
onClick: async () => {
|
|
411
|
-
const ok = await ctx.confirm('Clear all completed, failed, and cancelled tasks?', {
|
|
412
|
-
title: 'Clear Completed Tasks',
|
|
413
|
-
confirmLabel: 'Clear',
|
|
414
|
-
state: 'warning',
|
|
415
|
-
});
|
|
416
|
-
if (!ok) return;
|
|
417
|
-
try {
|
|
418
|
-
const raw = await callTool('fw_weaver_tasks_clear', { filter: 'completed' });
|
|
419
|
-
const data = parseToolResult(raw) as { cleared?: number } | null;
|
|
420
|
-
toast(`Cleared ${data?.cleared ?? 0} tasks`, { type: 'success' });
|
|
421
|
-
fetchTaskList();
|
|
422
|
-
} catch (err: unknown) {
|
|
423
|
-
toast(err instanceof Error ? err.message : 'Failed to clear tasks', { type: 'error' });
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
}, 'Clear Completed'),
|
|
427
|
-
React.createElement(Button, {
|
|
428
|
-
size: 'xs', variant: 'outlined', color: 'danger',
|
|
429
|
-
leftIcon: 'deleteForever',
|
|
430
|
-
onClick: async () => {
|
|
431
|
-
const ok = await ctx.confirm('This will stop the swarm and remove ALL tasks. This cannot be undone.', {
|
|
432
|
-
title: 'Clear All Tasks',
|
|
433
|
-
confirmLabel: 'Clear All',
|
|
434
|
-
state: 'danger',
|
|
435
|
-
});
|
|
436
|
-
if (!ok) return;
|
|
437
|
-
try {
|
|
438
|
-
const raw = await callTool('fw_weaver_tasks_clear', { filter: 'all' });
|
|
439
|
-
const data = parseToolResult(raw) as { cleared?: number } | null;
|
|
440
|
-
toast(`Cleared ${data?.cleared ?? 0} tasks`, { type: 'success' });
|
|
441
|
-
refreshAll();
|
|
442
|
-
} catch (err: unknown) {
|
|
443
|
-
toast(err instanceof Error ? err.message : 'Failed to clear tasks', { type: 'error' });
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
}, 'Clear All'),
|
|
447
|
-
),
|
|
448
|
-
|
|
449
|
-
// New Task button (bottom bar)
|
|
450
|
-
React.createElement(Flex, {
|
|
451
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
452
|
-
style: { flexShrink: 0, borderTop: '1px solid var(--color-border-default)', padding: '8px 16px' },
|
|
453
|
-
},
|
|
454
|
-
React.createElement(Button, {
|
|
455
|
-
size: 'xs', variant: 'clear', color: 'primary',
|
|
456
|
-
leftIcon: 'add',
|
|
457
|
-
onClick: () => setTaskEditorMode('create'),
|
|
458
|
-
}, 'New Task'),
|
|
459
|
-
),
|
|
460
|
-
),
|
|
461
|
-
|
|
462
|
-
// Bots tab
|
|
463
|
-
activeTab === 'bots' && React.createElement(Flex, {
|
|
464
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
465
|
-
style: { flex: 1, minHeight: 0 },
|
|
466
|
-
},
|
|
467
|
-
// When swarm is running: show swarm instances
|
|
468
|
-
hasSwarmInstances && React.createElement(Flex, {
|
|
469
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
470
|
-
},
|
|
471
|
-
React.createElement(Flex, {
|
|
472
|
-
variant: 'row-center-start-nowrap-8',
|
|
473
|
-
style: { padding: '8px 16px', borderBottom: '1px solid var(--color-border-default)' },
|
|
474
|
-
},
|
|
475
|
-
React.createElement(Typography, {
|
|
476
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
477
|
-
style: { width: '120px', flexShrink: 0 },
|
|
478
|
-
}, 'Worker'),
|
|
479
|
-
React.createElement(Typography, {
|
|
480
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
481
|
-
style: { width: '110px', flexShrink: 0 },
|
|
482
|
-
}, 'Bot'),
|
|
483
|
-
React.createElement(Typography, {
|
|
484
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
485
|
-
style: { width: '70px', flexShrink: 0 },
|
|
486
|
-
}, 'Status'),
|
|
487
|
-
React.createElement(Typography, {
|
|
488
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
489
|
-
style: { flex: 1, minWidth: 0 },
|
|
490
|
-
}, 'Task'),
|
|
491
|
-
React.createElement(Typography, {
|
|
492
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
493
|
-
style: { width: '50px', flexShrink: 0, textAlign: 'right' },
|
|
494
|
-
}, 'Tokens'),
|
|
495
|
-
React.createElement(Typography, {
|
|
496
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
497
|
-
style: { width: '50px', flexShrink: 0, textAlign: 'right' },
|
|
498
|
-
}, 'Cost'),
|
|
499
|
-
React.createElement(Typography, {
|
|
500
|
-
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
501
|
-
style: { width: '50px', flexShrink: 0 },
|
|
502
|
-
}, ''),
|
|
503
|
-
),
|
|
504
|
-
// Instance rows
|
|
505
|
-
...swarmInstanceEntries.map((inst: InstanceInfo) => {
|
|
506
|
-
// Look up profile → bot for this instance
|
|
507
|
-
const profile = profiles.find((p: Record<string, unknown>) => p.id === inst.profileId);
|
|
508
|
-
const botId = profile?.botId as string | undefined;
|
|
509
|
-
const bot = registeredBots.find((b) => b.id === botId);
|
|
510
|
-
return React.createElement(BotSlotCard, {
|
|
511
|
-
key: inst.instanceId,
|
|
512
|
-
bot: {
|
|
513
|
-
botId: inst.instanceId,
|
|
514
|
-
botName: inst.profileId ? `${inst.instanceId} (${inst.profileId})` : inst.instanceId,
|
|
515
|
-
status: inst.status,
|
|
516
|
-
currentTaskId: inst.currentTaskId,
|
|
517
|
-
currentRunId: inst.currentRunId,
|
|
518
|
-
startedAt: inst.startedAt,
|
|
519
|
-
tokensUsed: inst.tokensUsed,
|
|
520
|
-
cost: inst.cost,
|
|
521
|
-
},
|
|
522
|
-
profileName: (profile?.name as string) || inst.profileId,
|
|
523
|
-
botDisplayName: bot?.name,
|
|
524
|
-
botIcon: bot?.icon,
|
|
525
|
-
botColor: bot?.color,
|
|
526
|
-
currentTaskTitle: resolveTaskTitle(inst.currentTaskId, tasks),
|
|
527
|
-
onPause: (id: string) => handleSteerBot(id, 'pause'),
|
|
528
|
-
onResume: (id: string) => handleSteerBot(id, 'resume'),
|
|
529
|
-
onStop: (id: string) => handleSteerBot(id, 'cancel'),
|
|
530
|
-
});
|
|
531
|
-
}),
|
|
532
|
-
),
|
|
533
|
-
|
|
534
|
-
// When swarm is NOT running: show registered bots
|
|
535
|
-
!hasSwarmInstances && hasRegisteredBots && React.createElement(Flex, {
|
|
536
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
537
|
-
style: { padding: '8px 16px' },
|
|
538
|
-
},
|
|
539
|
-
...registeredBots.map((bot) => {
|
|
540
|
-
const isEditing = editingBotId === bot.id;
|
|
541
|
-
return React.createElement(Flex, {
|
|
542
|
-
key: bot.id,
|
|
543
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
544
|
-
style: { borderBottom: '1px solid var(--color-border-default)' },
|
|
545
|
-
},
|
|
546
|
-
// Bot row (clickable to toggle edit)
|
|
547
|
-
React.createElement(Flex, {
|
|
548
|
-
variant: 'row-center-start-nowrap-8',
|
|
549
|
-
style: { padding: '6px 0', cursor: 'pointer' },
|
|
550
|
-
onClick: () => setEditingBotId(isEditing ? null : bot.id),
|
|
551
|
-
},
|
|
552
|
-
React.createElement(Icon, {
|
|
553
|
-
name: bot.icon || 'smartToy', size: 16,
|
|
554
|
-
color: bot.color || 'color-text-medium',
|
|
555
|
-
}),
|
|
556
|
-
React.createElement(Flex, { variant: 'column-start-start-nowrap-1', style: { flex: 1, minWidth: 0 } },
|
|
557
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' }, bot.name),
|
|
558
|
-
bot.description && React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, bot.description),
|
|
559
|
-
),
|
|
560
|
-
React.createElement(Icon, { name: isEditing ? 'expandLess' : 'expandMore', size: 14, color: 'color-text-subtle' }),
|
|
561
|
-
),
|
|
562
|
-
|
|
563
|
-
// Edit section (icon + color pickers)
|
|
564
|
-
isEditing && React.createElement(Flex, {
|
|
565
|
-
variant: 'column-stretch-start-nowrap-12',
|
|
566
|
-
style: { padding: '8px 0 12px 24px' },
|
|
567
|
-
},
|
|
568
|
-
React.createElement(IconPicker, {
|
|
569
|
-
catalog: ICON_CATALOG,
|
|
570
|
-
value: bot.icon || 'smartToy',
|
|
571
|
-
onChange: (icon: string) => handleUpdateBot(bot.id, { icon }),
|
|
572
|
-
accentColor: bot.color || undefined,
|
|
573
|
-
defaultExpanded: true,
|
|
574
|
-
}),
|
|
575
|
-
React.createElement(ColorPicker, {
|
|
576
|
-
colors: BOT_COLORS,
|
|
577
|
-
value: bot.color || '',
|
|
578
|
-
onChange: (color: string) => handleUpdateBot(bot.id, { color }),
|
|
579
|
-
defaultExpanded: true,
|
|
580
|
-
}),
|
|
581
|
-
),
|
|
582
|
-
);
|
|
583
|
-
}),
|
|
584
|
-
),
|
|
585
|
-
|
|
586
|
-
// No bots at all
|
|
587
|
-
!hasSwarmInstances && !hasRegisteredBots && React.createElement(EmptyState, {
|
|
588
|
-
icon: 'smartToy',
|
|
589
|
-
message: 'No bots registered',
|
|
590
|
-
description: 'Register bots to start the swarm.',
|
|
591
|
-
}),
|
|
592
|
-
),
|
|
593
|
-
|
|
594
|
-
// Profiles tab — when ProfileEditor is open, render it INSTEAD of the list
|
|
595
|
-
activeTab === 'profiles' && profileEditorMode != null && React.createElement(ProfileEditor, {
|
|
596
|
-
mode: profileEditorMode,
|
|
597
|
-
profileId: editingProfileId ?? undefined,
|
|
598
|
-
bots: registeredBots.map((b: { id: string; name: string; icon?: string; color?: string }) => ({ id: b.id, name: b.name, icon: b.icon, color: b.color })),
|
|
599
|
-
onSave: () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); },
|
|
600
|
-
onCancel: () => { setProfileEditorMode(null); setEditingProfileId(null); },
|
|
601
|
-
onDelete: profileEditorMode === 'edit'
|
|
602
|
-
? () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); }
|
|
603
|
-
: undefined,
|
|
604
|
-
}),
|
|
605
|
-
|
|
606
|
-
// Profiles tab — default list view
|
|
607
|
-
activeTab === 'profiles' && profileEditorMode == null && React.createElement(Flex, {
|
|
608
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
609
|
-
style: { flex: 1, minHeight: 0 },
|
|
610
|
-
},
|
|
611
|
-
// ── Profile cards (scrollable) ──
|
|
612
|
-
React.createElement(Flex, {
|
|
613
|
-
variant: 'column-stretch-start-nowrap-8',
|
|
614
|
-
style: { flex: 1, minHeight: 0, overflow: 'auto', padding: '12px 16px' },
|
|
615
|
-
},
|
|
616
|
-
profiles.length > 0
|
|
617
|
-
? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
|
|
618
|
-
...profiles.map((p: Record<string, unknown>) => {
|
|
619
|
-
const activeCount = swarmInstanceEntries.filter(
|
|
620
|
-
(inst: InstanceInfo) => inst.profileId === p.id && inst.status === 'executing',
|
|
621
|
-
).length;
|
|
622
|
-
return React.createElement(ProfileCard, {
|
|
623
|
-
key: p.id as string,
|
|
624
|
-
profile: p as unknown,
|
|
625
|
-
activeInstances: activeCount,
|
|
626
|
-
onEdit: (id: string) => { setEditingProfileId(id); setProfileEditorMode('edit'); },
|
|
627
|
-
onDelete: async (id: string) => {
|
|
628
|
-
const ok = await ctx.confirm('Are you sure you want to delete this profile?', {
|
|
629
|
-
title: 'Delete Profile',
|
|
630
|
-
confirmLabel: 'Delete',
|
|
631
|
-
state: 'danger',
|
|
632
|
-
});
|
|
633
|
-
if (!ok) return;
|
|
634
|
-
try {
|
|
635
|
-
await callTool('fw_weaver_profile_delete', { id });
|
|
636
|
-
await fetchProfiles();
|
|
637
|
-
toast('Profile deleted', { type: 'success' });
|
|
638
|
-
} catch (err: unknown) {
|
|
639
|
-
toast(err instanceof Error ? err.message : 'Failed to delete profile', { type: 'error' });
|
|
640
|
-
}
|
|
641
|
-
},
|
|
642
|
-
});
|
|
643
|
-
}),
|
|
644
|
-
)
|
|
645
|
-
: React.createElement(EmptyState, {
|
|
646
|
-
icon: 'person',
|
|
647
|
-
message: 'No profiles',
|
|
648
|
-
description: 'Profiles define how bots behave. Start the swarm to create defaults, or create one below.',
|
|
649
|
-
}),
|
|
650
|
-
|
|
651
|
-
// ── Routing / Decision Log (inside scrollable area) ──
|
|
652
|
-
React.createElement(DecisionLog, {
|
|
653
|
-
decisions: orchestratorDecisions as unknown[],
|
|
654
|
-
}),
|
|
655
|
-
),
|
|
656
|
-
|
|
657
|
-
// ── New Profile button (bottom bar) ──
|
|
658
|
-
React.createElement(Flex, {
|
|
659
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
660
|
-
style: { flexShrink: 0, borderTop: '1px solid var(--color-border-default)', padding: '8px 16px' },
|
|
661
|
-
},
|
|
662
|
-
React.createElement(Button, {
|
|
663
|
-
size: 'xs', variant: 'clear', color: 'primary',
|
|
664
|
-
leftIcon: 'add',
|
|
665
|
-
onClick: () => setProfileEditorMode('create'),
|
|
666
|
-
}, 'New Profile'),
|
|
667
|
-
),
|
|
668
|
-
|
|
669
|
-
),
|
|
670
|
-
|
|
671
|
-
// Config tab
|
|
672
|
-
activeTab === 'config' && React.createElement(Flex, {
|
|
673
|
-
variant: 'column-stretch-start-nowrap-12',
|
|
674
|
-
style: { padding: '12px 16px' },
|
|
675
|
-
},
|
|
676
|
-
// ── Provider ──
|
|
677
|
-
React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
678
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Provider'),
|
|
679
|
-
React.createElement(Flex, { variant: 'row-center-start-nowrap-4' },
|
|
680
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
681
|
-
providers.find((p) => p.envVarsSet)?.name ?? 'None detected',
|
|
682
|
-
),
|
|
683
|
-
providers.find((p) => p.envVarsSet) && React.createElement(Chip, { label: 'active', size: 'small', color: 'color-status-info' }),
|
|
684
|
-
),
|
|
685
|
-
),
|
|
686
|
-
|
|
687
|
-
// Trust
|
|
688
|
-
insights?.trust && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
689
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Trust'),
|
|
690
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
691
|
-
`Phase ${(insights.trust as Record<string, unknown>).phase} · ${(insights.trust as Record<string, unknown>).score}/100`,
|
|
692
|
-
),
|
|
693
|
-
),
|
|
694
|
-
|
|
695
|
-
// Health
|
|
696
|
-
insights?.health && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
|
|
697
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Health'),
|
|
698
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
699
|
-
`${(insights.health as Record<string, unknown>).overall}/100`,
|
|
700
|
-
),
|
|
701
|
-
),
|
|
702
|
-
|
|
703
|
-
// Available providers
|
|
704
|
-
providers.length > 0 && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-4' },
|
|
705
|
-
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Available Providers'),
|
|
706
|
-
React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
|
|
707
|
-
...providers.map((p) =>
|
|
708
|
-
React.createElement(Chip, {
|
|
709
|
-
key: p.name,
|
|
710
|
-
label: p.name,
|
|
711
|
-
size: 'small',
|
|
712
|
-
color: p.envVarsSet ? 'color-status-positive' : 'color-brand-alt',
|
|
713
|
-
}),
|
|
714
|
-
),
|
|
715
|
-
),
|
|
716
|
-
),
|
|
717
|
-
|
|
718
|
-
// ── Reset section ──
|
|
719
|
-
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8', style: { marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--color-border-default)' } },
|
|
720
|
-
React.createElement(SectionTitle, { size: 'xs' }, 'Reset'),
|
|
721
|
-
React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
|
|
722
|
-
React.createElement(Button, {
|
|
723
|
-
size: 'xs', variant: 'outlined', color: 'secondary',
|
|
724
|
-
leftIcon: 'undo',
|
|
725
|
-
onClick: async () => {
|
|
726
|
-
const ok = await ctx.confirm('Reset Weaver configuration to defaults? Your .weaver.json will be deleted.', {
|
|
727
|
-
title: 'Reset Config',
|
|
728
|
-
confirmLabel: 'Reset',
|
|
729
|
-
state: 'warning',
|
|
730
|
-
});
|
|
731
|
-
if (!ok) return;
|
|
387
|
+
return (
|
|
388
|
+
<Flex
|
|
389
|
+
variant="column-stretch-start-nowrap-0"
|
|
390
|
+
style={{ width: '100%', height: '100%', overflow: 'hidden' }}
|
|
391
|
+
>
|
|
392
|
+
{/* SwarmControls (top bar) */}
|
|
393
|
+
<SwarmControls
|
|
394
|
+
swarmStatus={swarmStatus}
|
|
395
|
+
onRefresh={refreshAll}
|
|
396
|
+
/>
|
|
397
|
+
|
|
398
|
+
{/* BudgetBar (below controls) */}
|
|
399
|
+
{hasBudget && (
|
|
400
|
+
<Section padding="compact" border="bottom" shrink={true}>
|
|
401
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
402
|
+
<BudgetBar
|
|
403
|
+
label="Tokens"
|
|
404
|
+
used={sessionBudget!.usedTokens}
|
|
405
|
+
limit={sessionBudget!.limitTokens}
|
|
406
|
+
unit="tokens"
|
|
407
|
+
onLimitChange={async (newLimit: number) => {
|
|
732
408
|
try {
|
|
733
|
-
await callTool('
|
|
734
|
-
toast(
|
|
735
|
-
|
|
409
|
+
await callTool('fw_weaver_swarm_config', { sessionBudgetTokens: newLimit });
|
|
410
|
+
toast(`Token budget updated to ${newLimit.toLocaleString()}`, { type: 'success' });
|
|
411
|
+
fetchSwarmStatus();
|
|
736
412
|
} catch (err: unknown) {
|
|
737
|
-
toast(err instanceof Error ? err.message : 'Failed to
|
|
413
|
+
toast(err instanceof Error ? err.message : 'Failed to update budget', { type: 'error' });
|
|
738
414
|
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
{
|
|
748
|
-
title: 'Reset Everything',
|
|
749
|
-
confirmLabel: 'Reset Everything',
|
|
750
|
-
state: 'danger',
|
|
751
|
-
},
|
|
752
|
-
);
|
|
753
|
-
if (!ok) return;
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
<BudgetBar
|
|
418
|
+
label="Cost"
|
|
419
|
+
used={sessionBudget!.usedCost}
|
|
420
|
+
limit={sessionBudget!.limitCost}
|
|
421
|
+
unit="USD"
|
|
422
|
+
onLimitChange={async (newLimit: number) => {
|
|
754
423
|
try {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
refreshAll();
|
|
424
|
+
await callTool('fw_weaver_swarm_config', { sessionBudgetCost: newLimit });
|
|
425
|
+
toast(`Cost budget updated to $${newLimit.toFixed(2)}`, { type: 'success' });
|
|
426
|
+
fetchSwarmStatus();
|
|
759
427
|
} catch (err: unknown) {
|
|
760
|
-
toast(err instanceof Error ? err.message : 'Failed to
|
|
428
|
+
toast(err instanceof Error ? err.message : 'Failed to update budget', { type: 'error' });
|
|
761
429
|
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
430
|
+
}}
|
|
431
|
+
/>
|
|
432
|
+
</Flex>
|
|
433
|
+
</Section>
|
|
434
|
+
)}
|
|
435
|
+
|
|
436
|
+
{/* Tabs */}
|
|
437
|
+
<Tabs
|
|
438
|
+
tabs={[
|
|
439
|
+
{ id: 'tasks', title: `Tasks (${tasks.length})` },
|
|
440
|
+
{ id: 'control', title: 'Control' },
|
|
441
|
+
{ id: 'bots', title: hasSwarmInstances ? `Workers (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
|
|
442
|
+
{ id: 'profiles', title: `Profiles (${profiles.length})` },
|
|
443
|
+
{ id: 'capabilities', title: 'Capabilities' },
|
|
444
|
+
{ id: 'costs', title: 'Costs' },
|
|
445
|
+
{ id: 'config', title: 'Config' },
|
|
446
|
+
]}
|
|
447
|
+
activeTabId={activeTab}
|
|
448
|
+
onSelectTab={(id: string) => setActiveTab(id)}
|
|
449
|
+
size="sm"
|
|
450
|
+
/>
|
|
451
|
+
|
|
452
|
+
{/* Tab content */}
|
|
453
|
+
<Flex
|
|
454
|
+
variant="column-stretch-start-nowrap-0"
|
|
455
|
+
style={{ flex: 1, minHeight: 0, overflow: 'auto' }}
|
|
456
|
+
>
|
|
457
|
+
|
|
458
|
+
{/* Tasks tab — when TaskEditor is open, render it INSTEAD of the list */}
|
|
459
|
+
{activeTab === 'tasks' && taskEditorMode != null && (
|
|
460
|
+
<TaskEditor
|
|
461
|
+
mode={taskEditorMode}
|
|
462
|
+
taskId={editingTaskId ?? undefined}
|
|
463
|
+
onSave={() => { setTaskEditorMode(null); setEditingTaskId(null); fetchTaskList(); }}
|
|
464
|
+
onCancel={() => { setTaskEditorMode(null); setEditingTaskId(null); }}
|
|
465
|
+
onDelete={taskEditorMode === 'edit'
|
|
466
|
+
? () => { setTaskEditorMode(null); setEditingTaskId(null); fetchTaskList(); }
|
|
467
|
+
: undefined}
|
|
468
|
+
/>
|
|
469
|
+
)}
|
|
470
|
+
|
|
471
|
+
{/* Tasks tab — default view (list or board) */}
|
|
472
|
+
{activeTab === 'tasks' && taskEditorMode == null && (
|
|
473
|
+
<Flex
|
|
474
|
+
variant="column-stretch-start-nowrap-0"
|
|
475
|
+
style={{ flex: 1, minHeight: 0 }}
|
|
476
|
+
>
|
|
477
|
+
{/* View toggle + actions bar */}
|
|
478
|
+
<Section padding="compact" border="bottom" shrink>
|
|
479
|
+
<Flex variant="row-center-space-between-nowrap-8">
|
|
480
|
+
<Flex variant="row-center-start-nowrap-4">
|
|
481
|
+
<IconButton
|
|
482
|
+
icon="list"
|
|
483
|
+
size="xs"
|
|
484
|
+
variant={taskView === 'list' ? 'outlined' : 'clear'}
|
|
485
|
+
onClick={() => setTaskView('list')}
|
|
486
|
+
title="List view"
|
|
487
|
+
/>
|
|
488
|
+
<IconButton
|
|
489
|
+
icon="dashboard"
|
|
490
|
+
size="xs"
|
|
491
|
+
variant={taskView === 'board' ? 'outlined' : 'clear'}
|
|
492
|
+
onClick={() => setTaskView('board')}
|
|
493
|
+
title="Board view"
|
|
494
|
+
/>
|
|
495
|
+
</Flex>
|
|
496
|
+
<Flex variant="row-center-start-nowrap-4">
|
|
497
|
+
{tasks.length > 0 && (
|
|
498
|
+
<>
|
|
499
|
+
<Button size="xs" variant="clear" color="secondary" leftIcon="remove"
|
|
500
|
+
onClick={async () => {
|
|
501
|
+
const ok = await ctx.confirm('Clear all completed, failed, and cancelled tasks?', {
|
|
502
|
+
title: 'Clear Completed Tasks', confirmLabel: 'Clear', state: 'warning',
|
|
503
|
+
});
|
|
504
|
+
if (!ok) return;
|
|
505
|
+
try {
|
|
506
|
+
const raw = await callTool('fw_weaver_tasks_clear', { filter: 'completed' });
|
|
507
|
+
const data = parseToolResult(raw) as { cleared?: number } | null;
|
|
508
|
+
toast(`Cleared ${data?.cleared ?? 0} tasks`, { type: 'success' });
|
|
509
|
+
fetchTaskList();
|
|
510
|
+
} catch (err: unknown) {
|
|
511
|
+
toast(err instanceof Error ? err.message : 'Failed to clear tasks', { type: 'error' });
|
|
512
|
+
}
|
|
513
|
+
}}
|
|
514
|
+
>Clear Done</Button>
|
|
515
|
+
</>
|
|
516
|
+
)}
|
|
517
|
+
<Button size="xs" variant="clear" color="primary" leftIcon="add"
|
|
518
|
+
onClick={() => setTaskEditorMode('create')}
|
|
519
|
+
>New Task</Button>
|
|
520
|
+
</Flex>
|
|
521
|
+
</Flex>
|
|
522
|
+
</Section>
|
|
523
|
+
|
|
524
|
+
{/* Empty state */}
|
|
525
|
+
{!hasContent && !loading && (
|
|
526
|
+
<EmptyState
|
|
527
|
+
icon="smartToy"
|
|
528
|
+
message="No tasks yet"
|
|
529
|
+
description="Create tasks below or ask the AI assistant to create them for you."
|
|
530
|
+
/>
|
|
531
|
+
)}
|
|
532
|
+
|
|
533
|
+
{/* List view */}
|
|
534
|
+
{hasContent && taskView === 'list' && (
|
|
535
|
+
<TaskPoolList tasks={tasks} onTaskClick={handleTaskClick} />
|
|
536
|
+
)}
|
|
537
|
+
|
|
538
|
+
{/* Board view */}
|
|
539
|
+
{hasContent && taskView === 'board' && (() => {
|
|
540
|
+
// Split open tasks into ready vs blocked
|
|
541
|
+
const allTasks = tasks as Array<Record<string, unknown>>;
|
|
542
|
+
const doneIds = new Set(allTasks.filter(t => t.status === 'done' || t.status === 'cancelled').map(t => t.id as string));
|
|
543
|
+
|
|
544
|
+
const kanbanColumns = [
|
|
545
|
+
{ id: 'waiting', title: 'Waiting', color: 'var(--color-status-caution)' },
|
|
546
|
+
{ id: 'open', title: 'Ready', color: 'var(--color-text-subtle)' },
|
|
547
|
+
{ id: 'in-progress', title: 'In Progress', color: 'var(--color-brand-main)' },
|
|
548
|
+
{ id: 'done', title: 'Done', color: 'var(--color-status-positive)' },
|
|
549
|
+
{ id: 'cancelled', title: 'Cancelled', color: 'var(--color-status-negative)' },
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
const kanbanCards = allTasks
|
|
553
|
+
.filter(t => !t.isParent) // Only leaf tasks on the board
|
|
554
|
+
.map(t => {
|
|
555
|
+
const deps = (t.dependsOn as string[]) ?? [];
|
|
556
|
+
const unresolvedDeps = deps.filter(d => !doneIds.has(d));
|
|
557
|
+
const subtasks = allTasks.filter(st => st.parentId === t.id);
|
|
558
|
+
const subtasksDone = subtasks.filter(st => st.status === 'done').length;
|
|
559
|
+
const profileName = profiles.find((p: Record<string, unknown>) => p.id === t.assignedProfile)?.name as string | undefined;
|
|
560
|
+
|
|
561
|
+
// Open tasks with unresolved deps go to Waiting column
|
|
562
|
+
const columnId = t.status === 'open' && unresolvedDeps.length > 0
|
|
563
|
+
? 'waiting'
|
|
564
|
+
: t.status as string;
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
id: t.id as string,
|
|
568
|
+
columnId,
|
|
569
|
+
content: (
|
|
570
|
+
<TaskCard
|
|
571
|
+
title={t.title as string}
|
|
572
|
+
priority={t.priority as number}
|
|
573
|
+
profile={profileName ?? (t.assignedProfile as string) ?? undefined}
|
|
574
|
+
complexity={t.complexity as string ?? undefined}
|
|
575
|
+
subtaskProgress={subtasks.length > 0 ? { done: subtasksDone, total: subtasks.length } : undefined}
|
|
576
|
+
cost={t.costUsed as number ?? undefined}
|
|
577
|
+
blockedBy={unresolvedDeps.length > 0 ? unresolvedDeps : undefined}
|
|
578
|
+
isActive={!!(t.activeRunId)}
|
|
579
|
+
onClick={() => handleTaskClick(t.id as string)}
|
|
580
|
+
/>
|
|
581
|
+
),
|
|
582
|
+
};
|
|
583
|
+
});
|
|
774
584
|
|
|
585
|
+
return (
|
|
586
|
+
<Flex variant="column-stretch-start-nowrap-0" style={{ flex: 1, minHeight: 0, padding: '8px' }}>
|
|
587
|
+
<KanbanBoard columns={kanbanColumns} cards={kanbanCards} emptyLabel="No tasks" />
|
|
588
|
+
</Flex>
|
|
589
|
+
);
|
|
590
|
+
})()}
|
|
591
|
+
</Flex>
|
|
592
|
+
)}
|
|
593
|
+
|
|
594
|
+
{/* Bots tab */}
|
|
595
|
+
{activeTab === 'bots' && (
|
|
596
|
+
<Flex
|
|
597
|
+
variant="column-stretch-start-nowrap-0"
|
|
598
|
+
style={{ flex: 1, minHeight: 0 }}
|
|
599
|
+
>
|
|
600
|
+
{/* When swarm is running: show swarm instances */}
|
|
601
|
+
{hasSwarmInstances && (
|
|
602
|
+
<Flex variant="column-stretch-start-nowrap-0">
|
|
603
|
+
<Flex
|
|
604
|
+
variant="row-center-start-nowrap-8"
|
|
605
|
+
style={{ padding: '8px 16px', borderBottom: '1px solid var(--color-border-default)' }}
|
|
606
|
+
>
|
|
607
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '120px', flexShrink: 0 }}>Worker</Typography>
|
|
608
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '110px', flexShrink: 0 }}>Bot</Typography>
|
|
609
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '70px', flexShrink: 0 }}>Status</Typography>
|
|
610
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ flex: 1, minWidth: 0 }}>Task</Typography>
|
|
611
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '50px', flexShrink: 0, textAlign: 'right' }}>Tokens</Typography>
|
|
612
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '50px', flexShrink: 0, textAlign: 'right' }}>Cost</Typography>
|
|
613
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle" style={{ width: '50px', flexShrink: 0 }}>{''}</Typography>
|
|
614
|
+
</Flex>
|
|
615
|
+
{/* Instance rows */}
|
|
616
|
+
{swarmInstanceEntries.map((inst: InstanceInfo) => {
|
|
617
|
+
// Look up profile -> bot for this instance
|
|
618
|
+
const profile = profiles.find((p: Record<string, unknown>) => p.id === inst.profileId);
|
|
619
|
+
const botId = profile?.botId as string | undefined;
|
|
620
|
+
const bot = registeredBots.find((b) => b.id === botId);
|
|
621
|
+
return (
|
|
622
|
+
<BotSlotCard
|
|
623
|
+
key={inst.instanceId}
|
|
624
|
+
bot={{
|
|
625
|
+
botId: inst.instanceId,
|
|
626
|
+
botName: inst.profileId ? `${inst.instanceId} (${inst.profileId})` : inst.instanceId,
|
|
627
|
+
status: inst.status,
|
|
628
|
+
currentTaskId: inst.currentTaskId,
|
|
629
|
+
currentRunId: inst.currentRunId,
|
|
630
|
+
startedAt: inst.startedAt,
|
|
631
|
+
tokensUsed: inst.tokensUsed,
|
|
632
|
+
cost: inst.cost,
|
|
633
|
+
}}
|
|
634
|
+
profileName={(profile?.name as string) || inst.profileId}
|
|
635
|
+
botDisplayName={bot?.name}
|
|
636
|
+
botIcon={bot?.icon}
|
|
637
|
+
botColor={bot?.color}
|
|
638
|
+
currentTaskTitle={resolveTaskTitle(inst.currentTaskId, tasks)}
|
|
639
|
+
onView={() => handleInstanceClick(inst)}
|
|
640
|
+
onPause={(id: string) => handleSteerBot(id, 'pause')}
|
|
641
|
+
onResume={(id: string) => handleSteerBot(id, 'resume')}
|
|
642
|
+
onStop={(id: string) => handleSteerBot(id, 'cancel')}
|
|
643
|
+
/>
|
|
644
|
+
);
|
|
645
|
+
})}
|
|
646
|
+
</Flex>
|
|
647
|
+
)}
|
|
648
|
+
|
|
649
|
+
{/* When swarm is NOT running: show registered bots */}
|
|
650
|
+
{!hasSwarmInstances && hasRegisteredBots && (
|
|
651
|
+
<Flex
|
|
652
|
+
variant="column-stretch-start-nowrap-0"
|
|
653
|
+
style={{ padding: '8px 16px' }}
|
|
654
|
+
>
|
|
655
|
+
{registeredBots.map((bot) => {
|
|
656
|
+
const isEditing = editingBotId === bot.id;
|
|
657
|
+
return (
|
|
658
|
+
<Flex
|
|
659
|
+
key={bot.id}
|
|
660
|
+
variant="column-stretch-start-nowrap-0"
|
|
661
|
+
style={{ borderBottom: '1px solid var(--color-border-default)' }}
|
|
662
|
+
>
|
|
663
|
+
{/* Bot row (clickable to toggle edit) */}
|
|
664
|
+
<Flex
|
|
665
|
+
variant="row-center-start-nowrap-8"
|
|
666
|
+
style={{ padding: '6px 0', cursor: 'pointer' }}
|
|
667
|
+
onClick={() => setEditingBotId(isEditing ? null : bot.id)}
|
|
668
|
+
>
|
|
669
|
+
<Icon
|
|
670
|
+
name={bot.icon || 'smartToy'}
|
|
671
|
+
size={16}
|
|
672
|
+
color={bot.color || 'color-text-medium'}
|
|
673
|
+
/>
|
|
674
|
+
<Flex variant="column-start-start-nowrap-1" style={{ flex: 1, minWidth: 0 }}>
|
|
675
|
+
<Typography variant="smallCaption-regular" color="color-text-high">{bot.name}</Typography>
|
|
676
|
+
{bot.description && (
|
|
677
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">{bot.description}</Typography>
|
|
678
|
+
)}
|
|
679
|
+
</Flex>
|
|
680
|
+
<Icon name={isEditing ? 'expandLess' : 'expandMore'} size={14} color="color-text-subtle" />
|
|
681
|
+
</Flex>
|
|
682
|
+
|
|
683
|
+
{/* Edit section (icon + color pickers) */}
|
|
684
|
+
{isEditing && (
|
|
685
|
+
<Flex
|
|
686
|
+
variant="column-stretch-start-nowrap-12"
|
|
687
|
+
style={{ padding: '8px 0 12px 24px' }}
|
|
688
|
+
>
|
|
689
|
+
<IconPicker
|
|
690
|
+
catalog={ICON_CATALOG}
|
|
691
|
+
value={bot.icon || 'smartToy'}
|
|
692
|
+
onChange={(icon: string) => handleUpdateBot(bot.id, { icon })}
|
|
693
|
+
accentColor={bot.color || undefined}
|
|
694
|
+
defaultExpanded={true}
|
|
695
|
+
/>
|
|
696
|
+
<ColorPicker
|
|
697
|
+
colors={BOT_COLORS}
|
|
698
|
+
value={bot.color || ''}
|
|
699
|
+
onChange={(color: string) => handleUpdateBot(bot.id, { color })}
|
|
700
|
+
defaultExpanded={true}
|
|
701
|
+
/>
|
|
702
|
+
|
|
703
|
+
{/* Bot management actions */}
|
|
704
|
+
<Flex variant="row-center-start-nowrap-8">
|
|
705
|
+
<Button
|
|
706
|
+
size="xs"
|
|
707
|
+
variant="outlined"
|
|
708
|
+
color="secondary"
|
|
709
|
+
leftIcon="verified"
|
|
710
|
+
onClick={async () => {
|
|
711
|
+
try {
|
|
712
|
+
const raw = await callTool('fw_weaver_validate_bot', { id: bot.id });
|
|
713
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
714
|
+
if (data?.valid) {
|
|
715
|
+
toast(`${bot.name} is valid`, { type: 'success' });
|
|
716
|
+
} else {
|
|
717
|
+
toast(`Validation issues: ${JSON.stringify(data?.issues ?? data)}`, { type: 'warning' });
|
|
718
|
+
}
|
|
719
|
+
} catch (err: unknown) {
|
|
720
|
+
toast(err instanceof Error ? err.message : 'Validation failed', { type: 'error' });
|
|
721
|
+
}
|
|
722
|
+
}}
|
|
723
|
+
>Validate</Button>
|
|
724
|
+
<Button
|
|
725
|
+
size="xs"
|
|
726
|
+
variant="outlined"
|
|
727
|
+
color="danger"
|
|
728
|
+
leftIcon="deleteForever"
|
|
729
|
+
onClick={async () => {
|
|
730
|
+
const ok = await ctx.confirm(`Unregister bot "${bot.name}"? The workflow file will not be deleted.`, {
|
|
731
|
+
title: 'Unregister Bot',
|
|
732
|
+
confirmLabel: 'Unregister',
|
|
733
|
+
state: 'danger',
|
|
734
|
+
});
|
|
735
|
+
if (!ok) return;
|
|
736
|
+
try {
|
|
737
|
+
await callTool('fw_weaver_unregister_bot', { id: bot.id });
|
|
738
|
+
toast(`Bot "${bot.name}" unregistered`, { type: 'info' });
|
|
739
|
+
fetchBots();
|
|
740
|
+
} catch (err: unknown) {
|
|
741
|
+
toast(err instanceof Error ? err.message : 'Failed to unregister', { type: 'error' });
|
|
742
|
+
}
|
|
743
|
+
}}
|
|
744
|
+
>Unregister</Button>
|
|
745
|
+
</Flex>
|
|
746
|
+
</Flex>
|
|
747
|
+
)}
|
|
748
|
+
</Flex>
|
|
749
|
+
);
|
|
750
|
+
})}
|
|
751
|
+
</Flex>
|
|
752
|
+
)}
|
|
753
|
+
|
|
754
|
+
{/* No bots at all */}
|
|
755
|
+
{!hasSwarmInstances && !hasRegisteredBots && (
|
|
756
|
+
<EmptyState
|
|
757
|
+
icon="smartToy"
|
|
758
|
+
message="No bots registered"
|
|
759
|
+
description="Register bots to start the swarm."
|
|
760
|
+
/>
|
|
761
|
+
)}
|
|
762
|
+
</Flex>
|
|
763
|
+
)}
|
|
764
|
+
|
|
765
|
+
{/* Profiles tab — when ProfileEditor is open, render it INSTEAD of the list */}
|
|
766
|
+
{activeTab === 'profiles' && profileEditorMode != null && (
|
|
767
|
+
<ProfileEditor
|
|
768
|
+
mode={profileEditorMode}
|
|
769
|
+
profileId={editingProfileId ?? undefined}
|
|
770
|
+
bots={registeredBots.map((b: { id: string; name: string; icon?: string; color?: string }) => ({ id: b.id, name: b.name, icon: b.icon, color: b.color }))}
|
|
771
|
+
onSave={() => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); }}
|
|
772
|
+
onCancel={() => { setProfileEditorMode(null); setEditingProfileId(null); }}
|
|
773
|
+
onDelete={profileEditorMode === 'edit'
|
|
774
|
+
? () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); }
|
|
775
|
+
: undefined}
|
|
776
|
+
/>
|
|
777
|
+
)}
|
|
778
|
+
|
|
779
|
+
{/* Profiles tab — default list view */}
|
|
780
|
+
{activeTab === 'profiles' && profileEditorMode == null && (
|
|
781
|
+
<Flex
|
|
782
|
+
variant="column-stretch-start-nowrap-0"
|
|
783
|
+
style={{ flex: 1, minHeight: 0 }}
|
|
784
|
+
>
|
|
785
|
+
{/* Profile cards (scrollable) */}
|
|
786
|
+
<Flex
|
|
787
|
+
variant="column-stretch-start-nowrap-8"
|
|
788
|
+
style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '12px 16px' }}
|
|
789
|
+
>
|
|
790
|
+
{profiles.length > 0
|
|
791
|
+
? (
|
|
792
|
+
<Flex variant="column-stretch-start-nowrap-8">
|
|
793
|
+
{profiles.map((p: Record<string, unknown>) => {
|
|
794
|
+
const activeCount = swarmInstanceEntries.filter(
|
|
795
|
+
(inst: InstanceInfo) => inst.profileId === p.id && inst.status === 'executing',
|
|
796
|
+
).length;
|
|
797
|
+
return (
|
|
798
|
+
<ProfileCard
|
|
799
|
+
key={p.id as string}
|
|
800
|
+
profile={p as unknown}
|
|
801
|
+
activeInstances={activeCount}
|
|
802
|
+
onEdit={(id: string) => { setEditingProfileId(id); setProfileEditorMode('edit'); }}
|
|
803
|
+
onDelete={async (id: string) => {
|
|
804
|
+
const ok = await ctx.confirm('Are you sure you want to delete this profile?', {
|
|
805
|
+
title: 'Delete Profile',
|
|
806
|
+
confirmLabel: 'Delete',
|
|
807
|
+
state: 'danger',
|
|
808
|
+
});
|
|
809
|
+
if (!ok) return;
|
|
810
|
+
try {
|
|
811
|
+
await callTool('fw_weaver_profile_delete', { id });
|
|
812
|
+
await fetchProfiles();
|
|
813
|
+
toast('Profile deleted', { type: 'success' });
|
|
814
|
+
} catch (err: unknown) {
|
|
815
|
+
toast(err instanceof Error ? err.message : 'Failed to delete profile', { type: 'error' });
|
|
816
|
+
}
|
|
817
|
+
}}
|
|
818
|
+
/>
|
|
819
|
+
);
|
|
820
|
+
})}
|
|
821
|
+
</Flex>
|
|
822
|
+
)
|
|
823
|
+
: (
|
|
824
|
+
<EmptyState
|
|
825
|
+
icon="person"
|
|
826
|
+
message="No profiles"
|
|
827
|
+
description="Profiles define how bots behave. Start the swarm to create defaults, or create one below."
|
|
828
|
+
/>
|
|
829
|
+
)}
|
|
830
|
+
</Flex>
|
|
831
|
+
|
|
832
|
+
{/* New Profile button (bottom bar) */}
|
|
833
|
+
<Flex
|
|
834
|
+
variant="column-stretch-start-nowrap-0"
|
|
835
|
+
style={{ flexShrink: 0, borderTop: '1px solid var(--color-border-default)', padding: '8px 16px' }}
|
|
836
|
+
>
|
|
837
|
+
<Button
|
|
838
|
+
size="xs"
|
|
839
|
+
variant="clear"
|
|
840
|
+
color="primary"
|
|
841
|
+
leftIcon="add"
|
|
842
|
+
onClick={() => setProfileEditorMode('create')}
|
|
843
|
+
>New Profile</Button>
|
|
844
|
+
</Flex>
|
|
845
|
+
</Flex>
|
|
846
|
+
)}
|
|
847
|
+
|
|
848
|
+
{/* Capabilities tab */}
|
|
849
|
+
{activeTab === 'capabilities' && (
|
|
850
|
+
<CapabilityEditor
|
|
851
|
+
onSave={async (caps) => {
|
|
852
|
+
toast(`${caps.length} capabilities saved`, { type: 'success' });
|
|
853
|
+
}}
|
|
854
|
+
/>
|
|
855
|
+
)}
|
|
856
|
+
|
|
857
|
+
{/* Costs tab */}
|
|
858
|
+
{activeTab === 'costs' && (() => {
|
|
859
|
+
// Build sparkline data from run history (cost over time, chronological)
|
|
860
|
+
const costSparkline = [...runHistory]
|
|
861
|
+
.reverse()
|
|
862
|
+
.filter((r) => typeof r.startedAt === 'string')
|
|
863
|
+
.map((r, i) => ({
|
|
864
|
+
x: i,
|
|
865
|
+
y: ((r.cost as Record<string, unknown>)?.totalCost as number) ?? (r.cost as number) ?? 0,
|
|
866
|
+
}));
|
|
867
|
+
|
|
868
|
+
// Build token sparkline
|
|
869
|
+
const tokenSparkline = [...runHistory]
|
|
870
|
+
.reverse()
|
|
871
|
+
.filter((r) => typeof r.startedAt === 'string')
|
|
872
|
+
.map((r, i) => ({
|
|
873
|
+
x: i,
|
|
874
|
+
y: ((r.cost as Record<string, unknown>)?.totalInputTokens as number) ??
|
|
875
|
+
((r.cost as Record<string, unknown>)?.totalOutputTokens as number) ?? 0,
|
|
876
|
+
}));
|
|
877
|
+
|
|
878
|
+
// Build model breakdown for bar chart from costData
|
|
879
|
+
const costInsights = insights?.cost as Record<string, unknown> | undefined;
|
|
880
|
+
const byModel = costData?.byModel as Record<string, Record<string, unknown>> | undefined;
|
|
881
|
+
const modelBars = byModel
|
|
882
|
+
? Object.entries(byModel).map(([model, data]) => ({
|
|
883
|
+
label: model.replace('claude-', '').replace(/-\d.*$/, ''),
|
|
884
|
+
value: (data.cost as number) ?? 0,
|
|
885
|
+
}))
|
|
886
|
+
: [];
|
|
887
|
+
|
|
888
|
+
const trend = (costInsights?.trend as string) === 'increasing' ? 'up' as const
|
|
889
|
+
: (costInsights?.trend as string) === 'decreasing' ? 'down' as const
|
|
890
|
+
: 'stable' as const;
|
|
891
|
+
|
|
892
|
+
return (
|
|
893
|
+
<Section padding="default">
|
|
894
|
+
<Flex variant="column-stretch-start-nowrap-16">
|
|
895
|
+
|
|
896
|
+
{/* Metric cards row */}
|
|
897
|
+
<Flex variant="row-stretch-start-nowrap-8">
|
|
898
|
+
<MetricCard
|
|
899
|
+
value={`$${(swarmStatus?.totalCost ?? 0).toFixed(2)}`}
|
|
900
|
+
label="Session Cost"
|
|
901
|
+
trend={trend}
|
|
902
|
+
trendLabel={(costInsights?.trend as string) ?? undefined}
|
|
903
|
+
sparklineData={costSparkline.length >= 2 ? costSparkline : undefined}
|
|
904
|
+
/>
|
|
905
|
+
<MetricCard
|
|
906
|
+
value={(swarmStatus?.totalTokensUsed ?? 0).toLocaleString()}
|
|
907
|
+
label="Tokens Used"
|
|
908
|
+
sparklineData={tokenSparkline.length >= 2 ? tokenSparkline : undefined}
|
|
909
|
+
sparklineColor="var(--color-status-info)"
|
|
910
|
+
/>
|
|
911
|
+
<MetricCard
|
|
912
|
+
value={String(swarmStatus?.tasksCompleted ?? 0)}
|
|
913
|
+
label="Tasks Completed"
|
|
914
|
+
/>
|
|
915
|
+
<MetricCard
|
|
916
|
+
value={`$${((costInsights?.last7Days as number) ?? 0).toFixed(2)}`}
|
|
917
|
+
label="7-Day Cost"
|
|
918
|
+
trendLabel={(costInsights?.costPerSuccessfulRun as number)
|
|
919
|
+
? `$${(costInsights.costPerSuccessfulRun as number).toFixed(3)}/run`
|
|
920
|
+
: undefined}
|
|
921
|
+
/>
|
|
922
|
+
</Flex>
|
|
923
|
+
|
|
924
|
+
{/* Cost over time chart */}
|
|
925
|
+
{costSparkline.length >= 2 && (
|
|
926
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
927
|
+
<SectionTitle size="xs">Cost Over Time</SectionTitle>
|
|
928
|
+
<SparklineChart
|
|
929
|
+
data={costSparkline}
|
|
930
|
+
height={80}
|
|
931
|
+
responsive
|
|
932
|
+
showArea
|
|
933
|
+
showDots
|
|
934
|
+
showYLabels
|
|
935
|
+
formatY={(v) => `$${v.toFixed(3)}`}
|
|
936
|
+
strokeWidth={2}
|
|
937
|
+
/>
|
|
938
|
+
</Flex>
|
|
939
|
+
)}
|
|
940
|
+
|
|
941
|
+
{/* Cost by model bar chart */}
|
|
942
|
+
{modelBars.length > 0 && (
|
|
943
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
944
|
+
<SectionTitle size="xs">Cost by Model</SectionTitle>
|
|
945
|
+
<BarChart
|
|
946
|
+
data={modelBars}
|
|
947
|
+
formatValue={(v) => `$${v.toFixed(3)}`}
|
|
948
|
+
/>
|
|
949
|
+
</Flex>
|
|
950
|
+
)}
|
|
951
|
+
|
|
952
|
+
{/* High cost workflows */}
|
|
953
|
+
{(costInsights?.highCostWorkflows as Array<Record<string, unknown>> | undefined)?.length ? (
|
|
954
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
955
|
+
<SectionTitle size="xs">High-Cost Workflows</SectionTitle>
|
|
956
|
+
<BarChart
|
|
957
|
+
data={(costInsights!.highCostWorkflows as Array<Record<string, unknown>>).map((w) => ({
|
|
958
|
+
label: String(w.workflow ?? '').replace(/\.ts$/, ''),
|
|
959
|
+
value: (w.avgCost as number) ?? 0,
|
|
960
|
+
color: 'var(--color-status-caution)',
|
|
961
|
+
}))}
|
|
962
|
+
formatValue={(v) => `$${v.toFixed(3)}`}
|
|
963
|
+
/>
|
|
964
|
+
</Flex>
|
|
965
|
+
) : null}
|
|
966
|
+
|
|
967
|
+
{/* Detailed breakdown table */}
|
|
968
|
+
{runHistory.length > 0 && (
|
|
969
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
970
|
+
<SectionTitle size="xs">Recent Runs</SectionTitle>
|
|
971
|
+
<Table
|
|
972
|
+
size="compact"
|
|
973
|
+
getRowKey={(row: Record<string, unknown>) => (row.id as string) ?? String(Math.random())}
|
|
974
|
+
onRowClick={(row: Record<string, unknown>) => {
|
|
975
|
+
if (row.taskId) handleTaskClick(row.taskId as string);
|
|
976
|
+
}}
|
|
977
|
+
columns={[
|
|
978
|
+
{ key: 'outcome', header: '', width: '20px', render: (_: unknown, row: Record<string, unknown>) => (
|
|
979
|
+
<StatusIcon status={row.success ? 'completed' : 'failed'} size="xs" />
|
|
980
|
+
)},
|
|
981
|
+
{ key: 'taskId', header: 'Task', render: (_: unknown, row: Record<string, unknown>) => (
|
|
982
|
+
<Typography variant="smallCaption-regular" color="color-text-high" truncate>
|
|
983
|
+
{(row.taskId as string) ?? '-'}
|
|
984
|
+
</Typography>
|
|
985
|
+
)},
|
|
986
|
+
{ key: 'model', header: 'Model', width: '80px', render: (_: unknown, row: Record<string, unknown>) => {
|
|
987
|
+
const costObj = row.cost as Record<string, unknown> | undefined;
|
|
988
|
+
return (
|
|
989
|
+
<Typography variant="smallCaption-regular" color="color-text-medium" mono>
|
|
990
|
+
{((costObj?.model as string) ?? '').replace('claude-', '') || '-'}
|
|
991
|
+
</Typography>
|
|
992
|
+
);
|
|
993
|
+
}},
|
|
994
|
+
{ key: 'cost', header: 'Cost', width: '65px', align: 'right' as const, render: (_: unknown, row: Record<string, unknown>) => {
|
|
995
|
+
const costObj = row.cost as Record<string, unknown> | undefined;
|
|
996
|
+
const val = (costObj?.totalCost as number) ?? 0;
|
|
997
|
+
return (
|
|
998
|
+
<Typography variant="smallCaption-regular" color="color-text-high" mono>
|
|
999
|
+
{`$${val.toFixed(3)}`}
|
|
1000
|
+
</Typography>
|
|
1001
|
+
);
|
|
1002
|
+
}},
|
|
1003
|
+
]}
|
|
1004
|
+
data={runHistory as Array<Record<string, unknown>>}
|
|
1005
|
+
maxHeight="200px"
|
|
1006
|
+
/>
|
|
1007
|
+
</Flex>
|
|
1008
|
+
)}
|
|
1009
|
+
|
|
1010
|
+
{/* Empty state */}
|
|
1011
|
+
{!swarmStatus && !costData && runHistory.length === 0 && (
|
|
1012
|
+
<EmptyState icon="payments" message="No cost data yet" />
|
|
1013
|
+
)}
|
|
1014
|
+
</Flex>
|
|
1015
|
+
</Section>
|
|
1016
|
+
);
|
|
1017
|
+
})()}
|
|
1018
|
+
|
|
1019
|
+
{/* Control tab — active swarm management */}
|
|
1020
|
+
{activeTab === 'control' && (
|
|
1021
|
+
<Section padding="default">
|
|
1022
|
+
<Flex variant="column-stretch-start-nowrap-16">
|
|
1023
|
+
|
|
1024
|
+
{/* Orchestrator Guidance */}
|
|
1025
|
+
<Flex variant="column-stretch-start-nowrap-8">
|
|
1026
|
+
<SectionTitle size="xs">Orchestrator Guidance</SectionTitle>
|
|
1027
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
1028
|
+
Influence how the orchestrator routes tasks to profiles. Hints persist across dispatch cycles.
|
|
1029
|
+
</Typography>
|
|
1030
|
+
<Input
|
|
1031
|
+
type="text"
|
|
1032
|
+
value={hintText}
|
|
1033
|
+
onChange={(value: string) => setHintText(value)}
|
|
1034
|
+
onKeyDown={(e: { key: string }) => {
|
|
1035
|
+
if (e.key === 'Enter' && hintText.trim()) {
|
|
1036
|
+
setHintSending(true);
|
|
1037
|
+
callTool('fw_weaver_orchestrator_hint', { hint: hintText.trim() })
|
|
1038
|
+
.then(() => { toast('Hint sent to orchestrator', { type: 'success' }); setHintText(''); })
|
|
1039
|
+
.catch((err: Error) => toast(err.message, { type: 'error' }))
|
|
1040
|
+
.finally(() => setHintSending(false));
|
|
1041
|
+
}
|
|
1042
|
+
}}
|
|
1043
|
+
placeholder='e.g. "Prioritize security tasks" or "Use architect for auth changes"'
|
|
1044
|
+
disabled={hintSending}
|
|
1045
|
+
inputBoxStyle={{ maxWidth: 'none' }}
|
|
1046
|
+
/>
|
|
1047
|
+
<Flex variant="row-center-end-nowrap-0">
|
|
1048
|
+
<Button
|
|
1049
|
+
size="xs"
|
|
1050
|
+
variant="outlined"
|
|
1051
|
+
color="primary"
|
|
1052
|
+
onClick={() => {
|
|
1053
|
+
if (!hintText.trim()) return;
|
|
1054
|
+
setHintSending(true);
|
|
1055
|
+
callTool('fw_weaver_orchestrator_hint', { hint: hintText.trim() })
|
|
1056
|
+
.then(() => { toast('Hint sent to orchestrator', { type: 'success' }); setHintText(''); })
|
|
1057
|
+
.catch((err: Error) => toast(err.message, { type: 'error' }))
|
|
1058
|
+
.finally(() => setHintSending(false));
|
|
1059
|
+
}}
|
|
1060
|
+
loading={hintSending}
|
|
1061
|
+
disabled={!hintText.trim() || hintSending}
|
|
1062
|
+
>Send</Button>
|
|
1063
|
+
</Flex>
|
|
1064
|
+
</Flex>
|
|
1065
|
+
|
|
1066
|
+
{/* Concurrency & Budgets */}
|
|
1067
|
+
<Flex variant="column-stretch-start-nowrap-8">
|
|
1068
|
+
<SectionTitle size="xs">Concurrency & Budgets</SectionTitle>
|
|
1069
|
+
<Flex variant="row-center-start-nowrap-12">
|
|
1070
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">Max Concurrent</Typography>
|
|
1071
|
+
<Slider
|
|
1072
|
+
value={localConcurrency ?? swarmStatus?.maxConcurrent ?? 5}
|
|
1073
|
+
min={1}
|
|
1074
|
+
max={20}
|
|
1075
|
+
step={1}
|
|
1076
|
+
onChange={(val: number) => {
|
|
1077
|
+
setLocalConcurrency(val);
|
|
1078
|
+
if (concurrencyTimerRef.current) clearTimeout(concurrencyTimerRef.current);
|
|
1079
|
+
concurrencyTimerRef.current = setTimeout(async () => {
|
|
1080
|
+
try {
|
|
1081
|
+
await callTool('fw_weaver_swarm_config', { maxConcurrent: val });
|
|
1082
|
+
fetchSwarmStatus();
|
|
1083
|
+
} catch { /* non-fatal */ }
|
|
1084
|
+
setLocalConcurrency(null);
|
|
1085
|
+
}, 500);
|
|
1086
|
+
}}
|
|
1087
|
+
/>
|
|
1088
|
+
<Typography variant="caption-thick" color="color-text-high">
|
|
1089
|
+
{localConcurrency ?? swarmStatus?.maxConcurrent ?? 5}
|
|
1090
|
+
</Typography>
|
|
1091
|
+
</Flex>
|
|
1092
|
+
<Flex variant="row-center-start-nowrap-16">
|
|
1093
|
+
{sessionBudget && (
|
|
1094
|
+
<Field label="Token Limit" labelWidth={110}>
|
|
1095
|
+
<Input
|
|
1096
|
+
type="number"
|
|
1097
|
+
value={String(sessionBudget.limitTokens)}
|
|
1098
|
+
size="small"
|
|
1099
|
+
onChange={() => {}}
|
|
1100
|
+
onBlur={async (e: { target: { value: string } }) => {
|
|
1101
|
+
const val = parseInt(e.target.value, 10);
|
|
1102
|
+
if (!val || val < 1) return;
|
|
1103
|
+
try {
|
|
1104
|
+
await callTool('fw_weaver_swarm_config', { sessionBudgetTokens: val });
|
|
1105
|
+
toast(`Token limit: ${val.toLocaleString()}`, { type: 'success' });
|
|
1106
|
+
fetchSwarmStatus();
|
|
1107
|
+
} catch (err: unknown) {
|
|
1108
|
+
toast(err instanceof Error ? err.message : 'Failed', { type: 'error' });
|
|
1109
|
+
}
|
|
1110
|
+
}}
|
|
1111
|
+
/>
|
|
1112
|
+
</Field>
|
|
1113
|
+
)}
|
|
1114
|
+
{sessionBudget && (
|
|
1115
|
+
<Field label="Cost Limit ($)" labelWidth={110}>
|
|
1116
|
+
<Input
|
|
1117
|
+
type="number"
|
|
1118
|
+
value={String(sessionBudget.limitCost)}
|
|
1119
|
+
size="small"
|
|
1120
|
+
onChange={() => {}}
|
|
1121
|
+
onBlur={async (e: { target: { value: string } }) => {
|
|
1122
|
+
const val = parseFloat(e.target.value);
|
|
1123
|
+
if (!val || val <= 0) return;
|
|
1124
|
+
try {
|
|
1125
|
+
await callTool('fw_weaver_swarm_config', { sessionBudgetCost: val });
|
|
1126
|
+
toast(`Cost limit: $${val.toFixed(2)}`, { type: 'success' });
|
|
1127
|
+
fetchSwarmStatus();
|
|
1128
|
+
} catch (err: unknown) {
|
|
1129
|
+
toast(err instanceof Error ? err.message : 'Failed', { type: 'error' });
|
|
1130
|
+
}
|
|
1131
|
+
}}
|
|
1132
|
+
/>
|
|
1133
|
+
</Field>
|
|
1134
|
+
)}
|
|
1135
|
+
</Flex>
|
|
1136
|
+
{swarmStatus && (
|
|
1137
|
+
<Flex variant="row-center-start-nowrap-16">
|
|
1138
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
1139
|
+
{`Tokens used: ${swarmStatus.totalTokensUsed.toLocaleString()}`}
|
|
1140
|
+
</Typography>
|
|
1141
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">
|
|
1142
|
+
{`Cost: $${swarmStatus.totalCost.toFixed(2)}`}
|
|
1143
|
+
</Typography>
|
|
1144
|
+
</Flex>
|
|
1145
|
+
)}
|
|
1146
|
+
</Flex>
|
|
1147
|
+
|
|
1148
|
+
{/* Routing Decisions */}
|
|
1149
|
+
<DecisionLog decisions={orchestratorDecisions as unknown[]} />
|
|
1150
|
+
</Flex>
|
|
1151
|
+
</Section>
|
|
1152
|
+
)}
|
|
1153
|
+
|
|
1154
|
+
{/* Config tab — static settings, read-only info, resets */}
|
|
1155
|
+
{activeTab === 'config' && (
|
|
1156
|
+
<Flex
|
|
1157
|
+
variant="column-stretch-start-nowrap-12"
|
|
1158
|
+
style={{ padding: '12px 16px' }}
|
|
1159
|
+
>
|
|
1160
|
+
{/* Provider */}
|
|
1161
|
+
<Flex variant="row-center-space-between-nowrap-8">
|
|
1162
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">Provider</Typography>
|
|
1163
|
+
<Flex variant="row-center-start-nowrap-4">
|
|
1164
|
+
<Typography variant="smallCaption-regular" color="color-text-high">
|
|
1165
|
+
{providers.find((p) => p.envVarsSet)?.name ?? 'None detected'}
|
|
1166
|
+
</Typography>
|
|
1167
|
+
{providers.find((p) => p.envVarsSet) && (
|
|
1168
|
+
<Chip label="active" size="small" color="color-status-info" />
|
|
1169
|
+
)}
|
|
1170
|
+
</Flex>
|
|
1171
|
+
</Flex>
|
|
1172
|
+
|
|
1173
|
+
{/* Trust */}
|
|
1174
|
+
{insights?.trust && (
|
|
1175
|
+
<Flex variant="row-center-space-between-nowrap-8">
|
|
1176
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">Trust</Typography>
|
|
1177
|
+
<Typography variant="smallCaption-regular" color="color-text-high">
|
|
1178
|
+
{`Phase ${(insights.trust as Record<string, unknown>).phase} · ${(insights.trust as Record<string, unknown>).score}/100`}
|
|
1179
|
+
</Typography>
|
|
1180
|
+
</Flex>
|
|
1181
|
+
)}
|
|
1182
|
+
|
|
1183
|
+
{/* Health */}
|
|
1184
|
+
{insights?.health && (
|
|
1185
|
+
<Flex variant="row-center-space-between-nowrap-8">
|
|
1186
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">Health</Typography>
|
|
1187
|
+
<Typography variant="smallCaption-regular" color="color-text-high">
|
|
1188
|
+
{`${(insights.health as Record<string, unknown>).overall}/100`}
|
|
1189
|
+
</Typography>
|
|
1190
|
+
</Flex>
|
|
1191
|
+
)}
|
|
1192
|
+
|
|
1193
|
+
{/* Available providers */}
|
|
1194
|
+
{providers.length > 0 && (
|
|
1195
|
+
<Flex variant="column-stretch-start-nowrap-4">
|
|
1196
|
+
<Typography variant="smallCaption-regular" color="color-text-subtle">Available Providers</Typography>
|
|
1197
|
+
<Flex variant="row-center-start-wrap-4">
|
|
1198
|
+
{providers.map((p) => (
|
|
1199
|
+
<Chip
|
|
1200
|
+
key={p.name}
|
|
1201
|
+
label={p.name}
|
|
1202
|
+
size="small"
|
|
1203
|
+
color={p.envVarsSet ? 'color-status-positive' : 'color-brand-alt'}
|
|
1204
|
+
/>
|
|
1205
|
+
))}
|
|
1206
|
+
</Flex>
|
|
1207
|
+
</Flex>
|
|
1208
|
+
)}
|
|
1209
|
+
|
|
1210
|
+
{/* Reset section */}
|
|
1211
|
+
<Flex variant="column-stretch-start-nowrap-8" style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--color-border-default)' }}>
|
|
1212
|
+
<SectionTitle size="xs">Reset</SectionTitle>
|
|
1213
|
+
<Flex variant="row-center-start-nowrap-8">
|
|
1214
|
+
<Button
|
|
1215
|
+
size="xs"
|
|
1216
|
+
variant="outlined"
|
|
1217
|
+
color="secondary"
|
|
1218
|
+
leftIcon="undo"
|
|
1219
|
+
onClick={async () => {
|
|
1220
|
+
const ok = await ctx.confirm('Reset Weaver configuration to defaults? Your .weaver.json will be deleted.', {
|
|
1221
|
+
title: 'Reset Config',
|
|
1222
|
+
confirmLabel: 'Reset',
|
|
1223
|
+
state: 'warning',
|
|
1224
|
+
});
|
|
1225
|
+
if (!ok) return;
|
|
1226
|
+
try {
|
|
1227
|
+
await callTool('fw_weaver_config_reset', {});
|
|
1228
|
+
toast('Config reset to defaults', { type: 'success' });
|
|
1229
|
+
refreshAll();
|
|
1230
|
+
} catch (err: unknown) {
|
|
1231
|
+
toast(err instanceof Error ? err.message : 'Failed to reset config', { type: 'error' });
|
|
1232
|
+
}
|
|
1233
|
+
}}
|
|
1234
|
+
>Reset Config</Button>
|
|
1235
|
+
<Button
|
|
1236
|
+
size="xs"
|
|
1237
|
+
variant="outlined"
|
|
1238
|
+
color="danger"
|
|
1239
|
+
leftIcon="warning"
|
|
1240
|
+
onClick={async () => {
|
|
1241
|
+
const ok = await ctx.confirm(
|
|
1242
|
+
'This will stop the swarm, delete ALL tasks, profiles, run history, and configuration. This cannot be undone.',
|
|
1243
|
+
{
|
|
1244
|
+
title: 'Reset Everything',
|
|
1245
|
+
confirmLabel: 'Reset Everything',
|
|
1246
|
+
state: 'danger',
|
|
1247
|
+
},
|
|
1248
|
+
);
|
|
1249
|
+
if (!ok) return;
|
|
1250
|
+
try {
|
|
1251
|
+
const raw = await callTool('fw_weaver_reset_all', {});
|
|
1252
|
+
const data = parseToolResult(raw) as { tasksCleared?: number; profilesCleared?: number } | null;
|
|
1253
|
+
toast(`Full reset complete — ${data?.tasksCleared ?? 0} tasks, ${data?.profilesCleared ?? 0} profiles cleared`, { type: 'success' });
|
|
1254
|
+
refreshAll();
|
|
1255
|
+
} catch (err: unknown) {
|
|
1256
|
+
toast(err instanceof Error ? err.message : 'Failed to reset', { type: 'error' });
|
|
1257
|
+
}
|
|
1258
|
+
}}
|
|
1259
|
+
>Reset Everything</Button>
|
|
1260
|
+
</Flex>
|
|
1261
|
+
</Flex>
|
|
1262
|
+
|
|
1263
|
+
{/* Evolution */}
|
|
1264
|
+
<Flex variant="column-stretch-start-nowrap-8" style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--color-border-default)' }}>
|
|
1265
|
+
<SectionTitle size="xs">Evolution</SectionTitle>
|
|
1266
|
+
<Flex variant="row-center-start-nowrap-8">
|
|
1267
|
+
<Button
|
|
1268
|
+
size="xs"
|
|
1269
|
+
variant="outlined"
|
|
1270
|
+
color="primary"
|
|
1271
|
+
leftIcon="psychology"
|
|
1272
|
+
onClick={async () => {
|
|
1273
|
+
const ok = await ctx.confirm('Run a Genesis self-evolution cycle? This analyzes your workflows and suggests improvements.', {
|
|
1274
|
+
title: 'Run Genesis',
|
|
1275
|
+
confirmLabel: 'Run',
|
|
1276
|
+
state: 'info',
|
|
1277
|
+
});
|
|
1278
|
+
if (!ok) return;
|
|
1279
|
+
try {
|
|
1280
|
+
toast('Genesis cycle started...', { type: 'info' });
|
|
1281
|
+
const raw = await callTool('fw_weaver_genesis', {});
|
|
1282
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
1283
|
+
if (data?.success) {
|
|
1284
|
+
toast('Genesis cycle completed', { type: 'success' });
|
|
1285
|
+
} else {
|
|
1286
|
+
toast(`Genesis: ${data?.summary ?? 'Cycle finished'}`, { type: 'info' });
|
|
1287
|
+
}
|
|
1288
|
+
refreshAll();
|
|
1289
|
+
} catch (err: unknown) {
|
|
1290
|
+
toast(err instanceof Error ? err.message : 'Genesis failed', { type: 'error' });
|
|
1291
|
+
}
|
|
1292
|
+
}}
|
|
1293
|
+
>Run Genesis Cycle</Button>
|
|
1294
|
+
</Flex>
|
|
1295
|
+
</Flex>
|
|
1296
|
+
|
|
1297
|
+
{/* Pack version */}
|
|
1298
|
+
{swarmStatus?.packVersion && (
|
|
1299
|
+
<Typography
|
|
1300
|
+
variant="smallCaption-regular"
|
|
1301
|
+
color="color-text-low"
|
|
1302
|
+
style={{ textAlign: 'right', paddingTop: 8 }}
|
|
1303
|
+
>{`Weaver v${swarmStatus.packVersion}`}</Typography>
|
|
1304
|
+
)}
|
|
1305
|
+
</Flex>
|
|
1306
|
+
)}
|
|
1307
|
+
</Flex>
|
|
1308
|
+
</Flex>
|
|
775
1309
|
);
|
|
776
1310
|
}
|
|
777
1311
|
|
|
778
1312
|
export { SwarmDashboard };
|
|
779
1313
|
export default SwarmDashboard;
|
|
780
|
-
module.exports = SwarmDashboard;
|