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,106 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import { NotificationItem } from "@/components/notifications/notification-item";
5
+
6
+ const { push, contextReviewSpy, batchReviewSpy } = vi.hoisted(() => ({
7
+ push: vi.fn(),
8
+ contextReviewSpy: vi.fn(),
9
+ batchReviewSpy: vi.fn(),
10
+ }));
11
+
12
+ vi.mock("next/navigation", () => ({
13
+ useRouter: () => ({ push }),
14
+ }));
15
+
16
+ vi.mock("@/components/profiles/context-proposal-review", () => ({
17
+ ContextProposalReview: (props: {
18
+ notificationId: string;
19
+ profileId: string;
20
+ proposedAdditions: string;
21
+ onResponded: () => void;
22
+ }) => {
23
+ contextReviewSpy(props);
24
+ return <div>Context proposal review</div>;
25
+ },
26
+ }));
27
+
28
+ vi.mock("@/components/notifications/batch-proposal-review", () => ({
29
+ BatchProposalReview: (props: {
30
+ proposalIds: string[];
31
+ profileIds: string[];
32
+ body: string;
33
+ onResponded?: () => void;
34
+ }) => {
35
+ batchReviewSpy(props);
36
+ return <div>Batch proposal review</div>;
37
+ },
38
+ }));
39
+
40
+ describe("notification item", () => {
41
+ it("renders context proposal review actions using the full additions payload", () => {
42
+ render(
43
+ <NotificationItem
44
+ notification={{
45
+ id: "notif-1",
46
+ taskId: null,
47
+ type: "context_proposal",
48
+ title: "Context proposal",
49
+ body: "truncated body",
50
+ read: false,
51
+ toolName: "general",
52
+ toolInput: JSON.stringify({
53
+ profileId: "general",
54
+ additions: "Full learned additions",
55
+ }),
56
+ response: null,
57
+ respondedAt: null,
58
+ createdAt: "2026-04-10T00:00:00.000Z",
59
+ }}
60
+ onUpdated={vi.fn()}
61
+ />
62
+ );
63
+
64
+ expect(screen.getByText("Context proposal review")).toBeInTheDocument();
65
+ expect(contextReviewSpy).toHaveBeenCalledWith(
66
+ expect.objectContaining({
67
+ notificationId: "notif-1",
68
+ profileId: "general",
69
+ proposedAdditions: "Full learned additions",
70
+ })
71
+ );
72
+ });
73
+
74
+ it("renders batch proposal review actions for workflow learning notifications", () => {
75
+ render(
76
+ <NotificationItem
77
+ notification={{
78
+ id: "notif-2",
79
+ taskId: null,
80
+ type: "context_proposal_batch",
81
+ title: "Workflow learning batch",
82
+ body: "Batch summary",
83
+ read: false,
84
+ toolName: "workflow-context-batch",
85
+ toolInput: JSON.stringify({
86
+ proposalIds: ["p1", "p2"],
87
+ profileIds: ["general", "researcher"],
88
+ }),
89
+ response: null,
90
+ respondedAt: null,
91
+ createdAt: "2026-04-10T00:00:00.000Z",
92
+ }}
93
+ onUpdated={vi.fn()}
94
+ />
95
+ );
96
+
97
+ expect(screen.getByText("Batch proposal review")).toBeInTheDocument();
98
+ expect(batchReviewSpy).toHaveBeenCalledWith(
99
+ expect.objectContaining({
100
+ proposalIds: ["p1", "p2"],
101
+ profileIds: ["general", "researcher"],
102
+ body: "Batch summary",
103
+ })
104
+ );
105
+ });
106
+ });
@@ -62,6 +62,76 @@ describe("permission response actions", () => {
62
62
  });
63
63
  });
64
64
 
