stagent 0.1.9 → 0.1.10

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 (44) hide show
  1. package/README.md +129 -47
  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-sorted.png +0 -0
  13. package/public/readme/dashboard-workflow-confirm.png +0 -0
  14. package/public/readme/documents-grid.png +0 -0
  15. package/public/readme/documents-list.png +0 -0
  16. package/public/readme/home-below-fold.png +0 -0
  17. package/public/readme/home-list.png +0 -0
  18. package/public/readme/inbox-list.png +0 -0
  19. package/public/readme/monitor-list.png +0 -0
  20. package/public/readme/profiles-list.png +0 -0
  21. package/public/readme/projects-detail.png +0 -0
  22. package/public/readme/projects-list.png +0 -0
  23. package/public/readme/schedules-list.png +0 -0
  24. package/public/readme/settings-list.png +0 -0
  25. package/public/readme/workflows-list.png +0 -0
  26. package/src/app/api/workflows/from-assist/route.ts +143 -0
  27. package/src/app/dashboard/page.tsx +24 -2
  28. package/src/app/workflows/from-assist/page.tsx +35 -0
  29. package/src/components/projects/project-card.tsx +47 -35
  30. package/src/components/tasks/ai-assist-panel.tsx +31 -10
  31. package/src/components/tasks/task-card.tsx +16 -1
  32. package/src/components/tasks/task-create-panel.tsx +39 -0
  33. package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
  34. package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -0
  35. package/src/lib/agents/profiles/suggest.ts +36 -0
  36. package/src/lib/agents/runtime/claude.ts +36 -6
  37. package/src/lib/agents/runtime/task-assist-types.ts +12 -2
  38. package/src/lib/data/__tests__/clear.test.ts +42 -0
  39. package/src/lib/data/clear.ts +3 -0
  40. package/src/lib/notifications/permissions.ts +6 -2
  41. package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
  42. package/src/lib/workflows/assist-builder.ts +248 -0
  43. package/src/lib/workflows/assist-session.ts +78 -0
  44. package/src/lib/workflows/engine.ts +46 -1
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
 
3
+ import Link from "next/link";
3
4
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4
5
  import { Badge } from "@/components/ui/badge";
5
- import { FolderKanban, FolderOpen } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { FolderKanban, FolderOpen, Pencil } from "lucide-react";
6
8
  import { projectStatusVariant } from "@/lib/constants/status-colors";
7
9
 
