@synergenius/flow-weaver-pack-weaver 0.9.200 → 0.9.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/ai-chat-provider.js +5 -5
  2. package/dist/ai-chat-provider.js.map +1 -1
  3. package/dist/bot/acceptance-merge.d.ts +21 -0
  4. package/dist/bot/acceptance-merge.d.ts.map +1 -0
  5. package/dist/bot/acceptance-merge.js +46 -0
  6. package/dist/bot/acceptance-merge.js.map +1 -0
  7. package/dist/bot/ai-client.d.ts +14 -2
  8. package/dist/bot/ai-client.d.ts.map +1 -1
  9. package/dist/bot/ai-client.js +71 -24
  10. package/dist/bot/ai-client.js.map +1 -1
  11. package/dist/bot/assistant-tools.js +3 -3
  12. package/dist/bot/assistant-tools.js.map +1 -1
  13. package/dist/bot/audit-logger.d.ts.map +1 -1
  14. package/dist/bot/audit-logger.js +34 -14
  15. package/dist/bot/audit-logger.js.map +1 -1
  16. package/dist/bot/audit-trail.d.ts +67 -0
  17. package/dist/bot/audit-trail.d.ts.map +1 -0
  18. package/dist/bot/audit-trail.js +153 -0
  19. package/dist/bot/audit-trail.js.map +1 -0
  20. package/dist/bot/behavior-defaults.d.ts +1 -1
  21. package/dist/bot/behavior-defaults.d.ts.map +1 -1
  22. package/dist/bot/behavior-defaults.js +7 -3
  23. package/dist/bot/behavior-defaults.js.map +1 -1
  24. package/dist/bot/capability-registry.d.ts +9 -0
  25. package/dist/bot/capability-registry.d.ts.map +1 -1
  26. package/dist/bot/capability-registry.js +81 -27
  27. package/dist/bot/capability-registry.js.map +1 -1
  28. package/dist/bot/capability-types.d.ts +10 -0
  29. package/dist/bot/capability-types.d.ts.map +1 -1
  30. package/dist/bot/cli-provider.d.ts.map +1 -1
  31. package/dist/bot/cli-provider.js +8 -7
  32. package/dist/bot/cli-provider.js.map +1 -1
  33. package/dist/bot/preflight.d.ts +48 -0
  34. package/dist/bot/preflight.d.ts.map +1 -0
  35. package/dist/bot/preflight.js +247 -0
  36. package/dist/bot/preflight.js.map +1 -0
  37. package/dist/bot/provider-shim.d.ts +74 -0
  38. package/dist/bot/provider-shim.d.ts.map +1 -0
  39. package/dist/bot/provider-shim.js +176 -0
  40. package/dist/bot/provider-shim.js.map +1 -0
  41. package/dist/bot/runner.d.ts +2 -0
  42. package/dist/bot/runner.d.ts.map +1 -1
  43. package/dist/bot/runner.js +60 -17
  44. package/dist/bot/runner.js.map +1 -1
  45. package/dist/bot/step-executor.d.ts.map +1 -1
  46. package/dist/bot/step-executor.js +72 -115
  47. package/dist/bot/step-executor.js.map +1 -1
  48. package/dist/bot/swarm-controller.d.ts +2 -0
  49. package/dist/bot/swarm-controller.d.ts.map +1 -1
  50. package/dist/bot/swarm-controller.js +92 -20
  51. package/dist/bot/swarm-controller.js.map +1 -1
  52. package/dist/bot/task-create-handler.d.ts +37 -0
  53. package/dist/bot/task-create-handler.d.ts.map +1 -0
  54. package/dist/bot/task-create-handler.js +124 -0
  55. package/dist/bot/task-create-handler.js.map +1 -0
  56. package/dist/bot/task-store.d.ts +1 -0
  57. package/dist/bot/task-store.d.ts.map +1 -1
  58. package/dist/bot/task-store.js +61 -0
  59. package/dist/bot/task-store.js.map +1 -1
  60. package/dist/bot/types.d.ts +1 -1
  61. package/dist/bot/types.d.ts.map +1 -1
  62. package/dist/bot/weaver-tools.d.ts.map +1 -1
  63. package/dist/bot/weaver-tools.js +7 -39
  64. package/dist/bot/weaver-tools.js.map +1 -1
  65. package/dist/node-types/agent-execute.d.ts +25 -8
  66. package/dist/node-types/agent-execute.d.ts.map +1 -1
  67. package/dist/node-types/agent-execute.js +89 -23
  68. package/dist/node-types/agent-execute.js.map +1 -1
  69. package/dist/node-types/bot-report.d.ts.map +1 -1
  70. package/dist/node-types/bot-report.js +24 -3
  71. package/dist/node-types/bot-report.js.map +1 -1
  72. package/dist/node-types/plan-task.d.ts +8 -17
  73. package/dist/node-types/plan-task.d.ts.map +1 -1
  74. package/dist/node-types/plan-task.js +217 -256
  75. package/dist/node-types/plan-task.js.map +1 -1
  76. package/dist/node-types/review-result.js +8 -6
  77. package/dist/node-types/review-result.js.map +1 -1
  78. package/dist/palindrome.d.ts +9 -0
  79. package/dist/palindrome.d.ts.map +1 -0
  80. package/dist/palindrome.js +14 -0
  81. package/dist/palindrome.js.map +1 -0
  82. package/dist/ui/approval-card.js +91 -82
  83. package/dist/ui/bot-activity.js +73 -56
  84. package/dist/ui/bot-config.js +48 -31
  85. package/dist/ui/bot-dashboard.js +52 -36
  86. package/dist/ui/bot-panel.js +230 -228
  87. package/dist/ui/bot-slot-card.js +100 -90
  88. package/dist/ui/bot-status.js +37 -15
  89. package/dist/ui/budget-bar.js +57 -31
  90. package/dist/ui/capability-editor.js +447 -378
  91. package/dist/ui/chat-task-result.js +78 -71
  92. package/dist/ui/decision-log.js +68 -81
  93. package/dist/ui/genesis-block.js +86 -95
  94. package/dist/ui/instance-stream-view.js +722 -0
  95. package/dist/ui/profile-card.js +96 -221
  96. package/dist/ui/profile-editor.js +532 -575
  97. package/dist/ui/settings-section.js +41 -45
  98. package/dist/ui/swarm-controls.js +212 -135
  99. package/dist/ui/swarm-dashboard.js +3992 -2715
  100. package/dist/ui/task-detail-view.js +415 -521
  101. package/dist/ui/task-editor.js +339 -390
  102. package/dist/ui/task-pool-list.js +60 -55
  103. package/dist/workflows/src/palindrome.d.ts +11 -0
  104. package/dist/workflows/src/palindrome.d.ts.map +1 -0
  105. package/dist/workflows/src/palindrome.js +16 -0
  106. package/dist/workflows/src/palindrome.js.map +1 -0
  107. package/dist/workflows/tests/palindrome.test.d.ts +2 -0
  108. package/dist/workflows/tests/palindrome.test.d.ts.map +1 -0
  109. package/dist/workflows/tests/palindrome.test.js +41 -0
  110. package/dist/workflows/tests/palindrome.test.js.map +1 -0
  111. package/dist/workflows/weaver-bot-batch.js +1 -1
  112. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  113. package/dist/workflows/weaver-bot.js +1 -1
  114. package/dist/workflows/weaver-bot.js.map +1 -1
  115. package/flowweaver.manifest.json +1 -1
  116. package/package.json +8 -2
  117. package/src/ai-chat-provider.ts +5 -5
  118. package/src/bot/acceptance-merge.ts +62 -0
  119. package/src/bot/ai-client.ts +77 -21
  120. package/src/bot/assistant-tools.ts +3 -3
  121. package/src/bot/audit-logger.ts +42 -14
  122. package/src/bot/audit-trail.ts +211 -0
  123. package/src/bot/behavior-defaults.ts +7 -2
  124. package/src/bot/capability-registry.ts +84 -28
  125. package/src/bot/capability-types.ts +11 -0
  126. package/src/bot/cli-provider.ts +8 -7
  127. package/src/bot/preflight.ts +285 -0
  128. package/src/bot/provider-shim.ts +218 -0
  129. package/src/bot/runner.ts +68 -20
  130. package/src/bot/step-executor.ts +69 -127
  131. package/src/bot/swarm-controller.ts +94 -20
  132. package/src/bot/task-create-handler.ts +164 -0
  133. package/src/bot/task-store.ts +76 -0
  134. package/src/bot/types.ts +4 -1
  135. package/src/bot/weaver-tools.ts +7 -45
  136. package/src/node-types/agent-execute.ts +102 -16
  137. package/src/node-types/bot-report.ts +24 -3
  138. package/src/node-types/plan-task.ts +238 -280
  139. package/src/node-types/review-result.ts +8 -6
  140. package/src/palindrome.ts +14 -0
  141. package/src/ui/approval-card.tsx +78 -62
  142. package/src/ui/bot-activity.tsx +12 -10
  143. package/src/ui/bot-config.tsx +12 -10
  144. package/src/ui/bot-dashboard.tsx +13 -11
  145. package/src/ui/bot-panel.tsx +189 -171
  146. package/src/ui/bot-slot-card.tsx +125 -70
  147. package/src/ui/bot-status.tsx +4 -4
  148. package/src/ui/budget-bar.tsx +86 -25
  149. package/src/ui/capability-editor.tsx +392 -257
  150. package/src/ui/chat-task-result.tsx +81 -78
  151. package/src/ui/decision-log.tsx +76 -73
  152. package/src/ui/genesis-block.tsx +91 -61
  153. package/src/ui/instance-stream-view.tsx +861 -0
  154. package/src/ui/profile-card.tsx +195 -168
  155. package/src/ui/profile-editor.tsx +453 -370
  156. package/src/ui/settings-section.tsx +46 -39
  157. package/src/ui/swarm-controls.tsx +252 -123
  158. package/src/ui/swarm-dashboard.tsx +999 -466
  159. package/src/ui/task-detail-view.tsx +485 -428
  160. package/src/ui/task-editor.tsx +329 -271
  161. package/src/ui/task-pool-list.tsx +68 -62
  162. package/src/workflows/src/palindrome.ts +16 -0
  163. package/src/workflows/tests/palindrome.test.ts +49 -0
  164. package/src/workflows/weaver-bot-batch.ts +1 -1
  165. package/src/workflows/weaver-bot.ts +1 -1
  166. package/dist/ui/bot-constants.d.ts +0 -14
  167. package/dist/ui/bot-constants.d.ts.map +0 -1
  168. package/dist/ui/bot-constants.js +0 -189
  169. package/dist/ui/bot-constants.js.map +0 -1
  170. package/dist/ui/steer-api.d.ts +0 -7
  171. package/dist/ui/steer-api.d.ts.map +0 -1
  172. package/dist/ui/steer-api.js +0 -11
  173. package/dist/ui/steer-api.js.map +0 -1
  174. package/dist/ui/trace-to-timeline.d.ts +0 -91
  175. package/dist/ui/trace-to-timeline.d.ts.map +0 -1
  176. package/dist/ui/trace-to-timeline.js +0 -116
  177. package/dist/ui/trace-to-timeline.js.map +0 -1
  178. package/dist/ui/use-stream-timeline.d.ts +0 -50
  179. package/dist/ui/use-stream-timeline.d.ts.map +0 -1
  180. package/dist/ui/use-stream-timeline.js +0 -245
  181. package/dist/ui/use-stream-timeline.js.map +0 -1
