@desplega.ai/agent-swarm 1.49.0 → 1.52.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 (547) hide show
  1. package/README.md +1 -1
  2. package/openapi.json +2070 -728
  3. package/package.json +10 -1
  4. package/src/agentmail/handlers.ts +65 -10
  5. package/src/agentmail/templates.ts +111 -0
  6. package/src/be/db.ts +1233 -7
  7. package/src/be/migrations/014_prompt_templates.sql +33 -0
  8. package/src/be/migrations/015_workflow_workspace.sql +3 -0
  9. package/src/be/migrations/016_active_session_runner_session.sql +4 -0
  10. package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
  11. package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
  12. package/src/be/migrations/019_skills.sql +65 -0
  13. package/src/be/migrations/020_approval_requests.sql +41 -0
  14. package/src/be/seed.ts +62 -0
  15. package/src/be/skill-parser.ts +70 -0
  16. package/src/be/skill-sync.ts +106 -0
  17. package/src/commands/runner.ts +320 -132
  18. package/src/commands/templates.ts +172 -0
  19. package/src/github/handlers.ts +292 -77
  20. package/src/github/mentions-aliases.test.ts +73 -0
  21. package/src/github/mentions.test.ts +3 -3
  22. package/src/github/mentions.ts +32 -6
  23. package/src/github/templates.ts +398 -0
  24. package/src/gitlab/handlers.ts +63 -22
  25. package/src/gitlab/templates.ts +140 -0
  26. package/src/heartbeat/heartbeat.ts +19 -10
  27. package/src/heartbeat/templates.ts +30 -0
  28. package/src/http/active-sessions.ts +27 -0
  29. package/src/http/approval-requests.ts +247 -0
  30. package/src/http/config.ts +3 -3
  31. package/src/http/index.ts +9 -2
  32. package/src/http/poll.ts +135 -14
  33. package/src/http/prompt-templates.ts +412 -0
  34. package/src/http/schedules.ts +35 -0
  35. package/src/http/skills.ts +479 -0
  36. package/src/http/workflows.ts +8 -0
  37. package/src/linear/sync.ts +28 -4
  38. package/src/linear/templates.ts +47 -0
  39. package/src/prompts/base-prompt.ts +41 -490
  40. package/src/prompts/registry.ts +57 -0
  41. package/src/prompts/resolver.ts +296 -0
  42. package/src/prompts/session-templates.ts +604 -0
  43. package/src/providers/claude-adapter.ts +15 -2
  44. package/src/providers/pi-mono-extension.ts +5 -1
  45. package/src/scheduler/scheduler.ts +125 -91
  46. package/src/server.ts +44 -0
  47. package/src/slack/assistant.ts +7 -4
  48. package/src/slack/channel-activity.ts +177 -0
  49. package/src/slack/handlers.ts +21 -6
  50. package/src/slack/templates.ts +55 -0
  51. package/src/tests/approval-requests.test.ts +735 -0
  52. package/src/tests/artifact-sdk.test.ts +12 -12
  53. package/src/tests/base-prompt.test.ts +49 -49
  54. package/src/tests/channel-activity.test.ts +363 -0
  55. package/src/tests/heartbeat.test.ts +1 -0
  56. package/src/tests/linear-webhook.test.ts +7 -3
  57. package/src/tests/pool-session-logs.test.ts +199 -0
  58. package/src/tests/prompt-template-github.test.ts +682 -0
  59. package/src/tests/prompt-template-remaining.test.ts +504 -0
  60. package/src/tests/prompt-template-resolver.test.ts +621 -0
  61. package/src/tests/prompt-template-session.test.ts +363 -0
  62. package/src/tests/prompt-templates-db.test.ts +616 -0
  63. package/src/tests/self-improvement.test.ts +8 -7
  64. package/src/tests/skill-parser.test.ts +178 -0
  65. package/src/tests/skill-sync.test.ts +171 -0
  66. package/src/tests/slack-metadata-inheritance.test.ts +1 -1
  67. package/src/tests/slack-thread-followups.test.ts +1 -1
  68. package/src/tests/structured-output.test.ts +0 -4
  69. package/src/tests/tool-annotations.test.ts +2 -1
  70. package/src/tests/update-profile-agentid.test.ts +248 -0
  71. package/src/tests/update-profile-auth.test.ts +195 -0
  72. package/src/tests/workflow-async-v2.test.ts +126 -4
  73. package/src/tests/workflow-definition-validation.test.ts +76 -0
  74. package/src/tests/workflow-executors.test.ts +4 -2
  75. package/src/tests/workflow-retry-v2.test.ts +1 -1
  76. package/src/tests/workflow-schedule-trigger.test.ts +104 -0
  77. package/src/tests/workflow-workspace.test.ts +272 -0
  78. package/src/tools/prompt-templates/delete.ts +86 -0
  79. package/src/tools/prompt-templates/get.ts +89 -0
  80. package/src/tools/prompt-templates/index.ts +5 -0
  81. package/src/tools/prompt-templates/list.ts +95 -0
  82. package/src/tools/prompt-templates/preview.ts +84 -0
  83. package/src/tools/prompt-templates/set.ts +117 -0
  84. package/src/tools/request-human-input.ts +106 -0
  85. package/src/tools/skills/index.ts +11 -0
  86. package/src/tools/skills/skill-create.ts +105 -0
  87. package/src/tools/skills/skill-delete.ts +67 -0
  88. package/src/tools/skills/skill-get.ts +75 -0
  89. package/src/tools/skills/skill-install-remote.ts +152 -0
  90. package/src/tools/skills/skill-install.ts +101 -0
  91. package/src/tools/skills/skill-list.ts +77 -0
  92. package/src/tools/skills/skill-publish.ts +123 -0
  93. package/src/tools/skills/skill-search.ts +43 -0
  94. package/src/tools/skills/skill-sync-remote.ts +128 -0
  95. package/src/tools/skills/skill-uninstall.ts +60 -0
  96. package/src/tools/skills/skill-update.ts +128 -0
  97. package/src/tools/store-progress.ts +22 -4
  98. package/src/tools/task-action.ts +20 -0
  99. package/src/tools/templates.ts +53 -0
  100. package/src/tools/tool-config.ts +23 -0
  101. package/src/tools/update-profile.ts +106 -34
  102. package/src/tools/workflows/create-workflow.ts +19 -1
  103. package/src/tools/workflows/update-workflow.ts +16 -1
  104. package/src/types.ts +109 -2
  105. package/src/workflows/definition.ts +30 -12
  106. package/src/workflows/engine.ts +40 -14
  107. package/src/workflows/executors/agent-task.ts +14 -3
  108. package/src/workflows/executors/human-in-the-loop.ts +160 -0
  109. package/src/workflows/executors/registry.ts +2 -0
  110. package/src/workflows/index.ts +1 -1
  111. package/src/workflows/recovery.ts +72 -0
  112. package/src/workflows/resume.ts +162 -12
  113. package/src/workflows/triggers.ts +31 -2
  114. package/src/workflows/version.ts +2 -0
  115. package/.claude/settings.json +0 -84
  116. package/.claude/settings.local.json +0 -117
  117. package/.dockerignore +0 -61
  118. package/.editorconfig +0 -15
  119. package/.entire/settings.json +0 -4
  120. package/.env.docker.example +0 -56
  121. package/.env.example +0 -78
  122. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -78
  123. package/.github/ISSUE_TEMPLATE/community-template.yml +0 -77
  124. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  125. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -60
  126. package/.github/PULL_REQUEST_TEMPLATE/community-template.md +0 -29
  127. package/.github/workflows/ci.yml +0 -52
  128. package/.github/workflows/docker-and-deploy.yml +0 -132
  129. package/.github/workflows/merge-gate.yml +0 -233
  130. package/.opencode/plugins/entire.ts +0 -133
  131. package/.superset/config.json +0 -6
  132. package/.wts-config.json +0 -4
  133. package/.wts-setup.ts +0 -171
  134. package/CHANGELOG.md +0 -447
  135. package/CLAUDE.md +0 -521
  136. package/CONTRIBUTING.md +0 -315
  137. package/DEPLOYMENT.md +0 -622
  138. package/Dockerfile +0 -65
  139. package/Dockerfile.worker +0 -189
  140. package/MCP.md +0 -841
  141. package/UI.md +0 -40
  142. package/api-entrypoint.sh +0 -56
  143. package/assets/agent-swarm-logo-orange.png +0 -0
  144. package/assets/agent-swarm-logo.png +0 -0
  145. package/assets/agent-swarm.mp4 +0 -0
  146. package/assets/agent-swarm.png +0 -0
  147. package/biome.json +0 -39
  148. package/deploy/DEPLOY.md +0 -60
  149. package/deploy/agent-swarm.service +0 -17
  150. package/deploy/docker-push.ts +0 -30
  151. package/deploy/install.ts +0 -85
  152. package/deploy/prod-db.ts +0 -42
  153. package/deploy/uninstall.ts +0 -12
  154. package/deploy/update.ts +0 -21
  155. package/depot.json +0 -1
  156. package/docker-compose.example.yml +0 -350
  157. package/docker-compose.local.yml +0 -119
  158. package/docker-entrypoint.sh +0 -632
  159. package/docs-site/app/api/search/route.ts +0 -4
  160. package/docs-site/app/docs/[[...slug]]/page.tsx +0 -87
  161. package/docs-site/app/docs/layout.tsx +0 -12
  162. package/docs-site/app/globals.css +0 -24
  163. package/docs-site/app/layout.config.tsx +0 -34
  164. package/docs-site/app/layout.tsx +0 -119
  165. package/docs-site/app/llms-full.txt/route.ts +0 -11
  166. package/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +0 -24
  167. package/docs-site/app/llms.txt/route.ts +0 -8
  168. package/docs-site/app/page.tsx +0 -5
  169. package/docs-site/app/robots.ts +0 -13
  170. package/docs-site/app/sitemap.ts +0 -37
  171. package/docs-site/components/api-page.client.tsx +0 -4
  172. package/docs-site/components/api-page.tsx +0 -7
  173. package/docs-site/components/mdx/mermaid.tsx +0 -55
  174. package/docs-site/content/docs/(documentation)/architecture/agents.mdx +0 -117
  175. package/docs-site/content/docs/(documentation)/architecture/hooks.mdx +0 -77
  176. package/docs-site/content/docs/(documentation)/architecture/memory.mdx +0 -96
  177. package/docs-site/content/docs/(documentation)/architecture/meta.json +0 -4
  178. package/docs-site/content/docs/(documentation)/architecture/overview.mdx +0 -172
  179. package/docs-site/content/docs/(documentation)/concepts/epics.mdx +0 -98
  180. package/docs-site/content/docs/(documentation)/concepts/meta.json +0 -4
  181. package/docs-site/content/docs/(documentation)/concepts/scheduling.mdx +0 -136
  182. package/docs-site/content/docs/(documentation)/concepts/services.mdx +0 -104
  183. package/docs-site/content/docs/(documentation)/concepts/task-lifecycle.mdx +0 -148
  184. package/docs-site/content/docs/(documentation)/concepts/workflows.mdx +0 -209
  185. package/docs-site/content/docs/(documentation)/contributing.mdx +0 -158
  186. package/docs-site/content/docs/(documentation)/getting-started.mdx +0 -157
  187. package/docs-site/content/docs/(documentation)/guides/agentmail-integration.mdx +0 -79
  188. package/docs-site/content/docs/(documentation)/guides/deployment.mdx +0 -171
  189. package/docs-site/content/docs/(documentation)/guides/github-integration.mdx +0 -81
  190. package/docs-site/content/docs/(documentation)/guides/gitlab-integration.mdx +0 -93
  191. package/docs-site/content/docs/(documentation)/guides/linear-integration.mdx +0 -98
  192. package/docs-site/content/docs/(documentation)/guides/meta.json +0 -13
  193. package/docs-site/content/docs/(documentation)/guides/sentry-integration.mdx +0 -52
  194. package/docs-site/content/docs/(documentation)/guides/slack-integration.mdx +0 -179
  195. package/docs-site/content/docs/(documentation)/guides/x402-payments.mdx +0 -154
  196. package/docs-site/content/docs/(documentation)/index.mdx +0 -65
  197. package/docs-site/content/docs/(documentation)/meta.json +0 -19
  198. package/docs-site/content/docs/(documentation)/reference/cli.mdx +0 -241
  199. package/docs-site/content/docs/(documentation)/reference/environment-variables.mdx +0 -205
  200. package/docs-site/content/docs/(documentation)/reference/mcp-tools.mdx +0 -449
  201. package/docs-site/content/docs/(documentation)/reference/meta.json +0 -4
  202. package/docs-site/content/docs/api-reference/active-sessions.mdx +0 -9
  203. package/docs-site/content/docs/api-reference/agents.mdx +0 -9
  204. package/docs-site/content/docs/api-reference/channels.mdx +0 -9
  205. package/docs-site/content/docs/api-reference/config.mdx +0 -9
  206. package/docs-site/content/docs/api-reference/debug.mdx +0 -9
  207. package/docs-site/content/docs/api-reference/ecosystem.mdx +0 -9
  208. package/docs-site/content/docs/api-reference/epics.mdx +0 -9
  209. package/docs-site/content/docs/api-reference/index.mdx +0 -32
  210. package/docs-site/content/docs/api-reference/memory.mdx +0 -9
  211. package/docs-site/content/docs/api-reference/meta.json +0 -25
  212. package/docs-site/content/docs/api-reference/poll.mdx +0 -9
  213. package/docs-site/content/docs/api-reference/repos.mdx +0 -9
  214. package/docs-site/content/docs/api-reference/schedules.mdx +0 -9
  215. package/docs-site/content/docs/api-reference/session-data.mdx +0 -9
  216. package/docs-site/content/docs/api-reference/stats.mdx +0 -9
  217. package/docs-site/content/docs/api-reference/tasks.mdx +0 -9
  218. package/docs-site/content/docs/api-reference/trackers.mdx +0 -9
  219. package/docs-site/content/docs/api-reference/webhooks.mdx +0 -9
  220. package/docs-site/content/docs/api-reference/workflows.mdx +0 -9
  221. package/docs-site/content/docs/meta.json +0 -3
  222. package/docs-site/lib/get-llm-text.ts +0 -10
  223. package/docs-site/lib/openapi.ts +0 -23
  224. package/docs-site/lib/source.ts +0 -8
  225. package/docs-site/mdx-components.tsx +0 -13
  226. package/docs-site/next.config.mjs +0 -29
  227. package/docs-site/package.json +0 -35
  228. package/docs-site/pnpm-lock.yaml +0 -5407
  229. package/docs-site/postcss.config.mjs +0 -8
  230. package/docs-site/public/logo.png +0 -0
  231. package/docs-site/scripts/generate-docs.ts +0 -171
  232. package/docs-site/source.config.ts +0 -17
  233. package/docs-site/tsconfig.json +0 -46
  234. package/ecosystem.config.cjs +0 -66
  235. package/landing/next.config.ts +0 -14
  236. package/landing/package.json +0 -31
  237. package/landing/pnpm-lock.yaml +0 -1091
  238. package/landing/postcss.config.mjs +0 -8
  239. package/landing/public/apple-touch-icon.png +0 -0
  240. package/landing/public/favicon.ico +0 -0
  241. package/landing/public/logo.png +0 -0
  242. package/landing/public/og-image.png +0 -0
  243. package/landing/public/omghost-desplega.svg +0 -30
  244. package/landing/public/omghost-openfort.svg +0 -9
  245. package/landing/src/app/actions/waitlist.ts +0 -25
  246. package/landing/src/app/blog/openfort-hackathon/page.tsx +0 -863
  247. package/landing/src/app/blog/page.tsx +0 -162
  248. package/landing/src/app/blog/swarm-metrics/page.tsx +0 -685
  249. package/landing/src/app/examples/page.tsx +0 -174
  250. package/landing/src/app/examples/x402/page.tsx +0 -456
  251. package/landing/src/app/globals.css +0 -122
  252. package/landing/src/app/layout.tsx +0 -134
  253. package/landing/src/app/page.tsx +0 -27
  254. package/landing/src/app/robots.ts +0 -13
  255. package/landing/src/app/sitemap.ts +0 -44
  256. package/landing/src/components/architecture.tsx +0 -163
  257. package/landing/src/components/cta.tsx +0 -52
  258. package/landing/src/components/features.tsx +0 -160
  259. package/landing/src/components/footer.tsx +0 -100
  260. package/landing/src/components/hero.tsx +0 -217
  261. package/landing/src/components/how-it-works.tsx +0 -165
  262. package/landing/src/components/navbar.tsx +0 -147
  263. package/landing/src/components/waitlist.tsx +0 -110
  264. package/landing/src/components/why-choose.tsx +0 -149
  265. package/landing/src/components/workshops.tsx +0 -328
  266. package/landing/src/lib/utils.ts +0 -6
  267. package/landing/tsconfig.json +0 -41
  268. package/misc/transcripts/2026-03-09-pi-mono-e2e-verification.md +0 -154
  269. package/new-ui/CLAUDE.md +0 -92
  270. package/new-ui/README.md +0 -73
  271. package/new-ui/biome.json +0 -42
  272. package/new-ui/components.json +0 -21
  273. package/new-ui/index.html +0 -25
  274. package/new-ui/package.json +0 -49
  275. package/new-ui/pnpm-lock.yaml +0 -4845
  276. package/new-ui/public/logo.png +0 -0
  277. package/new-ui/src/api/client.ts +0 -814
  278. package/new-ui/src/api/hooks/index.ts +0 -64
  279. package/new-ui/src/api/hooks/use-agents.ts +0 -58
  280. package/new-ui/src/api/hooks/use-channels.ts +0 -115
  281. package/new-ui/src/api/hooks/use-config-api.ts +0 -46
  282. package/new-ui/src/api/hooks/use-costs.ts +0 -122
  283. package/new-ui/src/api/hooks/use-db-query.ts +0 -29
  284. package/new-ui/src/api/hooks/use-epics.ts +0 -75
  285. package/new-ui/src/api/hooks/use-repos.ts +0 -61
  286. package/new-ui/src/api/hooks/use-schedules.ts +0 -81
  287. package/new-ui/src/api/hooks/use-services.ts +0 -16
  288. package/new-ui/src/api/hooks/use-stats.ts +0 -27
  289. package/new-ui/src/api/hooks/use-tasks.ts +0 -89
  290. package/new-ui/src/api/hooks/use-workflows.ts +0 -109
  291. package/new-ui/src/api/types.ts +0 -549
  292. package/new-ui/src/app/App.tsx +0 -13
  293. package/new-ui/src/app/providers.tsx +0 -32
  294. package/new-ui/src/app/router.tsx +0 -52
  295. package/new-ui/src/components/layout/app-header.tsx +0 -47
  296. package/new-ui/src/components/layout/app-sidebar.tsx +0 -128
  297. package/new-ui/src/components/layout/breadcrumbs.tsx +0 -57
  298. package/new-ui/src/components/layout/config-guard.tsx +0 -22
  299. package/new-ui/src/components/layout/root-layout.tsx +0 -40
  300. package/new-ui/src/components/layout/swarm-switcher.tsx +0 -85
  301. package/new-ui/src/components/shared/command-menu.tsx +0 -131
  302. package/new-ui/src/components/shared/data-grid.tsx +0 -141
  303. package/new-ui/src/components/shared/empty-state.tsx +0 -24
  304. package/new-ui/src/components/shared/error-boundary.tsx +0 -72
  305. package/new-ui/src/components/shared/json-viewer.tsx +0 -47
  306. package/new-ui/src/components/shared/name-connection-modal.tsx +0 -99
  307. package/new-ui/src/components/shared/page-skeleton.tsx +0 -16
  308. package/new-ui/src/components/shared/session-log-viewer.tsx +0 -364
  309. package/new-ui/src/components/shared/stats-bar.tsx +0 -132
  310. package/new-ui/src/components/shared/status-badge.tsx +0 -131
  311. package/new-ui/src/components/shared/usage-summary.tsx +0 -179
  312. package/new-ui/src/components/ui/alert-dialog.tsx +0 -176
  313. package/new-ui/src/components/ui/alert.tsx +0 -60
  314. package/new-ui/src/components/ui/avatar.tsx +0 -96
  315. package/new-ui/src/components/ui/badge.tsx +0 -46
  316. package/new-ui/src/components/ui/button.tsx +0 -62
  317. package/new-ui/src/components/ui/card.tsx +0 -75
  318. package/new-ui/src/components/ui/command.tsx +0 -160
  319. package/new-ui/src/components/ui/dialog.tsx +0 -143
  320. package/new-ui/src/components/ui/dropdown-menu.tsx +0 -226
  321. package/new-ui/src/components/ui/input.tsx +0 -21
  322. package/new-ui/src/components/ui/label.tsx +0 -19
  323. package/new-ui/src/components/ui/progress.tsx +0 -26
  324. package/new-ui/src/components/ui/scroll-area.tsx +0 -54
  325. package/new-ui/src/components/ui/select.tsx +0 -175
  326. package/new-ui/src/components/ui/separator.tsx +0 -28
  327. package/new-ui/src/components/ui/sheet.tsx +0 -132
  328. package/new-ui/src/components/ui/sidebar.tsx +0 -691
  329. package/new-ui/src/components/ui/skeleton.tsx +0 -13
  330. package/new-ui/src/components/ui/sonner.tsx +0 -35
  331. package/new-ui/src/components/ui/switch.tsx +0 -33
  332. package/new-ui/src/components/ui/table.tsx +0 -92
  333. package/new-ui/src/components/ui/tabs.tsx +0 -79
  334. package/new-ui/src/components/ui/textarea.tsx +0 -18
  335. package/new-ui/src/components/ui/tooltip.tsx +0 -51
  336. package/new-ui/src/components/workflows/action-node.tsx +0 -53
  337. package/new-ui/src/components/workflows/condition-node.tsx +0 -50
  338. package/new-ui/src/components/workflows/graph-utils.ts +0 -124
  339. package/new-ui/src/components/workflows/json-tree.tsx +0 -189
  340. package/new-ui/src/components/workflows/node-styles.ts +0 -10
  341. package/new-ui/src/components/workflows/step-detail-sheet.tsx +0 -87
  342. package/new-ui/src/components/workflows/trigger-node.tsx +0 -41
  343. package/new-ui/src/components/workflows/workflow-graph.tsx +0 -65
  344. package/new-ui/src/hooks/use-auto-scroll.ts +0 -82
  345. package/new-ui/src/hooks/use-config.ts +0 -203
  346. package/new-ui/src/hooks/use-keyboard-shortcuts.ts +0 -41
  347. package/new-ui/src/hooks/use-mobile.ts +0 -19
  348. package/new-ui/src/hooks/use-theme.ts +0 -60
  349. package/new-ui/src/lib/config.ts +0 -188
  350. package/new-ui/src/lib/slugs.ts +0 -71
  351. package/new-ui/src/lib/utils.ts +0 -120
  352. package/new-ui/src/main.tsx +0 -11
  353. package/new-ui/src/pages/agents/[id]/page.tsx +0 -492
  354. package/new-ui/src/pages/agents/page.tsx +0 -134
  355. package/new-ui/src/pages/chat/page.tsx +0 -674
  356. package/new-ui/src/pages/config/page.tsx +0 -1109
  357. package/new-ui/src/pages/dashboard/page.tsx +0 -454
  358. package/new-ui/src/pages/debug/page.tsx +0 -275
  359. package/new-ui/src/pages/epics/[id]/page.tsx +0 -809
  360. package/new-ui/src/pages/epics/page.tsx +0 -321
  361. package/new-ui/src/pages/not-found/page.tsx +0 -18
  362. package/new-ui/src/pages/repos/page.tsx +0 -369
  363. package/new-ui/src/pages/schedules/[id]/page.tsx +0 -664
  364. package/new-ui/src/pages/schedules/page.tsx +0 -477
  365. package/new-ui/src/pages/services/page.tsx +0 -128
  366. package/new-ui/src/pages/tasks/[id]/page.tsx +0 -670
  367. package/new-ui/src/pages/tasks/page.tsx +0 -592
  368. package/new-ui/src/pages/usage/page.tsx +0 -195
  369. package/new-ui/src/pages/workflow-runs/[id]/page.tsx +0 -363
  370. package/new-ui/src/pages/workflows/[id]/page.tsx +0 -417
  371. package/new-ui/src/pages/workflows/page.tsx +0 -266
  372. package/new-ui/src/styles/ag-grid.css +0 -36
  373. package/new-ui/src/styles/globals.css +0 -213
  374. package/new-ui/test-results/.last-run.json +0 -4
  375. package/new-ui/tsconfig.app.json +0 -34
  376. package/new-ui/tsconfig.json +0 -4
  377. package/new-ui/tsconfig.node.json +0 -26
  378. package/new-ui/vercel.json +0 -4
  379. package/new-ui/vite.config.ts +0 -28
  380. package/plugin/README.md +0 -1
  381. package/plugin/build-pi-skills.ts +0 -233
  382. package/plugin/hooks/hooks.json +0 -71
  383. package/prek.toml +0 -75
  384. package/pyproject.toml +0 -9
  385. package/scripts/check-db-boundary.sh +0 -60
  386. package/scripts/e2e-docker-provider.ts +0 -820
  387. package/scripts/e2e-io-schemas-test.ts +0 -807
  388. package/scripts/e2e-provider-test.ts +0 -220
  389. package/scripts/e2e-workflow-redesign.sh +0 -229
  390. package/scripts/e2e-workflow-test.sh +0 -285
  391. package/scripts/e2e-workflow-test.ts +0 -857
  392. package/scripts/generate-mcp-docs.ts +0 -415
  393. package/scripts/generate-openapi.ts +0 -26
  394. package/scripts/measure-tool-tokens.ts +0 -118
  395. package/scripts/x402-e2e-test.ts +0 -195
  396. package/scripts/x402-test-server.ts +0 -236
  397. package/scripts/x402-testnet-e2e.ts +0 -668
  398. package/slack-manifest.json +0 -88
  399. package/templates-ui/README.md +0 -46
  400. package/templates-ui/components.json +0 -17
  401. package/templates-ui/eslint.config.mjs +0 -18
  402. package/templates-ui/next.config.ts +0 -7
  403. package/templates-ui/package.json +0 -35
  404. package/templates-ui/pnpm-lock.yaml +0 -4571
  405. package/templates-ui/postcss.config.mjs +0 -7
  406. package/templates-ui/public/file.svg +0 -1
  407. package/templates-ui/public/globe.svg +0 -1
  408. package/templates-ui/public/logo.png +0 -0
  409. package/templates-ui/public/next.svg +0 -1
  410. package/templates-ui/public/vercel.svg +0 -1
  411. package/templates-ui/public/window.svg +0 -1
  412. package/templates-ui/src/app/[category]/[name]/page.tsx +0 -89
  413. package/templates-ui/src/app/api/templates/[...slug]/route.ts +0 -52
  414. package/templates-ui/src/app/api/templates/route.ts +0 -18
  415. package/templates-ui/src/app/builder/page.tsx +0 -37
  416. package/templates-ui/src/app/globals.css +0 -94
  417. package/templates-ui/src/app/layout.tsx +0 -79
  418. package/templates-ui/src/app/page.tsx +0 -38
  419. package/templates-ui/src/app/robots.ts +0 -11
  420. package/templates-ui/src/app/sitemap.ts +0 -31
  421. package/templates-ui/src/components/compose-builder.tsx +0 -442
  422. package/templates-ui/src/components/compose-preview.tsx +0 -117
  423. package/templates-ui/src/components/file-preview.tsx +0 -77
  424. package/templates-ui/src/components/footer.tsx +0 -40
  425. package/templates-ui/src/components/header.tsx +0 -41
  426. package/templates-ui/src/components/template-card.tsx +0 -87
  427. package/templates-ui/src/components/template-detail.tsx +0 -125
  428. package/templates-ui/src/components/template-gallery.tsx +0 -263
  429. package/templates-ui/src/components/ui/badge.tsx +0 -36
  430. package/templates-ui/src/components/ui/button.tsx +0 -57
  431. package/templates-ui/src/components/ui/card.tsx +0 -76
  432. package/templates-ui/src/components/ui/separator.tsx +0 -31
  433. package/templates-ui/src/components/ui/tooltip.tsx +0 -32
  434. package/templates-ui/src/lib/compose-generator.ts +0 -241
  435. package/templates-ui/src/lib/templates.ts +0 -137
  436. package/templates-ui/src/lib/utils.ts +0 -6
  437. package/templates-ui/tsconfig.json +0 -34
  438. package/thoughts/research/2026-02-28-openfort-viem-x402-research.md +0 -679
  439. package/thoughts/research/2026-02-28-x402-payments-research.md +0 -686
  440. package/thoughts/researcher/plans/2026-02-20-agent-self-improvement-plan.md +0 -282
  441. package/thoughts/researcher/research/2026-02-20-agent-self-improvement.md +0 -492
  442. package/thoughts/shared/plans/.gitkeep +0 -0
  443. package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
  444. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
  445. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
  446. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
  447. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
  448. package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
  449. package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
  450. package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
  451. package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
  452. package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
  453. package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
  454. package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
  455. package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
  456. package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
  457. package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
  458. package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
  459. package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
  460. package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
  461. package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
  462. package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
  463. package/thoughts/shared/plans/2026-02-26-mcp-tool-context-reduction.md +0 -282
  464. package/thoughts/shared/plans/2026-03-02-claude-context-mode-integration.md +0 -328
  465. package/thoughts/shared/plans/2026-03-02-code-level-heartbeat.md +0 -224
  466. package/thoughts/shared/research/.gitkeep +0 -0
  467. package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
  468. package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
  469. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
  470. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
  471. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
  472. package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
  473. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
  474. package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
  475. package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
  476. package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
  477. package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
  478. package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
  479. package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
  480. package/thoughts/shared/research/2026-02-26-cliffy-mcp-tools.md +0 -159
  481. package/thoughts/shared/research/2026-03-03-database-migration-system-refactor.md +0 -337
  482. package/thoughts/swarm-researcher/plans/2026-02-23-openclaw-improvements-plan.md +0 -778
  483. package/thoughts/swarm-researcher/plans/2026-02-26-artifacts-localtunnel-plan.md +0 -1269
  484. package/thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md +0 -411
  485. package/thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md +0 -724
  486. package/thoughts/taras/brainstorms/2026-03-20-prompt-template-registry.md +0 -443
  487. package/thoughts/taras/brainstorms/2026-03-20-setup-cli-onboarding.md +0 -307
  488. package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
  489. package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
  490. package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
  491. package/thoughts/taras/plans/2026-02-20-auto-improvement.md +0 -803
  492. package/thoughts/taras/plans/2026-02-20-env-management.md +0 -538
  493. package/thoughts/taras/plans/2026-02-20-memory-system.md +0 -882
  494. package/thoughts/taras/plans/2026-02-20-repos-knowledge.md +0 -806
  495. package/thoughts/taras/plans/2026-02-20-session-attach.md +0 -647
  496. package/thoughts/taras/plans/2026-02-20-worker-identity.md +0 -820
  497. package/thoughts/taras/plans/2026-02-25-feat-new-ui-visual-redesign-plan.md +0 -768
  498. package/thoughts/taras/plans/2026-03-04-fix-buildSystemPrompt-missing-fields.md +0 -77
  499. package/thoughts/taras/plans/2026-03-04-new-ui-missing-actions.md +0 -543
  500. package/thoughts/taras/plans/2026-03-06-one-time-scheduled-tasks.md +0 -373
  501. package/thoughts/taras/plans/2026-03-08-memory-self-improvement-enhancements.md +0 -512
  502. package/thoughts/taras/plans/2026-03-08-pi-mono-provider-implementation.md +0 -919
  503. package/thoughts/taras/plans/2026-03-09-templates-registry.md +0 -723
  504. package/thoughts/taras/plans/2026-03-10-task-working-directory.md +0 -371
  505. package/thoughts/taras/plans/2026-03-11-archil-per-agent-write-strategy.md +0 -621
  506. package/thoughts/taras/plans/2026-03-12-eliminate-inbox-route-to-tasks.md +0 -61
  507. package/thoughts/taras/plans/2026-03-12-slack-thread-followup-additive.md +0 -488
  508. package/thoughts/taras/plans/2026-03-13-slack-ai-improvements.md +0 -644
  509. package/thoughts/taras/plans/2026-03-16-route-wrapper-openapi.md +0 -636
  510. package/thoughts/taras/plans/2026-03-17-multi-api-config.md +0 -444
  511. package/thoughts/taras/plans/2026-03-18-agent-fs-integration.md +0 -591
  512. package/thoughts/taras/plans/2026-03-18-debug-db-explorer.md +0 -446
  513. package/thoughts/taras/plans/2026-03-18-workflow-redesign.md +0 -987
  514. package/thoughts/taras/plans/2026-03-19-compound-learnings.md +0 -403
  515. package/thoughts/taras/plans/2026-03-19-ticket-tracker-linear-integration.md +0 -860
  516. package/thoughts/taras/plans/2026-03-19-workflow-io-schemas-and-bugs.md +0 -899
  517. package/thoughts/taras/plans/2026-03-20-setup-cli-onboarding.md +0 -874
  518. package/thoughts/taras/plans/2026-03-20-workflow-structured-output-validation-workspace.md +0 -723
  519. package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
  520. package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
  521. package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
  522. package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
  523. package/thoughts/taras/research/2026-02-19-agent-native-swarm-architecture.md +0 -390
  524. package/thoughts/taras/research/2026-02-19-swarm-gaps-implementation.md +0 -594
  525. package/thoughts/taras/research/2026-02-25-dashboard-ui-design-best-practices.md +0 -825
  526. package/thoughts/taras/research/2026-02-26-task-detail-page-redesign.md +0 -393
  527. package/thoughts/taras/research/2026-03-03-new-ui-missing-actions.md +0 -168
  528. package/thoughts/taras/research/2026-03-05-pi-mono-provider-research.md +0 -230
  529. package/thoughts/taras/research/2026-03-06-workflow-engine-design.md +0 -445
  530. package/thoughts/taras/research/2026-03-08-drive-loop-concept.md +0 -375
  531. package/thoughts/taras/research/2026-03-08-pi-mono-deep-dive.md +0 -869
  532. package/thoughts/taras/research/2026-03-09-templates-registry.md +0 -373
  533. package/thoughts/taras/research/2026-03-10-agent-working-directory.md +0 -223
  534. package/thoughts/taras/research/2026-03-10-configurable-event-prompts.md +0 -339
  535. package/thoughts/taras/research/2026-03-11-archil-production-setup.md +0 -181
  536. package/thoughts/taras/research/2026-03-11-archil-shared-disk-write-strategies.md +0 -437
  537. package/thoughts/taras/research/2026-03-13-slack-ai-features.md +0 -258
  538. package/thoughts/taras/research/2026-03-16-openapi-docs-generation.md +0 -335
  539. package/thoughts/taras/research/2026-03-16-route-wrapper-openapi.md +0 -670
  540. package/thoughts/taras/research/2026-03-16-slack-thread-followups-e2e.md +0 -54
  541. package/thoughts/taras/research/2026-03-18-agent-fs-integration.md +0 -558
  542. package/thoughts/taras/research/2026-03-18-linear-integration-finalization.md +0 -526
  543. package/thoughts/taras/research/2026-03-18-workflow-redesign.md +0 -797
  544. package/thoughts/taras/research/2026-03-19-workflow-node-io-schemas-and-bugs.md +0 -563
  545. package/thoughts/taras/research/2026-03-19-workflow-structured-output-validation-workspace.md +0 -486
  546. package/thoughts/taras/research/2026-03-20-prompt-template-registry.md +0 -469
  547. package/tsconfig.json +0 -37
