@synergenius/flow-weaver-pack-weaver 0.9.62 → 0.9.78

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 (162) 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 +173 -19
  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/bot-registry.js +2 -2
  14. package/dist/bot/bot-registry.js.map +1 -1
  15. package/dist/bot/conversation-store.d.ts +1 -0
  16. package/dist/bot/conversation-store.d.ts.map +1 -1
  17. package/dist/bot/conversation-store.js.map +1 -1
  18. package/dist/bot/improve-loop.js.map +1 -1
  19. package/dist/bot/instance-manager.d.ts +31 -0
  20. package/dist/bot/instance-manager.d.ts.map +1 -0
  21. package/dist/bot/instance-manager.js +115 -0
  22. package/dist/bot/instance-manager.js.map +1 -0
  23. package/dist/bot/orchestrator.d.ts +36 -0
  24. package/dist/bot/orchestrator.d.ts.map +1 -0
  25. package/dist/bot/orchestrator.js +176 -0
  26. package/dist/bot/orchestrator.js.map +1 -0
  27. package/dist/bot/profile-store.d.ts +36 -0
  28. package/dist/bot/profile-store.d.ts.map +1 -0
  29. package/dist/bot/profile-store.js +208 -0
  30. package/dist/bot/profile-store.js.map +1 -0
  31. package/dist/bot/profile-types.d.ts +126 -0
  32. package/dist/bot/profile-types.d.ts.map +1 -0
  33. package/dist/bot/profile-types.js +7 -0
  34. package/dist/bot/profile-types.js.map +1 -0
  35. package/dist/bot/session-state.d.ts +25 -0
  36. package/dist/bot/session-state.d.ts.map +1 -0
  37. package/dist/bot/session-state.js +110 -0
  38. package/dist/bot/session-state.js.map +1 -0
  39. package/dist/bot/swarm-controller.d.ts +37 -21
  40. package/dist/bot/swarm-controller.d.ts.map +1 -1
  41. package/dist/bot/swarm-controller.js +344 -163
  42. package/dist/bot/swarm-controller.js.map +1 -1
  43. package/dist/bot/task-prompt-builder.d.ts +2 -1
  44. package/dist/bot/task-prompt-builder.d.ts.map +1 -1
  45. package/dist/bot/task-prompt-builder.js +33 -10
  46. package/dist/bot/task-prompt-builder.js.map +1 -1
  47. package/dist/bot/task-queue.d.ts +46 -0
  48. package/dist/bot/task-queue.d.ts.map +1 -0
  49. package/dist/bot/task-queue.js +237 -0
  50. package/dist/bot/task-queue.js.map +1 -0
  51. package/dist/bot/task-store.d.ts +1 -6
  52. package/dist/bot/task-store.d.ts.map +1 -1
  53. package/dist/bot/task-store.js +27 -78
  54. package/dist/bot/task-store.js.map +1 -1
  55. package/dist/bot/task-types.d.ts +8 -4
  56. package/dist/bot/task-types.d.ts.map +1 -1
  57. package/dist/cli-handlers.d.ts.map +1 -1
  58. package/dist/cli-handlers.js +2 -3
  59. package/dist/cli-handlers.js.map +1 -1
  60. package/dist/cli.d.ts +3 -0
  61. package/dist/cli.d.ts.map +1 -0
  62. package/dist/cli.js +749 -0
  63. package/dist/cli.js.map +1 -0
  64. package/dist/docs/docs/weaver-bot-usage.md +35 -18
  65. package/dist/docs/docs/weaver-config.md +20 -0
  66. package/dist/docs/docs/weaver-task-queue.md +31 -19
  67. package/dist/docs/weaver-config.md +15 -9
  68. package/dist/mcp-tools.d.ts +17 -0
  69. package/dist/mcp-tools.d.ts.map +1 -1
  70. package/dist/mcp-tools.js +98 -232
  71. package/dist/mcp-tools.js.map +1 -1
  72. package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
  73. package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
  74. package/dist/node-types/orchestrator-dispatch.js +63 -0
  75. package/dist/node-types/orchestrator-dispatch.js.map +1 -0
  76. package/dist/node-types/orchestrator-load-state.d.ts +16 -0
  77. package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
  78. package/dist/node-types/orchestrator-load-state.js +60 -0
  79. package/dist/node-types/orchestrator-load-state.js.map +1 -0
  80. package/dist/node-types/orchestrator-route.d.ts +16 -0
  81. package/dist/node-types/orchestrator-route.d.ts.map +1 -0
  82. package/dist/node-types/orchestrator-route.js +28 -0
  83. package/dist/node-types/orchestrator-route.js.map +1 -0
  84. package/dist/node-types/receive-task.d.ts +2 -3
  85. package/dist/node-types/receive-task.d.ts.map +1 -1
  86. package/dist/node-types/receive-task.js +3 -28
  87. package/dist/node-types/receive-task.js.map +1 -1
  88. package/dist/templates/weaver-template.d.ts +11 -0
  89. package/dist/templates/weaver-template.d.ts.map +1 -0
  90. package/dist/templates/weaver-template.js +53 -0
  91. package/dist/templates/weaver-template.js.map +1 -0
  92. package/dist/ui/bot-constants.d.ts +14 -0
  93. package/dist/ui/bot-constants.d.ts.map +1 -0
  94. package/dist/ui/bot-constants.js +189 -0
  95. package/dist/ui/bot-constants.js.map +1 -0
  96. package/dist/ui/bot-panel.js +51 -90
  97. package/dist/ui/bot-slot-card.js +87 -122
  98. package/dist/ui/budget-bar.js +5 -3
  99. package/dist/ui/chat-task-result.js +4 -7
  100. package/dist/ui/decision-log.js +136 -0
  101. package/dist/ui/profile-card.js +158 -0
  102. package/dist/ui/profile-editor.js +597 -0
  103. package/dist/ui/swarm-controls.js +36 -27
  104. package/dist/ui/swarm-dashboard.js +2034 -736
  105. package/dist/ui/task-create-form.js +39 -116
  106. package/dist/ui/task-detail-view.js +490 -239
  107. package/dist/ui/task-pool-list.js +69 -94
  108. package/dist/workflows/orchestrator.d.ts +21 -0
  109. package/dist/workflows/orchestrator.d.ts.map +1 -0
  110. package/dist/workflows/orchestrator.js +281 -0
  111. package/dist/workflows/orchestrator.js.map +1 -0
  112. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  113. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  114. package/dist/workflows/weaver-bot-session.js +68 -0
  115. package/dist/workflows/weaver-bot-session.js.map +1 -0
  116. package/dist/workflows/weaver.d.ts +24 -0
  117. package/dist/workflows/weaver.d.ts.map +1 -0
  118. package/dist/workflows/weaver.js +28 -0
  119. package/dist/workflows/weaver.js.map +1 -0
  120. package/flowweaver.manifest.json +253 -66
  121. package/package.json +1 -1
  122. package/src/ai-chat-provider.ts +184 -18
  123. package/src/bot/ai-router.ts +132 -0
  124. package/src/bot/bot-registry.ts +2 -2
  125. package/src/bot/conversation-store.ts +2 -1
  126. package/src/bot/improve-loop.ts +6 -6
  127. package/src/bot/instance-manager.ts +128 -0
  128. package/src/bot/orchestrator.ts +244 -0
  129. package/src/bot/profile-store.ts +225 -0
  130. package/src/bot/profile-types.ts +141 -0
  131. package/src/bot/swarm-controller.ts +385 -186
  132. package/src/bot/task-prompt-builder.ts +37 -6
  133. package/src/bot/task-store.ts +28 -89
  134. package/src/bot/task-types.ts +10 -4
  135. package/src/cli-handlers.ts +2 -3
  136. package/src/docs/weaver-bot-usage.md +35 -18
  137. package/src/docs/weaver-config.md +20 -0
  138. package/src/docs/weaver-task-queue.md +31 -19
  139. package/src/mcp-tools.ts +129 -320
  140. package/src/node-types/orchestrator-dispatch.ts +71 -0
  141. package/src/node-types/orchestrator-load-state.ts +66 -0
  142. package/src/node-types/orchestrator-route.ts +33 -0
  143. package/src/node-types/receive-task.ts +3 -26
  144. package/src/ui/bot-constants.ts +192 -0
  145. package/src/ui/bot-panel.tsx +55 -79
  146. package/src/ui/bot-slot-card.tsx +69 -117
  147. package/src/ui/budget-bar.tsx +5 -3
  148. package/src/ui/chat-task-result.tsx +6 -9
  149. package/src/ui/decision-log.tsx +148 -0
  150. package/src/ui/profile-card.tsx +157 -0
  151. package/src/ui/profile-editor.tsx +384 -0
  152. package/src/ui/swarm-controls.tsx +35 -31
  153. package/src/ui/swarm-dashboard.tsx +409 -80
  154. package/src/ui/task-create-form.tsx +29 -119
  155. package/src/ui/task-detail-view.tsx +461 -215
  156. package/src/ui/task-pool-list.tsx +74 -95
  157. package/src/workflows/orchestrator.ts +302 -0
  158. package/dist/docs/weaver-bot-usage.md +0 -34
  159. package/dist/docs/weaver-genesis.md +0 -32
  160. package/dist/docs/weaver-task-queue.md +0 -34
  161. package/src/bot/error-guide.ts +0 -4
  162. package/src/bot/retry-utils.ts +0 -4
