@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,841 @@
1
+ /**
2
+ * Task Detail View — full task detail with context, subtasks, and run history.
3
+ *
4
+ * Shows a single task's complete information:
5
+ * - Header: title, status, assigned profile, priority, attempt count
6
+ * - Context: files list, notes
7
+ * - Subtasks: list with status icons (clickable)
8
+ * - Run history: each run as a TaskBlock
9
+ * - Active run: live streaming via useEventStream
10
+ * - Back button to return to dashboard
11
+ */
12
+ const React = require('react');
13
+ const { useState, useEffect, useCallback, useRef, useMemo } = React;
14
+ const {
15
+ Flex, Typography, ScrollArea, StatusIcon, Tag, Badge, Icon, IconButton, TaskBlock, Button,
16
+ Card, Chip, Checkbox, Table, Tabs, EmptyState, toast, usePackWorkspace, useEventStream,
17
+ } = require('@fw/plugin-ui-kit');
18
+
19
+ import { useStreamTimeline } from './use-stream-timeline';
20
+ import { traceToTimeline } from './trace-to-timeline';
21
+ import type { HistoricalRun } from './trace-to-timeline';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Types
25
+ // ---------------------------------------------------------------------------
26
+
27
+ type TaskStatus = 'pending' | 'in-progress' | 'blocked' | 'done' | 'failed' | 'cancelled';
28
+
29
+ interface TaskContext {
30
+ files: string[];
31
+ notes: string;
32
+ runSummaries: Array<{
33
+ runId: string;
34
+ botId: string;
35
+ outcome: string;
36
+ summary: string;
37
+ filesModified: string[];
38
+ error?: string;
39
+ durationMs: number;
40
+ tokensUsed: number;
41
+ cost: number;
42
+ }>;
43
+ lastError?: string;
44
+ }
45
+
46
+ interface Task {
47
+ id: string;
48
+ title: string;
49
+ description: string;
50
+ status: TaskStatus;
51
+ priority: number;
52
+ isParent: boolean;
53
+ parentId?: string;
54
+ dependsOn: string[];
55
+ currentBotId?: string;
56
+ currentRunId?: string;
57
+ context: TaskContext;
58
+ runs: string[];
59
+ attempt: number;
60
+ maxAttempts: number;
61
+ budgetTokens?: number;
62
+ budgetCost?: number;
63
+ tokensUsed: number;
64
+ costUsed: number;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ completedAt?: string;
68
+ assignedProfile?: string;
69
+ routingReason?: string;
70
+ }
71
+
72
+ interface Subtask {
73
+ id: string;
74
+ title: string;
75
+ status: TaskStatus;
76
+ priority: number;
77
+ assignedProfile?: string;
78
+ attempt: number;
79
+ }
80
+
81
+ interface TaskDetailViewProps {
82
+ taskId: string;
83
+ onBack: () => void;
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Status mapping
88
+ // ---------------------------------------------------------------------------
89
+
90
+ const statusToIcon: Record<TaskStatus, string> = {
91
+ 'pending': 'pending',
92
+ 'in-progress': 'running',
93
+ 'done': 'completed',
94
+ 'failed': 'failed',
95
+ 'blocked': 'pending',
96
+ 'cancelled': 'failed',
97
+ };
98
+
99
+ const statusToLabel: Record<TaskStatus, string> = {
100
+ 'pending': 'Pending',
101
+ 'in-progress': 'In Progress',
102
+ 'done': 'Done',
103
+ 'failed': 'Failed',
104
+ 'blocked': 'Blocked',
105
+ 'cancelled': 'Cancelled',
106
+ };
107
+
108
+ const priorityLabel = (p: number) => p >= 3 ? 'High' : p === 2 ? 'Medium' : p === 1 ? 'Low' : 'None';
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Inline styles for replaced styled components
112
+ // ---------------------------------------------------------------------------
113
+
114
+ const headerStyle: React.CSSProperties = {
115
+ padding: '12px 16px',
116
+ borderBottom: '1px solid var(--color-border-default)',
117
+ flexShrink: 0,
118
+ };
119
+
120
+ const sectionStyle: React.CSSProperties = {
121
+ padding: '12px 16px',
122
+ borderBottom: '1px solid var(--color-border-default)',
123
+ };
124
+
125
+ const fileItemStyle: React.CSSProperties = {
126
+ fontFamily: 'monospace',
127
+ fontSize: '12px',
128
+ color: 'var(--color-text-mid)',
129
+ padding: '2px 4px',
130
+ borderRadius: '4px',
131
+ };
132
+
133
+ const fileItemHoverStyle: React.CSSProperties = {
134
+ ...fileItemStyle,
135
+ backgroundColor: 'var(--color-surface-elevated)',
136
+ };
137
+
138
+ const notesBlockStyle: React.CSSProperties = {
139
+ whiteSpace: 'pre-wrap',
140
+ fontSize: '13px',
141
+ color: 'var(--color-text-mid)',
142
+ lineHeight: 1.5,
143
+ padding: '4px 0',
144
+ };
145
+
146
+ const subtaskRowStyle: React.CSSProperties = {
147
+ padding: '6px 8px',
148
+ cursor: 'pointer',
149
+ borderRadius: '6px',
150
+ };
151
+
152
+ const subtaskRowHoverStyle: React.CSSProperties = {
153
+ ...subtaskRowStyle,
154
+ backgroundColor: 'var(--color-surface-elevated)',
155
+ };
156
+
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
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Component
202
+ // ---------------------------------------------------------------------------
203
+
204
+ function TaskDetailView({ taskId, onBack }: TaskDetailViewProps) {
205
+ const ctx = usePackWorkspace();
206
+ const { callTool } = ctx;
207
+ const packId = ctx.packId;
208
+
209
+ // ── Task data ──
210
+ const [task, setTask] = useState<Task | null>(null);
211
+ const [subtasks, setSubtasks] = useState<Subtask[]>([]);
212
+ const [history, setHistory] = useState<HistoricalRun[]>([]);
213
+ const [loading, setLoading] = useState(true);
214
+
215
+ // ── Fetch task data ──
216
+ const fetchTask = useCallback(async () => {
217
+ try {
218
+ const raw = await callTool('fw_weaver_task_get', { id: taskId });
219
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
220
+ if (!data) return;
221
+
222
+ const t = (data.task ?? data) as Task;
223
+ setTask(t);
224
+
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;
231
+ if (Array.isArray(listData)) {
232
+ setSubtasks(listData as Subtask[]);
233
+ }
234
+ }
235
+ } catch (err) {
236
+ // Non-fatal — keep existing data
237
+ }
238
+ }, [callTool, taskId]);
239
+
240
+ const fetchHistory = useCallback(async () => {
241
+ try {
242
+ const raw = await callTool('fw_weaver_history', { taskId });
243
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
244
+ if (Array.isArray(data)) {
245
+ setHistory(
246
+ data.map((r: Record<string, unknown>) => {
247
+ const costObj = r.cost && typeof r.cost === 'object' ? (r.cost as Record<string, unknown>) : undefined;
248
+ return { ...r, costDetail: costObj, cost: costObj?.totalCost } as unknown as HistoricalRun;
249
+ }),
250
+ );
251
+ }
252
+ } catch {
253
+ // Non-fatal
254
+ }
255
+ }, [callTool, taskId]);
256
+
257
+ // Initial load
258
+ useEffect(() => {
259
+ setLoading(true);
260
+ Promise.all([fetchTask(), fetchHistory()]).finally(() => setLoading(false));
261
+ }, [fetchTask, fetchHistory]);
262
+
263
+ // Poll while in-progress (every 5s)
264
+ useEffect(() => {
265
+ if (!task || task.status !== 'in-progress') return;
266
+ const interval = setInterval(() => {
267
+ fetchTask();
268
+ fetchHistory();
269
+ }, 5_000);
270
+ return () => clearInterval(interval);
271
+ }, [task?.status, fetchTask, fetchHistory]);
272
+
273
+ // ── Live streaming for active run ──
274
+ const stream = useEventStream();
275
+ const {
276
+ timeline: liveTimeline,
277
+ phase: livePhase,
278
+ instruction: liveInstruction,
279
+ elapsed,
280
+ cost: liveCost,
281
+ plan,
282
+ awaitingApproval,
283
+ } = useStreamTimeline(stream.events, stream.isDone);
284
+
285
+ const currentRunId = task?.currentRunId;
286
+ const isLive = task?.status === 'in-progress' && !!currentRunId;
287
+
288
+ useEffect(() => {
289
+ if (!isLive || !currentRunId) return;
290
+ stream.start(packId, 'fw_weaver_events', currentRunId);
291
+ return () => stream.stop();
292
+ }, [isLive, currentRunId, packId]);
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
+
360
+ // ── Run expansion state ──
361
+ const [expandedRunId, setExpandedRunId] = useState<string | null>(null);
362
+ const [liveExpanded, setLiveExpanded] = useState(true);
363
+
364
+ const toggleExpand = useCallback((id: string) => {
365
+ setExpandedRunId((prev: string | null) => (prev === id ? null : id));
366
+ }, []);
367
+
368
+ // ── Auto-scroll ──
369
+ const scrollRef = useRef<HTMLDivElement>(null);
370
+ useEffect(() => {
371
+ const el = scrollRef.current;
372
+ if (el) el.scrollTop = el.scrollHeight;
373
+ }, [liveTimeline.length, stream.events.length]);
374
+
375
+ // ── Approval handling ──
376
+ const [approvalStatus, setApprovalStatus] = useState<'pending' | 'approved' | 'rejected' | null>(null);
377
+ const [approvalLoading, setApprovalLoading] = useState(false);
378
+
379
+ useEffect(() => {
380
+ if (awaitingApproval) setApprovalStatus('pending');
381
+ }, [awaitingApproval]);
382
+
383
+ useEffect(() => {
384
+ if (stream.events.length === 0) setApprovalStatus(null);
385
+ }, [stream.events.length]);
386
+
387
+ const handleApprove = useCallback(async () => {
388
+ setApprovalLoading(true);
389
+ try {
390
+ await callTool('fw_weaver_approve', { approved: true });
391
+ setApprovalStatus('approved');
392
+ toast('Plan approved', { type: 'success' });
393
+ } catch (err: unknown) {
394
+ toast(err instanceof Error ? err.message : 'Failed to approve', { type: 'error' });
395
+ }
396
+ setApprovalLoading(false);
397
+ }, [callTool]);
398
+
399
+ const handleReject = useCallback(async () => {
400
+ setApprovalLoading(true);
401
+ try {
402
+ await callTool('fw_weaver_approve', { approved: false });
403
+ setApprovalStatus('rejected');
404
+ toast('Plan rejected', { type: 'info' });
405
+ } catch (err: unknown) {
406
+ toast(err instanceof Error ? err.message : 'Failed to reject', { type: 'error' });
407
+ }
408
+ setApprovalLoading(false);
409
+ }, [callTool]);
410
+
411
+ // ── Build run timeline entries from history ──
412
+ const runItems = useMemo(() => {
413
+ return history.map((run: HistoricalRun) => ({
414
+ run,
415
+ runTimeline: traceToTimeline(run),
416
+ }));
417
+ }, [history]);
418
+
419
+ // ── Extract instruction from run ──
420
+ function extractInstruction(run: HistoricalRun): string {
421
+ if (run.instruction) {
422
+ const text = run.instruction;
423
+ return text.length > 120 ? text.slice(0, 117) + '...' : text;
424
+ }
425
+ if (run.summary) {
426
+ const summary = run.summary;
427
+ if (summary.includes('|')) {
428
+ const parts = summary.split('|').map((p: string) => p.trim());
429
+ const taskPart = parts.find((p: string) => p.startsWith('Task:'));
430
+ const summaryPart = parts.find((p: string) => p.startsWith('Summary:'));
431
+ const text = taskPart?.replace('Task:', '').trim() ?? summaryPart?.replace('Summary:', '').trim() ?? parts[0] ?? '';
432
+ return text.length > 120 ? text.slice(0, 117) + '...' : text;
433
+ }
434
+ return summary.length > 120 ? summary.slice(0, 117) + '...' : summary;
435
+ }
436
+ return 'Bot run';
437
+ }
438
+
439
+ // ── Loading state ──
440
+ if (loading && !task) {
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
+ },
449
+ React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
450
+ React.createElement(Icon, { name: 'arrowBack', size: 16 }),
451
+ ' Back',
452
+ ),
453
+ ),
454
+ React.createElement(Flex, {
455
+ variant: 'column-center-center-nowrap-0',
456
+ style: { flex: 1 },
457
+ },
458
+ React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
459
+ 'Loading task...',
460
+ ),
461
+ ),
462
+ );
463
+ }
464
+
465
+ if (!task) {
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
+ },
474
+ React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
475
+ React.createElement(Icon, { name: 'arrowBack', size: 16 }),
476
+ ' Back',
477
+ ),
478
+ ),
479
+ React.createElement(Flex, {
480
+ variant: 'column-center-center-nowrap-0',
481
+ style: { flex: 1 },
482
+ },
483
+ React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
484
+ 'Task not found.',
485
+ ),
486
+ ),
487
+ );
488
+ }
489
+
490
+ const hasContext = (task.context?.files?.length > 0) || task.context?.notes;
491
+ const hasSubtasks = task.isParent && subtasks.length > 0;
492
+ const hasRuns = runItems.length > 0 || isLive;
493
+
494
+ return React.createElement(Flex, {
495
+ variant: 'column-stretch-start-nowrap-0',
496
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
497
+ },
498
+ // ── Header ──
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
+ }),
509
+ React.createElement(StatusIcon, {
510
+ status: statusToIcon[task.status] || 'pending',
511
+ size: 'sm',
512
+ }),
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'),
518
+ ),
519
+
520
+ // Meta row: status tag, assigned profile, priority, attempts
521
+ React.createElement(Flex, { variant: 'row-center-start-wrap-6' },
522
+ React.createElement(Tag, {
523
+ size: 'small',
524
+ color: task.status === 'done' ? 'positive'
525
+ : task.status === 'failed' ? 'negative'
526
+ : task.status === 'in-progress' ? 'info'
527
+ : task.status === 'blocked' ? 'caution'
528
+ : 'secondary',
529
+ }, statusToLabel[task.status] || task.status || 'pending'),
530
+
531
+ task.assignedProfile && React.createElement(Tag, { key: `profile-${task.assignedProfile}`, size: 'small', color: 'info' }, task.assignedProfile),
532
+
533
+ task.priority > 0 && React.createElement(Tag, {
534
+ size: 'small', color: task.priority >= 3 ? 'caution' : 'info',
535
+ }, `P${task.priority}`),
536
+
537
+ (task.attempt != null && task.maxAttempts != null) && React.createElement(Typography, {
538
+ variant: 'smallCaption-regular',
539
+ color: 'color-text-subtle',
540
+ }, `Attempt ${task.attempt}/${task.maxAttempts}`),
541
+ ),
542
+
543
+ // Description
544
+ task.description && React.createElement(Typography, {
545
+ variant: 'smallCaption-regular',
546
+ color: 'color-text-medium',
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)
562
+ ),
563
+
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 ──
578
+ React.createElement(ScrollArea, { ref: scrollRef, style: { flex: 1 } },
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
+ ),
663
+
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
+ ),
681
+ ),
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
+ },
747
+ // Files
748
+ (task.context?.files?.length ?? 0) > 0 && React.createElement(Flex, {
749
+ variant: 'column-stretch-start-nowrap-4',
750
+ },
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),
759
+ ),
760
+ ),
761
+
762
+ // 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),
772
+ ),
773
+
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),
784
+ ),
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' },
797
+ },
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)}`),
806
+ ),
807
+ React.createElement(Typography, {
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}`),
817
+ ),
818
+ ),
819
+ ),
820
+
821
+ // Budget
822
+ (task.tokensUsed > 0 || task.costUsed > 0) && React.createElement(Flex, {
823
+ variant: 'column-stretch-start-nowrap-4',
824
+ },
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
+ ),
832
+ ),
833
+ ),
834
+ ),
835
+ ),
836
+ );
837
+ }
838
+
839
+ export { TaskDetailView };
840
+ export default TaskDetailView;
841
+ module.exports = TaskDetailView;