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
@@ -9,6 +9,47 @@ import { Inbox, Plus, CheckSquare, Square, ArrowRight, Play, Trash2 } from "luci
9
9
  import { TaskCard, type TaskItem } from "./task-card";
10
10
  import { WorkflowKanbanCard, type WorkflowKanbanItem } from "@/components/workflows/workflow-kanban-card";
11
11
  import type { TaskStatus } from "@/lib/constants/task-status";
12
+ import type { SortOrder } from "./kanban-board";
13
+
14
+ type KanbanItem =
15
+ | { kind: "task"; data: TaskItem }
16
+ | { kind: "workflow"; data: WorkflowKanbanItem };
17
+
18
+ function itemName(item: KanbanItem): string {
19
+ return item.kind === "task" ? item.data.title : item.data.name;
20
+ }
21
+
22
+ function mergedItems(
23
+ tasks: TaskItem[],
24
+ workflows: WorkflowKanbanItem[],
25
+ sortOrder: SortOrder
26
+ ): KanbanItem[] {
27
+ const items: KanbanItem[] = [
28
+ ...workflows.map((w) => ({ kind: "workflow" as const, data: w })),
29
+ ...tasks.map((t) => ({ kind: "task" as const, data: t })),
30
+ ];
31
+
32
+ switch (sortOrder) {
33
+ case "created-desc":
34
+ return items.sort(
35
+ (a, b) => new Date(b.data.createdAt).getTime() - new Date(a.data.createdAt).getTime()
36
+ );
37
+ case "created-asc":
38
+ return items.sort(
39
+ (a, b) => new Date(a.data.createdAt).getTime() - new Date(b.data.createdAt).getTime()
40
+ );
41
+ case "title-asc":
42
+ return items.sort((a, b) => itemName(a).localeCompare(itemName(b)));
43
+ case "priority":
44
+ // Workflows lack priority — keep them at top, then sort tasks by priority
45
+ return items.sort((a, b) => {
46
+ if (a.kind === "workflow" && b.kind === "workflow") return 0;
47
+ if (a.kind === "workflow") return -1;
48
+ if (b.kind === "workflow") return 1;
49
+ return a.data.priority - b.data.priority;
50
+ });
51
+ }
52
+ }
12
53
 
