stagent 0.1.11 → 0.1.13

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 (145) hide show
  1. package/README.md +74 -49
  2. package/package.json +3 -2
  3. package/public/readme/cost-usage-list.png +0 -0
  4. package/public/readme/dashboard-bulk-select.png +0 -0
  5. package/public/readme/dashboard-card-edit.png +0 -0
  6. package/public/readme/dashboard-create-form-ai-applied.png +0 -0
  7. package/public/readme/dashboard-create-form-ai-assist.png +0 -0
  8. package/public/readme/dashboard-create-form-empty.png +0 -0
  9. package/public/readme/dashboard-create-form-filled.png +0 -0
  10. package/public/readme/dashboard-filtered.png +0 -0
  11. package/public/readme/dashboard-list.png +0 -0
  12. package/public/readme/dashboard-workflow-confirm.png +0 -0
  13. package/public/readme/home-below-fold.png +0 -0
  14. package/public/readme/home-list.png +0 -0
  15. package/public/readme/inbox-list.png +0 -0
  16. package/public/readme/playbook-list.png +0 -0
  17. package/public/readme/profiles-list.png +0 -0
  18. package/public/readme/settings-list.png +0 -0
  19. package/public/readme/workflows-list.png +0 -0
  20. package/src/__tests__/e2e/blueprint.test.ts +63 -0
  21. package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
  22. package/src/__tests__/e2e/helpers.ts +286 -0
  23. package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
  24. package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
  25. package/src/__tests__/e2e/setup.ts +156 -0
  26. package/src/__tests__/e2e/single-task.test.ts +170 -0
  27. package/src/app/api/command-palette/recent/route.ts +41 -18
  28. package/src/app/api/context/batch/route.ts +44 -0
  29. package/src/app/api/permissions/presets/route.ts +80 -0
  30. package/src/app/api/playbook/status/route.ts +15 -0
  31. package/src/app/api/profiles/route.ts +23 -20
  32. package/src/app/api/settings/pricing/route.ts +15 -0
  33. package/src/app/api/tasks/[id]/route.ts +54 -3
  34. package/src/app/api/workflows/[id]/route.ts +43 -4
  35. package/src/app/api/workflows/[id]/status/route.ts +70 -2
  36. package/src/app/api/workflows/from-assist/route.ts +6 -32
  37. package/src/app/costs/page.tsx +53 -43
  38. package/src/app/dashboard/page.tsx +59 -21
  39. package/src/app/documents/[id]/page.tsx +10 -8
  40. package/src/app/globals.css +11 -0
  41. package/src/app/page.tsx +60 -3
  42. package/src/app/playbook/[slug]/page.tsx +76 -0
  43. package/src/app/playbook/page.tsx +54 -0
  44. package/src/app/profiles/page.tsx +7 -4
  45. package/src/app/settings/page.tsx +2 -2
  46. package/src/app/tasks/[id]/page.tsx +22 -2
  47. package/src/components/costs/cost-dashboard.tsx +226 -320
  48. package/src/components/dashboard/activity-feed.tsx +6 -2
  49. package/src/components/dashboard/greeting.tsx +3 -1
  50. package/src/components/dashboard/priority-queue.tsx +58 -9
  51. package/src/components/dashboard/stats-cards.tsx +16 -2
  52. package/src/components/documents/document-chip-bar.tsx +183 -0
  53. package/src/components/documents/document-content-renderer.tsx +146 -0
  54. package/src/components/documents/document-detail-view.tsx +16 -239
  55. package/src/components/documents/image-zoom-view.tsx +60 -0
  56. package/src/components/documents/smart-extracted-text.tsx +47 -0
  57. package/src/components/documents/utils.ts +70 -0
  58. package/src/components/notifications/batch-proposal-review.tsx +150 -0
  59. package/src/components/notifications/inbox-list.tsx +4 -5
  60. package/src/components/notifications/notification-item.tsx +73 -6
  61. package/src/components/notifications/pending-approval-host.tsx +63 -14
  62. package/src/components/playbook/adoption-heatmap.tsx +69 -0
  63. package/src/components/playbook/journey-card.tsx +110 -0
  64. package/src/components/playbook/playbook-action-button.tsx +22 -0
  65. package/src/components/playbook/playbook-browser.tsx +143 -0
  66. package/src/components/playbook/playbook-card.tsx +102 -0
  67. package/src/components/playbook/playbook-detail-view.tsx +225 -0
  68. package/src/components/playbook/playbook-homepage.tsx +142 -0
  69. package/src/components/playbook/playbook-toc.tsx +90 -0
  70. package/src/components/playbook/playbook-updated-badge.tsx +23 -0
  71. package/src/components/playbook/related-docs.tsx +30 -0
  72. package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
  73. package/src/components/profiles/context-proposal-review.tsx +7 -3
  74. package/src/components/profiles/learned-context-panel.tsx +116 -8
  75. package/src/components/profiles/profile-browser.tsx +1 -0
  76. package/src/components/profiles/profile-card.tsx +16 -8
  77. package/src/components/profiles/profile-detail-view.tsx +12 -4
  78. package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
  79. package/src/components/settings/api-key-form.tsx +5 -43
  80. package/src/components/settings/auth-config-section.tsx +10 -6
  81. package/src/components/settings/auth-status-badge.tsx +8 -0
  82. package/src/components/settings/budget-guardrails-section.tsx +403 -620
  83. package/src/components/settings/connection-test-control.tsx +63 -0
  84. package/src/components/settings/permissions-section.tsx +85 -75
  85. package/src/components/settings/permissions-sections.tsx +24 -0
  86. package/src/components/settings/presets-section.tsx +159 -0
  87. package/src/components/settings/pricing-registry-panel.tsx +164 -0
  88. package/src/components/shared/app-sidebar.tsx +4 -2
  89. package/src/components/shared/command-palette.tsx +30 -0
  90. package/src/components/shared/light-markdown.tsx +134 -0
  91. package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
  92. package/src/components/tasks/ai-assist-panel.tsx +108 -78
  93. package/src/components/tasks/content-preview.tsx +2 -1
  94. package/src/components/tasks/kanban-board.tsx +57 -5
  95. package/src/components/tasks/kanban-column.tsx +34 -23
  96. package/src/components/tasks/task-bento-cell.tsx +50 -0
  97. package/src/components/tasks/task-bento-grid.tsx +155 -0
  98. package/src/components/tasks/task-card.tsx +14 -16
  99. package/src/components/tasks/task-chip-bar.tsx +207 -0
  100. package/src/components/tasks/task-detail-view.tsx +42 -190
  101. package/src/components/tasks/task-result-renderer.tsx +33 -0
  102. package/src/components/workflows/blueprint-gallery.tsx +19 -12
  103. package/src/components/workflows/blueprint-preview.tsx +8 -1
  104. package/src/components/workflows/loop-status-view.tsx +2 -4
  105. package/src/components/workflows/swarm-dashboard.tsx +2 -3
  106. package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
  107. package/src/components/workflows/workflow-full-output.tsx +80 -0
  108. package/src/components/workflows/workflow-kanban-card.tsx +121 -0
  109. package/src/components/workflows/workflow-list.tsx +47 -42
  110. package/src/components/workflows/workflow-status-view.tsx +163 -16
  111. package/src/lib/agents/learned-context.ts +27 -15
  112. package/src/lib/agents/learning-session.ts +354 -0
  113. package/src/lib/agents/pattern-extractor.ts +19 -0
  114. package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
  115. package/src/lib/agents/profiles/sort.ts +7 -0
  116. package/src/lib/constants/card-icons.tsx +202 -0
  117. package/src/lib/constants/prose-styles.ts +7 -0
  118. package/src/lib/constants/settings.ts +1 -0
  119. package/src/lib/constants/task-status.ts +3 -0
  120. package/src/lib/db/schema.ts +3 -0
  121. package/src/lib/docs/adoption.ts +105 -0
  122. package/src/lib/docs/journey-tracker.ts +21 -0
  123. package/src/lib/docs/reader.ts +107 -0
  124. package/src/lib/docs/types.ts +54 -0
  125. package/src/lib/docs/usage-stage.ts +60 -0
  126. package/src/lib/documents/context-builder.ts +41 -0
  127. package/src/lib/notifications/actionable.ts +18 -10
  128. package/src/lib/queries/chart-data.ts +20 -1
  129. package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
  130. package/src/lib/settings/budget-guardrails.ts +213 -85
  131. package/src/lib/settings/permission-presets.ts +150 -0
  132. package/src/lib/settings/runtime-setup.ts +71 -0
  133. package/src/lib/usage/__tests__/ledger.test.ts +2 -2
  134. package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
  135. package/src/lib/usage/ledger.ts +1 -1
  136. package/src/lib/usage/pricing-registry.ts +570 -0
  137. package/src/lib/usage/pricing.ts +15 -95
  138. package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
  139. package/src/lib/utils/learned-context-history.ts +150 -0
  140. package/src/lib/validators/__tests__/settings.test.ts +23 -16
  141. package/src/lib/validators/settings.ts +3 -9
  142. package/src/lib/workflows/engine.ts +75 -61
  143. package/src/lib/workflows/types.ts +2 -0
  144. package/tsconfig.json +2 -1
  145. package/src/components/documents/document-preview.tsx +0 -68