8
10
  interface ProjectCardProps {
@@ -19,40 +21,50 @@ interface ProjectCardProps {
19
21
 
20
22
  export function ProjectCard({ project, onEdit }: ProjectCardProps) {
21
23
  return (
22
- <Card
23
- tabIndex={0}
24
- className="surface-card cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
25
- onKeyDown={(e) => {
26
- if (e.key === "Enter" || e.key === " ") {
27
- e.preventDefault();
28
- onEdit(project.id, e.currentTarget);
29
- }
30
- }}
31
- onClick={(e) => onEdit(project.id, e.currentTarget)}
32
- >
33
- <CardHeader className="flex flex-row items-center justify-between pb-2">
34
- <CardTitle className="text-base font-medium">{project.name}</CardTitle>
35
- <Badge variant={projectStatusVariant[project.status] ?? "secondary"}>
36
- {project.status}
37
- </Badge>
38
- </CardHeader>
39
- <CardContent>
40
- {project.description && (
41
- <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
42
- {project.description}
43
- </p>
44
- )}
45
- <div className="flex items-center gap-1 text-xs text-muted-foreground">
46
- <FolderKanban className="h-3 w-3" />
47
- <span>{project.taskCount} tasks</span>
48
- </div>
49
- {project.workingDirectory && (
50
- <div className="flex items-center gap-1 text-xs text-muted-foreground mt-1">
51
- <FolderOpen className="h-3 w-3" />
52
- <span className="truncate">{project.workingDirectory}</span>
24
+ <Link href={`/projects/${project.id}`} className="block">
25
+ <Card
26
+ tabIndex={0}
27
+ className="surface-card cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
28
+ >
29
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
30
+ <CardTitle className="text-base font-medium">{project.name}</CardTitle>
31
+ <div className="flex items-center gap-1.5">
32
+ <Button
33
+ variant="ghost"
34
+ size="icon"
35
+ className="h-7 w-7"
36
+ onClick={(e) => {
37
+ e.preventDefault();
38
+ e.stopPropagation();
39
+ onEdit(project.id, e.currentTarget);
40
+ }}
41
+ aria-label={`Edit ${project.name}`}
42
+ >
43
+ <Pencil className="h-3.5 w-3.5" />
44
+ </Button>
45
+ <Badge variant={projectStatusVariant[project.status] ?? "secondary"}>
46
+ {project.status}
47
+ </Badge>
53
48
  </div>
54
- )}
55
- </CardContent>
56
- </Card>
49
+ </CardHeader>
50
+ <CardContent>
51
+ {project.description && (
52
+ <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
53
+ {project.description}
54
+ </p>
55
+ )}
56
+ <div className="flex items-center gap-1 text-xs text-muted-foreground">
57
+ <FolderKanban className="h-3 w-3" />
58
+ <span>{project.taskCount} tasks</span>
59
+ </div>
60
+ {project.workingDirectory && (
61
+ <div className="flex items-center gap-1 text-xs text-muted-foreground mt-1">
62
+ <FolderOpen className="h-3 w-3" />
63
+ <span className="truncate">{project.workingDirectory}</span>
64
+ </div>
65
+ )}
66
+ </CardContent>
67
+ </Card>
68
+ </Link>
57
69
  );
58
70
  }
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { Card, CardContent } from "@/components/ui/card";
6
6
  import { Badge } from "@/components/ui/badge";
7
- import { Sparkles, Check, X } from "lucide-react";
7
+ import { Sparkles, Check, X, GitBranch } from "lucide-react";
8
8
 
9
9
  interface TaskSuggestion {
10
10
  title: string;
@@ -27,6 +27,7 @@ interface AIAssistPanelProps {
27
27
  onApplyDescription: (description: string) => void;
28
28
  onCreateSubtasks: (subtasks: TaskSuggestion[]) => void;
29
29
  onResultChange?: (hasResult: boolean) => void;
30
+ onCreateWorkflow?: (result: AssistResult) => void;
30
31
  }
31
32
 
32
33
  const patternLabels: Record<string, string> = {
@@ -34,6 +35,9 @@ const patternLabels: Record<string, string> = {
34
35
  sequence: "Sequence",
35
36
  "planner-executor": "Planner → Executor",
36
37
  checkpoint: "Human Checkpoint",
38
+ parallel: "Parallel",
39
+ loop: "Loop",
40
+ swarm: "Swarm",
37
41
  };
38
42
 
39
43
  const complexityColors: Record<string, string> = {
@@ -88,6 +92,7 @@ export function AIAssistPanel({
88
92
  onApplyDescription,
89
93
  onCreateSubtasks,
90
94
  onResultChange,
95
+ onCreateWorkflow,
91
96
  }: AIAssistPanelProps) {
92
97
  const [loading, setLoading] = useState(false);
93
98
  const [result, setResult] = useState<AssistResult | null>(null);
@@ -205,15 +210,31 @@ export function AIAssistPanel({
205
210
  <span className="text-xs font-medium text-muted-foreground">
206
211
  Suggested Breakdown ({result.breakdown.length} sub-tasks)
207
212
  </span>
208
- <Button
209
- type="button"
210
- variant="ghost"
211
- size="sm"
212
- className="h-6 text-xs"
213
- onClick={() => onCreateSubtasks(result.breakdown)}
214
- >
215
- Create All
216
- </Button>
213
+ <div className="flex gap-1">
214
+ {onCreateWorkflow &&
215
+ result.breakdown.length >= 2 &&
216
+ result.recommendedPattern !== "single" && (
217
+ <Button
218
+ type="button"
219
+ variant="ghost"
220
+ size="sm"
221
+ className="h-6 text-xs"
222
+ onClick={() => onCreateWorkflow(result)}
223
+ >
224
+ <GitBranch className="h-3 w-3 mr-1" />
225
+ Workflow
226
+ </Button>
227
+ )}
228
+ <Button
229
+ type="button"
230
+ variant="ghost"
231
+ size="sm"
232
+ className="h-6 text-xs"
233
+ onClick={() => onCreateSubtasks(result.breakdown)}
234
+ >
235
+ Create All
236
+ </Button>
237
+ </div>
217
238
  </div>
218
239
  <div className="max-h-60 overflow-y-auto space-y-1.5">
219
240
  {result.breakdown.map((sub, i) => (
@@ -5,7 +5,7 @@ import { useSortable } from "@dnd-kit/sortable";
5
5
  import { CSS } from "@dnd-kit/utilities";
6
6
  import { Card } from "@/components/ui/card";
7
7
  import { Badge } from "@/components/ui/badge";
8
- import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil } from "lucide-react";
8
+ import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil, Workflow } from "lucide-react";
9
9
  import type { TaskStatus } from "@/lib/constants/task-status";
10
10
 
11
11
  export interface TaskItem {
@@ -18,6 +18,8 @@ export interface TaskItem {
18
18
  agentProfile: string | null;
19
19
  projectId: string | null;
20
20
  projectName?: string;
21
+ linkedWorkflowId?: string;
22
+ linkedWorkflowStatus?: string;
21
23
  result: string | null;
22
24
  sessionId: string | null;
23
25
  resumeCount: number;
@@ -151,6 +153,19 @@ export function TaskCard({
151
153
  <span className="truncate">{task.assignedAgent}</span>
152
154
  </Badge>
153
155
  )}
156
+ {task.linkedWorkflowId && (
157
+ <Badge
158
+ variant="outline"
159
+ className="text-xs gap-1 cursor-pointer hover:bg-accent/50"
160
+ onClick={(e) => {
161
+ e.stopPropagation();
162
+ window.open(`/workflows/${task.linkedWorkflowId}`, "_self");
163
+ }}
164
+ >
165
+ <Workflow className="h-3 w-3 shrink-0" aria-hidden="true" />
166
+ {task.linkedWorkflowStatus === "completed" ? "Workflow done" : "Workflow"}
167
+ </Badge>
168
+ )}
154
169
  {isFailed && <AlertCircle className="h-3.5 w-3.5 text-destructive" aria-label="Task failed" />}
155
170
  {isRunning && (
156
171
  <span className="flex h-2 w-2" aria-label="Task running">
@@ -18,7 +18,13 @@ import { Bot, FileText, Settings, Paperclip } from "lucide-react";
18
18
  import { toast } from "sonner";
19
19
  import { AIAssistPanel } from "./ai-assist-panel";
20
20
  import { FileUpload } from "./file-upload";
21
+ import type { TaskAssistResponse } from "@/lib/agents/runtime/task-assist-types";
21
22
  import { FormSectionCard } from "@/components/shared/form-section-card";
23
+ import {
24
+ saveAssistState,
25
+ loadTaskFormState,
26
+ clearTaskFormState,
27
+ } from "@/lib/workflows/assist-session";
22
28
  import {
23
29
  type AgentRuntimeId,
24
30
  DEFAULT_AGENT_RUNTIME,
@@ -79,6 +85,24 @@ export function TaskCreatePanel({ projects, defaultProjectId }: TaskCreatePanelP
79
85
  .catch(() => {});
80
86
  }, []);
81
87
 
88
+ // Restore form state when returning from workflow confirmation
89
+ useEffect(() => {
90
+ const params = new URLSearchParams(window.location.search);
91
+ if (params.get("restore") !== "1") return;
92
+ const saved = loadTaskFormState();
93
+ if (saved) {
94
+ setTitle(saved.title);
95
+ setDescription(saved.description);
96
+ setProjectId(saved.projectId);
97
+ setPriority(saved.priority);
98
+ setAgentProfile(saved.agentProfile);
99
+ setAssignedAgent(saved.assignedAgent);
100
+ clearTaskFormState();
101
+ }
102
+ // Clean up URL param without triggering navigation
103
+ window.history.replaceState({}, "", window.location.pathname);
104
+ }, []);
105
+
82
106
  const selectedRuntimeId = (assignedAgent ||
83
107
  DEFAULT_AGENT_RUNTIME) as AgentRuntimeId;
84
108
  const selectedProfile = profiles.find((profile) => profile.id === agentProfile);
@@ -351,8 +375,23 @@ export function TaskCreatePanel({ projects, defaultProjectId }: TaskCreatePanelP
351
375
  }
352
376
  router.push("/dashboard");
353
377
  }}
378
+ onCreateWorkflow={(result) => {
379
+ saveAssistState({
380
+ assistResult: result as TaskAssistResponse,
381
+ formState: {
382
+ title,
383
+ description,
384
+ projectId,
385
+ priority,
386
+ agentProfile,
387
+ assignedAgent,
388
+ },
389
+ });
390
+ router.push("/workflows/from-assist");
391
+ }}
354
392
  />
355
393
  </div>
394
+
356
395
  </div>
357
396
  </form>
358
397
  </CardContent>