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
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Hash } from "lucide-react";
5
+ import { Input } from "@/components/ui/input";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { parseFilterInput, type FilterClause } from "@/lib/filters/parse";
8
+ import { FilterHint } from "./filter-hint";
9
+
10
+ interface FilterInputProps {
11
+ value: string;
12
+ onChange: (next: { raw: string; clauses: FilterClause[]; rawQuery: string }) => void;
13
+ placeholder?: string;
14
+ }
15
+
16
+ /**
17
+ * FilterInput — free-text input that recognizes `#key:value` filter syntax.
18
+ *
19
+ * Renders parsed clauses as outline badges next to the input. Consumer
20
+ * receives the raw string (for URL serialization) and the parsed breakdown
21
+ * (for list filtering). Keeps the existing free-text search behavior — the
22
+ * `rawQuery` is the text with filter clauses stripped.
23
+ */
24
+ export function FilterInput({ value, onChange, placeholder }: FilterInputProps) {
25
+ const [local, setLocal] = useState(value);
26
+
27
+ useEffect(() => {
28
+ setLocal(value);
29
+ }, [value]);
30
+
31
+ const parsed = parseFilterInput(local);
32
+
33
+ return (
34
+ <div className="flex flex-col gap-1 flex-1 min-w-0">
35
+ <div className="flex flex-wrap items-center gap-2">
36
+ <div className="relative flex-1 min-w-[16rem]">
37
+ <Hash className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
38
+ <Input
39
+ value={local}
40
+ onChange={(e) => {
41
+ const next = e.target.value;
42
+ setLocal(next);
43
+ const p = parseFilterInput(next);
44
+ onChange({ raw: next, clauses: p.clauses, rawQuery: p.rawQuery });
45
+ }}
46
+ placeholder={placeholder ?? "#status:blocked or search…"}
47
+ className="pl-7 h-8"
48
+ />
49
+ </div>
50
+ {parsed.clauses.map((c, i) => (
51
+ <Badge key={`${c.key}-${i}`} variant="outline" className="text-xs font-mono">
52
+ #{c.key}:{c.value}
53
+ </Badge>
54
+ ))}
55
+ </div>
56
+ <FilterHint inputValue={local} storageKey="stagent.filter-hint.dismissed" />
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,199 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Pencil, Trash2, Check, X } from "lucide-react";
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from "@/components/ui/dialog";
12
+ import { Input } from "@/components/ui/input";
13
+ import { Button } from "@/components/ui/button";
14
+ import { Badge } from "@/components/ui/badge";
15
+ import type { SavedSearch } from "@/hooks/use-saved-searches";
16
+
17
+ const LABEL_MAX = 120;
18
+
19
+ interface SavedSearchesManagerProps {
20
+ open: boolean;
21
+ onOpenChange: (open: boolean) => void;
22
+ searches: SavedSearch[];
23
+ onRename: (id: string, label: string) => void;
24
+ onRemove: (id: string) => void;
25
+ }
26
+
27
+ /**
28
+ * SavedSearchesManager — dialog for renaming or deleting saved searches.
29
+ *
30
+ * Distinct from the inline palette delete (which is one-click with a 5s
31
+ * undo toast). This dialog is a deliberate management context, so delete
32
+ * requires an explicit "Confirm" click (no undo).
33
+ */
34
+ export function SavedSearchesManager({
35
+ open,
36
+ onOpenChange,
37
+ searches,
38
+ onRename,
39
+ onRemove,
40
+ }: SavedSearchesManagerProps) {
41
+ const [renamingId, setRenamingId] = useState<string | null>(null);
42
+ const [draft, setDraft] = useState<string>("");
43
+ const [error, setError] = useState<string | null>(null);
44
+ const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null);
45
+
46
+ function startRename(s: SavedSearch) {
47
+ setRenamingId(s.id);
48
+ setDraft(s.label);
49
+ setError(null);
50
+ }
51
+
52
+ function cancelRename() {
53
+ setRenamingId(null);
54
+ setDraft("");
55
+ setError(null);
56
+ }
57
+
58
+ function commitRename(s: SavedSearch) {
59
+ // If renaming was already cancelled (e.g., via Escape) the renamingId
60
+ // no longer matches — blur fires after cancelRename() has set it to null.
61
+ if (renamingId !== s.id) return;
62
+
63
+ const next = draft.trim();
64
+ if (next.length === 0) {
65
+ setError("Label cannot be empty");
66
+ return;
67
+ }
68
+ if (next.length > LABEL_MAX) {
69
+ setError(`Label too long (max ${LABEL_MAX} chars)`);
70
+ return;
71
+ }
72
+ const dupe = searches.find(
73
+ (other) =>
74
+ other.id !== s.id &&
75
+ other.surface === s.surface &&
76
+ other.label.toLowerCase() === next.toLowerCase()
77
+ );
78
+ if (dupe) {
79
+ setError("A saved search with that label already exists for this surface");
80
+ return;
81
+ }
82
+ if (next !== s.label) onRename(s.id, next);
83
+ cancelRename();
84
+ }
85
+
86
+ return (
87
+ <Dialog open={open} onOpenChange={onOpenChange}>
88
+ <DialogContent className="max-w-lg">
89
+ <DialogHeader>
90
+ <DialogTitle>Manage saved searches</DialogTitle>
91
+ <DialogDescription>Rename or delete your saved filter combinations.</DialogDescription>
92
+ </DialogHeader>
93
+ <div className="px-6 pb-6 space-y-2 overflow-y-auto max-h-[60vh]">
94
+ {searches.length === 0 ? (
95
+ <p className="text-sm text-muted-foreground">No saved searches yet.</p>
96
+ ) : (
97
+ searches.map((s) => {
98
+ const isRenaming = renamingId === s.id;
99
+ const isPendingDelete = pendingDeleteId === s.id;
100
+ return (
101
+ <div
102
+ key={s.id}
103
+ className="flex items-center gap-2 rounded-md border border-border/60 px-3 py-2"
104
+ >
105
+ <div className="flex-1 min-w-0">
106
+ {isRenaming ? (
107
+ <div className="space-y-1">
108
+ <Input
109
+ aria-label="Rename"
110
+ autoFocus
111
+ value={draft}
112
+ onChange={(e) => {
113
+ setDraft(e.target.value);
114
+ setError(null);
115
+ }}
116
+ onKeyDown={(e) => {
117
+ if (e.key === "Escape") {
118
+ e.preventDefault();
119
+ cancelRename();
120
+ } else if (e.key === "Enter") {
121
+ e.preventDefault();
122
+ commitRename(s);
123
+ }
124
+ }}
125
+ onBlur={() => commitRename(s)}
126
+ className="h-7"
127
+ />
128
+ {error && (
129
+ <p className="text-xs text-destructive">{error}</p>
130
+ )}
131
+ </div>
132
+ ) : (
133
+ <div className="flex items-center gap-2">
134
+ <span className="truncate text-sm font-medium">{s.label}</span>
135
+ <Badge variant="outline" className="text-[10px] uppercase">
136
+ {s.surface}
137
+ </Badge>
138
+ </div>
139
+ )}
140
+ <p className="truncate text-xs font-mono text-muted-foreground">
141
+ {s.filterInput}
142
+ </p>
143
+ </div>
144
+ {!isRenaming && !isPendingDelete && (
145
+ <>
146
+ <Button
147
+ variant="ghost"
148
+ size="icon"
149
+ className="h-7 w-7"
150
+ aria-label={`Rename ${s.label}`}
151
+ onClick={() => startRename(s)}
152
+ >
153
+ <Pencil className="h-3.5 w-3.5" />
154
+ </Button>
155
+ <Button
156
+ variant="ghost"
157
+ size="icon"
158
+ className="h-7 w-7 text-destructive hover:text-destructive"
159
+ aria-label={`Delete ${s.label}`}
160
+ onClick={() => setPendingDeleteId(s.id)}
161
+ >
162
+ <Trash2 className="h-3.5 w-3.5" />
163
+ </Button>
164
+ </>
165
+ )}
166
+ {isPendingDelete && (
167
+ <div className="flex items-center gap-1">
168
+ <Button
169
+ variant="destructive"
170
+ size="sm"
171
+ className="h-7"
172
+ aria-label={`Confirm delete ${s.label}`}
173
+ onClick={() => {
174
+ onRemove(s.id);
175
+ setPendingDeleteId(null);
176
+ }}
177
+ >
178
+ <Check className="h-3.5 w-3.5" /> Confirm delete
179
+ </Button>
180
+ <Button
181
+ variant="ghost"
182
+ size="sm"
183
+ className="h-7"
184
+ aria-label="Cancel delete"
185
+ onClick={() => setPendingDeleteId(null)}
186
+ >
187
+ <X className="h-3.5 w-3.5" />
188
+ </Button>
189
+ </div>
190
+ )}
191
+ </div>
192
+ );
193
+ })
194
+ )}
195
+ </div>
196
+ </DialogContent>
197
+ </Dialog>
198
+ );
199
+ }
@@ -3,40 +3,21 @@
3
3
  import { useEffect, useState } from "react";
