@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.77

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 (217) hide show
  1. package/dist/ai-chat-provider.d.ts +12 -0
  2. package/dist/ai-chat-provider.d.ts.map +1 -1
  3. package/dist/ai-chat-provider.js +351 -335
  4. package/dist/ai-chat-provider.js.map +1 -1
  5. package/dist/bot/agent-loop.d.ts +20 -0
  6. package/dist/bot/agent-loop.d.ts.map +1 -0
  7. package/dist/bot/agent-loop.js +331 -0
  8. package/dist/bot/agent-loop.js.map +1 -0
  9. package/dist/bot/ai-router.d.ts +19 -0
  10. package/dist/bot/ai-router.d.ts.map +1 -0
  11. package/dist/bot/ai-router.js +104 -0
  12. package/dist/bot/ai-router.js.map +1 -0
  13. package/dist/bot/assistant-tools.d.ts.map +1 -1
  14. package/dist/bot/assistant-tools.js +49 -33
  15. package/dist/bot/assistant-tools.js.map +1 -1
  16. package/dist/bot/async-mutex.d.ts +13 -0
  17. package/dist/bot/async-mutex.d.ts.map +1 -0
  18. package/dist/bot/async-mutex.js +37 -0
  19. package/dist/bot/async-mutex.js.map +1 -0
  20. package/dist/bot/bot-manager.d.ts +2 -2
  21. package/dist/bot/bot-manager.d.ts.map +1 -1
  22. package/dist/bot/bot-manager.js +3 -3
  23. package/dist/bot/bot-manager.js.map +1 -1
  24. package/dist/bot/bot-registry.js +2 -2
  25. package/dist/bot/bot-registry.js.map +1 -1
  26. package/dist/bot/conversation-store.d.ts +1 -0
  27. package/dist/bot/conversation-store.d.ts.map +1 -1
  28. package/dist/bot/conversation-store.js.map +1 -1
  29. package/dist/bot/dashboard.d.ts.map +1 -1
  30. package/dist/bot/dashboard.js +17 -8
  31. package/dist/bot/dashboard.js.map +1 -1
  32. package/dist/bot/improve-loop.js.map +1 -1
  33. package/dist/bot/index.d.ts +2 -4
  34. package/dist/bot/index.d.ts.map +1 -1
  35. package/dist/bot/index.js +1 -2
  36. package/dist/bot/index.js.map +1 -1
  37. package/dist/bot/instance-manager.d.ts +31 -0
  38. package/dist/bot/instance-manager.d.ts.map +1 -0
  39. package/dist/bot/instance-manager.js +115 -0
  40. package/dist/bot/instance-manager.js.map +1 -0
  41. package/dist/bot/orchestrator.d.ts +36 -0
  42. package/dist/bot/orchestrator.d.ts.map +1 -0
  43. package/dist/bot/orchestrator.js +176 -0
  44. package/dist/bot/orchestrator.js.map +1 -0
  45. package/dist/bot/profile-store.d.ts +36 -0
  46. package/dist/bot/profile-store.d.ts.map +1 -0
  47. package/dist/bot/profile-store.js +208 -0
  48. package/dist/bot/profile-store.js.map +1 -0
  49. package/dist/bot/profile-types.d.ts +126 -0
  50. package/dist/bot/profile-types.d.ts.map +1 -0
  51. package/dist/bot/profile-types.js +7 -0
  52. package/dist/bot/profile-types.js.map +1 -0
  53. package/dist/bot/run-store.d.ts.map +1 -1
  54. package/dist/bot/run-store.js +8 -0
  55. package/dist/bot/run-store.js.map +1 -1
  56. package/dist/bot/runner.d.ts +4 -0
  57. package/dist/bot/runner.d.ts.map +1 -1
  58. package/dist/bot/runner.js +5 -1
  59. package/dist/bot/runner.js.map +1 -1
  60. package/dist/bot/swarm-controller.d.ts +109 -0
  61. package/dist/bot/swarm-controller.d.ts.map +1 -0
  62. package/dist/bot/swarm-controller.js +640 -0
  63. package/dist/bot/swarm-controller.js.map +1 -0
  64. package/dist/bot/swarm-event-log.d.ts +28 -0
  65. package/dist/bot/swarm-event-log.d.ts.map +1 -0
  66. package/dist/bot/swarm-event-log.js +54 -0
  67. package/dist/bot/swarm-event-log.js.map +1 -0
  68. package/dist/bot/task-prompt-builder.d.ts +22 -0
  69. package/dist/bot/task-prompt-builder.d.ts.map +1 -0
  70. package/dist/bot/task-prompt-builder.js +240 -0
  71. package/dist/bot/task-prompt-builder.js.map +1 -0
  72. package/dist/bot/task-store.d.ts +21 -0
  73. package/dist/bot/task-store.d.ts.map +1 -0
  74. package/dist/bot/task-store.js +364 -0
  75. package/dist/bot/task-store.js.map +1 -0
  76. package/dist/bot/task-types.d.ts +79 -0
  77. package/dist/bot/task-types.d.ts.map +1 -0
  78. package/dist/bot/task-types.js +6 -0
  79. package/dist/bot/task-types.js.map +1 -0
  80. package/dist/bot/types.d.ts +8 -0
  81. package/dist/bot/types.d.ts.map +1 -1
  82. package/dist/cli-handlers.d.ts.map +1 -1
  83. package/dist/cli-handlers.js +79 -54
  84. package/dist/cli-handlers.js.map +1 -1
  85. package/dist/cli.d.ts +3 -0
  86. package/dist/cli.d.ts.map +1 -0
  87. package/dist/cli.js +749 -0
  88. package/dist/cli.js.map +1 -0
  89. package/dist/docs/docs/weaver-bot-usage.md +35 -18
  90. package/dist/docs/docs/weaver-config.md +20 -0
  91. package/dist/docs/docs/weaver-task-queue.md +31 -19
  92. package/dist/docs/weaver-config.md +15 -9
  93. package/dist/index.d.ts +2 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/index.js.map +1 -1
  97. package/dist/mcp-tools.d.ts +17 -0
  98. package/dist/mcp-tools.d.ts.map +1 -1
  99. package/dist/mcp-tools.js +98 -279
  100. package/dist/mcp-tools.js.map +1 -1
  101. package/dist/node-types/bot-report.d.ts.map +1 -1
  102. package/dist/node-types/bot-report.js +6 -24
  103. package/dist/node-types/bot-report.js.map +1 -1
  104. package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
  105. package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
  106. package/dist/node-types/orchestrator-dispatch.js +63 -0
  107. package/dist/node-types/orchestrator-dispatch.js.map +1 -0
  108. package/dist/node-types/orchestrator-load-state.d.ts +16 -0
  109. package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
  110. package/dist/node-types/orchestrator-load-state.js +60 -0
  111. package/dist/node-types/orchestrator-load-state.js.map +1 -0
  112. package/dist/node-types/orchestrator-route.d.ts +16 -0
  113. package/dist/node-types/orchestrator-route.d.ts.map +1 -0
  114. package/dist/node-types/orchestrator-route.js +28 -0
  115. package/dist/node-types/orchestrator-route.js.map +1 -0
  116. package/dist/node-types/receive-task.d.ts +2 -3
  117. package/dist/node-types/receive-task.d.ts.map +1 -1
  118. package/dist/node-types/receive-task.js +3 -48
  119. package/dist/node-types/receive-task.js.map +1 -1
  120. package/dist/templates/weaver-template.d.ts +11 -0
  121. package/dist/templates/weaver-template.d.ts.map +1 -0
  122. package/dist/templates/weaver-template.js +53 -0
  123. package/dist/templates/weaver-template.js.map +1 -0
  124. package/dist/ui/bot-activity.js +2 -2
  125. package/dist/ui/bot-constants.d.ts +14 -0
  126. package/dist/ui/bot-constants.d.ts.map +1 -0
  127. package/dist/ui/bot-constants.js +189 -0
  128. package/dist/ui/bot-constants.js.map +1 -0
  129. package/dist/ui/bot-panel.js +207 -245
  130. package/dist/ui/bot-slot-card.js +141 -0
  131. package/dist/ui/budget-bar.js +59 -0
  132. package/dist/ui/chat-task-result.js +178 -0
  133. package/dist/ui/decision-log.js +136 -0
  134. package/dist/ui/profile-card.js +158 -0
  135. package/dist/ui/profile-editor.js +597 -0
  136. package/dist/ui/swarm-controls.js +245 -0
  137. package/dist/ui/swarm-dashboard.js +3012 -0
  138. package/dist/ui/task-create-form.js +98 -0
  139. package/dist/ui/task-detail-view.js +1044 -0
  140. package/dist/ui/task-pool-list.js +156 -0
  141. package/dist/workflows/orchestrator.d.ts +21 -0
  142. package/dist/workflows/orchestrator.d.ts.map +1 -0
  143. package/dist/workflows/orchestrator.js +281 -0
  144. package/dist/workflows/orchestrator.js.map +1 -0
  145. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  146. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  147. package/dist/workflows/weaver-bot-session.js +68 -0
  148. package/dist/workflows/weaver-bot-session.js.map +1 -0
  149. package/dist/workflows/weaver.d.ts +24 -0
  150. package/dist/workflows/weaver.d.ts.map +1 -0
  151. package/dist/workflows/weaver.js +28 -0
  152. package/dist/workflows/weaver.js.map +1 -0
  153. package/flowweaver.manifest.json +547 -133
  154. package/package.json +1 -1
  155. package/src/ai-chat-provider.ts +378 -371
  156. package/src/bot/ai-router.ts +132 -0
  157. package/src/bot/assistant-tools.ts +47 -29
  158. package/src/bot/async-mutex.ts +37 -0
  159. package/src/bot/bot-manager.ts +3 -3
  160. package/src/bot/bot-registry.ts +2 -2
  161. package/src/bot/conversation-store.ts +2 -1
  162. package/src/bot/dashboard.ts +17 -8
  163. package/src/bot/improve-loop.ts +6 -6
  164. package/src/bot/index.ts +2 -4
  165. package/src/bot/instance-manager.ts +128 -0
  166. package/src/bot/orchestrator.ts +244 -0
  167. package/src/bot/profile-store.ts +225 -0
  168. package/src/bot/profile-types.ts +141 -0
  169. package/src/bot/run-store.ts +8 -0
  170. package/src/bot/runner.ts +9 -1
  171. package/src/bot/swarm-controller.ts +780 -0
  172. package/src/bot/swarm-event-log.ts +57 -0
  173. package/src/bot/task-prompt-builder.ts +309 -0
  174. package/src/bot/task-store.ts +407 -0
  175. package/src/bot/task-types.ts +100 -0
  176. package/src/bot/types.ts +8 -0
  177. package/src/cli-handlers.ts +78 -53
  178. package/src/docs/weaver-bot-usage.md +35 -18
  179. package/src/docs/weaver-config.md +20 -0
  180. package/src/docs/weaver-task-queue.md +31 -19
  181. package/src/index.ts +5 -4
  182. package/src/mcp-tools.ts +129 -372
  183. package/src/node-types/bot-report.ts +6 -24
  184. package/src/node-types/orchestrator-dispatch.ts +71 -0
  185. package/src/node-types/orchestrator-load-state.ts +66 -0
  186. package/src/node-types/orchestrator-route.ts +33 -0
  187. package/src/node-types/receive-task.ts +3 -57
  188. package/src/ui/bot-activity.tsx +2 -2
  189. package/src/ui/bot-constants.ts +192 -0
  190. package/src/ui/bot-panel.tsx +213 -247
  191. package/src/ui/bot-slot-card.tsx +139 -0
  192. package/src/ui/budget-bar.tsx +30 -0
  193. package/src/ui/chat-task-result.tsx +236 -0
  194. package/src/ui/decision-log.tsx +148 -0
  195. package/src/ui/profile-card.tsx +157 -0
  196. package/src/ui/profile-editor.tsx +384 -0
  197. package/src/ui/swarm-controls.tsx +260 -0
  198. package/src/ui/swarm-dashboard.tsx +647 -0
  199. package/src/ui/task-create-form.tsx +87 -0
  200. package/src/ui/task-detail-view.tsx +841 -0
  201. package/src/ui/task-pool-list.tsx +187 -0
  202. package/src/workflows/orchestrator.ts +302 -0
  203. package/dist/docs/weaver-bot-usage.md +0 -34
  204. package/dist/docs/weaver-genesis.md +0 -32
  205. package/dist/docs/weaver-task-queue.md +0 -34
  206. package/dist/ui/bot-workspace.js +0 -1015
  207. package/dist/ui/chat-bot-result.js +0 -71
  208. package/dist/ui/queue-input.js +0 -82
  209. package/dist/ui/session-bar.js +0 -174
  210. package/src/bot/error-guide.ts +0 -4
  211. package/src/bot/retry-utils.ts +0 -4
  212. package/src/bot/session-state.ts +0 -116
  213. package/src/bot/task-queue.ts +0 -262
  214. package/src/ui/bot-workspace.tsx +0 -442
  215. package/src/ui/chat-bot-result.tsx +0 -81
  216. package/src/ui/queue-input.tsx +0 -56
  217. package/src/ui/session-bar.tsx +0 -157
