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
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { db } from "@/lib/db";
3
+ import { tasks, schedules, projects, settings, usageLedger } from "@/lib/db/schema";
4
+ import { eq } from "drizzle-orm";
5
+ import { randomUUID } from "crypto";
6
+ import { NextRequest } from "next/server";
7
+ import { POST } from "../[id]/execute/route";
8
+
9
+ vi.mock("@/lib/agents/runtime", () => ({
10
+ executeTaskWithRuntime: vi.fn().mockResolvedValue(undefined),
11
+ }));
12
+
13
+ function req(url: string): NextRequest {
14
+ return new NextRequest(new URL(url, "http://localhost"));
15
+ }
16
+
17
+ function seedSchedule(): string {
18
+ const pid = randomUUID();
19
+ const sid = randomUUID();
20
+ const now = new Date();
21
+ db.insert(projects)
22
+ .values({ id: pid, name: "p", status: "active", createdAt: now, updatedAt: now })
23
+ .run();
24
+ db.insert(schedules)
25
+ .values({
26
+ id: sid,
27
+ projectId: pid,
28
+ name: "manual",
29
+ prompt: "test",
30
+ cronExpression: "0 0 * * *",
31
+ status: "active",
32
+ type: "scheduled",
33
+ firingCount: 0,
34
+ suppressionCount: 0,
35
+ heartbeatSpentToday: 0,
36
+ failureStreak: 0,
37
+ turnBudgetBreachStreak: 0,
38
+ createdAt: now,
39
+ updatedAt: now,
40
+ })
41
+ .run();
42
+ return sid;
43
+ }
44
+
45
+ describe("POST /api/schedules/:id/execute", () => {
46
+ beforeEach(() => {
47
+ db.delete(usageLedger).run();
48
+ db.delete(tasks).run();
49
+ db.delete(schedules).run();
50
+ db.delete(projects).run();
51
+ db.delete(settings).where(eq(settings.key, "schedule.maxConcurrent")).run();
52
+ db.insert(settings)
53
+ .values({ key: "schedule.maxConcurrent", value: "1", updatedAt: new Date() })
54
+ .run();
55
+ });
56
+
57
+ it("fires when capacity available, returns 200 with taskId", async () => {
58
+ const sid = seedSchedule();
59
+ const res = await POST(req(`/api/schedules/${sid}/execute`), {
60
+ params: Promise.resolve({ id: sid }),
61
+ });
62
+ expect(res.status).toBe(200);
63
+ const body = await res.json();
64
+ expect(body.taskId).toBeDefined();
65
+ });
66
+
67
+ it("returns 429 when cap is full", async () => {
68
+ const sid1 = seedSchedule();
69
+ const sid2 = seedSchedule();
70
+
71
+ const res1 = await POST(req(`/api/schedules/${sid1}/execute`), {
72
+ params: Promise.resolve({ id: sid1 }),
73
+ });
74
+ expect(res1.status).toBe(200);
75
+
76
+ const res2 = await POST(req(`/api/schedules/${sid2}/execute`), {
77
+ params: Promise.resolve({ id: sid2 }),
78
+ });
79
+ expect(res2.status).toBe(429);
80
+ const body = await res2.json();
81
+ expect(body.error).toBe("capacity_full");
82
+ expect(body.slotEtaSec).toBeGreaterThanOrEqual(0);
83
+
84
+ const remaining = db.select().from(tasks).all();
85
+ expect(remaining.length).toBe(1); // only sid1's task remains; sid2's was cleaned up on refusal
86
+ });
87
+
88
+ it("bypasses the cap when ?force=true and writes audit-log entry", async () => {
89
+ const sid1 = seedSchedule();
90
+ const sid2 = seedSchedule();
91
+
92
+ await POST(req(`/api/schedules/${sid1}/execute`), {
93
+ params: Promise.resolve({ id: sid1 }),
94
+ });
95
+
96
+ const res2 = await POST(
97
+ req(`/api/schedules/${sid2}/execute?force=true`),
98
+ { params: Promise.resolve({ id: sid2 }) },
99
+ );
100
+ expect(res2.status).toBe(200);
101
+ const body2 = await res2.json();
102
+
103
+ const ledger = db
104
+ .select()
105
+ .from(usageLedger)
106
+ .where(eq(usageLedger.activityType, "manual_force_bypass"))
107
+ .all();
108
+ expect(ledger.length).toBe(1);
109
+ expect(ledger[0].taskId).toBe(body2.taskId);
110
+ });
111
+
112
+ it("returns 404 when the schedule does not exist", async () => {
113
+ const res = await POST(req("/api/schedules/nonexistent/execute"), {
114
+ params: Promise.resolve({ id: "nonexistent" }),
115
+ });
116
+ expect(res.status).toBe(404);
117
+ });
118
+ });
@@ -6,9 +6,7 @@ import { parseInterval, computeNextFireTime } from "@/lib/schedules/interval-par
6
6
  import { parseNaturalLanguage } from "@/lib/schedules/nlp-parser";
