@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
@@ -2,7 +2,7 @@
2
2
  * Task Detail View — full task detail with context, subtasks, and run history.
3
3
  *
4
4
  * Shows a single task's complete information:
5
- * - Header: title, status, assigned bots, priority, attempt count
5
+ * - Header: title, status, assigned profile, priority, attempt count
6
6
  * - Context: files list, notes
7
7
  * - Subtasks: list with status icons (clickable)
8
8
  * - Run history: each run as a TaskBlock
@@ -12,10 +12,9 @@
12
12
  const React = require('react');
13
13
  const { useState, useEffect, useCallback, useRef, useMemo } = React;
14
14
  const {
15
- Flex, Typography, ScrollArea, StatusIcon, Tag, Badge, Icon, TaskBlock, Button,
16
- toast, usePackWorkspace, useEventStream,
15
+ Flex, Typography, ScrollArea, StatusIcon, Tag, Badge, Icon, IconButton, TaskBlock, Button,
16
+ Card, Chip, Checkbox, Table, Tabs, EmptyState, toast, usePackWorkspace, useEventStream,
17
17
  } = require('@fw/plugin-ui-kit');
18
- const { styled } = require('@fw/plugin-theme');
19
18
 
20
19
  import { useStreamTimeline } from './use-stream-timeline';
21
20
  import { traceToTimeline } from './trace-to-timeline';
