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,351 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ interface WorkflowRow {
4
+ id: string;
5
+ name: string;
6
+ definition: string | null;
7
+ projectId: string | null;
8
+ }
9
+
10
+ const { mockWorkflowRows } = vi.hoisted(() => ({
11
+ mockWorkflowRows: { value: [] as WorkflowRow[] },
12
+ }));
13
+
14
+ // Minimal drizzle query builder stub — supports
15
+ // db.select({...}).from(table).where(...)
16
+ // by returning a thenable that resolves to mockWorkflowRows.value.
17
+ vi.mock("@/lib/db", () => {
18
+ const builder = {
19
+ from() {
20
+ return this;
21
+ },
22
+ where() {
23
+ return this;
24
+ },
25
+ then<TResolve>(resolve: (rows: WorkflowRow[]) => TResolve) {
26
+ return Promise.resolve(mockWorkflowRows.value).then(resolve);
27
+ },
28
+ };
29
+ return {
30
+ db: {
31
+ select: () => builder,
32
+ },
33
+ };
34
+ });
35
+
36
+ // Stub the schema import so drizzle-orm doesn't try to read a real table.
37
+ vi.mock("@/lib/db/schema", () => ({
38
+ workflows: { projectId: "projectId" },
39
+ tasks: {},
40
+ agentLogs: {},
41
+ notifications: {},
42
+ documents: {},
43
+ workflowDocumentInputs: {},
44
+ }));
45
+
46
+ // Stub drizzle-orm operators used in workflow-tools.ts — the tests only
47
+ // care about the return value of the builder, not the operator objects.
48
+ vi.mock("drizzle-orm", () => ({
49
+ eq: () => ({}),
50
+ and: () => ({}),
51
+ desc: () => ({}),
52
+ inArray: () => ({}),
53
+ like: () => ({}),
54
+ }));
55
+
56
+ import { findSimilarWorkflows } from "../workflow-tools";
57
+
58
+ function setRows(rows: WorkflowRow[]) {
59
+ mockWorkflowRows.value = rows;
60
+ }
61
+
62
+ describe("findSimilarWorkflows", () => {
63
+ beforeEach(() => {
64
+ setRows([]);
65
+ });
66
+
67
+ it("returns [] when projectId is null (no cross-project dedup)", async () => {
68
+ setRows([
69
+ {
70
+ id: "wf1",
71
+ name: "Research Customer Feedback",
72
+ definition: JSON.stringify({
73
+ pattern: "sequence",
74
+ steps: [{ id: "s1", name: "Research customer feedback", prompt: "do research" }],
75
+ }),
76
+ projectId: null,
77
+ },
78
+ ]);
79
+
80
+ const result = await findSimilarWorkflows(
81
+ null,
82
+ "Research Customer Feedback",
83
+ JSON.stringify({ pattern: "sequence", steps: [] })
84
+ );
85
+ expect(result).toEqual([]);
86
+ });
87
+
88
+ it("returns [] when no workflows exist in the project", async () => {
89
+ setRows([]);
90
+ const result = await findSimilarWorkflows(
91
+ "proj_a",
92
+ "Any name",
93
+ JSON.stringify({ pattern: "sequence", steps: [] })
94
+ );
95
+ expect(result).toEqual([]);
96
+ });
97
+
98
+ it("matches exact name (case-insensitive) with similarity 1.0", async () => {
99
+ setRows([
100
+ {
101
+ id: "wf1",
102
+ name: "Research Customer Feedback",
103
+ definition: null,
104
+ projectId: "proj_a",
105
+ },
106
+ ]);
107
+
108
+ const result = await findSimilarWorkflows(
109
+ "proj_a",
110
+ "research customer feedback",
111
+ JSON.stringify({ pattern: "sequence", steps: [] })
112
+ );
113
+
114
+ expect(result).toHaveLength(1);
115
+ expect(result[0]).toMatchObject({
116
+ id: "wf1",
117
+ similarity: 1,
118
+ });
119
+ expect(result[0].reason).toContain("Same name");
120
+ });
121
+
122
+ it("matches on Jaccard similarity over step names + prompts (redesign scenario)", async () => {
123
+ // Simulates the bug scenario: LLM "redesigns" a workflow mid-conversation,
124
+ // using mostly the same vocabulary as the original. The definitions are
125
+ // near-identical (as redesigns typically are in practice) so Jaccard
126
+ // should exceed the 0.7 threshold.
127
+ const sharedSteps = [
128
+ { id: "s1", name: "Research customer cohort", prompt: "Investigate customer research cohort feedback insights" },
129
+ { id: "s2", name: "Interview protocol draft", prompt: "Draft customer interview questions protocol script" },
130
+ { id: "s3", name: "Synthesize findings", prompt: "Summarize customer research findings insights report" },
131
+ ];
132
+ setRows([
133
+ {
134
+ id: "wf1",
135
+ name: "Customer Discovery Pipeline",
136
+ definition: JSON.stringify({ pattern: "sequence", steps: sharedSteps }),
137
+ projectId: "proj_a",
138
+ },
139
+ ]);
140
+
141
+ const result = await findSimilarWorkflows(
142
+ "proj_a",
143
+ "Customer Discovery Workflow v2",
144
+ JSON.stringify({ pattern: "sequence", steps: sharedSteps })
145
+ );
146
+
147
+ expect(result.length).toBeGreaterThanOrEqual(1);
148
+ expect(result[0].id).toBe("wf1");
149
+ expect(result[0].similarity).toBeGreaterThanOrEqual(0.7);
150
+ });
151
+
152
+ it("does NOT match when names and step text are completely different", async () => {
153
+ setRows([
154
+ {
155
+ id: "wf1",
156
+ name: "Deploy frontend release",
157
+ definition: JSON.stringify({
158
+ pattern: "sequence",
159
+ steps: [{ id: "s1", name: "Deploy staging", prompt: "Push release artifact to staging environment" }],
160
+ }),
161
+ projectId: "proj_a",
162
+ },
163
+ ]);
164
+
165
+ const result = await findSimilarWorkflows(
166
+ "proj_a",
167
+ "Customer interview analysis",
168
+ JSON.stringify({
169
+ pattern: "sequence",
170
+ steps: [{ id: "s2", name: "Summarize interviews", prompt: "Pull insights from recent customer interviews" }],
171
+ })
172
+ );
173
+
174
+ expect(result).toEqual([]);
175
+ });
176
+
177
+ it("caps results at 3 and sorts by similarity descending", async () => {
178
+ // Four rows, all exact-name matches (similarity 1.0). Expect exactly 3 returned.
179
+ setRows(
180
+ Array.from({ length: 4 }).map((_, i) => ({
181
+ id: `wf${i}`,
182
+ name: "Duplicate Workflow",
183
+ definition: null,
184
+ projectId: "proj_a",
185
+ }))
186
+ );
187
+
188
+ const result = await findSimilarWorkflows(
189
+ "proj_a",
190
+ "Duplicate Workflow",
191
+ JSON.stringify({ pattern: "sequence", steps: [] })
192
+ );
193
+
194
+ expect(result).toHaveLength(3);
195
+ expect(result.every((r) => r.similarity === 1)).toBe(true);
196
+ });
197
+
198
+ it("handles malformed definition JSON without crashing", async () => {
199
+ setRows([
200
+ {
201
+ id: "wf1",
202
+ name: "Legit Workflow",
203
+ definition: "not-json-at-all",
204
+ projectId: "proj_a",
205
+ },
206
+ ]);
207
+
208
+ // Should not throw — just degrades to name-only comparison.
209
+ const result = await findSimilarWorkflows(
210
+ "proj_a",
211
+ "Legit Workflow",
212
+ "also not json"
213
+ );
214
+ expect(result).toHaveLength(1);
215
+ expect(result[0].similarity).toBe(1); // exact name match
216
+ });
217
+
218
+ // ── Legitimate variant tolerance ────────────────────────────────────
219
+ //
220
+ // Regression tests for the concern flagged in the code review of
221
+ // commit b5ed09b: that WORKFLOW_DEDUP_THRESHOLD = 0.7 on a pooled
222
+ // Jaccard over keywords would flag legitimate target-entity variants
223
+ // (e.g. "Enrich contacts" vs "Enrich accounts") as duplicates,
224
+ // eroding trust in the guardrail. Each pair here shares a dominant
225
+ // verb and most of the step structure — the only difference is the
226
+ // target entity noun.
227
+ //
228
+ // Success criterion per spec:
229
+ // - the two "positive-variant" cases must return [] (no match)
230
+ // - the two "guard" cases must still flag duplicates (similarity >=
231
+ // WORKFLOW_DEDUP_THRESHOLD, or exact-name match)
232
+ describe("legitimate variant tolerance", () => {
233
+ it("allows Enrich contacts and Enrich accounts as distinct workflows", async () => {
234
+ setRows([
235
+ {
236
+ id: "wf1",
237
+ name: "Enrich contacts",
238
+ definition: JSON.stringify({
239
+ pattern: "sequence",
240
+ steps: [
241
+ { id: "s1", name: "Load rows from contacts table", prompt: "Select rows from the contacts table" },
242
+ { id: "s2", name: "Call enrichment agent", prompt: "Invoke enrichment agent on each row" },
243
+ { id: "s3", name: "Write back to table", prompt: "Write enriched data back to the contacts table" },
244
+ ],
245
+ }),
246
+ projectId: "proj_a",
247
+ },
248
+ ]);
249
+
250
+ const result = await findSimilarWorkflows(
251
+ "proj_a",
252
+ "Enrich accounts",
253
+ JSON.stringify({
254
+ pattern: "sequence",
255
+ steps: [
256
+ { id: "s1", name: "Load rows from accounts table", prompt: "Select rows from the accounts table" },
257
+ { id: "s2", name: "Call enrichment agent", prompt: "Invoke enrichment agent on each row" },
258
+ { id: "s3", name: "Write back to table", prompt: "Write enriched data back to the accounts table" },
259
+ ],
260
+ })
261
+ );
262
+
263
+ expect(result).toEqual([]);
264
+ });
265
+
266
+ it("allows Daily standup digest and Weekly standup digest as distinct workflows", async () => {
267
+ setRows([
268
+ {
269
+ id: "wf1",
270
+ name: "Daily standup digest",
271
+ definition: JSON.stringify({
272
+ pattern: "sequence",
273
+ steps: [
274
+ { id: "s1", name: "Fetch standup messages", prompt: "Pull daily standup messages from the team channel" },
275
+ { id: "s2", name: "Summarize daily topics", prompt: "Write a daily digest of key topics and blockers" },
276
+ { id: "s3", name: "Post digest to channel", prompt: "Post the daily summary digest to the #ops channel" },
277
+ ],
278
+ }),
279
+ projectId: "proj_a",
280
+ },
281
+ ]);
282
+
283
+ const result = await findSimilarWorkflows(
284
+ "proj_a",
285
+ "Weekly standup digest",
286
+ JSON.stringify({
287
+ pattern: "sequence",
288
+ steps: [
289
+ { id: "s1", name: "Fetch standup messages", prompt: "Pull weekly standup messages from the team channel" },
290
+ { id: "s2", name: "Summarize weekly topics", prompt: "Write a weekly digest of key topics and blockers" },
291
+ { id: "s3", name: "Post digest to channel", prompt: "Post the weekly summary digest to the #ops channel" },
292
+ ],
293
+ })
294
+ );
295
+
296
+ expect(result).toEqual([]);
297
+ });
298
+
299
+ it("still blocks exact case-insensitive name matches (guard)", async () => {
300
+ setRows([
301
+ {
302
+ id: "wf1",
303
+ name: "Enrich contacts",
304
+ definition: JSON.stringify({
305
+ pattern: "sequence",
306
+ steps: [
307
+ { id: "s1", name: "Load rows from contacts table", prompt: "Select rows from the contacts table" },
308
+ ],
309
+ }),
310
+ projectId: "proj_a",
311
+ },
312
+ ]);
313
+
314
+ const result = await findSimilarWorkflows(
315
+ "proj_a",
316
+ "ENRICH CONTACTS", // same name, different case
317
+ JSON.stringify({ pattern: "sequence", steps: [] })
318
+ );
319
+
320
+ expect(result).toHaveLength(1);
321
+ expect(result[0].similarity).toBe(1);
322
+ expect(result[0].reason).toContain("Same name");
323
+ });
324
+
325
+ it("still blocks near-identical step content with near-identical name (guard)", async () => {
326
+ const sharedSteps = [
327
+ { id: "s1", name: "Fetch customer segments list", prompt: "Load the customer segments list from BigQuery warehouse" },
328
+ { id: "s2", name: "Classify each segment bucket", prompt: "Classify each customer segment bucket using ML model" },
329
+ { id: "s3", name: "Write segments back warehouse", prompt: "Write segment classifications back into BigQuery warehouse" },
330
+ ];
331
+ setRows([
332
+ {
333
+ id: "wf1",
334
+ name: "Classify customer segments v1",
335
+ definition: JSON.stringify({ pattern: "sequence", steps: sharedSteps }),
336
+ projectId: "proj_a",
337
+ },
338
+ ]);
339
+
340
+ const result = await findSimilarWorkflows(
341
+ "proj_a",
342
+ "Classify customer segments v2",
343
+ JSON.stringify({ pattern: "sequence", steps: sharedSteps })
344
+ );
345
+
346
+ expect(result.length).toBeGreaterThanOrEqual(1);
347
+ expect(result[0].id).toBe("wf1");
348
+ expect(result[0].similarity).toBeGreaterThanOrEqual(0.7);
349
+ });
350
+ });
351
+ });
@@ -0,0 +1,190 @@
1
+ import { defineTool } from "../tool-registry";
2
+ import { z } from "zod";
3
+ import { ok, err, type ToolContext } from "./helpers";
4
+
5
+ export function blueprintTools(ctx: ToolContext) {
6
+ return [
7
+ defineTool(
8
+ "list_blueprints",
9
+ "List available workflow blueprints. Blueprints are reusable workflow templates with configurable variables. Use instantiate_blueprint to create a workflow from one.",
10
+ {
11
+ domain: z
12
+ .enum(["work", "personal"])
13
+ .optional()
14
+ .describe("Filter by domain"),
15
+ search: z
16
+ .string()
17
+ .optional()
18
+ .describe("Search in name, description, and tags"),
19
+ },
20
+ async (args) => {
21
+ try {
22
+ const { listBlueprints } = await import(
23
+ "@/lib/workflows/blueprints/registry"
24
+ );
25
+ let blueprints = listBlueprints();
26
+
27
+ if (args.domain) {
28
+ blueprints = blueprints.filter((b) => b.domain === args.domain);
29
+ }
30
+ if (args.search) {
31
+ const q = args.search.toLowerCase();
32
+ blueprints = blueprints.filter(
33
+ (b) =>
34
+ b.name.toLowerCase().includes(q) ||
35
+ b.description.toLowerCase().includes(q) ||
36
+ b.tags.some((t) => t.toLowerCase().includes(q))
37
+ );
38
+ }
39
+
40
+ return ok(
41
+ blueprints.map((b) => ({
42
+ id: b.id,
43
+ name: b.name,
44
+ description: b.description,
45
+ domain: b.domain,
46
+ pattern: b.pattern,
47
+ tags: b.tags,
48
+ difficulty: b.difficulty,
49
+ estimatedDuration: b.estimatedDuration,
50
+ isBuiltin: b.isBuiltin,
51
+ variableCount: b.variables.length,
52
+ stepCount: b.steps.length,
53
+ }))
54
+ );
55
+ } catch (e) {
56
+ return err(
57
+ e instanceof Error ? e.message : "Failed to list blueprints"
58
+ );
59
+ }
60
+ }
61
+ ),
62
+
63
+ defineTool(
64
+ "get_blueprint",
65
+ "Get full details of a workflow blueprint, including its variables and steps. Use this to understand what inputs are needed before calling instantiate_blueprint.",
66
+ {
67
+ blueprintId: z.string().describe("The blueprint ID to look up"),
68
+ },
69
+ async (args) => {
70
+ try {
71
+ const { getBlueprint } = await import(
72
+ "@/lib/workflows/blueprints/registry"
73
+ );
74
+ const blueprint = getBlueprint(args.blueprintId);
75
+ if (!blueprint)
76
+ return err(`Blueprint not found: ${args.blueprintId}`);
77
+ return ok(blueprint);
78
+ } catch (e) {
79
+ return err(
80
+ e instanceof Error ? e.message : "Failed to get blueprint"
81
+ );
82
+ }
83
+ }
84
+ ),
85
+
86
+ defineTool(
87
+ "instantiate_blueprint",
88
+ "Create a draft workflow from a blueprint by filling in its variables. The workflow is created in 'draft' status — use execute_workflow to run it. Call get_blueprint first to see required variables.",
89
+ {
90
+ blueprintId: z
91
+ .string()
92
+ .describe("The blueprint ID to instantiate"),
93
+ variables: z
94
+ .record(z.string(), z.unknown())
95
+ .describe(
96
+ "Key-value map of variable values. Keys are variable IDs from the blueprint. Required variables must be provided."
97
+ ),
98
+ projectId: z
99
+ .string()
100
+ .optional()
101
+ .describe(
102
+ "Project ID to attach the workflow to. Omit to use the active project."
103
+ ),
104
+ },
105
+ async (args) => {
106
+ try {
107
+ const { instantiateBlueprint } = await import(
108
+ "@/lib/workflows/blueprints/instantiator"
109
+ );
110
+ const effectiveProjectId =
111
+ args.projectId ?? ctx.projectId ?? undefined;
112
+
113
+ const result = await instantiateBlueprint(
114
+ args.blueprintId,
115
+ args.variables,
116
+ effectiveProjectId
117
+ );
118
+
119
+ ctx.onToolResult?.("instantiate_blueprint", result);
120
+ return ok({
121
+ workflowId: result.workflowId,
122
+ name: result.name,
123
+ stepsCount: result.stepsCount,
124
+ skippedSteps: result.skippedSteps,
125
+ status: "draft",
126
+ message:
127
+ "Workflow created from blueprint. Use execute_workflow to run it.",
128
+ });
129
+ } catch (e) {
130
+ return err(
131
+ e instanceof Error ? e.message : "Failed to instantiate blueprint"
132
+ );
133
+ }
134
+ }
135
+ ),
136
+
137
+ defineTool(
138
+ "create_blueprint",
139
+ "Create a custom workflow blueprint from YAML content. The YAML must include id, name, description, version, domain, tags, pattern, variables, and steps. Use get_blueprint on an existing blueprint to see the expected structure.",
140
+ {
141
+ yaml: z
142
+ .string()
143
+ .describe(
144
+ "Full blueprint YAML content. Must validate against the blueprint schema."
145
+ ),
146
+ },
147
+ async (args) => {
148
+ try {
149
+ const { createBlueprint } = await import(
150
+ "@/lib/workflows/blueprints/registry"
151
+ );
152
+ const blueprint = createBlueprint(args.yaml);
153
+ ctx.onToolResult?.("create_blueprint", blueprint);
154
+ return ok({
155
+ id: blueprint.id,
156
+ name: blueprint.name,
157
+ message: "Blueprint created successfully",
158
+ });
159
+ } catch (e) {
160
+ return err(
161
+ e instanceof Error ? e.message : "Failed to create blueprint"
162
+ );
163
+ }
164
+ }
165
+ ),
166
+
167
+ defineTool(
168
+ "delete_blueprint",
169
+ "Delete a custom workflow blueprint. Built-in blueprints cannot be deleted.",
170
+ {
171
+ blueprintId: z.string().describe("The blueprint ID to delete"),
172
+ },
173
+ async (args) => {
174
+ try {
175
+ const { deleteBlueprint } = await import(
176
+ "@/lib/workflows/blueprints/registry"
177
+ );
178
+ deleteBlueprint(args.blueprintId);
179
+ return ok({
180
+ message: `Blueprint "${args.blueprintId}" deleted`,
181
+ });
182
+ } catch (e) {
183
+ return err(
184
+ e instanceof Error ? e.message : "Failed to delete blueprint"
185
+ );
186
+ }
187
+ }
188
+ ),
189
+ ];
190
+ }
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import { db } from "@/lib/db";
4
4
  import { documents } from "@/lib/db/schema";
