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,564 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Skeleton } from "@/components/ui/skeleton";
9
+ import {
10
+ Copy,
11
+ Pencil,
12
+ Trash2,
13
+ Play,
14
+ Loader2,
15
+ CheckCircle2,
16
+ XCircle,
17
+ Bot,
18
+ Sparkles,
19
+ Tag,
20
+ User,
21
+ Thermometer,
22
+ Repeat,
23
+ FileOutput,
24
+ Wrench,
25
+ ShieldCheck,
26
+ ShieldX,
27
+ FileCode,
28
+ Cpu,
29
+ } from "lucide-react";
30
+ import { toast } from "sonner";
31
+ import { ConfirmDialog } from "@/components/shared/confirm-dialog";
32
+ import {
33
+ type AgentRuntimeId,
34
+ DEFAULT_AGENT_RUNTIME,
35
+ listRuntimeCatalog,
36
+ } from "@/lib/agents/runtime/catalog";
37
+ import {
38
+ getProfileRuntimeCompatibility,
39
+ getSupportedRuntimes,
40
+ } from "@/lib/agents/profiles/compatibility";
41
+ import type { AgentProfile } from "@/lib/agents/profiles/types";
42
+
43
+ interface TestResult {
44
+ task: string;
45
+ expectedKeywords: string[];
46
+ foundKeywords: string[];
47
+ missingKeywords: string[];
48
+ passed: boolean;
49
+ }
50
+
51
+ interface TestReport {
52
+ profileId: string;
53
+ profileName: string;
54
+ runtimeId: string;
55
+ results: TestResult[];
56
+ totalPassed: number;
57
+ totalFailed: number;
58
+ unsupported?: boolean;
59
+ unsupportedReason?: string;
60
+ }
61
+
62
+ interface ProfileWithBuiltin extends AgentProfile {
63
+ isBuiltin?: boolean;
64
+ }
65
+
66
+ interface ProfileDetailViewProps {
67
+ profileId: string;
68
+ isBuiltin: boolean;
69
+ initialProfile?: AgentProfile;
70
+ }
71
+
72
+ export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: ProfileDetailViewProps) {
73
+ const runtimeOptions = listRuntimeCatalog();
74
+ const runtimeLabelMap = new Map(
75
+ runtimeOptions.map((runtime) => [runtime.id, runtime.label])
76
+ );
77
+ const router = useRouter();
78
+ const [profile, setProfile] = useState<ProfileWithBuiltin | null>(initialProfile ?? null);
79
+ const [loaded, setLoaded] = useState(!!initialProfile);
80
+ const [confirmDelete, setConfirmDelete] = useState(false);
81
+ const [testReport, setTestReport] = useState<TestReport | null>(null);
82
+ const [runningTests, setRunningTests] = useState(false);
83
+ const [selectedTestRuntime, setSelectedTestRuntime] =
84
+ useState<AgentRuntimeId>(DEFAULT_AGENT_RUNTIME);
85
+
86
+ const refresh = useCallback(async () => {
87
+ try {
88
+ const res = await fetch(`/api/profiles/${profileId}`);
89
+ if (res.ok) {
90
+ setProfile(await res.json());
91
+ }
92
+ } catch {
93
+ // silent
94
+ }
95
+ setLoaded(true);
96
+ }, [profileId]);
97
+
98
+ useEffect(() => {
99
+ // Skip initial fetch if server provided data — only refresh on mutations
100
+ if (!initialProfile) refresh();
101
+ }, [refresh, initialProfile]);
102
+
103
+ async function handleDelete() {
104
+ setConfirmDelete(false);
105
+ try {
106
+ const res = await fetch(`/api/profiles/${profileId}`, {
107
+ method: "DELETE",
108
+ });
109
+ if (res.ok) {
110
+ toast.success("Profile deleted");
111
+ router.push("/profiles");
112
+ } else {
113
+ const data = await res.json().catch(() => null);
114
+ toast.error(data?.error ?? "Failed to delete profile");
115
+ }
116
+ } catch {
117
+ toast.error("Network error");
118
+ }
119
+ }
120
+
121
+ async function handleRunTests() {
122
+ setRunningTests(true);
123
+ setTestReport(null);
124
+ try {
125
+ const res = await fetch(`/api/profiles/${profileId}/test`, {
126
+ method: "POST",
127
+ headers: { "Content-Type": "application/json" },
128
+ body: JSON.stringify({ runtimeId: selectedTestRuntime }),
129
+ });
130
+ if (res.ok) {
131
+ const report: TestReport = await res.json();
132
+ setTestReport(report);
133
+ if (report.unsupported) {
134
+ toast.warning(report.unsupportedReason ?? "This runtime cannot test the selected profile");
135
+ } else if (report.totalFailed === 0) {
136
+ toast.success(`All ${report.totalPassed} tests passed`);
137
+ } else {
138
+ toast.warning(`${report.totalPassed} passed, ${report.totalFailed} failed`);
139
+ }
140
+ } else {
141
+ const data = await res.json().catch(() => null);
142
+ toast.error(data?.error ?? "Failed to run tests");
143
+ }
144
+ } catch {
145
+ toast.error("Network error running tests");
146
+ } finally {
147
+ setRunningTests(false);
148
+ }
149
+ }
150
+
151
+ if (!loaded) {
152
+ return (
153
+ <div className="space-y-4">
154
+ <Skeleton className="h-8 w-64" />
155
+ <Skeleton className="h-32 w-full" />
156
+ <Skeleton className="h-48 w-full" />
157
+ </div>
158
+ );
159
+ }
160
+
161
+ if (!profile) {
162
+ return <p className="text-muted-foreground">Profile not found.</p>;
163
+ }
164
+
165
+ const DomainIcon = profile.domain === "work" ? Bot : Sparkles;
166
+ const lineCount = profile.skillMd ? profile.skillMd.split("\n").length : 0;
167
+ const toolCount = (profile.allowedTools?.length ?? 0);
168
+ const hasPolicy = profile.canUseToolPolicy &&
169
+ ((profile.canUseToolPolicy.autoApprove?.length ?? 0) > 0 ||
170
+ (profile.canUseToolPolicy.autoDeny?.length ?? 0) > 0);
171
+ const testTotal = testReport ? testReport.totalPassed + testReport.totalFailed : 0;
172
+ const runtimeCompatibility = runtimeOptions.map((runtime) => ({
173
+ runtime,
174
+ compatibility: getProfileRuntimeCompatibility(profile, runtime.id),
175
+ }));
176
+
177
+ return (
178
+ <div className="space-y-6" aria-live="polite">
179
+ {/* Header */}
180
+ <div className="flex items-center justify-between">
181
+ <div className="flex items-center gap-2">
182
+ <h1 className="text-2xl font-bold">{profile.name}</h1>
183
+ <Badge variant={profile.domain === "work" ? "default" : "secondary"}>
184
+ {profile.domain}
185
+ </Badge>
186
+ </div>
187
+ <div className="flex items-center gap-2">
188
+ {!isBuiltin && (
189
+ <Button
190
+ variant="outline"
191
+ size="sm"
192
+ onClick={() => router.push(`/profiles/${profileId}/edit`)}
193
+ >
194
+ <Pencil className="h-3.5 w-3.5 mr-1" />
195
+ Edit
196
+ </Button>
197
+ )}
198
+ <Button
199
+ variant="outline"
200
+ size="sm"
201
+ onClick={() => router.push(`/profiles/${profileId}/edit?duplicate=true`)}
202
+ >
203
+ <Copy className="h-3.5 w-3.5 mr-1" />
204
+ Duplicate
205
+ </Button>
206
+ {!isBuiltin && (
207
+ <Button
208
+ variant="outline"
209
+ size="sm"
210
+ className="text-destructive"
211
+ onClick={() => setConfirmDelete(true)}
212
+ >
213
+ <Trash2 className="h-3.5 w-3.5 mr-1" />
214
+ Delete
215
+ </Button>
216
+ )}
217
+ </div>
218
+ </div>
219
+
220
+ {/* Bento Grid: Identity + Configuration + Runtime Coverage + Tools & Policy */}
221
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
222
+ {/* Identity Card */}
223
+ <Card className="surface-card">
224
+ <CardHeader className="pb-2">
225
+ <CardTitle className="text-sm font-medium flex items-center gap-2">
226
+ <DomainIcon className="h-4 w-4 text-muted-foreground" />
227
+ Identity
228
+ </CardTitle>
229
+ </CardHeader>
230
+ <CardContent className="space-y-3">
231
+ <p className="text-sm text-muted-foreground">{profile.description}</p>
232
+ {profile.tags && profile.tags.length > 0 && (
233
+ <div className="flex flex-wrap gap-1">
234
+ {profile.tags.map((tag) => (
235
+ <Badge key={tag} variant="secondary" className="text-xs">
236
+ {tag}
237
+ </Badge>
238
+ ))}
239
+ </div>
240
+ )}
241
+ {(profile.version || profile.author) && (
242
+ <div className="flex items-center gap-3 border-t border-border/60 pt-1 text-xs text-muted-foreground">
243
+ {profile.version && (
244
+ <span className="flex items-center gap-1">
245
+ <Tag className="h-3 w-3" />v{profile.version}
246
+ </span>
247
+ )}
248
+ {profile.author && (
249
+ <span className="flex items-center gap-1">
250
+ <User className="h-3 w-3" />{profile.author}
251
+ </span>
252
+ )}
253
+ </div>
254
+ )}
255
+ </CardContent>
256
+ </Card>
257
+
258
+ {/* Configuration Card */}
259
+ <Card className="surface-card">
260
+ <CardHeader className="pb-2">
261
+ <CardTitle className="text-sm font-medium">Configuration</CardTitle>
262
+ </CardHeader>
263
+ <CardContent className="space-y-3">
264
+ {/* Temperature Gauge */}
265
+ {profile.temperature !== undefined && (
266
+ <div className="flex items-center gap-2">
267
+ <Thermometer className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
268
+ <span className="text-xs text-muted-foreground w-20">Temperature</span>
269
+ <div className="flex-1 h-1.5 rounded-full bg-muted">
270
+ <div
271
+ className="h-full rounded-full bg-primary"
272
+ style={{ width: `${(profile.temperature / 2) * 100}%` }}
273
+ />
274
+ </div>
275
+ <span className="text-xs font-medium w-8 text-right">{profile.temperature}</span>
276
+ </div>
277
+ )}
278
+ {/* Max Turns */}
279
+ {profile.maxTurns !== undefined && (
280
+ <div className="flex items-center gap-2">
281
+ <Repeat className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
282
+ <span className="text-xs text-muted-foreground">Max Turns</span>
283
+ <span className="text-xl font-bold ml-auto">{profile.maxTurns}</span>
284
+ </div>
285
+ )}
286
+ {/* Output Format */}
287
+ {profile.outputFormat && (
288
+ <div className="flex items-center gap-2">
289
+ <FileOutput className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
290
+ <span className="text-xs text-muted-foreground">Output</span>
291
+ <Badge variant="outline" className="text-xs ml-auto">
292
+ {profile.outputFormat}
293
+ </Badge>
294
+ </div>
295
+ )}
296
+ {!profile.temperature && !profile.maxTurns && !profile.outputFormat && (
297
+ <p className="text-sm text-muted-foreground">Default configuration</p>
298
+ )}
299
+ </CardContent>
300
+ </Card>
301
+
302
+ {/* Runtime Coverage */}
303
+ <Card className="surface-card">
304
+ <CardHeader className="pb-2">
305
+ <CardTitle className="text-sm font-medium flex items-center gap-2">
306
+ <Cpu className="h-4 w-4 text-muted-foreground" />
307
+ Runtime Coverage
308
+ </CardTitle>
309
+ </CardHeader>
310
+ <CardContent className="space-y-3">
311
+ {runtimeCompatibility.map(({ runtime, compatibility }) => (
312
+ <div
313
+ key={runtime.id}
314
+ className="surface-card-muted rounded-lg border border-border/60 p-3"
315
+ >
316
+ <div className="flex items-center justify-between gap-3">
317
+ <span className="text-sm font-medium">{runtime.label}</span>
318
+ <Badge
319
+ variant={compatibility.supported ? "secondary" : "outline"}
320
+ className={
321
+ compatibility.supported
322
+ ? "bg-status-completed/10 text-status-completed"
323
+ : "text-muted-foreground"
324
+ }
325
+ >
326
+ {compatibility.supported ? "Supported" : "Unsupported"}
327
+ </Badge>
328
+ </div>
329
+ <p className="mt-2 text-xs text-muted-foreground">
330
+ {compatibility.supported
331
+ ? compatibility.instructionsSource === "runtime-override"
332
+ ? "Uses runtime-specific instructions"
333
+ : "Uses shared SKILL.md instructions"
334
+ : "Blocked before execution"}
335
+ </p>
336
+ </div>
337
+ ))}
338
+ <p className="text-xs text-muted-foreground">
339
+ {`Supports ${getSupportedRuntimes(profile)
340
+ .map((runtimeId) => runtimeLabelMap.get(runtimeId) ?? runtimeId)
341
+ .join(", ")}`}
342
+ </p>
343
+ </CardContent>
344
+ </Card>
345
+
346
+ {/* Tools & Policy Card */}
347
+ <Card className="surface-card md:col-span-2 lg:col-span-1">
348
+ <CardHeader className="pb-2">
349
+ <CardTitle className="text-sm font-medium flex items-center gap-2">
350
+ <Wrench className="h-4 w-4 text-muted-foreground" />
351
+ Tools & Policy
352
+ {toolCount > 0 && (
353
+ <Badge variant="secondary" className="text-xs ml-auto">{toolCount}</Badge>
354
+ )}
355
+ </CardTitle>
356
+ </CardHeader>
357
+ <CardContent className="space-y-3">
358
+ {/* Allowed Tools */}
359
+ {profile.allowedTools && profile.allowedTools.length > 0 ? (
360
+ <div className="flex flex-wrap gap-1">
361
+ {profile.allowedTools.map((tool) => (
362
+ <Badge key={tool} variant="outline" className="text-xs">
363
+ {tool}
364
+ </Badge>
365
+ ))}
366
+ </div>
367
+ ) : (
368
+ <p className="text-xs text-muted-foreground">All tools allowed</p>
369
+ )}
370
+
371
+ {/* Auto-approve */}
372
+ {hasPolicy && profile.canUseToolPolicy?.autoApprove && profile.canUseToolPolicy.autoApprove.length > 0 && (
373
+ <div>
374
+ <div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
375
+ <ShieldCheck className="h-3 w-3 text-status-completed" />
376
+ <span>Auto-approve</span>
377
+ </div>
378
+ <div className="flex flex-wrap gap-1">
379
+ {profile.canUseToolPolicy.autoApprove.map((tool) => (
380
+ <Badge
381
+ key={tool}
382
+ variant="outline"
383
+ className="border-status-completed/30 bg-status-completed/10 text-xs text-status-completed"
384
+ >
385
+ {tool}
386
+ </Badge>
387
+ ))}
388
+ </div>
389
+ </div>
390
+ )}
391
+
392
+ {/* Auto-deny */}
393
+ {hasPolicy && profile.canUseToolPolicy?.autoDeny && profile.canUseToolPolicy.autoDeny.length > 0 && (
394
+ <div>
395
+ <div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
396
+ <ShieldX className="h-3 w-3 text-status-failed" />
397
+ <span>Auto-deny</span>
398
+ </div>
399
+ <div className="flex flex-wrap gap-1">
400
+ {profile.canUseToolPolicy.autoDeny.map((tool) => (
401
+ <Badge
402
+ key={tool}
403
+ variant="outline"
404
+ className="border-status-failed/30 bg-status-failed/10 text-xs text-status-failed"
405
+ >
406
+ {tool}
407
+ </Badge>
408
+ ))}
409
+ </div>
410
+ </div>
411
+ )}
412
+
413
+ {!toolCount && !hasPolicy && (
414
+ <p className="text-xs text-muted-foreground">No tool restrictions</p>
415
+ )}
416
+ </CardContent>
417
+ </Card>
418
+ </div>
419
+
420
+ {/* Bottom row: SKILL.md + Tests */}
421
+ <div className="grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-4">
422
+ {/* SKILL.md — collapsible */}
423
+ {profile.skillMd && (
424
+ <details className="group" open>
425
+ <summary className="surface-card flex cursor-pointer list-none items-center gap-2 rounded-lg p-3 text-sm font-medium transition-colors hover:bg-accent/50">
426
+ <FileCode className="h-4 w-4 text-muted-foreground" />
427
+ <span>SKILL.md</span>
428
+ <Badge variant="secondary" className="text-xs ml-auto">
429
+ {lineCount} lines
430
+ </Badge>
431
+ <span className="text-muted-foreground text-xs group-open:rotate-90 transition-transform">▶</span>
432
+ </summary>
433
+ <div className="surface-panel mt-2 rounded-lg p-4">
434
+ <pre className="surface-scroll max-h-64 overflow-auto whitespace-pre-wrap rounded-lg p-4 text-xs">
435
+ {profile.skillMd}
436
+ </pre>
437
+ </div>
438
+ </details>
439
+ )}
440
+
441
+ {/* Tests */}
442
+ {profile.tests && profile.tests.length > 0 && (
443
+ <Card className="surface-card">
444
+ <CardHeader className="pb-2">
445
+ <div className="flex items-center justify-between">
446
+ <CardTitle className="text-sm font-medium">
447
+ Tests ({profile.tests.length})
448
+ </CardTitle>
449
+ <div className="flex items-center gap-2">
450
+ <select
451
+ value={selectedTestRuntime}
452
+ onChange={(event) =>
453
+ setSelectedTestRuntime(event.target.value as AgentRuntimeId)
454
+ }
455
+ className="surface-control h-7 rounded-md border border-border/60 px-2 text-xs"
456
+ >
457
+ {runtimeOptions.map((runtime) => (
458
+ <option key={runtime.id} value={runtime.id}>
459
+ {runtime.label}
460
+ </option>
461
+ ))}
462
+ </select>
463
+ <Button
464
+ size="sm"
465
+ variant="outline"
466
+ onClick={handleRunTests}
467
+ disabled={runningTests}
468
+ className="h-7"
469
+ >
470
+ {runningTests ? (
471
+ <Loader2 className="mr-1 h-3 w-3 animate-spin" />
472
+ ) : (
473
+ <Play className="mr-1 h-3 w-3" />
474
+ )}
475
+ {runningTests ? "Running..." : "Run Tests"}
476
+ </Button>
477
+ </div>
478
+ </div>
479
+ </CardHeader>
480
+ <CardContent className="space-y-2">
481
+ {testReport?.unsupported && (
482
+ <div className="surface-card-muted rounded-md border border-warning/30 bg-warning/10 p-2 text-xs text-warning">
483
+ {testReport.unsupportedReason ??
484
+ `${runtimeLabelMap.get(selectedTestRuntime) ?? selectedTestRuntime} cannot test this profile yet`}
485
+ </div>
486
+ )}
487
+ {/* Test Summary Bar */}
488
+ {testReport && !testReport.unsupported && (
489
+ <div className="space-y-1">
490
+ <div className="flex h-1.5 rounded-full overflow-hidden bg-muted">
491
+ <div
492
+ className="bg-status-completed"
493
+ style={{ width: `${(testReport.totalPassed / testTotal) * 100}%` }}
494
+ />
495
+ <div
496
+ className="bg-status-failed"
497
+ style={{ width: `${(testReport.totalFailed / testTotal) * 100}%` }}
498
+ />
499
+ </div>
500
+ <p className="text-xs text-muted-foreground">
501
+ {testReport.totalPassed}/{testTotal} passed
502
+ </p>
503
+ </div>
504
+ )}
505
+ {/* Test Items */}
506
+ <div className="space-y-1.5">
507
+ {profile.tests.map((test, i) => {
508
+ const result = testReport?.unsupported ? undefined : testReport?.results[i];
509
+ return (
510
+ <div key={i} className="surface-card-muted rounded-md border p-2 text-sm">
511
+ <div className="flex items-start gap-2">
512
+ {result && (
513
+ result.passed ? (
514
+ <CheckCircle2 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-status-completed" />
515
+ ) : (
516
+ <XCircle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-status-failed" />
517
+ )
518
+ )}
519
+ <div className="flex-1 min-w-0">
520
+ <p className="text-xs font-medium truncate">{test.task}</p>
521
+ <div className="mt-1 flex flex-wrap gap-0.5">
522
+ {test.expectedKeywords.map((kw) => {
523
+ const found = result?.foundKeywords.includes(kw);
524
+ const missing = result?.missingKeywords.includes(kw);
525
+ return (
526
+ <Badge
527
+ key={kw}
528
+ variant="outline"
529
+ className={`text-[10px] px-1.5 py-0 ${
530
+ found
531
+ ? "border-status-completed/30 bg-status-completed/10 text-status-completed"
532
+ : missing
533
+ ? "border-status-failed/30 bg-status-failed/10 text-status-failed"
534
+ : ""
535
+ }`}
536
+ >
537
+ {kw}
538
+ </Badge>
539
+ );
540
+ })}
541
+ </div>
542
+ </div>
543
+ </div>
544
+ </div>
545
+ );
546
+ })}
547
+ </div>
548
+ </CardContent>
549
+ </Card>
550
+ )}
551
+ </div>
552
+
553
+ <ConfirmDialog
554
+ open={confirmDelete}
555
+ onOpenChange={setConfirmDelete}
556
+ title="Delete Profile"
557
+ description="This will permanently delete this custom profile."
558
+ confirmLabel="Delete"
559
+ onConfirm={handleDelete}
560
+ destructive
561
+ />
562
+ </div>
563
+ );
564
+ }