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,59 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import { workflows } from "@/lib/db/schema";
4
+ import { eq } from "drizzle-orm";
5
+ import { resumeWorkflow } from "@/lib/workflows/engine";
6
+
7
+ /**
8
+ * POST /api/workflows/[id]/resume
9
+ *
10
+ * Manually resume a workflow that was paused at a delay step. Called by the
11
+ * "Resume Now" button in WorkflowStatusView to skip the remaining delay. The
12
+ * scheduler tick also calls resumeWorkflow() directly (not through this route)
13
+ * when workflows.resume_at is reached.
14
+ *
15
+ * Response codes:
16
+ * 202 Accepted — resume dispatched (fire-and-forget)
17
+ * 404 Not Found — workflow does not exist
18
+ * 409 Conflict — workflow is not in paused state (already resumed, racing
19
+ * scheduler, or was never paused). resumeWorkflow handles
20
+ * this internally with its atomic status transition, so the
21
+ * conflict is reported here for correct UX feedback.
22
+ */
23
+ export async function POST(
24
+ _req: NextRequest,
25
+ { params }: { params: Promise<{ id: string }> },
26
+ ) {
27
+ const { id } = await params;
28
+
29
+ const [workflow] = await db
30
+ .select()
31
+ .from(workflows)
32
+ .where(eq(workflows.id, id));
33
+
34
+ if (!workflow) {
35
+ return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
36
+ }
37
+
38
+ if (workflow.status !== "paused") {
39
+ return NextResponse.json(
40
+ {
41
+ error: `Workflow is not paused (current status: ${workflow.status})`,
42
+ status: workflow.status,
43
+ },
44
+ { status: 409 },
45
+ );
46
+ }
47
+
48
+ // Fire-and-forget. resumeWorkflow performs its own atomic status check, so
49
+ // if the scheduler tick beats this request by microseconds, the DB UPDATE
50
+ // will match zero rows and resumeWorkflow returns silently without harm.
51
+ resumeWorkflow(id).catch((error) => {
52
+ console.error(`Workflow ${id} resume failed:`, error);
53
+ });
54
+
55
+ return NextResponse.json(
56
+ { status: "resuming", workflowId: id },
57
+ { status: 202 },
58
+ );
59
+ }
@@ -3,6 +3,11 @@ import { db } from "@/lib/db";
3
3
  import { workflows, tasks, documents } from "@/lib/db/schema";
4
4
  import { eq, and, inArray, count, desc, sql as drizzleSql } from "drizzle-orm";
5
5
  import { parseWorkflowState } from "@/lib/workflows/engine";
6
+ import type {
7
+ WorkflowStatusResponse,
8
+ NonLoopPattern,
9
+ StepWithState,
10
+ } from "@/lib/workflows/types";
6
11
 
7
12
  /** Collect output documents for workflow step tasks + input documents from parent task */