5
5
  import { eq, and, desc } from "drizzle-orm";
6
- import { ok, err, type ToolContext } from "./helpers";
6
+ import { ok, err, resolveEntityId, type ToolContext } from "./helpers";
7
7
  import { access, stat, copyFile, mkdir } from "fs/promises";
8
8
  import { basename, extname, join } from "path";
9
9
  import crypto from "crypto";
@@ -105,6 +105,10 @@ export function documentTools(ctx: ToolContext) {
105
105
  },
106
106
  async (args) => {
107
107
  try {
108
+ const resolved = await resolveEntityId(documents, documents.id, args.documentId);
109
+ if ("error" in resolved) return err(resolved.error);
110
+ const documentId = resolved.id;
111
+
108
112
  const doc = await db
109
113
  .select({
110
114
  id: documents.id,
@@ -122,10 +126,10 @@ export function documentTools(ctx: ToolContext) {
122
126
  updatedAt: documents.updatedAt,
123
127
  })
124
128
  .from(documents)
125
- .where(eq(documents.id, args.documentId))
129
+ .where(eq(documents.id, documentId))
126
130
  .get();
127
131
 
128
- if (!doc) return err(`Document not found: ${args.documentId}`);
132
+ if (!doc) return err(`Document not found: ${documentId}`);
129
133
  ctx.onToolResult?.("get_document", doc);
130
134
  return ok(doc);
131
135
  } catch (e) {
@@ -204,13 +208,17 @@ export function documentTools(ctx: ToolContext) {
204
208
  },
205
209
  async (args) => {
206
210
  try {
211
+ const resolved = await resolveEntityId(documents, documents.id, args.documentId);
212
+ if ("error" in resolved) return err(resolved.error);
213
+ const documentId = resolved.id;
214
+
207
215
  const doc = await db
208
216
  .select()
209
217
  .from(documents)
210
- .where(eq(documents.id, args.documentId))
218
+ .where(eq(documents.id, documentId))
211
219
  .get();
212
220
 
213
- if (!doc) return err(`Document not found: ${args.documentId}`);
221
+ if (!doc) return err(`Document not found: ${documentId}`);
214
222
 
215
223
  const updates: Record<string, unknown> = { updatedAt: new Date() };
216
224
 
@@ -232,10 +240,10 @@ export function documentTools(ctx: ToolContext) {
232
240
  await db
233
241
  .update(documents)
234
242
  .set(updates)
235
- .where(eq(documents.id, args.documentId));
243
+ .where(eq(documents.id, documentId));
236
244
 
237
245
  if (args.reprocess) {
238
- processDocument(args.documentId).catch(() => {});
246
+ processDocument(documentId).catch(() => {});
239
247
  }
240
248
 
241
249
  const updatedFields = [];
@@ -243,7 +251,7 @@ export function documentTools(ctx: ToolContext) {
243
251
  if (args.reprocess) updatedFields.push("processingStatus");
244
252
 
245
253
  const result = {
246
- documentId: args.documentId,
254
+ documentId,
247
255
  updatedFields,
248
256
  processingStatus: args.reprocess ? "queued" : doc.status,
249
257
  };
@@ -264,13 +272,17 @@ export function documentTools(ctx: ToolContext) {
264
272
  },
265
273
  async (args) => {
266
274
  try {
275
+ const resolved = await resolveEntityId(documents, documents.id, args.documentId);
276
+ if ("error" in resolved) return err(resolved.error);
277
+ const documentId = resolved.id;
278
+
267
279
  const doc = await db
268
280
  .select()
269
281
  .from(documents)
270
- .where(eq(documents.id, args.documentId))
282
+ .where(eq(documents.id, documentId))
271
283
  .get();
272
284
 
273
- if (!doc) return err(`Document not found: ${args.documentId}`);
285
+ if (!doc) return err(`Document not found: ${documentId}`);
274
286
 
275
287
  // Check task linkage
276
288
  if (doc.taskId && !args.cascadeDelete) {
@@ -285,7 +297,7 @@ export function documentTools(ctx: ToolContext) {
285
297
  // File may already be deleted
286
298
  }
287
299
 
288
- await db.delete(documents).where(eq(documents.id, args.documentId));
300
+ await db.delete(documents).where(eq(documents.id, documentId));
289
301
 
290
302
  const result = {
291
303
  success: true,
@@ -308,6 +320,10 @@ export function documentTools(ctx: ToolContext) {
308
320
  },
309
321
  async (args) => {
310
322
  try {
323
+ const resolved = await resolveEntityId(documents, documents.id, args.documentId);
324
+ if ("error" in resolved) return err(resolved.error);
325
+ const documentId = resolved.id;
326
+
311
327
  const doc = await db
312
328
  .select({
313
329
  id: documents.id,
@@ -316,10 +332,10 @@ export function documentTools(ctx: ToolContext) {
316
332
  extractedText: documents.extractedText,
317
333
  })
318
334
  .from(documents)
319
- .where(eq(documents.id, args.documentId))
335
+ .where(eq(documents.id, documentId))
320
336
  .get();
321
337
 
322
- if (!doc) return err(`Document not found: ${args.documentId}`);
338
+ if (!doc) return err(`Document not found: ${documentId}`);
323
339
  if (doc.status !== "ready")
324
340
  return err(`Document not ready (status: ${doc.status}). Wait for preprocessing to complete.`);
325
341
  if (!doc.extractedText)