@@ -0,0 +1,647 @@
1
+ /**
2
+ * Swarm Dashboard — main workspace component for the swarm command center.
3
+ *
4
+ * Replaces bot-workspace.tsx as the primary center-dock workspace.
5
+ * Composes: SwarmControls, BudgetBar, BotSlotCard, TaskPoolList,
6
+ * TaskCreateForm, and TaskDetailView into a unified swarm dashboard.
7
+ *
8
+ * Two-panel navigation:
9
+ * - Default: full swarm dashboard (controls + budget + bots + task pool + create form)
10
+ * - On task click: TaskDetailView (with back button returning to dashboard)
11
+ *
12
+ * Data loading:
13
+ * - Polls fw_weaver_swarm_status every 3s for swarm state
14
+ * - Polls fw_weaver_task_list every 5s for task pool
15
+ * - Both called immediately on mount
16
+ *
17
+ * Pattern: CommonJS require for platform deps, ESM import for local components,
18
+ * React.createElement throughout, module.exports at end.
19
+ */
20
+ const React = require('react');
21
+ const { useState, useEffect, useCallback, useRef } = React;
22
+ const {
23
+ Flex, ScrollArea, EmptyState, Typography, StatusIcon, Icon, Tag, Card, Tabs, SectionTitle, Button,
24
+ Input, IconButton, IconPicker, ColorPicker, toast, usePackWorkspace,
25
+ } = require('@fw/plugin-ui-kit');
26
+
27
+ // Local pack-specific components (bundled by esbuild)
28
+ import SwarmControls from './swarm-controls';
29
+ import BudgetBar from './budget-bar';
30
+ import BotSlotCard from './bot-slot-card';
31
+ import TaskPoolList from './task-pool-list';
32
+ import TaskCreateForm from './task-create-form';
33
+ import TaskDetailView from './task-detail-view';
34
+ import ProfileCard from './profile-card';
35
+ import ProfileEditor from './profile-editor';
36
+ import DecisionLog from './decision-log';
37
+ import { ICON_CATALOG, BOT_COLORS } from './bot-constants';
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Types
41
+ // ---------------------------------------------------------------------------
42
+
43
+ interface InstanceInfo {
44
+ instanceId: string;
45
+ profileId: string;
46
+ index: number;
47
+ status: 'idle' | 'executing' | 'paused' | 'stopped';
48
+ currentTaskId?: string;
49
+ currentRunId?: string;
50
+ startedAt?: string;
51
+ tokensUsed: number;
52
+ cost: number;
53
+ tasksCompleted: number;
54
+ tasksFailed: number;
55
+ }
56
+
57
+ interface SwarmBudgetLayer {
58
+ limitTokens: number;
59
+ usedTokens: number;
60
+ limitCost: number;
61
+ usedCost: number;
62
+ }
63
+
64
+ interface SwarmStatus {
65
+ status: 'idle' | 'running' | 'paused' | 'stopping';
66
+ instances: Record<string, InstanceInfo>;
67
+ maxConcurrent: number;
68
+ tasksCompleted: number;
69
+ tasksFailed: number;
70
+ totalTokensUsed: number;
71
+ totalCost: number;
72
+ budgets?: {
73
+ workspace: SwarmBudgetLayer;
74
+ session: SwarmBudgetLayer;
75
+ };
76
+ startedAt?: string;
77
+ }
78
+
79
+ type TaskStatus = 'pending' | 'in-progress' | 'blocked' | 'done' | 'failed' | 'cancelled';
80
+
81
+ interface PoolTask {
82
+ id: string;
83
+ title: string;
84
+ status: TaskStatus;
85
+ priority: number;
86
+ isParent: boolean;
87
+ parentId?: string;
88
+ assignedProfile?: string;
89
+ createdAt: string;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Helpers
94
+ // ---------------------------------------------------------------------------
95
+
96
+ function parseToolResult(raw: unknown): unknown {
97
+ if (typeof raw === 'string') {
98
+ try { return JSON.parse(raw); } catch { return raw; }
99
+ }
100
+ return raw;
101
+ }
102
+
103
+ /** Resolve task title for a bot's currentTaskId from the task pool. */
104
+ function resolveTaskTitle(taskId: string | undefined, tasks: PoolTask[]): string | undefined {
105
+ if (!taskId) return undefined;
106
+ const task = tasks.find(t => t.id === taskId);
107
+ return task?.title;
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Component
112
+ // ---------------------------------------------------------------------------
113
+
114
+ function SwarmDashboard() {
115
+ const ctx = usePackWorkspace();
116
+ const { callTool, onRefresh } = ctx;
117
+
118
+ // --- Navigation state ---
119
+ const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
120
+ const [activeTab, setActiveTab] = useState('tasks');
121
+ const [editingBotId, setEditingBotId] = useState<string | null>(null);
122
+ const [profileEditorMode, setProfileEditorMode] = useState<'create' | 'edit' | null>(null);
123
+ const [editingProfileId, setEditingProfileId] = useState<string | null>(null);
124
+
125
+ // --- Data state ---
126
+ const [swarmStatus, setSwarmStatus] = useState<SwarmStatus | null>(null);
127
+ const [tasks, setTasks] = useState<PoolTask[]>([]);
128
+ const [registeredBots, setRegisteredBots] = useState<Array<{ id: string; name: string; description: string; icon?: string; color?: string }>>([]);
129
+ const [providers, setProviders] = useState<Array<{ name: string; source: string; envVarsSet: boolean }>>([]);
130
+ const [insights, setInsights] = useState<Record<string, unknown> | null>(null);
131
+ const [profiles, setProfiles] = useState<Array<Record<string, unknown>>>([]);
132
+ const [orchestratorDecisions, setOrchestratorDecisions] = useState<Array<Record<string, unknown>>>([]);
133
+ const [loading, setLoading] = useState(true);
134
+
135
+ // Refs to track mounted state for async cleanup
136
+ const mountedRef = useRef(true);
137
+ useEffect(() => {
138
+ mountedRef.current = true;
139
+ return () => { mountedRef.current = false; };
140
+ }, []);
141
+
142
+ // --- Data fetching ---
143
+
144
+ const fetchSwarmStatus = useCallback(async () => {
145
+ try {
146
+ const raw = await callTool('fw_weaver_swarm_status', {});
147
+ if (!mountedRef.current) return;
148
+ const data = parseToolResult(raw) as SwarmStatus | null;
149
+ if (data) setSwarmStatus(data);
150
+ } catch {
151
+ // non-fatal — swarm might not be started
152
+ }
153
+ }, [callTool]);
154
+
155
+ const fetchTaskList = useCallback(async () => {
156
+ try {
157
+ const raw = await callTool('fw_weaver_task_list', {});
158
+ if (!mountedRef.current) return;
159
+ const data = parseToolResult(raw);
160
+ if (Array.isArray(data)) {
161
+ setTasks(data as PoolTask[]);
162
+ } else if (data && typeof data === 'object' && Array.isArray((data as Record<string, unknown>).tasks)) {
163
+ setTasks((data as Record<string, unknown>).tasks as PoolTask[]);
164
+ }
165
+ } catch {
166
+ // non-fatal
167
+ }
168
+ }, [callTool]);
169
+
170
+ const fetchBots = useCallback(async () => {
171
+ try {
172
+ const raw = await callTool('fw_weaver_list_bots', {});
173
+ if (!mountedRef.current) return;
174
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
175
+ if (Array.isArray(data)) setRegisteredBots(data);
176
+ } catch { /* non-fatal */ }
177
+ }, [callTool]);
178
+
179
+ const fetchConfig = useCallback(async () => {
180
+ try {
181
+ const [provRaw, insRaw] = await Promise.all([
182
+ callTool('fw_weaver_providers', {}),
183
+ callTool('fw_weaver_insights', {}),
184
+ ]);
185
+ if (!mountedRef.current) return;
186
+ const provData = typeof provRaw === 'string' ? JSON.parse(provRaw) : provRaw;
187
+ const insData = typeof insRaw === 'string' ? JSON.parse(insRaw) : insRaw;
188
+ if (Array.isArray(provData)) setProviders(provData);
189
+ if (insData && typeof insData === 'object') setInsights(insData as Record<string, unknown>);
190
+ } catch { /* non-fatal */ }
191
+ }, [callTool]);
192
+
193
+ const fetchProfiles = useCallback(async () => {
194
+ try {
195
+ const raw = await callTool('fw_weaver_profile_list', {});
196
+ if (!mountedRef.current) return;
197
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
198
+ if (Array.isArray(data)) setProfiles(data);
199
+ } catch { /* non-fatal */ }
200
+ }, [callTool]);
201
+
202
+ const fetchOrchestratorStatus = useCallback(async () => {
203
+ try {
204
+ const raw = await callTool('fw_weaver_orchestrator_status', {});
205
+ if (!mountedRef.current) return;
206
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
207
+ if (data && typeof data === 'object') {
208
+ const decisions = (data as Record<string, unknown>).recentDecisions;
209
+ if (Array.isArray(decisions)) setOrchestratorDecisions(decisions);
210
+ }
211
+ } catch { /* non-fatal */ }
212
+ }, [callTool]);
213
+
214
+ const refreshAll = useCallback(async () => {
215
+ await Promise.all([fetchSwarmStatus(), fetchTaskList(), fetchBots(), fetchConfig(), fetchProfiles(), fetchOrchestratorStatus()]);
216
+ if (mountedRef.current) setLoading(false);
217
+ }, [fetchSwarmStatus, fetchTaskList, fetchBots, fetchConfig, fetchProfiles, fetchOrchestratorStatus]);
218
+
219
+ // Initial fetch on mount
220
+ useEffect(() => {
221
+ refreshAll();
222
+ }, [refreshAll]);
223
+
224
+ // Poll swarm status every 3s
225
+ useEffect(() => {
226
+ const interval = setInterval(fetchSwarmStatus, 3_000);
227
+ return () => clearInterval(interval);
228
+ }, [fetchSwarmStatus]);
229
+
230
+ // Poll task list every 5s
231
+ useEffect(() => {
232
+ const interval = setInterval(fetchTaskList, 5_000);
233
+ return () => clearInterval(interval);
234
+ }, [fetchTaskList]);
235
+
236
+ // Poll profiles & orchestrator status every 10s
237
+ useEffect(() => {
238
+ const interval = setInterval(() => {
239
+ fetchProfiles();
240
+ fetchOrchestratorStatus();
241
+ }, 10_000);
242
+ return () => clearInterval(interval);
243
+ }, [fetchProfiles, fetchOrchestratorStatus]);
244
+
245
+ // Listen for platform refresh events
246
+ useEffect(() => {
247
+ return onRefresh(() => refreshAll());
248
+ }, [refreshAll, onRefresh]);
249
+
250
+ // --- Bot edit handlers ---
251
+
252
+ const handleUpdateBot = useCallback(async (botId: string, patch: Record<string, unknown>) => {
253
+ try {
254
+ await callTool('fw_weaver_register_bot', { id: botId, ...patch });
255
+ await fetchBots();
256
+ toast('Bot updated', { type: 'success' });
257
+ } catch (err: unknown) {
258
+ toast(err instanceof Error ? err.message : 'Failed to update bot', { type: 'error' });
259
+ }
260
+ }, [callTool, fetchBots]);
261
+
262
+ // --- Bot steering handlers ---
263
+
264
+ const handleSteerBot = useCallback(async (botId: string, command: string) => {
265
+ try {
266
+ await callTool('fw_weaver_steer', { botId, command });
267
+ toast(`${command} signal sent to ${botId}`, { type: 'info' });
268
+ fetchSwarmStatus();
269
+ } catch (err: unknown) {
270
+ toast(err instanceof Error ? err.message : `Failed to ${command}`, { type: 'error' });
271
+ }
272
+ }, [callTool, fetchSwarmStatus]);
273
+
274
+ // --- Navigation handlers ---
275
+
276
+ const handleTaskClick = useCallback((taskId: string) => {
277
+ setSelectedTaskId(taskId);
278
+ }, []);
279
+
280
+ const handleBack = useCallback(() => {
281
+ setSelectedTaskId(null);
282
+ // Refresh data when returning to dashboard
283
+ refreshAll();
284
+ }, [refreshAll]);
285
+
286
+ const handleTaskCreated = useCallback(() => {
287
+ fetchTaskList();
288
+ }, [fetchTaskList]);
289
+
290
+ // --- Render: TaskDetailView (when task is selected) ---
291
+
292
+ if (selectedTaskId) {
293
+ return React.createElement(TaskDetailView, {
294
+ taskId: selectedTaskId,
295
+ onBack: handleBack,
296
+ });
297
+ }
298
+
299
+ // --- Render: Swarm Dashboard (default view) ---
300
+
301
+ const swarmInstances = swarmStatus?.instances ?? {};
302
+ const swarmInstanceEntries = Object.values(swarmInstances) as InstanceInfo[];
303
+ const hasSwarmInstances = swarmInstanceEntries.length > 0;
304
+ const hasRegisteredBots = registeredBots.length > 0;
305
+ const sessionBudget = swarmStatus?.budgets?.session;
306
+ const hasBudget = !!(sessionBudget && (sessionBudget.limitTokens > 0 || sessionBudget.limitCost > 0));
307
+ const hasContent = hasSwarmInstances || hasRegisteredBots || tasks.length > 0 || swarmStatus !== null;
308
+
309
+ return React.createElement(Flex, {
310
+ variant: 'column-stretch-start-nowrap-0',
311
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
312
+ },
313
+ // ── SwarmControls (top bar) ──────────────────────────────────
314
+ React.createElement(SwarmControls, {
315
+ swarmStatus,
316
+ onRefresh: refreshAll,
317
+ }),
318
+
319
+ // ── BudgetBar (below controls) ──────────────────────────────
320
+ hasBudget && React.createElement(Flex, {
321
+ variant: 'column-stretch-start-nowrap-4',
322
+ style: { padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid var(--color-border-default)' },
323
+ },
324
+ React.createElement(BudgetBar, {
325
+ label: 'Tokens',
326
+ used: sessionBudget!.usedTokens,
327
+ limit: sessionBudget!.limitTokens,
328
+ unit: 'tokens',
329
+ }),
330
+ React.createElement(BudgetBar, {
331
+ label: 'Cost',
332
+ used: sessionBudget!.usedCost,
333
+ limit: sessionBudget!.limitCost,
334
+ unit: 'USD',
335
+ }),
336
+ ),
337
+
338
+ // ── Bot slot cards (horizontal scrollable row) ──────────────
339
+ // ── Tabs ──────────────────────────────────────────────────────
340
+ React.createElement(Tabs, {
341
+ tabs: [
342
+ { id: 'tasks', title: `Tasks (${tasks.length})` },
343
+ { id: 'bots', title: hasSwarmInstances ? `Instances (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
344
+ { id: 'profiles', title: `Profiles (${profiles.length})` },
345
+ { id: 'config', title: 'Config' },
346
+ ],
347
+ activeTabId: activeTab,
348
+ onSelectTab: (id: string) => setActiveTab(id),
349
+ size: 'sm',
350
+ }),
351
+
352
+ // ── Tab content ──────────────────────────────────────────────
353
+ React.createElement(Flex, {
354
+ variant: 'column-stretch-start-nowrap-0',
355
+ style: { flex: 1, minHeight: 0, overflow: 'auto' },
356
+ },
357
+
358
+ // Tasks tab
359
+ activeTab === 'tasks' && React.createElement(Flex, {
360
+ variant: 'column-stretch-start-nowrap-0',
361
+ style: { flex: 1, minHeight: 0 },
362
+ },
363
+ !hasContent && !loading && React.createElement(EmptyState, {
364
+ icon: 'smartToy',
365
+ message: 'Swarm not started',
366
+ description: 'Start the swarm to begin processing tasks, or create tasks below.',
367
+ }),
368
+
369
+ hasContent && React.createElement(TaskPoolList, {
370
+ tasks,
371
+ onTaskClick: handleTaskClick,
372
+ }),
373
+
374
+ // Task create form (inside Tasks tab)
375
+ React.createElement(Flex, {
376
+ variant: 'column-stretch-start-nowrap-0',
377
+ style: { padding: '8px 16px', flexShrink: 0, borderTop: '1px solid var(--color-border-default)' },
378
+ },
379
+ React.createElement(TaskCreateForm, {
380
+ onTaskCreated: handleTaskCreated,
381
+ }),
382
+ ),
383
+ ),
384
+
385
+ // Bots tab
386
+ activeTab === 'bots' && React.createElement(Flex, {
387
+ variant: 'column-stretch-start-nowrap-0',
388
+ style: { flex: 1, minHeight: 0 },
389
+ },
390
+ // When swarm is running: show swarm instances
391
+ hasSwarmInstances && React.createElement(Flex, {
392
+ variant: 'column-stretch-start-nowrap-0',
393
+ },
394
+ React.createElement(Flex, {
395
+ variant: 'row-center-start-nowrap-8',
396
+ style: { padding: '8px 16px', borderBottom: '1px solid var(--color-border-default)' },
397
+ },
398
+ React.createElement(Typography, {
399
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
400
+ style: { width: '120px', flexShrink: 0 },
401
+ }, 'Instance'),
402
+ React.createElement(Typography, {
403
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
404
+ style: { width: '110px', flexShrink: 0 },
405
+ }, 'Bot'),
406
+ React.createElement(Typography, {
407
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
408
+ style: { width: '70px', flexShrink: 0 },
409
+ }, 'Status'),
410
+ React.createElement(Typography, {
411
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
412
+ style: { flex: 1, minWidth: 0 },
413
+ }, 'Task'),
414
+ React.createElement(Typography, {
415
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
416
+ style: { width: '50px', flexShrink: 0, textAlign: 'right' },
417
+ }, 'Tokens'),
418
+ React.createElement(Typography, {
419
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
420
+ style: { width: '50px', flexShrink: 0, textAlign: 'right' },
421
+ }, 'Cost'),
422
+ React.createElement(Typography, {
423
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
424
+ style: { width: '50px', flexShrink: 0 },
425
+ }, ''),
426
+ ),
427
+ // Instance rows
428
+ ...swarmInstanceEntries.map((inst: InstanceInfo) => {
429
+ // Look up profile → bot for this instance
430
+ const profile = profiles.find((p: Record<string, unknown>) => p.id === inst.profileId);
431
+ const botId = profile?.botId as string | undefined;
432
+ const bot = registeredBots.find((b) => b.id === botId);
433
+ return React.createElement(BotSlotCard, {
434
+ key: inst.instanceId,
435
+ bot: {
436
+ botId: inst.instanceId,
437
+ botName: `${inst.profileId} #${inst.index}`,
438
+ status: inst.status,
439
+ currentTaskId: inst.currentTaskId,
440
+ currentRunId: inst.currentRunId,
441
+ startedAt: inst.startedAt,
442
+ tokensUsed: inst.tokensUsed,
443
+ cost: inst.cost,
444
+ },
445
+ profileName: (profile?.name as string) || inst.profileId,
446
+ botDisplayName: bot?.name,
447
+ botIcon: bot?.icon,
448
+ botColor: bot?.color,
449
+ currentTaskTitle: resolveTaskTitle(inst.currentTaskId, tasks),
450
+ onPause: (id: string) => handleSteerBot(id, 'pause'),
451
+ onResume: (id: string) => handleSteerBot(id, 'resume'),
452
+ onStop: (id: string) => handleSteerBot(id, 'cancel'),
453
+ });
454
+ }),
455
+ ),
456
+
457
+ // When swarm is NOT running: show registered bots
458
+ !hasSwarmInstances && hasRegisteredBots && React.createElement(Flex, {
459
+ variant: 'column-stretch-start-nowrap-0',
460
+ style: { padding: '8px 16px' },
461
+ },
462
+ ...registeredBots.map((bot) => {
463
+ const isEditing = editingBotId === bot.id;
464
+ return React.createElement(Flex, {
465
+ key: bot.id,
466
+ variant: 'column-stretch-start-nowrap-0',
467
+ style: { borderBottom: '1px solid var(--color-border-default)' },
468
+ },
469
+ // Bot row (clickable to toggle edit)
470
+ React.createElement(Flex, {
471
+ variant: 'row-center-start-nowrap-8',
472
+ style: { padding: '6px 0', cursor: 'pointer' },
473
+ onClick: () => setEditingBotId(isEditing ? null : bot.id),
474
+ },
475
+ React.createElement(Icon, {
476
+ name: bot.icon || 'smartToy', size: 16,
477
+ color: bot.color || 'color-text-medium',
478
+ }),
479
+ React.createElement(Flex, { variant: 'column-start-start-nowrap-1', style: { flex: 1, minWidth: 0 } },
480
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' }, bot.name),
481
+ bot.description && React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, bot.description),
482
+ ),
483
+ React.createElement(Icon, { name: isEditing ? 'expandLess' : 'expandMore', size: 14, color: 'color-text-subtle' }),
484
+ ),
485
+
486
+ // Edit section (icon + color pickers)
487
+ isEditing && React.createElement(Flex, {
488
+ variant: 'column-stretch-start-nowrap-12',
489
+ style: { padding: '8px 0 12px 24px' },
490
+ },
491
+ React.createElement(IconPicker, {
492
+ catalog: ICON_CATALOG,
493
+ value: bot.icon || 'smartToy',
494
+ onChange: (icon: string) => handleUpdateBot(bot.id, { icon }),
495
+ accentColor: bot.color || undefined,
496
+ defaultExpanded: true,
497
+ }),
498
+ React.createElement(ColorPicker, {
499
+ colors: BOT_COLORS,
500
+ value: bot.color || '',
501
+ onChange: (color: string) => handleUpdateBot(bot.id, { color }),
502
+ defaultExpanded: true,
503
+ }),
504
+ ),
505
+ );
506
+ }),
507
+ ),
508
+
509
+ // No bots at all
510
+ !hasSwarmInstances && !hasRegisteredBots && React.createElement(EmptyState, {
511
+ icon: 'smartToy',
512
+ message: 'No bots registered',
513
+ description: 'Register bots to start the swarm.',
514
+ }),
515
+ ),
516
+
517
+ // Profiles tab — when ProfileEditor is open, render it INSTEAD of the list
518
+ activeTab === 'profiles' && profileEditorMode != null && React.createElement(ProfileEditor, {
519
+ mode: profileEditorMode,
520
+ profileId: editingProfileId ?? undefined,
521
+ bots: registeredBots.map((b: { id: string; name: string; icon?: string; color?: string }) => ({ id: b.id, name: b.name, icon: b.icon, color: b.color })),
522
+ onSave: () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); },
523
+ onCancel: () => { setProfileEditorMode(null); setEditingProfileId(null); },
524
+ onDelete: profileEditorMode === 'edit'
525
+ ? () => { setProfileEditorMode(null); setEditingProfileId(null); fetchProfiles(); }
526
+ : undefined,
527
+ }),
528
+
529
+ // Profiles tab — default list view
530
+ activeTab === 'profiles' && profileEditorMode == null && React.createElement(Flex, {
531
+ variant: 'column-stretch-start-nowrap-0',
532
+ style: { flex: 1, minHeight: 0 },
533
+ },
534
+ // ── Profile cards (scrollable) ──
535
+ React.createElement(Flex, {
536
+ variant: 'column-stretch-start-nowrap-8',
537
+ style: { flex: 1, minHeight: 0, overflow: 'auto', padding: '12px 16px' },
538
+ },
539
+ profiles.length > 0
540
+ ? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
541
+ ...profiles.map((p: Record<string, unknown>) => {
542
+ const activeCount = swarmInstanceEntries.filter(
543
+ (inst: InstanceInfo) => inst.profileId === p.id && inst.status === 'executing',
544
+ ).length;
545
+ return React.createElement(ProfileCard, {
546
+ key: p.id as string,
547
+ profile: p as unknown,
548
+ activeInstances: activeCount,
549
+ onEdit: (id: string) => { setEditingProfileId(id); setProfileEditorMode('edit'); },
550
+ onDelete: async (id: string) => {
551
+ const ok = await ctx.confirm('Are you sure you want to delete this profile?', {
552
+ title: 'Delete Profile',
553
+ confirmLabel: 'Delete',
554
+ state: 'danger',
555
+ });
556
+ if (!ok) return;
557
+ try {
558
+ await callTool('fw_weaver_profile_delete', { id });
559
+ await fetchProfiles();
560
+ toast('Profile deleted', { type: 'success' });
561
+ } catch (err: unknown) {
562
+ toast(err instanceof Error ? err.message : 'Failed to delete profile', { type: 'error' });
563
+ }
564
+ },
565
+ });
566
+ }),
567
+ )
568
+ : React.createElement(EmptyState, {
569
+ icon: 'person',
570
+ message: 'No profiles',
571
+ description: 'Profiles define how bots behave. Start the swarm to create defaults, or create one below.',
572
+ }),
573
+
574
+ // ── Routing / Decision Log (inside scrollable area) ──
575
+ orchestratorDecisions.length > 0 && React.createElement(DecisionLog, {
576
+ decisions: orchestratorDecisions as unknown[],
577
+ }),
578
+ ),
579
+
580
+ // ── New Profile button (bottom bar) ──
581
+ React.createElement(Flex, {
582
+ variant: 'column-stretch-start-nowrap-0',
583
+ style: { flexShrink: 0, borderTop: '1px solid var(--color-border-default)', padding: '8px 16px' },
584
+ },
585
+ React.createElement(Button, {
586
+ size: 'xs', variant: 'clear', color: 'primary',
587
+ leftIcon: 'add',
588
+ onClick: () => setProfileEditorMode('create'),
589
+ }, 'New Profile'),
590
+ ),
591
+
592
+ ),
593
+
594
+ // Config tab
595
+ activeTab === 'config' && React.createElement(Flex, {
596
+ variant: 'column-stretch-start-nowrap-12',
597
+ style: { padding: '12px 16px' },
598
+ },
599
+ // ── Provider ──
600
+ React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
601
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Provider'),
602
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-4' },
603
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
604
+ providers.find((p) => p.envVarsSet)?.name ?? 'None detected',
605
+ ),
606
+ providers.find((p) => p.envVarsSet) && React.createElement(Tag, { size: 'small', color: 'info' }, 'active'),
607
+ ),
608
+ ),
609
+
610
+ // Trust
611
+ insights?.trust && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
612
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Trust'),
613
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
614
+ `Phase ${(insights.trust as Record<string, unknown>).phase} · ${(insights.trust as Record<string, unknown>).score}/100`,
615
+ ),
616
+ ),
617
+
618
+ // Health
619
+ insights?.health && React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
620
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Health'),
621
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
622
+ `${(insights.health as Record<string, unknown>).overall}/100`,
623
+ ),
624
+ ),
625
+
626
+ // Available providers
627
+ providers.length > 0 && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-4' },
628
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Available Providers'),
629
+ React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
630
+ ...providers.map((p) =>
631
+ React.createElement(Tag, {
632
+ key: p.name,
633
+ size: 'small',
634
+ color: p.envVarsSet ? 'positive' : 'secondary',
635
+ }, p.name),
636
+ ),
637
+ ),
638
+ ),
639
+ ),
640
+ ),
641
+
642
+ );
643
+ }
644
+
645
+ export { SwarmDashboard };
646
+ export default SwarmDashboard;
647
+ module.exports = SwarmDashboard;