8
13
  async function getWorkflowDocuments(
@@ -97,15 +102,18 @@ export async function GET(
97
102
  const sourceTaskId: string | undefined = definition.sourceTaskId;
98
103
  const { stepDocuments, parentDocuments } = await getWorkflowDocuments(state, sourceTaskId);
99
104
 
100
- // Loop pattern returns loop-specific data instead of step states
105
+ // Loop pattern returns loop-specific data instead of step states.
106
+ // The `satisfies` annotation enforces the TDR-031 contract: the loop arm
107
+ // of WorkflowStatusResponse cannot emit `workflowState` or `resumeAt`, and
108
+ // its `steps` field is raw WorkflowStep[] (not StepWithState[]).
101
109
  if (definition.pattern === "loop") {
102
- return NextResponse.json({
110
+ const loopBody = {
103
111
  id: workflow.id,
104
112
  name: workflow.name,
105
113
  status: workflow.status,
106
114
  projectId: workflow.projectId,
107
115
  definition: workflow.definition,
108
- pattern: definition.pattern,
116
+ pattern: "loop" as const,
109
117
  loopConfig: definition.loopConfig,
110
118
  swarmConfig: definition.swarmConfig,
111
119
  loopState,
@@ -114,18 +122,23 @@ export async function GET(
114
122
  parentDocuments,
115
123
  runNumber: workflow.runNumber,
116
124
  runHistory,
117
- });
125
+ } satisfies WorkflowStatusResponse;
126
+ return NextResponse.json(loopBody);
118
127
  }
119
128
 
120
- return NextResponse.json({
129
+ // Non-loop arm: sequence, parallel, swarm, planner-executor, checkpoint all
130
+ // share the step-state rendering path. `satisfies` enforces that this branch
131
+ // cannot accidentally emit `loopState`, and that every step has `.state`.
132
+ const nonLoopBody = {
121
133
  id: workflow.id,
122
134
  name: workflow.name,
123
135
  status: workflow.status,
136
+ resumeAt: workflow.resumeAt ?? null,
124
137
  projectId: workflow.projectId,
125
138
  definition: workflow.definition,
126
- pattern: definition.pattern,
139
+ pattern: definition.pattern as NonLoopPattern,
127
140
  swarmConfig: definition.swarmConfig,
128
- steps: definition.steps.map((step, i) => ({
141
+ steps: definition.steps.map((step, i): StepWithState => ({
129
142
  ...step,
130
143
  state: state?.stepStates[i] ?? { stepId: step.id, status: "pending" },
131
144
  })),
@@ -134,5 +147,6 @@ export async function GET(
134
147
  parentDocuments,
135
148
  runNumber: workflow.runNumber,
136
149
  runHistory,
137
- });
150
+ } satisfies WorkflowStatusResponse;
151
+ return NextResponse.json(nonLoopBody);
138
152
  }
@@ -1,6 +1,8 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { getWorkspaceContext } from "@/lib/environment/workspace-context";
3
3
 
4
+ export const dynamic = "force-dynamic";
5
+
4
6
  export function GET() {
5
7
  const context = getWorkspaceContext();
6
8
  return NextResponse.json(context, {
@@ -0,0 +1,81 @@
1
+ import { NextResponse } from "next/server";
2
+ import { basename, join } from "path";
3
+ import { homedir } from "os";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import Database from "better-sqlite3";
6
+ import { getLaunchCwd } from "@/lib/environment/workspace-context";
7
+ import { isDevMode, isPrivateInstance } from "@/lib/instance/detect";
8
+ import { bootstrapStagentDatabase } from "@/lib/db/bootstrap";
9
+
10
+ export const dynamic = "force-dynamic";
11
+
12
+ /**
13
+ * POST /api/workspace/fix-data-dir
14
+ *
15
+ * Fixes a data-dir mismatch for domain clones by:
16
+ * 1. Deriving the correct STAGENT_DATA_DIR from the folder name
17
+ * 2. Writing it to .env.local (alongside STAGENT_CLOUD_DISABLED=true)
18
+ * 3. Creating the data dir + bootstrapping an empty database there
19
+ *
20
+ * Requires a dev server restart to take effect.
21
+ */
22
+ export async function POST() {
23
+ const cwd = getLaunchCwd();
24
+
25
+ // Guard: main repo doesn't need fixing
26
+ if (isDevMode(cwd)) {
27
+ return NextResponse.json(
28
+ { error: "Main dev repo does not need a data-dir fix" },
29
+ { status: 400 }
30
+ );
31
+ }
32
+
33
+ // Guard: already isolated
34
+ if (isPrivateInstance()) {
35
+ return NextResponse.json(
36
+ { error: "STAGENT_DATA_DIR is already set to a non-default path" },
37
+ { status: 400 }
38
+ );
39
+ }
40
+
41
+ const folderName = basename(cwd);
42
+ const home = homedir();
43
+ // stagent-wealth → ~/.stagent-wealth, stagent-growth → ~/.stagent-growth
44
+ const dataDir = join(home, `.${folderName}`);
45
+ const displayDataDir = `~/.${folderName}`;
46
+
47
+ // --- 1. Update .env.local ---
48
+ const envLocalPath = join(cwd, ".env.local");
49
+ let envContent = "";
50
+ if (existsSync(envLocalPath)) {
51
+ envContent = readFileSync(envLocalPath, "utf-8");
52
+ }
53
+
54
+ // Replace or append STAGENT_DATA_DIR
55
+ if (/^STAGENT_DATA_DIR=.*/m.test(envContent)) {
56
+ envContent = envContent.replace(
57
+ /^STAGENT_DATA_DIR=.*/m,
58
+ `STAGENT_DATA_DIR=${dataDir}`
59
+ );
60
+ } else {
61
+ envContent = envContent.trimEnd() + `\nSTAGENT_DATA_DIR=${dataDir}\n`;
62
+ }
63
+
64
+ writeFileSync(envLocalPath, envContent, "utf-8");
65
+
66
+ // --- 2. Create data dir + bootstrap DB ---
67
+ mkdirSync(dataDir, { recursive: true });
68
+ const dbPath = join(dataDir, "stagent.db");
69
+ const sqlite = new Database(dbPath);
70
+ sqlite.pragma("journal_mode = WAL");
71
+ sqlite.pragma("foreign_keys = ON");
72
+ bootstrapStagentDatabase(sqlite);
73
+ sqlite.close();
74
+
75
+ return NextResponse.json({
76
+ success: true,
77
+ dataDir: displayDataDir,
78
+ envLocalPath,
79
+ needsRestart: true,
80
+ });
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { listConversations } from "@/lib/data/chat";
2
2
  import { getPromptCategories } from "@/lib/chat/suggested-prompts";
3
+ import { reconcileStreamingMessages } from "@/lib/chat/reconcile";
3
4
  import { ChatShell } from "@/components/chat/chat-shell";
4
5
 
5
6
  export const dynamic = "force-dynamic";
@@ -10,6 +11,16 @@ export default async function ChatPage({
10
11
  searchParams: Promise<{ c?: string }>;
11
12
  }) {
12
13
  const params = await searchParams;
14
+
15
+ // Safety-net sweep: finalize any orphaned streaming rows from crashes,
16
+ // disconnects, or prior releases. Fire-and-forget-ish — we await so the
17
+ // UI never renders stale `streaming` placeholders, but errors are swallowed.
18
+ try {
19
+ await reconcileStreamingMessages();
20
+ } catch (err) {
21
+ console.error("[chat] reconcileStreamingMessages failed:", err);
22
+ }
23
+
13
24
  const [conversations, promptCategories] = await Promise.all([
14
25
  listConversations({ status: "active" }),
15
26
  getPromptCategories(),
@@ -1,3 +1,4 @@
1
+ import { Suspense } from "react";
1
2
  import { db } from "@/lib/db";
2
3
  import { documents, tasks, projects, workflows } from "@/lib/db/schema";
3
4
  import { desc, eq } from "drizzle-orm";
@@ -47,7 +48,9 @@ export default async function DocumentsPage() {
47
48
 
48
49
  return (
49
50
  <PageShell title="Documents">
50
- <DocumentBrowser initialDocuments={docs} projects={projectList} />
51
+ <Suspense fallback={null}>
52
+ <DocumentBrowser initialDocuments={docs} projects={projectList} />
53
+ </Suspense>
51
54
  </PageShell>
52
55
  );
53
56
  }
@@ -4,6 +4,10 @@ import { desc, eq, and, gte } from "drizzle-orm";
4
4
  import { InboxList } from "@/components/notifications/inbox-list";
5
5
  import { GovernanceStats } from "@/components/notifications/governance-stats";
6
6
  import { PageShell } from "@/components/shared/page-shell";
7
+ import {
8
+ buildDefaultNotificationVisibilityCondition,
9
+ filterDefaultVisibleNotifications,
10
+ } from "@/lib/notifications/visibility";
7
11
 
8
12
  export const dynamic = "force-dynamic";
9
13
 
@@ -16,6 +20,7 @@ export default async function InboxPage() {
16
20
  db
17
21
  .select()
18
22
  .from(notifications)
23
+ .where(buildDefaultNotificationVisibilityCondition())
19
24
  .orderBy(desc(notifications.createdAt))
20
25
  .limit(100),
21
26
  db
@@ -53,11 +58,13 @@ export default async function InboxPage() {
53
58
  ]);
54
59
 
55
60
  // Serialize Date objects for client component consumption
56
- const initialNotifications = rows.map((n) => ({
57
- ...n,
58
- createdAt: n.createdAt.toISOString(),
59
- respondedAt: n.respondedAt?.toISOString() ?? null,
60
- }));
61
+ const initialNotifications = filterDefaultVisibleNotifications(
62
+ rows.map((n) => ({
63
+ ...n,
64
+ createdAt: n.createdAt.toISOString(),
65
+ respondedAt: n.respondedAt?.toISOString() ?? null,
66
+ }))
67
+ );
61
68
 
62
69
  return (
63
70
  <PageShell
@@ -1,5 +1,5 @@
1
1
  import type { Metadata } from "next";
2
- import Script from "next/script";
2
+ import { cookies } from "next/headers";
3
3
  import { Inter, JetBrains_Mono } from "next/font/google";
4
4
  import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar";
5
5
  import { TooltipProvider } from "@/components/ui/tooltip";
@@ -8,6 +8,13 @@ import { CommandPalette } from "@/components/shared/command-palette";
8
8
  import { PendingApprovalHost } from "@/components/notifications/pending-approval-host";
9
9
  import { GlobalShortcuts } from "@/components/shared/global-shortcuts";
10
10
  import { Toaster } from "@/components/ui/sonner";
11
+ import { ChatSessionProvider } from "@/components/chat/chat-session-provider";
12
+ import {
13
+ DEFAULT_THEME,
14
+ THEME_COOKIE,
15
+ isResolvedTheme,
16
+ type ResolvedTheme,
17
+ } from "@/lib/theme";
11
18
  import "./globals.css";
12
19
 
13
20
  const inter = Inter({
@@ -55,22 +62,34 @@ const CRITICAL_THEME_CSS = `
55
62
  html { background: var(--background); font-size: 14px; }
56
63
  `.replace(/\s+/g, " ").trim();
57
64
 
58
- // Light-first: defaults to 'light' when no localStorage preference exists.
59
- // Static theme initialization script — no user input, safe from XSS.
60
- const THEME_INIT_SCRIPT = `(function(){try{var d=document.documentElement;var s=localStorage.getItem('stagent-theme');var t=s==='dark'||s==='light'?s:'light';d.classList.toggle('dark',t==='dark');d.dataset.theme=t;d.style.colorScheme=t;d.style.backgroundColor=t==='dark'?'oklch(0.14 0.02 250)':'oklch(0.985 0.004 250)';document.cookie='stagent-theme='+t+';path=/;max-age=31536000;SameSite=Lax';}catch(e){}})()`;
61
-
62
- export default function RootLayout({
65
+ export default async function RootLayout({
63
66
  children,
64
67
  }: {
65
68
  children: React.ReactNode;
66
69
  }) {
70
+ // Resolve theme server-side from the stagent-theme cookie. Every client-side
71
+ // theme toggle writes this cookie (see src/lib/theme.ts), so SSR stays in
72
+ // sync with the user's preference and there is no FOUC — and no pre-hydration
73
+ // <script> tag, which is what React 19 warns about.
74
+ const cookieStore = await cookies();
75
+ const cookieValue = cookieStore.get(THEME_COOKIE)?.value;
76
+ const theme: ResolvedTheme = isResolvedTheme(cookieValue)
77
+ ? cookieValue
78
+ : DEFAULT_THEME;
79
+ const htmlClass = theme === "dark" ? "dark" : undefined;
80
+ const htmlBackground =
81
+ theme === "dark" ? "oklch(0.14 0.02 250)" : "oklch(0.985 0.004 250)";
67
82
  return (
68
- <html lang="en" suppressHydrationWarning>
83
+ <html
84
+ lang="en"
85
+ className={htmlClass}
86
+ data-theme={theme}
87
+ style={{ colorScheme: theme, backgroundColor: htmlBackground }}
88
+ suppressHydrationWarning
89
+ >
69
90
  <head>
70
91
  {/* Static CSS — no user input, safe from XSS */}
71
92
  <style dangerouslySetInnerHTML={{ __html: CRITICAL_THEME_CSS }} />
72
- {/* Theme bootstrap — runs before paint to prevent FOUC */}
73
- <Script id="theme-init" strategy="beforeInteractive">{THEME_INIT_SCRIPT}</Script>
74
93
  </head>
75
94
  <body
76
95
  className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased bg-background text-foreground`}
@@ -79,18 +98,20 @@ export default function RootLayout({
79
98
  Skip to main content
80
99
  </a>
81
100
  <TooltipProvider>
82
- <SidebarProvider>
83
- <AppSidebar />
84
- <SidebarInset className="min-w-0">
85
- <main id="main-content">
86
- {children}
87
- </main>
88
- </SidebarInset>
89
- </SidebarProvider>
90
- <PendingApprovalHost />
91
- <CommandPalette />
92
- <GlobalShortcuts />
93
- <Toaster />
101
+ <ChatSessionProvider>
102
+ <SidebarProvider>
103
+ <AppSidebar />
104
+ <SidebarInset className="min-w-0">
105
+ <main id="main-content">
106
+ {children}
107
+ </main>
108
+ </SidebarInset>
109
+ </SidebarProvider>
110
+ <PendingApprovalHost />
111
+ <CommandPalette />
112
+ <GlobalShortcuts />
113
+ <Toaster />
114
+ </ChatSessionProvider>
94
115
  </TooltipProvider>
95
116
  </body>
96
117
  </html>
package/src/app/page.tsx CHANGED
@@ -12,7 +12,6 @@ import { QuickActions } from "@/components/dashboard/quick-actions";
12
12
  import { RecentProjects } from "@/components/dashboard/recent-projects";
13
13
  import type { RecentProject } from "@/components/dashboard/recent-projects";
14
14
  import { WelcomeLanding } from "@/components/dashboard/welcome-landing";
15
- import { EmailCaptureCard } from "@/components/onboarding/email-capture-card";
16
15
  import { ActivationChecklist } from "@/components/onboarding/activation-checklist";
17
16
  import {
18
17
  getCompletionsByDay,
@@ -107,7 +106,6 @@ export default async function HomePage() {
107
106
  return (
108
107
  <div className="bg-background min-h-screen p-4 sm:p-6">
109
108
  <div className="surface-page-shell min-h-[calc(100dvh-2rem)] rounded-xl p-5 sm:p-6 lg:p-7 space-y-6">
110
- <EmailCaptureCard />
111
109
  <WelcomeLanding />
112
110
  </div>
113
111
  </div>
@@ -1,7 +1,3 @@
1
- import { SubscriptionSection } from "@/components/settings/subscription-section";
2
- import { CloudAccountSection } from "@/components/settings/cloud-account-section";
3
- import { CloudSyncSection } from "@/components/settings/cloud-sync-section";
4
- import { TelemetrySection } from "@/components/settings/telemetry-section";
5
1
  import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
6
2
  import { PermissionsSections } from "@/components/settings/permissions-sections";
7
3
  import { DataManagementSection } from "@/components/settings/data-management-section";
@@ -11,32 +7,35 @@ import { ChatSettingsSection } from "@/components/settings/chat-settings-section
11
7
  import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
12
8
  import { BrowserToolsSection } from "@/components/settings/browser-tools-section";
13
9
  import { WebSearchSection } from "@/components/settings/web-search-section";
10
+ import { EnvironmentSection } from "@/components/settings/environment-section";
14
11
  import { LearningContextSection } from "@/components/settings/learning-context-section";
15
12
  import { OllamaSection } from "@/components/settings/ollama-section";
16
13
  import { ChannelsSection } from "@/components/settings/channels-section";
14
+ import { InstanceSection } from "@/components/instance/instance-section";
17
15
  import { PageShell } from "@/components/shared/page-shell";
18
16
 
19
17
  export const dynamic = "force-dynamic";
20
18
 
21
19
  export default function SettingsPage() {
22
20
  return (
23
- <PageShell title="Settings" description="Manage your Stagent configuration">
21
+ <PageShell
22
+ title="Settings"
23
+ description="Manage your Stagent configuration"
24
+ >
24
25
  <div className="space-y-6">
25
- <SubscriptionSection />
26
- <CloudAccountSection />
27
- <CloudSyncSection />
26
+ <InstanceSection />
28
27
  <ProvidersAndRuntimesSection />
29
28
  <OllamaSection />
30
29
  <ChatSettingsSection />
31
30
  <RuntimeTimeoutSection />
32
31
  <LearningContextSection />
33
32
  <WebSearchSection />
33
+ <EnvironmentSection />
34
34
  <BrowserToolsSection />
35
35
  <ChannelsSection />
36
36
  <BudgetGuardrailsSection />
37
37
  <PermissionsSections />
38
38
  <DatabaseSnapshotsSection />
39
- <TelemetrySection />
40
39
  <DataManagementSection />
41
40
  </div>
42
41
  </PageShell>
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import { CapabilityBanner } from "../capability-banner";
4
+
5
+ describe("CapabilityBanner", () => {
6
+ beforeEach(() => {
7
+ sessionStorage.clear();
8
+ });
9
+
10
+ it("is hidden on claude-code runtime", () => {
11
+ render(<CapabilityBanner runtimeId="claude-code" />);
12
+ expect(screen.queryByRole("status")).toBeNull();
13
+ });
14
+
15
+ it("is hidden on openai-codex-app-server runtime", () => {
16
+ render(<CapabilityBanner runtimeId="openai-codex-app-server" />);
17
+ expect(screen.queryByRole("status")).toBeNull();
18
+ });
19
+
20
+ it("is visible on ollama runtime with capability message", () => {
21
+ render(<CapabilityBanner runtimeId="ollama" />);
22
+ const status = screen.getByRole("status");
23
+ expect(status.textContent).toContain("file read/write");
24
+ });
25
+
26
+ it("hides on dismiss and persists to sessionStorage", () => {
27
+ render(<CapabilityBanner runtimeId="ollama" />);
28
+ fireEvent.click(screen.getByRole("button", { name: /dismiss/i }));
29
+ expect(screen.queryByRole("status")).toBeNull();
30
+ expect(sessionStorage.getItem("stagent.capability-banner.dismissed.ollama")).toBe("1");
31
+ });
32
+
33
+ it("stays dismissed on remount if sessionStorage flag set", () => {
34
+ sessionStorage.setItem("stagent.capability-banner.dismissed.ollama", "1");
35
+ render(<CapabilityBanner runtimeId="ollama" />);
36
+ expect(screen.queryByRole("status")).toBeNull();
37
+ });
38
+ });