stagent 0.1.9 → 0.1.11

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 (81) hide show
  1. package/README.md +144 -62
  2. package/package.json +1 -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-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/profiles/route.ts +0 -1
  27. package/src/app/api/workflows/from-assist/route.ts +143 -0
  28. package/src/app/dashboard/page.tsx +24 -2
  29. package/src/app/globals.css +0 -5
  30. package/src/app/tasks/page.tsx +5 -0
  31. package/src/app/workflows/from-assist/page.tsx +35 -0
  32. package/src/components/profiles/profile-detail-view.tsx +1 -16
  33. package/src/components/profiles/profile-form-view.tsx +0 -22
  34. package/src/components/projects/project-card.tsx +47 -35
  35. package/src/components/tasks/ai-assist-panel.tsx +31 -10
  36. package/src/components/tasks/task-card.tsx +16 -1
  37. package/src/components/tasks/task-create-panel.tsx +39 -0
  38. package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
  39. package/src/lib/agents/__tests__/claude-agent.test.ts +7 -2
  40. package/src/lib/agents/__tests__/learned-context.test.ts +500 -0
  41. package/src/lib/agents/__tests__/pattern-extractor.test.ts +243 -0
  42. package/src/lib/agents/__tests__/sweep.test.ts +202 -0
  43. package/src/lib/agents/claude-agent.ts +104 -78
  44. package/src/lib/agents/learned-context.ts +5 -13
  45. package/src/lib/agents/pattern-extractor.ts +15 -64
  46. package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -0
  47. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +0 -1
  48. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +0 -1
  49. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +0 -1
  50. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +0 -1
  51. package/src/lib/agents/profiles/builtins/general/profile.yaml +0 -1
  52. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +0 -1
  53. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +0 -1
  54. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +0 -1
  55. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +0 -1
  56. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +0 -1
  57. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +0 -1
  58. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +0 -1
  59. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +0 -1
  60. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +0 -1
  61. package/src/lib/agents/profiles/registry.ts +0 -1
  62. package/src/lib/agents/profiles/suggest.ts +36 -0
  63. package/src/lib/agents/profiles/types.ts +0 -1
  64. package/src/lib/agents/runtime/catalog.ts +1 -1
  65. package/src/lib/agents/runtime/claude.ts +102 -6
  66. package/src/lib/agents/runtime/task-assist-types.ts +12 -2
  67. package/src/lib/constants/task-status.ts +6 -0
  68. package/src/lib/data/__tests__/clear.test.ts +42 -0
  69. package/src/lib/data/clear.ts +3 -0
  70. package/src/lib/data/seed-data/profiles.ts +0 -3
  71. package/src/lib/notifications/permissions.ts +6 -2
  72. package/src/lib/usage/__tests__/ledger.test.ts +29 -5
  73. package/src/lib/usage/ledger.ts +3 -1
  74. package/src/lib/usage/pricing.ts +61 -7
  75. package/src/lib/validators/__tests__/profile.test.ts +0 -15
  76. package/src/lib/validators/profile.ts +0 -1
  77. package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
  78. package/src/lib/workflows/__tests__/engine.test.ts +2 -0
  79. package/src/lib/workflows/assist-builder.ts +248 -0
  80. package/src/lib/workflows/assist-session.ts +78 -0
  81. package/src/lib/workflows/engine.ts +47 -1
@@ -446,11 +446,6 @@
446
446
  box-shadow: var(--glass-shadow-sm);
447
447
  }
448
448
 
449
- /* Temperature slider gradient track */
450
- .slider-temperature [data-slot="slider-range"] {
451
- background: linear-gradient(90deg, oklch(0.6 0.18 260), oklch(0.7 0.15 55));
452
- }
453
-
454
449
  [data-slot="popover-content"],
455
450
  [data-slot="dropdown-menu-content"],
