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,242 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6
+ import { Skeleton } from "@/components/ui/skeleton";
7
+ import { Button } from "@/components/ui/button";
8
+ import { ScheduleCreateDialog } from "./schedule-create-dialog";
9
+ import { ScheduleStatusBadge } from "./schedule-status-badge";
10
+ import { ConfirmDialog } from "@/components/shared/confirm-dialog";
11
+ import { EmptyState } from "@/components/shared/empty-state";
12
+ import { describeCron } from "@/lib/schedules/interval-parser";
13
+ import { Clock, Pause, Play, Trash2 } from "lucide-react";
14
+ import { toast } from "sonner";
15
+
16
+ interface Schedule {
17
+ id: string;
18
+ name: string;
19
+ prompt: string;
20
+ cronExpression: string;
21
+ assignedAgent: string | null;
22
+ agentProfile: string | null;
23
+ recurs: boolean;
24
+ status: string;
25
+ maxFirings: number | null;
26
+ firingCount: number;
27
+ lastFiredAt: string | null;
28
+ nextFireAt: string | null;
29
+ createdAt: string;
30
+ }
31
+
32
+ interface ScheduleListProps {
33
+ projects: { id: string; name: string }[];
34
+ }
35
+
36
+ export function ScheduleList({ projects }: ScheduleListProps) {
37
+ const router = useRouter();
38
+ const [schedules, setSchedules] = useState<Schedule[]>([]);
39
+ const [loaded, setLoaded] = useState(false);
40
+ const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);
41
+
42
+ const refresh = useCallback(async () => {
43
+ const res = await fetch("/api/schedules");
44
+ if (res.ok) setSchedules(await res.json());
45
+ setLoaded(true);
46
+ }, []);
47
+
48
+ useEffect(() => {
49
+ refresh();
50
+ }, [refresh]);
51
+
52
+ async function handlePauseResume(id: string, currentStatus: string) {
53
+ const newStatus = currentStatus === "active" ? "paused" : "active";
54
+ const res = await fetch(`/api/schedules/${id}`, {
55
+ method: "PATCH",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({ status: newStatus }),
58
+ });
59
+ if (res.ok) {
60
+ toast.success(
61
+ newStatus === "paused" ? "Schedule paused" : "Schedule resumed"
62
+ );
63
+ refresh();
64
+ } else {
65
+ const data = await res.json().catch(() => null);
66
+ toast.error(data?.error ?? "Failed to update schedule");
67
+ }
68
+ }
69
+
70
+ async function handleDelete(id: string) {
71
+ setConfirmDeleteId(null);
72
+ const res = await fetch(`/api/schedules/${id}`, { method: "DELETE" });
73
+ if (res.ok) {
74
+ toast.success("Schedule deleted");
75
+ refresh();
76
+ } else {
77
+ const data = await res.json().catch(() => null);
78
+ toast.error(data?.error ?? "Failed to delete schedule");
79
+ }
80
+ }
81
+
82
+ function formatRelative(dateStr: string | null): string {
83
+ if (!dateStr) return "—";
84
+ const date = new Date(dateStr);
85
+ const now = new Date();
86
+ const diffMs = date.getTime() - now.getTime();
87
+ const absDiff = Math.abs(diffMs);
88
+
89
+ if (absDiff < 60_000) return diffMs > 0 ? "in <1m" : "<1m ago";
90
+ if (absDiff < 3_600_000) {
91
+ const mins = Math.round(absDiff / 60_000);
92
+ return diffMs > 0 ? `in ${mins}m` : `${mins}m ago`;
93
+ }
94
+ if (absDiff < 86_400_000) {
95
+ const hrs = Math.round(absDiff / 3_600_000);
96
+ return diffMs > 0 ? `in ${hrs}h` : `${hrs}h ago`;
97
+ }
98
+ return date.toLocaleDateString();
99
+ }
100
+
101
+ return (
102
+ <div>
103
+ <div className="flex items-center justify-between mb-6">
104
+ <h1 className="text-2xl font-bold">Schedules</h1>
105
+ <ScheduleCreateDialog projects={projects} onCreated={refresh} />
106
+ </div>
107
+
108
+ {!loaded ? (
109
+ <div
110
+ className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
111
+ aria-live="polite"
112
+ >
113
+ {[1, 2, 3].map((i) => (
114
+ <Card key={i}>
115
+ <CardHeader className="pb-2">
116
+ <div className="flex items-center justify-between">
117
+ <Skeleton className="h-5 w-32" />
118
+ <Skeleton className="h-5 w-16 rounded-full" />
119
+ </div>
120
+ </CardHeader>
121
+ <CardContent>
122
+ <Skeleton className="h-3 w-24" />
123
+ </CardContent>
124
+ </Card>
125
+ ))}
126
+ </div>
127
+ ) : schedules.length === 0 ? (
128
+ <EmptyState
129
+ icon={Clock}
130
+ heading="No schedules yet"
131
+ description="Create a schedule to run agent tasks on a recurring interval or one-time delay."
132
+ />
133
+ ) : (
134
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
135
+ {schedules.map((sched) => (
136
+ <Card
137
+ key={sched.id}
138
+ tabIndex={0}
139
+ className="cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
140
+ onClick={() => router.push(`/schedules/${sched.id}`)}
141
+ onKeyDown={(e) => {
142
+ if (e.key === "Enter" || e.key === " ") {
143
+ e.preventDefault();
144
+ router.push(`/schedules/${sched.id}`);
145
+ }
146
+ }}
147
+ >
148
+ <CardHeader className="pb-2">
149
+ <div className="flex items-center justify-between">
150
+ <CardTitle className="text-base font-medium">
151
+ {sched.name}
152
+ </CardTitle>
153
+ <ScheduleStatusBadge status={sched.status} />
154
+ </div>
155
+ </CardHeader>
156
+ <CardContent>
157
+ <div className="flex items-center gap-3 text-xs text-muted-foreground">
158
+ <span>{describeCron(sched.cronExpression)}</span>
159
+ <span>·</span>
160
+ <span>
161
+ {sched.firingCount} firing
162
+ {sched.firingCount !== 1 ? "s" : ""}
163
+ </span>
164
+ {!sched.recurs && (
165
+ <>
166
+ <span>·</span>
167
+ <span>One-shot</span>
168
+ </>
169
+ )}
170
+ </div>
171
+ <p className="text-xs text-muted-foreground line-clamp-2 mt-1.5">
172
+ {sched.prompt.length > 80
173
+ ? sched.prompt.slice(0, 80) + "..."
174
+ : sched.prompt}
175
+ </p>
176
+ {(sched.assignedAgent || sched.agentProfile) && (
177
+ <p className="text-xs text-muted-foreground mt-1">
178
+ {sched.assignedAgent ? `Runtime: ${sched.assignedAgent}` : "Default runtime"}
179
+ {sched.agentProfile ? ` · Profile: ${sched.agentProfile}` : ""}
180
+ </p>
181
+ )}
182
+ {sched.status === "active" && sched.nextFireAt && (
183
+ <p className="text-xs text-muted-foreground mt-1">
184
+ Next: {formatRelative(sched.nextFireAt)}
185
+ </p>
186
+ )}
187
+ <div className="flex items-center gap-1 mt-3">
188
+ {(sched.status === "active" || sched.status === "paused") && (
189
+ <Button
190
+ variant="ghost"
191
+ size="icon"
192
+ className="h-7 w-7"
193
+ aria-label={
194
+ sched.status === "active"
195
+ ? "Pause schedule"
196
+ : "Resume schedule"
197
+ }
198
+ onClick={(e) => {
199
+ e.stopPropagation();
200
+ handlePauseResume(sched.id, sched.status);
201
+ }}
202
+ >
203
+ {sched.status === "active" ? (
204
+ <Pause className="h-3.5 w-3.5" />
205
+ ) : (
206
+ <Play className="h-3.5 w-3.5" />
207
+ )}
208
+ </Button>
209
+ )}
210
+ <Button
211
+ variant="ghost"
212
+ size="icon"
213
+ className="h-7 w-7 text-destructive"
214
+ aria-label="Delete schedule"
215
+ onClick={(e) => {
216
+ e.stopPropagation();
217
+ setConfirmDeleteId(sched.id);
218
+ }}
219
+ >
220
+ <Trash2 className="h-3.5 w-3.5" />
221
+ </Button>
222
+ </div>
223
+ </CardContent>
224
+ </Card>
225
+ ))}
226
+ </div>
227
+ )}
228
+
229
+ <ConfirmDialog
230
+ open={confirmDeleteId !== null}
231
+ onOpenChange={(open) => {
232
+ if (!open) setConfirmDeleteId(null);
233
+ }}
234
+ title="Delete Schedule"
235
+ description="This will permanently delete this schedule. Child tasks will be kept."
236
+ confirmLabel="Delete"
237
+ onConfirm={() => confirmDeleteId && handleDelete(confirmDeleteId)}
238
+ destructive
239
+ />
240
+ </div>
241
+ );
242
+ }
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ import { Badge } from "@/components/ui/badge";
4
+ import { scheduleStatusVariant } from "@/lib/constants/status-colors";
5
+
6
+ interface ScheduleStatusBadgeProps {
7
+ status: string;
8
+ }
9
+
10
+ export function ScheduleStatusBadge({ status }: ScheduleStatusBadgeProps) {
11
+ return (
12
+ <Badge variant={scheduleStatusVariant[status] ?? "secondary"}>
13
+ {status}
14
+ </Badge>
15
+ );
16
+ }
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+ import { Label } from "@/components/ui/label";
7
+ import { Loader2, Eye, EyeOff, CheckCircle2, XCircle } from "lucide-react";
8
+
9
+ interface ApiKeyFormProps {
10
+ hasKey: boolean;
11
+ onSave: (key: string) => Promise<void>;
12
+ onTest: () => Promise<{ connected: boolean; apiKeySource?: string; error?: string }>;
13
+ keyPrefix?: string;
14
+ placeholder?: string;
15
+ maskedPrefix?: string;
16
+ envVarName?: string;
17
+ testButtonLabel?: string;
18
+ }
19
+
20
+ export function ApiKeyForm({
21
+ hasKey,
22
+ onSave,
23
+ onTest,
24
+ keyPrefix = "sk-ant-",
25
+ placeholder = "sk-ant-...",
26
+ maskedPrefix = "sk-ant-••••••",
27
+ envVarName = "ANTHROPIC_API_KEY",
28
+ testButtonLabel = "Test Connection",
29
+ }: ApiKeyFormProps) {
30
+ const [key, setKey] = useState("");
31
+ const [showInput, setShowInput] = useState(!hasKey);
32
+ const [showKey, setShowKey] = useState(false);
33
+ const [saving, setSaving] = useState(false);
34
+ const [testing, setTesting] = useState(false);
35
+ const [testResult, setTestResult] = useState<{
36
+ connected: boolean;
37
+ error?: string;
38
+ } | null>(null);
39
+
40
+ async function handleSave() {
41
+ if (!key.startsWith(keyPrefix)) return;
42
+ setSaving(true);
43
+ setTestResult(null);
44
+ try {
45
+ await onSave(key);
46
+ setKey("");
47
+ setShowInput(false);
48
+ } finally {
49
+ setSaving(false);
50
+ }
51
+ }
52
+
53
+ async function handleTest() {
54
+ setTesting(true);
55
+ setTestResult(null);
56
+ try {
57
+ const result = await onTest();
58
+ setTestResult(result);
59
+ } finally {
60
+ setTesting(false);
61
+ }
62
+ }
63
+
64
+ return (
65
+ <div className="space-y-4">
66
+ <Label className="text-sm font-medium">API Key</Label>
67
+
68
+ {hasKey && !showInput ? (
69
+ <div className="flex items-center gap-3">
70
+ <div className="flex-1 rounded-md border px-3 py-2 text-sm text-muted-foreground bg-muted/50">
71
+ Key configured ({maskedPrefix})
72
+ </div>
73
+ <Button variant="outline" size="sm" onClick={() => setShowInput(true)}>
74
+ Replace
75
+ </Button>
76
+ </div>
77
+ ) : (
78
+ <div className="flex items-center gap-2">
79
+ <div className="relative flex-1">
80
+ <Input
81
+ type={showKey ? "text" : "password"}
82
+ placeholder={placeholder}
83
+ value={key}
84
+ onChange={(e) => setKey(e.target.value)}
85
+ className="pr-10"
86
+ />
87
+ <button
88
+ type="button"
89
+ onClick={() => setShowKey(!showKey)}
90
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
91
+ >
92
+ {showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
93
+ </button>
94
+ </div>
95
+ <Button
96
+ size="sm"
97
+ disabled={!key.startsWith(keyPrefix) || saving}
98
+ onClick={handleSave}
99
+ >
100
+ {saving && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
101
+ Save
102
+ </Button>
103
+ {hasKey && (
104
+ <Button variant="ghost" size="sm" onClick={() => setShowInput(false)}>
105
+ Cancel
106
+ </Button>
107
+ )}
108
+ </div>
109
+ )}
110
+
111
+ <div className="flex items-center gap-3">
112
+ <Button variant="outline" size="sm" onClick={handleTest} disabled={testing}>
113
+ {testing && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
114
+ {testButtonLabel}
115
+ </Button>
116
+
117
+ {testResult && (
118
+ <span className="flex items-center gap-1.5 text-sm">
119
+ {testResult.connected ? (
120
+ <>
121
+ <CheckCircle2 className="h-4 w-4 text-success" />
122
+ <span className="text-success">Connected</span>
123
+ </>
124
+ ) : (
125
+ <>
126
+ <XCircle className="h-4 w-4 text-status-failed" />
127
+ <span className="text-status-failed">
128
+ {testResult.error || "Connection failed"}
129
+ </span>
130
+ </>
131
+ )}
132
+ </span>
133
+ )}
134
+ </div>
135
+
136
+ <p className="text-xs text-muted-foreground">
137
+ Environment fallback: `{envVarName}`.
138
+ </p>
139
+ </div>
140
+ );
141
+ }
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/components/ui/card";
11
+ import { Separator } from "@/components/ui/separator";
12
+ import { AuthMethodSelector } from "./auth-method-selector";
13
+ import { ApiKeyForm } from "./api-key-form";
14
+ import { AuthStatusBadge } from "./auth-status-badge";
15
+ import type { AuthMethod, ApiKeySource } from "@/lib/constants/settings";
16
+ import {
17
+ DEFAULT_AGENT_RUNTIME,
18
+ getRuntimeCatalogEntry,
19
+ } from "@/lib/agents/runtime/catalog";
20
+
21
+ interface AuthSettings {
22
+ method: AuthMethod;
23
+ hasKey: boolean;
24
+ apiKeySource: ApiKeySource;
25
+ }
26
+
27
+ export function AuthConfigSection() {
28
+ const runtime = getRuntimeCatalogEntry(DEFAULT_AGENT_RUNTIME);
29
+ const [settings, setSettings] = useState<AuthSettings>({
30
+ method: "oauth",
31
+ hasKey: false,
32
+ apiKeySource: "unknown",
33
+ });
34
+ const [connected, setConnected] = useState(false);
35
+
36
+ const fetchSettings = useCallback(async () => {
37
+ const res = await fetch("/api/settings");
38
+ if (res.ok) {
39
+ const data = await res.json();
40
+ setSettings(data);
41
+ setConnected(data.hasKey || data.apiKeySource === "oauth");
42
+ }
43
+ }, []);
44
+
45
+ useEffect(() => {
46
+ fetchSettings();
47
+ }, [fetchSettings]);
48
+
49
+ async function handleMethodChange(method: AuthMethod) {
50
+ const res = await fetch("/api/settings", {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({ method }),
54
+ });
55
+ if (res.ok) {
56
+ const data = await res.json();
57
+ setSettings(data);
58
+ }
59
+ }
60
+
61
+ async function handleSaveKey(apiKey: string) {
62
+ const res = await fetch("/api/settings", {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ method: "api_key", apiKey }),
66
+ });
67
+ if (res.ok) {
68
+ const data = await res.json();
69
+ setSettings(data);
70
+ setConnected(true);
71
+ }
72
+ }
73
+
74
+ async function handleTestConnection() {
75
+ const res = await fetch("/api/settings/test", { method: "POST" });
76
+ const data = await res.json();
77
+ setConnected(data.connected);
78
+ if (data.connected) {
79
+ const source = settings.method === "oauth" ? "oauth" : (data.apiKeySource || "unknown");
80
+ setSettings((prev) => ({ ...prev, apiKeySource: source as ApiKeySource }));
81
+ }
82
+ return data;
83
+ }
84
+
85
+ return (
86
+ <Card className="surface-card">
87
+ <CardHeader>
88
+ <div className="flex items-center justify-between">
89
+ <div>
90
+ <CardTitle>Authentication</CardTitle>
91
+ <CardDescription>
92
+ Configure how Stagent connects to the {runtime.label} runtime
93
+ </CardDescription>
94
+ </div>
95
+ <AuthStatusBadge connected={connected} apiKeySource={settings.apiKeySource} />
96
+ </div>
97
+ </CardHeader>
98
+ <CardContent className="space-y-6">
99
+ <AuthMethodSelector value={settings.method} onChange={handleMethodChange} />
100
+
101
+ {settings.method === "api_key" && (
102
+ <>
103
+ <Separator />
104
+ <ApiKeyForm
105
+ hasKey={settings.hasKey}
106
+ onSave={handleSaveKey}
107
+ onTest={handleTestConnection}
108
+ />
109
+ {settings.apiKeySource === "env" && (
110
+ <p className="text-xs text-muted-foreground">
111
+ Currently using API key from environment variable (ANTHROPIC_API_KEY).
112
+ You can optionally save a managed key above to override it.
113
+ </p>
114
+ )}
115
+ </>
116
+ )}
117
+
118
+ {settings.method === "oauth" && (
119
+ <>
120
+ <Separator />
121
+ <div className="space-y-2">
122
+ <p className="text-xs text-muted-foreground">
123
+ Supports resume, approvals, MCP passthrough, task assist, and profile smoke tests.
124
+ </p>
125
+ <p className="text-sm text-muted-foreground">
126
+ OAuth mode uses the Claude Agent SDK&apos;s built-in authentication flow.
127
+ Requires an active Claude Max or Pro subscription.
128
+ </p>
129
+ <button
130
+ onClick={handleTestConnection}
131
+ className="text-sm text-primary hover:underline cursor-pointer"
132
+ >
133
+ Test OAuth connection
134
+ </button>
135
+ </div>
136
+ </>
137
+ )}
138
+ </CardContent>
139
+ </Card>
140
+ );
141
+ }
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import { Key, Shield } from "lucide-react";
4
+ import { cn } from "@/lib/utils";
5
+ import type { AuthMethod } from "@/lib/constants/settings";
6
+
7
+ interface AuthMethodSelectorProps {
8
+ value: AuthMethod;
9
+ onChange: (method: AuthMethod) => void;
10
+ }
11
+
12
+ const methods = [
13
+ {
14
+ id: "api_key" as const,
15
+ icon: Key,
16
+ title: "API Key",
17
+ description: "Use an Anthropic API key for authentication",
18
+ },
19
+ {
20
+ id: "oauth" as const,
21
+ icon: Shield,
22
+ title: "OAuth",
23
+ description: "Claude Max or Pro subscription",
24
+ },
25
+ ];
26
+
27
+ export function AuthMethodSelector({ value, onChange }: AuthMethodSelectorProps) {
28
+ return (
29
+ <div className="space-y-2">
30
+ <p className="text-sm font-medium">Authentication Method</p>
31
+ <div className="grid grid-cols-2 gap-3">
32
+ {methods.map((method) => {
33
+ const Icon = method.icon;
34
+ const isSelected = value === method.id;
35
+ return (
36
+ <button
37
+ key={method.id}
38
+ type="button"
39
+ onClick={() => onChange(method.id)}
40
+ className={cn(
41
+ "flex flex-col items-center gap-2 rounded-lg border-2 p-4 text-center transition-all cursor-pointer",
42
+ "hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
43
+ isSelected
44
+ ? "border-primary bg-primary/5"
45
+ : "border-border/40 bg-card/30"
46
+ )}
47
+ >
48
+ <Icon className={cn(
49
+ "h-5 w-5",
50
+ isSelected ? "text-primary" : "text-muted-foreground"
51
+ )} />
52
+ <span className={cn(
53
+ "text-sm font-medium",
54
+ isSelected ? "text-foreground" : "text-muted-foreground"
55
+ )}>
56
+ {method.title}
57
+ </span>
58
+ <span className="text-xs text-muted-foreground">
59
+ {method.description}
60
+ </span>
61
+ </button>
62
+ );
63
+ })}
64
+ </div>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { Badge } from "@/components/ui/badge";
4
+ import type { ApiKeySource } from "@/lib/constants/settings";
5
+
6
+ interface AuthStatusBadgeProps {
7
+ connected: boolean;
8
+ apiKeySource: ApiKeySource;
9
+ }
10
+
11
+ const sourceLabels: Record<ApiKeySource, string> = {
12
+ db: "Managed API Key",
13
+ env: "Environment Variable",
14
+ oauth: "OAuth (Claude Max/Pro)",
15
+ unknown: "Unknown",
16
+ };
17
+
18
+ export function AuthStatusBadge({ connected, apiKeySource }: AuthStatusBadgeProps) {
19
+ if (!connected && apiKeySource === "unknown") {
20
+ return (
21
+ <Badge variant="outline" className="border-warning/50 text-warning">
22
+ Not configured
23
+ </Badge>
24
+ );
25
+ }
26
+
27
+ if (!connected) {
28
+ return (
29
+ <Badge variant="outline" className="border-status-failed/50 text-status-failed">
30
+ Disconnected
31
+ </Badge>
32
+ );
33
+ }
34
+
35
+ return (
36
+ <Badge variant="outline" className="border-success/50 text-success">
37
+ Connected via {sourceLabels[apiKeySource]}
38
+ </Badge>
39
+ );
40
+ }