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,144 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState, useCallback } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Card } from "@/components/ui/card";
6
+ import { ArrowDown } from "lucide-react";
7
+ import { LogEntry } from "./log-entry";
8
+ import { LogFilters } from "./log-filters";
9
+ import { ConnectionIndicator } from "./connection-indicator";
10
+
11
+ interface LogEntryData {
12
+ id: string;
13
+ taskId: string | null;
14
+ agentType: string;
15
+ event: string;
16
+ payload: string | null;
17
+ timestamp: string;
18
+ }
19
+
20
+ interface LogStreamProps {
21
+ tasks: { id: string; title: string }[];
22
+ }
23
+
24
+ export function LogStream({ tasks }: LogStreamProps) {
25
+ const [entries, setEntries] = useState<LogEntryData[]>([]);
26
+ const [connected, setConnected] = useState(false);
27
+ const [isAutoScrolling, setIsAutoScrolling] = useState(true);
28
+ const [taskFilter, setTaskFilter] = useState("all");
29
+ const [eventFilter, setEventFilter] = useState("all");
30
+ const containerRef = useRef<HTMLDivElement>(null);
31
+ const eventSourceRef = useRef<EventSource | null>(null);
32
+
33
+ const connect = useCallback(() => {
34
+ if (eventSourceRef.current) {
35
+ eventSourceRef.current.close();
36
+ }
37
+
38
+ const params = new URLSearchParams();
39
+ if (taskFilter !== "all") params.set("taskId", taskFilter);
40
+ if (eventFilter !== "all") params.set("eventType", eventFilter);
41
+
42
+ const es = new EventSource(`/api/logs/stream?${params}`);
43
+ eventSourceRef.current = es;
44
+
45
+ es.onopen = () => setConnected(true);
46
+
47
+ es.onmessage = (event) => {
48
+ try {
49
+ const data = JSON.parse(event.data);
50
+ setEntries((prev) => {
51
+ const next = [...prev, data];
52
+ return next.length > 500 ? next.slice(-500) : next;
53
+ });
54
+ } catch {
55
+ // Invalid JSON
56
+ }
57
+ };
58
+
59
+ es.onerror = () => {
60
+ setConnected(false);
61
+ es.close();
62
+ // Reconnect after 3s
63
+ setTimeout(connect, 3000);
64
+ };
65
+ }, [taskFilter, eventFilter]);
66
+
67
+ useEffect(() => {
68
+ connect();
69
+ return () => {
70
+ eventSourceRef.current?.close();
71
+ };
72
+ }, [connect]);
73
+
74
+ // Auto-scroll
75
+ useEffect(() => {
76
+ if (isAutoScrolling && containerRef.current) {
77
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
78
+ }
79
+ }, [entries, isAutoScrolling]);
80
+
81
+ function handleScroll() {
82
+ if (!containerRef.current) return;
83
+ const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
84
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
85
+ setIsAutoScrolling(isAtBottom);
86
+ }
87
+
88
+ function jumpToLatest() {
89
+ if (containerRef.current) {
90
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
91
+ setIsAutoScrolling(true);
92
+ }
93
+ }
94
+
95
+ return (
96
+ <Card className="relative flex flex-col">
97
+ <div className="flex items-center justify-between p-4 border-b border-border/60">
98
+ <h3 className="text-sm font-medium">Log Stream</h3>
99
+ <div className="flex items-center gap-4">
100
+ <LogFilters
101
+ taskId={taskFilter}
102
+ eventType={eventFilter}
103
+ tasks={tasks}
104
+ onTaskChange={(v) => {
105
+ setTaskFilter(v);
106
+ setEntries([]);
107
+ }}
108
+ onEventTypeChange={(v) => {
109
+ setEventFilter(v);
110
+ setEntries([]);
111
+ }}
112
+ />
113
+ <ConnectionIndicator connected={connected} />
114
+ </div>
115
+ </div>
116
+ <div
117
+ ref={containerRef}
118
+ onScroll={handleScroll}
119
+ className="flex-1 min-h-[300px] max-h-[calc(100vh-20rem)] overflow-auto p-2"
120
+ >
121
+ {entries.length === 0 ? (
122
+ <div className="flex items-center justify-center h-full min-h-[200px] text-sm text-muted-foreground">
123
+ Waiting for agent activity...
124
+ </div>
125
+ ) : (
126
+ entries.map((entry) => {
127
+ const taskName = entry.taskId
128
+ ? tasks.find((t) => t.id === entry.taskId)?.title
129
+ : undefined;
130
+ return <LogEntry key={entry.id} entry={entry} taskName={taskName} />;
131
+ })
132
+ )}
133
+ </div>
134
+ {!isAutoScrolling && (
135
+ <div className="absolute bottom-4 left-1/2 -translate-x-1/2">
136
+ <Button size="sm" variant="secondary" onClick={jumpToLatest}>
137
+ <ArrowDown className="h-3.5 w-3.5 mr-1" />
138
+ Jump to latest
139
+ </Button>
140
+ </div>
141
+ )}
142
+ </Card>
143
+ );
144
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef, useState, useTransition } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Button } from "@/components/ui/button";
6
+ import { RefreshCw } from "lucide-react";
7
+
8
+ const AUTO_REFRESH_INTERVAL = 15_000; // 15 seconds
9
+
10
+ export function MonitorRefreshButton() {
11
+ const router = useRouter();
12
+ const [isPending, startTransition] = useTransition();
13
+ const [autoRefresh, setAutoRefresh] = useState(true);
14
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
15
+
16
+ const handleRefresh = useCallback(() => {
17
+ startTransition(() => {
18
+ router.refresh();
19
+ });
20
+ }, [router, startTransition]);
21
+
22
+ // Pause auto-refresh when tab is not visible
23
+ const [visible, setVisible] = useState(true);
24
+ useEffect(() => {
25
+ function onVisibilityChange() {
26
+ setVisible(document.visibilityState === "visible");
27
+ }
28
+ document.addEventListener("visibilitychange", onVisibilityChange);
29
+ return () => document.removeEventListener("visibilitychange", onVisibilityChange);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ if (autoRefresh && visible) {
34
+ intervalRef.current = setInterval(handleRefresh, AUTO_REFRESH_INTERVAL);
35
+ return () => {
36
+ if (intervalRef.current) clearInterval(intervalRef.current);
37
+ };
38
+ } else {
39
+ if (intervalRef.current) clearInterval(intervalRef.current);
40
+ }
41
+ }, [autoRefresh, visible, handleRefresh]);
42
+
43
+ return (
44
+ <div className="flex items-center gap-2">
45
+ <Button
46
+ variant={autoRefresh ? "secondary" : "ghost"}
47
+ size="sm"
48
+ onClick={() => setAutoRefresh((prev) => !prev)}
49
+ aria-label={autoRefresh ? "Disable auto-refresh" : "Enable auto-refresh"}
50
+ >
51
+ {autoRefresh ? "Auto" : "Manual"}
52
+ </Button>
53
+ <Button
54
+ variant="ghost"
55
+ size="sm"
56
+ onClick={handleRefresh}
57
+ disabled={isPending}
58
+ aria-label="Refresh metrics"
59
+ >
60
+ <RefreshCw className={`h-4 w-4 ${isPending || autoRefresh ? "animate-spin" : ""}`} />
61
+ </Button>
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,119 @@
1
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2
+ import { db } from "@/lib/db";
3
+ import { tasks, agentLogs } from "@/lib/db/schema";
4
+ import { eq, count, sql, gte, and } from "drizzle-orm";
5
+ import { Activity, CheckCircle, Zap, Clock } from "lucide-react";
6
+ import { DonutRing } from "@/components/charts/donut-ring";
7
+ import { Sparkline } from "@/components/charts/sparkline";
8
+ import { getAgentActivityByHour } from "@/lib/queries/chart-data";
9
+
10
+ export async function MonitorOverview() {
11
+ const today = new Date();
12
+ today.setHours(0, 0, 0, 0);
13
+
14
+ const [activeResult] = await db
15
+ .select({ count: count() })
16
+ .from(tasks)
17
+ .where(eq(tasks.status, "running"));
18
+
19
+ const [todayResult] = await db
20
+ .select({ count: count() })
21
+ .from(tasks)
22
+ .where(gte(tasks.createdAt, today));
23
+
24
+ const [completedResult] = await db
25
+ .select({ count: count() })
26
+ .from(tasks)
27
+ .where(eq(tasks.status, "completed"));
28
+
29
+ const [totalResult] = await db
30
+ .select({ count: count() })
31
+ .from(tasks)
32
+ .where(
33
+ sql`${tasks.status} IN ('completed', 'failed')`
34
+ );
35
+
36
+ const successRate =
37
+ totalResult.count > 0
38
+ ? Math.round((completedResult.count / totalResult.count) * 100)
39
+ : 0;
40
+
41
+ const [lastLog] = await db
42
+ .select()
43
+ .from(agentLogs)
44
+ .orderBy(sql`${agentLogs.timestamp} DESC`)
45
+ .limit(1);
46
+
47
+ const hourlyActivity = await getAgentActivityByHour();
48
+
49
+ return (
50
+ <div
51
+ className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"
52
+ aria-live="polite"
53
+ aria-label="Monitor overview metrics"
54
+ >
55
+ {/* Active Agents */}
56
+ <Card className="surface-card">
57
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
58
+ <CardTitle className="text-sm font-medium text-muted-foreground">Active Agents</CardTitle>
59
+ <Activity className="h-4 w-4 text-status-running" />
60
+ </CardHeader>
61
+ <CardContent>
62
+ <div className="text-2xl font-bold">{activeResult.count}</div>
63
+ <Sparkline
64
+ data={hourlyActivity}
65
+ width={100}
66
+ height={20}
67
+ color="var(--chart-1)"
68
+ label="Agent activity over 24 hours"
69
+ className="w-full mt-1"
70
+ />
71
+ </CardContent>
72
+ </Card>
73
+
74
+ {/* Tasks Today */}
75
+ <Card className="surface-card">
76
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
77
+ <CardTitle className="text-sm font-medium text-muted-foreground">Tasks Today</CardTitle>
78
+ <Zap className="h-4 w-4 text-status-warning" />
79
+ </CardHeader>
80
+ <CardContent>
81
+ <div className="text-2xl font-bold">{todayResult.count}</div>
82
+ </CardContent>
83
+ </Card>
84
+
85
+ {/* Success Rate */}
86
+ <Card className="surface-card">
87
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
88
+ <CardTitle className="text-sm font-medium text-muted-foreground">Success Rate</CardTitle>
89
+ <CheckCircle className="h-4 w-4 text-status-completed" />
90
+ </CardHeader>
91
+ <CardContent>
92
+ <div className="flex items-center gap-3">
93
+ <DonutRing
94
+ value={successRate}
95
+ size={36}
96
+ strokeWidth={4}
97
+ color="var(--chart-2)"
98
+ label={`Success rate: ${successRate}%`}
99
+ />
100
+ <div className="text-2xl font-bold">{successRate}%</div>
101
+ </div>
102
+ </CardContent>
103
+ </Card>
104
+
105
+ {/* Last Activity */}
106
+ <Card className="surface-card">
107
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
108
+ <CardTitle className="text-sm font-medium text-muted-foreground">Last Activity</CardTitle>
109
+ <Clock className="h-4 w-4 text-muted-foreground" />
110
+ </CardHeader>
111
+ <CardContent>
112
+ <div className="text-2xl font-bold">
113
+ {lastLog ? new Date(lastLog.timestamp).toLocaleTimeString() : "None"}
114
+ </div>
115
+ </CardContent>
116
+ </Card>
117
+ </div>
118
+ );
119
+ }
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { RotateCcw } from "lucide-react";
6
+
7
+ interface FailureActionProps {
8
+ taskId: string;
9
+ onRetried: () => void;
10
+ }
11
+
12
+ export function FailureAction({ taskId, onRetried }: FailureActionProps) {
13
+ const [loading, setLoading] = useState(false);
14
+
15
+ async function handleRetry() {
16
+ setLoading(true);
17
+ try {
18
+ // Move back to queued
19
+ await fetch(`/api/tasks/${taskId}`, {
20
+ method: "PATCH",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify({ status: "queued" }),
23
+ });
24
+ // Then execute
25
+ await fetch(`/api/tasks/${taskId}/execute`, { method: "POST" });
26
+ onRetried();
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ }
31
+
32
+ return (
33
+ <Button size="sm" variant="outline" onClick={handleRetry} disabled={loading} className="mt-2">
34
+ <RotateCcw className="h-3.5 w-3.5 mr-1" />
35
+ Retry
36
+ </Button>
37
+ );
38
+ }
@@ -0,0 +1,165 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
5
+ import { Button } from "@/components/ui/button";
6
+ import { CheckCheck, Inbox, RefreshCw, Trash2 } from "lucide-react";
7
+ import { toast } from "sonner";
8
+ import { NotificationItem } from "./notification-item";
9
+ import { EmptyState } from "@/components/shared/empty-state";
10
+
11
+ interface Notification {
12
+ id: string;
13
+ taskId: string | null;
14
+ type: string;
15
+ title: string;
16
+ body: string | null;
17
+ read: boolean;
18
+ toolName: string | null;
19
+ toolInput: string | null;
20
+ response: string | null;
21
+ respondedAt: string | null;
22
+ createdAt: string;
23
+ }
24
+
25
+ export function InboxList({
26
+ initialNotifications,
27
+ }: {
28
+ initialNotifications: Notification[];
29
+ }) {
30
+ const [notifications, setNotifications] =
31
+ useState<Notification[]>(initialNotifications);
32
+ const [tab, setTab] = useState("all");
33
+
34
+ const refresh = useCallback(async () => {
35
+ const res = await fetch("/api/notifications");
36
+ if (res.ok) setNotifications(await res.json());
37
+ }, []);
38
+
39
+ // Poll every 10 seconds (consolidated from 3s inbox + 5s badge)
40
+ useEffect(() => {
41
+ const interval = setInterval(refresh, 10_000);
42
+ return () => clearInterval(interval);
43
+ }, [refresh]);
44
+
45
+ async function markAllRead() {
46
+ await fetch("/api/notifications/mark-all-read", { method: "PATCH" });
47
+ toast.success("All notifications marked as read");
48
+ refresh();
49
+ }
50
+
51
+ async function dismissAllRead() {
52
+ const readNotifications = notifications.filter((n) => n.read);
53
+ if (readNotifications.length === 0) return;
54
+ await Promise.all(
55
+ readNotifications.map((n) =>
56
+ fetch(`/api/notifications/${n.id}`, { method: "DELETE" })
57
+ )
58
+ );
59
+ toast.success(`Dismissed ${readNotifications.length} read notification${readNotifications.length !== 1 ? "s" : ""}`);
60
+ refresh();
61
+ }
62
+
63
+ const filtered =
64
+ tab === "all"
65
+ ? notifications
66
+ : tab === "unread"
67
+ ? notifications.filter((n) => !n.read)
68
+ : tab === "permissions"
69
+ ? notifications.filter((n) => n.type === "permission_required")
70
+ : notifications.filter(
71
+ (n) => n.type === "agent_message" || n.type === "budget_alert"
72
+ );
73
+
74
+ const unreadCount = notifications.filter((n) => !n.read).length;
75
+ const filteredCount = filtered.length;
76
+ const readCount = notifications.length - unreadCount;
77
+
78
+ return (
79
+ <div className="space-y-4">
80
+ <div className="surface-toolbar rounded-[24px] p-3 sm:p-4">
81
+ <div className="flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
82
+ <span>Notification Queue</span>
83
+ <span className="rounded-full bg-background/75 px-2.5 py-1 text-[11px] font-medium normal-case tracking-normal text-foreground">
84
+ {filteredCount} shown
85
+ </span>
86
+ {unreadCount > 0 && (
87
+ <span className="rounded-full bg-primary/10 px-2.5 py-1 text-[11px] font-medium normal-case tracking-normal text-primary">
88
+ {unreadCount} unread
89
+ </span>
90
+ )}
91
+ {readCount > 0 && (
92
+ <span className="rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium normal-case tracking-normal text-muted-foreground">
93
+ {readCount} read
94
+ </span>
95
+ )}
96
+ </div>
97
+
98
+ <div className="mt-3 flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
99
+ <Tabs value={tab} onValueChange={setTab}>
100
+ <TabsList className="h-auto flex-wrap justify-start gap-1 rounded-xl bg-background/75 p-1 shadow-none">
101
+ <TabsTrigger className="min-h-8 flex-none px-3 text-[13px]" value="all">
102
+ All
103
+ </TabsTrigger>
104
+ <TabsTrigger className="min-h-8 flex-none px-3 text-[13px]" value="unread">
105
+ Unread{unreadCount > 0 ? ` (${unreadCount})` : ""}
106
+ </TabsTrigger>
107
+ <TabsTrigger className="min-h-8 flex-none px-3 text-[13px]" value="permissions">
108
+ Permissions
109
+ </TabsTrigger>
110
+ <TabsTrigger className="min-h-8 flex-none px-3 text-[13px]" value="messages">
111
+ Messages
112
+ </TabsTrigger>
113
+ </TabsList>
114
+ </Tabs>
115
+
116
+ <div className="flex flex-wrap items-center gap-2">
117
+ <Button
118
+ variant="outline"
119
+ size="icon-sm"
120
+ onClick={refresh}
121
+ aria-label="Refresh notifications"
122
+ >
123
+ <RefreshCw className="h-4 w-4" />
124
+ </Button>
125
+ {unreadCount > 0 && (
126
+ <Button variant="secondary" size="sm" onClick={markAllRead}>
127
+ <CheckCheck className="h-4 w-4" />
128
+ Mark all read
129
+ </Button>
130
+ )}
131
+ {readCount > 0 && (
132
+ <Button
133
+ variant="outline"
134
+ size="sm"
135
+ onClick={dismissAllRead}
136
+ className="text-muted-foreground hover:text-destructive"
137
+ >
138
+ <Trash2 className="h-4 w-4" />
139
+ Dismiss read
140
+ </Button>
141
+ )}
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ <div className="space-y-3" aria-live="polite">
147
+ {filtered.length === 0 ? (
148
+ <EmptyState
149
+ icon={Inbox}
150
+ heading="No notifications"
151
+ description={tab === "all" ? "You're all caught up." : `No ${tab} notifications to show.`}
152
+ />
153
+ ) : (
154
+ filtered.map((n) => (
155
+ <NotificationItem
156
+ key={n.id}
157
+ notification={n}
158
+ onUpdated={refresh}
159
+ />
160
+ ))
161
+ )}
162
+ </div>
163
+ </div>
164
+ );
165
+ }