@wascartech/devbot 0.0.0-stage → 2.1.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 (376) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +638 -2
  3. package/dist/access-grant-store.js +99 -0
  4. package/dist/access-grant-store.js.map +1 -0
  5. package/dist/access-resolve.js +11 -0
  6. package/dist/access-resolve.js.map +1 -0
  7. package/dist/access-rule-store.js +99 -0
  8. package/dist/access-rule-store.js.map +1 -0
  9. package/dist/admin/access-routes.js +73 -0
  10. package/dist/admin/access-routes.js.map +1 -0
  11. package/dist/admin/access-rule-routes.js +107 -0
  12. package/dist/admin/access-rule-routes.js.map +1 -0
  13. package/dist/admin/custom-repos-routes.js +93 -0
  14. package/dist/admin/custom-repos-routes.js.map +1 -0
  15. package/dist/admin/env-routes.js +74 -0
  16. package/dist/admin/env-routes.js.map +1 -0
  17. package/dist/admin/global-settings-routes.js +254 -0
  18. package/dist/admin/global-settings-routes.js.map +1 -0
  19. package/dist/admin/mcp-cleanup.js +12 -0
  20. package/dist/admin/mcp-cleanup.js.map +1 -0
  21. package/dist/admin/mcp-routes.js +169 -0
  22. package/dist/admin/mcp-routes.js.map +1 -0
  23. package/dist/admin/query-params.js +80 -0
  24. package/dist/admin/query-params.js.map +1 -0
  25. package/dist/admin/routes.js +150 -0
  26. package/dist/admin/routes.js.map +1 -0
  27. package/dist/admin/run-history-routes.js +44 -0
  28. package/dist/admin/run-history-routes.js.map +1 -0
  29. package/dist/admin/run-routes.js +99 -0
  30. package/dist/admin/run-routes.js.map +1 -0
  31. package/dist/admin/settings-routes.js +119 -0
  32. package/dist/admin/settings-routes.js.map +1 -0
  33. package/dist/admin/stats-routes.js +51 -0
  34. package/dist/admin/stats-routes.js.map +1 -0
  35. package/dist/admin/task-routes.js +213 -0
  36. package/dist/admin/task-routes.js.map +1 -0
  37. package/dist/admin/test-routes.js +38 -0
  38. package/dist/admin/test-routes.js.map +1 -0
  39. package/dist/admin/volume-routes.js +82 -0
  40. package/dist/admin/volume-routes.js.map +1 -0
  41. package/dist/admin/webhook-routes.js +174 -0
  42. package/dist/admin/webhook-routes.js.map +1 -0
  43. package/dist/agents.js +48 -0
  44. package/dist/agents.js.map +1 -0
  45. package/dist/assignee-value.js +6 -0
  46. package/dist/assignee-value.js.map +1 -0
  47. package/dist/attachments.js +76 -0
  48. package/dist/attachments.js.map +1 -0
  49. package/dist/auto-commands.js +88 -0
  50. package/dist/auto-commands.js.map +1 -0
  51. package/dist/auto-features.js +32 -0
  52. package/dist/auto-features.js.map +1 -0
  53. package/dist/base-branch-store.js +40 -0
  54. package/dist/base-branch-store.js.map +1 -0
  55. package/dist/base-branches.js +9 -0
  56. package/dist/base-branches.js.map +1 -0
  57. package/dist/build-workflow.js +116 -0
  58. package/dist/build-workflow.js.map +1 -0
  59. package/dist/change-request-lifecycle.js +16 -0
  60. package/dist/change-request-lifecycle.js.map +1 -0
  61. package/dist/change-request-store.js +72 -0
  62. package/dist/change-request-store.js.map +1 -0
  63. package/dist/ci-failure.js +80 -0
  64. package/dist/ci-failure.js.map +1 -0
  65. package/dist/cli.js +3 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/config.js +17 -0
  68. package/dist/config.js.map +1 -0
  69. package/dist/conflict-fix.js +178 -0
  70. package/dist/conflict-fix.js.map +1 -0
  71. package/dist/cookie.js +70 -0
  72. package/dist/cookie.js.map +1 -0
  73. package/dist/crypto-util.js +40 -0
  74. package/dist/crypto-util.js.map +1 -0
  75. package/dist/custom-repos-store.js +90 -0
  76. package/dist/custom-repos-store.js.map +1 -0
  77. package/dist/custom-repos-sync.js +118 -0
  78. package/dist/custom-repos-sync.js.map +1 -0
  79. package/dist/db.js +12 -0
  80. package/dist/db.js.map +1 -0
  81. package/dist/devbot-scripts.js +18 -0
  82. package/dist/devbot-scripts.js.map +1 -0
  83. package/dist/devcontainer.js +141 -0
  84. package/dist/devcontainer.js.map +1 -0
  85. package/dist/env-config-store.js +132 -0
  86. package/dist/env-config-store.js.map +1 -0
  87. package/dist/error-log-store.js +193 -0
  88. package/dist/error-log-store.js.map +1 -0
  89. package/dist/git.js +605 -0
  90. package/dist/git.js.map +1 -0
  91. package/dist/global-queue.js +127 -0
  92. package/dist/global-queue.js.map +1 -0
  93. package/dist/global-settings-store.js +307 -0
  94. package/dist/global-settings-store.js.map +1 -0
  95. package/dist/help.js +41 -0
  96. package/dist/help.js.map +1 -0
  97. package/dist/http-csp.js +15 -0
  98. package/dist/http-csp.js.map +1 -0
  99. package/dist/http-util.js +40 -0
  100. package/dist/http-util.js.map +1 -0
  101. package/dist/idempotency.js +50 -0
  102. package/dist/idempotency.js.map +1 -0
  103. package/dist/lifecycle.js +234 -0
  104. package/dist/lifecycle.js.map +1 -0
  105. package/dist/logger.js +33 -0
  106. package/dist/logger.js.map +1 -0
  107. package/dist/lsp-servers.js +81 -0
  108. package/dist/lsp-servers.js.map +1 -0
  109. package/dist/mcp/auth.js +62 -0
  110. package/dist/mcp/auth.js.map +1 -0
  111. package/dist/mcp/effective-mcp.js +42 -0
  112. package/dist/mcp/effective-mcp.js.map +1 -0
  113. package/dist/mcp/oauth-refresh.js +138 -0
  114. package/dist/mcp/oauth-refresh.js.map +1 -0
  115. package/dist/mcp/peer-sync.js +109 -0
  116. package/dist/mcp/peer-sync.js.map +1 -0
  117. package/dist/mcp/registry.js +54 -0
  118. package/dist/mcp/registry.js.map +1 -0
  119. package/dist/mcp/runner-bridge.js +93 -0
  120. package/dist/mcp/runner-bridge.js.map +1 -0
  121. package/dist/mcp/server.js +173 -0
  122. package/dist/mcp/server.js.map +1 -0
  123. package/dist/mcp/tools/_helpers.js +88 -0
  124. package/dist/mcp/tools/_helpers.js.map +1 -0
  125. package/dist/mcp/tools/attachments.js +117 -0
  126. package/dist/mcp/tools/attachments.js.map +1 -0
  127. package/dist/mcp/tools/cicd.js +105 -0
  128. package/dist/mcp/tools/cicd.js.map +1 -0
  129. package/dist/mcp/tools/discovery.js +34 -0
  130. package/dist/mcp/tools/discovery.js.map +1 -0
  131. package/dist/mcp/tools/issues.js +56 -0
  132. package/dist/mcp/tools/issues.js.map +1 -0
  133. package/dist/mcp/tools/merge_requests.js +67 -0
  134. package/dist/mcp/tools/merge_requests.js.map +1 -0
  135. package/dist/mcp/tools/metadata.js +127 -0
  136. package/dist/mcp/tools/metadata.js.map +1 -0
  137. package/dist/mcp/tools/peer_sync.js +43 -0
  138. package/dist/mcp/tools/peer_sync.js.map +1 -0
  139. package/dist/mcp/tools/repository.js +178 -0
  140. package/dist/mcp/tools/repository.js.map +1 -0
  141. package/dist/mcp/tools/work_tracking.js +125 -0
  142. package/dist/mcp/tools/work_tracking.js.map +1 -0
  143. package/dist/mcp/tools/workitems.js +30 -0
  144. package/dist/mcp/tools/workitems.js.map +1 -0
  145. package/dist/mcp/volume-files.js +187 -0
  146. package/dist/mcp/volume-files.js.map +1 -0
  147. package/dist/mcp-auth-store.js +134 -0
  148. package/dist/mcp-auth-store.js.map +1 -0
  149. package/dist/mcp-config-store.js +83 -0
  150. package/dist/mcp-config-store.js.map +1 -0
  151. package/dist/mention.js +512 -0
  152. package/dist/mention.js.map +1 -0
  153. package/dist/migrate.js +73 -0
  154. package/dist/migrate.js.map +1 -0
  155. package/dist/mr-assignee.js +71 -0
  156. package/dist/mr-assignee.js.map +1 -0
  157. package/dist/mr-content.js +90 -0
  158. package/dist/mr-content.js.map +1 -0
  159. package/dist/oauth-routes.js +54 -0
  160. package/dist/oauth-routes.js.map +1 -0
  161. package/dist/onboarding-allowlist.js +32 -0
  162. package/dist/onboarding-allowlist.js.map +1 -0
  163. package/dist/opencode.js +291 -0
  164. package/dist/opencode.js.map +1 -0
  165. package/dist/outbound-webhook-store.js +229 -0
  166. package/dist/outbound-webhook-store.js.map +1 -0
  167. package/dist/outbound-webhook.js +314 -0
  168. package/dist/outbound-webhook.js.map +1 -0
  169. package/dist/path-sandbox.js +200 -0
  170. package/dist/path-sandbox.js.map +1 -0
  171. package/dist/platform/git-auth.js +34 -0
  172. package/dist/platform/git-auth.js.map +1 -0
  173. package/dist/platform/gitealike/attachments.js +62 -0
  174. package/dist/platform/gitealike/attachments.js.map +1 -0
  175. package/dist/platform/gitealike/base.js +935 -0
  176. package/dist/platform/gitealike/base.js.map +1 -0
  177. package/dist/platform/gitealike/forgejo.js +16 -0
  178. package/dist/platform/gitealike/forgejo.js.map +1 -0
  179. package/dist/platform/gitealike/gitea.js +16 -0
  180. package/dist/platform/gitealike/gitea.js.map +1 -0
  181. package/dist/platform/gitealike/http.js +152 -0
  182. package/dist/platform/gitealike/http.js.map +1 -0
  183. package/dist/platform/gitealike/schemas.js +284 -0
  184. package/dist/platform/gitealike/schemas.js.map +1 -0
  185. package/dist/platform/github/attachments.js +52 -0
  186. package/dist/platform/github/attachments.js.map +1 -0
  187. package/dist/platform/github/http.js +153 -0
  188. package/dist/platform/github/http.js.map +1 -0
  189. package/dist/platform/github/index.js +936 -0
  190. package/dist/platform/github/index.js.map +1 -0
  191. package/dist/platform/github/schemas.js +308 -0
  192. package/dist/platform/github/schemas.js.map +1 -0
  193. package/dist/platform/github/zip.js +85 -0
  194. package/dist/platform/github/zip.js.map +1 -0
  195. package/dist/platform/gitlab/admin.js +105 -0
  196. package/dist/platform/gitlab/admin.js.map +1 -0
  197. package/dist/platform/gitlab/api.js +229 -0
  198. package/dist/platform/gitlab/api.js.map +1 -0
  199. package/dist/platform/gitlab/attachments.js +79 -0
  200. package/dist/platform/gitlab/attachments.js.map +1 -0
  201. package/dist/platform/gitlab/http.js +165 -0
  202. package/dist/platform/gitlab/http.js.map +1 -0
  203. package/dist/platform/gitlab/index.js +630 -0
  204. package/dist/platform/gitlab/index.js.map +1 -0
  205. package/dist/platform/gitlab/oauth.js +58 -0
  206. package/dist/platform/gitlab/oauth.js.map +1 -0
  207. package/dist/platform/gitlab/read.js +182 -0
  208. package/dist/platform/gitlab/read.js.map +1 -0
  209. package/dist/platform/gitlab/schemas-read.js +274 -0
  210. package/dist/platform/gitlab/schemas-read.js.map +1 -0
  211. package/dist/platform/gitlab/schemas.js +251 -0
  212. package/dist/platform/gitlab/schemas.js.map +1 -0
  213. package/dist/platform/gitlab/signature.js +52 -0
  214. package/dist/platform/gitlab/signature.js.map +1 -0
  215. package/dist/platform/index.js +38 -0
  216. package/dist/platform/index.js.map +1 -0
  217. package/dist/platform/port.js +26 -0
  218. package/dist/platform/port.js.map +1 -0
  219. package/dist/platform/types.js +80 -0
  220. package/dist/platform/types.js.map +1 -0
  221. package/dist/podman.js +119 -0
  222. package/dist/podman.js.map +1 -0
  223. package/dist/project-sync.js +69 -0
  224. package/dist/project-sync.js.map +1 -0
  225. package/dist/prompt.js +47 -0
  226. package/dist/prompt.js.map +1 -0
  227. package/dist/queue-store.js +236 -0
  228. package/dist/queue-store.js.map +1 -0
  229. package/dist/queue.js +129 -0
  230. package/dist/queue.js.map +1 -0
  231. package/dist/redact.js +23 -0
  232. package/dist/redact.js.map +1 -0
  233. package/dist/run-files.js +297 -0
  234. package/dist/run-files.js.map +1 -0
  235. package/dist/run-filters.js +54 -0
  236. package/dist/run-filters.js.map +1 -0
  237. package/dist/run-history-store.js +229 -0
  238. package/dist/run-history-store.js.map +1 -0
  239. package/dist/run-log-sink.js +270 -0
  240. package/dist/run-log-sink.js.map +1 -0
  241. package/dist/run-log.js +97 -0
  242. package/dist/run-log.js.map +1 -0
  243. package/dist/run-usage.js +29 -0
  244. package/dist/run-usage.js.map +1 -0
  245. package/dist/run-view.js +112 -0
  246. package/dist/run-view.js.map +1 -0
  247. package/dist/runner.js +1094 -0
  248. package/dist/runner.js.map +1 -0
  249. package/dist/runtime-apply.js +26 -0
  250. package/dist/runtime-apply.js.map +1 -0
  251. package/dist/runtime-settings.js +71 -0
  252. package/dist/runtime-settings.js.map +1 -0
  253. package/dist/runtime-state.js +19 -0
  254. package/dist/runtime-state.js.map +1 -0
  255. package/dist/server.js +119 -0
  256. package/dist/server.js.map +1 -0
  257. package/dist/settings-store.js +320 -0
  258. package/dist/settings-store.js.map +1 -0
  259. package/dist/status.js +121 -0
  260. package/dist/status.js.map +1 -0
  261. package/dist/stop.js +56 -0
  262. package/dist/stop.js.map +1 -0
  263. package/dist/sweeps.js +87 -0
  264. package/dist/sweeps.js.map +1 -0
  265. package/dist/task-log.js +279 -0
  266. package/dist/task-log.js.map +1 -0
  267. package/dist/task-view.js +62 -0
  268. package/dist/task-view.js.map +1 -0
  269. package/dist/token-store.js +68 -0
  270. package/dist/token-store.js.map +1 -0
  271. package/dist/types/access.js +74 -0
  272. package/dist/types/access.js.map +1 -0
  273. package/dist/types/custom-repos.js +28 -0
  274. package/dist/types/custom-repos.js.map +1 -0
  275. package/dist/types/env-config.js +25 -0
  276. package/dist/types/env-config.js.map +1 -0
  277. package/dist/types/env.js +190 -0
  278. package/dist/types/env.js.map +1 -0
  279. package/dist/types/global-settings.js +178 -0
  280. package/dist/types/global-settings.js.map +1 -0
  281. package/dist/types/lsp-servers.js +145 -0
  282. package/dist/types/lsp-servers.js.map +1 -0
  283. package/dist/types/mcp-config.js +77 -0
  284. package/dist/types/mcp-config.js.map +1 -0
  285. package/dist/types/opencode.js +59 -0
  286. package/dist/types/opencode.js.map +1 -0
  287. package/dist/types/outbound-webhook.js +56 -0
  288. package/dist/types/outbound-webhook.js.map +1 -0
  289. package/dist/types/project-settings.js +114 -0
  290. package/dist/types/project-settings.js.map +1 -0
  291. package/dist/types/provider-registry.js +37 -0
  292. package/dist/types/provider-registry.js.map +1 -0
  293. package/dist/types/runtime-settings.js +78 -0
  294. package/dist/types/runtime-settings.js.map +1 -0
  295. package/dist/usage-stats.js +159 -0
  296. package/dist/usage-stats.js.map +1 -0
  297. package/dist/version.js +19 -0
  298. package/dist/version.js.map +1 -0
  299. package/dist/volume-gc.js +193 -0
  300. package/dist/volume-gc.js.map +1 -0
  301. package/dist/volume-manage.js +191 -0
  302. package/dist/volume-manage.js.map +1 -0
  303. package/dist/volume-store.js +122 -0
  304. package/dist/volume-store.js.map +1 -0
  305. package/dist/webhook.js +1106 -0
  306. package/dist/webhook.js.map +1 -0
  307. package/dist/work-item-auto-store.js +75 -0
  308. package/dist/work-item-auto-store.js.map +1 -0
  309. package/dist/work-item-branch-store.js +37 -0
  310. package/dist/work-item-branch-store.js.map +1 -0
  311. package/migrations/0001_initial.sql +59 -0
  312. package/migrations/0002_queue_status.sql +15 -0
  313. package/migrations/0003_work_item_base_branch.sql +14 -0
  314. package/migrations/0004_project_settings.sql +15 -0
  315. package/migrations/0005_global_settings.sql +34 -0
  316. package/migrations/0006_model_variants.sql +8 -0
  317. package/migrations/0007_container_error_logs.sql +21 -0
  318. package/migrations/0008_project_access_grants.sql +16 -0
  319. package/migrations/0009_drop_imported_legacy.sql +4 -0
  320. package/migrations/0010_container_error_log_model.sql +6 -0
  321. package/migrations/0011_container_error_log_title.sql +5 -0
  322. package/migrations/0012_pending_task_cancel.sql +6 -0
  323. package/migrations/0013_builder_image.sql +6 -0
  324. package/migrations/0014_build_workflow.sql +7 -0
  325. package/migrations/0015_access_rules.sql +24 -0
  326. package/migrations/0016_trigger_policy.sql +17 -0
  327. package/migrations/0017_attachment_upload.sql +8 -0
  328. package/migrations/0018_work_item_branch.sql +11 -0
  329. package/migrations/0019_auto_push_mr.sql +18 -0
  330. package/migrations/0020_outbound_webhooks.sql +47 -0
  331. package/migrations/0021_runtime_settings.sql +12 -0
  332. package/migrations/0022_auto_resolve_conflicts.sql +17 -0
  333. package/migrations/0023_ci_fail_auto_report.sql +27 -0
  334. package/migrations/0024_work_item_auto_override.sql +22 -0
  335. package/migrations/0025_run_history.sql +37 -0
  336. package/migrations/0026_run_history_task_seq.sql +6 -0
  337. package/migrations/0027_custom_repos.sql +11 -0
  338. package/migrations/0028_run_usage.sql +14 -0
  339. package/migrations/0029_drop_ocr_langs.sql +6 -0
  340. package/migrations/0030_mr_assignee.sql +9 -0
  341. package/migrations/0031_lsp_servers.sql +14 -0
  342. package/opencode-config/managed/devbot-agent-policy.md +61 -0
  343. package/opencode-config/managed/opencode.json +77 -0
  344. package/opencode-config/opencode.json +211 -0
  345. package/opencode-config/plugins/devbot-path-guard.js +224 -0
  346. package/package.json +157 -4
  347. package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
  348. package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
  349. package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
  350. package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
  351. package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
  352. package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
  353. package/public/assets/PageGrid-CBROIU87.js +1 -0
  354. package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
  355. package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
  356. package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
  357. package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
  358. package/public/assets/RunsPage-CRnnXU4P.js +1 -0
  359. package/public/assets/Select-Bvx32Diz.js +1 -0
  360. package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
  361. package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
  362. package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
  363. package/public/assets/UsagePage-BLMDEsv-.js +332 -0
  364. package/public/assets/UsagePage-DodEeAjB.css +1 -0
  365. package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
  366. package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
  367. package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
  368. package/public/assets/index-BMbMibyb.js +4 -0
  369. package/public/assets/index-C5RBBrPj.js +30 -0
  370. package/public/assets/index-CgX29UgC.css +1 -0
  371. package/public/assets/usage-format-jsb31_O8.js +1 -0
  372. package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
  373. package/public/assets/useRouteTab-DfwrcMic.js +1 -0
  374. package/public/favicon.ico +0 -0
  375. package/public/favicon.png +0 -0
  376. package/public/index.html +15 -0
