stagent 0.9.6 → 0.11.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 (396) hide show
  1. package/README.md +20 -44
  2. package/dist/cli.js +66 -18
  3. package/docs/.coverage-gaps.json +144 -56
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/agent-intelligence.md +12 -2
  6. package/docs/features/chat.md +40 -5
  7. package/docs/features/cost-usage.md +1 -1
  8. package/docs/features/documents.md +5 -2
  9. package/docs/features/inbox-notifications.md +10 -2
  10. package/docs/features/keyboard-navigation.md +12 -3
  11. package/docs/features/provider-runtimes.md +20 -2
  12. package/docs/features/schedules.md +32 -4
  13. package/docs/features/settings.md +28 -5
  14. package/docs/features/shared-components.md +7 -3
  15. package/docs/features/tables.md +11 -2
  16. package/docs/features/tool-permissions.md +6 -2
  17. package/docs/features/workflows.md +14 -4
  18. package/docs/index.md +1 -1
  19. package/docs/journeys/developer.md +39 -2
  20. package/docs/journeys/personal-use.md +32 -8
  21. package/docs/journeys/power-user.md +45 -14
  22. package/docs/journeys/work-use.md +17 -8
  23. package/docs/manifest.json +15 -15
  24. package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
  25. package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
  26. package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
  27. package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
  28. package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
  29. package/docs/superpowers/plans/2026-04-14-chat-command-namespace-refactor.md +1390 -0
  30. package/docs/superpowers/plans/2026-04-14-chat-environment-integration.md +1561 -0
  31. package/docs/superpowers/plans/2026-04-14-chat-polish-bundle-v1.md +1219 -0
  32. package/docs/superpowers/plans/2026-04-14-chat-session-persistence-provider-closeout.md +399 -0
  33. package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
  34. package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
  35. package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
  36. package/next.config.mjs +1 -0
  37. package/package.json +3 -2
  38. package/src/__tests__/instrumentation-smoke.test.ts +15 -0
  39. package/src/app/analytics/page.tsx +1 -21
  40. package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
  41. package/src/app/api/chat/conversations/[id]/skills/__tests__/activate.test.ts +141 -0
  42. package/src/app/api/chat/conversations/[id]/skills/activate/route.ts +74 -0
  43. package/src/app/api/chat/conversations/[id]/skills/deactivate/route.ts +33 -0
  44. package/src/app/api/chat/export/route.ts +52 -0
  45. package/src/app/api/chat/files/search/route.ts +50 -0
  46. package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
  47. package/src/app/api/environment/rescan-if-stale/__tests__/route.test.ts +45 -0
  48. package/src/app/api/environment/rescan-if-stale/route.ts +23 -0
  49. package/src/app/api/environment/skills/route.ts +13 -0
  50. package/src/app/api/instance/config/route.ts +41 -0
  51. package/src/app/api/instance/init/route.ts +34 -0
  52. package/src/app/api/instance/upgrade/check/route.ts +26 -0
  53. package/src/app/api/instance/upgrade/route.ts +96 -0
  54. package/src/app/api/instance/upgrade/status/route.ts +35 -0
  55. package/src/app/api/memory/route.ts +0 -11
  56. package/src/app/api/notifications/route.ts +4 -2
  57. package/src/app/api/projects/[id]/route.ts +5 -155
  58. package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
  59. package/src/app/api/schedules/[id]/execute/route.ts +111 -0
  60. package/src/app/api/schedules/[id]/route.ts +9 -1
  61. package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
  62. package/src/app/api/schedules/route.ts +3 -12
  63. package/src/app/api/settings/chat/pins/route.ts +94 -0
  64. package/src/app/api/settings/chat/saved-searches/__tests__/route.test.ts +119 -0
  65. package/src/app/api/settings/chat/saved-searches/route.ts +79 -0
  66. package/src/app/api/settings/environment/route.ts +26 -0
  67. package/src/app/api/settings/openai/login/route.ts +22 -0
  68. package/src/app/api/settings/openai/logout/route.ts +7 -0
  69. package/src/app/api/settings/openai/route.ts +21 -1
  70. package/src/app/api/settings/providers/route.ts +35 -8
  71. package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
  72. package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
  73. package/src/app/api/tables/[id]/enrich/route.ts +147 -0
  74. package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
  75. package/src/app/api/tasks/[id]/execute/route.ts +52 -33
  76. package/src/app/api/tasks/[id]/respond/route.ts +31 -15
  77. package/src/app/api/tasks/[id]/resume/route.ts +24 -3
  78. package/src/app/api/workflows/[id]/resume/route.ts +59 -0
  79. package/src/app/api/workflows/[id]/status/route.ts +22 -8
  80. package/src/app/api/workspace/context/route.ts +2 -0
  81. package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
  82. package/src/app/chat/page.tsx +11 -0
  83. package/src/app/documents/page.tsx +4 -1
  84. package/src/app/inbox/page.tsx +12 -5
  85. package/src/app/layout.tsx +42 -21
  86. package/src/app/page.tsx +0 -2
  87. package/src/app/settings/page.tsx +8 -9
  88. package/src/components/chat/__tests__/capability-banner.test.tsx +38 -0
  89. package/src/components/chat/__tests__/chat-session-provider.test.tsx +573 -0
  90. package/src/components/chat/__tests__/skill-row.test.tsx +91 -0
  91. package/src/components/chat/capability-banner.tsx +68 -0
  92. package/src/components/chat/chat-command-popover.tsx +670 -49
  93. package/src/components/chat/chat-input.tsx +104 -10
  94. package/src/components/chat/chat-message.tsx +12 -3
  95. package/src/components/chat/chat-session-provider.tsx +790 -0
  96. package/src/components/chat/chat-shell.tsx +151 -401
  97. package/src/components/chat/command-tab-bar.tsx +68 -0
  98. package/src/components/chat/conversation-template-picker.tsx +421 -0
  99. package/src/components/chat/help-dialog.tsx +39 -0
  100. package/src/components/chat/skill-composition-conflict-dialog.tsx +96 -0
  101. package/src/components/chat/skill-row.tsx +147 -0
  102. package/src/components/documents/document-browser.tsx +37 -19
  103. package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
  104. package/src/components/instance/instance-section.tsx +382 -0
  105. package/src/components/instance/upgrade-badge.tsx +219 -0
  106. package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
  107. package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
  108. package/src/components/notifications/__tests__/permission-response-actions.test.tsx +70 -0
  109. package/src/components/notifications/batch-proposal-review.tsx +20 -5
  110. package/src/components/notifications/inbox-list.tsx +11 -2
  111. package/src/components/notifications/notification-item.tsx +56 -2
  112. package/src/components/notifications/pending-approval-host.tsx +56 -37
  113. package/src/components/notifications/permission-response-actions.tsx +155 -1
  114. package/src/components/schedules/schedule-create-sheet.tsx +19 -1
  115. package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
  116. package/src/components/schedules/schedule-form.tsx +31 -0
  117. package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
  118. package/src/components/settings/auth-method-selector.tsx +19 -4
  119. package/src/components/settings/auth-status-badge.tsx +28 -3
  120. package/src/components/settings/environment-section.tsx +102 -0
  121. package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
  122. package/src/components/settings/openai-runtime-section.tsx +7 -1
  123. package/src/components/settings/providers-runtimes-section.tsx +138 -19
  124. package/src/components/shared/__tests__/filter-hint.test.tsx +40 -0
  125. package/src/components/shared/__tests__/saved-searches-manager.test.tsx +147 -0
  126. package/src/components/shared/app-sidebar.tsx +4 -3
  127. package/src/components/shared/command-palette.tsx +266 -7
  128. package/src/components/shared/filter-hint.tsx +70 -0
  129. package/src/components/shared/filter-input.tsx +59 -0
  130. package/src/components/shared/saved-searches-manager.tsx +199 -0
  131. package/src/components/shared/theme-toggle.tsx +5 -24
  132. package/src/components/shared/workspace-indicator.tsx +61 -2
  133. package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
  134. package/src/components/tables/table-create-sheet.tsx +4 -0
  135. package/src/components/tables/table-enrichment-runs.tsx +103 -0
  136. package/src/components/tables/table-enrichment-sheet.tsx +538 -0
  137. package/src/components/tables/table-spreadsheet.tsx +29 -5
  138. package/src/components/tables/table-toolbar.tsx +10 -1
  139. package/src/components/tasks/kanban-board.tsx +1 -0
  140. package/src/components/tasks/kanban-column.tsx +53 -14
  141. package/src/components/tasks/task-bento-grid.tsx +31 -2
  142. package/src/components/tasks/task-card.tsx +29 -3
  143. package/src/components/tasks/task-chip-bar.tsx +54 -1
  144. package/src/components/tasks/task-result-renderer.tsx +1 -1
  145. package/src/components/workflows/delay-step-body.tsx +109 -0
  146. package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
  147. package/src/components/workflows/loop-status-view.tsx +1 -1
  148. package/src/components/workflows/shared/step-result.tsx +78 -0
  149. package/src/components/workflows/shared/workflow-header.tsx +141 -0
  150. package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
  151. package/src/components/workflows/swarm-dashboard.tsx +2 -15
  152. package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
  153. package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
  154. package/src/components/workflows/workflow-form-view.tsx +133 -16
  155. package/src/components/workflows/workflow-status-view.tsx +30 -740
  156. package/src/hooks/__tests__/use-chat-autocomplete-tabs.test.ts +47 -0
  157. package/src/hooks/__tests__/use-saved-searches.test.ts +70 -0
  158. package/src/hooks/use-active-skills.ts +110 -0
  159. package/src/hooks/use-chat-autocomplete.ts +120 -7
  160. package/src/hooks/use-enriched-skills.ts +19 -0
  161. package/src/hooks/use-pinned-entries.ts +104 -0
  162. package/src/hooks/use-recent-user-messages.ts +19 -0
  163. package/src/hooks/use-saved-searches.ts +142 -0
  164. package/src/instrumentation-node.ts +94 -0
  165. package/src/instrumentation.ts +4 -48
  166. package/src/lib/agents/__tests__/claude-agent-sdk-options.test.ts +56 -0
  167. package/src/lib/agents/__tests__/claude-agent.test.ts +212 -0
  168. package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
  169. package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
  170. package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
  171. package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
  172. package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
  173. package/src/lib/agents/__tests__/task-dispatch.test.ts +166 -0
  174. package/src/lib/agents/__tests__/tool-permissions.test.ts +60 -0
  175. package/src/lib/agents/claude-agent.ts +217 -21
  176. package/src/lib/agents/execution-manager.ts +0 -35
  177. package/src/lib/agents/handoff/bus.ts +2 -2
  178. package/src/lib/agents/learned-context.ts +0 -12
  179. package/src/lib/agents/learning-session.ts +18 -5
  180. package/src/lib/agents/profiles/__tests__/list-fused-profiles.test.ts +110 -0
  181. package/src/lib/agents/profiles/__tests__/registry.test.ts +53 -4
  182. package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +97 -0
  183. package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +36 -0
  184. package/src/lib/agents/profiles/list-fused-profiles.ts +104 -0
  185. package/src/lib/agents/profiles/registry.ts +18 -0
  186. package/src/lib/agents/profiles/types.ts +7 -1
  187. package/src/lib/agents/router.ts +3 -6
  188. package/src/lib/agents/runtime/__tests__/catalog.test.ts +130 -0
  189. package/src/lib/agents/runtime/__tests__/execution-target.test.ts +183 -0
  190. package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
  191. package/src/lib/agents/runtime/anthropic-direct.ts +8 -0
  192. package/src/lib/agents/runtime/catalog.ts +121 -0
  193. package/src/lib/agents/runtime/claude-sdk.ts +32 -0
  194. package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
  195. package/src/lib/agents/runtime/execution-target.ts +456 -0
  196. package/src/lib/agents/runtime/index.ts +4 -0
  197. package/src/lib/agents/runtime/launch-failure.ts +101 -0
  198. package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
  199. package/src/lib/agents/runtime/openai-codex.ts +64 -60
  200. package/src/lib/agents/runtime/openai-direct.ts +8 -0
  201. package/src/lib/agents/runtime/types.ts +8 -0
  202. package/src/lib/agents/task-dispatch.ts +220 -0
  203. package/src/lib/agents/tool-permissions.ts +16 -1
  204. package/src/lib/book/chapter-mapping.ts +11 -0
  205. package/src/lib/book/content.ts +10 -0
  206. package/src/lib/chat/__tests__/active-skill-injection.test.ts +261 -0
  207. package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
  208. package/src/lib/chat/__tests__/clean-filter-input.test.ts +68 -0
  209. package/src/lib/chat/__tests__/command-tabs.test.ts +68 -0
  210. package/src/lib/chat/__tests__/context-builder-files.test.ts +112 -0
  211. package/src/lib/chat/__tests__/dismissals.test.ts +65 -0
  212. package/src/lib/chat/__tests__/engine-sdk-options.test.ts +117 -0
  213. package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
  214. package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
  215. package/src/lib/chat/__tests__/skill-conflict.test.ts +35 -0
  216. package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
  217. package/src/lib/chat/__tests__/types.test.ts +28 -0
  218. package/src/lib/chat/active-skills.ts +31 -0
  219. package/src/lib/chat/active-streams.ts +27 -0
  220. package/src/lib/chat/clean-filter-input.ts +30 -0
  221. package/src/lib/chat/codex-engine.ts +46 -24
  222. package/src/lib/chat/command-tabs.ts +61 -0
  223. package/src/lib/chat/context-builder.ts +146 -4
  224. package/src/lib/chat/dismissals.ts +73 -0
  225. package/src/lib/chat/engine.ts +159 -18
  226. package/src/lib/chat/files/__tests__/search.test.ts +135 -0
  227. package/src/lib/chat/files/expand-mention.ts +76 -0
  228. package/src/lib/chat/files/search.ts +99 -0
  229. package/src/lib/chat/reconcile.ts +117 -0
  230. package/src/lib/chat/skill-composition.ts +210 -0
  231. package/src/lib/chat/skill-conflict.ts +105 -0
  232. package/src/lib/chat/stagent-tools.ts +7 -19
  233. package/src/lib/chat/stream-telemetry.ts +137 -0
  234. package/src/lib/chat/suggested-prompts.ts +28 -1
  235. package/src/lib/chat/system-prompt.ts +48 -1
  236. package/src/lib/chat/tool-catalog.ts +35 -4
  237. package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
  238. package/src/lib/chat/tools/__tests__/profile-tools.test.ts +51 -0
  239. package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
  240. package/src/lib/chat/tools/__tests__/settings-tools.test.ts +294 -0
  241. package/src/lib/chat/tools/__tests__/skill-tools.test.ts +474 -0
  242. package/src/lib/chat/tools/__tests__/task-tools.test.ts +399 -0
  243. package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +351 -0
  244. package/src/lib/chat/tools/blueprint-tools.ts +190 -0
  245. package/src/lib/chat/tools/document-tools.ts +29 -13
  246. package/src/lib/chat/tools/helpers.ts +41 -0
  247. package/src/lib/chat/tools/notification-tools.ts +9 -5
  248. package/src/lib/chat/tools/profile-tools.ts +120 -23
  249. package/src/lib/chat/tools/project-tools.ts +33 -0
  250. package/src/lib/chat/tools/schedule-tools.ts +44 -11
  251. package/src/lib/chat/tools/skill-tools.ts +183 -0
  252. package/src/lib/chat/tools/table-tools.ts +71 -0
  253. package/src/lib/chat/tools/task-tools.ts +89 -21
  254. package/src/lib/chat/tools/workflow-tools.ts +275 -32
  255. package/src/lib/chat/types.ts +15 -0
  256. package/src/lib/constants/settings.ts +10 -18
  257. package/src/lib/data/__tests__/clear.test.ts +56 -2
  258. package/src/lib/data/clear.ts +17 -16
  259. package/src/lib/data/delete-project.ts +171 -0
  260. package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
  261. package/src/lib/db/bootstrap.ts +62 -16
  262. package/src/lib/db/index.ts +5 -0
  263. package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
  264. package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
  265. package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
  266. package/src/lib/db/migrations/0026_drop_license.sql +3 -0
  267. package/src/lib/db/migrations/meta/_journal.json +21 -0
  268. package/src/lib/db/schema.ts +94 -23
  269. package/src/lib/environment/__tests__/auto-promote.test.ts +132 -0
  270. package/src/lib/environment/__tests__/list-skills-enriched.test.ts +55 -0
  271. package/src/lib/environment/__tests__/skill-enrichment.test.ts +129 -0
  272. package/src/lib/environment/__tests__/skill-recommendations.test.ts +87 -0
  273. package/src/lib/environment/data.ts +9 -0
  274. package/src/lib/environment/list-skills.ts +176 -0
  275. package/src/lib/environment/parsers/__tests__/skill.test.ts +54 -0
  276. package/src/lib/environment/parsers/skill.ts +26 -5
  277. package/src/lib/environment/profile-generator.ts +54 -0
  278. package/src/lib/environment/skill-enrichment.ts +106 -0
  279. package/src/lib/environment/skill-recommendations.ts +66 -0
  280. package/src/lib/environment/workspace-context.ts +13 -1
  281. package/src/lib/filters/__tests__/parse.quoted.test.ts +40 -0
  282. package/src/lib/filters/__tests__/parse.test.ts +135 -0
  283. package/src/lib/filters/parse.ts +86 -0
  284. package/src/lib/import/dedup.ts +4 -54
  285. package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
  286. package/src/lib/instance/__tests__/detect.test.ts +115 -0
  287. package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
  288. package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
  289. package/src/lib/instance/__tests__/settings.test.ts +83 -0
  290. package/src/lib/instance/__tests__/upgrade-poller.test.ts +181 -0
  291. package/src/lib/instance/bootstrap.ts +270 -0
  292. package/src/lib/instance/detect.ts +49 -0
  293. package/src/lib/instance/fingerprint.ts +76 -0
  294. package/src/lib/instance/git-ops.ts +95 -0
  295. package/src/lib/instance/settings.ts +61 -0
  296. package/src/lib/instance/types.ts +77 -0
  297. package/src/lib/instance/upgrade-poller.ts +205 -0
  298. package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
  299. package/src/lib/notifications/visibility.ts +33 -0
  300. package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
  301. package/src/lib/schedules/__tests__/config.test.ts +62 -0
  302. package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
  303. package/src/lib/schedules/__tests__/integration.test.ts +82 -0
  304. package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
  305. package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
  306. package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
  307. package/src/lib/schedules/collision-check.ts +105 -0
  308. package/src/lib/schedules/config.ts +53 -0
  309. package/src/lib/schedules/scheduler.ts +236 -17
  310. package/src/lib/schedules/slot-claim.ts +105 -0
  311. package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
  312. package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
  313. package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
  314. package/src/lib/settings/openai-auth.ts +105 -10
  315. package/src/lib/settings/openai-login-manager.ts +260 -0
  316. package/src/lib/settings/runtime-setup.ts +14 -4
  317. package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
  318. package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
  319. package/src/lib/tables/enrichment-planner.ts +454 -0
  320. package/src/lib/tables/enrichment.ts +328 -0
  321. package/src/lib/tables/query-builder.ts +5 -2
  322. package/src/lib/tables/trigger-evaluator.ts +3 -2
  323. package/src/lib/theme.ts +71 -0
  324. package/src/lib/usage/ledger.ts +2 -18
  325. package/src/lib/util/__tests__/similarity.test.ts +106 -0
  326. package/src/lib/util/similarity.ts +77 -0
  327. package/src/lib/utils/format-timestamp.ts +24 -0
  328. package/src/lib/utils/stagent-paths.ts +12 -0
  329. package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
  330. package/src/lib/validators/__tests__/settings.test.ts +10 -0
  331. package/src/lib/validators/blueprint.ts +70 -9
  332. package/src/lib/validators/profile.ts +2 -2
  333. package/src/lib/validators/settings.ts +3 -1
  334. package/src/lib/workflows/__tests__/delay.test.ts +196 -0
  335. package/src/lib/workflows/__tests__/engine.test.ts +8 -0
  336. package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
  337. package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
  338. package/src/lib/workflows/blueprints/__tests__/render-prompt.test.ts +124 -0
  339. package/src/lib/workflows/blueprints/instantiator.ts +22 -1
  340. package/src/lib/workflows/blueprints/render-prompt.ts +71 -0
  341. package/src/lib/workflows/blueprints/types.ts +16 -2
  342. package/src/lib/workflows/delay.ts +106 -0
  343. package/src/lib/workflows/engine.ts +212 -7
  344. package/src/lib/workflows/loop-executor.ts +349 -24
  345. package/src/lib/workflows/post-action.ts +91 -0
  346. package/src/lib/workflows/types.ts +166 -1
  347. package/src/test/setup.ts +10 -0
  348. package/src/app/api/license/checkout/route.ts +0 -28
  349. package/src/app/api/license/portal/route.ts +0 -26
  350. package/src/app/api/license/route.ts +0 -89
  351. package/src/app/api/license/usage/route.ts +0 -63
  352. package/src/app/api/marketplace/browse/route.ts +0 -15
  353. package/src/app/api/marketplace/import/route.ts +0 -28
  354. package/src/app/api/marketplace/publish/route.ts +0 -40
  355. package/src/app/api/onboarding/email/route.ts +0 -53
  356. package/src/app/api/settings/telemetry/route.ts +0 -14
  357. package/src/app/api/sync/export/route.ts +0 -54
  358. package/src/app/api/sync/restore/route.ts +0 -37
  359. package/src/app/api/sync/sessions/route.ts +0 -24
  360. package/src/app/auth/callback/route.ts +0 -73
  361. package/src/app/marketplace/page.tsx +0 -19
  362. package/src/components/analytics/analytics-gate-card.tsx +0 -101
  363. package/src/components/marketplace/blueprint-card.tsx +0 -61
  364. package/src/components/marketplace/marketplace-browser.tsx +0 -131
  365. package/src/components/onboarding/email-capture-card.tsx +0 -104
  366. package/src/components/settings/activation-form.tsx +0 -95
  367. package/src/components/settings/cloud-account-section.tsx +0 -147
  368. package/src/components/settings/cloud-sync-section.tsx +0 -155
  369. package/src/components/settings/subscription-section.tsx +0 -410
  370. package/src/components/settings/telemetry-section.tsx +0 -80
  371. package/src/components/shared/premium-gate-overlay.tsx +0 -50
  372. package/src/components/shared/schedule-gate-dialog.tsx +0 -64
  373. package/src/components/shared/upgrade-banner.tsx +0 -112
  374. package/src/hooks/use-supabase-auth.ts +0 -79
  375. package/src/lib/billing/email.ts +0 -54
  376. package/src/lib/billing/products.ts +0 -80
  377. package/src/lib/billing/stripe.ts +0 -101
  378. package/src/lib/cloud/supabase-browser.ts +0 -32
  379. package/src/lib/cloud/supabase-client.ts +0 -56
  380. package/src/lib/license/__tests__/features.test.ts +0 -56
  381. package/src/lib/license/__tests__/key-format.test.ts +0 -88
  382. package/src/lib/license/__tests__/manager.test.ts +0 -64
  383. package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
  384. package/src/lib/license/cloud-validation.ts +0 -60
  385. package/src/lib/license/features.ts +0 -44
  386. package/src/lib/license/key-format.ts +0 -101
  387. package/src/lib/license/limit-check.ts +0 -111
  388. package/src/lib/license/limit-queries.ts +0 -51
  389. package/src/lib/license/manager.ts +0 -345
  390. package/src/lib/license/notifications.ts +0 -59
  391. package/src/lib/license/tier-limits.ts +0 -71
  392. package/src/lib/marketplace/marketplace-client.ts +0 -107
  393. package/src/lib/sync/cloud-sync.ts +0 -235
  394. package/src/lib/telemetry/conversion-events.ts +0 -71
  395. package/src/lib/telemetry/queue.ts +0 -122
  396. package/src/lib/validators/license.ts +0 -33
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from "react";
4
- import { Check, ShieldCheck, X } from "lucide-react";
4
+ import { Check, Send, ShieldCheck, X } from "lucide-react";
5
5
  import { toast } from "sonner";