4
4
  import { Moon, Sun } from "lucide-react";
5
5
  import { Button } from "@/components/ui/button";
6
-
7
- type ResolvedTheme = "light" | "dark";
8
-
9
- function resolveThemePreference(): ResolvedTheme {
10
- const stored = localStorage.getItem("stagent-theme");
11
- if (stored === "dark" || stored === "light") return stored;
12
- // Light-first: default to light when no explicit preference stored
13
- return "light";
14
- }
15
-
16
- function applyTheme(theme: ResolvedTheme) {
17
- const root = document.documentElement;
18
- root.classList.toggle("dark", theme === "dark");
19
- root.dataset.theme = theme;
20
- root.style.colorScheme = theme;
21
- root.style.backgroundColor =
22
- theme === "dark" ? "oklch(0.14 0.02 250)" : "oklch(0.985 0.004 250)";
23
- localStorage.setItem("stagent-theme", theme);
24
- document.cookie = `stagent-theme=${theme};path=/;max-age=31536000;SameSite=Lax`;
25
- }
6
+ import { applyTheme, readClientTheme } from "@/lib/theme";
26
7
 
27
8
  export function ThemeToggle() {
28
9
  const [dark, setDark] = useState(false);
29
10
 
30
11
  useEffect(() => {
31
- const theme = resolveThemePreference();
12
+ const theme = readClientTheme();
32
13
  applyTheme(theme);
33
14
  setDark(theme === "dark");
34
15
  }, []);
35
16
 
36
17
  function toggle() {
37
- const next = !dark;
38
- applyTheme(next ? "dark" : "light");
39
- setDark(next);
18
+ const next = dark ? "light" : "dark";
19
+ applyTheme(next);
20
+ setDark(next === "dark");
40
21
  }
41
22
 
42
23
  return (
@@ -1,13 +1,15 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useState } from "react";
4
- import { GitBranch } from "lucide-react";
4
+ import { Database, Folder, GitBranch, Wrench } from "lucide-react";
5
5
 
6
6
  interface WorkspaceData {
7
7
  folderName: string;
8
8
  parentPath: string;
9
9
  gitBranch: string | null;
10
10
  isWorktree: boolean;
11
+ dataDir: string | null;
12
+ dataDirMismatch: boolean;
11
13
  }
12
14
 
13
15
  interface WorkspaceIndicatorProps {
@@ -16,6 +18,8 @@ interface WorkspaceIndicatorProps {
16
18
 
17
19
  export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
18
20
  const [data, setData] = useState<WorkspaceData | null>(null);
21
+ const [fixState, setFixState] = useState<"idle" | "loading" | "done" | "error">("idle");
22
+ const [fixResult, setFixResult] = useState<string | null>(null);
19
23
 
20
24
  useEffect(() => {
21
25
  fetch("/api/workspace/context")
@@ -26,9 +30,28 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
26
30
 
27
31
  if (!data) return null;
28
32
 
33
+ async function handleFix() {
34
+ setFixState("loading");
35
+ try {
36
+ const res = await fetch("/api/workspace/fix-data-dir", { method: "POST" });
37
+ const json = await res.json();
38
+ if (res.ok && json.success) {
39
+ setFixState("done");
40
+ setFixResult(json.dataDir);
41
+ } else {
42
+ setFixState("error");
43
+ setFixResult(json.error || "Fix failed");
44
+ }
45
+ } catch {
46
+ setFixState("error");
47
+ setFixResult("Network error");
48
+ }
49
+ }
50
+
29
51
  if (variant === "inline") {
30
52
  return (
31
53
  <span className="text-sm text-muted-foreground">
54
+ <Folder className="h-3 w-3 inline mr-1" />
32
55
  {data.parentPath}/
33
56
  <span className="font-medium text-foreground">{data.folderName}</span>
34
57
  {data.gitBranch && (
@@ -37,13 +60,20 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
37
60
  {data.gitBranch}
38
61
  </span>
39
62
  )}
63
+ {data.dataDir && (
64
+ <span className={`ml-2 inline-flex items-center gap-1 text-xs ${data.dataDirMismatch ? "text-destructive" : "text-muted-foreground/70"}`}>
65
+ <Database className="h-3 w-3" />
66
+ {data.dataDir}
67
+ </span>
68
+ )}
40
69
  </span>
41
70
  );
42
71
  }
43
72
 
44
73
  return (
45
74
  <div className="min-w-0">
46
- <p className="text-[12px] text-muted-foreground truncate">
75
+ <p className="text-[12px] text-muted-foreground truncate flex items-center gap-1">
76
+ <Folder className="h-3 w-3 shrink-0" />
47
77
  {data.parentPath}/<span className="font-semibold text-foreground">{data.folderName}</span>
48
78
  </p>
49
79
  {data.gitBranch && (
@@ -55,6 +85,35 @@ export function WorkspaceIndicator({ variant }: WorkspaceIndicatorProps) {
55
85
  )}
56
86
  </p>
57
87
  )}
88
+ {data.dataDir && (
89
+ <p className={`text-[11px] truncate flex items-center gap-1 mt-0.5 ${data.dataDirMismatch ? "text-destructive" : "text-muted-foreground/70"}`}>
90
+ <Database className="h-3 w-3 shrink-0" />
91
+ {data.dataDir}
92
+ {data.dataDirMismatch && fixState === "idle" && (
93
+ <>
94
+ <span className="text-[10px] bg-destructive/10 text-destructive px-1 rounded">shared</span>
95
+ <button
96
+ onClick={handleFix}
97
+ className="text-[10px] bg-destructive/10 text-destructive px-1.5 py-0.5 rounded hover:bg-destructive/20 transition-colors cursor-pointer inline-flex items-center gap-0.5"
98
+ >
99
+ <Wrench className="h-2.5 w-2.5" />
100
+ Fix
101
+ </button>
102
+ </>
103
+ )}
104
+ {data.dataDirMismatch && fixState === "loading" && (
105
+ <span className="text-[10px] text-muted-foreground animate-pulse">fixing...</span>
106
+ )}
107
+ {data.dataDirMismatch && fixState === "done" && (
108
+ <span className="text-[10px] text-green-600">
109
+ → {fixResult} · restart to apply
110
+ </span>
111
+ )}
112
+ {data.dataDirMismatch && fixState === "error" && (
113
+ <span className="text-[10px] text-destructive">{fixResult}</span>
114
+ )}
115
+ </p>
116
+ )}
58
117
  </div>
59
118
  );
60
119
  }
@@ -0,0 +1,130 @@
1
+ import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
2
+
3
+ import { TableEnrichmentSheet } from "@/components/tables/table-enrichment-sheet";
4
+ import type { EnrichmentPlan } from "@/lib/tables/enrichment-planner";
5
+ import type { ColumnDef } from "@/lib/tables/types";
6
+
7
+ const { pushMock, toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({
8
+ pushMock: vi.fn(),
9
+ toastErrorMock: vi.fn(),
10
+ toastSuccessMock: vi.fn(),
11
+ }));
12
+
13
+ vi.mock("next/navigation", () => ({
14
+ useRouter: () => ({
15
+ push: pushMock,
16
+ }),
17
+ }));
18
+
19
+ vi.mock("sonner", () => ({
20
+ toast: {
21
+ error: toastErrorMock,
22
+ success: toastSuccessMock,
23
+ },
24
+ }));
25
+
26
+ const columns: ColumnDef[] = [
27
+ {
28
+ name: "company",
29
+ displayName: "Company",
30
+ dataType: "text",
31
+ position: 0,
32
+ required: false,
33
+ defaultValue: null,
34
+ config: null,
35
+ },
36
+ ];
37
+
38
+ const previewPlan: EnrichmentPlan = {
39
+ promptMode: "auto",
40
+ strategy: "single-pass-lookup",
41
+ agentProfile: "general",
42
+ reasoning: "Keep the row flow lightweight.",
43
+ steps: [
44
+ {
45
+ id: "lookup",
46
+ name: "Lookup value",
47
+ purpose: "Determine the final typed value for this row",
48
+ prompt: "Return only the final value.",
49
+ agentProfile: "general",
50
+ },
51
+ ],
52
+ targetContract: {
53
+ columnName: "company",
54
+ columnLabel: "Company",
55
+ dataType: "text",
56
+ },
57
+ eligibleRowCount: 2,
58
+ sampleBindings: [{ id: "row-1", company: "Acme" }],
59
+ };
60
+
61
+ describe("TableEnrichmentSheet", () => {
62
+ beforeEach(() => {
63
+ vi.stubGlobal(
64
+ "fetch",
65
+ vi.fn((input: RequestInfo | URL) => {
66
+ const url = String(input);
67
+ if (url === "/api/profiles") {
68
+ return Promise.resolve({
69
+ ok: true,
70
+ json: vi.fn().mockResolvedValue([{ id: "general", name: "General" }]),
71
+ });
72
+ }
73
+ if (url === "/api/tables/table-1/enrich/plan") {
74
+ return Promise.resolve({
75
+ ok: true,
76
+ json: vi.fn().mockResolvedValue(previewPlan),
77
+ });
78
+ }
79
+ if (url === "/api/tables/table-1/enrich") {
80
+ return Promise.resolve({
81
+ ok: true,
82
+ json: vi.fn().mockResolvedValue({ workflowId: "workflow-1", rowCount: 2 }),
83
+ });
84
+ }
85
+ return Promise.reject(new Error(`Unhandled fetch: ${url}`));
86
+ })
87
+ );
88
+ });
89
+
90
+ afterEach(() => {
91
+ vi.unstubAllGlobals();
92
+ vi.clearAllMocks();
93
+ });
94
+
95
+ it("keeps a fresh preview launchable when using the planner recommendation", async () => {
96
+ render(
97
+ <TableEnrichmentSheet
98
+ open
99
+ onOpenChange={() => {}}
100
+ tableId="table-1"
101
+ columns={columns}
102
+ />
103
+ );
104
+
105
+ const dialog = await screen.findByRole("dialog", { name: "Enrich Table" });
106
+ fireEvent.click(within(dialog).getByRole("button", { name: "Preview Plan" }));
107
+
108
+ await screen.findByText("Keep the row flow lightweight.");
109
+
110
+ expect(
111
+ screen.queryByText("Inputs changed after the last preview. Refresh the plan before launching.")
112
+ ).not.toBeInTheDocument();
113
+
114
+ await waitFor(() => {
115
+ expect(within(dialog).getByRole("button", { name: "Launch Enrichment" })).toBeEnabled();
116
+ });
117
+
118
+ fireEvent.click(within(dialog).getByRole("button", { name: "Launch Enrichment" }));
119
+
120
+ await waitFor(() => {
121
+ expect(fetch).toHaveBeenCalledWith(
122
+ "/api/tables/table-1/enrich",
123
+ expect.objectContaining({ method: "POST" })
124
+ );
125
+ expect(pushMock).toHaveBeenCalledWith("/workflows/workflow-1");
126
+ expect(toastErrorMock).not.toHaveBeenCalled();
127
+ expect(toastSuccessMock).toHaveBeenCalled();
128
+ });
129
+ });
130
+ });
@@ -15,6 +15,7 @@ import {
15
15
  import {
16
16
  Sheet,
17
17
  SheetContent,
18
+ SheetDescription,
18
19
  SheetHeader,
19
20
  SheetTitle,
20
21
  SheetFooter,
@@ -132,6 +133,9 @@ export function TableCreateSheet({
132
133
  <SheetContent side="right" className="w-[480px] sm:max-w-[480px]">
133
134
  <SheetHeader>
134
135
  <SheetTitle>Create Table</SheetTitle>
136
+ <SheetDescription>
137
+ Name the table, optionally assign a project, and define the first set of columns.
138
+ </SheetDescription>
135
139
  </SheetHeader>
136
140
 
137
141
  <div className="px-6 pb-6 space-y-4 overflow-y-auto">
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import Link from "next/link";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import { workflowStatusVariant } from "@/lib/constants/status-colors";
8
+
9
+ interface EnrichmentRunSummary {
10
+ workflowId: string;
11
+ name: string;
12
+ status: string;
13
+ updatedAt: string;
14
+ targetColumn: string;
15
+ targetColumnLabel: string;
16
+ rowCount: number;
17
+ strategy:
18
+ | "single-pass-lookup"
19
+ | "single-pass-classify"
20
+ | "research-and-synthesize";
21
+ promptMode: "auto" | "custom";
22
+ }
23
+
24
+ interface TableEnrichmentRunsProps {
25
+ tableId: string;
26
+ refreshKey?: number;
27
+ }
28
+
29
+ export function TableEnrichmentRuns({
30
+ tableId,
31
+ refreshKey = 0,
32
+ }: TableEnrichmentRunsProps) {
33
+ const [runs, setRuns] = useState<EnrichmentRunSummary[]>([]);
34
+ const [loading, setLoading] = useState(true);
35
+
36
+ const loadRuns = useCallback(async () => {
37
+ setLoading(true);
38
+ try {
39
+ const res = await fetch(`/api/tables/${tableId}/enrich/runs?limit=5`);
40
+ if (!res.ok) throw new Error("Failed to load runs");
41
+ const data = (await res.json()) as EnrichmentRunSummary[];
42
+ setRuns(data);
43
+ } catch {
44
+ setRuns([]);
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ }, [tableId]);
49
+
50
+ useEffect(() => {
51
+ loadRuns();
52
+ }, [loadRuns, refreshKey]);
53
+
54
+ if (!loading && runs.length === 0) {
55
+ return null;
56
+ }
57
+
58
+ return (
59
+ <section className="surface-card-muted rounded-lg border p-4 space-y-3">
60
+ <div className="flex items-center justify-between gap-3">
61
+ <div>
62
+ <h3 className="text-sm font-medium">Recent Enrichments</h3>
63
+ <p className="text-xs text-muted-foreground">
64
+ Recent planner-driven enrichment runs for this table.
65
+ </p>
66
+ </div>
67
+ <Button variant="outline" size="sm" onClick={loadRuns}>
68
+ Refresh
69
+ </Button>
70
+ </div>
71
+
72
+ {loading ? (
73
+ <p className="text-sm text-muted-foreground">Loading recent enrichment runs…</p>
74
+ ) : (
75
+ <div className="space-y-2">
76
+ {runs.map((run) => (
77
+ <div
78
+ key={run.workflowId}
79
+ className="surface-control rounded-lg border px-3 py-2 flex items-center justify-between gap-3"
80
+ >
81
+ <div className="min-w-0">
82
+ <div className="flex items-center gap-2 flex-wrap">
83
+ <p className="text-sm font-medium truncate">{run.targetColumnLabel}</p>
84
+ <Badge variant={workflowStatusVariant[run.status] ?? "outline"}>
85
+ {run.status}
86
+ </Badge>
87
+ <Badge variant="outline">{run.promptMode}</Badge>
88
+ </div>
89
+ <p className="text-xs text-muted-foreground">
90
+ {run.rowCount} rows · {run.strategy} ·{" "}
91
+ {new Date(run.updatedAt).toLocaleString()}
92
+ </p>
93
+ </div>
94
+ <Button asChild variant="ghost" size="sm">
95
+ <Link href={`/workflows/${run.workflowId}`}>View</Link>
96
+ </Button>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ )}
101
+ </section>
102
+ );
103
+ }