stagent 0.1.12 → 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 (77) hide show
  1. package/README.md +44 -50
  2. package/package.json +1 -1
  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/app/api/tasks/[id]/route.ts +54 -3
  21. package/src/app/api/workflows/[id]/route.ts +43 -4
  22. package/src/app/api/workflows/[id]/status/route.ts +70 -2
  23. package/src/app/api/workflows/from-assist/route.ts +6 -32
  24. package/src/app/dashboard/page.tsx +59 -21
  25. package/src/app/documents/[id]/page.tsx +10 -8
  26. package/src/app/globals.css +11 -0
  27. package/src/app/page.tsx +60 -3
  28. package/src/app/tasks/[id]/page.tsx +22 -2
  29. package/src/components/costs/cost-dashboard.tsx +1 -1
  30. package/src/components/dashboard/greeting.tsx +3 -1
  31. package/src/components/dashboard/priority-queue.tsx +58 -9
  32. package/src/components/dashboard/stats-cards.tsx +16 -2
  33. package/src/components/documents/document-chip-bar.tsx +183 -0
  34. package/src/components/documents/document-content-renderer.tsx +146 -0
  35. package/src/components/documents/document-detail-view.tsx +16 -239
  36. package/src/components/documents/image-zoom-view.tsx +60 -0
  37. package/src/components/documents/smart-extracted-text.tsx +47 -0
  38. package/src/components/documents/utils.ts +70 -0
  39. package/src/components/notifications/inbox-list.tsx +4 -5
  40. package/src/components/notifications/notification-item.tsx +72 -8
  41. package/src/components/notifications/pending-approval-host.tsx +7 -4
  42. package/src/components/playbook/playbook-detail-view.tsx +6 -4
  43. package/src/components/profiles/profile-browser.tsx +1 -0
  44. package/src/components/profiles/profile-card.tsx +16 -8
  45. package/src/components/profiles/profile-detail-view.tsx +6 -1
  46. package/src/components/shared/app-sidebar.tsx +2 -2
  47. package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
  48. package/src/components/tasks/ai-assist-panel.tsx +108 -78
  49. package/src/components/tasks/content-preview.tsx +2 -1
  50. package/src/components/tasks/kanban-board.tsx +57 -5
  51. package/src/components/tasks/kanban-column.tsx +34 -23
  52. package/src/components/tasks/task-bento-cell.tsx +50 -0
  53. package/src/components/tasks/task-bento-grid.tsx +155 -0
  54. package/src/components/tasks/task-card.tsx +14 -16
  55. package/src/components/tasks/task-chip-bar.tsx +207 -0
  56. package/src/components/tasks/task-detail-view.tsx +42 -190
  57. package/src/components/tasks/task-result-renderer.tsx +33 -0
  58. package/src/components/workflows/blueprint-gallery.tsx +19 -12
  59. package/src/components/workflows/blueprint-preview.tsx +8 -1
  60. package/src/components/workflows/loop-status-view.tsx +2 -8
  61. package/src/components/workflows/swarm-dashboard.tsx +2 -3
  62. package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
  63. package/src/components/workflows/workflow-full-output.tsx +80 -0
  64. package/src/components/workflows/workflow-kanban-card.tsx +121 -0
  65. package/src/components/workflows/workflow-list.tsx +47 -42
  66. package/src/components/workflows/workflow-status-view.tsx +160 -20
  67. package/src/lib/agents/learning-session.ts +138 -18
  68. package/src/lib/constants/card-icons.tsx +202 -0
  69. package/src/lib/constants/prose-styles.ts +7 -0
  70. package/src/lib/constants/task-status.ts +3 -0
  71. package/src/lib/docs/reader.ts +8 -3
  72. package/src/lib/documents/context-builder.ts +41 -0
  73. package/src/lib/queries/chart-data.ts +20 -1
  74. package/src/lib/workflows/engine.ts +57 -61
  75. package/src/lib/workflows/types.ts +2 -0
  76. package/tsconfig.json +2 -1
  77. package/src/components/documents/document-preview.tsx +0 -68