package/src/be/db.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Database } from "bun:sqlite";
2
+ import { configureDbResolver } from "../prompts/resolver";
2
3
  import type {
3
4
  ActiveSession,
4
5
  Agent,
@@ -7,6 +8,7 @@ import type {
7
8
  AgentMemory,
8
9
  AgentMemoryScope,
9
10
  AgentMemorySource,
11
+ AgentSkill,
10
12
  AgentStatus,
11
13
  AgentTask,
12
14
  AgentTaskSource,
@@ -24,11 +26,17 @@ import type {
24
26
  InboxMessage,
25
27
  InboxMessageStatus,
26
28
  InputValue,
29
+ PromptTemplate,
30
+ PromptTemplateHistory,
27
31
  ScheduledTask,
28
32
  Service,
29
33
  ServiceStatus,
30
34
  SessionCost,
31
35
  SessionLog,
36
+ Skill,
37
+ SkillScope,
38
+ SkillType,
39
+ SkillWithInstallInfo,
32
40
  SwarmConfig,
33
41
  SwarmRepo,
34
42
  TriggerConfig,
@@ -44,6 +52,7 @@ import type {
44
52
  WorkflowVersion,
45
53
  } from "../types";
46
54
  import { runMigrations } from "./migrations/runner";
55
+ import { seedDefaultTemplates } from "./seed";
47
56
 
48
57
  let db: Database | null = null;
49
58
 
@@ -55,9 +64,7 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
55
64
  db = new Database(dbPath, { create: true });
56
65
  console.log(`Database initialized at ${dbPath}`);
57
66
 
58
- // Capture in local const for TypeScript (db is guaranteed non-null here)
59
67
  const database = db;
60
-
61
68
  database.run("PRAGMA journal_mode = WAL;");
62
69
  database.run("PRAGMA foreign_keys = ON;");
63
70
 
@@ -187,6 +194,12 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
187
194
  // Backfill: Seed v1 for existing agents that don't have any context versions yet
188
195
  seedContextVersions();
189
196
 
197
+ // Inject DB resolver into the prompt template resolver (DI to avoid worker/API boundary violation)
198
+ configureDbResolver(resolvePromptTemplate);
199
+
200
+ // Seed default prompt templates from the in-memory code registry
201
+ seedDefaultTemplates();
202
+
190
203
  return db;
191
204
  }
192
205
 
@@ -2153,6 +2166,16 @@ export function getUnassignedTasksCount(): number {
2153
2166
  return result?.count ?? 0;
2154
2167
  }
2155
2168
 
2169
+ /** Get unassigned task IDs, ordered by priority (highest first) then creation time */
2170
+ export function getUnassignedTaskIds(limit = 10): string[] {
2171
+ const rows = getDb()
2172
+ .prepare<{ id: string }, [number]>(
2173
+ "SELECT id FROM agent_tasks WHERE status = 'unassigned' ORDER BY priority DESC, createdAt ASC LIMIT ?",
2174
+ )
2175
+ .all(limit);
2176
+ return rows.map((r) => r.id);
2177
+ }
2178
+
2156
2179
  // ============================================================================
2157
2180
  // Dependency Checking
2158
2181
  // ============================================================================
@@ -5430,6 +5453,7 @@ export function insertActiveSession(session: {
5430
5453
  triggerType: string;
5431
5454
  inboxMessageId?: string;
5432
5455
  taskDescription?: string;
5456
+ runnerSessionId?: string;
5433
5457
  }): ActiveSession {
5434
5458
  const id = crypto.randomUUID();
5435
5459
  const now = new Date().toISOString();
@@ -5437,10 +5461,20 @@ export function insertActiveSession(session: {
5437
5461
  const row = getDb()
5438
5462
  .prepare<
5439
5463
  ActiveSession,
5440
- [string, string, string | null, string, string | null, string | null, string, string]
5464
+ [
5465
+ string,
5466
+ string,
5467
+ string | null,
5468
+ string,
5469
+ string | null,
5470
+ string | null,
5471
+ string | null,
5472
+ string,
5473
+ string,
5474
+ ]
5441
5475
  >(
5442
- `INSERT INTO active_sessions (id, agentId, taskId, triggerType, inboxMessageId, taskDescription, startedAt, lastHeartbeatAt)
5443
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
5476
+ `INSERT INTO active_sessions (id, agentId, taskId, triggerType, inboxMessageId, taskDescription, runnerSessionId, startedAt, lastHeartbeatAt)
5477
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5444
5478
  RETURNING *`,
5445
5479
  )
5446
5480
  .get(
@@ -5450,6 +5484,7 @@ export function insertActiveSession(session: {
5450
5484
  session.triggerType,
5451
5485
  session.inboxMessageId ?? null,
5452
5486
  session.taskDescription ?? null,
5487
+ session.runnerSessionId ?? null,
5453
5488
  now,
5454
5489
  now,
5455
5490
  );
@@ -5502,6 +5537,30 @@ export function cleanupAgentSessions(agentId: string): number {
5502
5537
  return result.changes;
5503
5538
  }
5504
5539
 
5540
+ /** Update providerSessionId on an active session identified by taskId */
5541
+ export function updateActiveSessionProviderSessionId(
5542
+ taskId: string,
5543
+ providerSessionId: string,
5544
+ ): boolean {
5545
+ const result = getDb()
5546
+ .prepare("UPDATE active_sessions SET providerSessionId = ? WHERE taskId = ?")
5547
+ .run(providerSessionId, taskId);
5548
+ return result.changes > 0;
5549
+ }
5550
+
5551
+ /**
5552
+ * Reassociate session logs from a runner session to a real task ID.
5553
+ * Used when a pool task is claimed — logs were stored under a random UUID,
5554
+ * this updates them to use the real task ID.
5555
+ * Idempotent — safe to call multiple times.
5556
+ */
5557
+ export function reassociateSessionLogs(runnerSessionId: string, realTaskId: string): number {
5558
+ const result = getDb()
5559
+ .prepare("UPDATE session_logs SET taskId = ? WHERE sessionId = ? AND taskId != ?")
5560
+ .run(realTaskId, runnerSessionId, realTaskId);
5561
+ return result.changes;
5562
+ }
5563
+
5505
5564
  // ============================================================================
5506
5565
  // Heartbeat / Triage Query Functions
5507
5566
  // ============================================================================
@@ -5571,6 +5630,8 @@ type WorkflowRow = {
5571
5630
  cooldown: string | null;
5572
5631
  input: string | null;
5573
5632
  triggerSchema: string | null;
5633
+ dir: string | null;
5634
+ vcs_repo: string | null;
5574
5635
  createdByAgentId: string | null;
5575
5636
  createdAt: string;
5576
5637
  lastUpdatedAt: string;
@@ -5589,6 +5650,8 @@ function rowToWorkflow(row: WorkflowRow): Workflow {
5589
5650
  triggerSchema: row.triggerSchema
5590
5651
  ? (JSON.parse(row.triggerSchema) as Record<string, unknown>)
5591
5652
  : undefined,
5653
+ dir: row.dir ?? undefined,
5654
+ vcsRepo: row.vcs_repo ?? undefined,
5592
5655
  createdByAgentId: row.createdByAgentId ?? undefined,
5593
5656
  createdAt: row.createdAt,
5594
5657
  lastUpdatedAt: row.lastUpdatedAt,
@@ -5603,6 +5666,8 @@ export function createWorkflow(data: {
5603
5666
  cooldown?: CooldownConfig;
5604
5667
  input?: Record<string, InputValue>;
5605
5668
  triggerSchema?: Record<string, unknown>;
5669
+ dir?: string;
5670
+ vcsRepo?: string;
5606
5671
  createdByAgentId?: string;
5607
5672
  }): Workflow {
5608
5673
  const id = crypto.randomUUID();
@@ -5619,10 +5684,12 @@ export function createWorkflow(data: {
5619
5684
  string | null,
5620
5685
  string | null,
5621
5686
  string | null,
5687
+ string | null,
5688
+ string | null,
5622
5689
  ]
5623
5690
  >(
5624
- `INSERT INTO workflows (id, name, description, definition, triggers, cooldown, input, triggerSchema, createdByAgentId)
5625
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
5691
+ `INSERT INTO workflows (id, name, description, definition, triggers, cooldown, input, triggerSchema, dir, vcs_repo, createdByAgentId)
5692
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
5626
5693
  )
5627
5694
  .get(
5628
5695
  id,
@@ -5633,6 +5700,8 @@ export function createWorkflow(data: {
5633
5700
  data.cooldown ? JSON.stringify(data.cooldown) : null,
5634
5701
  data.input ? JSON.stringify(data.input) : null,
5635
5702
  data.triggerSchema ? JSON.stringify(data.triggerSchema) : null,
5703
+ data.dir ?? null,
5704
+ data.vcsRepo ?? null,
5636
5705
  data.createdByAgentId ?? null,
5637
5706
  );
5638
5707
  if (!row) throw new Error("Failed to create workflow");
@@ -5671,6 +5740,8 @@ export function updateWorkflow(
5671
5740
  cooldown?: CooldownConfig | null;
5672
5741
  input?: Record<string, InputValue> | null;
5673
5742
  triggerSchema?: Record<string, unknown> | null;
5743
+ dir?: string | null;
5744
+ vcsRepo?: string | null;
5674
5745
  },
5675
5746
  ): Workflow | null {
5676
5747
  const updates: string[] = [];
@@ -5707,6 +5778,14 @@ export function updateWorkflow(
5707
5778
  updates.push("triggerSchema = ?");
5708
5779
  params.push(data.triggerSchema ? JSON.stringify(data.triggerSchema) : null);
5709
5780
  }
5781
+ if (data.dir !== undefined) {
5782
+ updates.push("dir = ?");
5783
+ params.push(data.dir ?? null);
5784
+ }
5785
+ if (data.vcsRepo !== undefined) {
5786
+ updates.push("vcs_repo = ?");
5787
+ params.push(data.vcsRepo ?? null);
5788
+ }
5710
5789
  if (updates.length === 0) return getWorkflow(id);
5711
5790
  updates.push("lastUpdatedAt = ?");
5712
5791
  params.push(new Date().toISOString());
@@ -5739,6 +5818,22 @@ export function deleteWorkflow(id: string): boolean {
5739
5818
  return result.changes > 0;
5740
5819
  }
5741
5820
 
5821
+ /**
5822
+ * Find enabled workflows that have a schedule trigger matching the given scheduleId.
5823
+ * Uses SQLite JSON functions to query into the triggers JSON array.
5824
+ */
5825
+ export function getWorkflowsByScheduleId(scheduleId: string): Workflow[] {
5826
+ const rows = getDb()
5827
+ .prepare<WorkflowRow, [string]>(
5828
+ `SELECT w.* FROM workflows w, json_each(w.triggers) AS t
5829
+ WHERE w.enabled = 1
5830
+ AND json_extract(t.value, '$.type') = 'schedule'
5831
+ AND json_extract(t.value, '$.scheduleId') = ?`,
5832
+ )
5833
+ .all(scheduleId);
5834
+ return rows.map(rowToWorkflow);
5835
+ }
5836
+
5742
5837
  // ============================================================================
5743
5838
  // Workflow Run CRUD
5744
5839
  // ============================================================================
@@ -6138,3 +6233,1134 @@ export function getWorkflowVersion(workflowId: string, version: number): Workflo
6138
6233
  .get(workflowId, version);
6139
6234
  return row ? rowToWorkflowVersion(row) : null;
6140
6235
  }
6236
+
6237
+ // ============================================================================
6238
+ // Prompt Template Operations
6239
+ // ============================================================================
6240
+
6241
+ type PromptTemplateRow = {
6242
+ id: string;
6243
+ eventType: string;
6244
+ scope: string;
6245
+ scopeId: string | null;
6246
+ state: string;
6247
+ body: string;
6248
+ isDefault: number; // SQLite boolean
6249
+ version: number;
6250
+ createdBy: string | null;
6251
+ createdAt: string;
6252
+ updatedAt: string;
6253
+ };
6254
+
6255
+ type PromptTemplateHistoryRow = {
6256
+ id: string;
6257
+ templateId: string;
6258
+ version: number;
6259
+ body: string;
6260
+ state: string;
6261
+ changedBy: string | null;
6262
+ changedAt: string;
6263
+ changeReason: string | null;
6264
+ };
6265
+
6266
+ function rowToPromptTemplate(row: PromptTemplateRow): PromptTemplate {
6267
+ return {
6268
+ id: row.id,
6269
+ eventType: row.eventType,
6270
+ scope: row.scope as "global" | "agent" | "repo",
6271
+ scopeId: row.scopeId ?? null,
6272
+ state: row.state as "enabled" | "default_prompt_fallback" | "skip_event",
6273
+ body: row.body,
6274
+ isDefault: row.isDefault === 1,
6275
+ version: row.version,
6276
+ createdBy: row.createdBy ?? null,
6277
+ createdAt: row.createdAt,
6278
+ updatedAt: row.updatedAt,
6279
+ };
6280
+ }
6281
+
6282
+ function rowToPromptTemplateHistory(row: PromptTemplateHistoryRow): PromptTemplateHistory {
6283
+ return {
6284
+ id: row.id,
6285
+ templateId: row.templateId,
6286
+ version: row.version,
6287
+ body: row.body,
6288
+ state: row.state,
6289
+ changedBy: row.changedBy ?? null,
6290
+ changedAt: row.changedAt,
6291
+ changeReason: row.changeReason ?? null,
6292
+ };
6293
+ }
6294
+
6295
+ /**
6296
+ * List prompt templates with optional filters.
6297
+ */
6298
+ export function getPromptTemplates(filters?: {
6299
+ eventType?: string;
6300
+ scope?: string;
6301
+ scopeId?: string;
6302
+ isDefault?: boolean;
6303
+ }): PromptTemplate[] {
6304
+ const conditions: string[] = [];
6305
+ const params: (string | number)[] = [];
6306
+
6307
+ if (filters?.eventType) {
6308
+ conditions.push("eventType = ?");
6309
+ params.push(filters.eventType);
6310
+ }
6311
+ if (filters?.scope) {
6312
+ conditions.push("scope = ?");
6313
+ params.push(filters.scope);
6314
+ }
6315
+ if (filters?.scopeId) {
6316
+ conditions.push("scopeId = ?");
6317
+ params.push(filters.scopeId);
6318
+ }
6319
+ if (filters?.isDefault !== undefined) {
6320
+ conditions.push("isDefault = ?");
6321
+ params.push(filters.isDefault ? 1 : 0);
6322
+ }
6323
+
6324
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
6325
+ const query = `SELECT * FROM prompt_templates ${whereClause} ORDER BY eventType ASC`;
6326
+
6327
+ return getDb()
6328
+ .prepare<PromptTemplateRow, (string | number)[]>(query)
6329
+ .all(...params)
6330
+ .map(rowToPromptTemplate);
6331
+ }
6332
+
6333
+ /**
6334
+ * Get a single prompt template by ID.
6335
+ */
6336
+ export function getPromptTemplateById(id: string): PromptTemplate | null {
6337
+ const row = getDb()
6338
+ .prepare<PromptTemplateRow, [string]>("SELECT * FROM prompt_templates WHERE id = ?")
6339
+ .get(id);
6340
+ return row ? rowToPromptTemplate(row) : null;
6341
+ }
6342
+
6343
+ /**
6344
+ * Upsert a prompt template. Inserts or updates by (eventType, scope, scopeId) unique constraint.
6345
+ * Creates a history entry on both insert and update.
6346
+ */
6347
+ export function upsertPromptTemplate(data: {
6348
+ eventType: string;
6349
+ scope: "global" | "agent" | "repo";
6350
+ scopeId?: string | null;
6351
+ state?: "enabled" | "default_prompt_fallback" | "skip_event";
6352
+ body: string;
6353
+ createdBy?: string | null;
6354
+ changedBy?: string | null;
6355
+ changeReason?: string | null;
6356
+ isDefault?: boolean;
6357
+ }): PromptTemplate {
6358
+ const now = new Date().toISOString();
6359
+ const scopeId = data.scope === "global" ? null : (data.scopeId ?? null);
6360
+ const state = data.state ?? "enabled";
6361
+ const createdBy = data.createdBy ?? data.changedBy ?? null;
6362
+ const changedBy = data.changedBy ?? data.createdBy ?? null;
6363
+ const changeReason = data.changeReason ?? null;
6364
+
6365
+ // Manual check for existing entry because SQLite's UNIQUE constraint
6366
+ // treats NULL != NULL, so ON CONFLICT never fires when scopeId is NULL (global scope).
6367
+ const existing =
6368
+ scopeId === null
6369
+ ? getDb()
6370
+ .prepare<PromptTemplateRow, [string, string]>(
6371
+ "SELECT * FROM prompt_templates WHERE eventType = ? AND scope = ? AND scopeId IS NULL",
6372
+ )
6373
+ .get(data.eventType, data.scope)
6374
+ : getDb()
6375
+ .prepare<PromptTemplateRow, [string, string, string]>(
6376
+ "SELECT * FROM prompt_templates WHERE eventType = ? AND scope = ? AND scopeId = ?",
6377
+ )
6378
+ .get(data.eventType, data.scope, scopeId);
6379
+
6380
+ let row: PromptTemplateRow | null;
6381
+
6382
+ if (existing) {
6383
+ // If upserting at global scope and existing record has isDefault=true, flip it to false
6384
+ const newIsDefault =
6385
+ data.scope === "global" && existing.isDefault === 1 ? 0 : existing.isDefault;
6386
+ const newVersion = existing.version + 1;
6387
+
6388
+ row = getDb()
6389
+ .prepare<PromptTemplateRow, [string, string, number, number, string, string]>(
6390
+ `UPDATE prompt_templates SET body = ?, state = ?, isDefault = ?, version = ?, updatedAt = ?
6391
+ WHERE id = ? RETURNING *`,
6392
+ )
6393
+ .get(data.body, state, newIsDefault, newVersion, now, existing.id);
6394
+
6395
+ // Create history entry for the update
6396
+ getDb()
6397
+ .prepare(
6398
+ `INSERT INTO prompt_template_history (id, templateId, version, body, state, changedBy, changedAt, changeReason)
6399
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
6400
+ )
6401
+ .run(
6402
+ crypto.randomUUID(),
6403
+ existing.id,
6404
+ newVersion,
6405
+ data.body,
6406
+ state,
6407
+ changedBy,
6408
+ now,
6409
+ changeReason,
6410
+ );
6411
+ } else {
6412
+ const id = crypto.randomUUID();
6413
+ row = getDb()
6414
+ .prepare<
6415
+ PromptTemplateRow,
6416
+ [
6417
+ string,
6418
+ string,
6419
+ string,
6420
+ string | null,
6421
+ string,
6422
+ string,
6423
+ number,
6424
+ number,
6425
+ string | null,
6426
+ string,
6427
+ string,
6428
+ ]
6429
+ >(
6430
+ `INSERT INTO prompt_templates (id, eventType, scope, scopeId, state, body, isDefault, version, createdBy, createdAt, updatedAt)
6431
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
6432
+ )
6433
+ .get(
6434
+ id,
6435
+ data.eventType,
6436
+ data.scope,
6437
+ scopeId,
6438
+ state,
6439
+ data.body,
6440
+ data.isDefault ? 1 : 0,
6441
+ 1,
6442
+ createdBy,
6443
+ now,
6444
+ now,
6445
+ );
6446
+
6447
+ // Create history entry for the insert
6448
+ getDb()
6449
+ .prepare(
6450
+ `INSERT INTO prompt_template_history (id, templateId, version, body, state, changedBy, changedAt, changeReason)
6451
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
6452
+ )
6453
+ .run(
6454
+ crypto.randomUUID(),
6455
+ id,
6456
+ 1,
6457
+ data.body,
6458
+ state,
6459
+ changedBy,
6460
+ now,
6461
+ changeReason ?? "Initial creation",
6462
+ );
6463
+ }
6464
+
6465
+ if (!row) throw new Error("Failed to upsert prompt template");
6466
+ return rowToPromptTemplate(row);
6467
+ }
6468
+
6469
+ /**
6470
+ * Delete a prompt template by ID. Guards against deleting default templates.
6471
+ * Does NOT delete history rows (intentional for audit trail).
6472
+ */
6473
+ export function deletePromptTemplate(id: string): boolean {
6474
+ const existing = getDb()
6475
+ .prepare<PromptTemplateRow, [string]>("SELECT * FROM prompt_templates WHERE id = ?")
6476
+ .get(id);
6477
+
6478
+ if (!existing) return false;
6479
+ if (existing.isDefault === 1) {
6480
+ throw new Error(
6481
+ "Cannot delete a default prompt template. Use resetPromptTemplateToDefault instead.",
6482
+ );
6483
+ }
6484
+
6485
+ const result = getDb().run("DELETE FROM prompt_templates WHERE id = ?", [id]);
6486
+ return result.changes > 0;
6487
+ }
6488
+
6489
+ /**
6490
+ * Reset a prompt template to its default state.
6491
+ * Sets body to defaultBody, isDefault=true, state='enabled', bumps version.
6492
+ */
6493
+ export function resetPromptTemplateToDefault(id: string, defaultBody: string): PromptTemplate {
6494
+ const now = new Date().toISOString();
6495
+ const existing = getDb()
6496
+ .prepare<PromptTemplateRow, [string]>("SELECT * FROM prompt_templates WHERE id = ?")
6497
+ .get(id);
6498
+
6499
+ if (!existing) throw new Error(`Prompt template ${id} not found`);
6500
+
6501
+ const newVersion = existing.version + 1;
6502
+
6503
+ const row = getDb()
6504
+ .prepare<PromptTemplateRow, [string, number, string, string]>(
6505
+ `UPDATE prompt_templates SET body = ?, state = 'enabled', isDefault = 1, version = ?, updatedAt = ?
6506
+ WHERE id = ? RETURNING *`,
6507
+ )
6508
+ .get(defaultBody, newVersion, now, id);
6509
+
6510
+ if (!row) throw new Error("Failed to reset prompt template to default");
6511
+
6512
+ // Create history entry
6513
+ getDb()
6514
+ .prepare(
6515
+ `INSERT INTO prompt_template_history (id, templateId, version, body, state, changedBy, changedAt, changeReason)
6516
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
6517
+ )
6518
+ .run(
6519
+ crypto.randomUUID(),
6520
+ id,
6521
+ newVersion,
6522
+ defaultBody,
6523
+ "enabled",
6524
+ null,
6525
+ now,
6526
+ "Reset to default",
6527
+ );
6528
+
6529
+ return rowToPromptTemplate(row);
6530
+ }
6531
+
6532
+ /**
6533
+ * Get version history for a prompt template, ordered by version DESC.
6534
+ */
6535
+ export function getPromptTemplateHistory(templateId: string): PromptTemplateHistory[] {
6536
+ return getDb()
6537
+ .prepare<PromptTemplateHistoryRow, [string]>(
6538
+ "SELECT * FROM prompt_template_history WHERE templateId = ? ORDER BY version DESC",
6539
+ )
6540
+ .all(templateId)
6541
+ .map(rowToPromptTemplateHistory);
6542
+ }
6543
+
6544
+ /**
6545
+ * Resolve the best prompt template for a given eventType using scope precedence.
6546
+ *
6547
+ * Two-pass resolution:
6548
+ * Pass 1 (exact match): Try exact eventType at agent → repo → global scope.
6549
+ * Pass 2 (wildcard): Generate wildcards from eventType (e.g. "github.pull_request.*", "github.*")
6550
+ * and try each at agent → repo → global scope.
6551
+ *
6552
+ * Exact match at ANY scope always beats wildcard at ANY scope.
6553
+ *
6554
+ * State behavior:
6555
+ * - 'enabled': return the template
6556
+ * - 'skip_event': return { skip: true }
6557
+ * - 'default_prompt_fallback': continue to next scope level
6558
+ */
6559
+ export function resolvePromptTemplate(
6560
+ eventType: string,
6561
+ agentId?: string,
6562
+ repoId?: string,
6563
+ ): { template: PromptTemplate } | { skip: true } | null {
6564
+ // Helper to look up a template at a specific scope
6565
+ const lookupAtScope = (
6566
+ et: string,
6567
+ scope: "global" | "agent" | "repo",
6568
+ scopeId: string | null,
6569
+ ): PromptTemplateRow | undefined => {
6570
+ if (scopeId === null) {
6571
+ return (
6572
+ getDb()
6573
+ .prepare<PromptTemplateRow, [string, string]>(
6574
+ "SELECT * FROM prompt_templates WHERE eventType = ? AND scope = ? AND scopeId IS NULL",
6575
+ )
6576
+ .get(et, scope) ?? undefined
6577
+ );
6578
+ }
6579
+ return (
6580
+ getDb()
6581
+ .prepare<PromptTemplateRow, [string, string, string]>(
6582
+ "SELECT * FROM prompt_templates WHERE eventType = ? AND scope = ? AND scopeId = ?",
6583
+ )
6584
+ .get(et, scope, scopeId) ?? undefined
6585
+ );
6586
+ };
6587
+
6588
+ // Try resolution at the scope chain for a given eventType string
6589
+ const tryResolve = (et: string): { template: PromptTemplate } | { skip: true } | "continue" => {
6590
+ // Build scope chain: agent → repo → global
6591
+ const scopeChain: Array<{ scope: "global" | "agent" | "repo"; scopeId: string | null }> = [];
6592
+ if (agentId) scopeChain.push({ scope: "agent", scopeId: agentId });
6593
+ if (repoId) scopeChain.push({ scope: "repo", scopeId: repoId });
6594
+ scopeChain.push({ scope: "global", scopeId: null });
6595
+
6596
+ for (const { scope, scopeId } of scopeChain) {
6597
+ const row = lookupAtScope(et, scope, scopeId);
6598
+ if (!row) continue;
6599
+
6600
+ if (row.state === "enabled") {
6601
+ return { template: rowToPromptTemplate(row) };
6602
+ }
6603
+ if (row.state === "skip_event") {
6604
+ return { skip: true };
6605
+ }
6606
+ // default_prompt_fallback: continue to next scope
6607
+ }
6608
+
6609
+ return "continue";
6610
+ };
6611
+
6612
+ // Pass 1: exact match
6613
+ const exactResult = tryResolve(eventType);
6614
+ if (exactResult !== "continue") return exactResult;
6615
+
6616
+ // Pass 2: wildcard matching
6617
+ // e.g. "github.pull_request.review_submitted" → ["github.pull_request.*", "github.*"]
6618
+ const parts = eventType.split(".");
6619
+ const wildcards: string[] = [];
6620
+ for (let i = parts.length - 1; i >= 1; i--) {
6621
+ wildcards.push(`${parts.slice(0, i).join(".")}.*`);
6622
+ }
6623
+
6624
+ for (const wildcard of wildcards) {
6625
+ const wildcardResult = tryResolve(wildcard);
6626
+ if (wildcardResult !== "continue") return wildcardResult;
6627
+ }
6628
+
6629
+ return null;
6630
+ }
6631
+
6632
+ /**
6633
+ * Checkout a prompt template to a specific version from history.
6634
+ * Copies body and state from the history entry into the live record, bumps version.
6635
+ */
6636
+ export function checkoutPromptTemplate(id: string, targetVersion: number): PromptTemplate {
6637
+ const now = new Date().toISOString();
6638
+
6639
+ const existing = getDb()
6640
+ .prepare<PromptTemplateRow, [string]>("SELECT * FROM prompt_templates WHERE id = ?")
6641
+ .get(id);
6642
+ if (!existing) throw new Error(`Prompt template ${id} not found`);
6643
+
6644
+ const historyEntry = getDb()
6645
+ .prepare<PromptTemplateHistoryRow, [string, number]>(
6646
+ "SELECT * FROM prompt_template_history WHERE templateId = ? AND version = ?",
6647
+ )
6648
+ .get(id, targetVersion);
6649
+ if (!historyEntry)
6650
+ throw new Error(`No history entry at version ${targetVersion} for template ${id}`);
6651
+
6652
+ const newVersion = existing.version + 1;
6653
+
6654
+ const row = getDb()
6655
+ .prepare<PromptTemplateRow, [string, string, number, string, string]>(
6656
+ `UPDATE prompt_templates SET body = ?, state = ?, version = ?, updatedAt = ?
6657
+ WHERE id = ? RETURNING *`,
6658
+ )
6659
+ .get(historyEntry.body, historyEntry.state, newVersion, now, id);
6660
+
6661
+ if (!row) throw new Error("Failed to checkout prompt template");
6662
+
6663
+ // Create history entry for the checkout
6664
+ getDb()
6665
+ .prepare(
6666
+ `INSERT INTO prompt_template_history (id, templateId, version, body, state, changedBy, changedAt, changeReason)
6667
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
6668
+ )
6669
+ .run(
6670
+ crypto.randomUUID(),
6671
+ id,
6672
+ newVersion,
6673
+ historyEntry.body,
6674
+ historyEntry.state,
6675
+ null,
6676
+ now,
6677
+ `Checked out from version ${targetVersion}`,
6678
+ );
6679
+
6680
+ return rowToPromptTemplate(row);
6681
+ }
6682
+
6683
+ // ─── Channel Activity Cursors ─────────────────────────────────────────────────
6684
+
6685
+ type ChannelActivityCursorRow = {
6686
+ channelId: string;
6687
+ lastSeenTs: string;
6688
+ updatedAt: string;
6689
+ };
6690
+
6691
+ export interface ChannelActivityCursor {
6692
+ channelId: string;
6693
+ lastSeenTs: string;
6694
+ updatedAt: string;
6695
+ }
6696
+
6697
+ function rowToChannelActivityCursor(row: ChannelActivityCursorRow): ChannelActivityCursor {
6698
+ return {
6699
+ channelId: row.channelId,
6700
+ lastSeenTs: row.lastSeenTs,
6701
+ updatedAt: row.updatedAt,
6702
+ };
6703
+ }
6704
+
6705
+ export function getAllChannelActivityCursors(): ChannelActivityCursor[] {
6706
+ return getDb()
6707
+ .prepare<ChannelActivityCursorRow, []>("SELECT * FROM channel_activity_cursors")
6708
+ .all()
6709
+ .map(rowToChannelActivityCursor);
6710
+ }
6711
+
6712
+ export function getChannelActivityCursor(channelId: string): ChannelActivityCursor | null {
6713
+ const row = getDb()
6714
+ .prepare<ChannelActivityCursorRow, [string]>(
6715
+ "SELECT * FROM channel_activity_cursors WHERE channelId = ?",
6716
+ )
6717
+ .get(channelId);
6718
+ return row ? rowToChannelActivityCursor(row) : null;
6719
+ }
6720
+
6721
+ export function upsertChannelActivityCursor(channelId: string, lastSeenTs: string): void {
6722
+ getDb()
6723
+ .prepare(
6724
+ `INSERT INTO channel_activity_cursors (channelId, lastSeenTs, updatedAt)
6725
+ VALUES (?, ?, datetime('now'))
6726
+ ON CONFLICT(channelId) DO UPDATE SET lastSeenTs = excluded.lastSeenTs, updatedAt = excluded.updatedAt`,
6727
+ )
6728
+ .run(channelId, lastSeenTs);
6729
+ }
6730
+
6731
+ // ============================================================================
6732
+ // Approval Requests
6733
+ // ============================================================================
6734
+
6735
+ export interface ApprovalRequest {
6736
+ id: string;
6737
+ title: string;
6738
+ questions: unknown[];
6739
+ workflowRunId: string | null;
6740
+ workflowRunStepId: string | null;
6741
+ sourceTaskId: string | null;
6742
+ approvers: unknown;
6743
+ status: "pending" | "approved" | "rejected" | "timeout";
6744
+ responses: unknown | null;
6745
+ resolvedBy: string | null;
6746
+ resolvedAt: string | null;
6747
+ timeoutSeconds: number | null;
6748
+ expiresAt: string | null;
6749
+ notificationChannels: unknown[] | null;
6750
+ createdAt: string;
6751
+ updatedAt: string;
6752
+ }
6753
+
6754
+ interface ApprovalRequestRow {
6755
+ id: string;
6756
+ title: string;
6757
+ questions: string;
6758
+ workflowRunId: string | null;
6759
+ workflowRunStepId: string | null;
6760
+ sourceTaskId: string | null;
6761
+ approvers: string;
6762
+ status: string;
6763
+ responses: string | null;
6764
+ resolvedBy: string | null;
6765
+ resolvedAt: string | null;
6766
+ timeoutSeconds: number | null;
6767
+ expiresAt: string | null;
6768
+ notificationChannels: string | null;
6769
+ createdAt: string;
6770
+ updatedAt: string;
6771
+ }
6772
+
6773
+ function rowToApprovalRequest(row: ApprovalRequestRow): ApprovalRequest {
6774
+ return {
6775
+ id: row.id,
6776
+ title: row.title,
6777
+ questions: JSON.parse(row.questions),
6778
+ workflowRunId: row.workflowRunId,
6779
+ workflowRunStepId: row.workflowRunStepId,
6780
+ sourceTaskId: row.sourceTaskId,
6781
+ approvers: JSON.parse(row.approvers),
6782
+ status: row.status as ApprovalRequest["status"],
6783
+ responses: row.responses ? JSON.parse(row.responses) : null,
6784
+ resolvedBy: row.resolvedBy,
6785
+ resolvedAt: row.resolvedAt,
6786
+ timeoutSeconds: row.timeoutSeconds,
6787
+ expiresAt: row.expiresAt,
6788
+ notificationChannels: row.notificationChannels ? JSON.parse(row.notificationChannels) : null,
6789
+ createdAt: row.createdAt,
6790
+ updatedAt: row.updatedAt,
6791
+ };
6792
+ }
6793
+
6794
+ export function createApprovalRequest(data: {
6795
+ id: string;
6796
+ title: string;
6797
+ questions: unknown[];
6798
+ approvers: unknown;
6799
+ workflowRunId?: string;
6800
+ workflowRunStepId?: string;
6801
+ sourceTaskId?: string;
6802
+ timeoutSeconds?: number;
6803
+ notificationChannels?: unknown[];
6804
+ }): ApprovalRequest {
6805
+ const now = new Date().toISOString();
6806
+ const expiresAt = data.timeoutSeconds
6807
+ ? new Date(Date.now() + data.timeoutSeconds * 1000).toISOString()
6808
+ : null;
6809
+
6810
+ const row = getDb()
6811
+ .prepare<
6812
+ ApprovalRequestRow,
6813
+ [
6814
+ string,
6815
+ string,
6816
+ string,
6817
+ string | null,
6818
+ string | null,
6819
+ string | null,
6820
+ string,
6821
+ number | null,
6822
+ string | null,
6823
+ string | null,
6824
+ string,
6825
+ string,
6826
+ ]
6827
+ >(
6828
+ `INSERT INTO approval_requests (id, title, questions, workflowRunId, workflowRunStepId, sourceTaskId, approvers, timeoutSeconds, expiresAt, notificationChannels, createdAt, updatedAt)
6829
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6830
+ RETURNING *`,
6831
+ )
6832
+ .get(
6833
+ data.id,
6834
+ data.title,
6835
+ JSON.stringify(data.questions),
6836
+ data.workflowRunId ?? null,
6837
+ data.workflowRunStepId ?? null,
6838
+ data.sourceTaskId ?? null,
6839
+ JSON.stringify(data.approvers),
6840
+ data.timeoutSeconds ?? null,
6841
+ expiresAt,
6842
+ data.notificationChannels ? JSON.stringify(data.notificationChannels) : null,
6843
+ now,
6844
+ now,
6845
+ );
6846
+
6847
+ return rowToApprovalRequest(row!);
6848
+ }
6849
+
6850
+ export function getApprovalRequestById(id: string): ApprovalRequest | null {
6851
+ const row = getDb()
6852
+ .prepare<ApprovalRequestRow, [string]>("SELECT * FROM approval_requests WHERE id = ?")
6853
+ .get(id);
6854
+ return row ? rowToApprovalRequest(row) : null;
6855
+ }
6856
+
6857
+ export function resolveApprovalRequest(
6858
+ id: string,
6859
+ data: {
6860
+ status: "approved" | "rejected" | "timeout";
6861
+ responses?: unknown;
6862
+ resolvedBy?: string;
6863
+ },
6864
+ ): ApprovalRequest | null {
6865
+ const now = new Date().toISOString();
6866
+ const row = getDb()
6867
+ .prepare<ApprovalRequestRow, [string, string | null, string | null, string, string, string]>(
6868
+ `UPDATE approval_requests
6869
+ SET status = ?, responses = ?, resolvedBy = ?, resolvedAt = ?, updatedAt = ?
6870
+ WHERE id = ? AND status = 'pending'
6871
+ RETURNING *`,
6872
+ )
6873
+ .get(
6874
+ data.status,
6875
+ data.responses ? JSON.stringify(data.responses) : null,
6876
+ data.resolvedBy ?? null,
6877
+ now,
6878
+ now,
6879
+ id,
6880
+ );
6881
+ return row ? rowToApprovalRequest(row) : null;
6882
+ }
6883
+
6884
+ export function listApprovalRequests(filters?: {
6885
+ status?: string;
6886
+ workflowRunId?: string;
6887
+ limit?: number;
6888
+ }): ApprovalRequest[] {
6889
+ const conditions: string[] = [];
6890
+ const params: (string | number)[] = [];
6891
+
6892
+ if (filters?.status) {
6893
+ conditions.push("status = ?");
6894
+ params.push(filters.status);
6895
+ }
6896
+ if (filters?.workflowRunId) {
6897
+ conditions.push("workflowRunId = ?");
6898
+ params.push(filters.workflowRunId);
6899
+ }
6900
+
6901
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
6902
+ const limit = filters?.limit ?? 100;
6903
+ params.push(limit);
6904
+
6905
+ const stmt = getDb().prepare(
6906
+ `SELECT * FROM approval_requests ${where} ORDER BY createdAt DESC LIMIT ?`,
6907
+ );
6908
+ const rows = stmt.all(...params) as ApprovalRequestRow[];
6909
+
6910
+ return rows.map(rowToApprovalRequest);
6911
+ }
6912
+
6913
+ export interface StuckApprovalRun {
6914
+ runId: string;
6915
+ stepId: string;
6916
+ nodeId: string;
6917
+ workflowId: string;
6918
+ approvalId: string;
6919
+ approvalStatus: string;
6920
+ approvalResponses: string | null;
6921
+ expiresAt: string | null;
6922
+ }
6923
+
6924
+ export function getStuckApprovalRuns(): StuckApprovalRun[] {
6925
+ return getDb()
6926
+ .prepare<StuckApprovalRun, []>(
6927
+ `SELECT
6928
+ wr.id as runId,
6929
+ wrs.id as stepId,
6930
+ wrs.nodeId,
6931
+ wr.workflowId,
6932
+ ar.id as approvalId,
6933
+ ar.status as approvalStatus,
6934
+ ar.responses as approvalResponses,
6935
+ ar.expiresAt
6936
+ FROM workflow_runs wr
6937
+ JOIN workflow_run_steps wrs ON wrs.runId = wr.id AND wrs.status = 'waiting'
6938
+ JOIN approval_requests ar ON ar.workflowRunStepId = wrs.id
6939
+ WHERE wr.status = 'waiting'
6940
+ AND (ar.status IN ('approved', 'rejected', 'timeout')
6941
+ OR (ar.status = 'pending' AND ar.expiresAt IS NOT NULL AND ar.expiresAt < datetime('now')))`,
6942
+ )
6943
+ .all();
6944
+ }
6945
+
6946
+ export function getApprovalRequestByStepId(stepId: string): ApprovalRequest | null {
6947
+ const row = getDb()
6948
+ .prepare<ApprovalRequestRow, [string]>(
6949
+ "SELECT * FROM approval_requests WHERE workflowRunStepId = ?",
6950
+ )
6951
+ .get(stepId);
6952
+ return row ? rowToApprovalRequest(row) : null;
6953
+ }
6954
+
6955
+ // TODO: Wire into a periodic cron/sweep to auto-timeout expired approval requests (Phase 2)
6956
+ export function getExpiredPendingApprovals(): ApprovalRequest[] {
6957
+ const rows = getDb()
6958
+ .prepare<ApprovalRequestRow, []>(
6959
+ `SELECT * FROM approval_requests
6960
+ WHERE status = 'pending'
6961
+ AND expiresAt IS NOT NULL
6962
+ AND expiresAt < datetime('now')`,
6963
+ )
6964
+ .all();
6965
+ return rows.map(rowToApprovalRequest);
6966
+ }
6967
+
6968
+ // ============================================================================
6969
+ // Skills
6970
+ // ============================================================================
6971
+
6972
+ type SkillRow = {
6973
+ id: string;
6974
+ name: string;
6975
+ description: string;
6976
+ content: string;
6977
+ type: string;
6978
+ scope: string;
6979
+ ownerAgentId: string | null;
6980
+ sourceUrl: string | null;
6981
+ sourceRepo: string | null;
6982
+ sourcePath: string | null;
6983
+ sourceBranch: string;
6984
+ sourceHash: string | null;
6985
+ isComplex: number;
6986
+ allowedTools: string | null;
6987
+ model: string | null;
6988
+ effort: string | null;
6989
+ context: string | null;
6990
+ agent: string | null;
6991
+ disableModelInvocation: number;
6992
+ userInvocable: number;
6993
+ version: number;
6994
+ isEnabled: number;
6995
+ createdAt: string;
6996
+ lastUpdatedAt: string;
6997
+ lastFetchedAt: string | null;
6998
+ };
6999
+
7000
+ function rowToSkill(row: SkillRow): Skill {
7001
+ return {
7002
+ id: row.id,
7003
+ name: row.name,
7004
+ description: row.description,
7005
+ content: row.content,
7006
+ type: row.type as SkillType,
7007
+ scope: row.scope as SkillScope,
7008
+ ownerAgentId: row.ownerAgentId,
7009
+ sourceUrl: row.sourceUrl,
7010
+ sourceRepo: row.sourceRepo,
7011
+ sourcePath: row.sourcePath,
7012
+ sourceBranch: row.sourceBranch,
7013
+ sourceHash: row.sourceHash,
7014
+ isComplex: row.isComplex === 1,
7015
+ allowedTools: row.allowedTools,
7016
+ model: row.model,
7017
+ effort: row.effort,
7018
+ context: row.context,
7019
+ agent: row.agent,
7020
+ disableModelInvocation: row.disableModelInvocation === 1,
7021
+ userInvocable: row.userInvocable === 1,
7022
+ version: row.version,
7023
+ isEnabled: row.isEnabled === 1,
7024
+ createdAt: row.createdAt,
7025
+ lastUpdatedAt: row.lastUpdatedAt,
7026
+ lastFetchedAt: row.lastFetchedAt,
7027
+ };
7028
+ }
7029
+
7030
+ type AgentSkillRow = {
7031
+ id: string;
7032
+ agentId: string;
7033
+ skillId: string;
7034
+ isActive: number;
7035
+ installedAt: string;
7036
+ };
7037
+
7038
+ function rowToAgentSkill(row: AgentSkillRow): AgentSkill {
7039
+ return {
7040
+ id: row.id,
7041
+ agentId: row.agentId,
7042
+ skillId: row.skillId,
7043
+ isActive: row.isActive === 1,
7044
+ installedAt: row.installedAt,
7045
+ };
7046
+ }
7047
+
7048
+ type SkillWithInstallRow = SkillRow & { isActive: number; installedAt: string };
7049
+
7050
+ function rowToSkillWithInstall(row: SkillWithInstallRow): SkillWithInstallInfo {
7051
+ return {
7052
+ ...rowToSkill(row),
7053
+ isActive: row.isActive === 1,
7054
+ installedAt: row.installedAt,
7055
+ };
7056
+ }
7057
+
7058
+ export interface SkillInsert {
7059
+ name: string;
7060
+ description: string;
7061
+ content: string;
7062
+ type?: SkillType;
7063
+ scope?: SkillScope;
7064
+ ownerAgentId?: string;
7065
+ sourceUrl?: string;
7066
+ sourceRepo?: string;
7067
+ sourcePath?: string;
7068
+ sourceBranch?: string;
7069
+ sourceHash?: string;
7070
+ isComplex?: boolean;
7071
+ allowedTools?: string;
7072
+ model?: string;
7073
+ effort?: string;
7074
+ context?: string;
7075
+ agent?: string;
7076
+ disableModelInvocation?: boolean;
7077
+ userInvocable?: boolean;
7078
+ }
7079
+
7080
+ export function createSkill(data: SkillInsert): Skill {
7081
+ const id = crypto.randomUUID();
7082
+ const now = new Date().toISOString();
7083
+
7084
+ const row = getDb()
7085
+ .prepare<SkillRow, (string | number | null)[]>(
7086
+ `INSERT INTO skills (
7087
+ id, name, description, content, type, scope, ownerAgentId,
7088
+ sourceUrl, sourceRepo, sourcePath, sourceBranch, sourceHash, isComplex,
7089
+ allowedTools, model, effort, context, agent, disableModelInvocation, userInvocable,
7090
+ version, isEnabled, createdAt, lastUpdatedAt
7091
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?) RETURNING *`,
7092
+ )
7093
+ .get(
7094
+ id,
7095
+ data.name,
7096
+ data.description,
7097
+ data.content,
7098
+ data.type ?? "personal",
7099
+ data.scope ?? "agent",
7100
+ data.ownerAgentId ?? null,
7101
+ data.sourceUrl ?? null,
7102
+ data.sourceRepo ?? null,
7103
+ data.sourcePath ?? null,
7104
+ data.sourceBranch ?? "main",
7105
+ data.sourceHash ?? null,
7106
+ data.isComplex ? 1 : 0,
7107
+ data.allowedTools ?? null,
7108
+ data.model ?? null,
7109
+ data.effort ?? null,
7110
+ data.context ?? null,
7111
+ data.agent ?? null,
7112
+ data.disableModelInvocation ? 1 : 0,
7113
+ data.userInvocable === false ? 0 : 1,
7114
+ now,
7115
+ now,
7116
+ );
7117
+
7118
+ if (!row) throw new Error("Failed to create skill");
7119
+ return rowToSkill(row);
7120
+ }
7121
+
7122
+ export function updateSkill(
7123
+ id: string,
7124
+ updates: Partial<SkillInsert> & { isEnabled?: boolean; lastFetchedAt?: string },
7125
+ ): Skill | null {
7126
+ const existing = getSkillById(id);
7127
+ if (!existing) return null;
7128
+
7129
+ const now = new Date().toISOString();
7130
+ const sets: string[] = ["lastUpdatedAt = ?"];
7131
+ const params: (string | number | null)[] = [now];
7132
+
7133
+ if (updates.name !== undefined) {
7134
+ sets.push("name = ?");
7135
+ params.push(updates.name);
7136
+ }
7137
+ if (updates.description !== undefined) {
7138
+ sets.push("description = ?");
7139
+ params.push(updates.description);
7140
+ }
7141
+ if (updates.content !== undefined) {
7142
+ sets.push("content = ?");
7143
+ params.push(updates.content);
7144
+ }
7145
+ if (updates.scope !== undefined) {
7146
+ sets.push("scope = ?");
7147
+ params.push(updates.scope);
7148
+ }
7149
+ if (updates.isEnabled !== undefined) {
7150
+ sets.push("isEnabled = ?");
7151
+ params.push(updates.isEnabled ? 1 : 0);
7152
+ }
7153
+ if (updates.allowedTools !== undefined) {
7154
+ sets.push("allowedTools = ?");
7155
+ params.push(updates.allowedTools ?? null);
7156
+ }
7157
+ if (updates.model !== undefined) {
7158
+ sets.push("model = ?");
7159
+ params.push(updates.model ?? null);
7160
+ }
7161
+ if (updates.effort !== undefined) {
7162
+ sets.push("effort = ?");
7163
+ params.push(updates.effort ?? null);
7164
+ }
7165
+ if (updates.context !== undefined) {
7166
+ sets.push("context = ?");
7167
+ params.push(updates.context ?? null);
7168
+ }
7169
+ if (updates.agent !== undefined) {
7170
+ sets.push("agent = ?");
7171
+ params.push(updates.agent ?? null);
7172
+ }
7173
+ if (updates.disableModelInvocation !== undefined) {
7174
+ sets.push("disableModelInvocation = ?");
7175
+ params.push(updates.disableModelInvocation ? 1 : 0);
7176
+ }
7177
+ if (updates.userInvocable !== undefined) {
7178
+ sets.push("userInvocable = ?");
7179
+ params.push(updates.userInvocable ? 1 : 0);
7180
+ }
7181
+ if (updates.sourceUrl !== undefined) {
7182
+ sets.push("sourceUrl = ?");
7183
+ params.push(updates.sourceUrl ?? null);
7184
+ }
7185
+ if (updates.sourceRepo !== undefined) {
7186
+ sets.push("sourceRepo = ?");
7187
+ params.push(updates.sourceRepo ?? null);
7188
+ }
7189
+ if (updates.sourcePath !== undefined) {
7190
+ sets.push("sourcePath = ?");
7191
+ params.push(updates.sourcePath ?? null);
7192
+ }
7193
+ if (updates.sourceBranch !== undefined) {
7194
+ sets.push("sourceBranch = ?");
7195
+ params.push(updates.sourceBranch ?? "main");
7196
+ }
7197
+ if (updates.sourceHash !== undefined) {
7198
+ sets.push("sourceHash = ?");
7199
+ params.push(updates.sourceHash ?? null);
7200
+ }
7201
+ if (updates.isComplex !== undefined) {
7202
+ sets.push("isComplex = ?");
7203
+ params.push(updates.isComplex ? 1 : 0);
7204
+ }
7205
+ if (updates.lastFetchedAt !== undefined) {
7206
+ sets.push("lastFetchedAt = ?");
7207
+ params.push(updates.lastFetchedAt);
7208
+ }
7209
+
7210
+ // Bump version when content changes
7211
+ if (updates.content !== undefined) {
7212
+ sets.push("version = version + 1");
7213
+ }
7214
+
7215
+ params.push(id);
7216
+ const row = getDb()
7217
+ .prepare<SkillRow, (string | number | null)[]>(
7218
+ `UPDATE skills SET ${sets.join(", ")} WHERE id = ? RETURNING *`,
7219
+ )
7220
+ .get(...params);
7221
+
7222
+ return row ? rowToSkill(row) : null;
7223
+ }
7224
+
7225
+ export function deleteSkill(id: string): boolean {
7226
+ const result = getDb().prepare("DELETE FROM skills WHERE id = ?").run(id);
7227
+ return result.changes > 0;
7228
+ }
7229
+
7230
+ export function getSkillById(id: string): Skill | null {
7231
+ const row = getDb().prepare<SkillRow, [string]>("SELECT * FROM skills WHERE id = ?").get(id);
7232
+ return row ? rowToSkill(row) : null;
7233
+ }
7234
+
7235
+ export function getSkillByName(
7236
+ name: string,
7237
+ scope: SkillScope,
7238
+ ownerAgentId?: string,
7239
+ ): Skill | null {
7240
+ const row = getDb()
7241
+ .prepare<SkillRow, [string, string, string]>(
7242
+ "SELECT * FROM skills WHERE name = ? AND scope = ? AND COALESCE(ownerAgentId, '') = ?",
7243
+ )
7244
+ .get(name, scope, ownerAgentId ?? "");
7245
+ return row ? rowToSkill(row) : null;
7246
+ }
7247
+
7248
+ export interface SkillFilters {
7249
+ type?: SkillType;
7250
+ scope?: SkillScope;
7251
+ ownerAgentId?: string;
7252
+ isEnabled?: boolean;
7253
+ search?: string;
7254
+ limit?: number;
7255
+ includeContent?: boolean;
7256
+ }
7257
+
7258
+ export function listSkills(filters?: SkillFilters): Skill[] {
7259
+ const columns =
7260
+ filters?.includeContent === false
7261
+ ? "id, name, description, type, scope, ownerAgentId, sourceUrl, sourceRepo, sourcePath, sourceBranch, sourceHash, isComplex, allowedTools, model, effort, context, agent, disableModelInvocation, userInvocable, version, isEnabled, createdAt, lastUpdatedAt, lastFetchedAt, '' as content"
7262
+ : "*";
7263
+ let query = `SELECT ${columns} FROM skills WHERE 1=1`;
7264
+ const params: (string | number)[] = [];
7265
+
7266
+ if (filters?.type) {
7267
+ query += " AND type = ?";
7268
+ params.push(filters.type);
7269
+ }
7270
+ if (filters?.scope) {
7271
+ query += " AND scope = ?";
7272
+ params.push(filters.scope);
7273
+ }
7274
+ if (filters?.ownerAgentId) {
7275
+ query += " AND ownerAgentId = ?";
7276
+ params.push(filters.ownerAgentId);
7277
+ }
7278
+ if (filters?.isEnabled !== undefined) {
7279
+ query += " AND isEnabled = ?";
7280
+ params.push(filters.isEnabled ? 1 : 0);
7281
+ }
7282
+ if (filters?.search) {
7283
+ query += " AND (name LIKE ? OR description LIKE ?)";
7284
+ const term = `%${filters.search}%`;
7285
+ params.push(term, term);
7286
+ }
7287
+
7288
+ query += " ORDER BY name ASC";
7289
+
7290
+ if (filters?.limit) {
7291
+ query += " LIMIT ?";
7292
+ params.push(filters.limit);
7293
+ }
7294
+
7295
+ return getDb()
7296
+ .prepare<SkillRow, (string | number)[]>(query)
7297
+ .all(...params)
7298
+ .map(rowToSkill);
7299
+ }
7300
+
7301
+ export function searchSkills(query: string, limit = 20): Skill[] {
7302
+ const term = `%${query}%`;
7303
+ return getDb()
7304
+ .prepare<SkillRow, [string, string, number]>(
7305
+ "SELECT * FROM skills WHERE (name LIKE ? OR description LIKE ?) AND isEnabled = 1 ORDER BY name ASC LIMIT ?",
7306
+ )
7307
+ .all(term, term, limit)
7308
+ .map(rowToSkill);
7309
+ }
7310
+
7311
+ export function installSkill(agentId: string, skillId: string): AgentSkill {
7312
+ const id = crypto.randomUUID();
7313
+ const now = new Date().toISOString();
7314
+
7315
+ const row = getDb()
7316
+ .prepare<AgentSkillRow, [string, string, string, string]>(
7317
+ `INSERT INTO agent_skills (id, agentId, skillId, isActive, installedAt)
7318
+ VALUES (?, ?, ?, 1, ?)
7319
+ ON CONFLICT(agentId, skillId) DO UPDATE SET isActive = 1
7320
+ RETURNING *`,
7321
+ )
7322
+ .get(id, agentId, skillId, now);
7323
+
7324
+ if (!row) throw new Error("Failed to install skill");
7325
+ return rowToAgentSkill(row);
7326
+ }
7327
+
7328
+ export function uninstallSkill(agentId: string, skillId: string): boolean {
7329
+ const result = getDb()
7330
+ .prepare("DELETE FROM agent_skills WHERE agentId = ? AND skillId = ?")
7331
+ .run(agentId, skillId);
7332
+ return result.changes > 0;
7333
+ }
7334
+
7335
+ export function getAgentSkills(agentId: string, activeOnly = true): SkillWithInstallInfo[] {
7336
+ const query = `
7337
+ SELECT s.*, as2.isActive, as2.installedAt
7338
+ FROM skills s
7339
+ JOIN agent_skills as2 ON s.id = as2.skillId
7340
+ WHERE as2.agentId = ?
7341
+ ${activeOnly ? "AND as2.isActive = 1" : ""}
7342
+ AND s.isEnabled = 1
7343
+ ORDER BY
7344
+ CASE WHEN s.type = 'personal' THEN 0 ELSE 1 END,
7345
+ s.name
7346
+ `;
7347
+
7348
+ const rows = getDb().prepare<SkillWithInstallRow, [string]>(query).all(agentId);
7349
+
7350
+ // Deduplicate by name — personal skills take precedence (already sorted first)
7351
+ const seen = new Set<string>();
7352
+ return rows
7353
+ .filter((r) => {
7354
+ if (seen.has(r.name)) return false;
7355
+ seen.add(r.name);
7356
+ return true;
7357
+ })
7358
+ .map(rowToSkillWithInstall);
7359
+ }
7360
+
7361
+ export function toggleAgentSkill(agentId: string, skillId: string, isActive: boolean): boolean {
7362
+ const result = getDb()
7363
+ .prepare("UPDATE agent_skills SET isActive = ? WHERE agentId = ? AND skillId = ?")
7364
+ .run(isActive ? 1 : 0, agentId, skillId);
7365
+ return result.changes > 0;
7366
+ }