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,66 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { writeFile, mkdir } from "fs/promises";
3
+ import { join } from "path";
4
+ import { db } from "@/lib/db";
5
+ import { documents } from "@/lib/db/schema";
6
+ import { processDocument } from "@/lib/documents/processor";
7
+ import { getStagentUploadsDir } from "@/lib/utils/stagent-paths";
8
+
9
+ const UPLOAD_DIR = getStagentUploadsDir();
10
+
11
+ export async function POST(req: NextRequest) {
12
+ const formData = await req.formData();
13
+ const file = formData.get("file") as File | null;
14
+ const taskId = formData.get("taskId") as string | null;
15
+
16
+ if (!file) {
17
+ return NextResponse.json({ error: "No file provided" }, { status: 400 });
18
+ }
19
+
20
+ if (file.size > 50 * 1024 * 1024) {
21
+ return NextResponse.json({ error: "File too large (max 50MB)" }, { status: 400 });
22
+ }
23
+
24
+ await mkdir(UPLOAD_DIR, { recursive: true });
25
+
26
+ const id = crypto.randomUUID();
27
+ const ext = file.name.split(".").pop() ?? "";
28
+ const filename = ext ? `${id}.${ext}` : id;
29
+ const filepath = join(UPLOAD_DIR, filename);
30
+
31
+ const bytes = new Uint8Array(await file.arrayBuffer());
32
+ await writeFile(filepath, bytes);
33
+
34
+ // Create document record in DB
35
+ await db.insert(documents).values({
36
+ id,
37
+ taskId: taskId ?? null,
38
+ projectId: null,
39
+ filename,
40
+ originalName: file.name,
41
+ mimeType: file.type || "application/octet-stream",
42
+ size: file.size,
43
+ storagePath: filepath,
44
+ direction: "input",
45
+ status: "uploaded",
46
+ createdAt: new Date(),
47
+ updatedAt: new Date(),
48
+ });
49
+
50
+ // Fire-and-forget: trigger async document processing
51
+ processDocument(id).catch((err) =>
52
+ console.error(`[upload] Processing failed for ${id}:`, err)
53
+ );
54
+
55
+ return NextResponse.json(
56
+ {
57
+ id,
58
+ filename,
59
+ originalName: file.name,
60
+ size: file.size,
61
+ type: file.type,
62
+ taskId,
63
+ },
64
+ { status: 201 }
65
+ );
66
+ }
@@ -0,0 +1,82 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import { workflows } from "@/lib/db/schema";
4
+ import { eq, and } from "drizzle-orm";
5
+ import { executeWorkflow } from "@/lib/workflows/engine";
6
+ import type { WorkflowDefinition } from "@/lib/workflows/types";
7
+
8
+ export async function POST(
9
+ _req: NextRequest,
10
+ { params }: { params: Promise<{ id: string }> }
11
+ ) {
12
+ const { id } = await params;
13
+
14
+ const [workflow] = await db
15
+ .select()
16
+ .from(workflows)
17
+ .where(eq(workflows.id, id));
18
+
19
+ if (!workflow) {
20
+ return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
21
+ }
22
+
23
+ if (workflow.status === "active") {
24
+ return NextResponse.json(
25
+ { error: "Workflow is already running" },
26
+ { status: 409 }
27
+ );
28
+ }
29
+
30
+ // Re-run: reset state for completed/failed workflows
31
+ if (workflow.status === "completed" || workflow.status === "failed") {
32
+ try {
33
+ const def = JSON.parse(workflow.definition) as WorkflowDefinition & {
34
+ _state?: unknown;
35
+ _loopState?: unknown;
36
+ };
37
+ delete def._state;
38
+ delete def._loopState;
39
+
40
+ await db
41
+ .update(workflows)
42
+ .set({
43
+ definition: JSON.stringify(def),
44
+ status: "draft",
45
+ updatedAt: new Date(),
46
+ })
47
+ .where(eq(workflows.id, id));
48
+ } catch {
49
+ return NextResponse.json(
50
+ { error: "Failed to reset workflow state" },
51
+ { status: 500 }
52
+ );
53
+ }
54
+ }
55
+
56
+ // Atomic claim: transition to "active" only if still in draft state.
57
+ // Prevents concurrent double-execution from parallel requests.
58
+ const claimResult = db
59
+ .update(workflows)
60
+ .set({ status: "active", updatedAt: new Date() })
61
+ .where(
62
+ and(
63
+ eq(workflows.id, id),
64
+ eq(workflows.status, "draft")
65
+ )
66
+ )
67
+ .run();
68
+
69
+ if (claimResult.changes === 0) {
70
+ return NextResponse.json(
71
+ { error: "Workflow is already running" },
72
+ { status: 409 }
73
+ );
74
+ }
75
+
76
+ // Fire-and-forget execution
77
+ executeWorkflow(id).catch((error) => {
78
+ console.error(`Workflow ${id} execution failed:`, error);
79
+ });
80
+
81
+ return NextResponse.json({ status: "started", workflowId: id }, { status: 202 });
82
+ }
@@ -0,0 +1,133 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import { workflows } from "@/lib/db/schema";
4
+ import { eq } from "drizzle-orm";
5
+ import type { WorkflowDefinition } from "@/lib/workflows/types";
6
+ import { validateWorkflowDefinitionAssignments } from "@/lib/agents/profiles/assignment-validation";
7
+ import { validateWorkflowDefinition } from "@/lib/workflows/definition-validation";
8
+
9
+ export async function PATCH(
10
+ req: NextRequest,
11
+ { params }: { params: Promise<{ id: string }> }
12
+ ) {
13
+ const { id } = await params;
14
+ const body = await req.json();
15
+ const { status, name, definition } = body as {
16
+ status?: string;
17
+ name?: string;
18
+ definition?: WorkflowDefinition;
19
+ };
20
+
21
+ const [workflow] = await db
22
+ .select()
23
+ .from(workflows)
24
+ .where(eq(workflows.id, id));
25
+
26
+ if (!workflow) {
27
+ return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
28
+ }
29
+
30
+ // Edit name/definition — draft only
31
+ if (name !== undefined || definition !== undefined) {
32
+ if (workflow.status !== "draft") {
33
+ return NextResponse.json(
34
+ { error: "Can only edit draft workflows" },
35
+ { status: 409 }
36
+ );
37
+ }
38
+
39
+ const updates: Record<string, unknown> = { updatedAt: new Date() };
40
+
41
+ if (name !== undefined) {
42
+ if (!name.trim()) {
43
+ return NextResponse.json({ error: "Name is required" }, { status: 400 });
44
+ }
45
+ updates.name = name.trim();
46
+ }
47
+
48
+ if (definition !== undefined) {
49
+ const definitionError = validateWorkflowDefinition(definition);
50
+ if (definitionError) {
51
+ return NextResponse.json(
52
+ { error: definitionError },
53
+ { status: 400 }
54
+ );
55
+ }
56
+
57
+ const compatibilityError =
58
+ validateWorkflowDefinitionAssignments(definition);
59
+ if (compatibilityError) {
60
+ return NextResponse.json(
61
+ { error: compatibilityError },
62
+ { status: 400 }
63
+ );
64
+ }
65
+ updates.definition = JSON.stringify(definition);
66
+ }
67
+
68
+ await db.update(workflows).set(updates).where(eq(workflows.id, id));
69
+
70
+ const [updated] = await db.select().from(workflows).where(eq(workflows.id, id));
71
+ return NextResponse.json(updated);
72
+ }
73
+
74
+ // Status transitions
75
+ if (!status) {
76
+ return NextResponse.json({ error: "status, name, or definition is required" }, { status: 400 });
77
+ }
78
+
79
+ if (status === "paused") {
80
+ if (workflow.status !== "active") {
81
+ return NextResponse.json(
82
+ { error: "Can only pause an active workflow" },
83
+ { status: 409 }
84
+ );
85
+ }
86
+
87
+ await db
88
+ .update(workflows)
89
+ .set({ status: "paused", updatedAt: new Date() })
90
+ .where(eq(workflows.id, id));
91
+
92
+ return NextResponse.json({ id, status: "paused" });
93
+ }
94
+
95
+ if (status === "active") {
96
+ return NextResponse.json(
97
+ { error: "Use POST /api/workflows/[id]/execute to resume a workflow" },
98
+ { status: 400 }
99
+ );
100
+ }
101
+
102
+ return NextResponse.json(
103
+ { error: `Invalid status transition: ${status}` },
104
+ { status: 400 }
105
+ );
106
+ }
107
+
108
+ export async function DELETE(
109
+ _req: NextRequest,
110
+ { params }: { params: Promise<{ id: string }> }
111
+ ) {
112
+ const { id } = await params;
113
+
114
+ const [workflow] = await db
115
+ .select()
116
+ .from(workflows)
117
+ .where(eq(workflows.id, id));
118
+
119
+ if (!workflow) {
120
+ return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
121
+ }
122
+
123
+ if (workflow.status === "active") {
124
+ return NextResponse.json(
125
+ { error: "Cannot delete an active workflow — pause or wait for completion" },
126
+ { status: 409 }
127
+ );
128
+ }
129
+
130
+ await db.delete(workflows).where(eq(workflows.id, id));
131
+
132
+ return NextResponse.json({ deleted: true });
133
+ }
@@ -0,0 +1,54 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import { workflows } from "@/lib/db/schema";
4
+ import { eq } from "drizzle-orm";
5
+ import { parseWorkflowState } from "@/lib/workflows/engine";
6
+
7
+ export async function GET(
8
+ _req: NextRequest,
9
+ { params }: { params: Promise<{ id: string }> }
10
+ ) {
11
+ const { id } = await params;
12
+
13
+ const [workflow] = await db
14
+ .select()
15
+ .from(workflows)
16
+ .where(eq(workflows.id, id));
17
+
18
+ if (!workflow) {
19
+ return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
20
+ }
21
+
22
+ const { definition, state, loopState } = parseWorkflowState(workflow.definition);
23
+
24
+ // Loop pattern returns loop-specific data instead of step states
25
+ if (definition.pattern === "loop") {
26
+ return NextResponse.json({
27
+ id: workflow.id,
28
+ name: workflow.name,
29
+ status: workflow.status,
30
+ projectId: workflow.projectId,
31
+ definition: workflow.definition,
32
+ pattern: definition.pattern,
33
+ loopConfig: definition.loopConfig,
34
+ swarmConfig: definition.swarmConfig,
35
+ loopState,
36
+ steps: definition.steps,
37
+ });
38
+ }
39
+
40
+ return NextResponse.json({
41
+ id: workflow.id,
42
+ name: workflow.name,
43
+ status: workflow.status,
44
+ projectId: workflow.projectId,
45
+ definition: workflow.definition,
46
+ pattern: definition.pattern,
47
+ swarmConfig: definition.swarmConfig,
48
+ steps: definition.steps.map((step, i) => ({
49
+ ...step,
50
+ state: state?.stepStates[i] ?? { stepId: step.id, status: "pending" },
51
+ })),
52
+ workflowState: state,
53
+ });
54
+ }
@@ -0,0 +1,22 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { retryWorkflowStep } from "@/lib/workflows/engine";
3
+
4
+ export async function POST(
5
+ _req: NextRequest,
6
+ {
7
+ params,
8
+ }: {
9
+ params: Promise<{ id: string; stepId: string }>;
10
+ }
11
+ ) {
12
+ const { id, stepId } = await params;
13
+
14
+ retryWorkflowStep(id, stepId).catch((error) => {
15
+ console.error(`Workflow ${id} step ${stepId} retry failed:`, error);
16
+ });
17
+
18
+ return NextResponse.json(
19
+ { status: "retry_started", workflowId: id, stepId },
20
+ { status: 202 }
21
+ );
22
+ }
@@ -0,0 +1,61 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import { workflows } from "@/lib/db/schema";
4
+ import { desc, eq } from "drizzle-orm";
5
+ import type { WorkflowDefinition } from "@/lib/workflows/types";
6
+ import { validateWorkflowDefinitionAssignments } from "@/lib/agents/profiles/assignment-validation";
7
+ import { validateWorkflowDefinition } from "@/lib/workflows/definition-validation";
8
+
9
+ export async function GET() {
10
+ const result = await db
11
+ .select()
12
+ .from(workflows)
13
+ .orderBy(desc(workflows.createdAt));
14
+
15
+ return NextResponse.json(result);
16
+ }
17
+
18
+ export async function POST(req: NextRequest) {
19
+ const body = await req.json();
20
+ const { name, projectId, definition: rawDefinition } = body as {
21
+ name?: string;
22
+ projectId?: string;
23
+ definition?: WorkflowDefinition;
24
+ };
25
+
26
+ if (!name?.trim()) {
27
+ return NextResponse.json({ error: "Name is required" }, { status: 400 });
28
+ }
29
+ const definitionError = rawDefinition
30
+ ? validateWorkflowDefinition(rawDefinition)
31
+ : "Definition must include pattern and at least one step";
32
+ if (definitionError) {
33
+ return NextResponse.json(
34
+ { error: definitionError },
35
+ { status: 400 }
36
+ );
37
+ }
38
+
39
+ const definition = rawDefinition as WorkflowDefinition;
40
+ const compatibilityError = validateWorkflowDefinitionAssignments(definition);
41
+ if (compatibilityError) {
42
+ return NextResponse.json({ error: compatibilityError }, { status: 400 });
43
+ }
44
+
45
+ const id = crypto.randomUUID();
46
+ const now = new Date();
47
+
48
+ await db.insert(workflows).values({
49
+ id,
50
+ name: name.trim(),
51
+ projectId: projectId || null,
52
+ definition: JSON.stringify(definition),
53
+ status: "draft",
54
+ createdAt: now,
55
+ updatedAt: now,
56
+ });
57
+
58
+ const [created] = await db.select().from(workflows).where(eq(workflows.id, id));
59
+
60
+ return NextResponse.json(created, { status: 201 });
61
+ }
@@ -0,0 +1,31 @@
1
+ import { ImageResponse } from "next/og";
2
+ import { readFileSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ export const size = { width: 180, height: 180 };
6
+ export const contentType = "image/png";
7
+
8
+ export default function AppleIcon() {
9
+ const logoData = readFileSync(join(process.cwd(), "public/stagent-s-128.png"));
10
+ const logoSrc = `data:image/png;base64,${logoData.toString("base64")}`;
11
+
12
+ return new ImageResponse(
13
+ (
14
+ <div
15
+ style={{
16
+ width: "100%",
17
+ height: "100%",
18
+ display: "flex",
19
+ alignItems: "center",
20
+ justifyContent: "center",
21
+ background: "#0f172a",
22
+ borderRadius: "36px",
23
+ }}
24
+ >
25
+ {/* eslint-disable-next-line @next/next/no-img-element */}
26
+ <img src={logoSrc} width="130" height="130" alt="" />
27
+ </div>
28
+ ),
29
+ { ...size }
30
+ );
31
+ }
@@ -0,0 +1,256 @@
1
+ import { listRuntimeCatalog } from "@/lib/agents/runtime/catalog";
2
+ import { CostDashboard } from "@/components/costs/cost-dashboard";
3
+ import { getBudgetGuardrailSnapshot } from "@/lib/settings/budget-guardrails";
4
+ import {
5
+ getDailySpendTotals,
6
+ getDailyTokenTotals,
7
+ getProviderModelBreakdown,
8
+ listUsageAuditEntries,
9
+ type ProviderModelBreakdownEntry,
10
+ type UsageActivityType,
11
+ type UsageLedgerStatus,
12
+ } from "@/lib/usage/ledger";
13
+
14
+ export const dynamic = "force-dynamic";
15
+
16
+ const runtimeCatalog = listRuntimeCatalog();
17
+ const validRuntimeIds = new Set<string>(runtimeCatalog.map((runtime) => runtime.id));
18
+ const validDateRanges = new Set(["7d", "30d", "90d", "all"]);
19
+ const validStatuses = new Set<UsageLedgerStatus>([
20
+ "completed",
21
+ "failed",
22
+ "cancelled",
23
+ "blocked",
24
+ "unknown_pricing",
25
+ ]);
26
+ const validActivityTypes = new Set<UsageActivityType>([
27
+ "task_run",
28
+ "task_resume",
29
+ "workflow_step",
30
+ "scheduled_firing",
31
+ "task_assist",
32
+ "profile_test",
33
+ ]);
34
+
35
+ function toScalar(value: string | string[] | undefined) {
36
+ return Array.isArray(value) ? value[0] : value;
37
+ }
38
+
39
+ function resolveDateRange(value: string | undefined) {
40
+ return value && validDateRanges.has(value) ? value : "30d";
41
+ }
42
+
43
+ function resolveRuntime(value: string | undefined) {
44
+ return value && validRuntimeIds.has(value) ? value : "all";
45
+ }
46
+
47
+ function resolveStatus(value: string | undefined) {
48
+ return value && validStatuses.has(value as UsageLedgerStatus) ? value : "all";
49
+ }
50
+
51
+ function resolveActivityType(value: string | undefined) {
52
+ return value && validActivityTypes.has(value as UsageActivityType) ? value : "all";
53
+ }
54
+
55
+ function getRangeStart(range: string) {
56
+ if (range === "all") {
57
+ return undefined;
58
+ }
59
+
60
+ const now = new Date();
61
+ const days =
62
+ range === "7d"
63
+ ? 7
64
+ : range === "90d"
65
+ ? 90
66
+ : 30;
67
+ const start = new Date(now);
68
+ start.setHours(0, 0, 0, 0);
69
+ start.setDate(start.getDate() - (days - 1));
70
+ return start;
71
+ }
72
+
73
+ function startOfCurrentMonth() {
74
+ const now = new Date();
75
+ return new Date(now.getFullYear(), now.getMonth(), 1);
76
+ }
77
+
78
+ function buildDateKeys(days: number) {
79
+ const dates: string[] = [];
80
+ for (let i = days - 1; i >= 0; i--) {
81
+ const date = new Date();
82
+ date.setHours(0, 0, 0, 0);
83
+ date.setDate(date.getDate() - i);
84
+ dates.push(
85
+ new Intl.DateTimeFormat("en-CA", {
86
+ year: "numeric",
87
+ month: "2-digit",
88
+ day: "2-digit",
89
+ }).format(date)
90
+ );
91
+ }
92
+ return dates;
93
+ }
94
+
95
+ function fillSeries<T extends { day: string }>(
96
+ days: number,
97
+ rows: T[],
98
+ getValue: (row: T) => number
99
+ ) {
100
+ const keys = buildDateKeys(days);
101
+ const values = new Map(rows.map((row) => [row.day, getValue(row)]));
102
+ return keys.map((key) => values.get(key) ?? 0);
103
+ }
104
+
105
+ function findOverallSpend(
106
+ statuses: Array<{
107
+ scopeId: string;
108
+ window: string;
109
+ metric: string;
110
+ currentValue: number;
111
+ }>,
112
+ window: "daily" | "monthly"
113
+ ) {
114
+ return (
115
+ statuses.find(
116
+ (status) =>
117
+ status.scopeId === "overall" &&
118
+ status.window === window &&
119
+ status.metric === "spend"
120
+ )?.currentValue ?? 0
121
+ );
122
+ }
123
+
124
+ function buildRuntimeBreakdown(
125
+ rows: ProviderModelBreakdownEntry[]
126
+ ): Array<{
127
+ runtimeId: string;
128
+ label: string;
129
+ providerId: string;
130
+ costMicros: number;
131
+ totalTokens: number;
132
+ runs: number;
133
+ share: number;
134
+ unknownPricingRuns: number;
135
+ }> {
136
+ const totals = new Map<
137
+ string,
138
+ {
139
+ runtimeId: string;
140
+ label: string;
141
+ providerId: string;
142
+ costMicros: number;
143
+ totalTokens: number;
144
+ runs: number;
145
+ unknownPricingRuns: number;
146
+ }
147
+ >();
148
+
149
+ for (const row of rows) {
150
+ const current = totals.get(row.runtimeId) ?? {
151
+ runtimeId: row.runtimeId,
152
+ label:
153
+ runtimeCatalog.find((runtime) => runtime.id === row.runtimeId)?.label ??
154
+ row.runtimeId,
155
+ providerId: row.providerId,
156
+ costMicros: 0,
157
+ totalTokens: 0,
158
+ runs: 0,
159
+ unknownPricingRuns: 0,
160
+ };
161
+
162
+ current.costMicros += row.costMicros;
163
+ current.totalTokens += row.totalTokens;
164
+ current.runs += row.runs;
165
+ current.unknownPricingRuns += row.unknownPricingRuns;
166
+ totals.set(row.runtimeId, current);
167
+ }
168
+
169
+ const totalCost = Array.from(totals.values()).reduce(
170
+ (sum, row) => sum + row.costMicros,
171
+ 0
172
+ );
173
+
174
+ return Array.from(totals.values())
175
+ .map((row) => ({
176
+ ...row,
177
+ share: totalCost > 0 ? (row.costMicros / totalCost) * 100 : 0,
178
+ }))
179
+ .sort((left, right) => right.costMicros - left.costMicros);
180
+ }
181
+
182
+ export default async function CostsPage({
183
+ searchParams,
184
+ }: {
185
+ searchParams: Promise<{
186
+ range?: string | string[];
187
+ runtime?: string | string[];
188
+ status?: string | string[];
189
+ activity?: string | string[];
190
+ }>;
191
+ }) {
192
+ const params = await searchParams;
193
+ const dateRange = resolveDateRange(toScalar(params.range));
194
+ const runtimeId = resolveRuntime(toScalar(params.runtime));
195
+ const status = resolveStatus(toScalar(params.status));
196
+ const activityType = resolveActivityType(toScalar(params.activity));
197
+
198
+ const rangeStart = getRangeStart(dateRange);
199
+ const [spendRows30, tokenRows30, monthBreakdown, filteredBreakdown, auditEntries, budgetSnapshot] =
200
+ await Promise.all([
201
+ getDailySpendTotals(30),
202
+ getDailyTokenTotals(30),
203
+ getProviderModelBreakdown({ startedAt: startOfCurrentMonth() }),
204
+ getProviderModelBreakdown(
205
+ rangeStart ? { startedAt: rangeStart } : undefined
206
+ ),
207
+ listUsageAuditEntries({
208
+ limit: 100,
209
+ runtimeIds: runtimeId === "all" ? undefined : [runtimeId],
210
+ statuses: status === "all" ? undefined : [status as UsageLedgerStatus],
211
+ activityTypes:
212
+ activityType === "all"
213
+ ? undefined
214
+ : [activityType as UsageActivityType],
215
+ startedAt: rangeStart,
216
+ }),
217
+ getBudgetGuardrailSnapshot(),
218
+ ]);
219
+
220
+ const spendSeries30 = fillSeries(30, spendRows30, (row) => row.costMicros);
221
+ const tokenSeries30 = fillSeries(30, tokenRows30, (row) => row.totalTokens);
222
+ const runtimeBreakdown = buildRuntimeBreakdown(filteredBreakdown);
223
+ const monthTokens = monthBreakdown.reduce(
224
+ (sum, row) => sum + row.totalTokens,
225
+ 0
226
+ );
227
+
228
+ return (
229
+ <div className="gradient-neutral min-h-screen p-6">
230
+ <CostDashboard
231
+ filters={{
232
+ dateRange,
233
+ runtimeId,
234
+ status,
235
+ activityType,
236
+ }}
237
+ summary={{
238
+ todaySpendMicros: findOverallSpend(budgetSnapshot.statuses, "daily"),
239
+ monthSpendMicros: findOverallSpend(budgetSnapshot.statuses, "monthly"),
240
+ todayTokens: tokenSeries30[tokenSeries30.length - 1] ?? 0,
241
+ monthTokens,
242
+ }}
243
+ trendSeries={{
244
+ spend7: spendSeries30.slice(-7),
245
+ spend30: spendSeries30,
246
+ tokens7: tokenSeries30.slice(-7),
247
+ tokens30: tokenSeries30,
248
+ }}
249
+ budgetStatuses={budgetSnapshot.statuses}
250
+ runtimeBreakdown={runtimeBreakdown}
251
+ modelBreakdown={filteredBreakdown}
252
+ auditEntries={auditEntries}
253
+ />
254
+ </div>
255
+ );
256
+ }