456
451
  [data-slot="select-content"] {
@@ -0,0 +1,5 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ export default function TasksIndexPage() {
4
+ redirect("/dashboard");
5
+ }
@@ -0,0 +1,35 @@
1
+ import Link from "next/link";
2
+ import { db } from "@/lib/db";
3
+ import { projects } from "@/lib/db/schema";
4
+ import { Button } from "@/components/ui/button";
5
+ import { ArrowLeft } from "lucide-react";
6
+ import { WorkflowConfirmationView } from "@/components/workflows/workflow-confirmation-view";
7
+ import { listProfiles } from "@/lib/agents/profiles/registry";
8
+
9
+ export const dynamic = "force-dynamic";
10
+
11
+ export default async function WorkflowFromAssistPage() {
12
+ const allProjects = await db
13
+ .select({ id: projects.id, name: projects.name })
14
+ .from(projects)
15
+ .orderBy(projects.name);
16
+
17
+ const profiles = listProfiles().map((p) => ({
18
+ id: p.id,
19
+ name: p.name,
20
+ }));
21
+
22
+ return (
23
+ <div className="gradient-ocean-mist min-h-screen p-6">
24
+ <div>
25
+ <Link href="/tasks/new?restore=1">
26
+ <Button variant="ghost" size="sm" className="mb-4">
27
+ <ArrowLeft className="h-4 w-4 mr-1" />
28
+ Back to Task
29
+ </Button>
30
+ </Link>
31
+ <WorkflowConfirmationView projects={allProjects} profiles={profiles} />
32
+ </div>
33
+ </div>
34
+ );
35
+ }
@@ -18,7 +18,6 @@ import {
18
18
  Sparkles,
19
19
  Tag,
20
20
  User,
21
- Thermometer,
22
21
  Repeat,
23
22
  FileOutput,
24
23
  Wrench,
@@ -262,20 +261,6 @@ export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: Prof
262
261
  <CardTitle className="text-sm font-medium">Configuration</CardTitle>
263
262
  </CardHeader>
264
263
  <CardContent className="space-y-3">
265
- {/* Temperature Gauge */}
266
- {profile.temperature !== undefined && (
267
- <div className="flex items-center gap-2">
268
- <Thermometer className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
269
- <span className="text-xs text-muted-foreground w-20">Temperature</span>
270
- <div className="flex-1 h-1.5 rounded-full bg-muted">
271
- <div
272
- className="h-full rounded-full bg-primary"
273
- style={{ width: `${(profile.temperature / 2) * 100}%` }}
274
- />
275
- </div>
276
- <span className="text-xs font-medium w-8 text-right">{profile.temperature}</span>
277
- </div>
278
- )}
279
264
  {/* Max Turns */}
280
265
  {profile.maxTurns !== undefined && (
281
266
  <div className="flex items-center gap-2">
@@ -294,7 +279,7 @@ export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: Prof
294
279
  </Badge>
295
280
  </div>
296
281
  )}
297
- {!profile.temperature && !profile.maxTurns && !profile.outputFormat && (
282
+ {!profile.maxTurns && !profile.outputFormat && (
298
283
  <p className="text-sm text-muted-foreground">Default configuration</p>
299
284
  )}
300
285
  </CardContent>
@@ -70,7 +70,6 @@ export function ProfileFormView({
70
70
  ]);
71
71
  const [codexInstructions, setCodexInstructions] = useState("");
72
72
  const [allowedTools, setAllowedTools] = useState("");
73
- const [temperature, setTemperature] = useState(0.5);
74
73
  const [maxTurns, setMaxTurns] = useState(30);
75
74
  const [outputFormat, setOutputFormat] = useState("");
76
75
  const [submitting, setSubmitting] = useState(false);
@@ -94,7 +93,6 @@ export function ProfileFormView({
94
93
  profile.runtimeOverrides?.["openai-codex-app-server"]?.instructions ?? ""
95
94
  );
96
95
  setAllowedTools(profile.allowedTools?.join(", ") ?? "");
97
- setTemperature(profile.temperature ?? 0.5);
98
96
  setMaxTurns(profile.maxTurns ?? 30);
99
97
  setOutputFormat(profile.outputFormat ?? "");
100
98
  })
@@ -145,7 +143,6 @@ export function ProfileFormView({
145
143
  }
146
144
  : undefined,
147
145
  allowedTools: parseCommaSeparated(allowedTools),
148
- temperature,
149
146
  maxTurns,
150
147
  outputFormat: outputFormat.trim() || undefined,
151
148
  };
@@ -307,25 +304,6 @@ export function ProfileFormView({
307
304
  {/* Model Tuning */}
308
305
  <FormSectionCard icon={SlidersHorizontal} title="Model Tuning">
309
306
  <div className="space-y-4">
310
- <div className="space-y-2">
311
- <div className="flex items-center justify-between">
312
- <Label htmlFor="profile-temp">Temperature</Label>
313
- <Badge variant="secondary" className="tabular-nums text-xs">
314
- {temperature.toFixed(2)}
315
- </Badge>
316
- </div>
317
- <div className="slider-temperature">
318
- <Slider
319
- id="profile-temp"
320
- min={0}
321
- max={1}
322
- step={0.05}
323
- value={[temperature]}
324
- onValueChange={([v]) => setTemperature(v)}
325
- />
326
- </div>
327
- <p className="text-xs text-muted-foreground">Lower = deterministic, higher = creative</p>
328
- </div>
329
307
  <div className="space-y-2">
330
308
  <div className="flex items-center justify-between">
331
309
  <Label htmlFor="profile-turns">Max Turns</Label>
@@ -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>