@@ -9,12 +9,11 @@
9
9
  * - Active run: live streaming via useEventStream
10
10
  * - Back button to return to dashboard
11
11
  */
12
- const React = require('react');
13
- const { useState, useEffect, useCallback, useRef, useMemo } = React;
14
- const {
12
+ import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
13
+ import {
15
14
  Flex, Typography, ScrollArea, StatusIcon, Badge, Icon, IconButton, TaskBlock, Button,
16
15
  Card, Chip, Checkbox, Table, Tabs, EmptyState, toast, usePackWorkspace, useEventStream,
17
- } = require('@fw/plugin-ui-kit');
16
+ } from '@fw/plugin-ui-kit';
18
17
 
19
18
  import { useStreamTimeline } from './use-stream-timeline';
20
19
  import { traceToTimeline } from './trace-to-timeline';
@@ -155,36 +154,42 @@ const subtaskRowHoverStyle: React.CSSProperties = {
155
154
 
156
155
  function FileItemRow({ children }: { children: React.ReactNode }) {
157
156
  const [hovered, setHovered] = useState(false);
158
- return React.createElement(Flex, {
159
- variant: 'row-center-start-nowrap-0',
160
- style: hovered ? fileItemHoverStyle : fileItemStyle,
161
- onMouseEnter: () => setHovered(true),
162
- onMouseLeave: () => setHovered(false),
163
- }, children);
157
+ return (
158
+ <Flex
159
+ variant="row-center-start-nowrap-0"
160
+ style={hovered ? fileItemHoverStyle : fileItemStyle}
161
+ onMouseEnter={() => setHovered(true)}
162
+ onMouseLeave={() => setHovered(false)}
163
+ >
164
+ {children}
165
+ </Flex>
166
+ );
164
167
  }
165
168
 
166
169
  function SubtaskRowItem({ sub, onBack }: { sub: Subtask; onBack: () => void }) {
167
170
  const [hovered, setHovered] = useState(false);
168
- return React.createElement(Flex, {
169
- variant: 'row-center-start-nowrap-8',
170
- style: hovered ? subtaskRowHoverStyle : subtaskRowStyle,
171
- onClick: () => onBack(),
172
- onMouseEnter: () => setHovered(true),
173
- onMouseLeave: () => setHovered(false),
174
- },
175
- React.createElement(StatusIcon, {
176
- status: statusToIcon[sub.status] || 'pending',
177
- size: 'sm',
178
- }),
179
- React.createElement(Flex, {
180
- variant: 'row-center-start-nowrap-0',
181
- style: { flex: 1, minWidth: 0 },
182
- },
183
- React.createElement(Typography, { variant: 'caption-regular', truncate: true }, sub.title),
184
- ),
185
- sub.assignedProfile && React.createElement(Chip, {
186
- label: sub.assignedProfile, size: 'small', color: 'color-status-info',
187
- }),
171
+ return (
172
+ <Flex
173
+ variant="row-center-start-nowrap-8"
174
+ style={hovered ? subtaskRowHoverStyle : subtaskRowStyle}
175
+ onClick={() => onBack()}
176
+ onMouseEnter={() => setHovered(true)}
177
+ onMouseLeave={() => setHovered(false)}
178
+ >
179
+ <StatusIcon
180
+ status={statusToIcon[sub.status] || 'pending'}
181
+ size="sm"
182
+ />
183
+ <Flex
184
+ variant="row-center-start-nowrap-0"
185
+ style={{ flex: 1, minWidth: 0 }}
186
+ >
187
+ <Typography variant="caption-regular" truncate={true}>{sub.title}</Typography>
188
+ </Flex>
189
+ {sub.assignedProfile && (
190
+ <Chip label={sub.assignedProfile} size="small" color="color-status-info" />
191
+ )}
192
+ </Flex>
188
193
  );
189
194
  }
190
195
 
@@ -197,13 +202,13 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
197
202
  const { callTool } = ctx;
198
203
  const packId = ctx.packId;
199
204
 
200
- // ── Task data ──
205
+ // -- Task data --
201
206
  const [task, setTask] = useState<Task | null>(null);
202
207
  const [subtasks, setSubtasks] = useState<Subtask[]>([]);
203
208
  const [history, setHistory] = useState<HistoricalRun[]>([]);
204
209
  const [loading, setLoading] = useState(true);
205
210
 
206
- // ── Fetch task data ──
211
+ // -- Fetch task data --
207
212
  const fetchTask = useCallback(async () => {
208
213
  try {
209
214
  const raw = await callTool('fw_weaver_task_get', { id: taskId });
@@ -284,7 +289,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
284
289
  return () => clearInterval(interval);
285
290
  }, [task?.status, fetchTask, fetchHistory]);
286
291
 
287
- // ── Live streaming for active run ──
292
+ // -- Live streaming for active run --
288
293
  const stream = useEventStream();
289
294
  const {
290
295
  timeline: liveTimeline,
@@ -306,10 +311,10 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
306
311
  return () => stream.stop();
307
312
  }, [isLive, activeRunId, packId]);
308
313
 
309
- // ── Detail tab state ──
314
+ // -- Detail tab state --
310
315
  const [detailTab, setDetailTab] = useState('runs');
311
316
 
312
- // ── Action state ──
317
+ // -- Action state --
313
318
  const [actionLoading, setActionLoading] = useState<string | null>(null);
314
319
  const [availableProfiles, setAvailableProfiles] = useState<Array<{ id: string; name: string; icon?: string; color?: string }>>([]);
315
320
 
@@ -372,7 +377,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
372
377
  } catch { /* non-fatal */ }
373
378
  }, [callTool, taskId, task, fetchTask]);
