stagent 0.6.2 → 0.7.0

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 (176) hide show
  1. package/README.md +21 -2
  2. package/dist/cli.js +272 -1
  3. package/docs/.coverage-gaps.json +66 -16
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/dashboard-kanban.md +13 -7
  6. package/docs/features/settings.md +15 -3
  7. package/docs/features/tables.md +122 -0
  8. package/docs/index.md +3 -2
  9. package/docs/journeys/developer.md +26 -16
  10. package/docs/journeys/personal-use.md +23 -9
  11. package/docs/journeys/power-user.md +40 -14
  12. package/docs/journeys/work-use.md +43 -15
  13. package/docs/manifest.json +27 -17
  14. package/package.json +3 -2
  15. package/src/app/api/chat/entities/search/route.ts +12 -3
  16. package/src/app/api/documents/[id]/route.ts +5 -1
  17. package/src/app/api/documents/[id]/versions/route.ts +53 -0
  18. package/src/app/api/documents/route.ts +5 -1
  19. package/src/app/api/projects/[id]/documents/route.ts +124 -0
  20. package/src/app/api/projects/[id]/route.ts +72 -3
  21. package/src/app/api/projects/__tests__/delete-project.test.ts +13 -0
  22. package/src/app/api/schedules/route.ts +19 -1
  23. package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
  24. package/src/app/api/snapshots/[id]/route.ts +44 -0
  25. package/src/app/api/snapshots/route.ts +54 -0
  26. package/src/app/api/snapshots/settings/route.ts +67 -0
  27. package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
  28. package/src/app/api/tables/[id]/charts/route.ts +72 -0
  29. package/src/app/api/tables/[id]/columns/route.ts +70 -0
  30. package/src/app/api/tables/[id]/export/route.ts +94 -0
  31. package/src/app/api/tables/[id]/history/route.ts +15 -0
  32. package/src/app/api/tables/[id]/import/route.ts +111 -0
  33. package/src/app/api/tables/[id]/route.ts +86 -0
  34. package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
  35. package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
  36. package/src/app/api/tables/[id]/rows/route.ts +101 -0
  37. package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
  38. package/src/app/api/tables/[id]/triggers/route.ts +122 -0
  39. package/src/app/api/tables/route.ts +65 -0
  40. package/src/app/api/tables/templates/route.ts +92 -0
  41. package/src/app/api/tasks/[id]/route.ts +37 -2
  42. package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
  43. package/src/app/api/tasks/route.ts +8 -9
  44. package/src/app/api/workflows/[id]/documents/route.ts +209 -0
  45. package/src/app/api/workflows/[id]/execute/route.ts +6 -2
  46. package/src/app/api/workflows/[id]/route.ts +16 -3
  47. package/src/app/api/workflows/[id]/status/route.ts +18 -2
  48. package/src/app/api/workflows/route.ts +13 -2
  49. package/src/app/documents/page.tsx +5 -1
  50. package/src/app/layout.tsx +0 -1
  51. package/src/app/manifest.ts +3 -3
  52. package/src/app/projects/[id]/page.tsx +62 -2
  53. package/src/app/settings/page.tsx +2 -0
  54. package/src/app/tables/[id]/page.tsx +67 -0
  55. package/src/app/tables/page.tsx +21 -0
  56. package/src/app/tables/templates/page.tsx +19 -0
  57. package/src/components/chat/chat-table-result.tsx +139 -0
  58. package/src/components/documents/document-browser.tsx +1 -1
  59. package/src/components/documents/document-chip-bar.tsx +17 -1
  60. package/src/components/documents/document-detail-view.tsx +51 -0
  61. package/src/components/documents/document-grid.tsx +5 -0
  62. package/src/components/documents/document-table.tsx +4 -0
  63. package/src/components/documents/types.ts +3 -0
  64. package/src/components/projects/project-form-sheet.tsx +109 -2
  65. package/src/components/schedules/schedule-form.tsx +91 -1
  66. package/src/components/settings/data-management-section.tsx +17 -12
  67. package/src/components/settings/database-snapshots-section.tsx +469 -0
  68. package/src/components/shared/app-sidebar.tsx +2 -0
  69. package/src/components/shared/document-picker-sheet.tsx +486 -0
  70. package/src/components/tables/table-browser.tsx +234 -0
  71. package/src/components/tables/table-cell-editor.tsx +226 -0
  72. package/src/components/tables/table-chart-builder.tsx +288 -0
  73. package/src/components/tables/table-chart-view.tsx +146 -0
  74. package/src/components/tables/table-column-header.tsx +103 -0
  75. package/src/components/tables/table-column-sheet.tsx +331 -0
  76. package/src/components/tables/table-create-sheet.tsx +240 -0
  77. package/src/components/tables/table-detail-sheet.tsx +144 -0
  78. package/src/components/tables/table-detail-tabs.tsx +278 -0
  79. package/src/components/tables/table-grid.tsx +61 -0
  80. package/src/components/tables/table-history-tab.tsx +148 -0
  81. package/src/components/tables/table-import-wizard.tsx +542 -0
  82. package/src/components/tables/table-list-table.tsx +95 -0
  83. package/src/components/tables/table-relation-combobox.tsx +217 -0
  84. package/src/components/tables/table-spreadsheet.tsx +499 -0
  85. package/src/components/tables/table-template-gallery.tsx +162 -0
  86. package/src/components/tables/table-template-preview.tsx +219 -0
  87. package/src/components/tables/table-toolbar.tsx +79 -0
  88. package/src/components/tables/table-triggers-tab.tsx +446 -0
  89. package/src/components/tables/types.ts +6 -0
  90. package/src/components/tables/use-spreadsheet-keys.ts +171 -0
  91. package/src/components/tables/utils.ts +29 -0
  92. package/src/components/tasks/task-card.tsx +8 -1
  93. package/src/components/tasks/task-create-panel.tsx +111 -14
  94. package/src/components/tasks/task-detail-view.tsx +47 -0
  95. package/src/components/tasks/task-edit-dialog.tsx +103 -2
  96. package/src/components/workflows/workflow-form-view.tsx +207 -7
  97. package/src/components/workflows/workflow-kanban-card.tsx +8 -1
  98. package/src/components/workflows/workflow-list.tsx +90 -45
  99. package/src/components/workflows/workflow-status-view.tsx +168 -23
  100. package/src/instrumentation.ts +3 -0
  101. package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
  102. package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
  103. package/src/lib/agents/claude-agent.ts +3 -1
  104. package/src/lib/agents/profiles/registry.ts +6 -3
  105. package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
  106. package/src/lib/agents/runtime/openai-direct.ts +29 -0
  107. package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
  108. package/src/lib/book/chapter-generator.ts +4 -19
  109. package/src/lib/book/chapter-mapping.ts +17 -0
  110. package/src/lib/book/content.ts +5 -16
  111. package/src/lib/book/update-detector.ts +3 -16
  112. package/src/lib/chat/engine.ts +1 -0
  113. package/src/lib/chat/stagent-tools.ts +2 -0
  114. package/src/lib/chat/system-prompt.ts +9 -1
  115. package/src/lib/chat/tool-catalog.ts +35 -0
  116. package/src/lib/chat/tools/settings-tools.ts +109 -0
  117. package/src/lib/chat/tools/table-tools.ts +955 -0
  118. package/src/lib/chat/tools/workflow-tools.ts +145 -2
  119. package/src/lib/constants/table-status.ts +68 -0
  120. package/src/lib/data/__tests__/clear.test.ts +1 -1
  121. package/src/lib/data/clear.ts +57 -0
  122. package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
  123. package/src/lib/data/seed-data/conversations.ts +350 -42
  124. package/src/lib/data/seed-data/documents.ts +564 -591
  125. package/src/lib/data/seed-data/learned-context.ts +101 -22
  126. package/src/lib/data/seed-data/notifications.ts +344 -70
  127. package/src/lib/data/seed-data/profile-test-results.ts +92 -11
  128. package/src/lib/data/seed-data/profiles.ts +144 -46
  129. package/src/lib/data/seed-data/projects.ts +50 -18
  130. package/src/lib/data/seed-data/repo-imports.ts +28 -13
  131. package/src/lib/data/seed-data/schedules.ts +208 -41
  132. package/src/lib/data/seed-data/table-templates.ts +234 -0
  133. package/src/lib/data/seed-data/tasks.ts +614 -116
  134. package/src/lib/data/seed-data/usage-ledger.ts +182 -103
  135. package/src/lib/data/seed-data/user-tables.ts +203 -0
  136. package/src/lib/data/seed-data/views.ts +52 -7
  137. package/src/lib/data/seed-data/workflows.ts +231 -84
  138. package/src/lib/data/seed.ts +55 -14
  139. package/src/lib/data/tables.ts +417 -0
  140. package/src/lib/db/bootstrap.ts +275 -0
  141. package/src/lib/db/index.ts +9 -0
  142. package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
  143. package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
  144. package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -0
  145. package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
  146. package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
  147. package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
  148. package/src/lib/db/schema.ts +445 -0
  149. package/src/lib/docs/reader.ts +2 -3
  150. package/src/lib/documents/context-builder.ts +75 -2
  151. package/src/lib/documents/document-resolver.ts +119 -0
  152. package/src/lib/documents/processors/spreadsheet.ts +2 -1
  153. package/src/lib/schedules/scheduler.ts +31 -1
  154. package/src/lib/snapshots/auto-backup.ts +132 -0
  155. package/src/lib/snapshots/retention.ts +64 -0
  156. package/src/lib/snapshots/snapshot-manager.ts +429 -0
  157. package/src/lib/tables/computed.ts +61 -0
  158. package/src/lib/tables/context-builder.ts +139 -0
  159. package/src/lib/tables/formula-engine.ts +415 -0
  160. package/src/lib/tables/history.ts +115 -0
  161. package/src/lib/tables/import.ts +343 -0
  162. package/src/lib/tables/query-builder.ts +152 -0
  163. package/src/lib/tables/trigger-evaluator.ts +146 -0
  164. package/src/lib/tables/types.ts +141 -0
  165. package/src/lib/tables/validation.ts +119 -0
  166. package/src/lib/utils/app-root.ts +20 -0
  167. package/src/lib/utils/stagent-paths.ts +20 -0
  168. package/src/lib/validators/__tests__/task.test.ts +43 -10
  169. package/src/lib/validators/task.ts +7 -1
  170. package/src/lib/workflows/blueprints/registry.ts +3 -3
  171. package/src/lib/workflows/engine.ts +24 -8
  172. package/src/lib/workflows/types.ts +14 -0
  173. package/tsconfig.json +3 -1
  174. package/public/icon.svg +0 -13
  175. package/src/components/tasks/file-upload.tsx +0 -120
  176. /package/docs/features/{playbook.md → user-guide.md} +0 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect } from "react";
