@wascartech/devbot 0.0.0-stage → 2.1.1

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 +150 -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 +438 -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 +158 -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,213 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { listInFlight, getTask, countInFlight } from "../queue-store.js";
3
+ import { getProject } from "../token-store.js";
4
+ import { buildTaskViews, buildTaskQuota } from "../task-view.js";
5
+ import { globalQueueDepth, globalQueueMax, globalQueuePosition } from "../global-queue.js";
6
+ import { fetchTaskLogs, formatLogBody } from "../task-log.js";
7
+ import { readArchivedLog } from "../run-log.js";
8
+ import { getRunHistoryByTaskSeq } from "../run-history-store.js";
9
+ import { listErrorLogs, getErrorLog, countErrorLogs, clearErrorLogs, listErrorLogFilterOptions } from "../error-log-store.js";
10
+ import { resolveModelForMode, resolveVariantForMode } from "../settings-store.js";
11
+ import { getPlatform } from "../platform/index.js";
12
+ import { stopTask } from "../stop.js";
13
+ import { resolveUsageWindow } from "../usage-stats.js";
14
+ import { parseDateWindow, parseLimit, parseOffset, parsePositiveInt, parseQuery, parseString } from "./query-params.js";
15
+ // Browser link to a project's work item. Resolves the managed project path from
16
+ // the token store and delegates URL shaping to the active driver's Links
17
+ // capability, so both list endpoints agree on the format. Empty when the
18
+ // project is no longer managed.
19
+ function projectWorkItemUrl(projectId, workItemNumber) {
20
+ return getPlatform().links.workItemUrl(getProject(projectId)?.projectPath ?? "", workItemNumber);
21
+ }
22
+ function parseSeqParam(raw) {
23
+ const n = Number(Array.isArray(raw) ? raw[0] : raw);
24
+ return Number.isFinite(n) && Number.isInteger(n) && n > 0 ? n : null;
25
+ }
26
+ // Read-only observability endpoints for the WebUI "Running tasks" panel and its
27
+ // live-log viewer. Returns in-flight rows enriched with the managed project
28
+ // path; run content (prompt, description, repo URL) is stripped by
29
+ // buildTaskViews. Admin-gated via the session cookie like the other /api routes.
30
+ export function mountTaskRoutes(app) {
31
+ app.get("/api/tasks", requireSession((req, res) => {
32
+ const limit = parseLimit(req.query.limit, 15, 100);
33
+ const offset = parseOffset(req.query.offset);
34
+ const rows = listInFlight(limit, offset);
35
+ const tasks = buildTaskViews(rows, (projectId) => getProject(projectId)?.projectPath ?? "", (projectId, mode) => {
36
+ if (mode !== "plan" && mode !== "build")
37
+ return { model: "", variant: "" };
38
+ return {
39
+ model: resolveModelForMode(projectId, mode),
40
+ variant: resolveVariantForMode(projectId, mode),
41
+ };
42
+ }, projectWorkItemUrl, globalQueuePosition);
43
+ const total = countInFlight();
44
+ // Concurrency gate snapshot for the panel header: how many slots are held
45
+ // vs the cap, and how many tasks are waiting on the gate. In-memory depth,
46
+ // not the DB backlog (per-item queued rows are already in `tasks`).
47
+ const quota = buildTaskQuota(globalQueueDepth(), globalQueueMax());
48
+ logReq(req, "debug", "list in-flight tasks", { count: tasks.length, total, limit, offset, quota });
49
+ return res.json({ tasks, total, quota });
50
+ }));
51
+ // Live snapshot of a task's currently-active phase container logs (clone,
52
+ // sync, auth, run, push, …), not only the opencode `-run` container. Each
53
+ // phase container is short-lived and named per work item; a queued task has no
54
+ // container yet (available:false) and a settled task's containers are gone
55
+ // (`--rm`). A settled task falls back to its archived run-log file (work item
56
+ // #61) so the viewer does not dead-end the moment a run finishes. The browser
57
+ // polls this while the modal is open.
58
+ app.get("/api/tasks/:seq/logs", requireSession(async (req, res) => {
59
+ const seq = parseSeqParam(req.params.seq ?? "");
60
+ if (!seq) {
61
+ logReq(req, "warn", "task logs bad seq", { seq: req.params.seq });
62
+ return res.status(400).json({ error: "invalid seq" });
63
+ }
64
+ const row = getTask(seq);
65
+ if (row && row.status === "running") {
66
+ const logs = await fetchTaskLogs(row.work_item_id);
67
+ logReq(req, "debug", "task logs", { seq, phase: logs.phase, available: logs.available, truncated: logs.truncated });
68
+ return res.json({ seq, status: row.status, archived: false, ...logs });
69
+ }
70
+ // Not running (or already deleted post-settle): serve the archived file when
71
+ // one exists. A queued task has no history row, so it stays available:false.
72
+ const history = getRunHistoryByTaskSeq(seq);
73
+ if (history) {
74
+ const archived = readArchivedLog(history.logFile);
75
+ logReq(req, "debug", "task logs (archived)", { seq, available: archived.available, reason: archived.reason });
76
+ return res.json({
77
+ seq,
78
+ status: history.status,
79
+ archived: true,
80
+ available: archived.available,
81
+ text: archived.text,
82
+ truncated: archived.truncated,
83
+ phase: null,
84
+ phaseLabel: "",
85
+ reason: archived.reason,
86
+ });
87
+ }
88
+ // Neither an in-flight row nor a history row: the seq never existed (or its
89
+ // queued row was dropped without ever running). Keep the original 404 so a
90
+ // client can tell "unknown" from "settled, no archive".
91
+ if (!row) {
92
+ logReq(req, "debug", "task logs not found", { seq });
93
+ return res.status(404).json({ error: "not found" });
94
+ }
95
+ logReq(req, "debug", "task logs unavailable", { seq, status: row.status });
96
+ return res.json({ seq, status: row.status, archived: false, available: false, text: "", truncated: false, phase: null, phaseLabel: "" });
97
+ }));
98
+ // Stop one in-flight task from the WebUI. Queued -> row dropped; running ->
99
+ // durable cancel flag + phase-container kill (runner unwinds as cancelled).
100
+ // Best-effort stop note in the work item thread. Admin-gated like the rest.
101
+ app.post("/api/tasks/:seq/stop", requireSession(async (req, res) => {
102
+ const seq = parseSeqParam(req.params.seq ?? "");
103
+ if (!seq) {
104
+ logReq(req, "warn", "stop task bad seq", { seq: req.params.seq });
105
+ return res.status(400).json({ error: "invalid seq" });
106
+ }
107
+ const row = getTask(seq);
108
+ if (!row) {
109
+ logReq(req, "debug", "stop task not found", { seq });
110
+ return res.status(404).json({ error: "not found" });
111
+ }
112
+ const result = await stopTask(seq);
113
+ try {
114
+ const p = JSON.parse(row.payload);
115
+ const workItemNumber = p.workItemNumber ?? p.workItemIid;
116
+ if (typeof p.projectId === "number" && typeof workItemNumber === "number") {
117
+ await getPlatform().workItems.postComment(p.projectId, workItemNumber, result.message);
118
+ }
119
+ }
120
+ catch (e) {
121
+ logReq(req, "warn", "stop note failed", { seq, err: e });
122
+ }
123
+ logReq(req, "info", "stop task", { seq, ok: result.ok });
124
+ return res.json({ ok: result.ok, seq });
125
+ }));
126
+ // Persisted error logs (newest first, metadata only). The body is fetched
127
+ // per-row from GET /api/error-logs/:id so the list stays small.
128
+ app.get("/api/error-logs", requireSession((req, res) => {
129
+ const limit = parseLimit(req.query.limit);
130
+ const offset = parseOffset(req.query.offset);
131
+ const projectId = parsePositiveInt(req.query.projectId);
132
+ const model = parseString(req.query.model);
133
+ const mode = parseString(req.query.mode);
134
+ const title = parseQuery(req.query.q);
135
+ const { days, from, to } = parseDateWindow(req.query);
136
+ const window = resolveUsageWindow({ days, ...(from !== undefined ? { from } : {}), ...(to !== undefined ? { to } : {}) }, Date.now());
137
+ // created_at is epoch seconds: floor the lower bound, ceil the (exclusive)
138
+ // upper bound so the current second is never dropped.
139
+ const filter = {
140
+ ...(projectId !== undefined ? { projectId } : {}),
141
+ ...(model !== undefined ? { model } : {}),
142
+ ...(mode !== undefined ? { mode } : {}),
143
+ ...(title !== undefined ? { title } : {}),
144
+ sinceSec: Math.floor(window.windowStart / 1000),
145
+ untilSec: Math.ceil(window.windowEnd / 1000),
146
+ };
147
+ const logs = listErrorLogs(limit, offset, filter).map((l) => ({
148
+ ...l,
149
+ workItemUrl: projectWorkItemUrl(l.projectId, l.workItemIid),
150
+ projectPath: getProject(l.projectId)?.projectPath ?? "",
151
+ }));
152
+ const total = countErrorLogs(filter);
153
+ const filterOptions = listErrorLogFilterOptions();
154
+ const projectPaths = {};
155
+ for (const p of filterOptions.projects)
156
+ projectPaths[String(p)] = getProject(p)?.projectPath ?? `#${p}`;
157
+ logReq(req, "debug", "list error logs", { count: logs.length, total, limit, offset });
158
+ return res.json({ logs, total, filterOptions, projectPaths });
159
+ }));
160
+ app.delete("/api/error-logs", requireSession((req, res) => {
161
+ const cleared = clearErrorLogs();
162
+ logReq(req, "info", "clear error logs", { cleared });
163
+ return res.status(204).end();
164
+ }));
165
+ app.get("/api/error-logs/:id", requireSession((req, res) => {
166
+ const id = parseSeqParam(req.params.id ?? "");
167
+ if (!id) {
168
+ logReq(req, "warn", "error log bad id", { id: req.params.id });
169
+ return res.status(400).json({ error: "invalid id" });
170
+ }
171
+ const row = getErrorLog(id);
172
+ if (!row) {
173
+ logReq(req, "debug", "error log not found", { id });
174
+ return res.status(404).json({ error: "not found" });
175
+ }
176
+ logReq(req, "debug", "get error log", { id });
177
+ // Prefer the shared run-log file (work item #61): it holds the full
178
+ // multi-phase run, redacted + capped + formatted by the same renderer as the
179
+ // live and history views. Legacy rows (no file) serve their inline body. An
180
+ // expired file renders the same "logs expired" state as the history page.
181
+ const archived = row.logFile ? readArchivedLog(row.logFile) : null;
182
+ if (archived?.available) {
183
+ return res.json({
184
+ log: {
185
+ ...row,
186
+ workItemUrl: projectWorkItemUrl(row.projectId, row.workItemIid),
187
+ projectPath: getProject(row.projectId)?.projectPath ?? "",
188
+ logs: archived.text,
189
+ truncated: archived.truncated,
190
+ logAvailable: true,
191
+ logReason: "",
192
+ },
193
+ });
194
+ }
195
+ return res.json({
196
+ log: {
197
+ ...row,
198
+ workItemUrl: projectWorkItemUrl(row.projectId, row.workItemIid),
199
+ projectPath: getProject(row.projectId)?.projectPath ?? "",
200
+ // Format the stored body for display: per-line server-local clock derived
201
+ // from podman timestamps (live) or opencode event timestamps (persisted).
202
+ // Storage stays raw; only the response is decorated.
203
+ logs: formatLogBody(row.logs),
204
+ // A row with no file reference is a legacy/uncaptured row whose inline
205
+ // body is authoritative; a row WITH a file but no readable file is
206
+ // expired.
207
+ logAvailable: row.logFile === "",
208
+ logReason: archived?.reason ?? "",
209
+ },
210
+ });
211
+ }));
212
+ }
213
+ //# sourceMappingURL=task-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-routes.js","sourceRoot":"","sources":["../../src/admin/task-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { log } from "../logger.js";
2
+ import { logReq } from "../http-util.js";
3
+ import { signCookie, SESSION_COOKIE } from "../cookie.js";
4
+ // Test-only auth bypass for the browser E2E harness (scripts/check-ui-e2e.ts).
5
+ // The UI is gated behind forge OAuth (requireSession) and the session cookie is
6
+ // httpOnly + Secure (PUBLIC_URL must be https), so a local http:// test server
7
+ // cannot set it via document.cookie or complete the real OAuth flow. This route
8
+ // signs a session directly and sets a non-Secure cookie.
9
+ //
10
+ // SECURITY: mounted ONLY when DEVBOT_TEST_AUTH === "1". Production never sets
11
+ // that var, so these routes do not exist in a normal deployment. Do not enable
12
+ // it on a public host.
13
+ export function mountTestRoutes(app) {
14
+ if (process.env.DEVBOT_TEST_AUTH !== "1")
15
+ return;
16
+ log.warn("test-auth routes enabled (DEVBOT_TEST_AUTH=1) — never enable in production");
17
+ app.get("/test/login", (req, res) => {
18
+ const userId = Number(process.env.DEVBOT_TEST_USER_ID ?? "1");
19
+ const username = process.env.DEVBOT_TEST_USERNAME ?? "e2e-admin";
20
+ const exp = Math.floor(Date.now() / 1000) + 3600;
21
+ const payload = { accessToken: "test-access-token", userId, username, exp };
22
+ res.cookie(SESSION_COOKIE, signCookie(payload), {
23
+ httpOnly: true,
24
+ secure: false,
25
+ sameSite: "lax",
26
+ path: "/",
27
+ maxAge: 3600 * 1000,
28
+ });
29
+ logReq(req, "info", "test login", { userId, username });
30
+ return res.status(204).end();
31
+ });
32
+ app.get("/test/logout", (req, res) => {
33
+ res.clearCookie(SESSION_COOKIE, { path: "/" });
34
+ logReq(req, "info", "test logout");
35
+ return res.status(204).end();
36
+ });
37
+ }
38
+ //# sourceMappingURL=test-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-routes.js","sourceRoot":"","sources":["../../src/admin/test-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAuB,MAAM,cAAc,CAAC;AAE/E,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,yDAAyD;AACzD,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,uBAAuB;AACvB,MAAM,UAAU,eAAe,CAAC,GAAoB;IAClD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,GAAG;QAAE,OAAO;IAEjD,GAAG,CAAC,IAAI,CAAC,4EAA4E,CAAC,CAAC;IAEvF,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,WAAW,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;QACjD,MAAM,OAAO,GAAmB,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC5F,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE;YAC9C,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,GAAG;YACT,MAAM,EAAE,IAAI,GAAG,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnC,GAAG,CAAC,WAAW,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QACnC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { listVolumeViews, deleteVolumeForWebUi, filterVolumeViews, } from "../volume-manage.js";
3
+ import { parseLimit, parseOffset, parsePositiveInt, parseQuery } from "./query-params.js";
4
+ // WebUI volume management (work item #64): list per-work-item volumes with
5
+ // their GC state, and delete one. Session-gated like the other /api routes.
6
+ // Filtering by GC state/project and searching the work item title was added in
7
+ // work item #88.
8
+ function parseWorkItemIdParam(raw) {
9
+ const n = Number(Array.isArray(raw) ? raw[0] : raw);
10
+ return Number.isFinite(n) && Number.isInteger(n) && n > 0 ? n : null;
11
+ }
12
+ const VOLUME_STATES = ["expiring", "eligible", "missing", "untracked", "unmapped"];
13
+ function parseVolumeState(raw) {
14
+ const v = Array.isArray(raw) ? raw[0] : raw;
15
+ return typeof v === "string" && VOLUME_STATES.includes(v) ? v : undefined;
16
+ }
17
+ // Project options come from the full list (ignoring the active filters) so a
18
+ // selected project can always be cleared. The 0 id (untracked/unmapped rows)
19
+ // is not offered — those are reached via the GC state filter.
20
+ function volumeFilterOptions(views) {
21
+ const projects = new Set();
22
+ for (const v of views)
23
+ if (v.projectId > 0)
24
+ projects.add(v.projectId);
25
+ return { projects: [...projects].sort((a, b) => a - b) };
26
+ }
27
+ export function mountVolumeRoutes(app) {
28
+ app.get("/api/volumes", requireSession(async (req, res) => {
29
+ const limit = parseLimit(req.query.limit);
30
+ const offset = parseOffset(req.query.offset);
31
+ const projectId = parsePositiveInt(req.query.projectId);
32
+ const state = parseVolumeState(req.query.state);
33
+ const title = parseQuery(req.query.q);
34
+ try {
35
+ const result = await listVolumeViews();
36
+ const filterOptions = volumeFilterOptions(result.volumes);
37
+ const projectPaths = {};
38
+ for (const v of result.volumes) {
39
+ if (v.projectId > 0)
40
+ projectPaths[String(v.projectId)] = v.projectPath || `#${v.projectId}`;
41
+ }
42
+ const filtered = filterVolumeViews(result.volumes, {
43
+ ...(state !== undefined ? { state } : {}),
44
+ ...(projectId !== undefined ? { projectId } : {}),
45
+ ...(title !== undefined ? { title } : {}),
46
+ });
47
+ const volumes = filtered.slice(offset, offset + limit);
48
+ logReq(req, "debug", "list volumes", {
49
+ count: volumes.length, total: filtered.length, gcEnabled: result.gcEnabled, podmanAvailable: result.podmanAvailable,
50
+ });
51
+ return res.json({
52
+ volumes,
53
+ total: filtered.length,
54
+ gcEnabled: result.gcEnabled,
55
+ ttlMs: result.ttlMs,
56
+ podmanAvailable: result.podmanAvailable,
57
+ filterOptions,
58
+ projectPaths,
59
+ });
60
+ }
61
+ catch (e) {
62
+ logReq(req, "error", "list volumes failed", { err: e });
63
+ return res.status(500).json({ error: "failed to list volumes" });
64
+ }
65
+ }));
66
+ // DELETE /api/volumes/:workItemId — remove a work item's volumes (workspace +
67
+ // siblings) and drop its ledger row. Posts a note to an open work item;
68
+ // closed/deleted items and untracked volumes are removed silently. Mirrors the
69
+ // stop route: a failed teardown still returns 200 with ok:false so the UI can
70
+ // surface the message.
71
+ app.delete("/api/volumes/:workItemId", requireSession(async (req, res) => {
72
+ const workItemId = parseWorkItemIdParam(req.params.workItemId ?? "");
73
+ if (!workItemId) {
74
+ logReq(req, "warn", "delete volume bad id", { id: req.params.workItemId });
75
+ return res.status(400).json({ error: "invalid work item id" });
76
+ }
77
+ const result = await deleteVolumeForWebUi(workItemId, req.session.username);
78
+ logReq(req, "info", "delete volume", { workItemId, ok: result.ok, notePosted: result.notePosted });
79
+ return res.json(result);
80
+ }));
81
+ }
82
+ //# sourceMappingURL=volume-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volume-routes.js","sourceRoot":"","sources":["../../src/admin/volume-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE1F,2EAA2E;AAC3E,4EAA4E;AAC5E,+EAA+E;AAC/E,iBAAiB;AAEjB,SAAS,oBAAoB,CAAC,GAAY;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC;AAED,MAAM,aAAa,GAAoB,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;AAEpG,SAAS,gBAAgB,CAAC,GAAY;IACpC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAK,aAA0B,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAmB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7G,CAAC;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,8DAA8D;AAC9D,SAAS,mBAAmB,CAAC,KAAmB;IAC9C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAoB;IACpD,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;YACvC,MAAM,aAAa,GAAG,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1D,MAAM,YAAY,GAA2B,EAAE,CAAC;YAChD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC;oBAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAC9F,CAAC;YACD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,OAAO,EAAE;gBACjD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1C,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC;YACvD,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE;gBACnC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe;aACpH,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,IAAI,CAAC;gBACd,OAAO;gBACP,KAAK,EAAE,QAAQ,CAAC,MAAM;gBACtB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,eAAe,EAAE,MAAM,CAAC,eAAe;gBACvC,aAAa;gBACb,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;YACjE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;IAEJ,8EAA8E;IAC9E,wEAAwE;IACxE,+EAA+E;IAC/E,8EAA8E;IAC9E,uBAAuB;IACvB,GAAG,CAAC,MAAM,CAAC,0BAA0B,EAAE,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACvE,MAAM,UAAU,GAAG,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,sBAAsB,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;YAC3E,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5E,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QACnG,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,174 @@
1
+ import { z } from "zod";
2
+ import { logReq, requireSession } from "../http-util.js";
3
+ import { getProject } from "../token-store.js";
4
+ import { GLOBAL_SCOPE_ID } from "../types/global-settings.js";
5
+ import { listWebhooks, getWebhook, upsertWebhook, deleteWebhook, newWebhookSecret, listDeliveries, countDeliveries, getDelivery, replayDelivery, } from "../outbound-webhook-store.js";
6
+ import { testDelivery } from "../outbound-webhook.js";
7
+ import { WebhookEndpointSchema, WEBHOOK_NAME_RE, WEBHOOK_NAME_MAX, } from "../types/outbound-webhook.js";
8
+ // Outbound webhook admin routes. Per-project (/api/projects/:id/webhooks) and
9
+ // global (/api/settings/webhooks); both use the same handler factory. Global
10
+ // endpoints reuse the GLOBAL_SCOPE_ID sentinel, exactly like global MCP.
11
+ function parseProjectIdParam(raw) {
12
+ const id = Number(Array.isArray(raw) ? raw[0] : raw);
13
+ return Number.isFinite(id) && Number.isInteger(id) && id > 0 ? id : null;
14
+ }
15
+ function parseWebhookName(raw) {
16
+ return typeof raw === "string" && WEBHOOK_NAME_RE.test(raw) && raw.length <= WEBHOOK_NAME_MAX ? raw : null;
17
+ }
18
+ // Keep a stored header value when the incoming one is empty (the browser only
19
+ // ever receives masked values, so an empty field means "leave unchanged").
20
+ export function mergeHeaders(existing, incoming) {
21
+ const out = {};
22
+ for (const [k, v] of Object.entries(incoming)) {
23
+ out[k] = v === "" && existing[k] !== undefined ? existing[k] : v;
24
+ }
25
+ return out;
26
+ }
27
+ // Browser-facing shape: header values are masked as hasValue (they may carry
28
+ // bearer tokens). The signing secret is not returned here; it is readable via
29
+ // GET .../:name/secret so the admin can paste it into the receiver.
30
+ export function toWebhookSummary(w) {
31
+ return {
32
+ name: w.name,
33
+ url: w.url,
34
+ events: w.events,
35
+ headers: Object.entries(w.headers).map(([name, value]) => ({ name, hasValue: value !== "" })),
36
+ enabled: w.enabled,
37
+ hasSecret: w.secret.length > 0,
38
+ createdAt: w.createdAt,
39
+ updatedAt: w.updatedAt,
40
+ };
41
+ }
42
+ // PUT body for an endpoint. `secret` is optional: omit to keep the stored one,
43
+ // or send "rotate" to mint a new secret (also minted on first create).
44
+ const WebhookMutationSchema = z.object({
45
+ url: z.string().url(),
46
+ events: z.array(z.string()).min(1),
47
+ headers: z.record(z.string(), z.string()).optional(),
48
+ enabled: z.boolean().optional(),
49
+ secret: z.string().optional(),
50
+ });
51
+ function upsertFromBody(scopeId, name, body) {
52
+ const raw = WebhookMutationSchema.safeParse(body);
53
+ if (!raw.success)
54
+ return { ok: false, status: 400, issues: raw.error.issues };
55
+ const existing = getWebhook(scopeId, name);
56
+ const mergedHeaders = mergeHeaders(existing?.headers ?? {}, raw.data.headers ?? {});
57
+ const parsed = WebhookEndpointSchema.safeParse({
58
+ url: raw.data.url,
59
+ events: raw.data.events,
60
+ headers: mergedHeaders,
61
+ enabled: raw.data.enabled ?? true,
62
+ });
63
+ if (!parsed.success)
64
+ return { ok: false, status: 400, issues: parsed.error.issues };
65
+ const secret = raw.data.secret === "rotate" || !existing ? newWebhookSecret() : existing.secret;
66
+ upsertWebhook(scopeId, name, parsed.data, secret);
67
+ return { ok: true, summary: toWebhookSummary(getWebhook(scopeId, name)) };
68
+ }
69
+ export function mountWebhookRoutes(app) {
70
+ registerScope(app, "/api/projects/:id/webhooks", (req, res) => {
71
+ const projectId = parseProjectIdParam(req.params.id ?? "");
72
+ if (!projectId) {
73
+ logReq(req, "warn", "webhook route bad id", { id: req.params.id });
74
+ res.status(400).json({ error: "invalid project id" });
75
+ return null;
76
+ }
77
+ if (!getProject(projectId)) {
78
+ logReq(req, "warn", "webhook route not managed", { projectId });
79
+ res.status(404).json({ error: "not managed" });
80
+ return null;
81
+ }
82
+ return projectId;
83
+ });
84
+ registerScope(app, "/api/settings/webhooks", () => GLOBAL_SCOPE_ID);
85
+ }
86
+ function registerScope(app, base, resolveScope) {
87
+ function route(needsName, handler) {
88
+ return requireSession((req, res) => {
89
+ const scopeId = resolveScope(req, res);
90
+ if (scopeId === null)
91
+ return; // resolver already responded
92
+ let name = "";
93
+ if (needsName) {
94
+ const parsed = parseWebhookName(String(req.params.name ?? ""));
95
+ if (!parsed) {
96
+ logReq(req, "warn", "webhook route bad name", { scopeId, name: req.params.name });
97
+ return res.status(400).json({ error: "invalid name" });
98
+ }
99
+ name = parsed;
100
+ }
101
+ return handler({ scopeId, name }, req, res);
102
+ });
103
+ }
104
+ // GET base — list endpoints (secret + header values masked).
105
+ app.get(base, route(false, ({ scopeId }, req, res) => {
106
+ const webhooks = listWebhooks(scopeId).map(toWebhookSummary);
107
+ logReq(req, "debug", "list outbound webhooks", { scopeId, count: webhooks.length });
108
+ return res.json({ webhooks });
109
+ }));
110
+ // PUT base/:name — upsert.
111
+ app.put(`${base}/:name`, route(true, ({ scopeId, name }, req, res) => {
112
+ const result = upsertFromBody(scopeId, name, req.body);
113
+ if (!result.ok)
114
+ return res.status(result.status).json({ error: "bad request", issues: result.issues });
115
+ logReq(req, "info", "upsert outbound webhook", { scopeId, name });
116
+ return res.json({ ok: true, webhook: result.summary });
117
+ }));
118
+ // DELETE base/:name — remove.
119
+ app.delete(`${base}/:name`, route(true, ({ scopeId, name }, req, res) => {
120
+ const existed = deleteWebhook(scopeId, name);
121
+ if (!existed)
122
+ return res.status(404).json({ error: "not found" });
123
+ logReq(req, "info", "delete outbound webhook", { scopeId, name });
124
+ return res.status(204).end();
125
+ }));
126
+ // GET base/:name/secret — reveal the signing secret for receiver setup.
127
+ app.get(`${base}/:name/secret`, route(true, ({ scopeId, name }, _req, res) => {
128
+ const w = getWebhook(scopeId, name);
129
+ if (!w)
130
+ return res.status(404).json({ error: "not found" });
131
+ return res.json({ secret: w.secret });
132
+ }));
133
+ // POST base/:name/test — synchronous probe delivery.
134
+ app.post(`${base}/:name/test`, route(true, async ({ scopeId, name }, req, res) => {
135
+ const w = getWebhook(scopeId, name);
136
+ if (!w)
137
+ return res.status(404).json({ error: "not found" });
138
+ const result = await testDelivery(w);
139
+ logReq(req, "info", "test outbound webhook", { scopeId, name, ok: result.ok, status: result.status });
140
+ return res.json({ ok: result.ok, status: result.status, error: result.error });
141
+ }));
142
+ // GET base/:name/deliveries — paged metadata.
143
+ app.get(`${base}/:name/deliveries`, route(true, ({ scopeId, name }, req, res) => {
144
+ const limit = Math.min(Math.max(Number(req.query.limit) || 20, 1), 100);
145
+ const offset = Math.max(Number(req.query.offset) || 0, 0);
146
+ const deliveries = listDeliveries(scopeId, name, limit, offset);
147
+ return res.json({ deliveries, total: countDeliveries(scopeId, name) });
148
+ }));
149
+ // GET base/:name/deliveries/:deliveryId — payload + response snippet.
150
+ app.get(`${base}/:name/deliveries/:deliveryId`, route(true, ({ scopeId, name }, req, res) => {
151
+ const deliveryId = Number(req.params.deliveryId ?? "");
152
+ if (!Number.isInteger(deliveryId) || deliveryId <= 0)
153
+ return res.status(400).json({ error: "invalid params" });
154
+ const row = getDelivery(deliveryId);
155
+ if (!row || row.projectId !== scopeId || row.webhookName !== name) {
156
+ return res.status(404).json({ error: "not found" });
157
+ }
158
+ return res.json({ delivery: row });
159
+ }));
160
+ // POST base/:name/deliveries/:deliveryId/replay — re-queue for delivery.
161
+ app.post(`${base}/:name/deliveries/:deliveryId/replay`, route(true, ({ scopeId, name }, req, res) => {
162
+ const deliveryId = Number(req.params.deliveryId ?? "");
163
+ if (!Number.isInteger(deliveryId) || deliveryId <= 0)
164
+ return res.status(400).json({ error: "invalid params" });
165
+ const row = getDelivery(deliveryId);
166
+ if (!row || row.projectId !== scopeId || row.webhookName !== name) {
167
+ return res.status(404).json({ error: "not found" });
168
+ }
169
+ replayDelivery(deliveryId);
170
+ logReq(req, "info", "replay outbound webhook delivery", { scopeId, name, deliveryId });
171
+ return res.json({ ok: true });
172
+ }));
173
+ }
174
+ //# sourceMappingURL=webhook-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-routes.js","sourceRoot":"","sources":["../../src/admin/webhook-routes.ts"],"names":[],"mappings":"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"}
package/dist/agents.js ADDED
@@ -0,0 +1,48 @@
1
+ import { log } from "./logger.js";
2
+ import { getPlatform } from "./platform/index.js";
3
+ import { destroyWorkItemVolume } from "./runner.js";
4
+ import { deleteVolumeRow } from "./volume-store.js";
5
+ import { deleteBaseBranch } from "./base-branch-store.js";
6
+ // ponytail: one-way edge agents.ts → runner.ts (no cycle — runner.ts never
7
+ // imports agents.ts). Reaching into the runner's cleanup for a volume reset
8
+ // is a minor coupling smell; inject destroyWorkItemVolume as a param only if
9
+ // this edge ever grows into a cycle or runner.ts gets a second caller.
10
+ //
11
+ // Agent discovery/validation moved to the runner after syncing configured
12
+ // agent repos (work item #63); this module now only owns reset handling.
13
+ // ponytail: skips the per-work-item queue — reset is one podman rm -f + volume rm,
14
+ // ~100ms, no opencode spawn. Matches the close handler's precedent (also calls
15
+ // destroyWorkItemVolume outside the queue). Bypasses queue ordering: a reset
16
+ // mid-run kills the runner's container; the runner's runPodmanCapture sees
17
+ // non-zero exit and posts ERROR_REPLY, then this posts the reset result. Both
18
+ // notes appear in the thread — intentional, no suppression flag. The queue's
19
+ // per-work-item Promise chain keeps ordering so a queued follow-up won't start
20
+ // until the killed task's promise settles.
21
+ //
22
+ // Authorization (the configured trigger policy — author/assignee gate plus
23
+ // minimum role, refined by per-project rules) is enforced by the general
24
+ // trigger gate in webhook.ts before this is called; no local check here.
25
+ // triggeringNoteId: the note id that carried the /reset mention, used to reply
26
+ // in-thread on failure; undefined on the work-item-creation path (no note).
27
+ export async function handleResetNote(projectId, workItemNumber, workItemId, triggeringNoteId) {
28
+ try {
29
+ const result = await destroyWorkItemVolume(workItemId);
30
+ // Drop the ledger row so the GC sweep doesn't re-visit the freed volume.
31
+ // Best-effort; a stray row is harmless (sweep no-ops on a gone volume).
32
+ deleteVolumeRow(workItemId);
33
+ // Clear any /base choice so a post-reset re-mention with /base sets a fresh
34
+ // value (first-write-wins would otherwise keep the pre-reset base).
35
+ try {
36
+ deleteBaseBranch(workItemId);
37
+ }
38
+ catch (e) {
39
+ log.warn("reset base-branch delete failed", { workItemId, err: e });
40
+ }
41
+ await getPlatform().workItems.postComment(projectId, workItemNumber, result.message).catch((e) => log.warn("reset-result note failed", e));
42
+ }
43
+ catch (e) {
44
+ log.warn("reset failed", { workItemId, err: e });
45
+ await getPlatform().workItems.postErrorReply(projectId, workItemNumber, triggeringNoteId, "⚠️ Reset failed; see logs.").catch((e) => log.warn("reset-error note failed", e));
46
+ }
47
+ }
48
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,2EAA2E;AAC3E,8EAA8E;AAC9E,+EAA+E;AAC/E,yEAAyE;AACzE,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAE3E,mFAAmF;AACnF,iFAAiF;AACjF,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,6CAA6C;AAC7C,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,+EAA+E;AAC/E,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAiB,EACjB,cAAsB,EACtB,UAAkB,EAClB,gBAAoC;IAEpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACvD,yEAAyE;QACzE,0EAA0E;QAC1E,eAAe,CAAC,UAAU,CAAC,CAAC;QAC5B,4EAA4E;QAC5E,sEAAsE;QACtE,IAAI,CAAC;YACH,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,WAAW,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,SAAS,EAAE,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/F,GAAG,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,CAAC,CACxC,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;QAC1D,MAAM,WAAW,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,4BAA4B,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAClI,GAAG,CAAC,IAAI,CAAC,yBAAyB,EAAE,CAAC,CAAC,CACvC,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Shared, dependency-free assignee value rules. Imported by the server schemas
2
+ // and (through the @server alias) by the two WebUI validators so the username
3
+ // shape has exactly one definition per build graph.
4
+ export const ASSIGNEE_RE = /^[A-Za-z0-9._-]{1,255}$/;
5
+ export const ASSIGNEE_NONE = "none";
6
+ //# sourceMappingURL=assignee-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignee-value.js","sourceRoot":"","sources":["../src/assignee-value.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,oDAAoD;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC"}