@@ -20,6 +20,7 @@ import {
20
20
  openLearningSession,
21
21
  closeLearningSession,
22
22
  } from "@/lib/agents/learning-session";
23
+ import { buildWorkflowDocumentContext } from "@/lib/documents/context-builder";
23
24
 
24
25
  /**
25
26
  * Execute a workflow by advancing through its steps according to the pattern.
@@ -36,6 +37,9 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
36
37
  const definition: WorkflowDefinition = JSON.parse(workflow.definition);
37
38
  const state = createInitialState(definition);
38
39
 
40
+ // Extract parent task ID for document context propagation to child steps
41
+ const parentTaskId: string | undefined = definition.sourceTaskId ?? undefined;
42
+
39
43
  await updateWorkflowState(workflowId, state, "active");
40
44
 
41
45
  await db.insert(agentLogs).values({
@@ -56,8 +60,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
56
60
  try {
57
61
  await executeLoop(workflowId, definition);
58
62
 
59
- await syncSourceTaskStatus(workflowId, "completed");
60
-
61
63
  await db.insert(agentLogs).values({
62
64
  id: crypto.randomUUID(),
63
65
  taskId: null,
@@ -67,8 +69,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
67
69
  timestamp: new Date(),
68
70
  });
69
71
  } catch (error) {
70
- await syncSourceTaskStatus(workflowId, "failed");
71
-
72
72
  await db.insert(agentLogs).values({
73
73
  id: crypto.randomUUID(),
74
74
  taskId: null,
@@ -92,19 +92,19 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
92
92
  try {
93
93
  switch (definition.pattern) {
94
94
  case "sequence":
95
- await executeSequence(workflowId, definition, state);
95
+ await executeSequence(workflowId, definition, state, parentTaskId);
96
96
  break;
97
97
  case "planner-executor":
98
- await executePlannerExecutor(workflowId, definition, state);
98
+ await executePlannerExecutor(workflowId, definition, state, parentTaskId);
99
99
  break;
100
100
  case "checkpoint":
101
- await executeCheckpoint(workflowId, definition, state);
101
+ await executeCheckpoint(workflowId, definition, state, parentTaskId);
102
102
  break;
103
103
  case "parallel":
104
- await executeParallel(workflowId, definition, state);
104
+ await executeParallel(workflowId, definition, state, parentTaskId);
105
105
  break;
106
106
  case "swarm":
107
- await executeSwarm(workflowId, definition, state);
107
+ await executeSwarm(workflowId, definition, state, parentTaskId);
108
108
  break;
109
109
  }
110
110
 
@@ -112,9 +112,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
112
112
  state.completedAt = new Date().toISOString();
113
113
  await updateWorkflowState(workflowId, state, "completed");
114
114
 
115
- // Sync parent task status
116
- await syncSourceTaskStatus(workflowId, "completed");
117
-
118
115
  await db.insert(agentLogs).values({
119
116
  id: crypto.randomUUID(),
120
117
  taskId: null,
@@ -127,9 +124,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
127
124
  state.status = "failed";
128
125
  await updateWorkflowState(workflowId, state, "failed");
129
126
 
130
- // Sync parent task status
131
- await syncSourceTaskStatus(workflowId, "failed");
132
-
133
127
  await db.insert(agentLogs).values({
134
128
  id: crypto.randomUUID(),
135
129
  taskId: null,
@@ -155,7 +149,8 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
155
149
  async function executeSequence(
156
150
  workflowId: string,
157
151
  definition: WorkflowDefinition,
158
- state: WorkflowState
152
+ state: WorkflowState,
153
+ parentTaskId?: string
159
154
  ): Promise<void> {
160
155
  let previousOutput = "";
161
156
 
@@ -175,7 +170,8 @@ async function executeSequence(
175
170
  contextPrompt,
176
171
  state,
177
172
  step.assignedAgent,
178
- step.agentProfile
173
+ step.agentProfile,
174
+ parentTaskId
179
175
  );
180
176
 
181
177
  if (result.status === "failed") {
@@ -192,7 +188,8 @@ async function executeSequence(
192
188
  async function executePlannerExecutor(
193
189
  workflowId: string,
194
190
  definition: WorkflowDefinition,
195
- state: WorkflowState
191
+ state: WorkflowState,
192
+ parentTaskId?: string
196
193
  ): Promise<void> {
197
194
  if (definition.steps.length < 2) {
198
195
  throw new Error("Planner-Executor requires at least 2 steps (planner + executor)");
@@ -208,7 +205,8 @@ async function executePlannerExecutor(
208
205
  plannerStep.prompt,
209
206
  state,
210
207
  plannerStep.assignedAgent,
211
- plannerStep.agentProfile
208
+ plannerStep.agentProfile,
209
+ parentTaskId
212
210
  );
213
211
 
214
212
  if (planResult.status === "failed") {
@@ -228,7 +226,8 @@ async function executePlannerExecutor(
228
226
  contextPrompt,
229
227
  state,
230
228
  step.assignedAgent,
231
- step.agentProfile
229
+ step.agentProfile,
230
+ parentTaskId
232
231
  );
233
232
 
234
233
  if (result.status === "failed") {
@@ -243,7 +242,8 @@ async function executePlannerExecutor(
243
242
  async function executeCheckpoint(
244
243
  workflowId: string,
245
244
  definition: WorkflowDefinition,
246
- state: WorkflowState
245
+ state: WorkflowState,
246
+ parentTaskId?: string
247
247
  ): Promise<void> {
248
248
  let previousOutput = "";
249
249
 
@@ -275,7 +275,8 @@ async function executeCheckpoint(
275
275
  contextPrompt,
276
276
  state,
277
277
  step.assignedAgent,
278
- step.agentProfile
278
+ step.agentProfile,
279
+ parentTaskId
279
280
  );
280
281
 
281
282
  if (result.status === "failed") {
@@ -292,7 +293,8 @@ async function executeCheckpoint(
292
293
  async function executeParallel(
293
294
  workflowId: string,
294
295
  definition: WorkflowDefinition,
295
- state: WorkflowState
296
+ state: WorkflowState,
297
+ parentTaskId?: string
296
298
  ): Promise<void> {
297
299
  const structure = getParallelWorkflowStructure(definition);
298
300
  if (!structure) {
@@ -356,7 +358,8 @@ async function executeParallel(
356
358
  step.name,
357
359
  step.prompt,
358
360
  step.assignedAgent,
359
- step.agentProfile
361
+ step.agentProfile,
362
+ parentTaskId
360
363
  );
361
364
 
362
365
  const completedAt = new Date().toISOString();
@@ -429,7 +432,8 @@ async function executeParallel(
429
432
  synthesisStep.name,
430
433
  synthesisPrompt,
431
434
  synthesisStep.assignedAgent,
432
- synthesisStep.agentProfile
435
+ synthesisStep.agentProfile,
436
+ parentTaskId
433
437
  );
434
438
 
435
439
  await commitState((draft) => {
@@ -464,7 +468,8 @@ async function executeParallel(
464
468
  async function executeSwarm(
465
469
  workflowId: string,
466
470
  definition: WorkflowDefinition,
467
- state: WorkflowState
471
+ state: WorkflowState,
472
+ parentTaskId?: string
468
473
  ): Promise<void> {
469
474
  const structure = getSwarmWorkflowStructure(definition);
470
475
  if (!structure) {
@@ -490,7 +495,8 @@ async function executeSwarm(
490
495
  mayorStep.prompt,
491
496
  state,
492
497
  mayorStep.assignedAgent,
493
- mayorStep.agentProfile
498
+ mayorStep.agentProfile,
499
+ parentTaskId
494
500
  );
495
501
 
496
502
  if (mayorResult.status === "failed") {
@@ -552,7 +558,8 @@ async function executeSwarm(
552
558
  step.name,
553
559
  workerPrompt,
554
560
  step.assignedAgent,
555
- step.agentProfile
561
+ step.agentProfile,
562
+ parentTaskId
556
563
  );
557
564
 
558
565
  const completedAt = new Date().toISOString();
@@ -604,6 +611,7 @@ async function executeSwarm(
604
611
  stepName: worker.step.name,
605
612
  result: worker.result.result ?? "",
606
613
  })),
614
+ parentTaskId,
607
615
  });
608
616
  }
609
617
 
@@ -637,6 +645,7 @@ async function runSwarmRefinery(input: {
637
645
  };
638
646
  refineryIndex: number;
639
647
  workerOutputs: Array<{ stepName: string; result: string }>;
648
+ parentTaskId?: string;
640
649
  }): Promise<void> {
641
650
  const {
642
651
  workflowId,
@@ -646,6 +655,7 @@ async function runSwarmRefinery(input: {
646
655
  refineryStep,
647
656
  refineryIndex,
648
657
  workerOutputs,
658
+ parentTaskId,
649
659
  } = input;
650
660
 
651
661
  state.currentStepIndex = refineryIndex;
@@ -669,7 +679,8 @@ async function runSwarmRefinery(input: {
669
679
  refineryStep.name,
670
680
  refineryPrompt,
671
681
  refineryStep.assignedAgent,
672
- refineryStep.agentProfile
682
+ refineryStep.agentProfile,
683
+ parentTaskId
673
684
  );
674
685
 
675
686
  refineryState.taskId = refineryResult.taskId;
@@ -704,7 +715,8 @@ export async function executeChildTask(
704
715
  name: string,
705
716
  prompt: string,
706
717
  assignedAgent?: string,
707
- agentProfile?: string
718
+ agentProfile?: string,
719
+ parentTaskId?: string
708
720
  ): Promise<{ taskId: string; status: string; result?: string; error?: string }> {
709
721
  const [workflow] = await db
710
722
  .select()
@@ -717,6 +729,16 @@ export async function executeChildTask(
717
729
  ? classifyTaskProfile(name, prompt, assignedAgent)
718
730
  : agentProfile;
719
731
 
732
+ // Inject parent task's document context into step prompt so file attachments
733
+ // from the original task are visible to every workflow child step
734
+ let enrichedPrompt = prompt;
735
+ if (parentTaskId) {
736
+ const docContext = await buildWorkflowDocumentContext(parentTaskId);
737
+ if (docContext) {
738
+ enrichedPrompt = `${docContext}\n\n${prompt}`;
739
+ }
740
+ }
741
+
720
742
  const taskId = crypto.randomUUID();
721
743
  await db.insert(tasks).values({
722
744
  id: taskId,
@@ -724,7 +746,7 @@ export async function executeChildTask(
724
746
  workflowId,
725
747
  scheduleId: null,
726
748
  title: `[Workflow] ${name}`,
727
- description: prompt,
749
+ description: enrichedPrompt,
728
750
  status: "queued",
729
751
  priority: 1,
730
752
  assignedAgent: assignedAgent ?? null,
@@ -769,7 +791,8 @@ async function executeStep(
769
791
  prompt: string,
770
792
  state: WorkflowState,
771
793
  assignedAgent?: string,
772
- agentProfile?: string
794
+ agentProfile?: string,
795
+ parentTaskId?: string
773
796
  ): Promise<StepState> {
774
797
  const stepState = state.stepStates.find((s) => s.stepId === stepId);
775
798
  if (!stepState) throw new Error(`Step ${stepId} not found in state`);
@@ -783,7 +806,8 @@ async function executeStep(
783
806
  stepName,
784
807
  prompt,
785
808
  assignedAgent,
786
- agentProfile
809
+ agentProfile,
810
+ parentTaskId
787
811
  );
788
812
 
789
813
  stepState.taskId = result.taskId;
@@ -846,34 +870,6 @@ async function waitForApproval(
846
870
  return false; // Timeout — treat as denied
847
871
  }
848
872
 
849
- /**
850
- * Sync the parent (source) task's status with the workflow's final status.
851
- * The parent task is linked via `sourceTaskId` in the workflow's definition JSON.
852
- */
853
- async function syncSourceTaskStatus(
854
- workflowId: string,
855
- status: "completed" | "failed"
856
- ): Promise<void> {
857
- try {
858
- const result = await db
859
- .select()
860
- .from(workflows)
861
- .where(eq(workflows.id, workflowId));
862
-
863
- const workflow = Array.isArray(result) ? result[0] : undefined;
864
- if (!workflow) return;
865
-
866
- const def = JSON.parse(workflow.definition);
867
- if (!def.sourceTaskId) return;
868
-
869
- await db
870
- .update(tasks)
871
- .set({ status, updatedAt: new Date() })
872
- .where(eq(tasks.id, def.sourceTaskId));
873
- } catch (error) {
874
- console.error(`[workflow-engine] Failed to sync source task status for workflow ${workflowId}:`, error);
875
- }
876
- }
877
873
 