7
7
  import { resolveAgentRuntime } from "@/lib/agents/runtime/catalog";
8
8
  import { validateRuntimeProfileAssignment } from "@/lib/agents/profiles/assignment-validation";
9
- import { checkLimit, buildLimitErrorBody } from "@/lib/license/limit-check";
10
- import { getActiveScheduleCount } from "@/lib/license/limit-queries";
11
- import { createTierLimitNotification } from "@/lib/license/notifications";
9
+ import { checkCollision } from "@/lib/schedules/collision-check";
12
10
 
13
11
  export async function GET() {
14
12
  const result = await db
@@ -130,14 +128,6 @@ export async function POST(req: NextRequest) {
130
128
  return NextResponse.json({ error: compatibilityError }, { status: 400 });
131
129
  }
132
130
 
133
- // Tier limit check — active schedule cap
134
- const activeCount = getActiveScheduleCount();
135
- const limitResult = checkLimit("activeSchedules", activeCount);
136
- if (!limitResult.allowed) {
137
- createTierLimitNotification("activeSchedules", activeCount, limitResult.limit).catch(() => {});
138
- return NextResponse.json(buildLimitErrorBody("activeSchedules", limitResult), { status: 402 });
139
- }
140
-
141
131
  const id = crypto.randomUUID();
142
132
  const now = new Date();
143
133
  const nextFireAt = computeNextFireTime(cronExpression, now);
@@ -201,5 +191,6 @@ export async function POST(req: NextRequest) {
201
191
  .from(schedules)
202
192
  .where(eq(schedules.id, id));
203
193
 
204
- return NextResponse.json(created, { status: 201 });
194
+ const warnings = checkCollision(cronExpression, 0, projectId ?? null, null);
195
+ return NextResponse.json({ schedule: created, warnings }, { status: 201 });
205
196
  }
@@ -0,0 +1,94 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { z } from "zod";
4
+
5
+ /**
6
+ * GET/PUT `/api/settings/chat/pins` — per-user pinned entities for chat
7
+ * mention popover. Storage is a single JSON blob under the
8
+ * `chat.pinnedEntries` key in the `settings` key-value table.
9
+ *
10
+ * Design notes:
11
+ * - No server-side validation that `entityId` exists in its table.
12
+ * Pins are weakly referenced; popover filter just won't match if the
13
+ * entity has been deleted. Cheaper than maintaining referential integrity
14
+ * via cascading deletes, and stale pins can be removed by the user on next
15
+ * un-pin. Trade-off accepted.
16
+ * - PUT replaces the entire list (client is source of truth). Read-modify-
17
+ * write happens on the client to avoid concurrent-mutation issues, which
18
+ * for single-user local usage is a non-issue.
19
+ */
20
+
21
+ const SETTINGS_KEY = "chat.pinnedEntries";
22
+
23
+ // Entity types recognized by entities/search — mirrored here to constrain
24
+ // what can be pinned. Add new types as they become available.
25
+ const ENTITY_TYPES = [
26
+ "task",
27
+ "project",
28
+ "workflow",
29
+ "document",
30
+ "schedule",
31
+ "table",
32
+ "profile",
33
+ ] as const;
34
+
35
+ // We denormalize `label`, `description`, and `status` into the pin record
36
+ // so the Pinned group renders standalone, independent of whether the item
37
+ // appears in the current `entities/search` response window (top-20 per type).
38
+ // Trade-off: labels may go stale if the underlying entity is renamed.
39
+ // Acceptable for a UX affordance — selecting the pin still uses the canonical
40
+ // id, and the user can un-pin/re-pin to refresh. Mitigation is lazy refresh
41
+ // on next popover open (future enhancement).
42
+ const PinnedEntrySchema = z.object({
43
+ id: z.string().min(1),
44
+ type: z.enum(ENTITY_TYPES),
45
+ label: z.string().min(1),
46
+ description: z.string().optional(),
47
+ status: z.string().optional(),
48
+ pinnedAt: z.string(), // ISO 8601
49
+ });
50
+
51
+ const PinsPayloadSchema = z.object({
52
+ pins: z.array(PinnedEntrySchema),
53
+ });
54
+
55
+ export type PinnedEntry = z.infer<typeof PinnedEntrySchema>;
56
+
57
+ export async function GET() {
58
+ const raw = await getSetting(SETTINGS_KEY);
59
+ if (!raw) return NextResponse.json({ pins: [] });
60
+ try {
61
+ const parsed = PinsPayloadSchema.parse(JSON.parse(raw));
62
+ return NextResponse.json(parsed);
63
+ } catch {
64
+ // Malformed stored value (manual edit, version mismatch) — recover by
65
+ // returning an empty list rather than erroring. The user can re-pin.
66
+ return NextResponse.json({ pins: [] });
67
+ }
68
+ }
69
+
70
+ export async function PUT(req: NextRequest) {
71
+ let body: unknown;
72
+ try {
73
+ body = await req.json();
74
+ } catch {
75
+ return NextResponse.json({ error: "invalid JSON body" }, { status: 400 });
76
+ }
77
+
78
+ const result = PinsPayloadSchema.safeParse(body);
79
+ if (!result.success) {
80
+ return NextResponse.json(
81
+ { error: "invalid pins payload", issues: result.error.issues },
82
+ { status: 400 }
83
+ );
84
+ }
85
+
86
+ // De-dup by id — client may send the same pin twice on rapid clicks.
87
+ // Last write wins for pinnedAt.
88
+ const byId = new Map<string, PinnedEntry>();
89
+ for (const pin of result.data.pins) byId.set(pin.id, pin);
90
+ const deduped = Array.from(byId.values());
91
+
92
+ await setSetting(SETTINGS_KEY, JSON.stringify({ pins: deduped }));
93
+ return NextResponse.json({ pins: deduped });
94
+ }
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+
3
+ vi.mock("@/lib/settings/helpers", () => {
4
+ const store = new Map<string, string>();
5
+ return {
6
+ getSetting: vi.fn(async (k: string) => store.get(k) ?? null),
7
+ setSetting: vi.fn(async (k: string, v: string) => {
8
+ store.set(k, v);
9
+ }),
10
+ __store: store,
11
+ };
12
+ });
13
+
14
+ import * as helpers from "@/lib/settings/helpers";
15
+ import { GET, PUT } from "../route";
16
+ import { NextRequest } from "next/server";
17
+
18
+ beforeEach(() => {
19
+ // Reset the in-memory store between tests
20
+ (helpers as unknown as { __store: Map<string, string> }).__store.clear();
21
+ });
22
+
23
+ describe("saved-searches route", () => {
24
+ it("GET with no stored value returns empty list", async () => {
25
+ const res = await GET();
26
+ const body = await res.json();
27
+ expect(body).toEqual({ searches: [] });
28
+ });
29
+
30
+ it("PUT then GET round-trips a valid payload", async () => {
31
+ const payload = {
32
+ searches: [
33
+ {
34
+ id: "a",
35
+ surface: "task" as const,
36
+ label: "Blocked",
37
+ filterInput: "#status:blocked",
38
+ createdAt: "2026-04-14T00:00:00Z",
39
+ },
40
+ ],
41
+ };
42
+ const putRes = await PUT(
43
+ new NextRequest("http://x/api/settings/chat/saved-searches", {
44
+ method: "PUT",
45
+ body: JSON.stringify(payload),
46
+ })
47
+ );
48
+ expect(putRes.status).toBe(200);
49
+
50
+ const getBody = await (await GET()).json();
51
+ expect(getBody.searches).toHaveLength(1);
52
+ expect(getBody.searches[0].label).toBe("Blocked");
53
+ });
54
+
55
+ it("PUT dedupes by id — last write wins", async () => {
56
+ const req = new NextRequest("http://x", {
57
+ method: "PUT",
58
+ body: JSON.stringify({
59
+ searches: [
60
+ {
61
+ id: "a",
62
+ surface: "task",
63
+ label: "First",
64
+ filterInput: "#a:1",
65
+ createdAt: "2026-04-14T00:00:00Z",
66
+ },
67
+ {
68
+ id: "a",
69
+ surface: "task",
70
+ label: "Second (dup)",
71
+ filterInput: "#a:1",
72
+ createdAt: "2026-04-14T00:01:00Z",
73
+ },
74
+ ],
75
+ }),
76
+ });
77
+ const res = await PUT(req);
78
+ const body = await res.json();
79
+ expect(body.searches).toHaveLength(1);
80
+ expect(body.searches[0].label).toBe("Second (dup)");
81
+ });
82
+
83
+ it("PUT rejects invalid surface with 400", async () => {
84
+ const req = new NextRequest("http://x", {
85
+ method: "PUT",
86
+ body: JSON.stringify({
87
+ searches: [
88
+ {
89
+ id: "a",
90
+ surface: "bogus",
91
+ label: "x",
92
+ filterInput: "",
93
+ createdAt: "z",
94
+ },
95
+ ],
96
+ }),
97
+ });
98
+ const res = await PUT(req);
99
+ expect(res.status).toBe(400);
100
+ });
101
+
102
+ it("PUT rejects malformed JSON with 400", async () => {
103
+ const req = new NextRequest("http://x", {
104
+ method: "PUT",
105
+ body: "not json",
106
+ });
107
+ const res = await PUT(req);
108
+ expect(res.status).toBe(400);
109
+ });
110
+
111
+ it("GET recovers from malformed stored value", async () => {
112
+ (helpers as unknown as { __store: Map<string, string> }).__store.set(
113
+ "chat.savedSearches",
114
+ "not-json-at-all"
115
+ );
116
+ const body = await (await GET()).json();
117
+ expect(body).toEqual({ searches: [] });
118
+ });
119
+ });
@@ -0,0 +1,79 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { z } from "zod";
4
+
5
+ /**
6
+ * GET/PUT `/api/settings/chat/saved-searches` — per-user saved filter
7
+ * combinations for chat popovers and the ⌘K palette. Storage mirrors the
8
+ * pins route: a single JSON blob under `chat.savedSearches` in the
9
+ * settings key-value table.
10
+ *
11
+ * Design notes:
12
+ * - Full-list replacement on PUT. Client is source of truth — avoids
13
+ * concurrent-mutation bookkeeping for a single-user local product.
14
+ * - PUT dedupes by `id` with last-write-wins on rapid double-saves.
15
+ * - Malformed stored value (manual edit, schema drift) degrades to `[]`
16
+ * rather than erroring — the user can re-save.
17
+ */
18
+
19
+ const SETTINGS_KEY = "chat.savedSearches";
20
+
21
+ // Surfaces map to popover tabs + list routes. Extend as new popover
22
+ // surfaces are added (table, schedule, etc.).
23
+ const SURFACES = [
24
+ "task",
25
+ "project",
26
+ "workflow",
27
+ "document",
28
+ "skill",
29
+ "profile",
30
+ ] as const;
31
+
32
+ const SavedSearchSchema = z.object({
33
+ id: z.string().min(1),
34
+ surface: z.enum(SURFACES),
35
+ label: z.string().min(1).max(120),
36
+ filterInput: z.string().max(500),
37
+ createdAt: z.string(), // ISO 8601
38
+ });
39
+
40
+ const PayloadSchema = z.object({
41
+ searches: z.array(SavedSearchSchema),
42
+ });
43
+
44
+ export type SavedSearch = z.infer<typeof SavedSearchSchema>;
45
+
46
+ export async function GET() {
47
+ const raw = await getSetting(SETTINGS_KEY);
48
+ if (!raw) return NextResponse.json({ searches: [] });
49
+ try {
50
+ const parsed = PayloadSchema.parse(JSON.parse(raw));
51
+ return NextResponse.json(parsed);
52
+ } catch {
53
+ return NextResponse.json({ searches: [] });
54
+ }
55
+ }
56
+
57
+ export async function PUT(req: NextRequest) {
58
+ let body: unknown;
59
+ try {
60
+ body = await req.json();
61
+ } catch {
62
+ return NextResponse.json({ error: "invalid JSON body" }, { status: 400 });
63
+ }
64
+
65
+ const result = PayloadSchema.safeParse(body);
66
+ if (!result.success) {
67
+ return NextResponse.json(
68
+ { error: "invalid searches payload", issues: result.error.issues },
69
+ { status: 400 }
70
+ );
71
+ }
72
+
73
+ const byId = new Map<string, SavedSearch>();
74
+ for (const s of result.data.searches) byId.set(s.id, s);
75
+ const deduped = Array.from(byId.values());
76
+
77
+ await setSetting(SETTINGS_KEY, JSON.stringify({ searches: deduped }));
78
+ return NextResponse.json({ searches: deduped });
79
+ }
@@ -0,0 +1,26 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { SETTINGS_KEYS } from "@/lib/constants/settings";
4
+
5
+ export async function GET() {
6
+ const autoPromote = await getSetting(SETTINGS_KEYS.AUTO_PROMOTE_SKILLS);
7
+ return NextResponse.json({
8
+ autoPromoteSkills: autoPromote === "true",
9
+ });
10
+ }
11
+
12
+ export async function POST(req: NextRequest) {
13
+ const body = await req.json();
14
+
15
+ if (body.autoPromoteSkills !== undefined) {
16
+ await setSetting(
17
+ SETTINGS_KEYS.AUTO_PROMOTE_SKILLS,
18
+ body.autoPromoteSkills ? "true" : "false"
19
+ );
20
+ }
21
+
22
+ const autoPromote = await getSetting(SETTINGS_KEYS.AUTO_PROMOTE_SKILLS);
23
+ return NextResponse.json({
24
+ autoPromoteSkills: autoPromote === "true",
25
+ });
26
+ }
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from "next/server";
2
+ import {
3
+ cancelOpenAIChatGPTLogin,
4
+ getOpenAILoginState,
5
+ startOpenAIChatGPTLogin,
6
+ } from "@/lib/settings/openai-login-manager";
7
+ import { setOpenAIAuthSettings } from "@/lib/settings/openai-auth";
8
+
9
+ export async function GET() {
10
+ return NextResponse.json(getOpenAILoginState());
11
+ }
12
+
13
+ export async function POST() {
14
+ await setOpenAIAuthSettings({ method: "oauth" });
15
+ const state = await startOpenAIChatGPTLogin();
16
+ return NextResponse.json(state);
17
+ }
18
+
19
+ export async function DELETE() {
20
+ const state = await cancelOpenAIChatGPTLogin();
21
+ return NextResponse.json(state);
22
+ }
@@ -0,0 +1,7 @@
1
+ import { NextResponse } from "next/server";
2
+ import { logoutStagentCodexAuth } from "@/lib/agents/runtime/openai-codex-auth";
3
+
4
+ export async function POST() {
5
+ await logoutStagentCodexAuth();
6
+ return NextResponse.json({ success: true });
7
+ }
@@ -3,11 +3,31 @@ import {
3
3
  getOpenAIAuthSettings,
4
4
  setOpenAIAuthSettings,
5
5
  } from "@/lib/settings/openai-auth";
6
+ import { readStagentCodexAuthState } from "@/lib/agents/runtime/openai-codex-auth";
6
7
  import { updateOpenAISettingsSchema } from "@/lib/validators/settings";
7
8
 
8
9
  export async function GET() {
9
10
  const settings = await getOpenAIAuthSettings();
10
- return NextResponse.json(settings);
11
+ if (settings.method !== "oauth") {
12
+ return NextResponse.json(settings);
13
+ }
14
+
15
+ try {
16
+ const current = await readStagentCodexAuthState({ refreshToken: true });
17
+ return NextResponse.json({
18
+ ...settings,
19
+ oauthConnected: current.connected,
20
+ account: current.account,
21
+ rateLimits: current.rateLimits,
22
+ });
23
+ } catch {
24
+ return NextResponse.json({
25
+ ...settings,
26
+ oauthConnected: false,
27
+ account: null,
28
+ rateLimits: null,
29
+ });
30
+ }
11
31
  }
12
32
 
13
33
  export async function POST(req: NextRequest) {
@@ -1,17 +1,39 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { readStagentCodexAuthState } from "@/lib/agents/runtime/openai-codex-auth";
2
3
  import { getRuntimeSetupStates } from "@/lib/settings/runtime-setup";
3
4
  import { getRoutingPreference } from "@/lib/settings/routing";
4
5
  import { getAuthSettings } from "@/lib/settings/auth";
5
6
  import { getOpenAIAuthSettings } from "@/lib/settings/openai-auth";
7
+ import { getOpenAILoginState } from "@/lib/settings/openai-login-manager";
6
8
 
7
9
  export async function GET() {
8
- const [runtimeStates, routingPreference, anthropicAuth, openaiAuth] =
9
- await Promise.all([
10
- getRuntimeSetupStates(),
11
- getRoutingPreference(),
12
- getAuthSettings(),
13
- getOpenAIAuthSettings(),
14
- ]);
10
+ const [routingPreference, anthropicAuth, initialOpenaiAuth] = await Promise.all([
11
+ getRoutingPreference(),
12
+ getAuthSettings(),
13
+ getOpenAIAuthSettings(),
14
+ ]);
15
+
16
+ let openaiAuth = initialOpenaiAuth;
17
+ if (openaiAuth.method === "oauth") {
18
+ try {
19
+ const current = await readStagentCodexAuthState({ refreshToken: true });
20
+ openaiAuth = {
21
+ ...openaiAuth,
22
+ oauthConnected: current.connected,
23
+ account: current.account,
24
+ rateLimits: current.rateLimits,
25
+ };
26
+ } catch {
27
+ openaiAuth = {
28
+ ...openaiAuth,
29
+ oauthConnected: false,
30
+ account: null,
31
+ rateLimits: null,
32
+ };
33
+ }
34
+ }
35
+
36
+ const runtimeStates = await getRuntimeSetupStates();
15
37
 
16
38
  const anthropicConfigured =
17
39
  runtimeStates["claude-code"].configured ||
@@ -42,9 +64,14 @@ export async function GET() {
42
64
  },
43
65
  openai: {
44
66
  configured: openaiConfigured,
67
+ authMethod: openaiAuth.method,
45
68
  hasKey: openaiAuth.hasKey,
46
69
  apiKeySource: openaiAuth.apiKeySource,
47
- dualBilling: false,
70
+ oauthConnected: openaiAuth.oauthConnected,
71
+ account: openaiAuth.account,
72
+ rateLimits: openaiAuth.rateLimits,
73
+ login: getOpenAILoginState(),
74
+ dualBilling: openaiAuth.oauthConnected && openaiAuth.hasKey,
48
75
  runtimes: [
49
76
  runtimeStates["openai-codex-app-server"],
50
77
  runtimeStates["openai-direct"],