stagent 0.1.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 (333) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +399 -0
  3. package/components.json +21 -0
  4. package/dist/cli.js +171 -0
  5. package/drizzle.config.ts +12 -0
  6. package/next.config.mjs +15 -0
  7. package/package.json +114 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/icon-512.png +0 -0
  10. package/public/icon.svg +13 -0
  11. package/public/readme/home-workspace.png +0 -0
  12. package/public/readme/inbox-approvals.png +0 -0
  13. package/public/readme/workflow-blueprints.png +0 -0
  14. package/public/stagent-s-128.png +0 -0
  15. package/public/stagent-s-64.png +0 -0
  16. package/src/app/api/blueprints/[id]/instantiate/route.ts +27 -0
  17. package/src/app/api/blueprints/[id]/route.ts +39 -0
  18. package/src/app/api/blueprints/import/route.ts +68 -0
  19. package/src/app/api/blueprints/route.ts +29 -0
  20. package/src/app/api/command-palette/recent/route.ts +31 -0
  21. package/src/app/api/data/clear/route.ts +22 -0
  22. package/src/app/api/data/seed/route.ts +22 -0
  23. package/src/app/api/documents/[id]/file/route.ts +44 -0
  24. package/src/app/api/documents/[id]/route.ts +123 -0
  25. package/src/app/api/documents/route.ts +59 -0
  26. package/src/app/api/logs/stream/route.ts +101 -0
  27. package/src/app/api/notifications/[id]/route.ts +36 -0
  28. package/src/app/api/notifications/mark-all-read/route.ts +13 -0
  29. package/src/app/api/notifications/pending-approvals/route.ts +10 -0
  30. package/src/app/api/notifications/pending-approvals/stream/route.ts +101 -0
  31. package/src/app/api/notifications/route.ts +34 -0
  32. package/src/app/api/permissions/route.ts +46 -0
  33. package/src/app/api/profiles/[id]/route.ts +79 -0
  34. package/src/app/api/profiles/[id]/test/route.ts +42 -0
  35. package/src/app/api/profiles/import/route.ts +108 -0
  36. package/src/app/api/profiles/route.ts +50 -0
  37. package/src/app/api/projects/[id]/route.ts +72 -0
  38. package/src/app/api/projects/route.ts +53 -0
  39. package/src/app/api/schedules/[id]/route.ts +185 -0
  40. package/src/app/api/schedules/route.ts +117 -0
  41. package/src/app/api/settings/budgets/route.ts +24 -0
  42. package/src/app/api/settings/openai/route.ts +24 -0
  43. package/src/app/api/settings/route.ts +21 -0
  44. package/src/app/api/settings/test/route.ts +26 -0
  45. package/src/app/api/tasks/[id]/cancel/route.ts +21 -0
  46. package/src/app/api/tasks/[id]/execute/route.ts +90 -0
  47. package/src/app/api/tasks/[id]/logs/route.ts +95 -0
  48. package/src/app/api/tasks/[id]/output/route.ts +47 -0
  49. package/src/app/api/tasks/[id]/respond/route.ts +64 -0
  50. package/src/app/api/tasks/[id]/resume/route.ts +76 -0
  51. package/src/app/api/tasks/[id]/route.ts +77 -0
  52. package/src/app/api/tasks/assist/route.ts +35 -0
  53. package/src/app/api/tasks/route.ts +82 -0
  54. package/src/app/api/uploads/[id]/route.ts +81 -0
  55. package/src/app/api/uploads/cleanup/route.ts +7 -0
  56. package/src/app/api/uploads/route.ts +66 -0
  57. package/src/app/api/workflows/[id]/execute/route.ts +82 -0
  58. package/src/app/api/workflows/[id]/route.ts +133 -0
  59. package/src/app/api/workflows/[id]/status/route.ts +54 -0
  60. package/src/app/api/workflows/[id]/steps/[stepId]/retry/route.ts +22 -0
  61. package/src/app/api/workflows/route.ts +61 -0
  62. package/src/app/apple-icon.tsx +31 -0
  63. package/src/app/costs/page.tsx +256 -0
  64. package/src/app/dashboard/page.tsx +44 -0
  65. package/src/app/documents/[id]/page.tsx +46 -0
  66. package/src/app/documents/page.tsx +45 -0
  67. package/src/app/error.tsx +26 -0
  68. package/src/app/global-error.tsx +23 -0
  69. package/src/app/globals.css +733 -0
  70. package/src/app/icon.tsx +30 -0
  71. package/src/app/inbox/loading.tsx +15 -0
  72. package/src/app/inbox/page.tsx +35 -0
  73. package/src/app/layout.tsx +78 -0
  74. package/src/app/manifest.ts +32 -0
  75. package/src/app/monitor/page.tsx +37 -0
  76. package/src/app/page.tsx +162 -0
  77. package/src/app/profiles/[id]/edit/page.tsx +39 -0
  78. package/src/app/profiles/[id]/page.tsx +33 -0
  79. package/src/app/profiles/new/page.tsx +22 -0
  80. package/src/app/profiles/page.tsx +19 -0
  81. package/src/app/projects/[id]/page.tsx +134 -0
  82. package/src/app/projects/loading.tsx +17 -0
  83. package/src/app/projects/page.tsx +32 -0
  84. package/src/app/schedules/[id]/page.tsx +47 -0
  85. package/src/app/schedules/page.tsx +18 -0
  86. package/src/app/settings/loading.tsx +24 -0
  87. package/src/app/settings/page.tsx +27 -0
  88. package/src/app/tasks/[id]/page.tsx +45 -0
  89. package/src/app/tasks/new/page.tsx +27 -0
  90. package/src/app/workflows/[id]/edit/page.tsx +66 -0
  91. package/src/app/workflows/[id]/page.tsx +37 -0
  92. package/src/app/workflows/blueprints/[id]/page.tsx +40 -0
  93. package/src/app/workflows/blueprints/new/page.tsx +20 -0
  94. package/src/app/workflows/blueprints/page.tsx +11 -0
  95. package/src/app/workflows/new/page.tsx +36 -0
  96. package/src/app/workflows/page.tsx +18 -0
  97. package/src/components/charts/donut-ring.tsx +64 -0
  98. package/src/components/charts/mini-bar.tsx +75 -0
  99. package/src/components/charts/sparkline.tsx +107 -0
  100. package/src/components/costs/cost-dashboard.tsx +877 -0
  101. package/src/components/costs/cost-filters.tsx +179 -0
  102. package/src/components/dashboard/activity-feed.tsx +95 -0
  103. package/src/components/dashboard/greeting.tsx +30 -0
  104. package/src/components/dashboard/priority-queue.tsx +79 -0
  105. package/src/components/dashboard/quick-actions.tsx +62 -0
  106. package/src/components/dashboard/recent-projects.tsx +79 -0
  107. package/src/components/dashboard/stats-cards.tsx +114 -0
  108. package/src/components/documents/document-browser.tsx +235 -0
  109. package/src/components/documents/document-detail-view.tsx +367 -0
  110. package/src/components/documents/document-grid.tsx +78 -0
  111. package/src/components/documents/document-preview.tsx +68 -0
  112. package/src/components/documents/document-table.tsx +119 -0
  113. package/src/components/documents/document-upload-dialog.tsx +153 -0
  114. package/src/components/documents/types.ts +6 -0
  115. package/src/components/documents/utils.ts +57 -0
  116. package/src/components/monitoring/connection-indicator.tsx +14 -0
  117. package/src/components/monitoring/log-entry.tsx +79 -0
  118. package/src/components/monitoring/log-filters.tsx +57 -0
  119. package/src/components/monitoring/log-stream.tsx +144 -0
  120. package/src/components/monitoring/monitor-overview-wrapper.tsx +64 -0
  121. package/src/components/monitoring/monitor-overview.tsx +119 -0
  122. package/src/components/notifications/failure-action.tsx +38 -0
  123. package/src/components/notifications/inbox-list.tsx +165 -0
  124. package/src/components/notifications/message-response.tsx +196 -0
  125. package/src/components/notifications/notification-item.tsx +250 -0
  126. package/src/components/notifications/pending-approval-host.tsx +478 -0
  127. package/src/components/notifications/permission-action.tsx +37 -0
  128. package/src/components/notifications/permission-response-actions.tsx +126 -0
  129. package/src/components/notifications/unread-badge.tsx +35 -0
  130. package/src/components/profiles/profile-browser.tsx +117 -0
  131. package/src/components/profiles/profile-card.tsx +78 -0
  132. package/src/components/profiles/profile-detail-view.tsx +564 -0
  133. package/src/components/profiles/profile-form-view.tsx +480 -0
  134. package/src/components/profiles/profile-import-dialog.tsx +113 -0
  135. package/src/components/projects/project-card.tsx +58 -0
  136. package/src/components/projects/project-create-dialog.tsx +140 -0
  137. package/src/components/projects/project-detail.tsx +68 -0
  138. package/src/components/projects/project-edit-dialog.tsx +219 -0
  139. package/src/components/projects/project-list.tsx +108 -0
  140. package/src/components/schedules/schedule-create-dialog.tsx +403 -0
  141. package/src/components/schedules/schedule-detail-view.tsx +274 -0
  142. package/src/components/schedules/schedule-list.tsx +242 -0
  143. package/src/components/schedules/schedule-status-badge.tsx +16 -0
  144. package/src/components/settings/api-key-form.tsx +141 -0
  145. package/src/components/settings/auth-config-section.tsx +141 -0
  146. package/src/components/settings/auth-method-selector.tsx +67 -0
  147. package/src/components/settings/auth-status-badge.tsx +40 -0
  148. package/src/components/settings/auth-status-dot.tsx +59 -0
  149. package/src/components/settings/budget-guardrails-section.tsx +842 -0
  150. package/src/components/settings/data-management-section.tsx +141 -0
  151. package/src/components/settings/openai-runtime-section.tsx +104 -0
  152. package/src/components/settings/permissions-section.tsx +91 -0
  153. package/src/components/shared/app-sidebar.tsx +123 -0
  154. package/src/components/shared/card-skeleton.tsx +42 -0
  155. package/src/components/shared/command-palette.tsx +250 -0
  156. package/src/components/shared/confirm-dialog.tsx +52 -0
  157. package/src/components/shared/empty-state.tsx +24 -0
  158. package/src/components/shared/error-state.tsx +32 -0
  159. package/src/components/shared/form-section-card.tsx +33 -0
  160. package/src/components/shared/section-heading.tsx +14 -0
  161. package/src/components/shared/stagent-logo.tsx +21 -0
  162. package/src/components/shared/theme-toggle.tsx +46 -0
  163. package/src/components/tasks/ai-assist-panel.tsx +210 -0
  164. package/src/components/tasks/content-preview.tsx +89 -0
  165. package/src/components/tasks/empty-board.tsx +12 -0
  166. package/src/components/tasks/file-upload.tsx +120 -0
  167. package/src/components/tasks/kanban-board.tsx +275 -0
  168. package/src/components/tasks/kanban-column.tsx +75 -0
  169. package/src/components/tasks/skeleton-board.tsx +21 -0
  170. package/src/components/tasks/task-attachments.tsx +114 -0
  171. package/src/components/tasks/task-card.tsx +101 -0
  172. package/src/components/tasks/task-create-panel.tsx +360 -0
  173. package/src/components/tasks/task-detail-view.tsx +356 -0
  174. package/src/components/ui/alert-dialog.tsx +196 -0
  175. package/src/components/ui/badge.tsx +50 -0
  176. package/src/components/ui/button.tsx +71 -0
  177. package/src/components/ui/card.tsx +92 -0
  178. package/src/components/ui/checkbox.tsx +32 -0
  179. package/src/components/ui/command.tsx +184 -0
  180. package/src/components/ui/dialog.tsx +158 -0
  181. package/src/components/ui/dropdown-menu.tsx +257 -0
  182. package/src/components/ui/form.tsx +167 -0
  183. package/src/components/ui/input.tsx +21 -0
  184. package/src/components/ui/label.tsx +24 -0
  185. package/src/components/ui/popover.tsx +89 -0
  186. package/src/components/ui/progress.tsx +31 -0
  187. package/src/components/ui/radio-group.tsx +45 -0
  188. package/src/components/ui/scroll-area.tsx +58 -0
  189. package/src/components/ui/select.tsx +190 -0
  190. package/src/components/ui/separator.tsx +28 -0
  191. package/src/components/ui/sheet.tsx +143 -0
  192. package/src/components/ui/sidebar.tsx +726 -0
  193. package/src/components/ui/skeleton.tsx +13 -0
  194. package/src/components/ui/slider.tsx +63 -0
  195. package/src/components/ui/sonner.tsx +36 -0
  196. package/src/components/ui/switch.tsx +35 -0
  197. package/src/components/ui/table.tsx +116 -0
  198. package/src/components/ui/tabs.tsx +91 -0
  199. package/src/components/ui/textarea.tsx +18 -0
  200. package/src/components/ui/tooltip.tsx +57 -0
  201. package/src/components/workflows/blueprint-editor.tsx +109 -0
  202. package/src/components/workflows/blueprint-gallery.tsx +155 -0
  203. package/src/components/workflows/blueprint-preview.tsx +240 -0
  204. package/src/components/workflows/loop-status-view.tsx +272 -0
  205. package/src/components/workflows/swarm-dashboard.tsx +185 -0
  206. package/src/components/workflows/workflow-form-view.tsx +1376 -0
  207. package/src/components/workflows/workflow-list.tsx +230 -0
  208. package/src/components/workflows/workflow-status-view.tsx +477 -0
  209. package/src/hooks/use-mobile.ts +19 -0
  210. package/src/instrumentation.ts +7 -0
  211. package/src/lib/agents/claude-agent.ts +737 -0
  212. package/src/lib/agents/execution-manager.ts +27 -0
  213. package/src/lib/agents/profiles/assignment-validation.ts +75 -0
  214. package/src/lib/agents/profiles/builtins/code-reviewer/SKILL.md +21 -0
  215. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +28 -0
  216. package/src/lib/agents/profiles/builtins/data-analyst/SKILL.md +25 -0
  217. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +27 -0
  218. package/src/lib/agents/profiles/builtins/devops-engineer/SKILL.md +34 -0
  219. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +27 -0
  220. package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +16 -0
  221. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +27 -0
  222. package/src/lib/agents/profiles/builtins/general/SKILL.md +13 -0
  223. package/src/lib/agents/profiles/builtins/general/profile.yaml +18 -0
  224. package/src/lib/agents/profiles/builtins/health-fitness-coach/SKILL.md +34 -0
  225. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +26 -0
  226. package/src/lib/agents/profiles/builtins/learning-coach/SKILL.md +35 -0
  227. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +26 -0
  228. package/src/lib/agents/profiles/builtins/project-manager/SKILL.md +26 -0
  229. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +26 -0
  230. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +15 -0
  231. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +27 -0
  232. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +34 -0
  233. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +26 -0
  234. package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +31 -0
  235. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +29 -0
  236. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +23 -0
  237. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +26 -0
  238. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +24 -0
  239. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +26 -0
  240. package/src/lib/agents/profiles/compatibility.ts +109 -0
  241. package/src/lib/agents/profiles/registry.ts +293 -0
  242. package/src/lib/agents/profiles/test-runner.ts +18 -0
  243. package/src/lib/agents/profiles/test-types.ts +20 -0
  244. package/src/lib/agents/profiles/types.ts +43 -0
  245. package/src/lib/agents/router.ts +56 -0
  246. package/src/lib/agents/runtime/catalog.ts +85 -0
  247. package/src/lib/agents/runtime/claude-sdk.ts +12 -0
  248. package/src/lib/agents/runtime/claude.ts +370 -0
  249. package/src/lib/agents/runtime/codex-app-server-client.ts +289 -0
  250. package/src/lib/agents/runtime/index.ts +167 -0
  251. package/src/lib/agents/runtime/openai-codex.ts +1089 -0
  252. package/src/lib/agents/runtime/task-assist-types.ts +8 -0
  253. package/src/lib/agents/runtime/types.ts +30 -0
  254. package/src/lib/constants/settings.ts +13 -0
  255. package/src/lib/constants/status-colors.ts +44 -0
  256. package/src/lib/constants/task-status.ts +49 -0
  257. package/src/lib/data/clear.ts +63 -0
  258. package/src/lib/data/seed-data/documents.ts +715 -0
  259. package/src/lib/data/seed-data/logs.ts +195 -0
  260. package/src/lib/data/seed-data/notifications.ts +141 -0
  261. package/src/lib/data/seed-data/profiles.ts +175 -0
  262. package/src/lib/data/seed-data/projects.ts +61 -0
  263. package/src/lib/data/seed-data/schedules.ts +108 -0
  264. package/src/lib/data/seed-data/tasks.ts +341 -0
  265. package/src/lib/data/seed-data/usage-ledger.ts +130 -0
  266. package/src/lib/data/seed-data/workflows.ts +213 -0
  267. package/src/lib/data/seed.ts +129 -0
  268. package/src/lib/db/index.ts +221 -0
  269. package/src/lib/db/migrations/0000_aromatic_gargoyle.sql +59 -0
  270. package/src/lib/db/migrations/0001_first_iron_patriot.sql +6 -0
  271. package/src/lib/db/migrations/0002_add_resume_count.sql +1 -0
  272. package/src/lib/db/migrations/0003_add_settings.sql +5 -0
  273. package/src/lib/db/migrations/0004_add_documents.sql +20 -0
  274. package/src/lib/db/migrations/0005_add_document_preprocessing.sql +4 -0
  275. package/src/lib/db/migrations/0006_add_agent_profile.sql +2 -0
  276. package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +30 -0
  277. package/src/lib/db/migrations/0008_add_document_version.sql +1 -0
  278. package/src/lib/db/migrations/meta/0000_snapshot.json +416 -0
  279. package/src/lib/db/migrations/meta/0001_snapshot.json +461 -0
  280. package/src/lib/db/migrations/meta/0002_snapshot.json +469 -0
  281. package/src/lib/db/migrations/meta/_journal.json +27 -0
  282. package/src/lib/db/schema.ts +227 -0
  283. package/src/lib/documents/cleanup.ts +50 -0
  284. package/src/lib/documents/context-builder.ts +75 -0
  285. package/src/lib/documents/output-scanner.ts +166 -0
  286. package/src/lib/documents/processor.ts +120 -0
  287. package/src/lib/documents/processors/image.ts +21 -0
  288. package/src/lib/documents/processors/office.ts +36 -0
  289. package/src/lib/documents/processors/pdf.ts +12 -0
  290. package/src/lib/documents/processors/spreadsheet.ts +18 -0
  291. package/src/lib/documents/processors/text.ts +8 -0
  292. package/src/lib/documents/registry.ts +25 -0
  293. package/src/lib/notifications/actionable.ts +108 -0
  294. package/src/lib/notifications/permissions.ts +169 -0
  295. package/src/lib/queries/chart-data.ts +184 -0
  296. package/src/lib/schedules/interval-parser.ts +110 -0
  297. package/src/lib/schedules/scheduler.ts +220 -0
  298. package/src/lib/settings/auth.ts +98 -0
  299. package/src/lib/settings/budget-guardrails.ts +590 -0
  300. package/src/lib/settings/helpers.ts +23 -0
  301. package/src/lib/settings/openai-auth.ts +80 -0
  302. package/src/lib/settings/permissions.ts +102 -0
  303. package/src/lib/usage/ledger.ts +489 -0
  304. package/src/lib/usage/pricing.ts +68 -0
  305. package/src/lib/utils/crypto.ts +90 -0
  306. package/src/lib/utils/format-timestamp.ts +46 -0
  307. package/src/lib/utils/session-cleanup.ts +26 -0
  308. package/src/lib/utils/stagent-paths.ts +18 -0
  309. package/src/lib/utils.ts +6 -0
  310. package/src/lib/validators/blueprint.ts +43 -0
  311. package/src/lib/validators/profile.ts +64 -0
  312. package/src/lib/validators/project.ts +17 -0
  313. package/src/lib/validators/settings.ts +57 -0
  314. package/src/lib/validators/task.ts +30 -0
  315. package/src/lib/workflows/blueprints/builtins/code-review-pipeline.yaml +72 -0
  316. package/src/lib/workflows/blueprints/builtins/documentation-generation.yaml +62 -0
  317. package/src/lib/workflows/blueprints/builtins/investment-research.yaml +81 -0
  318. package/src/lib/workflows/blueprints/builtins/meal-planning.yaml +73 -0
  319. package/src/lib/workflows/blueprints/builtins/product-research.yaml +72 -0
  320. package/src/lib/workflows/blueprints/builtins/research-report.yaml +77 -0
  321. package/src/lib/workflows/blueprints/builtins/sprint-planning.yaml +77 -0
  322. package/src/lib/workflows/blueprints/builtins/travel-planning.yaml +80 -0
  323. package/src/lib/workflows/blueprints/instantiator.ts +131 -0
  324. package/src/lib/workflows/blueprints/registry.ts +128 -0
  325. package/src/lib/workflows/blueprints/template.ts +58 -0
  326. package/src/lib/workflows/blueprints/types.ts +38 -0
  327. package/src/lib/workflows/definition-validation.ts +121 -0
  328. package/src/lib/workflows/engine.ts +1113 -0
  329. package/src/lib/workflows/loop-executor.ts +270 -0
  330. package/src/lib/workflows/parallel.ts +55 -0
  331. package/src/lib/workflows/swarm.ts +97 -0
  332. package/src/lib/workflows/types.ts +112 -0
  333. package/tsconfig.json +41 -0