@@ -20,7 +20,8 @@
20
20
  const React = require('react');
21
21
  const { useState, useEffect, useCallback, useRef } = React;
22
22
  const {
23
- Flex, ScrollArea, EmptyState, Typography, usePackWorkspace,
23
+ Flex, ScrollArea, EmptyState, Typography, StatusIcon, Icon, Tag, Card, Tabs, SectionTitle, Button,
24
+ Input, IconButton, IconPicker, ColorPicker, toast, usePackWorkspace,
24
25
  } = require('@fw/plugin-ui-kit');
25
26
 
26
27
  // Local pack-specific components (bundled by esbuild)
@@ -30,38 +31,48 @@ import BotSlotCard from './bot-slot-card';
30
31
  import TaskPoolList from './task-pool-list';
31
32
  import TaskCreateForm from './task-create-form';
32
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';
33
38
 
34
39
  // ---------------------------------------------------------------------------
35
40
  // Types
36
41
  // ---------------------------------------------------------------------------
37
42
 
38
- interface BotSlotInfo {
39
- botId: string;
40
- botName: string;
43
+ interface InstanceInfo {
44
+ instanceId: string;
45
+ profileId: string;
46
+ index: number;
41
47
  status: 'idle' | 'executing' | 'paused' | 'stopped';
42
48
  currentTaskId?: string;
43
49
  currentRunId?: string;
44
50
  startedAt?: string;
45
51
  tokensUsed: number;
46
52
  cost: number;
53
+ tasksCompleted: number;
54
+ tasksFailed: number;
47
55
  }
48
56
 
49
- interface SwarmBudget {
50
- tokenLimit: number;
51
- tokensUsed: number;
52
- costLimit: number;
53
- costUsed: number;
57
+ interface SwarmBudgetLayer {
58
+ limitTokens: number;
59
+ usedTokens: number;
60
+ limitCost: number;
61
+ usedCost: number;
54
62
  }
55
63
 
56
64
  interface SwarmStatus {
57
65
  status: 'idle' | 'running' | 'paused' | 'stopping';
58
- bots: Record<string, BotSlotInfo>;
66
+ instances: Record<string, InstanceInfo>;
59
67
  maxConcurrent: number;
60
68
  tasksCompleted: number;
61
69
  tasksFailed: number;
62
70
  totalTokensUsed: number;
63
71
  totalCost: number;
64
- budget?: SwarmBudget;
72
+ budgets?: {
73
+ workspace: SwarmBudgetLayer;
74
+ session: SwarmBudgetLayer;
75
+ };
65
76
  startedAt?: string;
66
77
  }
67
78
 
@@ -74,7 +85,7 @@ interface PoolTask {
74
85
  priority: number;
75
86
  isParent: boolean;
76
87
  parentId?: string;
77
- assignedBots: string[];
88
+ assignedProfile?: string;
78
89
  createdAt: string;
79
90
  }
80
91
 
@@ -106,10 +117,19 @@ function SwarmDashboard() {
106
117
 
107
118
  // --- Navigation state ---
108
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);
109
124
 
110
125
  // --- Data state ---
111
126
  const [swarmStatus, setSwarmStatus] = useState<SwarmStatus | null>(null);
112
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>>>([]);
113
133
  const [loading, setLoading] = useState(true);
114
134
 
115
135
  // Refs to track mounted state for async cleanup
@@ -147,10 +167,54 @@ function SwarmDashboard() {
147
167
  }
148
168
  }, [callTool]);
