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,389 @@
1
+ import { mkdir, readFile, rm, writeFile } from "fs/promises";
2
+ import { dirname } from "path";
3
+ import { getLaunchCwd } from "@/lib/environment/workspace-context";
4
+ import {
5
+ getOpenAIApiKey,
6
+ getOpenAIAuthSettings,
7
+ updateOpenAIAuthStatus,
8
+ clearOpenAIOAuthStatus,
9
+ updateOpenAIOAuthStatus,
10
+ type OpenAIAccountInfo,
11
+ type OpenAIAuthMode,
12
+ type OpenAIRateLimitInfo,
13
+ type OpenAIRateLimitWindow,
14
+ } from "@/lib/settings/openai-auth";
15
+ import {
16
+ getStagentCodexAuthPath,
17
+ getStagentCodexConfigPath,
18
+ getStagentCodexDir,
19
+ } from "@/lib/utils/stagent-paths";
20
+ import { CodexAppServerClient } from "./codex-app-server-client";
21
+
22
+ const STAGENT_CODEX_CONFIG = `cli_auth_credentials_store = "file"
23
+ `;
24
+
25
+ interface AccountReadResult {
26
+ account?: {
27
+ type?: string;
28
+ email?: string | null;
29
+ planType?: string | null;
30
+ } | null;
31
+ requiresOpenaiAuth?: boolean;
32
+ }
33
+
34
+ interface RateLimitsReadResult {
35
+ rateLimits?: {
36
+ limitId?: string | null;
37
+ limitName?: string | null;
38
+ primary?: RateLimitWindowLike | null;
39
+ secondary?: RateLimitWindowLike | null;
40
+ } | null;
41
+ }
42
+
43
+ interface RateLimitWindowLike {
44
+ usedPercent?: unknown;
45
+ windowDurationMins?: unknown;
46
+ resetsAt?: unknown;
47
+ }
48
+
49
+ function parseRateLimitWindow(
50
+ value: RateLimitWindowLike | null | undefined
51
+ ): OpenAIRateLimitWindow | null {
52
+ if (!value || typeof value !== "object") return null;
53
+ return {
54
+ usedPercent:
55
+ typeof value.usedPercent === "number" ? value.usedPercent : null,
56
+ windowDurationMins:
57
+ typeof value.windowDurationMins === "number" ? value.windowDurationMins : null,
58
+ resetsAt: typeof value.resetsAt === "number" ? value.resetsAt : null,
59
+ };
60
+ }
61
+
62
+ function parseAccountInfo(
63
+ value: AccountReadResult["account"]
64
+ ): OpenAIAccountInfo | null {
65
+ if (!value?.type) return null;
66
+ if (
67
+ value.type !== "apiKey" &&
68
+ value.type !== "chatgpt" &&
69
+ value.type !== "chatgptAuthTokens"
70
+ ) {
71
+ return null;
72
+ }
73
+
74
+ return {
75
+ type: value.type,
76
+ email: value.email ?? null,
77
+ planType:
78
+ value.planType && value.planType.toLowerCase() !== "unknown"
79
+ ? value.planType
80
+ : null,
81
+ };
82
+ }
83
+
84
+ function extractPlanTypeFromError(error: unknown): string | null {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ const match = message.match(/"plan_type"\s*:\s*"([^"]+)"/);
87
+ return match?.[1] ?? null;
88
+ }
89
+
90
+ function decodeBase64Url(value: string): string | null {
91
+ try {
92
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
93
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
94
+ return Buffer.from(padded, "base64").toString("utf8");
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ export function extractPlanTypeFromIdToken(idToken: string): string | null {
101
+ const [, payload] = idToken.split(".");
102
+ if (!payload) return null;
103
+
104
+ const decoded = decodeBase64Url(payload);
105
+ if (!decoded) return null;
106
+
107
+ try {
108
+ const parsed = JSON.parse(decoded) as {
109
+ "https://api.openai.com/auth"?: {
110
+ chatgpt_plan_type?: string | null;
111
+ };
112
+ };
113
+ return parsed["https://api.openai.com/auth"]?.chatgpt_plan_type ?? null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ async function readStagentCodexPlanTypeFromAuthFile(): Promise<string | null> {
120
+ try {
121
+ const raw = await readFile(getStagentCodexAuthPath(), "utf8");
122
+ const parsed = JSON.parse(raw) as {
123
+ tokens?: {
124
+ id_token?: string | null;
125
+ } | null;
126
+ };
127
+ const idToken = parsed.tokens?.id_token;
128
+ return idToken ? extractPlanTypeFromIdToken(idToken) : null;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ async function ensureCodexHomeConfig() {
135
+ const codexDir = getStagentCodexDir();
136
+ const configPath = getStagentCodexConfigPath();
137
+
138
+ await mkdir(codexDir, { recursive: true });
139
+ await mkdir(dirname(configPath), { recursive: true });
140
+
141
+ let current = "";
142
+ try {
143
+ current = await readFile(configPath, "utf8");
144
+ } catch {
145
+ // File will be created below.
146
+ }
147
+
148
+ if (current.includes('cli_auth_credentials_store = "file"')) {
149
+ return;
150
+ }
151
+
152
+ const next = current.trim().length > 0
153
+ ? `${current.trimEnd()}\n\n${STAGENT_CODEX_CONFIG}`
154
+ : STAGENT_CODEX_CONFIG;
155
+ await writeFile(configPath, next, "utf8");
156
+ }
157
+
158
+ export async function buildCodexAuthEnv(
159
+ env?: Record<string, string | undefined>
160
+ ): Promise<Record<string, string | undefined>> {
161
+ await ensureCodexHomeConfig();
162
+
163
+ return {
164
+ ...env,
165
+ CODEX_HOME: getStagentCodexDir(),
166
+ OPENAI_API_KEY: env?.OPENAI_API_KEY,
167
+ };
168
+ }
169
+
170
+ export async function connectStagentCodexClient(options: {
171
+ cwd?: string;
172
+ env?: Record<string, string | undefined>;
173
+ } = {}) {
174
+ const env = await buildCodexAuthEnv(options.env);
175
+ return CodexAppServerClient.connect({
176
+ cwd: options.cwd ?? getLaunchCwd(),
177
+ env,
178
+ });
179
+ }
180
+
181
+ export async function initializeCodexClient(client: CodexAppServerClient) {
182
+ await client.request("initialize", {
183
+ clientInfo: {
184
+ name: "Stagent",
185
+ version: "0.1.1",
186
+ },
187
+ capabilities: null,
188
+ });
189
+ }
190
+
191
+ export async function readCodexAuthStateFromClient(
192
+ client: CodexAppServerClient,
193
+ options: { refreshToken?: boolean } = {}
194
+ ) {
195
+ const accountResult = (await client.request("account/read", {
196
+ refreshToken: options.refreshToken ?? false,
197
+ })) as AccountReadResult;
198
+
199
+ const account = parseAccountInfo(accountResult.account ?? null);
200
+ if (account?.type === "chatgpt" && !account.planType) {
201
+ account.planType = await readStagentCodexPlanTypeFromAuthFile();
202
+ }
203
+ let rateLimits: OpenAIRateLimitInfo | null = null;
204
+ if (account?.type === "chatgpt") {
205
+ try {
206
+ const rateLimitResult = (await client.request(
207
+ "account/rateLimits/read"
208
+ )) as RateLimitsReadResult;
209
+ const payload = rateLimitResult.rateLimits ?? null;
210
+ if (payload) {
211
+ rateLimits = {
212
+ limitId: payload.limitId ?? null,
213
+ limitName: payload.limitName ?? null,
214
+ primary: parseRateLimitWindow(payload.primary),
215
+ secondary: parseRateLimitWindow(payload.secondary),
216
+ };
217
+ }
218
+ } catch (error) {
219
+ if (!account.planType) {
220
+ account.planType = extractPlanTypeFromError(error);
221
+ }
222
+ rateLimits = null;
223
+ }
224
+ }
225
+
226
+ const authMode: OpenAIAuthMode =
227
+ account?.type === "apiKey"
228
+ ? "apikey"
229
+ : account?.type === "chatgpt"
230
+ ? "chatgpt"
231
+ : account?.type === "chatgptAuthTokens"
232
+ ? "chatgptAuthTokens"
233
+ : null;
234
+
235
+ return {
236
+ connected: account?.type === "chatgpt",
237
+ account,
238
+ rateLimits,
239
+ requiresOpenaiAuth: Boolean(accountResult.requiresOpenaiAuth),
240
+ authMode,
241
+ };
242
+ }
243
+
244
+ export type ResolvedOpenAICodexAuthContext =
245
+ | {
246
+ method: "api_key";
247
+ apiKeySource: "db" | "env" | "unknown";
248
+ connect: (cwd?: string) => Promise<CodexAppServerClient>;
249
+ }
250
+ | {
251
+ method: "oauth";
252
+ apiKeySource: "oauth";
253
+ connect: (cwd?: string) => Promise<CodexAppServerClient>;
254
+ };
255
+
256
+ export async function resolveOpenAICodexAuthContext(): Promise<ResolvedOpenAICodexAuthContext> {
257
+ const settings = await getOpenAIAuthSettings();
258
+
259
+ if (settings.method === "oauth") {
260
+ if (!settings.oauthConnected) {
261
+ try {
262
+ const state = await readStagentCodexAuthState({ refreshToken: false });
263
+ if (!state.connected) {
264
+ throw new Error("OpenAI ChatGPT sign-in is not configured.");
265
+ }
266
+ } catch {
267
+ throw new Error(
268
+ "OpenAI ChatGPT sign-in is not configured. Sign in from Settings > Providers & Runtimes."
269
+ );
270
+ }
271
+ }
272
+
273
+ return {
274
+ method: "oauth",
275
+ apiKeySource: "oauth",
276
+ connect: (cwd?: string) =>
277
+ connectStagentCodexClient({
278
+ cwd,
279
+ env: { OPENAI_API_KEY: undefined },
280
+ }),
281
+ };
282
+ }
283
+
284
+ const { apiKey, source } = await getOpenAIApiKey();
285
+ if (!apiKey) {
286
+ throw new Error("OpenAI API key is not configured");
287
+ }
288
+
289
+ return {
290
+ method: "api_key",
291
+ apiKeySource: source,
292
+ connect: (cwd?: string) =>
293
+ connectStagentCodexClient({
294
+ cwd,
295
+ env: { OPENAI_API_KEY: apiKey },
296
+ }),
297
+ };
298
+ }
299
+
300
+ export async function ensureOpenAICodexClientAuthenticated(
301
+ client: CodexAppServerClient,
302
+ auth: ResolvedOpenAICodexAuthContext
303
+ ) {
304
+ await initializeCodexClient(client);
305
+
306
+ if (auth.method === "api_key") {
307
+ const { apiKey } = await getOpenAIApiKey();
308
+ if (!apiKey) {
309
+ throw new Error("OpenAI API key is not configured");
310
+ }
311
+ await client.request("account/login/start", {
312
+ type: "apiKey",
313
+ apiKey,
314
+ });
315
+ await updateOpenAIAuthStatus(auth.apiKeySource);
316
+ return;
317
+ }
318
+
319
+ const state = await readCodexAuthStateFromClient(client, {
320
+ refreshToken: true,
321
+ });
322
+ if (!state.connected || state.account?.type !== "chatgpt") {
323
+ throw new Error(
324
+ "OpenAI ChatGPT sign-in is not configured. Sign in from Settings > Providers & Runtimes."
325
+ );
326
+ }
327
+ await updateOpenAIOAuthStatus({
328
+ connected: true,
329
+ account: state.account,
330
+ authMode: state.authMode,
331
+ rateLimits: state.rateLimits,
332
+ });
333
+ }
334
+
335
+ export async function readStagentCodexAuthState(options: {
336
+ refreshToken?: boolean;
337
+ cwd?: string;
338
+ } = {}) {
339
+ let client: CodexAppServerClient | null = null;
340
+
341
+ try {
342
+ client = await connectStagentCodexClient({ cwd: options.cwd });
343
+ await initializeCodexClient(client);
344
+
345
+ const state = await readCodexAuthStateFromClient(client, {
346
+ refreshToken: options.refreshToken,
347
+ });
348
+
349
+ await updateOpenAIOAuthStatus({
350
+ connected: state.connected,
351
+ account: state.account,
352
+ authMode: state.authMode,
353
+ rateLimits: state.rateLimits,
354
+ });
355
+
356
+ return state;
357
+ } catch (error) {
358
+ await clearOpenAIOAuthStatus();
359
+ throw error;
360
+ } finally {
361
+ if (client) {
362
+ await client.close();
363
+ }
364
+ }
365
+ }
366
+
367
+ export async function logoutStagentCodexAuth() {
368
+ let client: CodexAppServerClient | null = null;
369
+
370
+ try {
371
+ client = await connectStagentCodexClient();
372
+ await initializeCodexClient(client);
373
+ await client.request("account/logout");
374
+ } catch {
375
+ // Even if app-server logout fails, clear the isolated credential cache below.
376
+ } finally {
377
+ if (client) {
378
+ await client.close();
379
+ }
380
+ }
381
+
382
+ try {
383
+ await rm(getStagentCodexAuthPath(), { force: true });
384
+ } catch {
385
+ // Ignore cleanup failures.
386
+ }
387
+
388
+ await clearOpenAIOAuthStatus();
389
+ }
@@ -14,14 +14,21 @@ import {
14
14
  prepareTaskOutputDirectory,
15
15
  scanTaskOutputDocuments,
16
16
  } from "@/lib/documents/output-scanner";
17
- import {
18
- getOpenAIApiKey,
19
- updateOpenAIAuthStatus,
20
- } from "@/lib/settings/openai-auth";
21
17
  import { isToolAllowed } from "@/lib/settings/permissions";
22
18
  import { getRuntimeCatalogEntry } from "./catalog";
23
19
  import { getLaunchCwd } from "@/lib/environment/workspace-context";
24
20
  import { CodexAppServerClient } from "./codex-app-server-client";
21
+ import {
22
+ ensureOpenAICodexClientAuthenticated,
23
+ readCodexAuthStateFromClient,
24
+ resolveOpenAICodexAuthContext,
25
+ } from "./openai-codex-auth";
26
+ import {
27
+ classifyTaskFailureReason,
28
+ RetryableRuntimeLaunchError,
29
+ toRetryableRuntimeLaunchError,
30
+ type RuntimeLaunchProgress,
31
+ } from "./launch-failure";
25
32
  import type {
26
33
  AgentRuntimeAdapter,
27
34
  RuntimeConnectionResult,
@@ -227,6 +234,14 @@ async function finalizeTaskUsage(
227
234
  startedAt: state.startedAt,
228
235
  finishedAt: new Date(),
229
236
  });
237
+
238
+ await db
239
+ .update(tasks)
240
+ .set({
241
+ effectiveModelId: state.modelId ?? null,
242
+ updatedAt: new Date(),
243
+ })
244
+ .where(eq(tasks.id, state.taskId));
230
245
  }
231
246
 
232
247
  function buildTurnInput(prompt: string) {
@@ -372,6 +387,7 @@ async function markTaskFailed(taskId: string, title: string, message: string) {
372
387
  .set({
373
388
  status: "failed",
374
389
  result: message,
390
+ failureReason: classifyTaskFailureReason(new Error(message)),
375
391
  updatedAt: new Date(),
376
392
  })
377
393
  .where(eq(tasks.id, taskId));
@@ -585,43 +601,19 @@ async function handleServerRequest(
585
601
  }
586
602
  }
587
603
 
588
- async function initializeOpenAIClient(
589
- client: CodexAppServerClient,
590
- apiKey: string
591
- ) {
592
- await client.request("initialize", {
593
- clientInfo: {
594
- name: "Stagent",
595
- version: "0.1.1",
596
- },
597
- capabilities: null,
598
- });
599
-
600
- await client.request("account/login/start", {
601
- type: "apiKey",
602
- apiKey,
603
- });
604
- }
605
-
606
604
  async function runAssistTurn({
607
605
  prompt,
608
606
  developerInstructions,
609
607
  cwd,
610
608
  }: AssistTurnOptions): Promise<{ text: string; usage: UsageSnapshot }> {
611
- const { apiKey, source } = await getOpenAIApiKey();
612
- if (!apiKey) {
613
- throw new Error("OpenAI API key is not configured");
614
- }
609
+ const auth = await resolveOpenAICodexAuthContext();
615
610
 
616
611
  let client: CodexAppServerClient | null = null;
617
612
  let text = "";
618
613
  let usage: UsageSnapshot = {};
619
614
 
620
615
  try {
621
- client = await CodexAppServerClient.connect({
622
- cwd,
623
- env: { OPENAI_API_KEY: apiKey },
624
- });
616
+ client = await auth.connect(cwd);
625
617
 
626
618
  client.onNotification = (notification: JsonRpcLikeNotification) => {
627
619
  if (notification.method !== "item/agentMessage/delta") return;
@@ -631,8 +623,7 @@ async function runAssistTurn({
631
623
  }
632
624
  };
633
625
 
634
- await initializeOpenAIClient(client, apiKey);
635
- await updateOpenAIAuthStatus(source);
626
+ await ensureOpenAICodexClientAuthenticated(client, auth);
636
627
 
637
628
  const threadResponse = (await client.request("thread/start", {
638
629
  cwd,
@@ -706,11 +697,7 @@ async function executeOpenAICodexTask(
706
697
  ): Promise<void> {
707
698
  const { task, profileId, instructions, prompt, cwd } =
708
699
  await resolveTaskExecutionContext(taskId, options);
709
- const { apiKey, source } = await getOpenAIApiKey();
710
-
711
- if (!apiKey) {
712
- throw new Error("OpenAI API key is not configured");
713
- }
700
+ const auth = await resolveOpenAICodexAuthContext();
714
701
 
715
702
  const abortController = new AbortController();
716
703
  let client: CodexAppServerClient | null = null;
@@ -718,6 +705,7 @@ async function executeOpenAICodexTask(
718
705
  let turnId: string | null = null;
719
706
  let agentOutput = "";
720
707
  let settled = false;
708
+ const launchProgress: RuntimeLaunchProgress = {};
721
709
  let resolveCompletion: (() => void) | null = null;
722
710
  let rejectCompletion: ((error: Error) => void) | null = null;
723
711
  const usageState = createTaskUsageState(task, Boolean(task.sessionId));
@@ -729,12 +717,21 @@ async function executeOpenAICodexTask(
729
717
  };
730
718
 
731
719
  try {
732
- client = await CodexAppServerClient.connect({
733
- cwd,
734
- env: { OPENAI_API_KEY: apiKey },
735
- });
720
+ client = await auth.connect(cwd);
736
721
 
737
722
  client.onProcessError = (error) => {
723
+ const retryableLaunchError =
724
+ !options.resume
725
+ ? toRetryableRuntimeLaunchError({
726
+ runtimeId: "openai-codex-app-server",
727
+ error,
728
+ progress: launchProgress,
729
+ })
730
+ : null;
731
+ if (retryableLaunchError) {
732
+ rejectCompletion?.(retryableLaunchError);
733
+ return;
734
+ }
738
735
  if (settled) return;
739
736
  void settle(async () => {
740
737
  await markTaskFailed(taskId, task.title, error.message);
@@ -775,6 +772,7 @@ async function executeOpenAICodexTask(
775
772
 
776
773
  case "turn/started": {
777
774
  turnId = extractTurnId(params);
775
+ launchProgress.hasTurnStarted = true;
778
776
  setExecution(taskId, {
779
777
  abortController,
780
778
  sessionId: threadId,
@@ -813,6 +811,7 @@ async function executeOpenAICodexTask(
813
811
  }
814
812
 
815
813
  case "item/commandExecution/outputDelta": {
814
+ launchProgress.hasToolUse = true;
816
815
  await insertLog(taskId, "command_output_delta", {
817
816
  threadId,
818
817
  turnId,
@@ -834,6 +833,7 @@ async function executeOpenAICodexTask(
834
833
 
835
834
  case "turn/completed": {
836
835
  const { status, errorMessage } = extractTurnStatus(params);
836
+ launchProgress.hasResult = true;
837
837
 
838
838
  if (status === "completed") {
839
839
  const finalResult =
@@ -879,8 +879,7 @@ async function executeOpenAICodexTask(
879
879
  };
880
880
  });
881
881
 
882
- await initializeOpenAIClient(client, apiKey);
883
- await updateOpenAIAuthStatus(source);
882
+ await ensureOpenAICodexClientAuthenticated(client, auth);
884
883
 
885
884
  if (threadId) {
886
885
  await client.request("thread/resume", {
@@ -949,6 +948,10 @@ async function executeOpenAICodexTask(
949
948
  return;
950
949
  }
951
950
 
951
+ if (error instanceof RetryableRuntimeLaunchError) {
952
+ throw error;
953
+ }
954
+
952
955
  const message = error instanceof Error ? error.message : String(error);
953
956
  await settle(async () => {
954
957
  await markTaskFailed(taskId, task.title, message);
@@ -1051,29 +1054,30 @@ async function runOpenAITaskAssist(
1051
1054
  }
1052
1055
 
1053
1056
  async function testOpenAIConnection(): Promise<RuntimeConnectionResult> {
1054
- const { apiKey, source } = await getOpenAIApiKey();
1055
- if (!apiKey) {
1056
- return {
1057
- connected: false,
1058
- apiKeySource: "unknown",
1059
- error: "OpenAI API key is not configured",
1060
- };
1061
- }
1062
-
1063
1057
  let client: CodexAppServerClient | null = null;
1064
1058
  try {
1065
- client = await CodexAppServerClient.connect({
1066
- cwd: getLaunchCwd(),
1067
- env: { OPENAI_API_KEY: apiKey },
1059
+ const auth = await resolveOpenAICodexAuthContext();
1060
+ client = await auth.connect(getLaunchCwd());
1061
+ await ensureOpenAICodexClientAuthenticated(client, auth);
1062
+ const accountState = await readCodexAuthStateFromClient(client, {
1063
+ refreshToken: auth.apiKeySource === "oauth",
1068
1064
  });
1069
- await initializeOpenAIClient(client, apiKey);
1070
- await client.request("account/read", { refreshToken: false });
1071
- await updateOpenAIAuthStatus(source);
1072
- return { connected: true, apiKeySource: source };
1065
+
1066
+ return {
1067
+ connected: auth.apiKeySource === "oauth" ? accountState.connected : true,
1068
+ apiKeySource: auth.apiKeySource,
1069
+ account: accountState.account,
1070
+ rateLimits: accountState.rateLimits,
1071
+ authMode: accountState.authMode,
1072
+ };
1073
1073
  } catch (error) {
1074
1074
  return {
1075
1075
  connected: false,
1076
- apiKeySource: source,
1076
+ apiKeySource:
1077
+ error instanceof Error &&
1078
+ error.message.includes("ChatGPT sign-in is not configured")
1079
+ ? "oauth"
1080
+ : "unknown",
1077
1081
  error: error instanceof Error ? error.message : String(error),
1078
1082
  };
1079
1083
  } finally {
@@ -396,6 +396,14 @@ async function executeOpenAIDirectTask(taskId: string, isResume = false): Promis
396
396
  startedAt: usageState.startedAt,
397
397
  finishedAt: new Date(),
398
398
  });
399
+
400
+ await db
401
+ .update(tasks)
402
+ .set({
403
+ effectiveModelId: result.totalUsage.modelId ?? modelId,
404
+ updatedAt: new Date(),
405
+ })
406
+ .where(eq(tasks.id, taskId));
399
407
  } catch (err) {
400
408
  if (!abortController.signal.aborted) {
401
409
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -3,10 +3,18 @@ import type { ProfileTestReport } from "@/lib/agents/profiles/test-types";
3
3
  import type { RuntimeCapabilities, RuntimeCatalogEntry } from "./catalog";
4
4
  import type { TaskAssistResponse } from "./task-assist-types";
5
5
  import type { ProfileAssistRequest, ProfileAssistResponse } from "./profile-assist-types";
6
+ import type {
7
+ OpenAIAccountInfo,
8
+ OpenAIAuthMode,
9
+ OpenAIRateLimitInfo,
10
+ } from "@/lib/settings/openai-auth";
6
11
 
7
12
  export interface RuntimeConnectionResult {
8
13
  connected: boolean;
9
14
  apiKeySource?: ApiKeySource;
15
+ account?: OpenAIAccountInfo | null;
16
+ rateLimits?: OpenAIRateLimitInfo | null;
17
+ authMode?: OpenAIAuthMode;
10
18
  error?: string;
11
19
  }
12
20