@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,138 @@
1
+ import { discoverOAuthServerInfo, refreshAuthorization } from "@modelcontextprotocol/sdk/client/auth.js";
2
+ import { getMcpAuthEntry, listMcpAuthScopes, upsertMcpAuth } from "../mcp-auth-store.js";
3
+ import { listMcpServers } from "../mcp-config-store.js";
4
+ import { redact } from "../redact.js";
5
+ import { log } from "../logger.js";
6
+ // Host-side OAuth token refresh for remote MCP servers.
7
+ //
8
+ // Why this exists: opencode refreshes tokens inside the run container, but its
9
+ // refreshed/rotated tokens are written only to the container's mcp-auth.json —
10
+ // which the runner overwrites from the DB before every run. A provider that
11
+ // rotates the refresh token therefore leaves DevBot holding a stale one and the
12
+ // next refresh fails silently. Refreshing here and persisting the rotation back
13
+ // to project_mcp_auth closes that loop.
14
+ //
15
+ // One primitive, three triggers: the periodic sweep (src/sweeps.ts), the pre-run
16
+ // safety net (prepareMcpRun), and the manual admin endpoint. None of them throw —
17
+ // a failed refresh logs (redacted) and leaves the stored row untouched.
18
+ const REFRESH_TIMEOUT_MS = 10_000;
19
+ // Serialize refreshes per (scopeId, name): a token provider that rotates on
20
+ // refresh must never have two concurrent refresh calls racing for the same
21
+ // rotating refresh token (the loser would invalidate the winner's token).
22
+ const inflight = new Set();
23
+ function message(e) {
24
+ return e instanceof Error ? e.message : String(e);
25
+ }
26
+ // Resolve `{env:VAR}` placeholders against the host env (the config values are
27
+ // authored for the container, but the host runs the refresh). Returns null when a
28
+ // placeholder cannot be resolved so the caller skips instead of sending a literal.
29
+ function resolveEnv(value) {
30
+ const out = value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? `{env:${name}}`);
31
+ return out.includes("{env:") ? null : out;
32
+ }
33
+ // Client credentials for the token request: pasted clientInfo wins (it is what
34
+ // the provider registered with), else the admin-configured oauth block. The
35
+ // latter may carry {env:VAR} placeholders; an unresolved placeholder means we
36
+ // cannot authenticate and must skip.
37
+ function clientInformation(entry, oauth) {
38
+ const info = entry.clientInfo;
39
+ if (info?.clientId) {
40
+ return { client_id: info.clientId, ...(info.clientSecret ? { client_secret: info.clientSecret } : {}) };
41
+ }
42
+ if (oauth && typeof oauth === "object" && "clientId" in oauth) {
43
+ const o = oauth;
44
+ const clientId = resolveEnv(o.clientId);
45
+ if (!clientId)
46
+ return null;
47
+ const clientSecret = o.clientSecret ? resolveEnv(o.clientSecret) : undefined;
48
+ if (o.clientSecret && !clientSecret)
49
+ return null;
50
+ return { client_id: clientId, ...(clientSecret ? { client_secret: clientSecret } : {}) };
51
+ }
52
+ return null;
53
+ }
54
+ // Refresh one stored entry if it has a refresh token and is due. Never throws.
55
+ export async function refreshMcpAuthEntry(scopeId, name, opts = {}) {
56
+ const key = `${scopeId}:${name}`;
57
+ if (inflight.has(key))
58
+ return { ok: false, skipped: true, reason: "refresh already in flight" };
59
+ inflight.add(key);
60
+ try {
61
+ const entry = getMcpAuthEntry(scopeId, name);
62
+ if (!entry)
63
+ return { ok: false, skipped: true, reason: "no stored auth entry" };
64
+ const refreshToken = entry.tokens.refreshToken;
65
+ if (!refreshToken)
66
+ return { ok: false, skipped: true, reason: "no refresh token" };
67
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
68
+ const horizonSec = Math.floor((opts.horizonMs ?? 0) / 1000);
69
+ if (!opts.force &&
70
+ entry.tokens.expiresAt !== undefined &&
71
+ entry.tokens.expiresAt > now + horizonSec) {
72
+ return { ok: true, skipped: true, reason: "not due", expiresAt: entry.tokens.expiresAt };
73
+ }
74
+ const cfg = listMcpServers(scopeId).find((s) => s.name === name);
75
+ if (!cfg || cfg.config.type !== "remote") {
76
+ return { ok: false, skipped: true, reason: "no remote server config" };
77
+ }
78
+ const client = clientInformation(entry, cfg.config.oauth);
79
+ if (!client) {
80
+ return { ok: false, skipped: true, reason: "no client credentials (clientInfo or oauth.clientId)" };
81
+ }
82
+ const fetchFn = opts.fetchFn ?? globalThis.fetch;
83
+ const timedFetch = (input, init) => fetchFn(input, { ...init, signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS) });
84
+ const serverInfo = await discoverOAuthServerInfo(cfg.config.url, { fetchFn: timedFetch });
85
+ const resource = serverInfo.resourceMetadata?.resource
86
+ ? new URL(serverInfo.resourceMetadata.resource)
87
+ : undefined;
88
+ const tokens = await refreshAuthorization(serverInfo.authorizationServerUrl, {
89
+ ...(serverInfo.authorizationServerMetadata ? { metadata: serverInfo.authorizationServerMetadata } : {}),
90
+ clientInformation: client,
91
+ refreshToken,
92
+ ...(resource ? { resource } : {}),
93
+ fetchFn: timedFetch,
94
+ });
95
+ // Preserve the previous refresh token when the provider did not rotate it
96
+ // (the SDK guarantees this, but be explicit). Drop a stale expiresAt only when
97
+ // the provider returns a fresh lifetime; otherwise keep the old value.
98
+ const nextTokens = {
99
+ accessToken: tokens.access_token,
100
+ refreshToken: tokens.refresh_token ?? refreshToken,
101
+ ...(tokens.expires_in !== undefined
102
+ ? { expiresAt: now + tokens.expires_in }
103
+ : entry.tokens.expiresAt !== undefined
104
+ ? { expiresAt: entry.tokens.expiresAt }
105
+ : {}),
106
+ ...(tokens.scope ?? entry.tokens.scope ? { scope: tokens.scope ?? entry.tokens.scope } : {}),
107
+ };
108
+ upsertMcpAuth(scopeId, name, { ...entry, tokens: nextTokens });
109
+ log.info("mcp token refreshed", { scopeId, name, expiresAt: nextTokens.expiresAt });
110
+ return { ok: true, expiresAt: nextTokens.expiresAt };
111
+ }
112
+ catch (e) {
113
+ const reason = redact(message(e));
114
+ log.warn("mcp token refresh failed", { scopeId, name, reason });
115
+ return { ok: false, reason };
116
+ }
117
+ finally {
118
+ inflight.delete(key);
119
+ }
120
+ }
121
+ // Sweep stored auth rows, refreshing those expiring within `horizonMs`.
122
+ // Sequential on purpose: bounded, and refresh endpoints are frequently
123
+ // rate-limited. One row's failure never aborts the sweep. When `scopeIds` is
124
+ // given, only those scopes are swept (the pre-run path limits itself to the
125
+ // global + run project scopes it is about to emit); omitted = every scope.
126
+ export async function refreshDueMcpAuth(horizonMs, scopeIds) {
127
+ const allowed = scopeIds ? new Set(scopeIds) : null;
128
+ for (const { scopeId, name } of listMcpAuthScopes()) {
129
+ if (allowed && !allowed.has(scopeId))
130
+ continue;
131
+ await refreshMcpAuthEntry(scopeId, name, { horizonMs });
132
+ }
133
+ }
134
+ // Test-only: clear the in-flight guard between cases.
135
+ export function resetRefreshInflightForTest() {
136
+ inflight.clear();
137
+ }
138
+ //# sourceMappingURL=oauth-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-refresh.js","sourceRoot":"","sources":["../../src/mcp/oauth-refresh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { config } from "../config.js";
2
+ import { log } from "../logger.js";
3
+ import { redact } from "../redact.js";
4
+ import { podman } from "../podman.js";
5
+ import { volumeName, gitExtraHeader, envArgs, logDriverArgs, volumeSelinuxArgs } from "../git.js";
6
+ import { PROJECT_ROOT, validateProjectRelativePath } from "../path-sandbox.js";
7
+ // On-demand clone/fetch of an authorized peer project into the run volume.
8
+ //
9
+ // Layout: /workspace/.devbot-peers/<path_with_namespace>/ — inside the volume
10
+ // but excluded from git via .git/info/exclude (local-only ignore), so the
11
+ // project's own `git add -A` / `git status` never see it. This is the
12
+ // "out of scope of git but accessible by the agent" requirement: the agent can
13
+ // read/grep the peer tree, but it can't be committed by mistake.
14
+ //
15
+ // The clone runs in a short-lived podman container mounting the work-item
16
+ // volume. The bot's forge token is injected only into that one git invocation
17
+ // via the same http.extraHeader used by the host-side clone/push; it is never
18
+ // written into the peer tree.
19
+ export const PEERS_DIR = ".devbot-peers";
20
+ export function peerPath(projectPath) {
21
+ // Defense in depth: the forge project path is interpolated into a shell
22
+ // command and a clone destination. shellQuote already blocks injection, but
23
+ // a `..`/absolute path could still escape the peers dir. Validate first.
24
+ return `${PROJECT_ROOT}/${PEERS_DIR}/${validateProjectRelativePath(projectPath)}`;
25
+ }
26
+ // Append one or more dir entries (each with a trailing slash) to the repo's
27
+ // local exclude file if absent. Local exclude lives at .git/info/exclude and is
28
+ // never committed, unlike .gitignore. Runs inside a container (the volume is
29
+ // only reachable there). Used for `.devbot-peers/` and the outbound-attachment
30
+ // staging dir `.devbot-out/`.
31
+ export function excludeEntriesScript(entries) {
32
+ const lines = ["cd /workspace", "mkdir -p .git/info"];
33
+ for (const e of entries) {
34
+ lines.push(`grep -qxF '${e}' .git/info/exclude 2>/dev/null || echo '${e}' >> .git/info/exclude`);
35
+ }
36
+ return lines.join(" && ");
37
+ }
38
+ // Back-compat single-entry helper for the peer folder.
39
+ export function excludeScript() {
40
+ return excludeEntriesScript([`${PEERS_DIR}/`]);
41
+ }
42
+ // Compose the shell command that clones or fetches one peer. Pure (given the
43
+ // inputs) so it can be unit-tested without podman. `extraHeader` is the
44
+ // token-bearing git prefix; the URL is the enrolled project's http URL.
45
+ export function peerSyncScript(projectPath, cloneUrl, ref, extraHeader) {
46
+ const dir = `${PEERS_DIR}/${validateProjectRelativePath(projectPath)}`;
47
+ const branchFlag = ref ? ` --branch ${shellQuote(ref)}` : "";
48
+ const checkout = ref ? ` && git -C ${shellQuote(dir)} checkout ${shellQuote(ref)}` : "";
49
+ // mkdir -p the parent so the path with namespace (group/subgroup/project) nests.
50
+ const parent = dir.split("/").slice(0, -1).join("/");
51
+ return [
52
+ "cd /workspace",
53
+ excludeScript(),
54
+ `mkdir -p ${shellQuote(parent)}`,
55
+ `if [ -d ${shellQuote(dir)}/.git ]; then ` +
56
+ `git -C ${shellQuote(dir)} fetch --all --prune${checkout}; ` +
57
+ `else ${extraHeader} clone${branchFlag} ${shellQuote(cloneUrl)} ${shellQuote(dir)}; fi`,
58
+ `git -C ${shellQuote(dir)} rev-parse --short HEAD`,
59
+ ].join(" && ");
60
+ }
61
+ function shellQuote(s) {
62
+ return "'" + s.replace(/'/g, "'\\''") + "'";
63
+ }
64
+ // Write the git-exclude line(s) proactively at run start (when the project has
65
+ // any granted targets) so the peers folder is invisible to git even before the
66
+ // first sync_project call. Idempotent. Best-effort: a failure is logged, not
67
+ // fatal — sync_project writes the exclude again before cloning anyway.
68
+ export async function ensureGitExcludes(workItemId, entries, envVars = [], timeoutMs = 60_000, builderImage = config.BUILDER_IMAGE) {
69
+ const vol = volumeName(workItemId);
70
+ const r = await podman([
71
+ "run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${workItemId}-gitexclude`,
72
+ ...volumeSelinuxArgs(workItemId),
73
+ "-v", `${vol}:/workspace:Z`,
74
+ ...envArgs(envVars),
75
+ builderImage,
76
+ "sh", "-c", excludeEntriesScript(entries),
77
+ ], { timeoutMs });
78
+ if (r.code !== 0) {
79
+ log.warn("git exclude init failed", { workItemId, entries, code: r.code });
80
+ }
81
+ }
82
+ // Back-compat wrapper: ensure just the peer folder is excluded.
83
+ export async function ensurePeerExclude(workItemId, envVars = [], timeoutMs = 60_000, builderImage = config.BUILDER_IMAGE) {
84
+ return ensureGitExcludes(workItemId, [`${PEERS_DIR}/`], envVars, timeoutMs, builderImage);
85
+ }
86
+ export async function syncPeerProject(input) {
87
+ const vol = volumeName(input.workItemId);
88
+ const script = peerSyncScript(input.projectPath, input.cloneUrl, input.ref, gitExtraHeader());
89
+ const r = await podman([
90
+ "run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${input.workItemId}-peersync`,
91
+ // Same per-work-item SELinux level as the agent run container; :Z alone
92
+ // would relabel the volume to this helper's random pair mid-run.
93
+ ...volumeSelinuxArgs(input.workItemId),
94
+ "-v", `${vol}:/workspace:Z`,
95
+ ...envArgs(input.envVars ?? []),
96
+ input.builderImage ?? config.BUILDER_IMAGE,
97
+ "sh", "-c", script,
98
+ ], { timeoutMs: input.timeoutMs ?? 5 * 60_000 });
99
+ const path = peerPath(input.projectPath);
100
+ if (r.code !== 0) {
101
+ const msg = redact(r.stderr.slice(-400));
102
+ log.warn("peer sync failed", { workItemId: input.workItemId, projectPath: input.projectPath, code: r.code });
103
+ return { ok: false, path, sha: "", message: `peer sync failed: ${msg}` };
104
+ }
105
+ const sha = r.stdout.trim().split("\n").pop()?.trim() ?? "";
106
+ log.info("peer sync ok", { workItemId: input.workItemId, projectPath: input.projectPath, ref: input.ref ?? "default" });
107
+ return { ok: true, path, sha, message: `synced ${input.projectPath}` };
108
+ }
109
+ //# sourceMappingURL=peer-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-sync.js","sourceRoot":"","sources":["../../src/mcp/peer-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAE/E,2EAA2E;AAC3E,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,sEAAsE;AACtE,+EAA+E;AAC/E,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,8EAA8E;AAC9E,8BAA8B;AAE9B,MAAM,CAAC,MAAM,SAAS,GAAG,eAAe,CAAC;AAEzC,MAAM,UAAU,QAAQ,CAAC,WAAmB;IAC1C,wEAAwE;IACxE,8EAA8E;IAC9E,2EAA2E;IAC3E,OAAO,GAAG,YAAY,IAAI,SAAS,IAAI,2BAA2B,CAAC,WAAW,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,+EAA+E;AAC/E,8BAA8B;AAC9B,MAAM,UAAU,oBAAoB,CAAC,OAAiB;IACpD,MAAM,KAAK,GAAG,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,4CAA4C,CAAC,wBAAwB,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,aAAa;IAC3B,OAAO,oBAAoB,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,6EAA6E;AAC7E,wEAAwE;AACxE,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,QAAgB,EAAE,GAAuB,EAAE,WAAmB;IAChH,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,2BAA2B,CAAC,WAAW,CAAC,EAAE,CAAC;IACvE,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,cAAc,UAAU,CAAC,GAAG,CAAC,aAAa,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxF,iFAAiF;IACjF,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrD,OAAO;QACL,eAAe;QACf,aAAa,EAAE;QACf,YAAY,UAAU,CAAC,MAAM,CAAC,EAAE;QAChC,WAAW,UAAU,CAAC,GAAG,CAAC,gBAAgB;YACxC,UAAU,UAAU,CAAC,GAAG,CAAC,uBAAuB,QAAQ,IAAI;YAC5D,QAAQ,WAAW,SAAS,UAAU,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM;QACzF,UAAU,UAAU,CAAC,GAAG,CAAC,yBAAyB;KACnD,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,GAAG,CAAC;AAC9C,CAAC;AASD,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,OAAiB,EACjB,UAAkD,EAAE,EACpD,SAAS,GAAG,MAAM,EAClB,eAAuB,MAAM,CAAC,aAAa;IAE3C,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,MAAM,MAAM,CACpB;QACE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,EAAE,QAAQ,EAAE,aAAa,UAAU,aAAa;QACjF,GAAG,iBAAiB,CAAC,UAAU,CAAC;QAChC,IAAI,EAAE,GAAG,GAAG,eAAe;QAC3B,GAAG,OAAO,CAAC,OAAO,CAAC;QACnB,YAAY;QACZ,IAAI,EAAE,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;KAC1C,EACD,EAAE,SAAS,EAAE,CACd,CAAC;IACF,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACjB,GAAG,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,UAAkD,EAAE,EACpD,SAAS,GAAG,MAAM,EAClB,eAAuB,MAAM,CAAC,aAAa;IAE3C,OAAO,iBAAiB,CAAC,UAAU,EAAE,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAC5F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAQrC;IACC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC;IAC9F,MAAM,CAAC,GAAG,MAAM,MAAM,CACpB;QACE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,EAAE,QAAQ,EAAE,aAAa,KAAK,CAAC,UAAU,WAAW;QACrF,wEAAwE;QACxE,iEAAiE;QACjE,GAAG,iBAAiB,CAAC,KAAK,CAAC,UAAU,CAAC;QACtC,IAAI,EAAE,GAAG,GAAG,eAAe;QAC3B,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QAC/B,KAAK,CAAC,YAAY,IAAI,MAAM,CAAC,aAAa;QAC1C,IAAI,EAAE,IAAI,EAAE,MAAM;KACnB,EACD,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,CAAC,GAAG,MAAM,EAAE,CAC7C,CAAC;IACF,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7G,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,qBAAqB,GAAG,EAAE,EAAE,CAAC;IAC3E,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5D,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC;IACxH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;AACzE,CAAC"}
@@ -0,0 +1,54 @@
1
+ import * as issues from "./tools/issues.js";
2
+ import * as mergeRequests from "./tools/merge_requests.js";
3
+ import * as workItems from "./tools/workitems.js";
4
+ import * as discovery from "./tools/discovery.js";
5
+ import * as repository from "./tools/repository.js";
6
+ import * as cicd from "./tools/cicd.js";
7
+ import * as workTracking from "./tools/work_tracking.js";
8
+ import * as metadata from "./tools/metadata.js";
9
+ import * as peerSync from "./tools/peer_sync.js";
10
+ import * as attachments from "./tools/attachments.js";
11
+ // Flat array: adding a tool = create a file under tools/, export a Tool, add
12
+ // it here. tools/list and tools/call dispatch from this array.
13
+ export const TOOLS = [
14
+ // Existing, now cross-project-capable.
15
+ issues.listIssues,
16
+ issues.getIssue,
17
+ mergeRequests.listMergeRequests,
18
+ mergeRequests.getMergeRequestDiscussions,
19
+ workItems.searchWorkItems,
20
+ // Discovery.
21
+ discovery.listAccessibleProjects,
22
+ discovery.getProject,
23
+ // Repository.
24
+ repository.listRepositoryTree,
25
+ repository.getRepositoryFile,
26
+ repository.listCommits,
27
+ repository.getCommit,
28
+ repository.compareRefs,
29
+ repository.listBranches,
30
+ repository.listTags,
31
+ // CI/CD.
32
+ cicd.listPipelines,
33
+ cicd.getPipeline,
34
+ cicd.listPipelineJobs,
35
+ cicd.getJobLog,
36
+ // Work tracking.
37
+ workTracking.listLabels,
38
+ workTracking.listMilestones,
39
+ workTracking.listIssueNotes,
40
+ workTracking.getMergeRequest,
41
+ workTracking.listMergeRequestNotes,
42
+ // Repository metadata.
43
+ metadata.listMembers,
44
+ metadata.listReleases,
45
+ metadata.listWikiPages,
46
+ metadata.getWikiPage,
47
+ metadata.searchProject,
48
+ // Peer code materialization.
49
+ peerSync.syncProject,
50
+ // Attachment download (agent reads work-item files) + upload.
51
+ attachments.downloadWorkItemAttachment,
52
+ attachments.uploadWorkItemAttachment,
53
+ ];
54
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/mcp/registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,UAAU,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,YAAY,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,WAAW,MAAM,wBAAwB,CAAC;AA0BtD,6EAA6E;AAC7E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,uCAAuC;IACvC,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,QAAQ;IACf,aAAa,CAAC,iBAAiB;IAC/B,aAAa,CAAC,0BAA0B;IACxC,SAAS,CAAC,eAAe;IACzB,aAAa;IACb,SAAS,CAAC,sBAAsB;IAChC,SAAS,CAAC,UAAU;IACpB,cAAc;IACd,UAAU,CAAC,kBAAkB;IAC7B,UAAU,CAAC,iBAAiB;IAC5B,UAAU,CAAC,WAAW;IACtB,UAAU,CAAC,SAAS;IACpB,UAAU,CAAC,WAAW;IACtB,UAAU,CAAC,YAAY;IACvB,UAAU,CAAC,QAAQ;IACnB,SAAS;IACT,IAAI,CAAC,aAAa;IAClB,IAAI,CAAC,WAAW;IAChB,IAAI,CAAC,gBAAgB;IACrB,IAAI,CAAC,SAAS;IACd,iBAAiB;IACjB,YAAY,CAAC,UAAU;IACvB,YAAY,CAAC,cAAc;IAC3B,YAAY,CAAC,cAAc;IAC3B,YAAY,CAAC,eAAe;IAC5B,YAAY,CAAC,qBAAqB;IAClC,uBAAuB;IACvB,QAAQ,CAAC,WAAW;IACpB,QAAQ,CAAC,YAAY;IACrB,QAAQ,CAAC,aAAa;IACtB,QAAQ,CAAC,WAAW;IACpB,QAAQ,CAAC,aAAa;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,WAAW;IACpB,8DAA8D;IAC9D,WAAW,CAAC,0BAA0B;IACtC,WAAW,CAAC,wBAAwB;CACrC,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { mintMcpToken, deleteMcpToken } from "./auth.js";
2
+ import { mergedMcpServers, effectiveMcpServers } from "./effective-mcp.js";
3
+ import { buildMcpAuthJsonForScopes } from "../mcp-auth-store.js";
4
+ import { refreshDueMcpAuth } from "./oauth-refresh.js";
5
+ import { buildEnvArray } from "../env-config-store.js";
6
+ import { resolveModelForMode, resolveVariantForMode, resolvePlugins, resolveBaseBranches, resolveBuilderImage, resolveBuildWorkflow, resolveMrDescription, resolveLspServers } from "../settings-store.js";
7
+ import { autoFeatureEnabled } from "../work-item-auto-store.js";
8
+ import { enabledProvidersForRun } from "../global-settings-store.js";
9
+ import { config } from "../config.js";
10
+ import { listAccessTargets } from "../access-grant-store.js";
11
+ import { resolveCustomRepos } from "../custom-repos-store.js";
12
+ import { GLOBAL_SCOPE_ID } from "../types/global-settings.js";
13
+ import { log } from "../logger.js";
14
+ export async function prepareMcpRun(projectId, workItemId, workItemNumber, ttlMs) {
15
+ const { token } = mintMcpToken({ projectId, workItemId, workItemNumber, ttlMs });
16
+ // MCP merge order: global first, project second (project wins on name
17
+ // collision). The bundled internal servers are injected later in
18
+ // buildOpencodeConfig and always win.
19
+ const mcpServers = mergedMcpServers(projectId);
20
+ const effective = effectiveMcpServers(projectId);
21
+ const configs = new Map();
22
+ for (const e of effective) {
23
+ if (e.config.type === "remote")
24
+ configs.set(e.name, { url: e.config.url });
25
+ }
26
+ // Observable per-run record of which external servers a run receives and
27
+ // where each came from, so "did the global apply?" is answerable from logs.
28
+ // Names/state only — never secrets or header values.
29
+ log.info("mcp run servers", {
30
+ projectId,
31
+ workItemId,
32
+ servers: effective.map((e) => ({
33
+ name: e.name,
34
+ origin: e.origin,
35
+ enabled: e.enabled,
36
+ hasAuth: configs.has(e.name),
37
+ overriddenByProject: e.overriddenByProject,
38
+ })),
39
+ });
40
+ // Pre-run OAuth refresh safety net (always on, independent of the scheduler
41
+ // toggle): tokens expiring within this run's TTL are renewed host-side so
42
+ // opencode never has to refresh in-container (and lose the rotation). Never
43
+ // throws — a failed refresh leaves the stored row and the run proceeds.
44
+ await refreshDueMcpAuth(ttlMs, [GLOBAL_SCOPE_ID, projectId]);
45
+ const mcpAuthJson = JSON.stringify(buildMcpAuthJsonForScopes([GLOBAL_SCOPE_ID, projectId], configs), null, 2);
46
+ const envVars = buildEnvArray(projectId);
47
+ const models = {
48
+ plan: { model: resolveModelForMode(projectId, "plan"), variant: resolveVariantForMode(projectId, "plan") },
49
+ build: { model: resolveModelForMode(projectId, "build"), variant: resolveVariantForMode(projectId, "build") },
50
+ };
51
+ const plugins = resolvePlugins(projectId);
52
+ const baseBranches = resolveBaseBranches(projectId);
53
+ const builderImage = resolveBuilderImage(projectId) || config.BUILDER_IMAGE;
54
+ const buildWorkflow = resolveBuildWorkflow(projectId);
55
+ const mrDescription = resolveMrDescription(projectId);
56
+ const autoPush = autoFeatureEnabled(projectId, workItemId, "autoPush");
57
+ const autoMr = autoFeatureEnabled(projectId, workItemId, "autoMr");
58
+ const autoResolveConflicts = autoFeatureEnabled(projectId, workItemId, "autoResolveConflicts");
59
+ const lspServers = resolveLspServers(projectId);
60
+ const providers = enabledProvidersForRun();
61
+ const peerProjects = listAccessTargets(projectId).filter((t) => t.projectId !== projectId);
62
+ const customRepos = {
63
+ skills: resolveCustomRepos(projectId, "skills"),
64
+ agents: resolveCustomRepos(projectId, "agents"),
65
+ };
66
+ return { mcpToken: token, mcpServers, mcpAuthJson, envVars, models, plugins, baseBranches, builderImage, buildWorkflow, mrDescription, autoPush, autoMr, autoResolveConflicts, lspServers, providers, peerProjects, customRepos };
67
+ }
68
+ export function cleanupMcpRun(token) {
69
+ deleteMcpToken(token);
70
+ }
71
+ // Per-project env vars for a push-only run (standalone /force-push), which has
72
+ // no MCP context. Kept here so runner.ts stays free of direct store imports.
73
+ export function projectEnvVars(projectId) {
74
+ return buildEnvArray(projectId);
75
+ }
76
+ // Resolved builder image for a push-only run (standalone /force-push), which has
77
+ // no MCP context. Kept here so runner.ts stays free of direct store imports.
78
+ export function projectBuilderImage(projectId) {
79
+ return resolveBuilderImage(projectId) || config.BUILDER_IMAGE;
80
+ }
81
+ // Resolved base-branch walk for a push/MR-only run (standalone /push, /mr),
82
+ // which has no MCP context and so cannot read `mcpCtx.baseBranches`. Kept here
83
+ // so runner.ts stays free of direct store imports.
84
+ export function projectBaseBranches(projectId) {
85
+ return resolveBaseBranches(projectId);
86
+ }
87
+ // Resolved conflict auto-fix switch for the standalone /mr path, which has no
88
+ // MCP context. Kept here so runner.ts stays free of direct store imports.
89
+ // Work-item override aware (work item #58).
90
+ export function projectAutoResolveConflicts(projectId, workItemId) {
91
+ return autoFeatureEnabled(projectId, workItemId, "autoResolveConflicts");
92
+ }
93
+ //# sourceMappingURL=runner-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-bridge.js","sourceRoot":"","sources":["../../src/mcp/runner-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import { Router } from "express";
2
+ import { randomUUID } from "node:crypto";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import { hostHeaderValidation } from "@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js";
6
+ import { log } from "../logger.js";
7
+ import { verifyMcpToken } from "./auth.js";
8
+ import { TOOLS } from "./registry.js";
9
+ // ---- Bearer auth ----------------------------------------------------------
10
+ // Every /mcp request must carry a per-thread bearer. The token is looked up in
11
+ // mcp_thread_tokens; its project_id is injected into the per-session McpContext
12
+ // used by every tool handler. Tools never read a project_id from the client.
13
+ function extractBearer(req) {
14
+ const h = req.headers["authorization"];
15
+ if (typeof h !== "string")
16
+ return null;
17
+ if (!h.toLowerCase().startsWith("bearer "))
18
+ return null;
19
+ return h.slice(7).trim();
20
+ }
21
+ const sessions = new Map();
22
+ // ponytail: no idle-session reaper. Sessions live for the duration of an
23
+ // opencode run (minutes, not hours); the runner deletes the token on run
24
+ // end, after which auth fails and the session is inert. Add a sweep if a
25
+ // leaked session ever shows up in metrics.
26
+ const SESSION_TIMEOUT_MS = 60 * 60 * 1000;
27
+ function registerToolsOn(server, ctx) {
28
+ for (const tool of TOOLS) {
29
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args) => tool.handler(ctx, args ?? {}));
30
+ }
31
+ }
32
+ async function createSession(ctx) {
33
+ const transport = new StreamableHTTPServerTransport({
34
+ sessionIdGenerator: () => randomUUID(),
35
+ onsessioninitialized: (sessionId) => {
36
+ // sessionId is assigned by the transport during initialize handling
37
+ // (webStandardStreamableHttp.js:433), then this callback fires. Register
38
+ // here — NOT by reading transport.sessionId before handleRequest (that's
39
+ // always undefined at that point, and the old guard threw on every run).
40
+ if (sessions.has(sessionId))
41
+ return;
42
+ sessions.set(sessionId, { transport, server, ctx, lastSeen: Date.now() });
43
+ },
44
+ });
45
+ const server = new McpServer({ name: "devbot-forge", version: "1.0.0" }, { capabilities: { tools: {} } });
46
+ registerToolsOn(server, ctx);
47
+ // ponytail: cast through `as Transport` — the SDK's StreamableHTTPServerTransport
48
+ // declares optional callbacks as `?: () => void` (no `| undefined`) while
49
+ // our tsconfig has `exactOptionalPropertyTypes: true`, which makes the
50
+ // implementation's `(() => void) | undefined` form incompatible with the
51
+ // interface's stricter signature. The runtime shape is identical; this is
52
+ // a type-only mismatch in the SDK's own declarations. Drop the cast if the
53
+ // SDK ever ships `| undefined` on its Transport callbacks.
54
+ await server.connect(transport);
55
+ transport.onclose = () => {
56
+ const sid = transport.sessionId;
57
+ if (sid)
58
+ sessions.delete(sid);
59
+ };
60
+ // Returned before handleRequest runs; onsessioninitialized registers the
61
+ // session during the initialize request the caller is about to send.
62
+ return { transport, server };
63
+ }
64
+ // ---- Router ---------------------------------------------------------------
65
+ export function mcpRouter() {
66
+ const router = Router();
67
+ // Host-header gate: defense-in-depth against DNS rebinding on top of the
68
+ // separate port (127.0.0.1 bind) + bearer auth. Allows host.containers.internal
69
+ // (the default MCP_BASE_URL the container uses via --add-host) alongside the
70
+ // localhost variants.
71
+ router.use(hostHeaderValidation(["localhost", "127.0.0.1", "[::1]", "host.containers.internal"]));
72
+ // Bearer auth runs before session routing — every request (initialize or
73
+ // subsequent) must present a valid token. The McpContext is stashed on the
74
+ // request for the session-creation path.
75
+ router.use((req, res, next) => {
76
+ const token = extractBearer(req);
77
+ if (!token) {
78
+ log.warn("mcp auth: missing bearer");
79
+ res.status(401).json({ error: "unauthorized" });
80
+ return;
81
+ }
82
+ const ctx = verifyMcpToken(token);
83
+ if (!ctx) {
84
+ log.warn("mcp auth: invalid or expired token");
85
+ res.status(401).json({ error: "unauthorized" });
86
+ return;
87
+ }
88
+ req.mcpCtx = ctx;
89
+ next();
90
+ });
91
+ const handle = async (req, res) => {
92
+ try {
93
+ const ctx = req.mcpCtx;
94
+ const sessionId = req.headers["mcp-session-id"];
95
+ // Existing session: validate id + token still maps to the same project.
96
+ if (sessionId) {
97
+ const state = sessions.get(sessionId);
98
+ if (!state) {
99
+ res.status(404).json({ error: "session not found" });
100
+ return;
101
+ }
102
+ if (state.ctx.projectId !== ctx.projectId ||
103
+ state.ctx.workItemId !== ctx.workItemId ||
104
+ state.ctx.workItemNumber !== ctx.workItemNumber) {
105
+ // Token swapped mid-session — reject, don't silently rebind.
106
+ log.warn("mcp session token mismatch", { sessionId });
107
+ res.status(401).json({ error: "unauthorized" });
108
+ return;
109
+ }
110
+ state.lastSeen = Date.now();
111
+ await state.transport.handleRequest(req, res, req.body);
112
+ return;
113
+ }
114
+ // No session id: this must be an initialize. Parse the body to confirm.
115
+ const body = req.body;
116
+ const method = body?.method;
117
+ if (method !== "initialize") {
118
+ res.status(400).json({ error: "missing Mcp-Session-Id header" });
119
+ return;
120
+ }
121
+ const { transport } = await createSession(ctx);
122
+ await transport.handleRequest(req, res, req.body);
123
+ }
124
+ catch (e) {
125
+ log.warn("mcp POST handler error", { err: e instanceof Error ? e.message : String(e) });
126
+ if (!res.headersSent)
127
+ res.status(500).json({ error: "internal error" });
128
+ }
129
+ };
130
+ router.post("/", handle);
131
+ router.get("/", (req, res) => {
132
+ // SSE stream for server-initiated notifications. Requires an existing
133
+ // session (the client opens this after initialize). Stateless clients
134
+ // that don't use notifications get a 405.
135
+ const sessionId = req.headers["mcp-session-id"];
136
+ const state = sessionId ? sessions.get(sessionId) : undefined;
137
+ if (!state) {
138
+ res.status(404).json({ error: "session not found" });
139
+ return;
140
+ }
141
+ state.transport.handleRequest(req, res).catch((e) => {
142
+ log.warn("mcp GET handler error", { err: e instanceof Error ? e.message : String(e) });
143
+ if (!res.headersSent)
144
+ res.status(500).json({ error: "internal error" });
145
+ });
146
+ });
147
+ router.delete("/", (req, res) => {
148
+ const sessionId = req.headers["mcp-session-id"];
149
+ if (sessionId) {
150
+ const state = sessions.get(sessionId);
151
+ if (state) {
152
+ state.transport.close().catch(() => { });
153
+ sessions.delete(sessionId);
154
+ }
155
+ }
156
+ res.status(204).end();
157
+ });
158
+ // ponytail: one-shot idle sweep on each new session creation, not a timer.
159
+ // Cheap (Map iteration over a handful of live sessions) and avoids a
160
+ // long-lived setInterval keeping the process awake.
161
+ setInterval(() => {
162
+ const now = Date.now();
163
+ for (const [id, state] of sessions) {
164
+ if (now - state.lastSeen > SESSION_TIMEOUT_MS) {
165
+ log.debug("mcp session idle timeout", { sessionId: id });
166
+ state.transport.close().catch(() => { });
167
+ sessions.delete(id);
168
+ }
169
+ }
170
+ }, 15 * 60 * 1000).unref();
171
+ return router;
172
+ }
173
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}