65
+ it("renders option cards for AskUserQuestion with options and posts { answer } on click", async () => {
66
+ const onResponded = vi.fn();
67
+ render(
68
+ <PermissionResponseActions
69
+ taskId="task-42"
70
+ notificationId="notif-q1"
71
+ toolName="AskUserQuestion"
72
+ toolInput={{
73
+ question: "Which version?",
74
+ options: [
75
+ { label: "Keep my version", description: "Use your changes" },
76
+ { label: "Take main's version", description: "Use main's changes" },
77
+ ],
78
+ }}
79
+ responded={false}
80
+ response={null}
81
+ onResponded={onResponded}
82
+ />
83
+ );
84
+
85
+ const group = screen.getByRole("radiogroup");
86
+ expect(group).toBeInTheDocument();
87
+ fireEvent.click(screen.getByText("Take main's version"));
88
+
89
+ await waitFor(() => {
90
+ expect(fetch).toHaveBeenCalledWith("/api/tasks/task-42/respond", {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({
94
+ notificationId: "notif-q1",
95
+ behavior: "allow",
96
+ updatedInput: { answer: "Take main's version" },
97
+ }),
98
+ });
99
+ expect(onResponded).toHaveBeenCalled();
100
+ });
101
+ });
102
+
103
+ it("renders a textarea for AskUserQuestion without options and posts the typed answer", async () => {
104
+ const onResponded = vi.fn();
105
+ render(
106
+ <PermissionResponseActions
107
+ taskId="task-42"
108
+ notificationId="notif-q2"
109
+ toolName="AskUserQuestion"
110
+ toolInput={{ question: "Move commits to local or abort?" }}
111
+ responded={false}
112
+ response={null}
113
+ onResponded={onResponded}
114
+ />
115
+ );
116
+
117
+ const textarea = screen.getByPlaceholderText("Type your reply…");
118
+ fireEvent.change(textarea, { target: { value: "move them to local" } });
119
+ fireEvent.click(screen.getByRole("button", { name: /Send/i }));
120
+
121
+ await waitFor(() => {
122
+ expect(fetch).toHaveBeenCalledWith("/api/tasks/task-42/respond", {
123
+ method: "POST",
124
+ headers: { "Content-Type": "application/json" },
125
+ body: JSON.stringify({
126
+ notificationId: "notif-q2",
127
+ behavior: "allow",
128
+ updatedInput: { answer: "move them to local" },
129
+ }),
130
+ });
131
+ expect(onResponded).toHaveBeenCalled();
132
+ });
133
+ });
134
+
65
135
  it("renders the resolved state label when a response already exists", () => {
66
136
  render(
67
137
  <PermissionResponseActions
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from "react";
4
+ import { toast } from "sonner";
4
5
  import { Button } from "@/components/ui/button";
5
6
  import { Badge } from "@/components/ui/badge";
6
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -12,6 +13,7 @@ interface BatchProposalReviewProps {
12
13
  profileIds: string[];
13
14
  body: string;
14
15
  onResponded?: () => void;
16
+ onRequestFailed?: () => void;
15
17
  compact?: boolean;
16
18
  }
17
19
 
@@ -20,6 +22,7 @@ export function BatchProposalReview({
20
22
  profileIds,
21
23
  body,
22
24
  onResponded,
25
+ onRequestFailed,
23
26
  compact = false,
24
27
  }: BatchProposalReviewProps) {
25
28
  const [loading, setLoading] = useState<"approve" | "reject" | null>(null);
@@ -31,18 +34,30 @@ export function BatchProposalReview({
31
34
 
32
35
  async function handleBatchAction(action: "approve" | "reject") {
33
36
  setLoading(action);
37
+ setResponded(true);
38
+ onResponded?.();
39
+
34
40
  try {
35
41
  const res = await fetch("/api/context/batch", {
36
42
  method: "POST",
37
43
  headers: { "Content-Type": "application/json" },
38
44
  body: JSON.stringify({ proposalIds, action }),
39
45
  });
40
- if (res.ok) {
41
- const data = await res.json();
42
- setResult({ action: data.action, count: data.count });
43
- setResponded(true);
44
- onResponded?.();
46
+
47
+ if (!res.ok) {
48
+ const data = await res.json().catch(() => null);
49
+ throw new Error(data?.error ?? `Failed to ${action} batch proposal`);
45
50
  }
51
+
52
+ const data = await res.json();
53
+ setResult({ action: data.action, count: data.count });
54
+ } catch (error) {
55
+ setResponded(false);
56
+ setResult(null);
57
+ toast.error(
58
+ error instanceof Error ? error.message : "Batch approval failed"
59
+ );
60
+ onRequestFailed?.();
46
61
  } finally {
47
62
  setLoading(null);
48
63
  }
@@ -7,6 +7,7 @@ import { Eye, Inbox, RefreshCw, Trash2 } from "lucide-react";
7
7
  import { toast } from "sonner";
8
8
  import { NotificationItem } from "./notification-item";
9
9
  import { EmptyState } from "@/components/shared/empty-state";
10
+ import { filterDefaultVisibleNotifications } from "@/lib/notifications/visibility";
10
11
 
11
12
  interface Notification {
12
13
  id: string;
@@ -28,12 +29,15 @@ export function InboxList({
28
29
  initialNotifications: Notification[];
29
30
  }) {
30
31
  const [notifications, setNotifications] =
31
- useState<Notification[]>(initialNotifications);
32
+ useState<Notification[]>(() => filterDefaultVisibleNotifications(initialNotifications));
32
33
  const [tab, setTab] = useState("all");
33
34
 
34
35
  const refresh = useCallback(async () => {
35
36
  const res = await fetch("/api/notifications");
36
- if (res.ok) setNotifications(await res.json());
37
+ if (res.ok) {
38
+ const next = (await res.json()) as Notification[];
39
+ setNotifications(filterDefaultVisibleNotifications(next));
40
+ }
37
41
  }, []);
38
42
 
39
43
  // Poll every 10 seconds (consolidated from 3s inbox + 5s badge)
@@ -154,6 +158,11 @@ export function InboxList({
154
158
  <NotificationItem
155
159
  key={n.id}
156
160
  notification={n}
161
+ onRemoved={(notificationId) =>
162
+ setNotifications((current) =>
163
+ current.filter((item) => item.id !== notificationId)
164
+ )
165
+ }
157
166
  onUpdated={refresh}
158
167
  />
159
168
  ))
@@ -18,6 +18,8 @@ import {
18
18
  parseNotificationToolInput,
19
19
  type PermissionToolInput,
20
20
  } from "@/lib/notifications/permissions";
21
+ import { ContextProposalReview } from "@/components/profiles/context-proposal-review";
22
+ import { BatchProposalReview } from "./batch-proposal-review";
21
23
 
22
24
  interface Notification {
23
25
  id: string;
@@ -35,6 +37,7 @@ interface Notification {
35
37
 
36
38
  interface NotificationItemProps {
37
39
  notification: Notification;
40
+ onRemoved?: (notificationId: string) => void;
38
41
  onUpdated: () => void;
39
42
  }
40
43
 
@@ -104,7 +107,25 @@ function formatToolInput(
104
107
 
105
108
  const navigableTypes = new Set(["task_completed", "task_failed", "permission_required", "agent_message"]);
106
109
 
107
- export function NotificationItem({ notification, onUpdated }: NotificationItemProps) {
110
+ function parseBatchToolInput(toolInput: PermissionToolInput | null): {
111
+ proposalIds: string[];
112
+ profileIds: string[];
113
+ } {
114
+ return {
115
+ proposalIds: Array.isArray(toolInput?.proposalIds)
116
+ ? toolInput.proposalIds.filter((id): id is string => typeof id === "string")
117
+ : [],
118
+ profileIds: Array.isArray(toolInput?.profileIds)
119
+ ? toolInput.profileIds.filter((id): id is string => typeof id === "string")
120
+ : [],
121
+ };
122
+ }
123
+
124
+ export function NotificationItem({
125
+ notification,
126
+ onRemoved,
127
+ onUpdated,
128
+ }: NotificationItemProps) {
108
129
  const router = useRouter();
109
130
  const [toggling, setToggling] = useState(false);
110
131
  const [dismissing, setDismissing] = useState(false);
@@ -212,7 +233,9 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
212
233
  {/* Body for non-tool notifications */}
213
234
  {notification.body &&
214
235
  notification.type !== "permission_required" &&
215
- notification.type !== "agent_message" && (
236
+ notification.type !== "agent_message" &&
237
+ notification.type !== "context_proposal" &&
238
+ notification.type !== "context_proposal_batch" && (
216
239
  <div className="mt-1" onClick={(e) => e.stopPropagation()}>
217
240
  <div
218
241
  className={`${PROSE_NOTIFICATION} ${
@@ -270,6 +293,37 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
270
293
  <FailureAction taskId={notification.taskId} onRetried={onUpdated} />
271
294
  )}
272
295
 
296
+ {notification.type === "context_proposal" && (
297
+ <div className="mt-3" onClick={(e) => e.stopPropagation()}>
298
+ <ContextProposalReview
299
+ notificationId={notification.id}
300
+ profileId={
301
+ typeof parsedToolInput?.profileId === "string"
302
+ ? parsedToolInput.profileId
303
+ : (notification.toolName ?? "")
304
+ }
305
+ proposedAdditions={
306
+ typeof parsedToolInput?.additions === "string"
307
+ ? parsedToolInput.additions
308
+ : (notification.body ?? "")
309
+ }
310
+ onResponded={onUpdated}
311
+ />
312
+ </div>
313
+ )}
314
+
315
+ {notification.type === "context_proposal_batch" && (
316
+ <div className="mt-3" onClick={(e) => e.stopPropagation()}>
317
+ <BatchProposalReview
318
+ proposalIds={parseBatchToolInput(parsedToolInput).proposalIds}
319
+ profileIds={parseBatchToolInput(parsedToolInput).profileIds}
320
+ body={notification.body ?? ""}
321
+ onResponded={() => onRemoved?.(notification.id)}
322
+ onRequestFailed={onUpdated}
323
+ />
324
+ </div>
325
+ )}
326
+
273
327
  <p className="text-xs text-muted-foreground mt-2">
274
328
  {formatTimestamp(notification.createdAt)}
275
329
  {notification.respondedAt && (
@@ -2,7 +2,7 @@
2
2
 
3
3
  import Link from "next/link";
4
4
  import { usePathname, useRouter } from "next/navigation";
5
- import { useEffect, useMemo, useRef, useState } from "react";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
6
  import {
7
7
  ArrowUpRight,
8
8
  Inbox,
@@ -108,12 +108,14 @@ function PendingApprovalDetail({
108
108
  selected,
109
109
  overflow,
110
110
  onResponded,
111
+ onRequestFailed,
111
112
  onOpenInbox,
112
113
  onSelect,
113
114
  }: {
114
115
  selected: PendingApprovalPayload;
115
116
  overflow: PendingApprovalPayload[];
116
117
  onResponded: () => void;
118
+ onRequestFailed: () => void;
117
119
  onOpenInbox: () => void;
118
120
  onSelect: (notificationId: string) => void;
119
121
  }) {
@@ -172,6 +174,7 @@ function PendingApprovalDetail({
172
174
  profileIds={parsed.profileIds}
173
175
  body={selected.body ?? ""}
174
176
  onResponded={onResponded}
177
+ onRequestFailed={onRequestFailed}
175
178
  />
176
179
  );
177
180
  })()
@@ -264,6 +267,33 @@ export function PendingApprovalHost() {
264
267
  const router = useRouter();
265
268
  const pathname = usePathname();
266
269
 
270
+ const applySnapshot = useCallback((snapshot: PendingApprovalPayload[]) => {
271
+ const nextItems = dedupePendingApprovals(snapshot);
272
+ const previousIds = new Set(knownIdsRef.current);
273
+ const newestNew = nextItems.find(
274
+ (item) => !previousIds.has(item.notificationId)
275
+ );
276
+
277
+ if (newestNew) {
278
+ setAnnouncement(
279
+ `Permission required for ${buildContextLabel(newestNew)}. ${newestNew.compactSummary}`
280
+ );
281
+ }
282
+
283
+ knownIdsRef.current = nextItems.map((item) => item.notificationId);
284
+ setItems(nextItems);
285
+ }, []);
286
+
287
+ const refreshApprovals = useCallback(async () => {
288
+ const res = await fetch("/api/notifications/pending-approvals", {
289
+ cache: "no-store",
290
+ });
291
+ if (!res.ok) return;
292
+
293
+ const snapshot = (await res.json()) as PendingApprovalPayload[];
294
+ applySnapshot(snapshot);
295
+ }, [applySnapshot]);
296
+
267
297
  const primary = items[0] ?? null;
268
298
  const selected = useMemo(() => {
269
299
  if (!items.length) return null;
@@ -287,51 +317,25 @@ export function PendingApprovalHost() {
287
317
  let pollId: ReturnType<typeof setInterval> | null = null;
288
318
  let eventSource: EventSource | null = null;
289
319
 
290
- function applySnapshot(snapshot: PendingApprovalPayload[]) {
291
- if (cancelled) return;
292
-
293
- const nextItems = dedupePendingApprovals(snapshot);
294
- const previousIds = new Set(knownIdsRef.current);
295
- const newestNew = nextItems.find(
296
- (item) => !previousIds.has(item.notificationId)
297
- );
298
-
299
- if (newestNew) {
300
- setAnnouncement(
301
- `Permission required for ${buildContextLabel(newestNew)}. ${newestNew.compactSummary}`
302
- );
303
- }
304
-
305
- knownIdsRef.current = nextItems.map((item) => item.notificationId);
306
- setItems(nextItems);
307
- }
308
-
309
- async function refresh() {
310
- try {
311
- const res = await fetch("/api/notifications/pending-approvals", {
312
- cache: "no-store",
313
- });
314
- if (!res.ok) return;
315
-
316
- const snapshot = (await res.json()) as PendingApprovalPayload[];
317
- applySnapshot(snapshot);
318
- } catch {
319
- // Fallback refresh should fail quietly.
320
- }
321
- }
322
-
323
320
  const startPolling = () => {
324
321
  if (pollId) return;
325
- pollId = setInterval(refresh, 15_000);
322
+ pollId = setInterval(() => {
323
+ refreshApprovals().catch(() => {
324
+ // Fallback refresh should fail quietly.
325
+ });
326
+ }, 15_000);
326
327
  };
327
328
 
328
- refresh();
329
+ refreshApprovals().catch(() => {
330
+ // Initial refresh should fail quietly.
331
+ });
329
332
 
330
333
  try {
331
334
  eventSource = new EventSource("/api/notifications/pending-approvals/stream");
332
335
  eventSource.onmessage = (event) => {
333
336
  try {
334
337
  const snapshot = JSON.parse(event.data) as PendingApprovalPayload[];
338
+ if (cancelled) return;
335
339
  applySnapshot(snapshot);
336
340
  } catch {
337
341
  startPolling();
@@ -351,7 +355,7 @@ export function PendingApprovalHost() {
351
355
  if (pollId) clearInterval(pollId);
352
356
  eventSource?.close();
353
357
  };
354
- }, []);
358
+ }, [applySnapshot, refreshApprovals]);
355
359
 
356
360
  function removeNotification(notificationId: string) {
357
361
  setItems((current) =>
@@ -453,6 +457,11 @@ export function PendingApprovalHost() {
453
457
  profileIds={parsed.profileIds}
454
458
  body={primary.body ?? ""}
455
459
  onResponded={() => removeNotification(primary.notificationId)}
460
+ onRequestFailed={() => {
461
+ refreshApprovals().catch(() => {
462
+ // Refresh failures are surfaced by the batch review toast.
463
+ });
464
+ }}
456
465
  compact
457
466
  />
458
467
  );
@@ -518,6 +527,11 @@ export function PendingApprovalHost() {
518
527
  selected={selected}
519
528
  overflow={overflowItems}
520
529
  onResponded={() => removeNotification(selected.notificationId)}
530
+ onRequestFailed={() => {
531
+ refreshApprovals().catch(() => {
532
+ // Refresh failures are surfaced by the batch review toast.
533
+ });
534
+ }}
521
535
  onOpenInbox={handleOpenInbox}
522
536
  onSelect={setSelectedId}
523
537
  />
@@ -545,6 +559,11 @@ export function PendingApprovalHost() {
545
559
  selected={selected}
546
560
  overflow={overflowItems}
547
561
  onResponded={() => removeNotification(selected.notificationId)}
562
+ onRequestFailed={() => {
563
+ refreshApprovals().catch(() => {
564
+ // Refresh failures are surfaced by the batch review toast.
565
+ });
566
+ }}
548
567
  onOpenInbox={handleOpenInbox}
549
568
  onSelect={setSelectedId}
550
569
  />