@@ -0,0 +1,229 @@
1
+ import { db } from "./db.js";
2
+ import { log } from "./logger.js";
3
+ import { encrypt, decryptOrPlaintext } from "./crypto-util.js";
4
+ import { randomBytes } from "node:crypto";
5
+ import { WebhookEndpointSchema, } from "./types/outbound-webhook.js";
6
+ // Outbound webhook endpoints + their delivery queue. Same DB file as the other
7
+ // stores (devbot.db, 0600). Endpoints are keyed by (project_id, name); global
8
+ // endpoints reuse the sentinel scope id 0 (GLOBAL_SCOPE_ID), exactly like
9
+ // global MCP servers. The signing secret is encrypted at rest with SESSION_KEY.
10
+ //
11
+ // Deliveries are durable rows: emit() inserts one per matching endpoint, a
12
+ // worker claims due rows (atomic pending→delivering CAS), and settles them.
13
+ // A failed delivery is re-queued with a backoff until retries are exhausted.
14
+ const SEC = () => Math.floor(Date.now() / 1000);
15
+ export function initOutboundWebhookTables() {
16
+ // Belt-and-braces: migration 0020 is authoritative; keep the idempotent
17
+ // CREATE so a test DB opened without migrate() still works.
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS outbound_webhooks (
20
+ project_id INTEGER NOT NULL,
21
+ name TEXT NOT NULL,
22
+ url TEXT NOT NULL,
23
+ events TEXT NOT NULL,
24
+ headers TEXT NOT NULL DEFAULT '{}',
25
+ enabled INTEGER NOT NULL DEFAULT 1,
26
+ secret TEXT NOT NULL,
27
+ created_at INTEGER NOT NULL,
28
+ updated_at INTEGER NOT NULL,
29
+ PRIMARY KEY (project_id, name)
30
+ )
31
+ `);
32
+ db.exec(`
33
+ CREATE TABLE IF NOT EXISTS outbound_webhook_deliveries (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ project_id INTEGER NOT NULL,
36
+ webhook_name TEXT NOT NULL,
37
+ event TEXT NOT NULL,
38
+ payload TEXT NOT NULL,
39
+ status TEXT NOT NULL DEFAULT 'pending',
40
+ attempts INTEGER NOT NULL DEFAULT 0,
41
+ next_attempt_at INTEGER NOT NULL,
42
+ response_status INTEGER,
43
+ response_body TEXT NOT NULL DEFAULT '',
44
+ last_error TEXT NOT NULL DEFAULT '',
45
+ created_at INTEGER NOT NULL,
46
+ updated_at INTEGER NOT NULL
47
+ )
48
+ `);
49
+ db.exec("CREATE INDEX IF NOT EXISTS idx_outbound_deliveries_due ON outbound_webhook_deliveries(status, next_attempt_at)");
50
+ db.exec("CREATE INDEX IF NOT EXISTS idx_outbound_deliveries_webhook ON outbound_webhook_deliveries(project_id, webhook_name)");
51
+ }
52
+ export function newWebhookSecret() {
53
+ return `whsec_${randomBytes(32).toString("base64url")}`;
54
+ }
55
+ function parseEvents(raw) {
56
+ try {
57
+ const parsed = JSON.parse(raw);
58
+ return Array.isArray(parsed) ? parsed : [];
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ }
64
+ function parseHeaders(raw) {
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
68
+ ? parsed
69
+ : {};
70
+ }
71
+ catch {
72
+ return {};
73
+ }
74
+ }
75
+ function toStored(r) {
76
+ return {
77
+ projectId: r.project_id,
78
+ name: r.name,
79
+ url: r.url,
80
+ events: parseEvents(r.events),
81
+ headers: parseHeaders(r.headers),
82
+ enabled: r.enabled === 1,
83
+ secret: decryptOrPlaintext(r.secret),
84
+ createdAt: r.created_at,
85
+ updatedAt: r.updated_at,
86
+ };
87
+ }
88
+ export function upsertWebhook(projectId, name, endpoint, secret) {
89
+ // Re-validate at the store boundary — the API layer validates too, but this
90
+ // is the trust boundary for the delivery worker.
91
+ const v = WebhookEndpointSchema.safeParse(endpoint);
92
+ if (!v.success)
93
+ throw new Error("invalid webhook endpoint");
94
+ const now = SEC();
95
+ db.prepare(`INSERT OR REPLACE INTO outbound_webhooks
96
+ (project_id, name, url, events, headers, enabled, secret, created_at, updated_at)
97
+ VALUES (?, ?, ?, ?, ?, ?, ?,
98
+ COALESCE((SELECT created_at FROM outbound_webhooks WHERE project_id = ? AND name = ?), ?),
99
+ ?)`).run(projectId, name, v.data.url, JSON.stringify(v.data.events), JSON.stringify(v.data.headers), v.data.enabled ? 1 : 0, encrypt(secret), projectId, name, now, now);
100
+ }
101
+ export function getWebhook(projectId, name) {
102
+ const row = db
103
+ .prepare("SELECT * FROM outbound_webhooks WHERE project_id = ? AND name = ?")
104
+ .get(projectId, name);
105
+ return row ? toStored(row) : null;
106
+ }
107
+ export function listWebhooks(projectId) {
108
+ const rows = db
109
+ .prepare("SELECT * FROM outbound_webhooks WHERE project_id = ? ORDER BY name")
110
+ .all(projectId);
111
+ return rows.map(toStored);
112
+ }
113
+ export function deleteWebhook(projectId, name) {
114
+ const r = db
115
+ .prepare("DELETE FROM outbound_webhooks WHERE project_id = ? AND name = ?")
116
+ .run(projectId, name);
117
+ return r.changes > 0;
118
+ }
119
+ export function deleteAllOutboundWebhooksForProject(projectId) {
120
+ db.prepare("DELETE FROM outbound_webhooks WHERE project_id = ?").run(projectId);
121
+ db.prepare("DELETE FROM outbound_webhook_deliveries WHERE project_id = ?").run(projectId);
122
+ }
123
+ // Enabled endpoints in this project's scope (project + global) subscribed to
124
+ // `event`. Global endpoints use scope id 0, which is never a real project id.
125
+ export function matchingWebhooks(projectId, event) {
126
+ const rows = db
127
+ .prepare("SELECT * FROM outbound_webhooks WHERE enabled = 1 AND project_id IN (?, 0) ORDER BY project_id, name")
128
+ .all(projectId);
129
+ return rows.map(toStored).filter((w) => w.events.includes(event));
130
+ }
131
+ // ---- Delivery queue --------------------------------------------------------
132
+ export function enqueueDelivery(projectId, webhookName, event, payload) {
133
+ const now = SEC();
134
+ const r = db
135
+ .prepare(`INSERT INTO outbound_webhook_deliveries
136
+ (project_id, webhook_name, event, payload, status, attempts, next_attempt_at, created_at, updated_at)
137
+ VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?)`)
138
+ .run(projectId, webhookName, event, payload, Date.now(), now, now);
139
+ return Number(r.lastInsertRowid);
140
+ }
141
+ function toDeliverySummary(r) {
142
+ return {
143
+ id: r.id,
144
+ projectId: r.project_id,
145
+ webhookName: r.webhook_name,
146
+ event: r.event,
147
+ status: r.status,
148
+ attempts: r.attempts,
149
+ nextAttemptAt: r.next_attempt_at,
150
+ responseStatus: r.response_status,
151
+ lastError: r.last_error,
152
+ createdAt: r.created_at,
153
+ updatedAt: r.updated_at,
154
+ };
155
+ }
156
+ function toDeliveryRow(r) {
157
+ return { ...toDeliverySummary(r), payload: r.payload, responseBody: r.response_body };
158
+ }
159
+ // Claim due pending rows (atomic pending→delivering CAS). Returns the claimed
160
+ // rows so the worker can deliver them. `nowMs` is explicit for testability.
161
+ export function claimDueDeliveries(limit, nowMs) {
162
+ const rows = db
163
+ .prepare(`SELECT * FROM outbound_webhook_deliveries
164
+ WHERE status = 'pending' AND next_attempt_at <= ?
165
+ ORDER BY id ASC LIMIT ?`)
166
+ .all(nowMs, limit);
167
+ const claimed = [];
168
+ const upd = db.prepare("UPDATE outbound_webhook_deliveries SET status = 'delivering', updated_at = ? WHERE id = ? AND status = 'pending'");
169
+ for (const r of rows) {
170
+ if (upd.run(SEC(), r.id).changes === 0)
171
+ continue; // CAS lost
172
+ claimed.push(toDeliveryRow({ ...r, status: "delivering" }));
173
+ }
174
+ return claimed;
175
+ }
176
+ export function settleDeliverySucceeded(id, responseStatus, responseBody) {
177
+ db.prepare(`UPDATE outbound_webhook_deliveries
178
+ SET status = 'succeeded', attempts = attempts + 1, response_status = ?, response_body = ?,
179
+ last_error = '', updated_at = ?
180
+ WHERE id = ?`).run(responseStatus, responseBody.slice(0, 2000), SEC(), id);
181
+ }
182
+ // Settle a failed attempt. `retryAt` non-null re-queues as pending for that
183
+ // time; null marks the delivery terminally failed (retries exhausted).
184
+ export function settleDeliveryFailed(id, input) {
185
+ db.prepare(`UPDATE outbound_webhook_deliveries
186
+ SET status = ?, attempts = attempts + 1, response_status = ?, response_body = ?,
187
+ last_error = ?, next_attempt_at = ?, updated_at = ?
188
+ WHERE id = ?`).run(input.retryAt === null ? "failed" : "pending", input.responseStatus, input.responseBody.slice(0, 2000), input.error.slice(0, 2000), input.retryAt ?? Date.now(), SEC(), id);
189
+ }
190
+ // Crash recovery: a delivery claimed but never settled goes back to pending.
191
+ export function resetStuckDeliveries() {
192
+ const r = db
193
+ .prepare("UPDATE outbound_webhook_deliveries SET status = 'pending', updated_at = ? WHERE status = 'delivering'")
194
+ .run(SEC());
195
+ return Number(r.changes);
196
+ }
197
+ export function listDeliveries(projectId, webhookName, limit, offset) {
198
+ const rows = db
199
+ .prepare(`SELECT * FROM outbound_webhook_deliveries
200
+ WHERE project_id = ? AND webhook_name = ?
201
+ ORDER BY id DESC LIMIT ? OFFSET ?`)
202
+ .all(projectId, webhookName, limit, offset);
203
+ return rows.map(toDeliverySummary);
204
+ }
205
+ export function countDeliveries(projectId, webhookName) {
206
+ const row = db
207
+ .prepare("SELECT COUNT(*) AS c FROM outbound_webhook_deliveries WHERE project_id = ? AND webhook_name = ?")
208
+ .get(projectId, webhookName);
209
+ return row.c;
210
+ }
211
+ export function getDelivery(id) {
212
+ const row = db.prepare("SELECT * FROM outbound_webhook_deliveries WHERE id = ?").get(id);
213
+ return row ? toDeliveryRow(row) : null;
214
+ }
215
+ // Re-queue a terminal delivery for another attempt (manual replay from the UI).
216
+ export function replayDelivery(id) {
217
+ const r = db
218
+ .prepare("UPDATE outbound_webhook_deliveries SET status = 'pending', next_attempt_at = ?, updated_at = ? WHERE id = ?")
219
+ .run(Date.now(), SEC(), id);
220
+ return r.changes > 0;
221
+ }
222
+ export function purgeDeliveriesOlderThan(days) {
223
+ const cutoff = SEC() - days * 24 * 3600;
224
+ const r = db.prepare("DELETE FROM outbound_webhook_deliveries WHERE created_at < ?").run(cutoff);
225
+ if (r.changes > 0)
226
+ log.debug("outbound delivery purge", { purged: r.changes, days });
227
+ return Number(r.changes);
228
+ }
229
+ //# sourceMappingURL=outbound-webhook-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-webhook-store.js","sourceRoot":"","sources":["../src/outbound-webhook-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,qBAAqB,GAKtB,MAAM,6BAA6B,CAAC;AAErC,+EAA+E;AAC/E,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E;AAE7E,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAEhD,MAAM,UAAU,yBAAyB;IACvC,wEAAwE;IACxE,4DAA4D;IAC5D,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;GAaP,CAAC,CAAC;IACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;GAgBP,CAAC,CAAC;IACH,EAAE,CAAC,IAAI,CAAC,gHAAgH,CAAC,CAAC;IAC1H,EAAE,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAC;AACjI,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAC1D,CAAC;AA0BD,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACnE,CAAC,CAAE,MAAiC;YACpC,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,CAAe;IAC/B,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;QAC7B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;QAChC,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,CAAC;QACxB,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC;QACpC,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,SAAS,EAAE,CAAC,CAAC,UAAU;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,IAAY,EAAE,QAAyB,EAAE,MAAc;IACtG,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,CAAC,GAAG,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC;IAClB,EAAE,CAAC,OAAO,CACR;;;;UAIM,CACP,CAAC,GAAG,CACH,SAAS,EACT,IAAI,EACJ,CAAC,CAAC,IAAI,CAAC,GAAG,EACV,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAC7B,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAC9B,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACtB,OAAO,CAAC,MAAM,CAAC,EACf,SAAS,EACT,IAAI,EACJ,GAAG,EACH,GAAG,CACJ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,IAAY;IACxD,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,mEAAmE,CAAC;SAC5E,GAAG,CAAC,SAAS,EAAE,IAAI,CAA6B,CAAC;IACpD,OAAO,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,oEAAoE,CAAC;SAC7E,GAAG,CAAC,SAAS,CAA8B,CAAC;IAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,IAAY;IAC3D,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CAAC,iEAAiE,CAAC;SAC1E,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,mCAAmC,CAAC,SAAiB;IACnE,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAChF,EAAE,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC5F,CAAC;AAED,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,KAAoB;IACtE,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN,sGAAsG,CACvG;SACA,GAAG,CAAC,SAAS,CAA8B,CAAC;IAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,+EAA+E;AAE/E,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,WAAmB,EAAE,KAAa,EAAE,OAAe;IACpG,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC;IAClB,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CACN;;kDAE4C,CAC7C;SACA,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;AACnC,CAAC;AAkBD,SAAS,iBAAiB,CAAC,CAAgB;IACzC,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,WAAW,EAAE,CAAC,CAAC,YAAY;QAC3B,KAAK,EAAE,CAAC,CAAC,KAAsB;QAC/B,MAAM,EAAE,CAAC,CAAC,MAA0C;QACpD,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,aAAa,EAAE,CAAC,CAAC,eAAe;QAChC,cAAc,EAAE,CAAC,CAAC,eAAe;QACjC,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,SAAS,EAAE,CAAC,CAAC,UAAU;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,CAAgB;IACrC,OAAO,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC;AACxF,CAAC;AAED,8EAA8E;AAC9E,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAAa;IAC7D,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;gCAE0B,CAC3B;SACA,GAAG,CAAC,KAAK,EAAE,KAAK,CAA+B,CAAC;IACnD,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CACpB,kHAAkH,CACnH,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS,CAAC,WAAW;QAC7D,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,EAAU,EAAE,cAA6B,EAAE,YAAoB;IACrG,EAAE,CAAC,OAAO,CACR;;;mBAGe,CAChB,CAAC,GAAG,CAAC,cAAc,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,4EAA4E;AAC5E,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAClC,EAAU,EACV,KAAqG;IAErG,EAAE,CAAC,OAAO,CACR;;;mBAGe,CAChB,CAAC,GAAG,CACH,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAC7C,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EACjC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAC1B,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,EAC3B,GAAG,EAAE,EACL,EAAE,CACH,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,oBAAoB;IAClC,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CAAC,uGAAuG,CAAC;SAChH,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IACd,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,WAAmB,EAAE,KAAa,EAAE,MAAc;IAClG,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;0CAEoC,CACrC;SACA,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,CAA+B,CAAC;IAC5E,OAAO,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,WAAmB;IACpE,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,iGAAiG,CAAC;SAC1G,GAAG,CAAC,SAAS,EAAE,WAAW,CAAkB,CAAC;IAChD,OAAO,GAAG,CAAC,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,CAAC,EAAE,CAE1E,CAAC;IACd,OAAO,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CACN,6GAA6G,CAC9G;SACA,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjG,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC;QAAE,GAAG,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,314 @@
1
+ import { createHmac, randomUUID } from "node:crypto";
2
+ import { lookup } from "node:dns/promises";
3
+ import { isIP } from "node:net";
4
+ import { config } from "./config.js";
5
+ import { log } from "./logger.js";
6
+ import { matchingWebhooks, enqueueDelivery, getWebhook, claimDueDeliveries, settleDeliverySucceeded, settleDeliveryFailed, resetStuckDeliveries, purgeDeliveriesOlderThan, } from "./outbound-webhook-store.js";
7
+ import { getProject } from "./token-store.js";
8
+ import { getPlatform } from "./platform/index.js";
9
+ // Outbound webhook emitter + delivery worker. Forge-agnostic by construction:
10
+ // the only platform call is the neutral links.workItemUrl. emit* is a purely
11
+ // local DB write (one row per matching endpoint); the worker does all network
12
+ // I/O, so a webhook can never block or fail a run.
13
+ const SIGNATURE_HEADER = "X-DevBot-Signature-256";
14
+ export function signPayload(secret, body) {
15
+ return "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
16
+ }
17
+ // The delivery id carried in the payload; used for the X-DevBot-Delivery header
18
+ // so a receiver can dedupe on the same value it sees in the body.
19
+ function deliveryIdOf(payload) {
20
+ try {
21
+ const parsed = JSON.parse(payload);
22
+ return typeof parsed.delivery === "string" ? parsed.delivery : "";
23
+ }
24
+ catch {
25
+ return "";
26
+ }
27
+ }
28
+ // ---- SSRF guard ------------------------------------------------------------
29
+ // Outbound URLs are admin-configured, but a URL like http://169.254.169.254/
30
+ // can reach cloud metadata. Block loopback/link-local/private/CGNAT/ULA/
31
+ // multicast/broadcast by default; WEBHOOK_ALLOW_PRIVATE_HOSTS=true opts in to
32
+ // internal CI/messaging endpoints (see docs/outbound-webhooks.md).
33
+ function ipv4Blocked(ip) {
34
+ const parts = ip.split(".").map((n) => Number(n));
35
+ const [a, b] = parts;
36
+ if (a === 0 || a === 10 || a === 127)
37
+ return true;
38
+ if (a === 100 && b >= 64 && b <= 127)
39
+ return true; // CGNAT 100.64/10
40
+ if (a === 169 && b === 254)
41
+ return true; // link-local
42
+ if (a === 172 && b >= 16 && b <= 31)
43
+ return true; // private
44
+ if (a === 192 && b === 168)
45
+ return true; // private
46
+ if (a === 192 && b === 0)
47
+ return true; // 192.0.0.0/24 + 192.0.2.0/24
48
+ if (a >= 224)
49
+ return true; // multicast + reserved + broadcast
50
+ return false;
51
+ }
52
+ function ipv6Blocked(ip) {
53
+ const v = ip.toLowerCase().split("%")[0];
54
+ if (v === "::1" || v === "::")
55
+ return true;
56
+ if (v.startsWith("fe80"))
57
+ return true; // link-local
58
+ const first = v.split(":")[0];
59
+ const n = parseInt(first || "0", 16);
60
+ if ((n & 0xfe00) === 0xfc00)
61
+ return true; // ULA fc00::/7
62
+ if ((n & 0xff00) === 0xff00)
63
+ return true; // multicast ff00::/8
64
+ // IPv4-mapped (::ffff:a.b.c.d) — classify on the embedded v4.
65
+ const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(v);
66
+ if (mapped)
67
+ return ipv4Blocked(mapped[1]);
68
+ return false;
69
+ }
70
+ export function isBlockedAddress(addr) {
71
+ const v = isIP(addr);
72
+ if (v === 4)
73
+ return ipv4Blocked(addr);
74
+ if (v === 6)
75
+ return ipv6Blocked(addr);
76
+ return true; // not an IP literal → treat as blocked/unknown
77
+ }
78
+ // Resolve a URL's host and reject when any resolved address is blocked (unless
79
+ // private hosts are explicitly allowed). Returns an error string, or null when
80
+ // the target is safe to call.
81
+ export async function checkTargetAllowed(rawUrl) {
82
+ let url;
83
+ try {
84
+ url = new URL(rawUrl);
85
+ }
86
+ catch {
87
+ return "invalid url";
88
+ }
89
+ if (url.protocol !== "http:" && url.protocol !== "https:")
90
+ return "url must be http(s)";
91
+ if (config.WEBHOOK_ALLOW_PRIVATE_HOSTS)
92
+ return null;
93
+ const host = url.hostname.replace(/^\[|\]$/g, "");
94
+ if (isIP(host))
95
+ return isBlockedAddress(host) ? "blocked address" : null;
96
+ let addrs;
97
+ try {
98
+ addrs = await lookup(host, { all: true, verbatim: true });
99
+ }
100
+ catch {
101
+ return "dns resolution failed";
102
+ }
103
+ if (addrs.length === 0)
104
+ return "dns resolution failed";
105
+ if (addrs.some((a) => isBlockedAddress(a.address)))
106
+ return "blocked address";
107
+ return null;
108
+ }
109
+ export function buildRunOutcomePayload(event) {
110
+ return JSON.stringify(event);
111
+ }
112
+ export function buildMrPayload(event) {
113
+ return JSON.stringify(event);
114
+ }
115
+ function workItemUrl(projectId, workItemNumber) {
116
+ const projectPath = getProject(projectId)?.projectPath ?? "";
117
+ try {
118
+ return getPlatform().links.workItemUrl(projectPath, workItemNumber);
119
+ }
120
+ catch {
121
+ return "";
122
+ }
123
+ }
124
+ function projectPath(projectId) {
125
+ return getProject(projectId)?.projectPath ?? "";
126
+ }
127
+ // Resolve the concrete endpoints (project + global) an event should be sent to.
128
+ // Kept exported for tests and the "test delivery" route.
129
+ export function resolveMatchingEndpoints(projectId, event) {
130
+ return matchingWebhooks(projectId, event);
131
+ }
132
+ // Persist one delivery row per matching endpoint. Best-effort: a store failure
133
+ // is logged and never propagates into the run.
134
+ export function emitEvent(projectId, event, payloadFor) {
135
+ let count = 0;
136
+ try {
137
+ for (const endpoint of resolveMatchingEndpoints(projectId, event)) {
138
+ const eventPayload = payloadFor();
139
+ eventPayload.delivery = randomUUID();
140
+ enqueueDelivery(projectId, endpoint.name, event, buildRunOutcomePayload(eventPayload));
141
+ count++;
142
+ }
143
+ }
144
+ catch (e) {
145
+ log.warn("outbound webhook emit failed", { projectId, event, err: e });
146
+ }
147
+ return count;
148
+ }
149
+ // A run emits `run.started` once per task lifecycle — only on the first
150
+ // attempt. A transient failure requeues the row with attempts++, so retries
151
+ // don't re-notify subscribers; a crash-replayed row keeps attempts 0 and does.
152
+ export function shouldEmitRunStarted(attempts) {
153
+ return attempts === 0;
154
+ }
155
+ // Emit `run.started` for a task that is about to execute. `run.status` is
156
+ // "running" with no failure detail (none exists yet).
157
+ export function emitRunStarted(input) {
158
+ emitEvent(input.projectId, "run.started", () => baseEvent(input, "run.started", "running", {}));
159
+ }
160
+ export function emitRunOutcome(input, outcome) {
161
+ const event = outcome.kind === "ok" ? "run.succeeded" : outcome.kind === "cancelled" ? "run.cancelled" : "run.failed";
162
+ const failure = outcome.failure;
163
+ emitEvent(input.projectId, event, () => baseEvent(input, event, outcome.kind, {
164
+ reason: outcome.reason ?? failure?.message ?? "",
165
+ phase: failure?.phase ?? "",
166
+ kind: failure?.kind ?? "",
167
+ }));
168
+ }
169
+ export function emitMrEvent(input, changeRequest, created) {
170
+ const event = created ? "mr.created" : "mr.updated";
171
+ emitEvent(input.projectId, event, () => ({
172
+ ...baseEvent(input, event, "ok", {}),
173
+ changeRequest: { number: changeRequest.number, url: changeRequest.webUrl, created },
174
+ }));
175
+ }
176
+ function baseEvent(input, event, status, opts) {
177
+ return {
178
+ event,
179
+ delivery: "",
180
+ timestamp: new Date().toISOString(),
181
+ project: { id: input.projectId, path: projectPath(input.projectId) },
182
+ workItem: {
183
+ id: input.workItemId,
184
+ number: input.workItemNumber,
185
+ url: workItemUrl(input.projectId, input.workItemNumber),
186
+ },
187
+ run: {
188
+ mode: input.mode,
189
+ model: input.model,
190
+ variant: input.variant,
191
+ status,
192
+ phase: opts.phase ?? "",
193
+ kind: opts.kind ?? "",
194
+ reason: opts.reason ?? "",
195
+ },
196
+ };
197
+ }
198
+ // ---- Delivery worker -------------------------------------------------------
199
+ async function deliverOnce(endpoint, event, delivery, payload) {
200
+ const blocked = await checkTargetAllowed(endpoint.url);
201
+ if (blocked)
202
+ return { ok: false, responseStatus: null, responseBody: "", error: blocked };
203
+ const controller = new AbortController();
204
+ const timer = setTimeout(() => controller.abort(), config.WEBHOOK_DELIVERY_TIMEOUT_MS);
205
+ try {
206
+ const res = await fetch(endpoint.url, {
207
+ method: "POST",
208
+ redirect: "manual",
209
+ signal: controller.signal,
210
+ headers: {
211
+ "Content-Type": "application/json",
212
+ "User-Agent": "DevBot-Webhook/1.0",
213
+ "X-DevBot-Event": event,
214
+ "X-DevBot-Delivery": delivery,
215
+ [SIGNATURE_HEADER]: signPayload(endpoint.secret, payload),
216
+ ...endpoint.headers,
217
+ },
218
+ body: payload,
219
+ });
220
+ const body = await res.text().catch(() => "");
221
+ if (res.status >= 200 && res.status < 300) {
222
+ return { ok: true, responseStatus: res.status, responseBody: body, error: "" };
223
+ }
224
+ if (res.status >= 300 && res.status < 400) {
225
+ return { ok: false, responseStatus: res.status, responseBody: body, error: `redirect not followed (${res.status})` };
226
+ }
227
+ return { ok: false, responseStatus: res.status, responseBody: body, error: `http ${res.status}` };
228
+ }
229
+ catch (e) {
230
+ const msg = e instanceof Error && e.name === "AbortError" ? "timeout" : e instanceof Error ? e.message : String(e);
231
+ return { ok: false, responseStatus: null, responseBody: "", error: msg };
232
+ }
233
+ finally {
234
+ clearTimeout(timer);
235
+ }
236
+ }
237
+ // Deliver every due row once. Exported so tests can drive it without timers.
238
+ export async function processDueDeliveries(deps = {}) {
239
+ const now = deps.now ? deps.now() : Date.now();
240
+ const due = claimDueDeliveries(deps.limit ?? 20, now);
241
+ for (const delivery of due) {
242
+ const endpoint = getWebhook(delivery.projectId, delivery.webhookName);
243
+ if (!endpoint) {
244
+ // Endpoint removed after enqueue — nothing to deliver.
245
+ settleDeliveryFailed(delivery.id, {
246
+ responseStatus: null,
247
+ responseBody: "",
248
+ error: "endpoint removed",
249
+ retryAt: null,
250
+ });
251
+ continue;
252
+ }
253
+ const result = await deliverOnce(endpoint, delivery.event, deliveryIdOf(delivery.payload), delivery.payload);
254
+ if (result.ok) {
255
+ settleDeliverySucceeded(delivery.id, result.responseStatus, result.responseBody);
256
+ continue;
257
+ }
258
+ const willRetry = delivery.attempts + 1 < config.WEBHOOK_RETRY_MAX;
259
+ settleDeliveryFailed(delivery.id, {
260
+ responseStatus: result.responseStatus,
261
+ responseBody: result.responseBody,
262
+ error: result.error,
263
+ retryAt: willRetry ? Date.now() + config.WEBHOOK_RETRY_BACKOFF_MS * (delivery.attempts + 1) : null,
264
+ });
265
+ log.warn("outbound webhook delivery failed", {
266
+ projectId: delivery.projectId,
267
+ webhook: delivery.webhookName,
268
+ event: delivery.event,
269
+ status: result.responseStatus,
270
+ willRetry,
271
+ error: result.error,
272
+ });
273
+ }
274
+ return due.length;
275
+ }
276
+ let workerTimer = null;
277
+ export function startWebhookWorker() {
278
+ if (workerTimer)
279
+ return;
280
+ const stuck = resetStuckDeliveries();
281
+ if (stuck > 0)
282
+ log.info("reset stuck outbound deliveries", { count: stuck });
283
+ workerTimer = setInterval(() => {
284
+ void processDueDeliveries().catch((e) => log.warn("outbound webhook worker failed", { err: e }));
285
+ }, config.WEBHOOK_POLL_INTERVAL_MS);
286
+ workerTimer.unref();
287
+ log.info("outbound webhook worker scheduled", {
288
+ intervalMs: config.WEBHOOK_POLL_INTERVAL_MS,
289
+ retryMax: config.WEBHOOK_RETRY_MAX,
290
+ });
291
+ }
292
+ export function stopWebhookWorker() {
293
+ if (workerTimer) {
294
+ clearInterval(workerTimer);
295
+ workerTimer = null;
296
+ }
297
+ }
298
+ // Test helper: deliver one endpoint synchronously (the UI "Test" button).
299
+ export async function testDelivery(endpoint) {
300
+ const deliveryId = randomUUID();
301
+ const payload = buildRunOutcomePayload({
302
+ event: endpoint.events[0] ?? "run.succeeded",
303
+ delivery: deliveryId,
304
+ timestamp: new Date().toISOString(),
305
+ project: { id: endpoint.projectId, path: projectPath(endpoint.projectId) },
306
+ workItem: { id: 0, number: 0, url: "" },
307
+ run: { mode: "test", model: "", variant: "", status: "test", phase: "", kind: "", reason: "test delivery" },
308
+ });
309
+ const result = await deliverOnce(endpoint, endpoint.events[0] ?? "run.succeeded", deliveryId, payload);
310
+ return { ok: result.ok, status: result.responseStatus, error: result.error };
311
+ }
312
+ // Re-export for the delivery-log route.
313
+ export { purgeDeliveriesOlderThan };
314
+ //# sourceMappingURL=outbound-webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-webhook.js","sourceRoot":"","sources":["../src/outbound-webhook.ts"],"names":[],"mappings":"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"}