@@ -53,7 +52,6 @@ interface Task {
53
52
  isParent: boolean;
54
53
  parentId?: string;
55
54
  dependsOn: string[];
56
- assignedBots: string[];
57
55
  currentBotId?: string;
58
56
  currentRunId?: string;
59
57
  context: TaskContext;
@@ -67,6 +65,8 @@ interface Task {
67
65
  createdAt: string;
68
66
  updatedAt: string;
69
67
  completedAt?: string;
68
+ assignedProfile?: string;
69
+ routingReason?: string;
70
70
  }
71
71
 
72
72
  interface Subtask {
@@ -74,7 +74,7 @@ interface Subtask {
74
74
  title: string;
75
75
  status: TaskStatus;
76
76
  priority: number;
77
- assignedBots: string[];
77
+ assignedProfile?: string;
78
78
  attempt: number;
79
79
  }
80
80
 
@@ -108,88 +108,94 @@ const statusToLabel: Record<TaskStatus, string> = {
108
108
  const priorityLabel = (p: number) => p >= 3 ? 'High' : p === 2 ? 'Medium' : p === 1 ? 'Low' : 'None';
109
109
 
110
110
  // ---------------------------------------------------------------------------
111
- // Styled components
111
+ // Inline styles for replaced styled components
112
112
  // ---------------------------------------------------------------------------
113
113
 
114
- const Container = styled.div({
115
- display: 'flex',
116
- flexDirection: 'column',
117
- width: '100%',
118
- height: '100%',
119
- overflow: 'hidden',
120
- });
121
-
122
- const Header = styled.div({
114
+ const headerStyle: React.CSSProperties = {
123
115
  padding: '12px 16px',
124
- borderBottom: '1px solid $color-border-default',
125
- display: 'flex',
126
- flexDirection: 'column',
127
- gap: '8px',
116
+ borderBottom: '1px solid var(--color-border-default)',
128
117
  flexShrink: 0,
129
- });
130
-
131
- const HeaderTopRow = styled.div({
132
- display: 'flex',
133
- alignItems: 'center',
134
- gap: '8px',
135
- });
136
-
137
- const HeaderMeta = styled.div({
138
- display: 'flex',
139
- alignItems: 'center',
140
- gap: '8px',
141
- flexWrap: 'wrap',
142
- });
143
-
144
- const Section = styled.div({
145
- padding: '12px 16px',
146
- borderBottom: '1px solid $color-border-default',
147
- });
148
-
149
- const SectionTitle = styled.div({
150
- marginBottom: '8px',
151
- });
118
+ };
152
119
 
153
- const FileList = styled.div({
154
- display: 'flex',
155
- flexDirection: 'column',
156
- gap: '2px',
157
- });
120
+ const sectionStyle: React.CSSProperties = {
121
+ padding: '12px 16px',
122
+ borderBottom: '1px solid var(--color-border-default)',
123
+ };
158
124
 
159
- const FileItem = styled.div({
125
+ const fileItemStyle: React.CSSProperties = {
160
126
  fontFamily: 'monospace',
161
127
  fontSize: '12px',
162
- color: '$color-text-mid',
128
+ color: 'var(--color-text-mid)',
163
129
  padding: '2px 4px',
164
130
  borderRadius: '4px',
165
- '&:hover': { backgroundColor: '$color-surface-elevated' },
166
- });
131
+ };
167
132
 
168
- const NotesBlock = styled.div({
133
+ const fileItemHoverStyle: React.CSSProperties = {
134
+ ...fileItemStyle,
135
+ backgroundColor: 'var(--color-surface-elevated)',
136
+ };
137
+
138
+ const notesBlockStyle: React.CSSProperties = {
169
139
  whiteSpace: 'pre-wrap',
170
140
  fontSize: '13px',
171
- color: '$color-text-mid',
141
+ color: 'var(--color-text-mid)',
172
142
  lineHeight: 1.5,
173
143
  padding: '4px 0',
174
- });
144
+ };
175
145
 
176
- const SubtaskRow = styled.div({
177
- display: 'flex',
178
- alignItems: 'center',
179
- gap: '8px',
146
+ const subtaskRowStyle: React.CSSProperties = {
180
147
  padding: '6px 8px',
181
148
  cursor: 'pointer',
182
149
  borderRadius: '6px',
183
- '&:hover': { backgroundColor: '$color-surface-elevated' },
184
- });
150
+ };
151
+
152
+ const subtaskRowHoverStyle: React.CSSProperties = {
153
+ ...subtaskRowStyle,
154
+ backgroundColor: 'var(--color-surface-elevated)',
155
+ };
185
156
 
186
- const SubtaskTitle = styled.div({
187
- flex: 1,
188
- overflow: 'hidden',
189
- textOverflow: 'ellipsis',
190
- whiteSpace: 'nowrap',
191
- minWidth: 0,
192
- });
157
+ // ---------------------------------------------------------------------------
158
+ // Small hover-wrapper components
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function FileItemRow({ children }: { children: React.ReactNode }) {
162
+ const [hovered, setHovered] = useState(false);
163
+ return React.createElement(Flex, {
164
+ variant: 'row-center-start-nowrap-0',
165
+ style: hovered ? fileItemHoverStyle : fileItemStyle,
166
+ onMouseEnter: () => setHovered(true),
167
+ onMouseLeave: () => setHovered(false),
168
+ }, children);
169
+ }
170
+
171
+ function SubtaskRowItem({ sub, onBack }: { sub: Subtask; onBack: () => void }) {
172
+ const [hovered, setHovered] = useState(false);
173
+ return React.createElement(Flex, {
174
+ variant: 'row-center-start-nowrap-8',
175
+ style: hovered ? subtaskRowHoverStyle : subtaskRowStyle,
176
+ onClick: () => onBack(),
177
+ onMouseEnter: () => setHovered(true),
178
+ onMouseLeave: () => setHovered(false),
179
+ },
180
+ React.createElement(StatusIcon, {
181
+ status: statusToIcon[sub.status] || 'pending',
182
+ size: 'sm',
183
+ }),
184
+ React.createElement(Flex, {
185
+ variant: 'row-center-start-nowrap-0',
186
+ style: { flex: 1, minWidth: 0 },
187
+ },
188
+ React.createElement(Typography, { variant: 'caption-regular', truncate: true }, sub.title),
189
+ ),
190
+ sub.assignedProfile && React.createElement(Tag, {
191
+ size: 'small', color: 'info',
192
+ }, sub.assignedProfile),
193
+ React.createElement(Typography, {
194
+ variant: 'caption-regular',
195
+ color: 'color-text-subtle',
196
+ }, `#${sub.attempt}`),
197
+ );
198
+ }
193
199
 
194
200
  // ---------------------------------------------------------------------------
195
201
  // Component
@@ -209,15 +215,19 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
209
215
  // ── Fetch task data ──
210
216
  const fetchTask = useCallback(async () => {
211
217
  try {
212
- const data = (await callTool('fw_weaver_task_get', { id: taskId })) as Record<string, unknown> | null;
218
+ const raw = await callTool('fw_weaver_task_get', { id: taskId });
219
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
213
220
  if (!data) return;
214
221
 
215
- const t = data as unknown as Task;
222
+ const t = (data.task ?? data) as Task;
216
223
  setTask(t);
217
224
 
218
- // If parent task, fetch subtasks from the task list
219
- if (t.isParent) {
220
- const listData = (await callTool('fw_weaver_task_list', { parentId: taskId })) as unknown[];
225
+ // Subtasks from response or fetch separately
226
+ if (data.subtasks && Array.isArray(data.subtasks)) {
227
+ setSubtasks(data.subtasks as Subtask[]);
228
+ } else if (t.isParent) {
229
+ const listRaw = await callTool('fw_weaver_task_list', { parentId: taskId });
230
+ const listData = typeof listRaw === 'string' ? JSON.parse(listRaw) : listRaw;
221
231
  if (Array.isArray(listData)) {
222
232
  setSubtasks(listData as Subtask[]);
223
233
  }
@@ -229,7 +239,8 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
229
239
 
230
240
  const fetchHistory = useCallback(async () => {
231
241
  try {
232
- const data = (await callTool('fw_weaver_history', { taskId })) as unknown;
242
+ const raw = await callTool('fw_weaver_history', { taskId });
243
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
233
244
  if (Array.isArray(data)) {
234
245
  setHistory(
235
246
  data.map((r: Record<string, unknown>) => {
@@ -280,6 +291,72 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
280
291
  return () => stream.stop();
281
292
  }, [isLive, currentRunId, packId]);
282
293
 
294
+ // ── Detail tab state ──
295
+ const [detailTab, setDetailTab] = useState('runs');
296
+
297
+ // ── Action state ──
298
+ const [actionLoading, setActionLoading] = useState<string | null>(null);
299
+ const [availableProfiles, setAvailableProfiles] = useState<Array<{ id: string; name: string; icon?: string; color?: string }>>([]);
300
+
301
+ // Fetch profiles for assign dropdown
302
+ useEffect(() => {
303
+ callTool('fw_weaver_profile_list', {}).then((raw: unknown) => {
304
+ const data = typeof raw === 'string' ? JSON.parse(raw as string) : raw;
305
+ if (Array.isArray(data)) setAvailableProfiles(data);
306
+ }).catch(() => {});
307
+ }, [callTool]);
308
+
309
+ const handleRetry = useCallback(async () => {
310
+ setActionLoading('retry');
311
+ try {
312
+ await callTool('fw_weaver_task_retry', { id: taskId });
313
+ toast('Task retried, back in pool', { type: 'success' });
314
+ fetchTask();
315
+ } catch (err: unknown) {
316
+ toast(err instanceof Error ? err.message : 'Failed to retry', { type: 'error' });
317
+ }
318
+ setActionLoading(null);
319
+ }, [callTool, taskId, fetchTask]);
320
+
321
+ const handleCancel = useCallback(async () => {
322
+ const ok = await ctx.confirm('Cancel this task? It cannot be undone.', {
323
+ title: 'Cancel Task',
324
+ confirmLabel: 'Cancel Task',
325
+ state: 'warning',
326
+ });
327
+ if (!ok) return;
328
+ setActionLoading('cancel');
329
+ try {
330
+ await callTool('fw_weaver_task_cancel', { id: taskId });
331
+ toast('Task cancelled', { type: 'info' });
332
+ fetchTask();
333
+ } catch (err: unknown) {
334
+ toast(err instanceof Error ? err.message : 'Failed to cancel', { type: 'error' });
335
+ }
336
+ setActionLoading(null);
337
+ }, [callTool, taskId, fetchTask, ctx]);
338
+
339
+ const handleAssignProfile = useCallback(async (profileId: string) => {
340
+ setActionLoading('assign-profile');
341
+ try {
342
+ const newProfile = task?.assignedProfile === profileId ? undefined : profileId;
343
+ await callTool('fw_weaver_task_update', { id: taskId, assignedProfile: newProfile ?? null });
344
+ toast(newProfile ? `Assigned profile ${profileId}` : `Unassigned profile`, { type: 'success' });
345
+ fetchTask();
346
+ } catch (err: unknown) {
347
+ toast(err instanceof Error ? err.message : 'Failed to assign profile', { type: 'error' });
348
+ }
349
+ setActionLoading(null);
350
+ }, [callTool, taskId, task, fetchTask]);
351
+
352
+ const handlePriorityChange = useCallback(async (delta: number) => {
353
+ const newPriority = Math.max(0, (task?.priority ?? 0) + delta);
354
+ try {
355
+ await callTool('fw_weaver_task_update', { id: taskId, priority: newPriority });
356
+ fetchTask();
357
+ } catch { /* non-fatal */ }
358
+ }, [callTool, taskId, task, fetchTask]);
359
+
283
360
  // ── Run expansion state ──
284
361
  const [expandedRunId, setExpandedRunId] = useState<string | null>(null);
285
362
  const [liveExpanded, setLiveExpanded] = useState(true);
@@ -361,8 +438,14 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
361
438
 
362
439
  // ── Loading state ──
363
440
  if (loading && !task) {
364
- return React.createElement(Container, null,
365
- React.createElement(Header, null,
441
+ return React.createElement(Flex, {
442
+ variant: 'column-stretch-start-nowrap-0',
443
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
444
+ },
445
+ React.createElement(Flex, {
446
+ variant: 'column-stretch-start-nowrap-8',
447
+ style: headerStyle,
448
+ },
366
449
  React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
367
450
  React.createElement(Icon, { name: 'arrowBack', size: 16 }),
368
451
  ' Back',
@@ -370,7 +453,7 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
370
453
  ),
371
454
  React.createElement(Flex, {
372
455
  variant: 'column-center-center-nowrap-0',
373
- style: { flex: 1, padding: '24px' },
456
+ style: { flex: 1 },
374
457
  },
375
458
  React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
376
459
  'Loading task...',
@@ -380,8 +463,14 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
380
463
  }
381
464
 
382
465
  if (!task) {
383
- return React.createElement(Container, null,
384
- React.createElement(Header, null,
466
+ return React.createElement(Flex, {
467
+ variant: 'column-stretch-start-nowrap-0',
468
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
469
+ },
470
+ React.createElement(Flex, {
471
+ variant: 'column-stretch-start-nowrap-8',
472
+ style: headerStyle,
473
+ },
385
474
  React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
386
475
  React.createElement(Icon, { name: 'arrowBack', size: 16 }),
387
476
  ' Back',
@@ -389,7 +478,7 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
389
478
  ),
390
479
  React.createElement(Flex, {
391
480
  variant: 'column-center-center-nowrap-0',
392
- style: { flex: 1, padding: '24px' },
481
+ style: { flex: 1 },
393
482
  },
394
483
  React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
395
484
  'Task not found.',
@@ -402,187 +491,344 @@ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
402
491
  const hasSubtasks = task.isParent && subtasks.length > 0;
403
492
  const hasRuns = runItems.length > 0 || isLive;
404
493
 
405
- return React.createElement(Container, null,
494
+ return React.createElement(Flex, {
495
+ variant: 'column-stretch-start-nowrap-0',
496
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
497
+ },
406
498
  // ── Header ──
407
- React.createElement(Header, null,
408
- // Back button
409
- React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
410
- React.createElement(Icon, { name: 'arrowBack', size: 16 }),
411
- ' Back',
412
- ),
413
-
414
- // Title row
415
- React.createElement(HeaderTopRow, null,
499
+ React.createElement(Flex, {
500
+ variant: 'column-stretch-start-nowrap-6',
501
+ style: { ...headerStyle, padding: '12px 16px', borderBottom: '1px solid var(--color-border-default)' },
502
+ },
503
+ // Top row: back + title + status
504
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
505
+ React.createElement(IconButton, {
506
+ icon: 'back', size: 'xs', variant: 'clear',
507
+ onClick: onBack,
508
+ }),
416
509
  React.createElement(StatusIcon, {
417
510
  status: statusToIcon[task.status] || 'pending',
418
- size: 'md',
511
+ size: 'sm',
419
512
  }),
420
- React.createElement(Typography, { variant: 'body-semibold' }, task.title),
513
+ React.createElement(Typography, {
514
+ variant: 'caption-thick',
515
+ color: 'color-text-high',
516
+ style: { flex: 1, minWidth: 0, wordBreak: 'break-word' },
517
+ }, task.title || 'Untitled Task'),
421
518
  ),
422
519
 
423
- // Meta row: status label, assigned bots, priority, attempts
424
- React.createElement(HeaderMeta, null,
520
+ // Meta row: status tag, assigned profile, priority, attempts
521
+ React.createElement(Flex, { variant: 'row-center-start-wrap-6' },
425
522
  React.createElement(Tag, {
426
523
  size: 'small',
427
524
  color: task.status === 'done' ? 'positive'
428
525
  : task.status === 'failed' ? 'negative'
429
526
  : task.status === 'in-progress' ? 'info'
430
527
  : task.status === 'blocked' ? 'caution'
431
- : 'default',
432
- }, statusToLabel[task.status]),
528
+ : 'secondary',
529
+ }, statusToLabel[task.status] || task.status || 'pending'),
433
530
 
434
- // Assigned bots
435
- ...task.assignedBots.map((botId: string) =>
436
- React.createElement(Tag, { key: `bot-${botId}`, size: 'small', color: 'info' }, botId),
437
- ),
531
+ task.assignedProfile && React.createElement(Tag, { key: `profile-${task.assignedProfile}`, size: 'small', color: 'info' }, task.assignedProfile),
438
532
 
439
- // Priority
440
- task.priority > 0 && React.createElement(Badge, {
441
- variant: task.priority >= 3 ? 'warning' : 'info',
442
- }, `P${task.priority} ${priorityLabel(task.priority)}`),
533
+ task.priority > 0 && React.createElement(Tag, {
534
+ size: 'small', color: task.priority >= 3 ? 'caution' : 'info',
535
+ }, `P${task.priority}`),
443
536
 
444
- // Attempt count
445
- React.createElement(Typography, {
446
- variant: 'caption-regular',
537
+ (task.attempt != null && task.maxAttempts != null) && React.createElement(Typography, {
538
+ variant: 'smallCaption-regular',
447
539
  color: 'color-text-subtle',
448
540
  }, `Attempt ${task.attempt}/${task.maxAttempts}`),
449
541
  ),
450
542
 
451
543
  // Description
452
544
  task.description && React.createElement(Typography, {
453
- variant: 'caption-regular',
454
- color: 'color-text-mid',
455
- style: { marginTop: '4px' },
545
+ variant: 'smallCaption-regular',
546
+ color: 'color-text-medium',
456
547
  }, task.description),
548
+
549
+ // Profile routing info
550
+ task.assignedProfile && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-2' },
551
+ React.createElement(Typography, {
552
+ variant: 'smallCaption-regular',
553
+ color: 'color-text-medium',
554
+ }, `Profile: ${task.assignedProfile}`),
555
+ task.routingReason && React.createElement(Typography, {
556
+ variant: 'smallCaption-regular',
557
+ color: 'color-text-subtle',
558
+ }, task.routingReason),
559
+ ),
560
+
561
+ // (Actions moved to tab)
457
562
  ),
458
563
 
459
- // ── Scrollable content ──
564
+ // ── Tabs ──
565
+ React.createElement(Tabs, {
566
+ tabs: [
567
+ { id: 'runs', title: `Runs (${runItems.length}${isLive ? '+1' : ''})` },
568
+ ...(hasSubtasks ? [{ id: 'subtasks', title: `Subtasks (${subtasks.filter((s: Subtask) => s.status === 'done').length}/${subtasks.length})` }] : []),
569
+ ...(task.status !== 'done' && task.status !== 'cancelled' ? [{ id: 'actions', title: 'Actions' }] : []),
570
+ ...(hasContext ? [{ id: 'context', title: 'Context' }] : []),
571
+ ],
572
+ activeTabId: detailTab,
573
+ onSelectTab: (id: string) => setDetailTab(id),
574
+ size: 'sm',
575
+ }),
576
+
577
+ // ── Tab content ──
460
578
  React.createElement(ScrollArea, { ref: scrollRef, style: { flex: 1 } },
461
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-0' },
579
+ React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8', style: { padding: '12px 16px' } },
580
+
581
+ // ── Runs tab ──
582
+ detailTab === 'runs' && (hasRuns || (task.runs?.length ?? 0) > 0)
583
+ ? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
584
+ // Historical runs
585
+ ...runItems.map(({ run, runTimeline }: { run: HistoricalRun; runTimeline: Array<Record<string, unknown>> }) => {
586
+ const runId = run.id;
587
+ const isExpanded = expandedRunId === runId;
588
+ const isSuccess = run.outcome === 'completed' || run.success === true;
589
+ return React.createElement(TaskBlock, {
590
+ key: `run-${runId}`,
591
+ state: isSuccess ? 'completed' : 'failed',
592
+ instruction: extractInstruction(run),
593
+ timeline: runTimeline,
594
+ cost: typeof run.cost === 'number' ? run.cost : ((run.costDetail?.totalCost as number) ?? null),
595
+ plan: run.plan,
596
+ startedAt: run.startedAt,
597
+ durationMs: run.durationMs ?? run.duration,
598
+ expanded: isExpanded,
599
+ onToggleExpand: () => toggleExpand(runId),
600
+ });
601
+ }),
602
+ // Live run
603
+ isLive && React.createElement(TaskBlock, {
604
+ key: 'live-run',
605
+ state: 'running',
606
+ instruction: liveInstruction ?? task.title,
607
+ timeline: liveTimeline,
608
+ phase: livePhase,
609
+ elapsed,
610
+ cost: liveCost,
611
+ plan,
612
+ error: stream.error,
613
+ approval: approvalStatus ?? undefined,
614
+ approvalLoading,
615
+ onApprove: handleApprove,
616
+ onReject: handleReject,
617
+ expanded: liveExpanded,
618
+ onToggleExpand: () => setLiveExpanded((v: boolean) => !v),
619
+ }),
620
+ )
621
+ : detailTab === 'runs' && React.createElement(EmptyState, {
622
+ icon: 'smartToy',
623
+ message: 'No runs yet',
624
+ description: 'This task has not been executed yet.',
625
+ }),
626
+
627
+ // ── Subtasks tab ──
628
+ detailTab === 'subtasks' && hasSubtasks && React.createElement(Flex, {
629
+ variant: 'column-stretch-start-nowrap-0',
630
+ },
631
+ ...(subtasks ?? []).map((sub: Subtask) =>
632
+ React.createElement(SubtaskRowItem, {
633
+ key: sub.id,
634
+ sub,
635
+ onBack,
636
+ }),
637
+ ),
638
+ ),
639
+
640
+ // ── Actions tab ──
641
+ detailTab === 'actions' && React.createElement(Flex, {
642
+ variant: 'column-stretch-start-nowrap-16',
643
+ },
644
+ // Status actions
645
+ React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
646
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Status'),
647
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
648
+ task.status === 'failed' && React.createElement(Button, {
649
+ size: 'xs', variant: 'fill', color: 'primary',
650
+ onClick: handleRetry,
651
+ loading: actionLoading === 'retry',
652
+ disabled: !!actionLoading,
653
+ }, 'Retry Task'),
654
+
655
+ (task.status === 'pending' || task.status === 'in-progress' || task.status === 'blocked') && React.createElement(Button, {
656
+ size: 'xs', variant: 'outlined', color: 'danger',
657
+ onClick: handleCancel,
658
+ loading: actionLoading === 'cancel',
659
+ disabled: !!actionLoading,
660
+ }, 'Cancel Task'),
661
+ ),
662
+ ),
462
663
 
463
- // ── Context section ──
464
- hasContext && React.createElement(Section, null,
465
- React.createElement(SectionTitle, null,
466
- React.createElement(Typography, { variant: 'caption-semibold', color: 'color-text-high' }, 'Context'),
664
+ // Priority
665
+ React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
666
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Priority'),
667
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
668
+ React.createElement(IconButton, {
669
+ icon: 'expandLess', size: 'xs', variant: 'outlined',
670
+ onClick: () => handlePriorityChange(1),
671
+ title: 'Increase priority',
672
+ }),
673
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
674
+ `P${task.priority ?? 0}`),
675
+ React.createElement(IconButton, {
676
+ icon: 'expandMore', size: 'xs', variant: 'outlined',
677
+ onClick: () => handlePriorityChange(-1),
678
+ title: 'Decrease priority',
679
+ }),
680
+ ),
467
681
  ),
468
682
 
683
+ // Assign Profile
684
+ availableProfiles.length > 0 && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
685
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Assign Profile'),
686
+ React.createElement(Table, {
687
+ size: 'compact',
688
+ getRowKey: (row: Record<string, unknown>) => row.id as string,
689
+ columns: [
690
+ {
691
+ key: 'icon',
692
+ header: '',
693
+ width: '30px',
694
+ render: (_: unknown, row: Record<string, unknown>) =>
695
+ React.createElement(Icon, {
696
+ name: (row.icon as string) || 'smartToy',
697
+ size: 14,
698
+ color: (row.color as string) || 'color-text-medium',
699
+ }),
700
+ },
701
+ {
702
+ key: 'name',
703
+ header: 'Profile',
704
+ },
705
+ {
706
+ key: 'capabilities',
707
+ header: 'Capabilities',
708
+ render: (_: unknown, row: Record<string, unknown>) => {
709
+ const caps = (row.capabilities as Array<{ name: string; description: string }>) || [];
710
+ if (caps.length === 0) return null;
711
+ return React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
712
+ ...caps.slice(0, 4).map((cap) =>
713
+ React.createElement('span', { key: cap.name, title: cap.description },
714
+ React.createElement(Chip, { label: cap.name, size: 'small', color: 'color-brand-main' }),
715
+ )
716
+ ),
717
+ caps.length > 4 && React.createElement(Typography, {
718
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
719
+ }, `+${caps.length - 4}`),
720
+ );
721
+ },
722
+ },
723
+ {
724
+ key: 'assigned',
725
+ header: 'Assign',
726
+ width: '50px',
727
+ align: 'right' as const,
728
+ render: (_: unknown, row: Record<string, unknown>) => {
729
+ const isAssigned = task.assignedProfile === (row.id as string);
730
+ return React.createElement(Checkbox, {
731
+ checked: isAssigned,
732
+ onChange: () => handleAssignProfile(row.id as string),
733
+ size: 'sm',
734
+ });
735
+ },
736
+ },
737
+ ],
738
+ data: availableProfiles,
739
+ }),
740
+ ),
741
+ ),
742
+
743
+ // ── Context tab ──
744
+ detailTab === 'context' && hasContext && React.createElement(Flex, {
745
+ variant: 'column-stretch-start-nowrap-12',
746
+ },
469
747
  // Files
470
- task.context.files.length > 0 && React.createElement(Flex, {
748
+ (task.context?.files?.length ?? 0) > 0 && React.createElement(Flex, {
471
749
  variant: 'column-stretch-start-nowrap-4',
472
- style: { marginBottom: '8px' },
473
750
  },
474
- React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' }, 'Files:'),
475
- React.createElement(FileList, null,
476
- ...task.context.files.map((file: string) =>
477
- React.createElement(FileItem, { key: file }, file),
478
- ),
751
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Files'),
752
+ ...(task.context?.files ?? []).map((file: string) =>
753
+ React.createElement(Typography, {
754
+ key: file,
755
+ variant: 'smallCaption-regular',
756
+ color: 'color-text-high',
757
+ style: { fontFamily: 'var(--font-mono, monospace)' },
758
+ }, file),
479
759
  ),
480
760
  ),
481
761
 
482
762
  // Notes
483
- task.context.notes && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-4' },
484
- React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' }, 'Notes:'),
485
- React.createElement(NotesBlock, null, task.context.notes),
763
+ task.context?.notes && React.createElement(Flex, {
764
+ variant: 'column-stretch-start-nowrap-4',
765
+ },
766
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Notes'),
767
+ React.createElement(Typography, {
768
+ variant: 'smallCaption-regular',
769
+ color: 'color-text-high',
770
+ style: { whiteSpace: 'pre-wrap' },
771
+ }, task.context.notes),
486
772
  ),
487
- ),
488
773
 
489
- // ── Subtasks section ──
490
- hasSubtasks && React.createElement(Section, null,
491
- React.createElement(SectionTitle, null,
492
- React.createElement(Typography, { variant: 'caption-semibold', color: 'color-text-high' },
493
- `Subtasks (${subtasks.filter((s: Subtask) => s.status === 'done').length}/${subtasks.length} done)`,
494
- ),
774
+ // Last error
775
+ task.context?.lastError && React.createElement(Flex, {
776
+ variant: 'column-stretch-start-nowrap-4',
777
+ },
778
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-status-negative' }, 'Last Error'),
779
+ React.createElement(Typography, {
780
+ variant: 'smallCaption-regular',
781
+ color: 'color-text-high',
782
+ style: { fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'pre-wrap' },
783
+ }, task.context.lastError),
495
784
  ),
496
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-0' },
497
- ...subtasks.map((sub: Subtask) =>
498
- React.createElement(SubtaskRow, {
499
- key: sub.id,
500
- onClick: () => {
501
- // Navigate to subtask detail reuse same callback pattern
502
- // The parent dashboard handles navigation by changing taskId
503
- onBack();
504
- // Small delay so dashboard re-renders, then we'd need parent to support subtask nav
505
- // For now, this is a placeholder — the dashboard's onTaskClick handles it
506
- },
785
+
786
+ // Run summaries (accumulated context)
787
+ (task.context?.runSummaries?.length ?? 0) > 0 && React.createElement(Flex, {
788
+ variant: 'column-stretch-start-nowrap-6',
789
+ },
790
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Run Summaries'),
791
+ ...(task.context?.runSummaries ?? []).map((rs: Record<string, unknown>, i: number) =>
792
+ React.createElement(Card, {
793
+ key: `rs-${i}`,
794
+ variant: 'bordered',
795
+ padding: 'compact',
796
+ style: { gap: '4px' },
507
797
  },
508
- React.createElement(StatusIcon, {
509
- status: statusToIcon[sub.status] || 'pending',
510
- size: 'sm',
511
- }),
512
- React.createElement(SubtaskTitle, null,
513
- React.createElement(Typography, { variant: 'caption-regular' }, sub.title),
798
+ React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
799
+ React.createElement(Typography, {
800
+ variant: 'smallCaption-thick',
801
+ color: rs.outcome === 'success' ? 'color-status-positive' : 'color-status-negative',
802
+ }, `${rs.outcome === 'success' ? 'Success' : 'Failed'} (${(rs as Record<string, unknown>).botId ?? 'unknown bot'})`),
803
+ React.createElement(Typography, {
804
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
805
+ }, `${Math.round((rs.durationMs as number ?? 0) / 1000)}s · ${rs.tokensUsed ?? 0} tok · $${((rs.cost as number) ?? 0).toFixed(3)}`),
514
806
  ),
515
- sub.assignedBots.length > 0 && React.createElement(Tag, {
516
- size: 'small', color: 'info',
517
- }, sub.assignedBots[0]),
518
807
  React.createElement(Typography, {
519
- variant: 'caption-regular',
520
- color: 'color-text-subtle',
521
- }, `#${sub.attempt}`),
808
+ variant: 'smallCaption-regular', color: 'color-text-medium',
809
+ }, rs.summary as string ?? ''),
810
+ (rs.filesModified as string[] ?? []).length > 0 && React.createElement(Typography, {
811
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
812
+ style: { fontFamily: 'var(--font-mono, monospace)' },
813
+ }, `Files: ${(rs.filesModified as string[]).join(', ')}`),
814
+ rs.error && React.createElement(Typography, {
815
+ variant: 'smallCaption-regular', color: 'color-status-negative',
816
+ }, `Error: ${rs.error}`),
522
817
  ),
523
818
  ),
524
819
  ),
525
- ),
526
820
 
527
- // ── Run history section ──
528
- (hasRuns || task.runs.length > 0) && React.createElement(Section, { style: { borderBottom: 'none' } },
529
- React.createElement(SectionTitle, null,
530
- React.createElement(Typography, { variant: 'caption-semibold', color: 'color-text-high' },
531
- `Run History (${runItems.length}${isLive ? ' + 1 live' : ''})`,
532
- ),
533
- ),
534
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
535
-
536
- // Historical runs
537
- ...runItems.map(({ run, runTimeline }: { run: HistoricalRun; runTimeline: Array<Record<string, unknown>> }) => {
538
- const runId = run.id;
539
- const isExpanded = expandedRunId === runId;
540
- const isSuccess = run.outcome === 'completed' || run.success === true;
541
- return React.createElement(TaskBlock, {
542
- key: `run-${runId}`,
543
- state: isSuccess ? 'completed' : 'failed',
544
- instruction: extractInstruction(run),
545
- timeline: runTimeline,
546
- cost: typeof run.cost === 'number' ? run.cost : ((run.costDetail?.totalCost as number) ?? null),
547
- plan: run.plan,
548
- startedAt: run.startedAt,
549
- durationMs: run.durationMs ?? run.duration,
550
- expanded: isExpanded,
551
- onToggleExpand: () => toggleExpand(runId),
552
- });
553
- }),
554
-
555
- // Live run
556
- isLive && React.createElement(TaskBlock, {
557
- key: 'live-run',
558
- state: 'running',
559
- instruction: liveInstruction ?? task.title,
560
- timeline: liveTimeline,
561
- phase: livePhase,
562
- elapsed,
563
- cost: liveCost,
564
- plan,
565
- error: stream.error,
566
- approval: approvalStatus ?? undefined,
567
- approvalLoading,
568
- onApprove: handleApprove,
569
- onReject: handleReject,
570
- expanded: liveExpanded,
571
- onToggleExpand: () => setLiveExpanded((v: boolean) => !v),
572
- }),
573
- ),
574
- ),
575
-
576
- // Empty state when no runs and no live
577
- !hasRuns && task.runs.length === 0 && React.createElement(Section, { style: { borderBottom: 'none' } },
578
- React.createElement(Flex, {
579
- variant: 'column-center-center-nowrap-0',
580
- style: { padding: '16px' },
821
+ // Budget
822
+ (task.tokensUsed > 0 || task.costUsed > 0) && React.createElement(Flex, {
823
+ variant: 'column-stretch-start-nowrap-4',
581
824
  },
582
- React.createElement(Typography, {
583
- variant: 'caption-regular',
584
- color: 'color-text-subtle',
585
- }, 'No runs yet for this task.'),
825
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Budget'),
826
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-16' },
827
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
828
+ `Tokens: ${task.tokensUsed?.toLocaleString() ?? 0}${task.budgetTokens ? ` / ${task.budgetTokens.toLocaleString()}` : ''}`),
829
+ React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
830
+ `Cost: $${(task.costUsed ?? 0).toFixed(3)}${task.budgetCost ? ` / $${task.budgetCost.toFixed(2)}` : ''}`),
831
+ ),
586
832
  ),
587
833
  ),
588
834
  ),