@@ -0,0 +1,140 @@
1
+ "use client";
2
+
3
+ import { useRef, useState } from "react";
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from "@/components/ui/dialog";
12
+ import { Button } from "@/components/ui/button";
13
+ import { Input } from "@/components/ui/input";
14
+ import { Textarea } from "@/components/ui/textarea";
15
+ import { Label } from "@/components/ui/label";
16
+ import { Plus, FolderOpen, AlignLeft, FolderCode } from "lucide-react";
17
+ import { toast } from "sonner";
18
+
19
+ interface ProjectCreateDialogProps {
20
+ onCreated: () => void;
21
+ }
22
+
23
+ export function ProjectCreateDialog({ onCreated }: ProjectCreateDialogProps) {
24
+ const [open, setOpen] = useState(false);
25
+ const [name, setName] = useState("");
26
+ const [description, setDescription] = useState("");
27
+ const [workingDirectory, setWorkingDirectory] = useState("");
28
+ const [loading, setLoading] = useState(false);
29
+ const [error, setError] = useState<string | null>(null);
30
+ const triggerRef = useRef<HTMLButtonElement>(null);
31
+
32
+ async function handleSubmit(e: React.FormEvent) {
33
+ e.preventDefault();
34
+ if (!name.trim()) return;
35
+ setLoading(true);
36
+ setError(null);
37
+ try {
38
+ const res = await fetch("/api/projects", {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({
42
+ name: name.trim(),
43
+ description: description.trim() || undefined,
44
+ workingDirectory: workingDirectory.trim() || undefined,
45
+ }),
46
+ });
47
+ if (res.ok) {
48
+ setName("");
49
+ setDescription("");
50
+ setWorkingDirectory("");
51
+ setOpen(false);
52
+ toast.success("Project created");
53
+ onCreated();
54
+ } else {
55
+ const data = await res.json().catch(() => null);
56
+ setError(data?.error ?? `Failed to create project (${res.status})`);
57
+ }
58
+ } catch (err) {
59
+ setError("Network error — could not reach server");
60
+ console.error("Project creation failed:", err);
61
+ } finally {
62
+ setLoading(false);
63
+ }
64
+ }
65
+
66
+ return (
67
+ <Dialog open={open} onOpenChange={setOpen}>
68
+ <DialogTrigger asChild>
69
+ <Button ref={triggerRef}>
70
+ <Plus className="h-4 w-4 mr-2" />
71
+ New Project
72
+ </Button>
73
+ </DialogTrigger>
74
+ <DialogContent
75
+ onCloseAutoFocus={(event) => {
76
+ event.preventDefault();
77
+ triggerRef.current?.focus();
78
+ }}
79
+ >
80
+ <DialogHeader>
81
+ <DialogTitle>Create Project</DialogTitle>
82
+ <DialogDescription>
83
+ Create a project workspace with optional context so tasks stay grouped and runnable in the right directory.
84
+ </DialogDescription>
85
+ </DialogHeader>
86
+ <form onSubmit={handleSubmit} className="space-y-4">
87
+ <div className="space-y-2">
88
+ <Label htmlFor="name" className="flex items-center gap-1.5">
89
+ <FolderOpen className="h-3.5 w-3.5 text-muted-foreground" />
90
+ Name
91
+ </Label>
92
+ <Input
93
+ id="name"
94
+ value={name}
95
+ onChange={(e) => setName(e.target.value)}
96
+ placeholder="Project name"
97
+ required
98
+ />
99
+ <p className="text-xs text-muted-foreground">Short, memorable name</p>
100
+ </div>
101
+ <div className="space-y-2">
102
+ <Label htmlFor="description" className="flex items-center gap-1.5">
103
+ <AlignLeft className="h-3.5 w-3.5 text-muted-foreground" />
104
+ Description
105
+ </Label>
106
+ <Textarea
107
+ id="description"
108
+ value={description}
109
+ onChange={(e) => setDescription(e.target.value)}
110
+ placeholder="Optional description"
111
+ rows={3}
112
+ />
113
+ <p className="text-xs text-muted-foreground">Optional context for agents</p>
114
+ </div>
115
+ <div className="space-y-2">
116
+ <Label htmlFor="working-dir" className="flex items-center gap-1.5">
117
+ <FolderCode className="h-3.5 w-3.5 text-muted-foreground" />
118
+ Working Directory
119
+ </Label>
120
+ <Input
121
+ id="working-dir"
122
+ value={workingDirectory}
123
+ onChange={(e) => setWorkingDirectory(e.target.value)}
124
+ placeholder="/path/to/project (optional)"
125
+ />
126
+ <p className="text-xs text-muted-foreground">
127
+ Agent tasks will execute in this directory. Defaults to the Stagent server directory if empty.
128
+ </p>
129
+ </div>
130
+ {error && (
131
+ <p className="text-sm text-destructive">{error}</p>
132
+ )}
133
+ <Button type="submit" disabled={loading || !name.trim()} className="w-full">
134
+ {loading ? "Creating..." : "Create Project"}
135
+ </Button>
136
+ </form>
137
+ </DialogContent>
138
+ </Dialog>
139
+ );
140
+ }
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Card } from "@/components/ui/card";
6
+ import { EmptyState } from "@/components/shared/empty-state";
7
+ import { ClipboardList } from "lucide-react";
8
+ import { taskStatusVariant } from "@/lib/constants/status-colors";
9
+ import { SectionHeading } from "@/components/shared/section-heading";
10
+
11
+ interface Task {
12
+ id: string;
13
+ title: string;
14
+ status: string;
15
+ priority: number;
16
+ description: string | null;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ }
20
+
21
+ interface ProjectDetailClientProps {
22
+ tasks: Task[];
23
+ projectId: string;
24
+ }
25
+
26
+ const priorityLabels = ["P0", "P1", "P2", "P3"];
27
+
28
+ export function ProjectDetailClient({ tasks }: ProjectDetailClientProps) {
29
+ if (tasks.length === 0) {
30
+ return (
31
+ <EmptyState
32
+ icon={ClipboardList}
33
+ heading="No tasks yet"
34
+ description="Create a task and assign it to this project."
35
+ />
36
+ );
37
+ }
38
+
39
+ return (
40
+ <div>
41
+ <SectionHeading>Tasks ({tasks.length})</SectionHeading>
42
+ <div className="space-y-2">
43
+ {tasks.map((task) => (
44
+ <Link key={task.id} href={`/tasks/${task.id}`}>
45
+ <Card className="p-3 cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl">
46
+ <div className="flex items-center gap-3">
47
+ <span className="text-xs font-mono text-muted-foreground w-6">
48
+ {priorityLabels[task.priority] ?? "P3"}
49
+ </span>
50
+ <div className="flex-1 min-w-0">
51
+ <p className="text-sm font-medium truncate">{task.title}</p>
52
+ {task.description && (
53
+ <p className="text-xs text-muted-foreground truncate">
54
+ {task.description}
55
+ </p>
56
+ )}
57
+ </div>
58
+ <Badge variant={taskStatusVariant[task.status] ?? "secondary"} className="text-xs">
59
+ {task.status}
60
+ </Badge>
61
+ </div>
62
+ </Card>
63
+ </Link>
64
+ ))}
65
+ </div>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,219 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from "@/components/ui/dialog";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Input } from "@/components/ui/input";
13
+ import { Textarea } from "@/components/ui/textarea";
14
+ import { Label } from "@/components/ui/label";
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from "@/components/ui/select";
22
+ import { FolderOpen, AlignLeft, FolderCode } from "lucide-react";
23
+ import { toast } from "sonner";
24
+ import { ConfirmDialog } from "@/components/shared/confirm-dialog";
25
+
26
+ interface Project {
27
+ id: string;
28
+ name: string;
29
+ description: string | null;
30
+ workingDirectory: string | null;
31
+ status: string;
32
+ }
33
+
34
+ interface ProjectEditDialogProps {
35
+ project: Project | null;
36
+ open: boolean;
37
+ onOpenChange: (open: boolean) => void;
38
+ onUpdated: () => void;
39
+ restoreFocusElement?: HTMLElement | null;
40
+ }
41
+
42
+ export function ProjectEditDialog({
43
+ project,
44
+ open,
45
+ onOpenChange,
46
+ onUpdated,
47
+ restoreFocusElement,
48
+ }: ProjectEditDialogProps) {
49
+ const [name, setName] = useState("");
50
+ const [description, setDescription] = useState("");
51
+ const [workingDirectory, setWorkingDirectory] = useState("");
52
+ const [status, setStatus] = useState("active");
53
+ const [loading, setLoading] = useState(false);
54
+ const [confirmDelete, setConfirmDelete] = useState(false);
55
+
56
+ useEffect(() => {
57
+ if (project) {
58
+ setName(project.name);
59
+ setDescription(project.description ?? "");
60
+ setWorkingDirectory(project.workingDirectory ?? "");
61
+ setStatus(project.status);
62
+ }
63
+ }, [project]);
64
+
65
+ async function handleSubmit(e: React.FormEvent) {
66
+ e.preventDefault();
67
+ if (!project || !name.trim()) return;
68
+ setLoading(true);
69
+ try {
70
+ const res = await fetch(`/api/projects/${project.id}`, {
71
+ method: "PATCH",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify({
74
+ name: name.trim(),
75
+ description: description.trim() || undefined,
76
+ workingDirectory: workingDirectory.trim() || undefined,
77
+ status,
78
+ }),
79
+ });
80
+ if (res.ok) {
81
+ toast.success("Project saved");
82
+ onOpenChange(false);
83
+ onUpdated();
84
+ } else {
85
+ toast.error("Failed to save project");
86
+ }
87
+ } finally {
88
+ setLoading(false);
89
+ }
90
+ }
91
+
92
+ async function handleDelete() {
93
+ if (!project) return;
94
+ setLoading(true);
95
+ try {
96
+ await fetch(`/api/projects/${project.id}`, { method: "DELETE" });
97
+ toast.success("Project deleted");
98
+ onOpenChange(false);
99
+ onUpdated();
100
+ } finally {
101
+ setLoading(false);
102
+ }
103
+ }
104
+
105
+ return (
106
+ <>
107
+ <Dialog open={open} onOpenChange={onOpenChange}>
108
+ <DialogContent
109
+ onCloseAutoFocus={(event) => {
110
+ if (!restoreFocusElement) return;
111
+ event.preventDefault();
112
+ restoreFocusElement.focus();
113
+ }}
114
+ >
115
+ <DialogHeader>
116
+ <DialogTitle>Edit Project</DialogTitle>
117
+ <DialogDescription>
118
+ Update the project name, status, and execution context without changing its associated tasks.
119
+ </DialogDescription>
120
+ </DialogHeader>
121
+ <form onSubmit={handleSubmit} className="space-y-4">
122
+ <div className="space-y-2">
123
+ <Label htmlFor="edit-name" className="flex items-center gap-1.5">
124
+ <FolderOpen className="h-3.5 w-3.5 text-muted-foreground" />
125
+ Name
126
+ </Label>
127
+ <Input
128
+ id="edit-name"
129
+ value={name}
130
+ onChange={(e) => setName(e.target.value)}
131
+ required
132
+ />
133
+ <p className="text-xs text-muted-foreground">Short, memorable name</p>
134
+ </div>
135
+ <div className="space-y-2">
136
+ <Label htmlFor="edit-description" className="flex items-center gap-1.5">
137
+ <AlignLeft className="h-3.5 w-3.5 text-muted-foreground" />
138
+ Description
139
+ </Label>
140
+ <Textarea
141
+ id="edit-description"
142
+ value={description}
143
+ onChange={(e) => setDescription(e.target.value)}
144
+ rows={3}
145
+ />
146
+ <p className="text-xs text-muted-foreground">Optional context for agents</p>
147
+ </div>
148
+ <div className="space-y-2">
149
+ <Label htmlFor="edit-working-dir" className="flex items-center gap-1.5">
150
+ <FolderCode className="h-3.5 w-3.5 text-muted-foreground" />
151
+ Working Directory
152
+ </Label>
153
+ <Input
154
+ id="edit-working-dir"
155
+ value={workingDirectory}
156
+ onChange={(e) => setWorkingDirectory(e.target.value)}
157
+ placeholder="/path/to/project (optional)"
158
+ />
159
+ <p className="text-xs text-muted-foreground">
160
+ Agent tasks will execute in this directory.
161
+ </p>
162
+ </div>
163
+ <div className="space-y-2">
164
+ <Label htmlFor="edit-status">Status</Label>
165
+ <Select value={status} onValueChange={setStatus}>
166
+ <SelectTrigger>
167
+ <SelectValue />
168
+ </SelectTrigger>
169
+ <SelectContent>
170
+ <SelectItem value="active">
171
+ <span className="flex items-center gap-1.5">
172
+ <span className="h-2 w-2 rounded-full bg-[var(--status-completed)] inline-block" />
173
+ Active
174
+ </span>
175
+ </SelectItem>
176
+ <SelectItem value="paused">
177
+ <span className="flex items-center gap-1.5">
178
+ <span className="h-2 w-2 rounded-full bg-[var(--status-warning)] inline-block" />
179
+ Paused
180
+ </span>
181
+ </SelectItem>
182
+ <SelectItem value="completed">
183
+ <span className="flex items-center gap-1.5">
184
+ <span className="h-2 w-2 rounded-full bg-[var(--status-running)] inline-block" />
185
+ Completed
186
+ </span>
187
+ </SelectItem>
188
+ </SelectContent>
189
+ </Select>
190
+ <p className="text-xs text-muted-foreground">Paused projects won&apos;t accept task executions</p>
191
+ </div>
192
+ <div className="flex gap-2">
193
+ <Button type="submit" disabled={loading || !name.trim()} className="flex-1">
194
+ {loading ? "Saving..." : "Save"}
195
+ </Button>
196
+ <Button
197
+ type="button"
198
+ variant="destructive"
199
+ onClick={() => setConfirmDelete(true)}
200
+ disabled={loading}
201
+ >
202
+ Delete
203
+ </Button>
204
+ </div>
205
+ </form>
206
+ </DialogContent>
207
+ </Dialog>
208
+ <ConfirmDialog
209
+ open={confirmDelete}
210
+ onOpenChange={setConfirmDelete}
211
+ title="Delete project?"
212
+ description="This will permanently delete the project. Tasks associated with it will not be deleted."
213
+ confirmLabel="Delete Project"
214
+ destructive
215
+ onConfirm={handleDelete}
216
+ />
217
+ </>
218
+ );
219
+ }
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback, useRef } from "react";
4
+ import { FolderKanban } from "lucide-react";
5
+ import { ProjectCard } from "./project-card";
6
+ import { ProjectCreateDialog } from "./project-create-dialog";
7
+ import { ProjectEditDialog } from "./project-edit-dialog";
8
+ import { EmptyState } from "@/components/shared/empty-state";
9
+ import { SectionHeading } from "@/components/shared/section-heading";
10
+
11
+ interface Project {
12
+ id: string;
13
+ name: string;
14
+ description: string | null;
15
+ workingDirectory: string | null;
16
+ status: string;
17
+ taskCount: number;
18
+ }
19
+
20
+ export function ProjectList({ initialProjects }: { initialProjects: Project[] }) {
21
+ const [projects, setProjects] = useState<Project[]>(initialProjects);
22
+ const [editProject, setEditProject] = useState<Project | null>(null);
23
+ const [editOpen, setEditOpen] = useState(false);
24
+ const restoreFocusRef = useRef<HTMLElement | null>(null);
25
+ const activeProjects = projects.filter((project) => project.status === "active").length;
26
+ const totalTasks = projects.reduce((sum, project) => sum + project.taskCount, 0);
27
+
28
+ const refresh = useCallback(async () => {
29
+ const res = await fetch("/api/projects");
30
+ if (res.ok) setProjects(await res.json());
31
+ }, []);
32
+
33
+ function handleEdit(id: string, trigger: HTMLElement | null) {
34
+ const project = projects.find((p) => p.id === id);
35
+ if (project) {
36
+ restoreFocusRef.current = trigger;
37
+ setEditProject(project);
38
+ setEditOpen(true);
39
+ }
40
+ }
41
+
42
+ return (
43
+ <div className="space-y-6">
44
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
45
+ <div className="space-y-1">
46
+ <h1 className="text-2xl font-bold">Projects</h1>
47
+ <p className="max-w-2xl text-sm text-muted-foreground">
48
+ Keep agent work anchored to durable project spaces so tasks, files, and follow-up flows stay legible.
49
+ </p>
50
+ </div>
51
+ <ProjectCreateDialog onCreated={refresh} />
52
+ </div>
53
+
54
+ <div className="grid gap-3 md:grid-cols-3">
55
+ <div className="surface-control rounded-2xl p-4">
56
+ <p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
57
+ Total Projects
58
+ </p>
59
+ <p className="mt-3 text-3xl font-semibold">{projects.length}</p>
60
+ </div>
61
+ <div className="surface-control rounded-2xl p-4">
62
+ <p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
63
+ Active
64
+ </p>
65
+ <p className="mt-3 text-3xl font-semibold">{activeProjects}</p>
66
+ </div>
67
+ <div className="surface-control rounded-2xl p-4">
68
+ <p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
69
+ Linked Tasks
70
+ </p>
71
+ <p className="mt-3 text-3xl font-semibold">{totalTasks}</p>
72
+ </div>
73
+ </div>
74
+
75
+ <section className="surface-panel rounded-[24px] p-4 sm:p-5">
76
+ <div className="mb-4 flex items-baseline justify-between gap-3">
77
+ <SectionHeading className="mb-0">All Projects</SectionHeading>
78
+ <p className="text-xs text-muted-foreground">{projects.length} visible</p>
79
+ </div>
80
+ {projects.length === 0 ? (
81
+ <EmptyState
82
+ icon={FolderKanban}
83
+ heading="No projects yet"
84
+ description="Create your first project to organize your tasks."
85
+ />
86
+ ) : (
87
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
88
+ {projects.map((project) => (
89
+ <ProjectCard
90
+ key={project.id}
91
+ project={project}
92
+ onEdit={handleEdit}
93
+ />
94
+ ))}
95
+ </div>
96
+ )}
97
+ </section>
98
+
99
+ <ProjectEditDialog
100
+ project={editProject}
101
+ open={editOpen}
102
+ onOpenChange={setEditOpen}
103
+ onUpdated={refresh}
104
+ restoreFocusElement={restoreFocusRef.current}
105
+ />
106
+ </div>
107
+ );
108
+ }