@@ -2,39 +2,17 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6
- import { Badge } from "@/components/ui/badge";
7
- import { Button } from "@/components/ui/button";
8
- import { Separator } from "@/components/ui/separator";
9
5
  import { Skeleton } from "@/components/ui/skeleton";
10
- import { Play, Square, RotateCcw, ArrowRight, FastForward, Trash2, Pencil } from "lucide-react";
11
6
  import { toast } from "sonner";
12
7
  import { ConfirmDialog } from "@/components/shared/confirm-dialog";
13
- import { ContentPreview } from "./content-preview";
14
8
  import { TaskAttachments } from "./task-attachments";
15
- import { formatTimestamp } from "@/lib/utils/format-timestamp";
16
- import { MAX_RESUME_COUNT } from "@/lib/constants/task-status";
17
- import { taskStatusVariant } from "@/lib/constants/status-colors";
18
- import type { TaskItem } from "./task-card";
9
+ import { TaskChipBar } from "./task-chip-bar";
10
+ import { TaskBentoGrid } from "./task-bento-grid";
11
+ import { TaskResultRenderer } from "./task-result-renderer";
19
12
  import { TaskEditDialog } from "./task-edit-dialog";
13
+ import type { TaskItem } from "./task-card";
20
14
  import type { DocumentRow } from "@/lib/db/schema";