374
379
 
375
- // ── Run expansion state ──
380
+ // -- Run expansion state --
376
381
  const [expandedRunId, setExpandedRunId] = useState<string | null>(null);
377
382
  const [liveExpanded, setLiveExpanded] = useState(true);
378
383
 
@@ -380,14 +385,14 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
380
385
  setExpandedRunId((prev: string | null) => (prev === id ? null : id));
381
386
  }, []);
382
387
 
383
- // ── Auto-scroll ──
388
+ // -- Auto-scroll --
384
389
  const scrollRef = useRef<HTMLDivElement>(null);
385
390
  useEffect(() => {
386
391
  const el = scrollRef.current;
387
392
  if (el) el.scrollTop = el.scrollHeight;
388
393
  }, [liveTimeline.length, stream.events.length]);
389
394
 
390
- // ── Approval handling ──
395
+ // -- Approval handling --
391
396
  const [approvalStatus, setApprovalStatus] = useState<'pending' | 'approved' | 'rejected' | null>(null);
392
397
  const [approvalLoading, setApprovalLoading] = useState(false);
393
398
 
@@ -423,7 +428,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
423
428
  setApprovalLoading(false);
424
429
  }, [callTool]);
425
430
 
426
- // ── Build run timeline entries from history ──
431
+ // -- Build run timeline entries from history --
427
432
  const runItems = useMemo(() => {
428
433
  return history.map((run: HistoricalRun) => ({
429
434
  run,
@@ -431,24 +436,19 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
431
436
  }));
432
437
  }, [history]);
433
438
 
434
- // ── Extract instruction from run ──
435
- // The run.instruction may be a raw markdown prompt ("## Task: Title\n\n...")
436
- // or a plain string. Extract just the title for display.
439
+ // -- Extract instruction from run --
437
440
  function extractInstruction(run: HistoricalRun): string {
438
441
  if (run.instruction) {
439
442
  const text = run.instruction;
440
- // Extract title from markdown prompt format: "## Task: Title\n\n..."
441
443
  const titleMatch = text.match(/^##\s*Task:\s*(.+)/m);
442
444
  if (titleMatch) {
443
445
  const title = titleMatch[1].trim();
444
446
  return title.length > 120 ? title.slice(0, 117) + '...' : title;
445
447
  }
446
- // Extract from "Task: Title | ..." pipe-separated summary
447
448
  if (text.startsWith('Task:')) {
448
449
  const title = text.split('|')[0].replace('Task:', '').trim();
449
450
  return title.length > 120 ? title.slice(0, 117) + '...' : title;
450
451
  }
451
- // Plain instruction — use first line only
452
452
  const firstLine = text.split('\n')[0].trim();
453
453
  return firstLine.length > 120 ? firstLine.slice(0, 117) + '...' : firstLine;
454
454
  }
@@ -463,58 +463,49 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
463
463
  }
464
464
  return summary.length > 120 ? summary.slice(0, 117) + '...' : summary;
465
465
  }
466
- // Fall back to task title if available
467
466
  return task?.title ?? 'Bot run';
468
467
  }
469
468
 