149
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
+
150
214
  const refreshAll = useCallback(async () => {
151
- await Promise.all([fetchSwarmStatus(), fetchTaskList()]);
215
+ await Promise.all([fetchSwarmStatus(), fetchTaskList(), fetchBots(), fetchConfig(), fetchProfiles(), fetchOrchestratorStatus()]);
152
216
  if (mountedRef.current) setLoading(false);
153
- }, [fetchSwarmStatus, fetchTaskList]);
217
+ }, [fetchSwarmStatus, fetchTaskList, fetchBots, fetchConfig, fetchProfiles, fetchOrchestratorStatus]);
154
218
 
155
219
  // Initial fetch on mount
156
220
  useEffect(() => {
@@ -169,11 +233,44 @@ function SwarmDashboard() {
169
233
  return () => clearInterval(interval);
170
234
  }, [fetchTaskList]);
171
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
+
172
245
  // Listen for platform refresh events
173
246
  useEffect(() => {
174
247
  return onRefresh(() => refreshAll());
175
248
  }, [refreshAll, onRefresh]);
176
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
+
177
274
  // --- Navigation handlers ---
178
275
 
179
276
  const handleTaskClick = useCallback((taskId: string) => {
@@ -201,11 +298,13 @@ function SwarmDashboard() {
201
298
 
202
299
  // --- Render: Swarm Dashboard (default view) ---
203
300
 
204
- const bots = swarmStatus?.bots ?? {};
205
- const botEntries = Object.values(bots) as BotSlotInfo[];
206
- const hasBots = botEntries.length > 0;
207
- const hasBudget = !!(swarmStatus?.budget);
208
- const hasContent = hasBots || tasks.length > 0 || swarmStatus !== null;
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;
209
308
 
210
309
  return React.createElement(Flex, {
211
310
  variant: 'column-stretch-start-nowrap-0',
@@ -220,96 +319,326 @@ function SwarmDashboard() {
220
319
  // ── BudgetBar (below controls) ──────────────────────────────
221
320
  hasBudget && React.createElement(Flex, {
222
321
  variant: 'column-stretch-start-nowrap-4',
223
- style: {
224
- padding: '8px 16px',
225
- borderBottom: '1px solid var(--color-border-default)',
226
- flexShrink: 0,
227
- },
322
+ style: { padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid var(--color-border-default)' },
228
323
  },
229
324
  React.createElement(BudgetBar, {
230
325
  label: 'Tokens',
231
- used: swarmStatus!.budget!.tokensUsed,
232
- limit: swarmStatus!.budget!.tokenLimit,
326
+ used: sessionBudget!.usedTokens,
327
+ limit: sessionBudget!.limitTokens,
233
328
  unit: 'tokens',
234
329
  }),
235
330
  React.createElement(BudgetBar, {
236
331
  label: 'Cost',
237
- used: swarmStatus!.budget!.costUsed,
238
- limit: swarmStatus!.budget!.costLimit,
332
+ used: sessionBudget!.usedCost,
333
+ limit: sessionBudget!.limitCost,
239
334
  unit: 'USD',
240
335
  }),
241
336
  ),
242
337
 
243
338
  // ── Bot slot cards (horizontal scrollable row) ──────────────
244
- hasBots && React.createElement(Flex, {
245
- variant: 'row-start-start-nowrap-8',
246
- style: {
247
- padding: '8px 16px',
248
- overflowX: 'auto',
249
- overflowY: 'hidden',
250
- flexShrink: 0,
251
- borderBottom: '1px solid var(--color-border-default)',
252
- },
253
- },
254
- ...botEntries.map((bot: BotSlotInfo) =>
255
- React.createElement(BotSlotCard, {
256
- key: bot.botId,
257
- bot,
258
- currentTaskTitle: resolveTaskTitle(bot.currentTaskId, tasks),
259
- }),
260
- ),
261
- ),
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
+ }),
262
351
 
263
- // ── Main scrollable area ────────────────────────────────────
352
+ // ── Tab content ──────────────────────────────────────────────
264
353
  React.createElement(Flex, {
265
354
  variant: 'column-stretch-start-nowrap-0',
266
- style: { flex: 1, minHeight: 0 },
355
+ style: { flex: 1, minHeight: 0, overflow: 'auto' },
267
356
  },
268
- // Section header
269
- React.createElement(Flex, {
270
- variant: 'row-center-space-between-nowrap-8',
271
- style: {
272
- padding: '8px 16px 4px',
273
- flexShrink: 0,
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)' },
274
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 },
275
389
  },
276
- React.createElement(Typography, {
277
- variant: 'caption-bold',
278
- color: 'color-text-medium',
279
- }, `Task Pool (${tasks.length})`),
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
+ }),
280
515
  ),
281
516
 
282
- // Task pool list (scrollable)
283
- !hasContent && !loading && React.createElement(EmptyState, {
284
- icon: 'smartToy',
285
- message: 'Swarm not started',
286
- description: 'Start the swarm to begin processing tasks, or create tasks below.',
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,
287
527
  }),
288
528
 
289
- hasContent && React.createElement(Flex, {
529
+ // Profiles tab — default list view
530
+ activeTab === 'profiles' && profileEditorMode == null && React.createElement(Flex, {
290
531
  variant: 'column-stretch-start-nowrap-0',
291
532
  style: { flex: 1, minHeight: 0 },
292
533
  },
293
- React.createElement(TaskPoolList, {
294
- tasks,
295
- onTaskClick: handleTaskClick,
296
- }),
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
+
297
592
  ),
298
- ),
299
593
 
300
- // ── Task create form (bottom, collapsible) ──────────────────
301
- React.createElement(Flex, {
302
- variant: 'column-stretch-start-nowrap-0',
303
- style: {
304
- padding: '8px 16px',
305
- borderTop: '1px solid var(--color-border-default)',
306
- flexShrink: 0,
594
+ // Config tab
595
+ activeTab === 'config' && React.createElement(Flex, {
596
+ variant: 'column-stretch-start-nowrap-12',
597
+ style: { padding: '12px 16px' },
307
598
  },
308
- },
309
- React.createElement(TaskCreateForm, {
310
- onTaskCreated: handleTaskCreated,
311
- }),
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
+ ),
312
640
  ),
641
+
313
642
  );
314
643
  }
315
644