21
15
 
22
- function detectContentType(content: string): "text" | "markdown" | "code" | "json" | "unknown" {
23
- if (content.startsWith("{") || content.startsWith("[")) {
24
- try { JSON.parse(content); return "json"; } catch { /* not json */ }
25
- }
26
- if (content.includes("```") || content.includes("# ") || content.includes("**")) return "markdown";
27
- if (content.includes("function ") || content.includes("const ") || content.includes("import ")) return "code";
28
- return "text";
29
- }
30
-
31
- const priorityLabels: Record<number, string> = {
32
- 0: "P0 - Critical",
33
- 1: "P1 - High",
34
- 2: "P2 - Medium",
35
- 3: "P3 - Low",
36
- };
37
-
38
16
  interface TaskDetailViewProps {
39
17
  taskId: string;
40
18
  initialTask?: TaskItem;
@@ -76,8 +54,6 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
76
54
  }, [taskId, fetchDocs]);
77
55
 
78
56
  useEffect(() => {
79
- // If server provided initial data, only fetch supplementary data (docs)
80
- // and skip the redundant task refresh
81
57
  if (!initialTask) refresh();
82
58
  fetchDocs();
83
59
  }, [refresh, fetchDocs, initialTask]);
@@ -201,8 +177,8 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
201
177
  if (!loaded) {
202
178
  return (
203
179
  <div className="space-y-4">
204
- <Skeleton className="h-8 w-64" />
205
- <Skeleton className="h-32 w-full" />
180
+ <Skeleton className="h-24 w-full" />
181
+ <Skeleton className="h-20 w-full" />
206
182
  <Skeleton className="h-48 w-full" />
207
183
  </div>
208
184
  );
@@ -216,174 +192,50 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
216
192
  const outputDocs = docs.filter((doc) => doc.direction === "output");
217
193
 
218
194
  return (
219
- <div className="space-y-6" aria-live="polite">
220
- {/* Header */}
221
- <div className="flex items-center justify-between">
222
- <div>
223
- <h1 className="text-2xl font-bold">{task.title}</h1>
224
- <div className="flex items-center gap-2 mt-1">
225
- <Badge variant={taskStatusVariant[task.status] ?? "secondary"}>
226
- {task.status}
227
- </Badge>
228
- <span className="text-sm text-muted-foreground">
229
- {priorityLabels[task.priority] ?? `P${task.priority}`}
230
- </span>
231
- </div>
232
- </div>
233
- <div className="flex items-center gap-2">
234
- {(task.status === "planned" || task.status === "queued") && (
235
- <Button size="sm" variant="outline" onClick={() => setEditOpen(true)} disabled={loading}>
236
- <Pencil className="h-3.5 w-3.5 mr-1" />
237
- Edit
238
- </Button>
239
- )}
240
- {task.status === "planned" && (
241
- <Button size="sm" onClick={() => handleStatusChange("queued")} disabled={loading}>
242
- <ArrowRight className="h-3.5 w-3.5 mr-1" />
243
- {loading ? "Queueing..." : "Queue"}
244
- </Button>
245
- )}
246
- {task.status === "queued" && (
247
- <Button size="sm" onClick={handleExecute} disabled={loading}>
248
- <Play className="h-3.5 w-3.5 mr-1" />
249
- {loading ? "Starting..." : "Run"}
250
- </Button>
251
- )}
252
- {task.status === "running" && (
253
- <Button
254
- size="sm"
255
- variant="destructive"
256
- onClick={() => setConfirmCancel(true)}
257
- disabled={loading}
258
- >
259
- <Square className="h-3.5 w-3.5 mr-1" />
260
- {loading ? "Cancelling..." : "Cancel"}
261
- </Button>
262
- )}
263
- {(task.status === "failed" || task.status === "cancelled") && (
264
- <>
265
- {task.sessionId && task.resumeCount < MAX_RESUME_COUNT && (
266
- <Button size="sm" onClick={handleResume} disabled={loading}>
267
- <FastForward className="h-3.5 w-3.5 mr-1" />
268
- {loading ? "Resuming..." : "Resume"}
269
- </Button>
270
- )}
271
- <Button
272
- size="sm"
273
- variant={task.sessionId ? "outline" : "default"}
274
- onClick={() => handleStatusChange("queued")}
275
- disabled={loading}
276
- >
277
- <RotateCcw className="h-3.5 w-3.5 mr-1" />
278
- {loading ? "Retrying..." : "Retry"}
279
- </Button>
280
- </>
281
- )}
282
- {task.status !== "running" && (
283
- <Button
284
- size="sm"
285
- variant="ghost"
286
- className="text-muted-foreground hover:text-destructive"
287
- onClick={() => setConfirmDelete(true)}
288
- disabled={loading}
289
- >
290
- <Trash2 className="h-3.5 w-3.5 mr-1" />
291
- Delete
292
- </Button>
293
- )}
294
- </div>
295
- </div>
195
+ <div className="space-y-4" aria-live="polite">
196
+ {error && <p className="text-sm text-destructive">{error}</p>}
296
197
 
297
- {error && (
298
- <p className="text-sm text-destructive">{error}</p>
299
- )}
198
+ <TaskChipBar
199
+ task={task}
200
+ loading={loading}
201
+ onEdit={() => setEditOpen(true)}
202
+ onQueue={() => handleStatusChange("queued")}
203
+ onRun={handleExecute}
204
+ onCancel={() => setConfirmCancel(true)}
205
+ onResume={handleResume}
206
+ onRetry={() => handleStatusChange("queued")}
207
+ onDelete={() => setConfirmDelete(true)}
208
+ />
300
209
 
301
- {/* Description */}
302
- {task.description && (
303
- <Card>
304
- <CardHeader className="pb-2">
305
- <CardTitle className="text-sm font-medium">Description</CardTitle>
306
- </CardHeader>
307
- <CardContent>
308
- <p className="text-sm text-muted-foreground whitespace-pre-wrap">
309
- {task.description}
310
- </p>
311
- </CardContent>
312
- </Card>
313
- )}
210
+ <TaskBentoGrid task={task} docs={docs} />
314
211
 
315
- {/* Agent Info */}
316
- {(task.assignedAgent || task.agentProfile) && (
317
- <Card>
318
- <CardHeader className="pb-2">
319
- <CardTitle className="text-sm font-medium">Agent</CardTitle>
320
- </CardHeader>
321
- <CardContent className="space-y-1">
322
- {task.assignedAgent && (
323
- <p className="text-sm text-muted-foreground">{task.assignedAgent}</p>
324
- )}
325
- {task.agentProfile && (
326
- <p className="text-sm text-muted-foreground">
327
- Profile: {task.agentProfile}
328
- </p>
329
- )}
330
- </CardContent>
331
- </Card>
332
- )}
333
-
334
- {/* Documents */}
335
212
  {docs.length > 0 && (
336
- <Card>
337
- <CardHeader className="pb-2">
338
- <CardTitle className="text-sm font-medium">
339
- Documents ({docs.length})
340
- </CardTitle>
341
- </CardHeader>
342
- <CardContent className="space-y-5">
343
- {inputDocs.length > 0 && (
344
- <TaskAttachments
345
- documents={inputDocs}
346
- title={`Input Attachments (${inputDocs.length})`}
347
- onDeleted={fetchDocs}
348
- />
349
- )}
350
- {outputDocs.length > 0 && (
351
- <TaskAttachments
352
- documents={outputDocs}
353
- title={`Generated Outputs (${outputDocs.length})`}
354
- onDeleted={fetchDocs}
355
- />
356
- )}
357
- </CardContent>
358
- </Card>
213
+ <div className="surface-card-muted rounded-lg p-4 space-y-4">
214
+ {inputDocs.length > 0 && (
215
+ <TaskAttachments
216
+ documents={inputDocs}
217
+ title={`Input Attachments (${inputDocs.length})`}
218
+ onDeleted={fetchDocs}
219
+ />
220
+ )}
221
+ {outputDocs.length > 0 && (
222
+ <TaskAttachments
223
+ documents={outputDocs}
224
+ title={`Generated Outputs (${outputDocs.length})`}
225
+ onDeleted={fetchDocs}
226
+ />
227
+ )}
228
+ </div>
359
229
  )}
360
230
 
361
- {/* Result / Error */}
362
- {task.result && (
363
- <Card>
364
- <CardHeader className="pb-2">
365
- <CardTitle className="text-sm font-medium">
366
- {task.status === "failed" ? "Error" : "Result"}
367
- </CardTitle>
368
- </CardHeader>
369
- <CardContent>
370
- {task.status === "failed" ? (
371
- <pre className="text-xs text-muted-foreground bg-muted p-3 rounded-md overflow-auto max-h-60 whitespace-pre-wrap">
372
- {task.result}
373
- </pre>
374
- ) : (
375
- <ContentPreview content={task.result} contentType={detectContentType(task.result)} />
376
- )}
377
- </CardContent>
378
- </Card>
231
+ {(task.description || task.result) && (
232
+ <TaskResultRenderer
233
+ description={task.description}
234
+ result={task.result}
235
+ status={task.status}
236
+ />
379
237
  )}
380
238
 
381
- {/* Timestamps */}
382
- <div className="text-xs text-muted-foreground">
383
- <p>Created: {formatTimestamp(task.createdAt)}</p>
384
- <p>Updated: {formatTimestamp(task.updatedAt)}</p>
385
- </div>
386
-
387
239
  <ConfirmDialog
388
240
  open={confirmCancel}
389
241
  onOpenChange={setConfirmCancel}
@@ -0,0 +1,33 @@
1
+ import { Separator } from "@/components/ui/separator";
2
+ import { LightMarkdown } from "@/components/shared/light-markdown";
3
+ import { ExpandableResult } from "@/components/workflows/workflow-status-view";
4
+
5
+ interface TaskResultRendererProps {
6
+ description: string | null;
7
+ result: string | null;
8
+ status: string;
9
+ }
10
+
11
+ export function TaskResultRenderer({ description, result, status }: TaskResultRendererProps) {
12
+ if (!description && !result) return null;
13
+
14
+ return (
15
+ <div className="prose-reader-surface">
16
+ {description && (
17
+ <LightMarkdown content={description} textSize="sm" />
18
+ )}
19
+ {description && result && <Separator className="my-4" />}
20
+ {result && (
21
+ status === "failed" ? (
22
+ <pre className="text-xs text-destructive bg-destructive/5 p-3 rounded-md overflow-auto max-h-60 whitespace-pre-wrap">
23
+ {result}
24
+ </pre>
25
+ ) : (
26
+ <div className="max-h-[70vh] overflow-y-auto">
27
+ <ExpandableResult result={result} />
28
+ </div>
29
+ )
30
+ )}
31
+ </div>
32
+ );
33
+ }
@@ -11,6 +11,7 @@ import { EmptyState } from "@/components/shared/empty-state";
11
11
  import { Button } from "@/components/ui/button";
12
12
  import { Search, Layers, Plus } from "lucide-react";
13
13
  import { patternLabels } from "@/lib/constants/status-colors";
14
+ import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
14
15
  import type { WorkflowBlueprint } from "@/lib/workflows/blueprints/types";
15
16
 
16
17
  const difficultyColors: Record<string, string> = {
@@ -114,19 +115,25 @@ export function BlueprintGallery() {
114
115
  }}
115
116
  >
116
117
  <CardHeader className="pb-1">
117
- <div className="flex items-center justify-between">
118
- <CardTitle className="text-sm font-medium">
119
- {bp.name}
120
- </CardTitle>
121
- <div className="flex items-center gap-1.5">
122
- <Badge variant={bp.domain === "work" ? "default" : "secondary"}>
123
- {bp.domain}
124
- </Badge>
125
- {bp.difficulty && (
126
- <Badge variant="outline" className={`text-xs ${difficultyColors[bp.difficulty] ?? ""}`}>
127
- {bp.difficulty}
118
+ <div className="flex items-center gap-3">
119
+ <IconCircle
120
+ icon={getWorkflowIconFromName(bp.name, bp.pattern).icon}
121
+ colors={getWorkflowIconFromName(bp.name, bp.pattern).colors}
122
+ />
123
+ <div className="flex min-w-0 flex-1 items-center justify-between">
124
+ <CardTitle className="truncate text-sm font-medium">
125
+ {bp.name}
126
+ </CardTitle>
127
+ <div className="flex shrink-0 items-center gap-1.5">
128
+ <Badge variant={bp.domain === "work" ? "default" : "secondary"}>
129
+ {bp.domain}
128
130
  </Badge>
129
- )}
131
+ {bp.difficulty && (
132
+ <Badge variant="outline" className={`text-xs ${difficultyColors[bp.difficulty] ?? ""}`}>
133
+ {bp.difficulty}
134
+ </Badge>
135
+ )}
136
+ </div>
130
137
  </div>
131
138
  </div>
132
139
  </CardHeader>
@@ -19,6 +19,7 @@ import {
19
19
  import { Loader2, Play } from "lucide-react";
20
20
  import { toast } from "sonner";
21
21
  import { patternLabels } from "@/lib/constants/status-colors";
22
+ import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
22
23
  import type { WorkflowBlueprint, BlueprintVariable } from "@/lib/workflows/blueprints/types";
23
24
 
24
25
  interface BlueprintPreviewProps {
@@ -76,7 +77,13 @@ export function BlueprintPreview({
76
77
  <div className="space-y-6 max-w-2xl">
77
78
  {/* Header */}
78
79
  <div>
79
- <h1 className="text-2xl font-bold tracking-tight">{blueprint.name}</h1>
80
+ <div className="flex items-center gap-3">
81
+ <IconCircle
82
+ icon={getWorkflowIconFromName(blueprint.name, blueprint.pattern).icon}
83
+ colors={getWorkflowIconFromName(blueprint.name, blueprint.pattern).colors}
84
+ />
85
+ <h1 className="text-2xl font-bold tracking-tight">{blueprint.name}</h1>
86
+ </div>
80
87
  <p className="text-sm text-muted-foreground mt-1">{blueprint.description}</p>
81
88
  <div className="flex flex-wrap gap-1.5 mt-3">
82
89
  <Badge variant={blueprint.domain === "work" ? "default" : "secondary"}>
@@ -3,6 +3,7 @@
3
3
  import { useState } from "react";
4
4
  import { Badge } from "@/components/ui/badge";
5
5
  import { Button } from "@/components/ui/button";
6
+ import { ExpandableResult } from "./workflow-status-view";
6
7
  import {
7
8
  CheckCircle,
8
9
  Circle,
@@ -233,10 +234,7 @@ export function LoopStatusView({
233
234
  <p className="text-xs text-destructive">{iter.error}</p>
234
235
  )}
235
236
  {iter.result && (
236
- <p className="text-xs text-muted-foreground whitespace-pre-wrap line-clamp-6">
237
- {iter.result.slice(0, 1000)}
238
- {iter.result.length > 1000 ? "..." : ""}
239
- </p>
237
+ <ExpandableResult result={iter.result} />
240
238
  )}
241
239
  {iter.taskId && (
242
240
  <a
@@ -5,6 +5,7 @@ import { Brain, GitBranch, MessageSquareMore, RotateCcw } from "lucide-react";
5
5
  import { toast } from "sonner";
6
6
  import { Badge } from "@/components/ui/badge";
7
7
  import { Button } from "@/components/ui/button";
8
+ import { ExpandableResult } from "./workflow-status-view";
8
9
  import type { SwarmConfig } from "@/lib/workflows/types";
9
10
 
10
11
  interface StepWithState {
@@ -101,9 +102,7 @@ export function SwarmDashboard({
101
102
  <p className="text-xs text-destructive">{step.state.error}</p>
102
103
  )}
103
104
  {step.state.result && step.state.status === "completed" && (
104
- <p className="text-xs text-muted-foreground line-clamp-4">
105
- {step.state.result.slice(0, 320)}
106
- </p>
105
+ <ExpandableResult result={step.state.result} />
107
106
  )}
108
107
  {canRetry && (
109
108
  <Button
@@ -188,11 +188,6 @@ export function WorkflowConfirmationView({
188
188
  priority,
189
189
  assignedAgent: assignedAgent || undefined,
190
190
  executeImmediately,
191
- parentTask: {
192
- title: steps[0].title,
193
- description: steps[0].description,
194
- agentProfile: steps[0].profile === "auto" ? undefined : steps[0].profile,
195
- },
196
191
  }),
197
192
  });
198
193
 
@@ -205,8 +200,8 @@ export function WorkflowConfirmationView({
205
200
  const data = await res.json();
206
201
  toast.success(
207
202
  executeImmediately
208
- ? `Task created with workflow (${data.taskIds.length} steps)`
209
- : `Task created with draft workflow (${data.taskIds.length} steps)`,
203
+ ? `Workflow started (${data.taskIds.length} steps)`
204
+ : `Workflow created as draft (${data.taskIds.length} steps)`,
210
205
  {
211
206
  action: {
212
207
  label: "View workflow",
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
+ import { Copy, Download, FileText } from "lucide-react";
8
+ import { toast } from "sonner";
9
+ import { PROSE_READER } from "@/lib/constants/prose-styles";
10
+
11
+ interface StepOutput {
12
+ name: string;
13
+ result: string;
14
+ }
15
+
16
+ interface WorkflowFullOutputProps {
17
+ workflowName: string;
18
+ steps: StepOutput[];
19
+ }
20
+
21
+ export function WorkflowFullOutput({ workflowName, steps }: WorkflowFullOutputProps) {
22
+ const completedSteps = steps.filter((s) => s.result);
23
+
24
+ if (completedSteps.length === 0) return null;
25
+
26
+ const fullMarkdown = completedSteps
27
+ .map((step) => `## ${step.name}\n\n${step.result}`)
28
+ .join("\n\n---\n\n");
29
+
30
+ function copyAll() {
31
+ navigator.clipboard.writeText(fullMarkdown);
32
+ toast.success("Full output copied");
33
+ }
34
+
35
+ function downloadAll() {
36
+ const blob = new Blob([fullMarkdown], { type: "text/markdown" });
37
+ const url = URL.createObjectURL(blob);
38
+ const a = document.createElement("a");
39
+ a.href = url;
40
+ a.download = `${workflowName.replace(/\s+/g, "-").toLowerCase()}-output.md`;
41
+ a.click();
42
+ URL.revokeObjectURL(url);
43
+ }
44
+
45
+ return (
46
+ <Card>
47
+ <CardHeader>
48
+ <div className="flex items-center justify-between">
49
+ <div className="flex items-center gap-2">
50
+ <FileText className="h-4 w-4 text-muted-foreground" />
51
+ <CardTitle className="text-base">Full Output</CardTitle>
52
+ </div>
53
+ <div className="flex gap-1">
54
+ <Button variant="ghost" size="sm" onClick={copyAll}>
55
+ <Copy className="h-3 w-3 mr-1" />
56
+ Copy All
57
+ </Button>
58
+ <Button variant="ghost" size="sm" onClick={downloadAll}>
59
+ <Download className="h-3 w-3 mr-1" />
60
+ Download .md
61
+ </Button>
62
+ </div>
63
+ </div>
64
+ </CardHeader>
65
+ <CardContent>
66
+ <div className="prose-reader-surface space-y-6">
67
+ {completedSteps.map((step, i) => (
68
+ <div key={i}>
69
+ {i > 0 && <hr className="border-border" />}
70
+ <h3 className="text-sm font-semibold mb-3">{step.name}</h3>
71
+ <div className={PROSE_READER}>
72
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{step.result}</ReactMarkdown>
73
+ </div>
74
+ </div>
75
+ ))}
76
+ </div>
77
+ </CardContent>
78
+ </Card>
79
+ );
80
+ }
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { Card } from "@/components/ui/card";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Workflow, Loader2 } from "lucide-react";
7
+ import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
8
+
9
+ export interface WorkflowKanbanItem {
10
+ type: "workflow";
11
+ id: string;
12
+ name: string;
13
+ status: string;
14
+ pattern: string;
15
+ projectName?: string;
16
+ stepProgress: { current: number; total: number };
17
+ currentStepName?: string;
18
+ createdAt: string;
19
+ }
20
+
21
+ interface WorkflowKanbanCardProps {
22
+ workflow: WorkflowKanbanItem;
23
+ }
24
+
25
+ const statusStripBg: Record<string, string> = {
26
+ draft: "bg-muted/40 border-t-border/30",
27
+ active: "bg-status-running/8 border-t-status-running/15",
28
+ completed: "bg-status-completed/10 border-t-status-completed/20",
29
+ failed: "bg-status-failed/10 border-t-status-failed/20",
30
+ paused: "bg-status-warning/8 border-t-status-warning/15",
31
+ };
32
+
33
+ export function WorkflowKanbanCard({ workflow }: WorkflowKanbanCardProps) {
34
+ const isActive = workflow.status === "active";
35
+ const isFailed = workflow.status === "failed";
36
+ const progressPct =
37
+ workflow.stepProgress.total > 0
38
+ ? (workflow.stepProgress.current / workflow.stepProgress.total) * 100
39
+ : 0;
40
+
41
+ return (
42
+ <Link href={`/workflows/${workflow.id}`} className="block">
43
+ <Card
44
+ role="button"
45
+ aria-label={`${workflow.name}, ${patternLabels[workflow.pattern] ?? workflow.pattern}, ${workflow.status}`}
46
+ className={`surface-card cursor-pointer transition-shadow hover:shadow-md group overflow-hidden py-0 gap-0 ${
47
+ isFailed
48
+ ? "border-l-4 border-l-destructive"
49
+ : isActive
50
+ ? "border-l-4 border-l-primary"
51
+ : ""
52
+ }`}
53
+ >
54
+ <div className="p-3">
55
+ <div className="flex items-start gap-2">
56
+ <Workflow
57
+ className={`mt-0.5 h-4 w-4 shrink-0 ${
58
+ isFailed ? "text-destructive" : "text-primary"
59
+ }`}
60
+ aria-hidden="true"
61
+ />
62
+ <div className="min-w-0 flex-1">
63
+ <p className="text-sm font-medium line-clamp-2">{workflow.name}</p>
64
+ {workflow.projectName && (
65
+ <p className="text-xs text-muted-foreground mt-0.5 truncate">
66
+ {workflow.projectName}
67
+ </p>
68
+ )}
69
+ <div className="flex items-center gap-2 mt-1.5">
70
+ <Badge variant="outline" className="text-xs gap-1">
71
+ {patternLabels[workflow.pattern] ?? workflow.pattern}
72
+ </Badge>
73
+ {workflow.stepProgress.total > 0 && (
74
+ <span className="text-xs text-muted-foreground">
75
+ {workflow.stepProgress.current}/{workflow.stepProgress.total}
76
+ </span>
77
+ )}
78
+ </div>
79
+
80
+ {/* Progress bar */}
81
+ {workflow.stepProgress.total > 0 && (
82
+ <div className="mt-2 h-1.5 w-full rounded-full bg-muted overflow-hidden">
83
+ <div
84
+ className={`h-full rounded-full transition-all ${
85
+ isFailed ? "bg-destructive" : "bg-primary"
86
+ }`}
87
+ style={{ width: `${progressPct}%` }}
88
+ />
89
+ </div>
90
+ )}
91
+
92
+ {/* Current step indicator */}
93
+ {isActive && workflow.currentStepName && (
94
+ <div className="flex items-center gap-1 mt-1.5">
95
+ <Loader2 className="h-3 w-3 animate-spin text-primary shrink-0" />
96
+ <span className="text-xs text-muted-foreground truncate">
97
+ {workflow.currentStepName}
98
+ </span>
99
+ </div>
100
+ )}
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ {/* Status strip */}
106
+ <div className={`flex items-center h-7 px-3 border-t transition-colors ${statusStripBg[workflow.status] ?? statusStripBg.draft}`}>
107
+ <Badge
108
+ variant={workflowStatusVariant[workflow.status] ?? "secondary"}
109
+ className="text-[11px] h-5"
110
+ >
111
+ {workflow.status}
112
+ </Badge>
113
+ <div className="flex-1" />
114
+ <span className="text-[11px] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
115
+ View →
116
+ </span>
117
+ </div>
118
+ </Card>
119
+ </Link>
120
+ );
121
+ }