470
- // ── Loading state ──
469
+ // -- Loading state --
471
470
  if (loading && !task) {
472
- return React.createElement(Flex, {
473
- variant: 'column-stretch-start-nowrap-0',
474
- style: { width: '100%', height: '100%', overflow: 'hidden' },
475
- },
476
- React.createElement(Flex, {
477
- variant: 'column-stretch-start-nowrap-8',
478
- style: headerStyle,
479
- },
480
- React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
481
- React.createElement(Icon, { name: 'arrowBack', size: 16 }),
482
- ' Back',
483
- ),
484
- ),
485
- React.createElement(Flex, {
486
- variant: 'column-center-center-nowrap-0',
487
- style: { flex: 1 },
488
- },
489
- React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
490
- 'Loading task...',
491
- ),
492
- ),
471
+ return (
472
+ <Flex
473
+ variant="column-stretch-start-nowrap-0"
474
+ style={{ width: '100%', height: '100%', overflow: 'hidden' }}
475
+ >
476
+ <Flex variant="column-stretch-start-nowrap-8" style={headerStyle}>
477
+ <Button variant="ghost" size="sm" onClick={onBack}>
478
+ <Icon name="arrowBack" size={16} />
479
+ {' Back'}
480
+ </Button>
481
+ </Flex>
482
+ <Flex variant="column-center-center-nowrap-0" style={{ flex: 1 }}>
483
+ <Typography variant="caption-regular" color="color-text-subtle">
484
+ Loading task...
485
+ </Typography>
486
+ </Flex>
487
+ </Flex>
493
488
  );
494
489
  }
495
490
 
