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,108 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ resolvePostAction,
4
+ shouldSkipPostActionValue,
5
+ extractPostActionValue,
6
+ } from "../post-action";
7
+ import type { StepPostAction } from "../types";
8
+
9
+ describe("resolvePostAction", () => {
10
+ it("substitutes {{row.id}} with the row's id field", () => {
11
+ const action: StepPostAction = {
12
+ type: "update_row",
13
+ tableId: "tbl_contacts",
14
+ rowId: "{{row.id}}",
15
+ column: "linkedin",
16
+ };
17
+ const row = { id: "row_abc", name: "Alice" };
18
+
19
+ const resolved = resolvePostAction(action, row, "row");
20
+
21
+ expect(resolved.rowId).toBe("row_abc");
22
+ expect(resolved.tableId).toBe("tbl_contacts");
23
+ expect(resolved.column).toBe("linkedin");
24
+ });
25
+
26
+ it("respects a custom itemVariable name", () => {
27
+ const action: StepPostAction = {
28
+ type: "update_row",
29
+ tableId: "tbl_x",
30
+ rowId: "{{contact.id}}",
31
+ column: "email",
32
+ };
33
+ const row = { id: "c_42" };
34
+
35
+ const resolved = resolvePostAction(action, row, "contact");
36
+
37
+ expect(resolved.rowId).toBe("c_42");
38
+ });
39
+
40
+ it("supports nested field paths like {{row.meta.id}}", () => {
41
+ const action: StepPostAction = {
42
+ type: "update_row",
43
+ tableId: "tbl_x",
44
+ rowId: "{{row.meta.id}}",
45
+ column: "value",
46
+ };
47
+ const row = { meta: { id: "nested_99" } };
48
+
49
+ const resolved = resolvePostAction(action, row, "row");
50
+
51
+ expect(resolved.rowId).toBe("nested_99");
52
+ });
53
+
54
+ it("leaves rowId untouched when no placeholder is present", () => {
55
+ const action: StepPostAction = {
56
+ type: "update_row",
57
+ tableId: "tbl_x",
58
+ rowId: "literal_row_id",
59
+ column: "value",
60
+ };
61
+
62
+ const resolved = resolvePostAction(action, { id: "ignored" }, "row");
63
+
64
+ expect(resolved.rowId).toBe("literal_row_id");
65
+ });
66
+ });
67
+
68
+ describe("shouldSkipPostActionValue", () => {
69
+ it("skips empty strings", () => {
70
+ expect(shouldSkipPostActionValue("")).toBe(true);
71
+ expect(shouldSkipPostActionValue(" ")).toBe(true);
72
+ expect(shouldSkipPostActionValue("\n\t")).toBe(true);
73
+ });
74
+
75
+ it("skips NOT_FOUND sentinel (case-insensitive)", () => {
76
+ expect(shouldSkipPostActionValue("NOT_FOUND")).toBe(true);
77
+ expect(shouldSkipPostActionValue("not_found")).toBe(true);
78
+ expect(shouldSkipPostActionValue(" NOT_FOUND ")).toBe(true);
79
+ expect(shouldSkipPostActionValue("Not_Found")).toBe(true);
80
+ });
81
+
82
+ it("does not skip real values", () => {
83
+ expect(shouldSkipPostActionValue("https://linkedin.com/in/alice")).toBe(false);
84
+ expect(shouldSkipPostActionValue("alice@example.com")).toBe(false);
85
+ expect(shouldSkipPostActionValue("0")).toBe(false);
86
+ });
87
+
88
+ it("does not skip values that merely contain NOT_FOUND as a substring", () => {
89
+ // We only skip when the trimmed value IS the sentinel; substrings stay.
90
+ expect(shouldSkipPostActionValue("Status: NOT_FOUND in registry")).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe("extractPostActionValue", () => {
95
+ it("trims whitespace from the agent result", () => {
96
+ expect(extractPostActionValue(" hello ")).toBe("hello");
97
+ expect(extractPostActionValue("\nhttps://example.com\n")).toBe("https://example.com");
98
+ });
99
+
100
+ it("returns the raw string for normal values", () => {
101
+ expect(extractPostActionValue("plain")).toBe("plain");
102
+ });
103
+
104
+ it("returns empty string for null/undefined-shaped inputs", () => {
105
+ expect(extractPostActionValue(undefined)).toBe("");
106
+ expect(extractPostActionValue("")).toBe("");
107
+ });
108
+ });
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ renderBlueprintPrompt,
4
+ UnresolvedTokenError,
5
+ } from "../render-prompt";
6
+ import type { WorkflowBlueprint } from "../types";
7
+
8
+ function makeBlueprint(
9
+ overrides: Partial<WorkflowBlueprint> = {}
10
+ ): WorkflowBlueprint {
11
+ return {
12
+ id: "test-bp",
13
+ name: "Test Blueprint",
14
+ description: "A blueprint for tests",
15
+ version: "1.0.0",
16
+ domain: "work",
17
+ tags: [],
18
+ pattern: "sequence",
19
+ variables: [],
20
+ steps: [],
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ describe("renderBlueprintPrompt", () => {
26
+ it("uses chatPrompt when present", () => {
27
+ const bp = makeBlueprint({
28
+ chatPrompt: "Research {{topic}} for {{timeframe}}.",
29
+ steps: [
30
+ { name: "s1", promptTemplate: "fallback should not be used", requiresApproval: false },
31
+ ],
32
+ });
33
+ const out = renderBlueprintPrompt(bp, { topic: "AI agents", timeframe: "2026" });
34
+ expect(out.firstMessage).toBe("Research AI agents for 2026.");
35
+ });
36
+
37
+ it("falls back to steps[0].promptTemplate when chatPrompt absent", () => {
38
+ const bp = makeBlueprint({
39
+ steps: [
40
+ {
41
+ name: "s1",
42
+ promptTemplate: "Summarize {{topic}} in ≤500 words.",
43
+ requiresApproval: false,
44
+ },
45
+ ],
46
+ });
47
+ const out = renderBlueprintPrompt(bp, { topic: "browser agents" });
48
+ expect(out.firstMessage).toBe("Summarize browser agents in ≤500 words.");
49
+ });
50
+
51
+ it("renders title with variable substitution", () => {
52
+ const bp = makeBlueprint({
53
+ name: "Research {{topic}}",
54
+ chatPrompt: "go",
55
+ });
56
+ const out = renderBlueprintPrompt(bp, { topic: "SSE streams" });
57
+ expect(out.title).toBe("Research SSE streams");
58
+ });
59
+
60
+ it("returns empty firstMessage when no chatPrompt and no steps", () => {
61
+ const bp = makeBlueprint({ steps: [] });
62
+ const out = renderBlueprintPrompt(bp, {});
63
+ expect(out.firstMessage).toBe("");
64
+ expect(out.title).toBe("Test Blueprint");
65
+ });
66
+
67
+ it("substitutes missing optional variables with empty string (non-strict)", () => {
68
+ const bp = makeBlueprint({
69
+ chatPrompt: "Topic: {{topic}}. Scope: {{scope}}.",
70
+ });
71
+ const out = renderBlueprintPrompt(bp, { topic: "rate limits" });
72
+ expect(out.firstMessage).toBe("Topic: rate limits. Scope: .");
73
+ });
74
+
75
+ it("handles {{#if}} conditional blocks", () => {
76
+ const bp = makeBlueprint({
77
+ chatPrompt:
78
+ "Base prompt.{{#if extra}}\n\nExtra: {{extra}}{{/if}}",
79
+ });
80
+ const withExtra = renderBlueprintPrompt(bp, { extra: "deep dive" });
81
+ expect(withExtra.firstMessage).toBe("Base prompt.\n\nExtra: deep dive");
82
+
83
+ const withoutExtra = renderBlueprintPrompt(bp, {});
84
+ expect(withoutExtra.firstMessage).toBe("Base prompt.");
85
+ });
86
+
87
+ it("throws UnresolvedTokenError in strict mode when token is missing", () => {
88
+ const bp = makeBlueprint({
89
+ chatPrompt: "Hello {{name}}, today is {{date}}.",
90
+ });
91
+ expect(() =>
92
+ renderBlueprintPrompt(bp, { name: "Ada" }, { strict: true })
93
+ ).toThrow(UnresolvedTokenError);
94
+
95
+ try {
96
+ renderBlueprintPrompt(bp, { name: "Ada" }, { strict: true });
97
+ } catch (err) {
98
+ expect(err).toBeInstanceOf(UnresolvedTokenError);
99
+ expect((err as UnresolvedTokenError).tokens).toEqual(["date"]);
100
+ }
101
+ });
102
+
103
+ it("does not throw in strict mode when all tokens resolve", () => {
104
+ const bp = makeBlueprint({
105
+ chatPrompt: "Hello {{name}}.",
106
+ });
107
+ const out = renderBlueprintPrompt(
108
+ bp,
109
+ { name: "Ada" },
110
+ { strict: true }
111
+ );
112
+ expect(out.firstMessage).toBe("Hello Ada.");
113
+ });
114
+
115
+ it("strict mode treats empty-string values as resolved (not unresolved)", () => {
116
+ // Empty string is a resolved-to-empty substitution, distinct from an
117
+ // undefined variable which leaves {{token}} in the output string.
118
+ const bp = makeBlueprint({
119
+ chatPrompt: "Name: [{{name}}]",
120
+ });
121
+ const out = renderBlueprintPrompt(bp, { name: "" }, { strict: true });
122
+ expect(out.firstMessage).toBe("Name: []");
123
+ });
124
+ });
@@ -2,7 +2,7 @@ import { db } from "@/lib/db";
2
2
  import { workflows } from "@/lib/db/schema";
3
3
  import { getBlueprint } from "./registry";
4
4
  import { resolveTemplate, evaluateCondition } from "./template";
5
- import type { BlueprintVariable, WorkflowBlueprint } from "./types";
5
+ import type { BlueprintVariable } from "./types";
6
6
  import type { WorkflowStep } from "../types";
7
7
 
8
8
  interface InstantiateResult {
@@ -48,6 +48,27 @@ export async function instantiateBlueprint(
48
48
  continue;
49
49
  }
50
50
 
51
+ // Delay step: a pure time wait with no prompt/profile. Blueprint validation
52
+ // enforces that delayDuration and profileId+promptTemplate are mutually
53
+ // exclusive (XOR), so branching here is safe.
54
+ if (step.delayDuration) {
55
+ resolvedSteps.push({
56
+ id: crypto.randomUUID(),
57
+ name: step.name,
58
+ prompt: "",
59
+ requiresApproval: step.requiresApproval,
60
+ delayDuration: step.delayDuration,
61
+ });
62
+ continue;
63
+ }
64
+
65
+ // Task step: profileId + promptTemplate must be present (XOR contract).
66
+ if (!step.promptTemplate) {
67
+ throw new Error(
68
+ `Blueprint step "${step.name}" has no promptTemplate — blueprint validation should have caught this.`,
69
+ );
70
+ }
71
+
51
72
  const resolvedPrompt = resolveTemplate(step.promptTemplate, resolvedVars);
52
73
 
53
74
  resolvedSteps.push({
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Render a blueprint into a chat-ready first message.
3
+ *
4
+ * Pure template substitution over `blueprint.chatPrompt` with fallback to
5
+ * `blueprint.steps[0].promptTemplate` for the 13 existing built-ins that
6
+ * predate the `chatPrompt` field. Variable resolution is shared with the
7
+ * workflow engine via `resolveTemplate` so behavior stays consistent.
8
+ */
9
+
10
+ import type { WorkflowBlueprint } from "./types";
11
+ import { resolveTemplate } from "./template";
12
+
13
+ export interface RenderedBlueprintPrompt {
14
+ /** The seed message to pre-fill into the chat composer. */
15
+ firstMessage: string;
16
+ /** The conversation title, with variable substitution applied. */
17
+ title: string;
18
+ }
19
+
20
+ export interface RenderBlueprintPromptOptions {
21
+ /**
22
+ * Throw if any `{{token}}` references an undefined variable. Defaults to
23
+ * false — the underlying `resolveTemplate` substitutes undefined values with
24
+ * empty strings, which is the right behavior for optional blueprint vars.
25
+ * Set to `true` to validate that all referenced tokens were resolved.
26
+ */
27
+ strict?: boolean;
28
+ }
29
+
30
+ export class UnresolvedTokenError extends Error {
31
+ constructor(public readonly tokens: string[]) {
32
+ super(`Unresolved template tokens: ${tokens.join(", ")}`);
33
+ this.name = "UnresolvedTokenError";
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Render a blueprint's chat prompt and title with the provided parameters.
39
+ *
40
+ * Falls back to `steps[0].promptTemplate` if `chatPrompt` is absent. If the
41
+ * blueprint has no steps and no `chatPrompt`, returns an empty first message
42
+ * (the picker UI is expected to validate this case upstream).
43
+ */
44
+ export function renderBlueprintPrompt(
45
+ blueprint: WorkflowBlueprint,
46
+ params: Record<string, unknown>,
47
+ options: RenderBlueprintPromptOptions = {}
48
+ ): RenderedBlueprintPrompt {
49
+ const source =
50
+ blueprint.chatPrompt ?? blueprint.steps[0]?.promptTemplate ?? "";
51
+
52
+ if (options.strict) {
53
+ // Collect {{token}} names from the source (before resolveTemplate
54
+ // substitutes undefined with empty string). Skip `#if`/`/if` directives.
55
+ const unresolved = new Set<string>();
56
+ for (const combined of [source, blueprint.name]) {
57
+ const matches = combined.matchAll(/\{\{(\w+)\}\}/g);
58
+ for (const m of matches) {
59
+ if (!(m[1] in params)) unresolved.add(m[1]);
60
+ }
61
+ }
62
+ if (unresolved.size > 0) {
63
+ throw new UnresolvedTokenError([...unresolved]);
64
+ }
65
+ }
66
+
67
+ const firstMessage = resolveTemplate(source, params);
68
+ const title = resolveTemplate(blueprint.name, params);
69
+
70
+ return { firstMessage, title };
71
+ }
@@ -11,10 +11,18 @@ export interface BlueprintVariable {
11
11
  max?: number;
12
12
  }
13
13
 
14
+ /**
15
+ * A blueprint step is either a task step (profileId + promptTemplate) OR a
16
+ * delay step (delayDuration only). The XOR is enforced at validation time
17
+ * by BlueprintStepSchema in src/lib/validators/blueprint.ts — at the type
18
+ * level, all three fields are optional so either shape is assignable.
19
+ */
14
20
  export interface BlueprintStep {
15
21
  name: string;
16
- profileId: string;
17
- promptTemplate: string;
22
+ profileId?: string;
23
+ promptTemplate?: string;
24
+ /** If set, this step is a pure time delay. Format: Nm|Nh|Nd|Nw. */
25
+ delayDuration?: string;
18
26
  requiresApproval: boolean;
19
27
  expectedOutput?: string;
20
28
  condition?: string;
@@ -35,4 +43,10 @@ export interface WorkflowBlueprint {
35
43
  estimatedDuration?: string;
36
44
  difficulty?: "beginner" | "intermediate" | "advanced";
37
45
  isBuiltin?: boolean;
46
+ /**
47
+ * Optional chat-composer seed prompt. When present, `chat-conversation-templates`
48
+ * renders this (with variable substitution) into the first user message of a
49
+ * new conversation. When absent, consumers fall back to `steps[0].promptTemplate`.
50
+ */
51
+ chatPrompt?: string;
38
52
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Duration parser and formatter for workflow step delays.
3
+ *
4
+ * Format: Nm (minutes), Nh (hours), Nd (days), Nw (weeks).
5
+ * Bounds: minimum 1 minute, maximum 30 days.
6
+ * Compound formats (e.g. "3d2h") are not supported.
7
+ *
8
+ * See features/workflow-step-delays.md for the spec.
9
+ */
10
+
11
+ const MS_PER_MINUTE = 60_000;
12
+ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
13
+ const MS_PER_DAY = 24 * MS_PER_HOUR;
14
+ const MS_PER_WEEK = 7 * MS_PER_DAY;
15
+
16
+ const MIN_DURATION_MS = MS_PER_MINUTE;
17
+ const MAX_DURATION_MS = 30 * MS_PER_DAY;
18
+
19
+ const DURATION_PATTERN = /^(\d+)(m|h|d|w)$/;
20
+
21
+ const UNIT_MS: Record<string, number> = {
22
+ m: MS_PER_MINUTE,
23
+ h: MS_PER_HOUR,
24
+ d: MS_PER_DAY,
25
+ w: MS_PER_WEEK,
26
+ };
27
+
28
+ /**
29
+ * Parse a duration string into milliseconds.
30
+ *
31
+ * @throws if the format is invalid or the value is outside bounds.
32
+ */
33
+ export function parseDuration(input: string): number {
34
+ const match = input.match(DURATION_PATTERN);
35
+ if (!match) {
36
+ throw new Error(
37
+ `Invalid duration: "${input}". Use format: 30m, 2h, 3d, 1w`,
38
+ );
39
+ }
40
+
41
+ const value = Number.parseInt(match[1], 10);
42
+ const unit = match[2];
43
+ const ms = value * UNIT_MS[unit];
44
+
45
+ if (ms < MIN_DURATION_MS) {
46
+ throw new Error(`Duration below minimum: "${input}". Minimum is 1 minute (1m).`);
47
+ }
48
+ if (ms > MAX_DURATION_MS) {
49
+ throw new Error(`Duration above maximum: "${input}". Maximum is 30 days (30d).`);
50
+ }
51
+
52
+ return ms;
53
+ }
54
+
55
+ /**
56
+ * Result of checking whether a workflow step is a delay step or a task step.
57
+ * The engine branches on this — delay steps pause the workflow, task steps
58
+ * execute normally.
59
+ */
60
+ export type DelayCheck =
61
+ | { type: "task" }
62
+ | { type: "delay"; resumeAt: number };
63
+
64
+ /**
65
+ * Classify a workflow step as either a delay step or a task step, and compute
66
+ * the resume timestamp for delay steps.
67
+ *
68
+ * Pure function: no I/O, no side effects. The engine calls this inside the
69
+ * sequence executor loop and branches on the result. Invalid duration formats
70
+ * throw — blueprint validation (src/lib/validators/blueprint.ts) should catch
71
+ * these at the workflow-creation boundary, so any invalid duration reaching
72
+ * here is a programming error and must fail loudly.
73
+ *
74
+ * @param step Workflow step (any object with an optional delayDuration field)
75
+ * @param now Current epoch timestamp in milliseconds (injected for testability)
76
+ */
77
+ export function checkDelayStep(
78
+ step: { delayDuration?: string },
79
+ now: number,
80
+ ): DelayCheck {
81
+ if (!step.delayDuration) {
82
+ return { type: "task" };
83
+ }
84
+ return {
85
+ type: "delay",
86
+ resumeAt: now + parseDuration(step.delayDuration),
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Format a millisecond duration back into the canonical string form.
92
+ * Prefers the largest unit that divides cleanly; falls back to minutes
93
+ * when no larger unit divides evenly.
94
+ */
95
+ export function formatDuration(ms: number): string {
96
+ if (ms >= MS_PER_WEEK && ms % MS_PER_WEEK === 0) {
97
+ return `${ms / MS_PER_WEEK}w`;
98
+ }
99
+ if (ms >= MS_PER_DAY && ms % MS_PER_DAY === 0) {
100
+ return `${ms / MS_PER_DAY}d`;
101
+ }
102
+ if (ms >= MS_PER_HOUR && ms % MS_PER_HOUR === 0) {
103
+ return `${ms / MS_PER_HOUR}h`;
104
+ }
105
+ return `${ms / MS_PER_MINUTE}m`;
106
+ }