13
54
  const columnLabels: Record<string, string> = {
14
55
  planned: "Planned",
@@ -22,6 +63,7 @@ export function KanbanColumn({
22
63
  status,
23
64
  tasks,
24
65
  workflows = [],
66
+ sortOrder = "priority",
25
67
  exitingIds,
26
68
  onTaskClick,
27
69
  onAddTask,
@@ -34,6 +76,7 @@ export function KanbanColumn({
34
76
  status: TaskStatus;
35
77
  tasks: TaskItem[];
36
78
  workflows?: WorkflowKanbanItem[];
79
+ sortOrder?: SortOrder;
37
80
  exitingIds?: Set<string>;
38
81
  onTaskClick: (task: TaskItem) => void;
39
82
  onAddTask?: () => void;
@@ -195,30 +238,26 @@ export function KanbanColumn({
195
238
  </div>
196
239
  ) : (
197
240
  <>
198
- {/* Workflow cards first (not draggable) */}
199
- {workflows.map((workflow) => (
200
- <WorkflowKanbanCard key={workflow.id} workflow={workflow} />
201
- ))}
202
- {/* Task cards (draggable) */}
203
- {tasks.map((task) => {
204
- const isExiting = exitingIds?.has(task.id);
205
- return (
241
+ {mergedItems(tasks, workflows, sortOrder).map((item) =>
242
+ item.kind === "workflow" ? (
243
+ <WorkflowKanbanCard key={item.data.id} workflow={item.data} />
244
+ ) : (
206
245
  <div
207
- key={task.id}
208
- className={isExiting ? "animate-card-exit pointer-events-none" : ""}
246
+ key={item.data.id}
247
+ className={exitingIds?.has(item.data.id) ? "animate-card-exit pointer-events-none" : ""}
209
248
  >
210
249
  <TaskCard
211
- task={task}
250
+ task={item.data}
212
251
  onClick={onTaskClick}
213
252
  selectionMode={selectMode}
214
- selected={selectedIds.has(task.id)}
253
+ selected={selectedIds.has(item.data.id)}
215
254
  onSelect={handleSelect}
216
255
  onDelete={onDeleteTask}
217
256
  onEdit={onEditTask}
218
257
  />
219
258
  </div>
220
- );
221
- })}
259
+ )
260
+ )}
222
261
  </>
223
262
  )}
224
263
  </div>
@@ -10,11 +10,15 @@ import {
10
10
  Timer,
11
11
  Cpu,
12
12
  Paperclip,
13
+ CalendarClock,
14
+ CalendarCheck,
13
15
  } from "lucide-react";
14
16
  import { taskStatusVariant } from "@/lib/constants/status-colors";
17
+ import { formatCompactDateTime } from "@/lib/utils/format-timestamp";
15
18
  import { TaskBentoCell } from "./task-bento-cell";
16
19
  import type { TaskItem } from "./task-card";
17
20
  import type { DocumentRow } from "@/lib/db/schema";
21
+ import { getRuntimeCatalogEntry } from "@/lib/agents/runtime/catalog";
18
22
 
19
23
  const priorityConfig: Record<number, { icon: typeof ArrowUp; label: string }> = {
20
24
  0: { icon: ArrowUp, label: "P0 Critical" },
@@ -75,6 +79,7 @@ export function TaskBentoGrid({ task, docs }: TaskBentoGridProps) {
75
79
 
76
80
  const inputDocs = docs.filter((d) => d.direction === "input");
77
81
  const outputDocs = docs.filter((d) => d.direction === "output");
82
+ const modelId = task.effectiveModelId ?? usage?.modelId ?? null;
78
83
  const docSummary =
79
84
  inputDocs.length > 0 && outputDocs.length > 0
80
85
  ? `${inputDocs.length} in / ${outputDocs.length} out`
@@ -127,6 +132,22 @@ export function TaskBentoGrid({ task, docs }: TaskBentoGridProps) {
127
132
  />
128
133
  )}
129
134
 
135
+ {usage?.startedAt && (
136
+ <TaskBentoCell
137
+ icon={CalendarClock}
138
+ label="Started At"
139
+ value={formatCompactDateTime(usage.startedAt)}
140
+ />
141
+ )}
142
+
143
+ {usage?.finishedAt && (
144
+ <TaskBentoCell
145
+ icon={CalendarCheck}
146
+ label="Finished At"
147
+ value={formatCompactDateTime(usage.finishedAt)}
148
+ />
149
+ )}
150
+
130
151
  {usage?.startedAt && usage?.finishedAt && (
131
152
  <TaskBentoCell
132
153
  icon={Timer}
@@ -135,11 +156,19 @@ export function TaskBentoGrid({ task, docs }: TaskBentoGridProps) {
135
156
  />
136
157
  )}
137
158
 
138
- {usage?.modelId && (
159
+ {task.effectiveRuntimeId && (
160
+ <TaskBentoCell
161
+ icon={Cpu}
162
+ label="Runtime Used"
163
+ value={getRuntimeCatalogEntry(task.effectiveRuntimeId as never).label}
164
+ />
165
+ )}
166
+
167
+ {modelId && (
139
168
  <TaskBentoCell
140
169
  icon={Cpu}
141
170
  label="Model"
142
- value={<span className="text-sm font-semibold">{truncateModel(usage.modelId)}</span>}
171
+ value={<span className="text-sm font-semibold">{truncateModel(modelId)}</span>}
143
172
  />
144
173
  )}
145
174
 
@@ -5,7 +5,8 @@ import { useSortable } from "@dnd-kit/sortable";
5
5
  import { CSS } from "@dnd-kit/utilities";
6
6
  import { Card } from "@/components/ui/card";
7
7
  import { Badge } from "@/components/ui/badge";
8
- import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil, FileText } from "lucide-react";
8
+ import { AlertCircle, Bot, ArrowUp, ArrowDown, Minus, Trash2, Check, X, Loader2, Square, CheckSquare, Pencil, FileText, Clock } from "lucide-react";
9
+ import { formatCompactDateTime } from "@/lib/utils/format-timestamp";
9
10
  import type { TaskStatus } from "@/lib/constants/task-status";
10
11
 
11
12
  export interface TaskItem {
@@ -16,6 +17,9 @@ export interface TaskItem {
16
17
  priority: number;
17
18
  assignedAgent: string | null;
18
19
  agentProfile: string | null;
20
+ effectiveRuntimeId?: string | null;
21
+ effectiveModelId?: string | null;
22
+ runtimeFallbackReason?: string | null;
19
23
  projectId: string | null;
20
24
  projectName?: string;
21
25
  workflowId: string | null;
@@ -122,6 +126,14 @@ export function TaskCard({
122
126
  const showDeleteButton = onDelete && !isRunning;
123
127
  const isEditable = (task.status === "planned" || task.status === "queued") && !!onEdit;
124
128
 
129
+ // Status-contextual date: planned→createdAt, running→startedAt, completed/failed→finishedAt
130
+ const relevantDate =
131
+ task.status === "planned" || task.status === "queued"
132
+ ? task.createdAt
133
+ : task.status === "running"
134
+ ? task.usage?.startedAt ?? task.updatedAt
135
+ : task.usage?.finishedAt ?? task.updatedAt;
136
+
125
137
  return (
126
138
  <Card
127
139
  ref={setNodeRef}
@@ -244,7 +256,13 @@ export function TaskCard({
244
256
  <Pencil className="h-3.5 w-3.5" />
245
257
  </button>
246
258
  )}
247
- <div className="flex-1" />
259
+ <span
260
+ className="flex items-center gap-1 text-xs text-muted-foreground tabular-nums truncate min-w-0 flex-1"
261
+ title={new Date(relevantDate).toLocaleString()}
262
+ >
263
+ <Clock className="h-3 w-3 shrink-0" />
264
+ {formatCompactDateTime(relevantDate)}
265
+ </span>
248
266
  {showDeleteButton && (
249
267
  <button
250
268
  type="button"
@@ -257,7 +275,15 @@ export function TaskCard({
257
275
  )}
258
276
  </>
259
277
  )
260
- ) : null}
278
+ ) : (
279
+ <span
280
+ className="flex items-center gap-1 text-xs text-muted-foreground tabular-nums truncate min-w-0 flex-1"
281
+ title={new Date(relevantDate).toLocaleString()}
282
+ >
283
+ <Clock className="h-3 w-3 shrink-0" />
284
+ {formatCompactDateTime(relevantDate)}
285
+ </span>
286
+ )}
261
287
  </div>
262
288
  </Card>
263
289
  );
@@ -19,11 +19,14 @@ import {
19
19
  ArrowUp,
20
20
  ArrowDown,
21
21
  Minus,
22
+ CalendarClock,
23
+ CalendarCheck,
22
24
  } from "lucide-react";
23
25
  import { taskStatusVariant } from "@/lib/constants/status-colors";
24
26
  import { MAX_RESUME_COUNT } from "@/lib/constants/task-status";
25
27
  import { formatTimestamp } from "@/lib/utils/format-timestamp";
26
28
  import type { TaskItem } from "./task-card";
29
+ import { getRuntimeCatalogEntry } from "@/lib/agents/runtime/catalog";
27
30
 
28
31
  const priorityConfig: Record<number, { icon: typeof ArrowUp; label: string }> = {
29
32
  0: { icon: ArrowUp, label: "P0 Critical" },
@@ -57,6 +60,12 @@ export function TaskChipBar({
57
60
  }: TaskChipBarProps) {
58
61
  const priority = priorityConfig[task.priority] ?? priorityConfig[2];
59
62
  const PriorityIcon = priority.icon;
63
+ const requestedRuntimeLabel = task.assignedAgent
64
+ ? getRuntimeCatalogEntry(task.assignedAgent as never).label
65
+ : null;
66
+ const effectiveRuntimeLabel = task.effectiveRuntimeId
67
+ ? getRuntimeCatalogEntry(task.effectiveRuntimeId as never).label
68
+ : null;
60
69
 
61
70
  const hasRelationships = task.projectId || task.workflowId || task.scheduleId;
62
71
 
@@ -147,7 +156,29 @@ export function TaskChipBar({
147
156
  {task.assignedAgent && (
148
157
  <Badge variant="secondary" className="text-xs gap-1">
149
158
  <Bot className="h-3 w-3" />
150
- {task.assignedAgent}
159
+ {requestedRuntimeLabel ?? task.assignedAgent}
160
+ </Badge>
161
+ )}
162
+
163
+ {task.effectiveRuntimeId &&
164
+ task.effectiveRuntimeId !== task.assignedAgent && (
165
+ <Badge
166
+ variant="outline"
167
+ className="text-xs gap-1 border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300"
168
+ title={task.runtimeFallbackReason ?? undefined}
169
+ >
170
+ <Bot className="h-3 w-3" />
171
+ Ran on {effectiveRuntimeLabel ?? task.effectiveRuntimeId}
172
+ </Badge>
173
+ )}
174
+
175
+ {task.runtimeFallbackReason && (
176
+ <Badge
177
+ variant="outline"
178
+ className="text-xs border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300"
179
+ title={task.runtimeFallbackReason}
180
+ >
181
+ Fallback applied
151
182
  </Badge>
152
183
  )}
153
184
 
@@ -172,6 +203,28 @@ export function TaskChipBar({
172
203
  >
173
204
  Updated {formatTimestamp(task.updatedAt)}
174
205
  </Badge>
206
+
207
+ {task.usage?.startedAt && (
208
+ <Badge
209
+ variant="outline"
210
+ className="text-xs font-normal gap-1"
211
+ title={new Date(task.usage.startedAt).toLocaleString()}
212
+ >
213
+ <CalendarClock className="h-3 w-3" />
214
+ Started {formatTimestamp(task.usage.startedAt)}
215
+ </Badge>
216
+ )}
217
+
218
+ {task.usage?.finishedAt && (
219
+ <Badge
220
+ variant="outline"
221
+ className="text-xs font-normal gap-1"
222
+ title={new Date(task.usage.finishedAt).toLocaleString()}
223
+ >
224
+ <CalendarCheck className="h-3 w-3" />
225
+ Finished {formatTimestamp(task.usage.finishedAt)}
226
+ </Badge>
227
+ )}
175
228
  </div>
176
229
 
177
230
  {/* Row 3: Relationship Links (only if any FK exists) */}
@@ -1,6 +1,6 @@
1
1
  import { Separator } from "@/components/ui/separator";
2
2
  import { LightMarkdown } from "@/components/shared/light-markdown";
3
- import { ExpandableResult } from "@/components/workflows/workflow-status-view";
3
+ import { ExpandableResult } from "@/components/workflows/shared/step-result";
4
4
 
5
5
  interface TaskResultRendererProps {
6
6
  description: string | null;
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Play } from "lucide-react";
6
+ import { toast } from "sonner";
7
+ import { formatDuration } from "@/lib/workflows/delay";
8
+
9
+ /**
10
+ * Body content for a delay step row in the workflow status view. Renders
11
+ * three visual states keyed off the step's status:
12
+ *
13
+ * - Pending (workflow hasn't reached this step yet): "Will wait 3d"
14
+ * - Active delay (workflow paused, waiting): absolute resume time +
15
+ * remaining duration + Resume Now button
16
+ * - Completed (workflow has already passed this step): "Delayed 3d — completed"
17
+ *
18
+ * Countdown is static on mount/focus — no per-second ticking, because live
19
+ * aria-live updates would flood assistive tech users. Users needing a refresh
20
+ * can reload or refocus the page.
21
+ *
22
+ * Extracted from workflow-status-view.tsx during the TDR-031 router refactor
23
+ * so the non-loop subview can import it without a circular dependency on the
24
+ * thin router file.
25
+ */
26
+ export function DelayStepBody({
27
+ workflowId,
28
+ delayDuration,
29
+ stepStatus,
30
+ resumeAt,
31
+ }: {
32
+ workflowId: string;
33
+ delayDuration: string;
34
+ stepStatus: string;
35
+ resumeAt: number | null;
36
+ }) {
37
+ const [resuming, setResuming] = useState(false);
38
+
39
+ const handleResumeNow = useCallback(async () => {
40
+ setResuming(true);
41
+ try {
42
+ const res = await fetch(`/api/workflows/${workflowId}/resume`, { method: "POST" });
43
+ if (res.status === 202) {
44
+ toast.success("Resume dispatched");
45
+ } else if (res.status === 409) {
46
+ toast.info("Workflow already resumed by scheduler");
47
+ } else {
48
+ const body = await res.json().catch(() => ({}));
49
+ toast.error(body.error ?? "Failed to resume workflow");
50
+ }
51
+ } catch (err) {
52
+ toast.error(err instanceof Error ? err.message : "Failed to resume workflow");
53
+ } finally {
54
+ setResuming(false);
55
+ }
56
+ }, [workflowId]);
57
+
58
+ if (stepStatus === "completed") {
59
+ return (
60
+ <p className="text-xs text-muted-foreground mt-0.5">
61
+ Delayed {delayDuration} — completed
62
+ </p>
63
+ );
64
+ }
65
+
66
+ if (stepStatus === "delayed" && resumeAt) {
67
+ const resumeDate = new Date(resumeAt);
68
+ const remainingMs = Math.max(0, resumeAt - Date.now());
69
+ const remainingLabel =
70
+ remainingMs < 60_000
71
+ ? "less than a minute"
72
+ : formatDuration(Math.round(remainingMs / 60_000) * 60_000);
73
+ return (
74
+ <div className="mt-1 space-y-2">
75
+ <p className="text-xs text-status-warning">
76
+ Resumes{" "}
77
+ <time dateTime={resumeDate.toISOString()}>
78
+ {resumeDate.toLocaleString(undefined, {
79
+ weekday: "short",
80
+ month: "short",
81
+ day: "numeric",
82
+ hour: "numeric",
83
+ minute: "2-digit",
84
+ timeZoneName: "short",
85
+ })}
86
+ </time>{" "}
87
+ <span className="text-muted-foreground">({remainingLabel} remaining)</span>
88
+ </p>
89
+ <Button
90
+ size="sm"
91
+ variant="outline"
92
+ onClick={handleResumeNow}
93
+ disabled={resuming}
94
+ aria-label="Resume workflow now"
95
+ >
96
+ <Play className="h-3 w-3 mr-1" />
97
+ {resuming ? "Resuming..." : "Resume Now"}
98
+ </Button>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ // Pending / upcoming delay — workflow hasn't reached this step yet
104
+ return (
105
+ <p className="text-xs text-muted-foreground mt-0.5">
106
+ Will wait {delayDuration}
107
+ </p>
108
+ );
109
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import type { WorkflowStatusResponse } from "@/lib/workflows/types";
5
+
6
+ /**
7
+ * Polling hook for `GET /api/workflows/[id]/status`.
8
+ *
9
+ * Owns the fetch, the 3-second interval, cancellation on unmount, and
10
+ * re-subscription when `workflowId` changes. Exposes `setData` so callers can
11
+ * apply optimistic updates (e.g. flipping a step to "running" immediately when
12
+ * Execute is clicked) without racing the next poll tick.
13
+ *
14
+ * Returns `WorkflowStatusResponse | null` — narrowing on `data.pattern` is the
15
+ * caller's job per TDR-031. This hook intentionally does not narrow for
16
+ * consumers; each pattern-specific subview handles its own arm.
17
+ */
18
+ export function useWorkflowStatus(workflowId: string): {
19
+ data: WorkflowStatusResponse | null;
20
+ setData: (updater: (current: WorkflowStatusResponse | null) => WorkflowStatusResponse | null) => void;
21
+ refetch: () => Promise<void>;
22
+ } {
23
+ const [data, setDataInternal] = useState<WorkflowStatusResponse | null>(null);
24
+
25
+ const refetch = useCallback(async () => {
26
+ const res = await fetch(`/api/workflows/${workflowId}/status`);
27
+ if (res.ok) {
28
+ const json = (await res.json()) as WorkflowStatusResponse;
29
+ setDataInternal(json);
30
+ }
31
+ }, [workflowId]);
32
+
33
+ useEffect(() => {
34
+ // Reset data when the workflowId changes so the old workflow's state
35
+ // never briefly shows while the new one is fetching.
36
+ setDataInternal(null);
37
+ refetch();
38
+ const interval = setInterval(refetch, 3000);
39
+ return () => clearInterval(interval);
40
+ }, [refetch]);
41
+
42
+ const setData = useCallback(
43
+ (updater: (current: WorkflowStatusResponse | null) => WorkflowStatusResponse | null) => {
44
+ setDataInternal((prev) => updater(prev));
45
+ },
46
+ []
47
+ );
48
+
49
+ return { data, setData, refetch };
50
+ }
@@ -3,7 +3,7 @@
3
3
  import { useState } from "react";
4
4
  import { Badge } from "@/components/ui/badge";
5
5
  import { Button } from "@/components/ui/button";
6
- import { ExpandableResult } from "./workflow-status-view";
6
+ import { ExpandableResult } from "./shared/step-result";
7
7
  import {
8
8
  CheckCircle,
9
9
  Circle,
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Link from "next/link";
5
+ import ReactMarkdown from "react-markdown";
6
+ import remarkGfm from "remark-gfm";
7
+ import { FileText } from "lucide-react";
8
+ import { PROSE_NOTIFICATION } from "@/lib/constants/prose-styles";
9
+ import type { WorkflowStatusDocument } from "@/lib/workflows/types";
10
+
11
+ /**
12
+ * Expandable step result with gradient fade progressive disclosure.
13
+ * Extracted from the legacy workflow-status-view god component so it can be
14
+ * reused by pattern-specific subviews and LoopStatusView without a circular
15
+ * import.
16
+ */
17
+ export function ExpandableResult({ result }: { result: string }) {
18
+ const [expanded, setExpanded] = useState(false);
19
+
20
+ if (!result) return null;
21
+
22
+ return (
23
+ <div className="mt-2">
24
+ <div
25
+ className={`${PROSE_NOTIFICATION} ${
26
+ expanded
27
+ ? "max-h-96 overflow-auto"
28
+ : result.length > 200
29
+ ? "max-h-20 overflow-hidden mask-fade-bottom"
30
+ : ""
31
+ }`}
32
+ >
33
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{result}</ReactMarkdown>
34
+ </div>
35
+ {result.length > 200 && (
36
+ <button
37
+ type="button"
38
+ className="mt-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
39
+ onClick={() => setExpanded(!expanded)}
40
+ >
41
+ {expanded ? "Show less" : "Show more"}
42
+ </button>
43
+ )}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Document list for a single step or parent task — renders a labeled cluster
50
+ * of links to document detail pages.
51
+ */
52
+ export function DocumentList({
53
+ docs,
54
+ label,
55
+ }: {
56
+ docs: WorkflowStatusDocument[];
57
+ label: string;
58
+ }) {
59
+ if (docs.length === 0) return null;
60
+
61
+ return (
62
+ <div className="mt-3">
63
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">{label}</p>
64
+ <div className="space-y-1">
65
+ {docs.map((doc) => (
66
+ <Link
67
+ key={doc.id}
68
+ href={`/documents/${doc.id}`}
69
+ className="flex items-center gap-2 text-xs text-brand-blue hover:underline"
70
+ >
71
+ <FileText className="h-3 w-3 shrink-0" />
72
+ {doc.originalName}
73
+ </Link>
74
+ ))}
75
+ </div>
76
+ </div>
77
+ );
78
+ }