4
- import { useRouter } from "next/navigation";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
5
  import { Button } from "@/components/ui/button";
6
6
  import { Input } from "@/components/ui/input";
7
7
  import { Textarea } from "@/components/ui/textarea";
@@ -27,6 +27,8 @@ import {
27
27
  ArrowDown,
28
28
  Brain,
29
29
  ShieldCheck,
30
+ FileText,
31
+ X,
30
32
  } from "lucide-react";
31
33
  import { toast } from "sonner";
32
34
  import { FormSectionCard } from "@/components/shared/form-section-card";
@@ -47,6 +49,8 @@ import {
47
49
  MAX_PARALLEL_BRANCHES,
48
50
  MIN_PARALLEL_BRANCHES,
49
51
  } from "@/lib/workflows/parallel";
52
+ import { DocumentPickerSheet } from "@/components/shared/document-picker-sheet";
53
+ import { getFileIcon, formatSize } from "@/components/documents/utils";
50
54
  import {
51
55
  DEFAULT_SWARM_CONCURRENCY_LIMIT,
52
56
  MAX_SWARM_WORKERS,
@@ -284,6 +288,15 @@ function normalizeSwarmSteps(
284
288
  );
285
289
  }
286
290
 
291
+ const PATTERN_LABELS: Record<string, string> = {
292
+ sequence: "Sequence",
293
+ "planner-executor": "Planner → Executor",
294
+ checkpoint: "Checkpoint",
295
+ loop: "Autonomous Loop",
296
+ parallel: "Parallel Research",
297
+ swarm: "Multi-Agent Swarm",
298
+ };
299
+
287
300
  const PATTERN_ICONS: Record<string, React.ReactNode> = {
288
301
  sequence: <ArrowDown className="h-3.5 w-3.5 text-muted-foreground" />,
289
302
  "planner-executor": <Brain className="h-3.5 w-3.5 text-muted-foreground" />,
@@ -304,6 +317,7 @@ export function WorkflowFormView({
304
317
  runtimeOptions.map((runtime) => [runtime.id, runtime.label])
305
318
  );
306
319
  const router = useRouter();
320
+ const searchParams = useSearchParams();
307
321
  const mode = workflow ? (clone ? "clone" : "edit") : "create";
308
322
 
309
323
  const [name, setName] = useState("");
@@ -313,6 +327,13 @@ export function WorkflowFormView({
313
327
  const [loading, setLoading] = useState(false);
314
328
  const [error, setError] = useState<string | null>(null);
315
329
 
330
+ // Document pool state
331
+ const [selectedDocIds, setSelectedDocIds] = useState<Set<string>>(new Set());
332
+ const [selectedDocs, setSelectedDocs] = useState<
333
+ Array<{ id: string; originalName: string; mimeType: string; size: number }>
334
+ >([]);
335
+ const [pickerOpen, setPickerOpen] = useState(false);
336
+
316
337
  // Loop-specific state
317
338
  const [loopPrompt, setLoopPrompt] = useState("");
318
339
  const [maxIterations, setMaxIterations] = useState(5);
@@ -325,6 +346,93 @@ export function WorkflowFormView({
325
346
  DEFAULT_SWARM_CONCURRENCY_LIMIT
326
347
  );
327
348
 
349
+ // Pre-populate documents from URL params (e.g., from Output Dock chain)
350
+ useEffect(() => {
351
+ const inputDocsParam = searchParams.get("inputDocs");
352
+ if (inputDocsParam) {
353
+ const docIds = inputDocsParam.split(",").filter(Boolean);
354
+ if (docIds.length > 0) {
355
+ setSelectedDocIds(new Set(docIds));
356
+ // Fetch document metadata for display
357
+ Promise.all(
358
+ docIds.map((id) =>
359
+ fetch(`/api/documents?id=${id}`)
360
+ .then((r) => r.json())
361
+ .then((docs) =>
362
+ Array.isArray(docs) && docs.length > 0 ? docs[0] : null
363
+ )
364
+ .catch(() => null)
365
+ )
366
+ ).then((results) => {
367
+ setSelectedDocs(
368
+ results.filter(Boolean).map((d: Record<string, unknown>) => ({
369
+ id: d.id as string,
370
+ originalName: d.originalName as string,
371
+ mimeType: d.mimeType as string,
372
+ size: d.size as number,
373
+ }))
374
+ );
375
+ });
376
+ }
377
+ }
378
+ }, [searchParams]);
379
+
380
+ // Handle document picker confirmation
381
+ const handleDocPickerConfirm = useCallback(
382
+ (ids: string[], meta: Array<{ id: string; originalName: string; mimeType: string; size: number }>) => {
383
+ setSelectedDocIds(new Set(ids));
384
+ setSelectedDocs(meta);
385
+ },
386
+ []
387
+ );
388
+
389
+ function removeDocument(id: string) {
390
+ setSelectedDocIds((prev) => {
391
+ const next = new Set(prev);
392
+ next.delete(id);
393
+ return next;
394
+ });
395
+ setSelectedDocs((prev) => prev.filter((d) => d.id !== id));
396
+ }
397
+
398
+ // Load existing document bindings when editing
399
+ useEffect(() => {
400
+ if (!workflow || clone) return;
401
+ fetch(`/api/workflows/${workflow.id}/documents`)
402
+ .then((r) => r.json())
403
+ .then((bindings: Array<{ documentId: string; document: { id: string; originalName: string; mimeType: string; size: number } | null }>) => {
404
+ const docs = bindings
405
+ .filter((b) => b.document)
406
+ .map((b) => b.document!);
407
+ if (docs.length > 0) {
408
+ setSelectedDocIds(new Set(docs.map((d) => d.id)));
409
+ setSelectedDocs(docs);
410
+ }
411
+ })
412
+ .catch(() => {});
413
+ }, [workflow, clone]);
414
+
415
+ // Auto-populate project default documents for new workflows
416
+ useEffect(() => {
417
+ if (workflow || !projectId) return; // Only for create mode with a project selected
418
+ fetch(`/api/projects/${projectId}/documents`)
419
+ .then((r) => r.json())
420
+ .then((docs: Array<Record<string, unknown>>) => {
421
+ if (Array.isArray(docs) && docs.length > 0) {
422
+ setSelectedDocIds(new Set(docs.map((d) => d.id as string)));
423
+ setSelectedDocs(
424
+ docs.map((d) => ({
425
+ id: d.id as string,
426
+ originalName: d.originalName as string,
427
+ mimeType: d.mimeType as string,
428
+ size: d.size as number,
429
+ }))
430
+ );
431
+ }
432
+ })
433
+ .catch(() => {});
434
+ }, [projectId, workflow]);
435
+
328
436
  // Pre-populate form for edit/clone
329
437
  useEffect(() => {
330
438
  if (!workflow) return;
@@ -723,6 +831,23 @@ export function WorkflowFormView({
723
831
  });
724
832
 
725
833
  if (res.ok) {
834
+ const data = await res.json().catch(() => null);
835
+ const workflowId = isEdit ? workflow.id : data?.id;
836
+
837
+ // Attach pool documents to the workflow via junction table
838
+ if (workflowId && selectedDocIds.size > 0) {
839
+ await fetch(`/api/workflows/${workflowId}/documents`, {
840
+ method: "POST",
841
+ headers: { "Content-Type": "application/json" },
842
+ body: JSON.stringify({
843
+ documentIds: [...selectedDocIds],
844
+ }),
845
+ }).catch(() => {
846
+ // Non-blocking — workflow was created, docs attachment is best-effort
847
+ console.warn("[workflow-form] Failed to attach pool documents");
848
+ });
849
+ }
850
+
726
851
  toast.success(
727
852
  mode === "edit"
728
853
  ? "Workflow updated"
@@ -734,9 +859,8 @@ export function WorkflowFormView({
734
859
  if (isEdit) {
735
860
  router.push(`/workflows/${workflow.id}`);
736
861
  } else {
737
- const data = await res.json().catch(() => null);
738
- if (data?.id) {
739
- router.push(`/workflows/${data.id}`);
862
+ if (workflowId) {
863
+ router.push(`/workflows/${workflowId}`);
740
864
  } else {
741
865
  router.push("/workflows");
742
866
  }
@@ -963,15 +1087,20 @@ export function WorkflowFormView({
963
1087
  </div>
964
1088
  <div className="space-y-1.5">
965
1089
  <Label>Pattern</Label>
1090
+ {mode === "edit" ? (
1091
+ <div className="flex h-9 w-fit items-center gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm opacity-50 cursor-not-allowed">
1092
+ {PATTERN_ICONS[pattern]}
1093
+ {PATTERN_LABELS[pattern] ?? pattern}
1094
+ </div>
1095
+ ) : (
966
1096
  <Select
967
1097
  value={pattern}
968
1098
  onValueChange={(value) =>
969
1099
  setPattern(value as WorkflowPattern)
970
1100
  }
971
- disabled={mode === "edit"}
972
1101
  >
973
1102
  <SelectTrigger>
974
- <SelectValue />
1103
+ <SelectValue placeholder="Select pattern" />
975
1104
  </SelectTrigger>
976
1105
  <SelectContent>
977
1106
  <SelectItem value="sequence">
@@ -1012,6 +1141,7 @@ export function WorkflowFormView({
1012
1141
  </SelectItem>
1013
1142
  </SelectContent>
1014
1143
  </Select>
1144
+ )}
1015
1145
  <p className="text-xs text-muted-foreground">How steps execute</p>
1016
1146
  </div>
1017
1147
  {projects.length > 0 && (
@@ -1041,6 +1171,76 @@ export function WorkflowFormView({
1041
1171
  </div>
1042
1172
  </FormSectionCard>
1043
1173
 
1174
+ {/* Input Documents — Document Pool */}
1175
+ {projectId && (
1176
+ <FormSectionCard
1177
+ icon={FileText}
1178
+ title="Input Documents"
1179
+ hint="Attach documents from the project pool as context for this workflow"
1180
+ >
1181
+ <div className="space-y-3">
1182
+ {selectedDocs.length > 0 && (
1183
+ <div className="flex flex-wrap gap-2">
1184
+ {selectedDocs.map((doc) => {
1185
+ const Icon = getFileIcon(doc.mimeType);
1186
+ return (
1187
+ <Badge
1188
+ key={doc.id}
1189
+ variant="secondary"
1190
+ className="flex items-center gap-1.5 pl-2 pr-1 py-1"
1191
+ >
1192
+ <Icon className="h-3 w-3" />
1193
+ <span className="text-xs max-w-[180px] truncate">
1194
+ {doc.originalName}
1195
+ </span>
1196
+ <span className="text-[10px] text-muted-foreground">
1197
+ {formatSize(doc.size)}
1198
+ </span>
1199
+ <button
1200
+ type="button"
1201
+ onClick={() => removeDocument(doc.id)}
1202
+ className="ml-0.5 rounded-full p-0.5 hover:bg-muted transition-colors"
1203
+ aria-label={`Remove ${doc.originalName}`}
1204
+ >
1205
+ <X className="h-3 w-3" />
1206
+ </button>
1207
+ </Badge>
1208
+ );
1209
+ })}
1210
+ </div>
1211
+ )}
1212
+ <Button
1213
+ type="button"
1214
+ variant="outline"
1215
+ size="sm"
1216
+ onClick={() => setPickerOpen(true)}
1217
+ className="gap-1.5"
1218
+ >
1219
+ <Plus className="h-3.5 w-3.5" />
1220
+ {selectedDocs.length > 0
1221
+ ? "Add More Documents"
1222
+ : "Attach Documents"}
1223
+ </Button>
1224
+ {selectedDocs.length > 0 && (
1225
+ <p className="text-xs text-muted-foreground">
1226
+ {selectedDocs.length} document{selectedDocs.length !== 1 ? "s" : ""} will be injected as context for all steps
1227
+ </p>
1228
+ )}
1229
+ </div>
1230
+
1231
+ <DocumentPickerSheet
1232
+ open={pickerOpen}
1233
+ onOpenChange={setPickerOpen}
1234
+ projectId={projectId}
1235
+ selectedIds={selectedDocIds}
1236
+ onConfirm={handleDocPickerConfirm}
1237
+ groupBy="workflow"
1238
+ allowCrossProject
1239
+ selectedDocumentMeta={selectedDocs}
1240
+ />
1241
+ </FormSectionCard>
1242
+ )}
1243
+
1044
1244
  {isLoop && (
1045
1245
  <FormSectionCard icon={RefreshCw} title="Loop Config">
1046
1246
  <div className="space-y-4">
@@ -3,7 +3,7 @@
3
3
  import Link from "next/link";
4
4
  import { Card } from "@/components/ui/card";
5
5
  import { Badge } from "@/components/ui/badge";
6
- import { Workflow, Loader2 } from "lucide-react";
6
+ import { Workflow, Loader2, FileText } from "lucide-react";
7
7
  import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
8
8
 
9
9
  export interface WorkflowKanbanItem {
@@ -16,6 +16,7 @@ export interface WorkflowKanbanItem {
16
16
  projectName?: string;
17
17
  stepProgress: { current: number; total: number };
18
18
  currentStepName?: string;
19
+ outputDocCount?: number;
19
20
  createdAt: string;
20
21
  updatedAt?: string;
21
22
  }
@@ -112,6 +113,12 @@ export function WorkflowKanbanCard({ workflow }: WorkflowKanbanCardProps) {
112
113
  >
113
114
  {workflow.status}
114
115
  </Badge>
116
+ {workflow.outputDocCount != null && workflow.outputDocCount > 0 && (
117
+ <span className="text-[11px] text-muted-foreground flex items-center gap-0.5">
118
+ <FileText className="h-3 w-3" />
119
+ {workflow.outputDocCount}
120
+ </span>
121
+ )}
115
122
  <div className="flex-1" />
116
123
  <span className="text-[11px] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
117
124
  View →
@@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
10
10
  import { ConfirmDialog } from "@/components/shared/confirm-dialog";
11
11
  import { EmptyState } from "@/components/shared/empty-state";
12
12
  import { GitBranch, Pencil, Copy, RotateCcw, Trash2, FileCog, Play } from "lucide-react";
13
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
13
14
  import { toast } from "sonner";
14
15
  import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
15
16
  import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
@@ -23,6 +24,9 @@ interface Workflow {
23
24
  definition: string;
24
25
  createdAt: string;
25
26
  updatedAt: string;
27
+ taskCount?: number;
28
+ outputDocCount?: number;
29
+ runNumber?: number;
26
30
  }
27
31
 
28
32
  interface WorkflowListProps {
@@ -175,6 +179,18 @@ export function WorkflowList({ projects }: WorkflowListProps) {
175
179
  <span>{patternLabels[pattern] ?? pattern}</span>
176
180
  <span>&middot;</span>
177
181
  <span>{stepCount} step{stepCount !== 1 ? "s" : ""}</span>
182
+ {wf.taskCount != null && wf.taskCount > 0 && (
183
+ <>
184
+ <span className="text-muted-foreground">&middot;</span>
185
+ <span>{wf.taskCount} task{wf.taskCount !== 1 ? "s" : ""}</span>
186
+ </>
187
+ )}
188
+ {wf.outputDocCount != null && wf.outputDocCount > 0 && (
189
+ <>
190
+ <span className="text-muted-foreground">&middot;</span>
191
+ <span>{wf.outputDocCount} doc{wf.outputDocCount !== 1 ? "s" : ""}</span>
192
+ </>
193
+ )}
178
194
  </div>
179
195
  {promptPreview && (
180
196
  <p className="text-xs text-muted-foreground line-clamp-2 mt-1.5">
@@ -182,53 +198,82 @@ export function WorkflowList({ projects }: WorkflowListProps) {
182
198
  </p>
183
199
  )}
184
200
  <div className="flex items-center justify-between mt-3">
185
- <Badge variant={workflowStatusVariant[wf.status] ?? "secondary"}>
186
- {wf.status}
187
- </Badge>
188
- <div className="flex items-center gap-1">
189
- {wf.status === "draft" && (
190
- <Button
191
- variant="ghost"
192
- size="icon"
193
- className="h-7 w-7"
194
- aria-label="Edit workflow"
195
- onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit`); }}
196
- >
197
- <Pencil className="h-3.5 w-3.5" />
198
- </Button>
199
- )}
200
- <Button
201
- variant="ghost"
202
- size="icon"
203
- className="h-7 w-7"
204
- aria-label="Clone workflow"
205
- onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit?clone=true`); }}
206
- >
207
- <Copy className="h-3.5 w-3.5" />
208
- </Button>
209
- {(wf.status === "completed" || wf.status === "failed") && (
210
- <Button
211
- variant="ghost"
212
- size="icon"
213
- className="h-7 w-7"
214
- aria-label="Re-run workflow"
215
- onClick={(e) => { e.stopPropagation(); handleRerun(wf.id); }}
216
- >
217
- <RotateCcw className="h-3.5 w-3.5" />
218
- </Button>
219
- )}
220
- {wf.status !== "active" && (
221
- <Button
222
- variant="ghost"
223
- size="icon"
224
- className="h-7 w-7 text-destructive"
225
- aria-label="Delete workflow"
226
- onClick={(e) => { e.stopPropagation(); setConfirmDeleteId(wf.id); }}
227
- >
228
- <Trash2 className="h-3.5 w-3.5" />
229
- </Button>
201
+ <div className="flex items-center gap-2">
202
+ <Badge variant={workflowStatusVariant[wf.status] ?? "secondary"}>
203
+ {wf.status}
204
+ </Badge>
205
+ {wf.runNumber != null && wf.runNumber > 0 && (
206
+ <Badge variant="outline" className="text-[10px] font-normal">
207
+ Run #{wf.runNumber}
208
+ </Badge>
230
209
  )}
231
210
  </div>
211
+ <TooltipProvider>
212
+ <div className="flex items-center gap-1">
213
+ {(wf.status === "draft" || wf.status === "completed" || wf.status === "failed") && (
214
+ <Tooltip>
215
+ <TooltipTrigger asChild>
216
+ <Button
217
+ variant="ghost"
218
+ size="icon"
219
+ className="h-7 w-7"
220
+ aria-label="Edit workflow"
221
+ onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit`); }}
222
+ >
223
+ <Pencil className="h-3.5 w-3.5" />
224
+ </Button>
225
+ </TooltipTrigger>
226
+ <TooltipContent>Edit</TooltipContent>
227
+ </Tooltip>
228
+ )}
229
+ <Tooltip>
230
+ <TooltipTrigger asChild>
231
+ <Button
232
+ variant="ghost"
233
+ size="icon"
234
+ className="h-7 w-7"
235
+ aria-label="Clone workflow"
236
+ onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit?clone=true`); }}
237
+ >
238
+ <Copy className="h-3.5 w-3.5" />
239
+ </Button>
240
+ </TooltipTrigger>
241
+ <TooltipContent>Clone</TooltipContent>
242
+ </Tooltip>
243
+ {(wf.status === "completed" || wf.status === "failed") && (
244
+ <Tooltip>
245
+ <TooltipTrigger asChild>
246
+ <Button
247
+ variant="ghost"
248
+ size="icon"
249
+ className="h-7 w-7"
250
+ aria-label="Re-run workflow"
251
+ onClick={(e) => { e.stopPropagation(); handleRerun(wf.id); }}
252
+ >
253
+ <RotateCcw className="h-3.5 w-3.5" />
254
+ </Button>
255
+ </TooltipTrigger>
256
+ <TooltipContent>Re-run</TooltipContent>
257
+ </Tooltip>
258
+ )}
259
+ {wf.status !== "active" && (
260
+ <Tooltip>
261
+ <TooltipTrigger asChild>
262
+ <Button
263
+ variant="ghost"
264
+ size="icon"
265
+ className="h-7 w-7 text-destructive"
266
+ aria-label="Delete workflow"
267
+ onClick={(e) => { e.stopPropagation(); setConfirmDeleteId(wf.id); }}
268
+ >
269
+ <Trash2 className="h-3.5 w-3.5" />
270
+ </Button>
271
+ </TooltipTrigger>
272
+ <TooltipContent>Delete</TooltipContent>
273
+ </Tooltip>
274
+ )}
275
+ </div>
276
+ </TooltipProvider>
232
277
  </div>
233
278
  </CardContent>
234
279
  </Card>