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.
- package/README.md +129 -47
- package/package.json +1 -1
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-sorted.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/documents-grid.png +0 -0
- package/public/readme/documents-list.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/monitor-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/projects-detail.png +0 -0
- package/public/readme/projects-list.png +0 -0
- package/public/readme/schedules-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/workflows/from-assist/route.ts +143 -0
- package/src/app/dashboard/page.tsx +24 -2
- package/src/app/workflows/from-assist/page.tsx +35 -0
- package/src/components/projects/project-card.tsx +47 -35
- package/src/components/tasks/ai-assist-panel.tsx +31 -10
- package/src/components/tasks/task-card.tsx +16 -1
- package/src/components/tasks/task-create-panel.tsx +39 -0
- package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
- package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -0
- package/src/lib/agents/profiles/suggest.ts +36 -0
- package/src/lib/agents/runtime/claude.ts +36 -6
- package/src/lib/agents/runtime/task-assist-types.ts +12 -2
- package/src/lib/data/__tests__/clear.test.ts +42 -0
- package/src/lib/data/clear.ts +3 -0
- package/src/lib/notifications/permissions.ts +6 -2
- package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
- package/src/lib/workflows/assist-builder.ts +248 -0
- package/src/lib/workflows/assist-session.ts +78 -0
- 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 {
|
|
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
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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>
|