878
874
  /**
879
875
  * Update workflow state in the database.
@@ -33,6 +33,8 @@ export interface WorkflowDefinition {
33
33
  steps: WorkflowStep[];
34
34
  loopConfig?: LoopConfig;
35
35
  swarmConfig?: SwarmConfig;
36
+ /** Parent task ID — set when workflow is created from AI assist, used to propagate document context */
37
+ sourceTaskId?: string;
36
38
  }
37
39
 
38
40
  export type LoopStopReason =
package/tsconfig.json CHANGED
@@ -36,6 +36,7 @@
36
36
  ".next/dev/types/**/*.ts"
37
37
  ],
38
38
  "exclude": [
39
- "node_modules"
39
+ "node_modules",
40
+ "vitest.config*.ts"
40
41
  ]
41
42
  }
@@ -1,68 +0,0 @@
1
- "use client";
2
-
3
- import ReactMarkdown from "react-markdown";
4
- import remarkGfm from "remark-gfm";
5
- import type { DocumentWithRelations } from "./types";
6
-
7
- interface DocumentPreviewProps {
8
- document: DocumentWithRelations;
9
- }
10
-
11
- export function DocumentPreview({ document: doc }: DocumentPreviewProps) {
12
- const isImage = doc.mimeType.startsWith("image/");
13
- const isPdf = doc.mimeType === "application/pdf";
14
- const isMarkdown = doc.mimeType === "text/markdown";
15
- const isText =
16
- doc.mimeType.startsWith("text/") ||
17
- doc.mimeType === "application/json";
18
-
19
- if (isImage) {
20
- return (
21
- <div className="rounded-md overflow-hidden border border-border bg-muted/30 flex items-center justify-center">
22
- <img
23
- src={`/api/documents/${doc.id}/file?inline=1`}
24
- alt={doc.originalName}
25
- className="max-h-64 object-contain"
26
- />
27
- </div>
28
- );
29
- }
30
-
31
- if (isPdf) {
32
- return (
33
- <div className="rounded-md overflow-hidden border border-border">
34
- <iframe
35
- src={`/api/documents/${doc.id}/file?inline=1`}
36
- className="w-full h-64"
37
- title={doc.originalName}
38
- />
39
- </div>
40
- );
41
- }
42
-
43
- if (isMarkdown && doc.extractedText) {
44
- return (
45
- <div className="rounded-md border border-border p-3 prose prose-sm dark:prose-invert max-h-64 overflow-y-auto">
46
- <ReactMarkdown remarkPlugins={[remarkGfm]}>
47
- {doc.extractedText.slice(0, 5000)}
48
- </ReactMarkdown>
49
- </div>
50
- );
51
- }
52
-
53
- if (isText && doc.extractedText) {
54
- return (
55
- <pre className="text-xs bg-muted p-3 rounded-md max-h-64 overflow-y-auto whitespace-pre-wrap break-words border border-border">
56
- {doc.extractedText.slice(0, 5000)}
57
- </pre>
58
- );
59
- }
60
-
61
- // Fallback — no preview
62
- return (
63
- <div className="rounded-md border border-border p-6 text-center text-muted-foreground">
64
- <p className="text-sm">No preview available for this file type.</p>
65
- <p className="text-xs mt-1">Download the file to view its contents.</p>
66
- </div>
67
- );
68
- }