6
6
 
7
7
  import { Button } from "@/components/ui/button";
8
+ import { Textarea } from "@/components/ui/textarea";
8
9
  import { cn } from "@/lib/utils";
9
10
  import {
10
11
  buildPermissionPattern,
@@ -12,6 +13,28 @@ import {
12
13
  type PermissionToolInput,
13
14
  } from "@/lib/notifications/permissions";
14
15
 
16
+ interface AskUserQuestionOption {
17
+ label: string;
18
+ description?: string;
19
+ }
20
+
21
+ function parseQuestionOptions(toolInput: PermissionToolInput): AskUserQuestionOption[] {
22
+ const raw = (toolInput as { options?: unknown }).options;
23
+ if (!Array.isArray(raw)) return [];
24
+ const out: AskUserQuestionOption[] = [];
25
+ for (const item of raw) {
26
+ if (item && typeof item === "object" && typeof (item as { label?: unknown }).label === "string") {
27
+ const entry: AskUserQuestionOption = {
28
+ label: (item as { label: string }).label,
29
+ };
30
+ const desc = (item as { description?: unknown }).description;
31
+ if (typeof desc === "string") entry.description = desc;
32
+ out.push(entry);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
15
38
  interface PermissionResponseActionsProps {
16
39
  taskId?: string | null;
17
40
  notificationId: string;
@@ -87,6 +110,19 @@ export function PermissionResponseActions({
87
110
  }
88
111
  }
89
112
 
113
+ if (toolName === "AskUserQuestion" || toolName === "ask_user_question") {
114
+ return (
115
+ <QuestionReplyActions
116
+ taskId={taskId}
117
+ notificationId={notificationId}
118
+ toolInput={toolInput}
119
+ onResponded={onResponded}
120
+ className={className}
121
+ buttonSize={buttonSize}
122
+ />
123
+ );
124
+ }
125
+
90
126
  return (
91
127
  <div
92
128
  className={cn(
@@ -124,3 +160,121 @@ export function PermissionResponseActions({
124
160
  </div>
125
161
  );
126
162
  }
163
+
164
+ interface QuestionReplyActionsProps {
165
+ taskId?: string | null;
166
+ notificationId: string;
167
+ toolInput: PermissionToolInput;
168
+ onResponded?: () => void;
169
+ className?: string;
170
+ buttonSize?: "sm" | "default";
171
+ }
172
+
173
+ /**
174
+ * Renders the response UI for an `AskUserQuestion` notification:
175
+ * - If `toolInput.options` is a non-empty array → card-cluster radiogroup (one click = answer).
176
+ * - Otherwise → free-form textarea + Send.
177
+ *
178
+ * Posts to /api/tasks/[id]/respond with `{ behavior: "allow", updatedInput: { answer } }`.
179
+ * The task runtime's `waitForToolPermissionResponse()` unblocks and returns `{ answer }`
180
+ * to the agent.
181
+ */
182
+ function QuestionReplyActions({
183
+ taskId,
184
+ notificationId,
185
+ toolInput,
186
+ onResponded,
187
+ className,
188
+ buttonSize = "sm",
189
+ }: QuestionReplyActionsProps) {
190
+ const [loading, setLoading] = useState(false);
191
+ const [draft, setDraft] = useState("");
192
+ const options = parseQuestionOptions(toolInput);
193
+
194
+ async function sendAnswer(answer: string) {
195
+ if (!answer.trim()) return;
196
+ setLoading(true);
197
+ try {
198
+ const res = await fetch(`/api/tasks/${taskId ?? "_checkpoint"}/respond`, {
199
+ method: "POST",
200
+ headers: { "Content-Type": "application/json" },
201
+ body: JSON.stringify({
202
+ notificationId,
203
+ behavior: "allow",
204
+ updatedInput: { answer: answer.trim() },
205
+ }),
206
+ });
207
+
208
+ if (!res.ok) {
209
+ const data = (await res.json().catch(() => null)) as
210
+ | { error?: string }
211
+ | null;
212
+ throw new Error(data?.error ?? "Failed to send answer");
213
+ }
214
+
215
+ onResponded?.();
216
+ } catch (error) {
217
+ toast.error(
218
+ error instanceof Error ? error.message : "Failed to send answer"
219
+ );
220
+ } finally {
221
+ setLoading(false);
222
+ }
223
+ }
224
+
225
+ if (options.length > 0) {
226
+ return (
227
+ <div
228
+ role="radiogroup"
229
+ aria-label="Choose a response"
230
+ className={cn("grid gap-2 sm:grid-cols-1", className)}
231
+ >
232
+ {options.map((option) => (
233
+ <button
234
+ key={option.label}
235
+ type="button"
236
+ role="radio"
237
+ aria-checked={false}
238
+ onClick={() => sendAnswer(option.label)}
239
+ disabled={loading}
240
+ className="rounded-lg border border-border/60 bg-background/60 p-3 text-left transition-colors hover:bg-accent/40 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
241
+ >
242
+ <div className="text-sm font-medium text-foreground">{option.label}</div>
243
+ {option.description && (
244
+ <div className="mt-1 text-xs text-muted-foreground">{option.description}</div>
245
+ )}
246
+ </button>
247
+ ))}
248
+ </div>
249
+ );
250
+ }
251
+
252
+ return (
253
+ <div className={cn("flex flex-col gap-2", className)}>
254
+ <Textarea
255
+ value={draft}
256
+ onChange={(e) => setDraft(e.target.value)}
257
+ placeholder="Type your reply…"
258
+ rows={3}
259
+ disabled={loading}
260
+ onKeyDown={(e) => {
261
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
262
+ e.preventDefault();
263
+ sendAnswer(draft);
264
+ }
265
+ }}
266
+ />
267
+ <div className="flex items-center justify-between gap-2">
268
+ <span className="text-xs text-muted-foreground">⌘/Ctrl + Enter to send</span>
269
+ <Button
270
+ size={buttonSize}
271
+ onClick={() => sendAnswer(draft)}
272
+ disabled={loading || !draft.trim()}
273
+ >
274
+ <Send className="h-3.5 w-3.5" />
275
+ Send
276
+ </Button>
277
+ </div>
278
+ </div>
279
+ );
280
+ }
@@ -11,6 +11,7 @@ import {
11
11
  import { Calendar } from "lucide-react";
12
12
  import { toast } from "sonner";
13
13
  import { ScheduleForm, type ScheduleFormValues } from "./schedule-form";
14
+ import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
14
15
 
15
16
  interface ScheduleCreateSheetProps {
16
17
  projects: { id: string; name: string }[];
@@ -27,6 +28,7 @@ export function ScheduleCreateSheet({
27
28
  }: ScheduleCreateSheetProps) {
28
29
  const [loading, setLoading] = useState(false);
29
30
  const [error, setError] = useState<string | null>(null);
31
+ const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
30
32
 
31
33
  async function handleSubmit(values: ScheduleFormValues) {
32
34
  setLoading(true);
@@ -58,10 +60,15 @@ export function ScheduleCreateSheet({
58
60
  });
59
61
 
60
62
  if (res.ok) {
63
+ const { warnings: newWarnings } = await res.json();
61
64
  setError(null);
62
- onOpenChange(false);
65
+ setWarnings(newWarnings ?? []);
63
66
  toast.success("Schedule created");
64
67
  onCreated();
68
+ if (!newWarnings || newWarnings.length === 0) {
69
+ onOpenChange(false);
70
+ }
71
+ // Keep sheet open if there are warnings so the user sees the banner
65
72
  } else {
66
73
  const data = await res.json().catch(() => null);
67
74
  setError(data?.error ?? `Failed to create schedule (${res.status})`);
@@ -89,6 +96,17 @@ export function ScheduleCreateSheet({
89
96
 
90
97
  {/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
91
98
  <div className="px-6 pb-6 overflow-y-auto">
99
+ {warnings.length > 0 && (
100
+ <div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
101
+ <p className="font-medium text-amber-900">
102
+ Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
103
+ </p>
104
+ <p className="text-amber-800">
105
+ Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
106
+ Schedules will take turns; the last to run may be delayed.
107
+ </p>
108
+ </div>
109
+ )}
92
110
  <ScheduleForm
93
111
  projects={projects}
94
112
  onSubmit={handleSubmit}
@@ -16,6 +16,7 @@ import {
16
16
  type ScheduleFormValues,
17
17
  type ScheduleFormInitialValues,
18
18
  } from "./schedule-form";
19
+ import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
19
20
 
20
21
  interface ScheduleEditSheetProps {
21
22
  scheduleId: string | null;
@@ -49,6 +50,7 @@ export function ScheduleEditSheet({
49
50
  const [loaded, setLoaded] = useState(false);
50
51
  const [loading, setLoading] = useState(false);
51
52
  const [error, setError] = useState<string | null>(null);
53
+ const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
52
54
 
53
55
  const fetchSchedule = useCallback(async () => {
54
56
  if (!scheduleId) return;
@@ -63,6 +65,7 @@ export function ScheduleEditSheet({
63
65
  setSchedule(null);
64
66
  setLoaded(false);
65
67
  setError(null);
68
+ setWarnings([]);
66
69
  return;
67
70
  }
68
71
  fetchSchedule();
@@ -102,9 +105,14 @@ export function ScheduleEditSheet({
102
105
  });
103
106
 
104
107
  if (res.ok) {
108
+ const { warnings: newWarnings } = await res.json();
109
+ setWarnings(newWarnings ?? []);
105
110
  toast.success("Schedule updated");
106
- onOpenChange(false);
107
111
  onUpdated();
112
+ if (!newWarnings || newWarnings.length === 0) {
113
+ onOpenChange(false);
114
+ }
115
+ // Keep sheet open if there are warnings so the user sees the banner
108
116
  } else {
109
117
  const data = await res.json().catch(() => null);
110
118
  setError(data?.error ?? `Failed to update schedule (${res.status})`);
@@ -131,6 +139,17 @@ export function ScheduleEditSheet({
131
139
 
132
140
  {/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
133
141
  <div className="px-6 pb-6 overflow-y-auto">
142
+ {warnings.length > 0 && (
143
+ <div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
144
+ <p className="font-medium text-amber-900">
145
+ Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
146
+ </p>
147
+ <p className="text-amber-800">
148
+ Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
149
+ Schedules will take turns; the last to run may be delayed.
150
+ </p>
151
+ </div>
152
+ )}
134
153
  {!loaded ? (
135
154
  <div className="space-y-4">
136
155
  <Skeleton className="h-8 w-full" />
@@ -63,6 +63,7 @@ export interface ScheduleFormValues {
63
63
  activeTimezone: string;
64
64
  heartbeatBudgetPerDay: number | "";
65
65
  documentIds: string[];
66
+ maxTurns: number | null;
66
67
  }
67
68
 
68
69
  export interface ScheduleFormInitialValues {
@@ -76,6 +77,7 @@ export interface ScheduleFormInitialValues {
76
77
  recurs: boolean;
77
78
  maxFirings: number | null;
78
79
  expiresAt: string | null;
80
+ maxTurns?: number | null;
79
81
  }
80
82
 
81
83
  interface ScheduleFormProps {
@@ -140,6 +142,7 @@ export function ScheduleForm({
140
142
  const [expiresInHours, setExpiresInHours] = useState<number | "">(
141
143
  initialValues ? "" : ""
142
144
  );
145
+ const [maxTurns, setMaxTurns] = useState<number | null>(initialValues?.maxTurns ?? null);
143
146
  const [profiles, setProfiles] = useState<ProfileOption[]>([]);
144
147
 
145
148
  // NL schedule input state
@@ -279,6 +282,7 @@ export function ScheduleForm({
279
282
  activeTimezone,
280
283
  heartbeatBudgetPerDay,
281
284
  documentIds: [...selectedDocIds],
285
+ maxTurns,
282
286
  });
283
287
  }
284
288
 
@@ -507,6 +511,13 @@ export function ScheduleForm({
507
511
  ? "Extra instructions appended to the heartbeat evaluation"
508
512
  : "Instructions for each execution"}
509
513
  </p>
514
+ {scheduleType === "scheduled" && (
515
+ <p className="text-muted-foreground text-xs">
516
+ Note: writing &quot;MAX N turns&quot; in your prompt is a hint to the model,
517
+ not a runtime limit. Use <strong>Max agent steps</strong> below to enforce
518
+ a budget.
519
+ </p>
520
+ )}
510
521
  </div>
511
522
 
512
523
  {/* Natural Language Schedule Input */}
@@ -640,6 +651,26 @@ export function ScheduleForm({
640
651
  )}
641
652
  </div>
642
653
 
654
+ {/* Max agent steps */}
655
+ <div className="space-y-2">
656
+ <Label htmlFor="max-turns">Max agent steps per run</Label>
657
+ <Input
658
+ id="max-turns"
659
+ type="number"
660
+ min={1}
661
+ max={10000}
662
+ placeholder="Inherits global default"
663
+ value={maxTurns ?? ""}
664
+ onChange={(e) =>
665
+ setMaxTurns(e.target.value ? parseInt(e.target.value, 10) : null)
666
+ }
667
+ />
668
+ <p className="text-muted-foreground text-xs">
669
+ One step = one agent action (message, tool call, or sub-response). Most
670
+ schedules use 50–500 steps; heavy research runs 2,000+.
671
+ </p>
672
+ </div>
673
+
643
674
  {/* Project */}
644
675
  {projects.length > 0 && (
645
676
  <div className="space-y-2">
@@ -0,0 +1,149 @@
1
+ import { render, screen, waitFor } from "@testing-library/react";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
5
+
6
+ describe("providers and runtimes section", () => {
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ vi.stubGlobal("open", vi.fn());
10
+ vi.stubGlobal(
11
+ "fetch",
12
+ vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
13
+ const url = String(input);
14
+ const method = init?.method ?? "GET";
15
+
16
+ if (url === "/api/settings/providers" && method === "GET") {
17
+ return {
18
+ ok: true,
19
+ json: async () => ({
20
+ providers: {
21
+ anthropic: {
22
+ configured: false,
23
+ authMethod: "api_key",
24
+ hasKey: false,
25
+ apiKeySource: "unknown",
26
+ dualBilling: false,
27
+ runtimes: [
28
+ {
29
+ runtimeId: "claude-code",
30
+ label: "Claude Code",
31
+ providerId: "anthropic",
32
+ configured: false,
33
+ authMethod: "none",
34
+ apiKeySource: "unknown",
35
+ billingMode: "usage",
36
+ },
37
+ {
38
+ runtimeId: "anthropic-direct",
39
+ label: "Anthropic Direct API",
40
+ providerId: "anthropic",
41
+ configured: false,
42
+ authMethod: "none",
43
+ apiKeySource: "unknown",
44
+ billingMode: "usage",
45
+ },
46
+ ],
47
+ },
48
+ openai: {
49
+ configured: true,
50
+ authMethod: "oauth",
51
+ hasKey: true,
52
+ apiKeySource: "env",
53
+ oauthConnected: false,
54
+ account: null,
55
+ rateLimits: null,
56
+ login: {
57
+ phase: "idle",
58
+ loginId: null,
59
+ authUrl: null,
60
+ account: null,
61
+ rateLimits: null,
62
+ error: null,
63
+ startedAt: null,
64
+ updatedAt: new Date("2026-04-10T15:00:00.000Z").toISOString(),
65
+ },
66
+ dualBilling: false,
67
+ runtimes: [
68
+ {
69
+ runtimeId: "openai-codex-app-server",
70
+ label: "OpenAI Codex App Server",
71
+ providerId: "openai",
72
+ configured: false,
73
+ authMethod: "oauth",
74
+ apiKeySource: "oauth",
75
+ billingMode: "usage",
76
+ },
77
+ {
78
+ runtimeId: "openai-direct",
79
+ label: "OpenAI Direct API",
80
+ providerId: "openai",
81
+ configured: true,
82
+ authMethod: "api_key",
83
+ apiKeySource: "env",
84
+ billingMode: "usage",
85
+ },
86
+ ],
87
+ },
88
+ },
89
+ routingPreference: "quality",
90
+ configuredProviderCount: 1,
91
+ }),
92
+ };
93
+ }
94
+
95
+ if (url === "/api/settings/openai/login" && method === "POST") {
96
+ return {
97
+ ok: true,
98
+ json: async () => ({
99
+ phase: "pending",
100
+ loginId: "login-1",
101
+ authUrl: "https://auth.openai.com/log-in",
102
+ account: null,
103
+ rateLimits: null,
104
+ error: null,
105
+ startedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
106
+ updatedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
107
+ }),
108
+ };
109
+ }
110
+
111
+ throw new Error(`Unexpected fetch: ${url}`);
112
+ })
113
+ );
114
+ });
115
+
116
+ afterEach(() => {
117
+ vi.unstubAllGlobals();
118
+ });
119
+
120
+ it("shows partial OpenAI setup state when ChatGPT auth is selected but not connected", async () => {
121
+ render(<ProvidersAndRuntimesSection />);
122
+
123
+ await waitFor(() => {
124
+ expect(screen.getByText("Direct API only")).toBeInTheDocument();
125
+ });
126
+
127
+ expect(
128
+ screen.getByText("Codex App Server needs ChatGPT sign-in. OpenAI Direct API remains active.")
129
+ ).toBeInTheDocument();
130
+ expect(screen.getAllByText("Sign in with ChatGPT")).toHaveLength(2);
131
+ });
132
+
133
+ it("updates the provider row immediately when ChatGPT sign-in starts", async () => {
134
+ render(<ProvidersAndRuntimesSection />);
135
+
136
+ const signInButton = await screen.findByRole("button", {
137
+ name: "Sign in with ChatGPT",
138
+ });
139
+ signInButton.click();
140
+
141
+ await waitFor(() => {
142
+ expect(
143
+ screen.getByText("Waiting for ChatGPT sign-in. OpenAI Direct API remains active.")
144
+ ).toBeInTheDocument();
145
+ });
146
+
147
+ expect(screen.getAllByText("Waiting for ChatGPT sign-in")).toHaveLength(2);
148
+ });
149
+ });
@@ -4,13 +4,22 @@ import { Key, Shield } from "lucide-react";
4
4
  import { cn } from "@/lib/utils";
5
5
  import type { AuthMethod } from "@/lib/constants/settings";
6
6
 
7
+ interface AuthMethodOption {
8
+ id: AuthMethod;
9
+ icon: typeof Key;
10
+ title: string;
11
+ description: string;
12
+ }
13
+
7
14
  interface AuthMethodSelectorProps {
8
15
  value: AuthMethod;
9
16
  onChange: (method: AuthMethod) => void;
10
17
  recommendedMethod?: AuthMethod | null;
18
+ label?: string;
19
+ options?: AuthMethodOption[];
11
20
  }
12
21
 
13
- const methods = [
22
+ const defaultMethods = [
14
23
  {
15
24
  id: "api_key" as const,
16
25
  icon: Key,
@@ -25,12 +34,18 @@ const methods = [
25
34
  },
26
35
  ];
27
36
 
28
- export function AuthMethodSelector({ value, onChange, recommendedMethod }: AuthMethodSelectorProps) {
37
+ export function AuthMethodSelector({
38
+ value,
39
+ onChange,
40
+ recommendedMethod,
41
+ label = "Authentication Method",
42
+ options = defaultMethods,
43
+ }: AuthMethodSelectorProps) {
29
44
  return (
30
45
  <div className="space-y-2">
31
- <p className="text-sm font-medium">Authentication Method</p>
46
+ <p className="text-sm font-medium">{label}</p>
32
47
  <div className="grid grid-cols-2 gap-3">
33
- {methods.map((method) => {
48
+ {options.map((method) => {
34
49
  const Icon = method.icon;
35
50
  const isSelected = value === method.id;
36
51
  return (
@@ -1,21 +1,30 @@
1
1
  "use client";
2
2
 
3
3
  import { Badge } from "@/components/ui/badge";
4
- import type { ApiKeySource } from "@/lib/constants/settings";
4
+ import type { ApiKeySource, AuthMethod } from "@/lib/constants/settings";
5
5
 
6
6
  interface AuthStatusBadgeProps {
7
7
  connected: boolean;
8
8
  apiKeySource: ApiKeySource;
9
+ authMethod?: AuthMethod | "none";
10
+ oauthLabel?: string;
11
+ oauthConnected?: boolean;
9
12
  }
10
13
 
11
14
  const sourceLabels: Record<ApiKeySource, string> = {
12
15
  db: "Managed API Key",
13
16
  env: "Environment Variable",
14
- oauth: "OAuth (Claude Max/Pro)",
17
+ oauth: "OAuth",
15
18
  unknown: "Unknown",
16
19
  };
17
20
 
18
- export function AuthStatusBadge({ connected, apiKeySource }: AuthStatusBadgeProps) {
21
+ export function AuthStatusBadge({
22
+ connected,
23
+ apiKeySource,
24
+ authMethod,
25
+ oauthLabel = "OAuth",
26
+ oauthConnected,
27
+ }: AuthStatusBadgeProps) {
19
28
  if (!connected && apiKeySource === "unknown") {
20
29
  return (
21
30
  <Badge variant="outline" className="border-warning/50 text-warning">
@@ -32,6 +41,22 @@ export function AuthStatusBadge({ connected, apiKeySource }: AuthStatusBadgeProp
32
41
  );
33
42
  }
34
43
 
44
+ if (connected && authMethod === "oauth" && oauthConnected === false) {
45
+ return (
46
+ <Badge variant="outline" className="border-status-warning/50 text-status-warning">
47
+ Direct API only
48
+ </Badge>
49
+ );
50
+ }
51
+
52
+ if (connected && authMethod === "oauth" && (oauthConnected ?? true)) {
53
+ return (
54
+ <Badge variant="outline" className="border-success/50 text-success">
55
+ Connected via {oauthLabel}
56
+ </Badge>
57
+ );
58
+ }
59
+
35
60
  if (apiKeySource === "unknown") {
36
61
  return (
37
62
  <Badge variant="outline" className="border-success/50 text-success">