496
491
  if (!task) {
497
- return React.createElement(Flex, {
498
- variant: 'column-stretch-start-nowrap-0',
499
- style: { width: '100%', height: '100%', overflow: 'hidden' },
500
- },
501
- React.createElement(Flex, {
502
- variant: 'column-stretch-start-nowrap-8',
503
- style: headerStyle,
504
- },
505
- React.createElement(Button, { variant: 'ghost', size: 'sm', onClick: onBack },
506
- React.createElement(Icon, { name: 'arrowBack', size: 16 }),
507
- ' Back',
508
- ),
509
- ),
510
- React.createElement(Flex, {
511
- variant: 'column-center-center-nowrap-0',
512
- style: { flex: 1 },
513
- },
514
- React.createElement(Typography, { variant: 'caption-regular', color: 'color-text-subtle' },
515
- 'Task not found.',
516
- ),
517
- ),
492
+ return (
493
+ <Flex
494
+ variant="column-stretch-start-nowrap-0"
495
+ style={{ width: '100%', height: '100%', overflow: 'hidden' }}
496
+ >
497
+ <Flex variant="column-stretch-start-nowrap-8" style={headerStyle}>
498
+ <Button variant="ghost" size="sm" onClick={onBack}>
499
+ <Icon name="arrowBack" size={16} />
500
+ {' Back'}
501
+ </Button>
502
+ </Flex>
503
+ <Flex variant="column-center-center-nowrap-0" style={{ flex: 1 }}>
504
+ <Typography variant="caption-regular" color="color-text-subtle">
505
+ Task not found.
506
+ </Typography>
507
+ </Flex>
508
+ </Flex>
518
509
  );
519
510
  }
520
511
 
@@ -522,348 +513,414 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
522
513
  const hasSubtasks = task.isParent && subtasks.length > 0;
523
514
  const hasRuns = runItems.length > 0 || isLive;
524
515
 
525
- return React.createElement(Flex, {
526
- variant: 'column-stretch-start-nowrap-0',
527
- style: { width: '100%', height: '100%', overflow: 'hidden' },
528
- },
529
- // ── Header ──
530
- React.createElement(Flex, {
531
- variant: 'column-stretch-start-nowrap-6',
532
- style: { ...headerStyle, padding: '12px 16px', borderBottom: '1px solid var(--color-border-default)' },
533
- },
534
- // Top row: back + title + status + edit
535
- React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
536
- React.createElement(Flex, { variant: 'row-center-start-nowrap-8', style: { flex: 1, minWidth: 0 } },
537
- React.createElement(IconButton, {
538
- icon: 'back', size: 'xs', variant: 'clear',
539
- onClick: onBack,
540
- }),
541
- React.createElement(StatusIcon, {
542
- status: statusToIcon[task.status] || 'pending',
543
- size: 'sm',
544
- }),
545
- React.createElement(Typography, {
546
- variant: 'caption-thick',
547
- color: 'color-text-high',
548
- style: { flex: 1, minWidth: 0, wordBreak: 'break-word' },
549
- }, task.title || 'Untitled Task'),
550
- ),
551
- onEdit && React.createElement(IconButton, {
552
- icon: 'edit', size: 'xs', variant: 'clear',
553
- onClick: () => onEdit(taskId),
554
- title: 'Edit task',
555
- }),
556
- ),
557
-
558
- // Meta row: status tag, assigned profile, priority, attempts
559
- React.createElement(Flex, { variant: 'row-center-start-wrap-6' },
560
- React.createElement(Chip, {
561
- label: statusToLabel[task.status as TaskStatus] || task.status || 'open',
562
- size: 'small',
563
- color: task.status === 'done' ? 'color-status-positive'
564
- : task.status === 'cancelled' ? 'color-status-negative'
565
- : task.status === 'in-progress' ? 'color-status-info'
566
- : 'color-brand-alt',
567
- }),
568
-
569
- task.assignedProfile && React.createElement(Chip, { key: `profile-${task.assignedProfile}`, label: task.assignedProfile, size: 'small', color: 'color-status-info' }),
570
-
571
- task.priority > 0 && React.createElement(Chip, {
572
- label: `P${task.priority}`, size: 'small', color: task.priority >= 3 ? 'color-status-caution' : 'color-status-info',
573
- }),
574
-
575
- (task.context?.runHistory?.length ?? 0) > 0 && React.createElement(Typography, {
576
- variant: 'smallCaption-regular',
577
- color: 'color-text-subtle',
578
- }, `${task.context.runHistory.length} run${task.context.runHistory.length !== 1 ? 's' : ''}`),
579
- ),
580
-
581
- // Description
582
- task.description && React.createElement(Typography, {
583
- variant: 'smallCaption-regular',
584
- color: 'color-text-medium',
585
- }, task.description),
586
-
587
- // Profile routing info
588
- task.assignedProfile && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-2' },
589
- React.createElement(Typography, {
590
- variant: 'smallCaption-regular',
591
- color: 'color-text-medium',
592
- }, `Profile: ${task.assignedProfile}`),
593
- task.routingReason && React.createElement(Typography, {
594
- variant: 'smallCaption-regular',
595
- color: 'color-text-subtle',
596
- }, task.routingReason),
597
- ),
598
-
599
- // (Actions moved to tab)
600
- ),
601
-
602
- // ── Tabs ──
603
- React.createElement(Tabs, {
604
- tabs: [
605
- { id: 'runs', title: `Runs (${runItems.length}${isLive ? '+1' : ''})` },
606
- ...(hasSubtasks ? [{ id: 'subtasks', title: `Subtasks (${subtasks.filter((s: Subtask) => s.status === 'done').length}/${subtasks.length})` }] : []),
607
- ...(task.status !== 'done' && task.status !== 'cancelled' ? [{ id: 'actions', title: 'Actions' }] : []),
608
- ...(hasContext ? [{ id: 'context', title: 'Context' }] : []),
609
- ],
610
- activeTabId: detailTab,
611
- onSelectTab: (id: string) => setDetailTab(id),
612
- size: 'sm',
613
- }),
614
-
615
- // ── Tab content ──
616
- React.createElement(ScrollArea, { ref: scrollRef, style: { flex: 1 } },
617
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8', style: { padding: '12px 16px' } },
618
-
619
- // ── Runs tab ──
620
- detailTab === 'runs' && (hasRuns || (task.context?.runHistory?.length ?? 0) > 0)
621
- ? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
622
- // Historical runs
623
- ...runItems.map(({ run, runTimeline }: { run: HistoricalRun; runTimeline: Array<Record<string, unknown>> }) => {
624
- const runId = run.id;
625
- const isExpanded = expandedRunId === runId;
626
- const isSuccess = run.outcome === 'completed' || run.success === true;
627
- return React.createElement(TaskBlock, {
628
- key: `run-${runId}`,
629
- state: isSuccess ? 'completed' : 'failed',
630
- instruction: extractInstruction(run),
631
- timeline: runTimeline,
632
- report: run.report ?? null,
633
- cost: typeof run.cost === 'number' ? run.cost : ((run.costDetail?.totalCost as number) ?? null),
634
- plan: run.plan,
635
- startedAt: run.startedAt,
636
- durationMs: run.durationMs ?? run.duration,
637
- expanded: isExpanded,
638
- onToggleExpand: () => toggleExpand(runId),
639
- });
640
- }),
641
- // Live run
642
- isLive && React.createElement(TaskBlock, {
643
- key: 'live-run',
644
- state: 'running',
645
- instruction: liveInstruction ?? task.title,
646
- timeline: liveTimeline,
647
- report: liveReport,
648
- phase: livePhase,
649
- elapsed,
650
- cost: liveCost,
651
- plan,
652
- error: stream.error,
653
- approval: approvalStatus ?? undefined,
654
- approvalLoading,
655
- onApprove: handleApprove,
656
- onReject: handleReject,
657
- expanded: liveExpanded,
658
- onToggleExpand: () => setLiveExpanded((v: boolean) => !v),
659
- }),
516
+ return (
517
+ <Flex
518
+ variant="column-stretch-start-nowrap-0"
519
+ style={{ width: '100%', height: '100%', overflow: 'hidden' }}
520
+ >
521
+ {/* -- Header -- */}
522
+ <Flex
523
+ variant="column-stretch-start-nowrap-6"
524
+ style={{ ...headerStyle, padding: '12px 16px', borderBottom: '1px solid var(--color-border-default)' }}
525
+ >
526
+ {/* Top row: back + title + status + edit */}
527
+ <Flex variant="row-center-space-between-nowrap-8">
528
+ <Flex variant="row-center-start-nowrap-8" style={{ flex: 1, minWidth: 0 }}>
529
+ <IconButton
530
+ icon="back"
531
+ size="xs"
532
+ variant="clear"
533
+ onClick={onBack}
534
+ />
535
+ <StatusIcon
536
+ status={statusToIcon[task.status] || 'pending'}
537
+ size="sm"
538
+ />
539
+ <Typography
540
+ variant="caption-thick"
541
+ color="color-text-high"
542
+ style={{ flex: 1, minWidth: 0, wordBreak: 'break-word' }}
543
+ >
544
+ {task.title || 'Untitled Task'}
545
+ </Typography>
546
+ </Flex>
547
+ {onEdit && (
548
+ <IconButton
549
+ icon="edit"
550
+ size="xs"
551
+ variant="clear"
552
+ onClick={() => onEdit(taskId)}
553
+ title="Edit task"
554
+ />
555
+ )}
556
+ </Flex>
557
+
558
+ {/* Meta row: status tag, assigned profile, priority, attempts */}
559
+ <Flex variant="row-center-start-wrap-6">
560
+ <Chip
561
+ label={statusToLabel[task.status as TaskStatus] || task.status || 'open'}
562
+ size="small"
563
+ color={task.status === 'done' ? 'color-status-positive'
564
+ : task.status === 'cancelled' ? 'color-status-negative'
565
+ : task.status === 'in-progress' ? 'color-status-info'
566
+ : 'color-brand-alt'}
567
+ />
568
+
569
+ {task.assignedProfile && (
570
+ <Chip key={`profile-${task.assignedProfile}`} label={task.assignedProfile} size="small" color="color-status-info" />
571
+ )}
572
+
573
+ {task.priority > 0 && (
574
+ <Chip
575
+ label={`P${task.priority}`}
576
+ size="small"
577
+ color={task.priority >= 3 ? 'color-status-caution' : 'color-status-info'}
578
+ />
579
+ )}
580
+
581
+ {(task.context?.runHistory?.length ?? 0) > 0 && (
582
+ <Typography variant="smallCaption-regular" color="color-text-subtle">
583
+ {`${task.context.runHistory.length} run${task.context.runHistory.length !== 1 ? 's' : ''}`}
584
+ </Typography>
585
+ )}
586
+ </Flex>
587
+
588
+ {/* Description */}
589
+ {task.description && (
590
+ <Typography variant="smallCaption-regular" color="color-text-medium">
591
+ {task.description}
592
+ </Typography>
593
+ )}
594
+
595
+ {/* Profile routing info */}
596
+ {task.assignedProfile && (
597
+ <Flex variant="column-stretch-start-nowrap-2">
598
+ <Typography variant="smallCaption-regular" color="color-text-medium">
599
+ {`Profile: ${task.assignedProfile}`}
600
+ </Typography>
601
+ {task.routingReason && (
602
+ <Typography variant="smallCaption-regular" color="color-text-subtle">
603
+ {task.routingReason}
604
+ </Typography>
605
+ )}
606
+ </Flex>
607
+ )}
608
+ </Flex>
609
+
610
+ {/* -- Tabs -- */}
611
+ <Tabs
612
+ tabs={[
613
+ { id: 'runs', title: `Runs (${runItems.length}${isLive ? '+1' : ''})` },
614
+ ...(hasSubtasks ? [{ id: 'subtasks', title: `Subtasks (${subtasks.filter((s: Subtask) => s.status === 'done').length}/${subtasks.length})` }] : []),
615
+ ...(task.status !== 'done' && task.status !== 'cancelled' ? [{ id: 'actions', title: 'Actions' }] : []),
616
+ ...(hasContext ? [{ id: 'context', title: 'Context' }] : []),
617
+ ]}
618
+ activeTabId={detailTab}
619
+ onSelectTab={(id: string) => setDetailTab(id)}
620
+ size="sm"
621
+ />
622
+
623
+ {/* -- Tab content -- */}
624
+ <ScrollArea ref={scrollRef} style={{ flex: 1 }}>
625
+ <Flex variant="column-stretch-start-nowrap-8" style={{ padding: '12px 16px' }}>
626
+
627
+ {/* -- Runs tab -- */}
628
+ {detailTab === 'runs' && (hasRuns || (task.context?.runHistory?.length ?? 0) > 0)
629
+ ? (
630
+ <Flex variant="column-stretch-start-nowrap-8">
631
+ {/* Historical runs */}
632
+ {runItems.map(({ run, runTimeline }: { run: HistoricalRun; runTimeline: Array<Record<string, unknown>> }) => {
633
+ const runId = run.id;
634
+ const isExpanded = expandedRunId === runId;
635
+ const isSuccess = run.outcome === 'completed' || run.success === true;
636
+ return (
637
+ <TaskBlock
638
+ key={`run-${runId}`}
639
+ state={isSuccess ? 'completed' : 'failed'}
640
+ instruction={extractInstruction(run)}
641
+ timeline={runTimeline}
642
+ report={run.report ?? null}
643
+ cost={typeof run.cost === 'number' ? run.cost : ((run.costDetail?.totalCost as number) ?? null)}
644
+ plan={run.plan}
645
+ startedAt={run.startedAt}
646
+ durationMs={run.durationMs ?? run.duration}
647
+ expanded={isExpanded}
648
+ onToggleExpand={() => toggleExpand(runId)}
649
+ />
650
+ );
651
+ })}
652
+ {/* Live run */}
653
+ {isLive && (
654
+ <TaskBlock
655
+ key="live-run"
656
+ state="running"
657
+ instruction={liveInstruction ?? task.title}
658
+ timeline={liveTimeline}
659
+ report={liveReport}
660
+ phase={livePhase}
661
+ elapsed={elapsed}
662
+ cost={liveCost}
663
+ plan={plan}
664
+ error={stream.error}
665
+ approval={approvalStatus ?? undefined}
666
+ approvalLoading={approvalLoading}
667
+ onApprove={handleApprove}
668
+ onReject={handleReject}
669
+ expanded={liveExpanded}
670
+ onToggleExpand={() => setLiveExpanded((v: boolean) => !v)}
671
+ />
672
+ )}
673
+ </Flex>
660
674
  )
661
- : detailTab === 'runs' && React.createElement(EmptyState, {
662
- icon: 'smartToy',
663
- message: 'No runs yet',
664
- description: 'This task has not been executed yet.',
665
- }),
666
-
667
- // ── Subtasks tab ──
668
- detailTab === 'subtasks' && hasSubtasks && React.createElement(Flex, {
669
- variant: 'column-stretch-start-nowrap-0',
670
- },
671
- ...(subtasks ?? []).map((sub: Subtask) =>
672
- React.createElement(SubtaskRowItem, {
673
- key: sub.id,
674
- sub,
675
- onBack,
676
- }),
677
- ),
678
- ),
679
-
680
- // ── Actions tab ──
681
- detailTab === 'actions' && React.createElement(Flex, {
682
- variant: 'column-stretch-start-nowrap-16',
683
- },
684
- // Status actions
685
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
686
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Status'),
687
- React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
688
- (task.status === 'open' && (task.context?.runHistory?.length ?? 0) > 0) && React.createElement(Button, {
689
- size: 'xs', variant: 'fill', color: 'primary',
690
- onClick: handleRetry,
691
- loading: actionLoading === 'retry',
692
- disabled: !!actionLoading,
693
- }, 'Retry Task'),
694
-
695
- (task.status === 'open' || task.status === 'in-progress') && React.createElement(Button, {
696
- size: 'xs', variant: 'outlined', color: 'danger',
697
- onClick: handleCancel,
698
- loading: actionLoading === 'cancel',
699
- disabled: !!actionLoading,
700
- }, 'Cancel Task'),
701
- ),
702
- ),
703
-
704
- // Priority
705
- React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
706
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Priority'),
707
- React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
708
- React.createElement(IconButton, {
709
- icon: 'expandLess', size: 'xs', variant: 'outlined',
710
- onClick: () => handlePriorityChange(1),
711
- title: 'Increase priority',
712
- }),
713
- React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
714
- `P${task.priority ?? 0}`),
715
- React.createElement(IconButton, {
716
- icon: 'expandMore', size: 'xs', variant: 'outlined',
717
- onClick: () => handlePriorityChange(-1),
718
- title: 'Decrease priority',
719
- }),
720
- ),
721
- ),
722
-
723
- // Assign Profile
724
- availableProfiles.length > 0 && React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
725
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Assign Profile'),
726
- React.createElement(Table, {
727
- size: 'compact',
728
- getRowKey: (row: Record<string, unknown>) => row.id as string,
729
- columns: [
730
- {
731
- key: 'icon',
732
- header: '',
733
- width: '30px',
734
- render: (_: unknown, row: Record<string, unknown>) =>
735
- React.createElement(Icon, {
736
- name: (row.icon as string) || 'smartToy',
737
- size: 14,
738
- color: (row.color as string) || 'color-text-medium',
739
- }),
740
- },
741
- {
742
- key: 'name',
743
- header: 'Profile',
744
- },
745
- {
746
- key: 'capabilities',
747
- header: 'Capabilities',
748
- render: (_: unknown, row: Record<string, unknown>) => {
749
- const caps = (row.capabilities as Array<{ name: string; description: string }>) || [];
750
- if (caps.length === 0) return null;
751
- return React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
752
- ...caps.slice(0, 4).map((cap) =>
753
- React.createElement('span', { key: cap.name, title: cap.description },
754
- React.createElement(Chip, { label: cap.name, size: 'small', color: 'color-brand-main' }),
755
- )
756
- ),
757
- caps.length > 4 && React.createElement(Typography, {
758
- variant: 'smallCaption-regular', color: 'color-text-subtle',
759
- }, `+${caps.length - 4}`),
760
- );
761
- },
762
- },
763
- {
764
- key: 'assigned',
765
- header: 'Assign',
766
- width: '50px',
767
- align: 'right' as const,
768
- render: (_: unknown, row: Record<string, unknown>) => {
769
- const isAssigned = task.assignedProfile === (row.id as string);
770
- return React.createElement(Checkbox, {
771
- checked: isAssigned,
772
- onChange: () => handleAssignProfile(row.id as string),
773
- size: 'sm',
774
- });
775
- },
776
- },
777
- ],
778
- data: availableProfiles,
779
- }),
780
- ),
781
- ),
782
-
783
- // ── Context tab ──
784
- detailTab === 'context' && hasContext && React.createElement(Flex, {
785
- variant: 'column-stretch-start-nowrap-12',
786
- },
787
- // Files
788
- (task.context?.files?.length ?? 0) > 0 && React.createElement(Flex, {
789
- variant: 'column-stretch-start-nowrap-4',
790
- },
791
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Files'),
792
- ...(task.context?.files ?? []).map((file: string) =>
793
- React.createElement(Typography, {
794
- key: file,
795
- variant: 'smallCaption-regular',
796
- color: 'color-text-high',
797
- style: { fontFamily: 'var(--font-mono, monospace)' },
798
- }, file),
799
- ),
800
- ),
801
-
802
- // Notes
803
- task.context?.notes && React.createElement(Flex, {
804
- variant: 'column-stretch-start-nowrap-4',
805
- },
806
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Notes'),
807
- React.createElement(Typography, {
808
- variant: 'smallCaption-regular',
809
- color: 'color-text-high',
810
- style: { whiteSpace: 'pre-wrap' },
811
- }, task.context.notes),
812
- ),
813
-
814
- // Run history (accumulated context)
815
- (task.context?.runHistory?.length ?? 0) > 0 && React.createElement(Flex, {
816
- variant: 'column-stretch-start-nowrap-6',
817
- },
818
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Run History'),
819
- ...(task.context?.runHistory ?? []).map((rs: Record<string, unknown>, i: number) =>
820
- React.createElement(Card, {
821
- key: `rs-${i}`,
822
- variant: 'bordered',
823
- padding: 'compact',
824
- style: { gap: '4px' },
825
- },
826
- React.createElement(Flex, { variant: 'row-center-space-between-nowrap-8' },
827
- React.createElement(Typography, {
828
- variant: 'smallCaption-thick',
829
- color: rs.outcome === 'success' ? 'color-status-positive' : 'color-status-negative',
830
- }, `${rs.outcome === 'success' ? 'Success' : 'Failed'} (${(rs as Record<string, unknown>).botId ?? 'unknown bot'})`),
831
- React.createElement(Typography, {
832
- variant: 'smallCaption-regular', color: 'color-text-subtle',
833
- }, `${Math.round((rs.durationMs as number ?? 0) / 1000)}s · ${rs.tokensUsed ?? 0} tok · $${((rs.cost as number) ?? 0).toFixed(3)}`),
834
- ),
835
- React.createElement(Typography, {
836
- variant: 'smallCaption-regular', color: 'color-text-medium',
837
- }, rs.summary as string ?? ''),
838
- (rs.filesModified as string[] ?? []).length > 0 && React.createElement(Typography, {
839
- variant: 'smallCaption-regular', color: 'color-text-subtle',
840
- style: { fontFamily: 'var(--font-mono, monospace)' },
841
- }, `Files: ${(rs.filesModified as string[]).join(', ')}`),
842
- rs.remainingWork && React.createElement(Typography, {
843
- variant: 'smallCaption-regular', color: 'color-status-caution',
844
- }, `Remaining: ${rs.remainingWork}`),
845
- ),
846
- ),
847
- ),
848
-
849
- // Budget
850
- (task.tokensUsed > 0 || task.costUsed > 0) && React.createElement(Flex, {
851
- variant: 'column-stretch-start-nowrap-4',
852
- },
853
- React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Budget'),
854
- React.createElement(Flex, { variant: 'row-center-start-nowrap-16' },
855
- React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
856
- `Tokens: ${task.tokensUsed?.toLocaleString() ?? 0}`),
857
- React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
858
- `Cost: $${(task.costUsed ?? 0).toFixed(3)}${task.budgetCost ? ` / $${task.budgetCost.toFixed(2)}` : ''}`),
859
- ),
860
- ),
861
- ),
862
- ),
863
- ),
675
+ : detailTab === 'runs' && (
676
+ <EmptyState
677
+ icon="smartToy"
678
+ message="No runs yet"
679
+ description="This task has not been executed yet."
680
+ />
681
+ )}
682
+
683
+ {/* -- Subtasks tab -- */}
684
+ {detailTab === 'subtasks' && hasSubtasks && (
685
+ <Flex variant="column-stretch-start-nowrap-0">
686
+ {(subtasks ?? []).map((sub: Subtask) => (
687
+ <SubtaskRowItem
688
+ key={sub.id}
689
+ sub={sub}
690
+ onBack={onBack}
691
+ />
692
+ ))}
693
+ </Flex>
694
+ )}
695
+
696
+ {/* -- Actions tab -- */}
697
+ {detailTab === 'actions' && (
698
+ <Flex variant="column-stretch-start-nowrap-16">
699
+ {/* Status actions */}
700
+ <Flex variant="column-stretch-start-nowrap-8">
701
+ <Typography variant="caption-thick" color="color-text-medium">Status</Typography>
702
+ <Flex variant="row-center-start-nowrap-6">
703
+ {(task.status === 'open' && (task.context?.runHistory?.length ?? 0) > 0) && (
704
+ <Button
705
+ size="xs"
706
+ variant="fill"
707
+ color="primary"
708
+ onClick={handleRetry}
709
+ loading={actionLoading === 'retry'}
710
+ disabled={!!actionLoading}
711
+ >
712
+ Retry Task
713
+ </Button>
714
+ )}
715
+
716
+ {(task.status === 'open' || task.status === 'in-progress') && (
717
+ <Button
718
+ size="xs"
719
+ variant="outlined"
720
+ color="danger"
721
+ onClick={handleCancel}
722
+ loading={actionLoading === 'cancel'}
723
+ disabled={!!actionLoading}
724
+ >
725
+ Cancel Task
726
+ </Button>
727
+ )}
728
+ </Flex>
729
+ </Flex>
730
+
731
+ {/* Priority */}
732
+ <Flex variant="column-stretch-start-nowrap-8">
733
+ <Typography variant="caption-thick" color="color-text-medium">Priority</Typography>
734
+ <Flex variant="row-center-start-nowrap-8">
735
+ <IconButton
736
+ icon="expandLess"
737
+ size="xs"
738
+ variant="outlined"
739
+ onClick={() => handlePriorityChange(1)}
740
+ title="Increase priority"
741
+ />
742
+ <Typography variant="smallCaption-regular" color="color-text-high">
743
+ {`P${task.priority ?? 0}`}
744
+ </Typography>
745
+ <IconButton
746
+ icon="expandMore"
747
+ size="xs"
748
+ variant="outlined"
749
+ onClick={() => handlePriorityChange(-1)}
750
+ title="Decrease priority"
751
+ />
752
+ </Flex>
753
+ </Flex>
754
+
755
+ {/* Assign Profile */}
756
+ {availableProfiles.length > 0 && (
757
+ <Flex variant="column-stretch-start-nowrap-8">
758
+ <Typography variant="caption-thick" color="color-text-medium">Assign Profile</Typography>
759
+ <Table
760
+ size="compact"
761
+ getRowKey={(row: Record<string, unknown>) => row.id as string}
762
+ columns={[
763
+ {
764
+ key: 'icon',
765
+ header: '',
766
+ width: '30px',
767
+ render: (_: unknown, row: Record<string, unknown>) => (
768
+ <Icon
769
+ name={(row.icon as string) || 'smartToy'}
770
+ size={14}
771
+ color={(row.color as string) || 'color-text-medium'}
772
+ />
773
+ ),
774
+ },
775
+ {
776
+ key: 'name',
777
+ header: 'Profile',
778
+ },
779
+ {
780
+ key: 'capabilities',
781
+ header: 'Capabilities',
782
+ render: (_: unknown, row: Record<string, unknown>) => {
783
+ const caps = (row.capabilities as Array<{ name: string; description: string }>) || [];
784
+ if (caps.length === 0) return null;
785
+ return (
786
+ <Flex variant="row-center-start-wrap-4">
787
+ {caps.slice(0, 4).map((cap) => (
788
+ <span key={cap.name} title={cap.description}>
789
+ <Chip label={cap.name} size="small" color="color-brand-main" />
790
+ </span>
791
+ ))}
792
+ {caps.length > 4 && (
793
+ <Typography variant="smallCaption-regular" color="color-text-subtle">
794
+ {`+${caps.length - 4}`}
795
+ </Typography>
796
+ )}
797
+ </Flex>
798
+ );
799
+ },
800
+ },
801
+ {
802
+ key: 'assigned',
803
+ header: 'Assign',
804
+ width: '50px',
805
+ align: 'right' as const,
806
+ render: (_: unknown, row: Record<string, unknown>) => {
807
+ const isAssigned = task.assignedProfile === (row.id as string);
808
+ return (
809
+ <Checkbox
810
+ checked={isAssigned}
811
+ onChange={() => handleAssignProfile(row.id as string)}
812
+ size="sm"
813
+ />
814
+ );
815
+ },
816
+ },
817
+ ]}
818
+ data={availableProfiles}
819
+ />
820
+ </Flex>
821
+ )}
822
+ </Flex>
823
+ )}
824
+
825
+ {/* -- Context tab -- */}
826
+ {detailTab === 'context' && hasContext && (
827
+ <Flex variant="column-stretch-start-nowrap-12">
828
+ {/* Files */}
829
+ {(task.context?.files?.length ?? 0) > 0 && (
830
+ <Flex variant="column-stretch-start-nowrap-4">
831
+ <Typography variant="caption-thick" color="color-text-medium">Files</Typography>
832
+ {(task.context?.files ?? []).map((file: string) => (
833
+ <Typography
834
+ key={file}
835
+ variant="smallCaption-regular"
836
+ color="color-text-high"
837
+ style={{ fontFamily: 'var(--font-mono, monospace)' }}
838
+ >
839
+ {file}
840
+ </Typography>
841
+ ))}
842
+ </Flex>
843
+ )}
844
+
845
+ {/* Notes */}
846
+ {task.context?.notes && (
847
+ <Flex variant="column-stretch-start-nowrap-4">
848
+ <Typography variant="caption-thick" color="color-text-medium">Notes</Typography>
849
+ <Typography
850
+ variant="smallCaption-regular"
851
+ color="color-text-high"
852
+ style={{ whiteSpace: 'pre-wrap' }}
853
+ >
854
+ {task.context.notes}
855
+ </Typography>
856
+ </Flex>
857
+ )}
858
+
859
+ {/* Run history (accumulated context) */}
860
+ {(task.context?.runHistory?.length ?? 0) > 0 && (
861
+ <Flex variant="column-stretch-start-nowrap-6">
862
+ <Typography variant="caption-thick" color="color-text-medium">Run History</Typography>
863
+ {(task.context?.runHistory ?? []).map((rs: Record<string, unknown>, i: number) => (
864
+ <Card
865
+ key={`rs-${i}`}
866
+ variant="bordered"
867
+ padding="compact"
868
+ style={{ gap: '4px' }}
869
+ >
870
+ <Flex variant="row-center-space-between-nowrap-8">
871
+ <Typography
872
+ variant="smallCaption-thick"
873
+ color={rs.outcome === 'success' ? 'color-status-positive' : 'color-status-negative'}
874
+ >
875
+ {`${rs.outcome === 'success' ? 'Success' : 'Failed'} (${(rs as Record<string, unknown>).botId ?? 'unknown bot'})`}
876
+ </Typography>
877
+ <Typography variant="smallCaption-regular" color="color-text-subtle">
878
+ {`${Math.round((rs.durationMs as number ?? 0) / 1000)}s · ${rs.tokensUsed ?? 0} tok · $${((rs.cost as number) ?? 0).toFixed(3)}`}
879
+ </Typography>
880
+ </Flex>
881
+ <Typography variant="smallCaption-regular" color="color-text-medium">
882
+ {rs.summary as string ?? ''}
883
+ </Typography>
884
+ {(rs.filesModified as string[] ?? []).length > 0 && (
885
+ <Typography
886
+ variant="smallCaption-regular"
887
+ color="color-text-subtle"
888
+ style={{ fontFamily: 'var(--font-mono, monospace)' }}
889
+ >
890
+ {`Files: ${(rs.filesModified as string[]).join(', ')}`}
891
+ </Typography>
892
+ )}
893
+ {rs.remainingWork && (
894
+ <Typography variant="smallCaption-regular" color="color-status-caution">
895
+ {`Remaining: ${rs.remainingWork}`}
896
+ </Typography>
897
+ )}
898
+ </Card>
899
+ ))}
900
+ </Flex>
901
+ )}
902
+
903
+ {/* Budget */}
904
+ {(task.tokensUsed > 0 || task.costUsed > 0) && (
905
+ <Flex variant="column-stretch-start-nowrap-4">
906
+ <Typography variant="caption-thick" color="color-text-medium">Budget</Typography>
907
+ <Flex variant="row-center-start-nowrap-16">
908
+ <Typography variant="smallCaption-regular" color="color-text-high">
909
+ {`Tokens: ${task.tokensUsed?.toLocaleString() ?? 0}`}
910
+ </Typography>
911
+ <Typography variant="smallCaption-regular" color="color-text-high">
912
+ {`Cost: $${(task.costUsed ?? 0).toFixed(3)}${task.budgetCost ? ` / $${task.budgetCost.toFixed(2)}` : ''}`}
913
+ </Typography>
914
+ </Flex>
915
+ </Flex>
916
+ )}
917
+ </Flex>
918
+ )}
919
+ </Flex>
920
+ </ScrollArea>
921
+ </Flex>
864
922
  );
865
923
  }
866
924
 
867
925
  export { TaskDetailView };
868
926
  export default TaskDetailView;
869
- module.exports = TaskDetailView;