@wascartech/devbot 0.0.0-stage → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +638 -2
  3. package/dist/access-grant-store.js +99 -0
  4. package/dist/access-grant-store.js.map +1 -0
  5. package/dist/access-resolve.js +11 -0
  6. package/dist/access-resolve.js.map +1 -0
  7. package/dist/access-rule-store.js +99 -0
  8. package/dist/access-rule-store.js.map +1 -0
  9. package/dist/admin/access-routes.js +73 -0
  10. package/dist/admin/access-routes.js.map +1 -0
  11. package/dist/admin/access-rule-routes.js +107 -0
  12. package/dist/admin/access-rule-routes.js.map +1 -0
  13. package/dist/admin/custom-repos-routes.js +93 -0
  14. package/dist/admin/custom-repos-routes.js.map +1 -0
  15. package/dist/admin/env-routes.js +74 -0
  16. package/dist/admin/env-routes.js.map +1 -0
  17. package/dist/admin/global-settings-routes.js +254 -0
  18. package/dist/admin/global-settings-routes.js.map +1 -0
  19. package/dist/admin/mcp-cleanup.js +12 -0
  20. package/dist/admin/mcp-cleanup.js.map +1 -0
  21. package/dist/admin/mcp-routes.js +169 -0
  22. package/dist/admin/mcp-routes.js.map +1 -0
  23. package/dist/admin/query-params.js +80 -0
  24. package/dist/admin/query-params.js.map +1 -0
  25. package/dist/admin/routes.js +150 -0
  26. package/dist/admin/routes.js.map +1 -0
  27. package/dist/admin/run-history-routes.js +44 -0
  28. package/dist/admin/run-history-routes.js.map +1 -0
  29. package/dist/admin/run-routes.js +99 -0
  30. package/dist/admin/run-routes.js.map +1 -0
  31. package/dist/admin/settings-routes.js +119 -0
  32. package/dist/admin/settings-routes.js.map +1 -0
  33. package/dist/admin/stats-routes.js +51 -0
  34. package/dist/admin/stats-routes.js.map +1 -0
  35. package/dist/admin/task-routes.js +213 -0
  36. package/dist/admin/task-routes.js.map +1 -0
  37. package/dist/admin/test-routes.js +38 -0
  38. package/dist/admin/test-routes.js.map +1 -0
  39. package/dist/admin/volume-routes.js +82 -0
  40. package/dist/admin/volume-routes.js.map +1 -0
  41. package/dist/admin/webhook-routes.js +174 -0
  42. package/dist/admin/webhook-routes.js.map +1 -0
  43. package/dist/agents.js +48 -0
  44. package/dist/agents.js.map +1 -0
  45. package/dist/assignee-value.js +6 -0
  46. package/dist/assignee-value.js.map +1 -0
  47. package/dist/attachments.js +76 -0
  48. package/dist/attachments.js.map +1 -0
  49. package/dist/auto-commands.js +88 -0
  50. package/dist/auto-commands.js.map +1 -0
  51. package/dist/auto-features.js +32 -0
  52. package/dist/auto-features.js.map +1 -0
  53. package/dist/base-branch-store.js +40 -0
  54. package/dist/base-branch-store.js.map +1 -0
  55. package/dist/base-branches.js +9 -0
  56. package/dist/base-branches.js.map +1 -0
  57. package/dist/build-workflow.js +116 -0
  58. package/dist/build-workflow.js.map +1 -0
  59. package/dist/change-request-lifecycle.js +16 -0
  60. package/dist/change-request-lifecycle.js.map +1 -0
  61. package/dist/change-request-store.js +72 -0
  62. package/dist/change-request-store.js.map +1 -0
  63. package/dist/ci-failure.js +80 -0
  64. package/dist/ci-failure.js.map +1 -0
  65. package/dist/cli.js +3 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/config.js +17 -0
  68. package/dist/config.js.map +1 -0
  69. package/dist/conflict-fix.js +178 -0
  70. package/dist/conflict-fix.js.map +1 -0
  71. package/dist/cookie.js +70 -0
  72. package/dist/cookie.js.map +1 -0
  73. package/dist/crypto-util.js +40 -0
  74. package/dist/crypto-util.js.map +1 -0
  75. package/dist/custom-repos-store.js +90 -0
  76. package/dist/custom-repos-store.js.map +1 -0
  77. package/dist/custom-repos-sync.js +118 -0
  78. package/dist/custom-repos-sync.js.map +1 -0
  79. package/dist/db.js +12 -0
  80. package/dist/db.js.map +1 -0
  81. package/dist/devbot-scripts.js +18 -0
  82. package/dist/devbot-scripts.js.map +1 -0
  83. package/dist/devcontainer.js +141 -0
  84. package/dist/devcontainer.js.map +1 -0
  85. package/dist/env-config-store.js +132 -0
  86. package/dist/env-config-store.js.map +1 -0
  87. package/dist/error-log-store.js +193 -0
  88. package/dist/error-log-store.js.map +1 -0
  89. package/dist/git.js +605 -0
  90. package/dist/git.js.map +1 -0
  91. package/dist/global-queue.js +127 -0
  92. package/dist/global-queue.js.map +1 -0
  93. package/dist/global-settings-store.js +307 -0
  94. package/dist/global-settings-store.js.map +1 -0
  95. package/dist/help.js +41 -0
  96. package/dist/help.js.map +1 -0
  97. package/dist/http-csp.js +15 -0
  98. package/dist/http-csp.js.map +1 -0
  99. package/dist/http-util.js +40 -0
  100. package/dist/http-util.js.map +1 -0
  101. package/dist/idempotency.js +50 -0
  102. package/dist/idempotency.js.map +1 -0
  103. package/dist/lifecycle.js +234 -0
  104. package/dist/lifecycle.js.map +1 -0
  105. package/dist/logger.js +33 -0
  106. package/dist/logger.js.map +1 -0
  107. package/dist/lsp-servers.js +81 -0
  108. package/dist/lsp-servers.js.map +1 -0
  109. package/dist/mcp/auth.js +62 -0
  110. package/dist/mcp/auth.js.map +1 -0
  111. package/dist/mcp/effective-mcp.js +42 -0
  112. package/dist/mcp/effective-mcp.js.map +1 -0
  113. package/dist/mcp/oauth-refresh.js +138 -0
  114. package/dist/mcp/oauth-refresh.js.map +1 -0
  115. package/dist/mcp/peer-sync.js +109 -0
  116. package/dist/mcp/peer-sync.js.map +1 -0
  117. package/dist/mcp/registry.js +54 -0
  118. package/dist/mcp/registry.js.map +1 -0
  119. package/dist/mcp/runner-bridge.js +93 -0
  120. package/dist/mcp/runner-bridge.js.map +1 -0
  121. package/dist/mcp/server.js +173 -0
  122. package/dist/mcp/server.js.map +1 -0
  123. package/dist/mcp/tools/_helpers.js +88 -0
  124. package/dist/mcp/tools/_helpers.js.map +1 -0
  125. package/dist/mcp/tools/attachments.js +117 -0
  126. package/dist/mcp/tools/attachments.js.map +1 -0
  127. package/dist/mcp/tools/cicd.js +105 -0
  128. package/dist/mcp/tools/cicd.js.map +1 -0
  129. package/dist/mcp/tools/discovery.js +34 -0
  130. package/dist/mcp/tools/discovery.js.map +1 -0
  131. package/dist/mcp/tools/issues.js +56 -0
  132. package/dist/mcp/tools/issues.js.map +1 -0
  133. package/dist/mcp/tools/merge_requests.js +67 -0
  134. package/dist/mcp/tools/merge_requests.js.map +1 -0
  135. package/dist/mcp/tools/metadata.js +127 -0
  136. package/dist/mcp/tools/metadata.js.map +1 -0
  137. package/dist/mcp/tools/peer_sync.js +43 -0
  138. package/dist/mcp/tools/peer_sync.js.map +1 -0
  139. package/dist/mcp/tools/repository.js +178 -0
  140. package/dist/mcp/tools/repository.js.map +1 -0
  141. package/dist/mcp/tools/work_tracking.js +125 -0
  142. package/dist/mcp/tools/work_tracking.js.map +1 -0
  143. package/dist/mcp/tools/workitems.js +30 -0
  144. package/dist/mcp/tools/workitems.js.map +1 -0
  145. package/dist/mcp/volume-files.js +187 -0
  146. package/dist/mcp/volume-files.js.map +1 -0
  147. package/dist/mcp-auth-store.js +134 -0
  148. package/dist/mcp-auth-store.js.map +1 -0
  149. package/dist/mcp-config-store.js +83 -0
  150. package/dist/mcp-config-store.js.map +1 -0
  151. package/dist/mention.js +512 -0
  152. package/dist/mention.js.map +1 -0
  153. package/dist/migrate.js +73 -0
  154. package/dist/migrate.js.map +1 -0
  155. package/dist/mr-assignee.js +71 -0
  156. package/dist/mr-assignee.js.map +1 -0
  157. package/dist/mr-content.js +90 -0
  158. package/dist/mr-content.js.map +1 -0
  159. package/dist/oauth-routes.js +54 -0
  160. package/dist/oauth-routes.js.map +1 -0
  161. package/dist/onboarding-allowlist.js +32 -0
  162. package/dist/onboarding-allowlist.js.map +1 -0
  163. package/dist/opencode.js +291 -0
  164. package/dist/opencode.js.map +1 -0
  165. package/dist/outbound-webhook-store.js +229 -0
  166. package/dist/outbound-webhook-store.js.map +1 -0
  167. package/dist/outbound-webhook.js +314 -0
  168. package/dist/outbound-webhook.js.map +1 -0
  169. package/dist/path-sandbox.js +200 -0
  170. package/dist/path-sandbox.js.map +1 -0
  171. package/dist/platform/git-auth.js +34 -0
  172. package/dist/platform/git-auth.js.map +1 -0
  173. package/dist/platform/gitealike/attachments.js +62 -0
  174. package/dist/platform/gitealike/attachments.js.map +1 -0
  175. package/dist/platform/gitealike/base.js +935 -0
  176. package/dist/platform/gitealike/base.js.map +1 -0
  177. package/dist/platform/gitealike/forgejo.js +16 -0
  178. package/dist/platform/gitealike/forgejo.js.map +1 -0
  179. package/dist/platform/gitealike/gitea.js +16 -0
  180. package/dist/platform/gitealike/gitea.js.map +1 -0
  181. package/dist/platform/gitealike/http.js +152 -0
  182. package/dist/platform/gitealike/http.js.map +1 -0
  183. package/dist/platform/gitealike/schemas.js +284 -0
  184. package/dist/platform/gitealike/schemas.js.map +1 -0
  185. package/dist/platform/github/attachments.js +52 -0
  186. package/dist/platform/github/attachments.js.map +1 -0
  187. package/dist/platform/github/http.js +153 -0
  188. package/dist/platform/github/http.js.map +1 -0
  189. package/dist/platform/github/index.js +936 -0
  190. package/dist/platform/github/index.js.map +1 -0
  191. package/dist/platform/github/schemas.js +308 -0
  192. package/dist/platform/github/schemas.js.map +1 -0
  193. package/dist/platform/github/zip.js +85 -0
  194. package/dist/platform/github/zip.js.map +1 -0
  195. package/dist/platform/gitlab/admin.js +105 -0
  196. package/dist/platform/gitlab/admin.js.map +1 -0
  197. package/dist/platform/gitlab/api.js +229 -0
  198. package/dist/platform/gitlab/api.js.map +1 -0
  199. package/dist/platform/gitlab/attachments.js +79 -0
  200. package/dist/platform/gitlab/attachments.js.map +1 -0
  201. package/dist/platform/gitlab/http.js +165 -0
  202. package/dist/platform/gitlab/http.js.map +1 -0
  203. package/dist/platform/gitlab/index.js +630 -0
  204. package/dist/platform/gitlab/index.js.map +1 -0
  205. package/dist/platform/gitlab/oauth.js +58 -0
  206. package/dist/platform/gitlab/oauth.js.map +1 -0
  207. package/dist/platform/gitlab/read.js +182 -0
  208. package/dist/platform/gitlab/read.js.map +1 -0
  209. package/dist/platform/gitlab/schemas-read.js +274 -0
  210. package/dist/platform/gitlab/schemas-read.js.map +1 -0
  211. package/dist/platform/gitlab/schemas.js +251 -0
  212. package/dist/platform/gitlab/schemas.js.map +1 -0
  213. package/dist/platform/gitlab/signature.js +52 -0
  214. package/dist/platform/gitlab/signature.js.map +1 -0
  215. package/dist/platform/index.js +38 -0
  216. package/dist/platform/index.js.map +1 -0
  217. package/dist/platform/port.js +26 -0
  218. package/dist/platform/port.js.map +1 -0
  219. package/dist/platform/types.js +80 -0
  220. package/dist/platform/types.js.map +1 -0
  221. package/dist/podman.js +119 -0
  222. package/dist/podman.js.map +1 -0
  223. package/dist/project-sync.js +69 -0
  224. package/dist/project-sync.js.map +1 -0
  225. package/dist/prompt.js +47 -0
  226. package/dist/prompt.js.map +1 -0
  227. package/dist/queue-store.js +236 -0
  228. package/dist/queue-store.js.map +1 -0
  229. package/dist/queue.js +129 -0
  230. package/dist/queue.js.map +1 -0
  231. package/dist/redact.js +23 -0
  232. package/dist/redact.js.map +1 -0
  233. package/dist/run-files.js +297 -0
  234. package/dist/run-files.js.map +1 -0
  235. package/dist/run-filters.js +54 -0
  236. package/dist/run-filters.js.map +1 -0
  237. package/dist/run-history-store.js +229 -0
  238. package/dist/run-history-store.js.map +1 -0
  239. package/dist/run-log-sink.js +270 -0
  240. package/dist/run-log-sink.js.map +1 -0
  241. package/dist/run-log.js +97 -0
  242. package/dist/run-log.js.map +1 -0
  243. package/dist/run-usage.js +29 -0
  244. package/dist/run-usage.js.map +1 -0
  245. package/dist/run-view.js +112 -0
  246. package/dist/run-view.js.map +1 -0
  247. package/dist/runner.js +1094 -0
  248. package/dist/runner.js.map +1 -0
  249. package/dist/runtime-apply.js +26 -0
  250. package/dist/runtime-apply.js.map +1 -0
  251. package/dist/runtime-settings.js +71 -0
  252. package/dist/runtime-settings.js.map +1 -0
  253. package/dist/runtime-state.js +19 -0
  254. package/dist/runtime-state.js.map +1 -0
  255. package/dist/server.js +119 -0
  256. package/dist/server.js.map +1 -0
  257. package/dist/settings-store.js +320 -0
  258. package/dist/settings-store.js.map +1 -0
  259. package/dist/status.js +121 -0
  260. package/dist/status.js.map +1 -0
  261. package/dist/stop.js +56 -0
  262. package/dist/stop.js.map +1 -0
  263. package/dist/sweeps.js +87 -0
  264. package/dist/sweeps.js.map +1 -0
  265. package/dist/task-log.js +279 -0
  266. package/dist/task-log.js.map +1 -0
  267. package/dist/task-view.js +62 -0
  268. package/dist/task-view.js.map +1 -0
  269. package/dist/token-store.js +68 -0
  270. package/dist/token-store.js.map +1 -0
  271. package/dist/types/access.js +74 -0
  272. package/dist/types/access.js.map +1 -0
  273. package/dist/types/custom-repos.js +28 -0
  274. package/dist/types/custom-repos.js.map +1 -0
  275. package/dist/types/env-config.js +25 -0
  276. package/dist/types/env-config.js.map +1 -0
  277. package/dist/types/env.js +190 -0
  278. package/dist/types/env.js.map +1 -0
  279. package/dist/types/global-settings.js +178 -0
  280. package/dist/types/global-settings.js.map +1 -0
  281. package/dist/types/lsp-servers.js +145 -0
  282. package/dist/types/lsp-servers.js.map +1 -0
  283. package/dist/types/mcp-config.js +77 -0
  284. package/dist/types/mcp-config.js.map +1 -0
  285. package/dist/types/opencode.js +59 -0
  286. package/dist/types/opencode.js.map +1 -0
  287. package/dist/types/outbound-webhook.js +56 -0
  288. package/dist/types/outbound-webhook.js.map +1 -0
  289. package/dist/types/project-settings.js +114 -0
  290. package/dist/types/project-settings.js.map +1 -0
  291. package/dist/types/provider-registry.js +37 -0
  292. package/dist/types/provider-registry.js.map +1 -0
  293. package/dist/types/runtime-settings.js +78 -0
  294. package/dist/types/runtime-settings.js.map +1 -0
  295. package/dist/usage-stats.js +159 -0
  296. package/dist/usage-stats.js.map +1 -0
  297. package/dist/version.js +19 -0
  298. package/dist/version.js.map +1 -0
  299. package/dist/volume-gc.js +193 -0
  300. package/dist/volume-gc.js.map +1 -0
  301. package/dist/volume-manage.js +191 -0
  302. package/dist/volume-manage.js.map +1 -0
  303. package/dist/volume-store.js +122 -0
  304. package/dist/volume-store.js.map +1 -0
  305. package/dist/webhook.js +1106 -0
  306. package/dist/webhook.js.map +1 -0
  307. package/dist/work-item-auto-store.js +75 -0
  308. package/dist/work-item-auto-store.js.map +1 -0
  309. package/dist/work-item-branch-store.js +37 -0
  310. package/dist/work-item-branch-store.js.map +1 -0
  311. package/migrations/0001_initial.sql +59 -0
  312. package/migrations/0002_queue_status.sql +15 -0
  313. package/migrations/0003_work_item_base_branch.sql +14 -0
  314. package/migrations/0004_project_settings.sql +15 -0
  315. package/migrations/0005_global_settings.sql +34 -0
  316. package/migrations/0006_model_variants.sql +8 -0
  317. package/migrations/0007_container_error_logs.sql +21 -0
  318. package/migrations/0008_project_access_grants.sql +16 -0
  319. package/migrations/0009_drop_imported_legacy.sql +4 -0
  320. package/migrations/0010_container_error_log_model.sql +6 -0
  321. package/migrations/0011_container_error_log_title.sql +5 -0
  322. package/migrations/0012_pending_task_cancel.sql +6 -0
  323. package/migrations/0013_builder_image.sql +6 -0
  324. package/migrations/0014_build_workflow.sql +7 -0
  325. package/migrations/0015_access_rules.sql +24 -0
  326. package/migrations/0016_trigger_policy.sql +17 -0
  327. package/migrations/0017_attachment_upload.sql +8 -0
  328. package/migrations/0018_work_item_branch.sql +11 -0
  329. package/migrations/0019_auto_push_mr.sql +18 -0
  330. package/migrations/0020_outbound_webhooks.sql +47 -0
  331. package/migrations/0021_runtime_settings.sql +12 -0
  332. package/migrations/0022_auto_resolve_conflicts.sql +17 -0
  333. package/migrations/0023_ci_fail_auto_report.sql +27 -0
  334. package/migrations/0024_work_item_auto_override.sql +22 -0
  335. package/migrations/0025_run_history.sql +37 -0
  336. package/migrations/0026_run_history_task_seq.sql +6 -0
  337. package/migrations/0027_custom_repos.sql +11 -0
  338. package/migrations/0028_run_usage.sql +14 -0
  339. package/migrations/0029_drop_ocr_langs.sql +6 -0
  340. package/migrations/0030_mr_assignee.sql +9 -0
  341. package/migrations/0031_lsp_servers.sql +14 -0
  342. package/opencode-config/managed/devbot-agent-policy.md +61 -0
  343. package/opencode-config/managed/opencode.json +77 -0
  344. package/opencode-config/opencode.json +211 -0
  345. package/opencode-config/plugins/devbot-path-guard.js +224 -0
  346. package/package.json +157 -4
  347. package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
  348. package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
  349. package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
  350. package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
  351. package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
  352. package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
  353. package/public/assets/PageGrid-CBROIU87.js +1 -0
  354. package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
  355. package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
  356. package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
  357. package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
  358. package/public/assets/RunsPage-CRnnXU4P.js +1 -0
  359. package/public/assets/Select-Bvx32Diz.js +1 -0
  360. package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
  361. package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
  362. package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
  363. package/public/assets/UsagePage-BLMDEsv-.js +332 -0
  364. package/public/assets/UsagePage-DodEeAjB.css +1 -0
  365. package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
  366. package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
  367. package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
  368. package/public/assets/index-BMbMibyb.js +4 -0
  369. package/public/assets/index-C5RBBrPj.js +30 -0
  370. package/public/assets/index-CgX29UgC.css +1 -0
  371. package/public/assets/usage-format-jsb31_O8.js +1 -0
  372. package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
  373. package/public/assets/useRouteTab-DfwrcMic.js +1 -0
  374. package/public/favicon.ico +0 -0
  375. package/public/favicon.png +0 -0
  376. package/public/index.html +15 -0
@@ -0,0 +1,178 @@
1
+ import { shq } from "./redact.js";
2
+ // Pure conflict-fix helpers (work item #56): the git shell-script builders,
3
+ // their result parsers, the resolver prompt/result parser, and the note
4
+ // formatters. Deliberately dependency-light (no config/podman) so the contract
5
+ // is unit-testable without a container. The podman wrappers live in git.ts,
6
+ // which imports these one-way; this module must never import git.ts.
7
+ // ---- Conflict check --------------------------------------------------------
8
+ // Host-side conflict check. Order is load-bearing:
9
+ // 1. abort any half-finished rebase/merge a prior killed resolver left behind
10
+ // (a /stop or timeout mid-rebase); `|| true` so a clean tree is a no-op.
11
+ // 2. fetch origin so the target ref is current.
12
+ // 3. record the remote branch tip (TIP:<sha>) so the publish can pin its
13
+ // force-with-lease to the exact commit this run started from.
14
+ // 4. verify origin/<target> exists (a deleted target is an actionable failure,
15
+ // not an empty merge-tree success).
16
+ // 5. merge-tree --write-tree --name-only origin/<target> HEAD: exit 0 = clean,
17
+ // exit 1 = conflicts printed on stdout after the tree OID line.
18
+ export function conflictCheckScript(target, branch, extraHeader) {
19
+ return [
20
+ "cd /workspace",
21
+ "git rebase --abort 2>/dev/null || true",
22
+ "git merge --abort 2>/dev/null || true",
23
+ `${extraHeader} fetch origin`,
24
+ `echo "TIP:$(git rev-parse origin/${shq(branch)} 2>/dev/null || true)"`,
25
+ `git rev-parse --verify --quiet origin/${shq(target)} >/dev/null || { echo "CHECK_FAILED:missing-target"; exit 8; }`,
26
+ `git merge-tree --write-tree --name-only origin/${shq(target)} ${shq(branch)}`,
27
+ ].join(" && ");
28
+ }
29
+ // Parse a conflict-check result. exit 0 = clean. exit 1 = merge-tree reported
30
+ // conflicts: stdout is the tree OID, then one path per line, then a blank line
31
+ // and human messages. Any other exit (8 missing target, fetch failure) → null.
32
+ export function parseConflictCheckResult(code, stdout) {
33
+ if (code === 0)
34
+ return { conflictFree: true, files: [] };
35
+ if (code !== 1)
36
+ return null;
37
+ // Strip the `TIP:` line the script prints before merge-tree, then skip the
38
+ // first remaining non-empty line (the merged-tree OID) and read paths up to
39
+ // the blank separator.
40
+ const lines = stdout.split("\n").filter((l) => !l.startsWith("TIP:"));
41
+ const files = [];
42
+ let sawOid = false;
43
+ for (const line of lines) {
44
+ if (line === "")
45
+ break;
46
+ if (!sawOid) {
47
+ sawOid = true;
48
+ continue;
49
+ }
50
+ if (line.trim() !== "")
51
+ files.push(line.trim());
52
+ }
53
+ return { conflictFree: false, files };
54
+ }
55
+ // ---- Rewrite-aware publish -------------------------------------------------
56
+ // Compose the host-side publish script. Order is load-bearing:
57
+ // 1. HEAD guard (exit 7 WRONG_HEAD).
58
+ // 2. fallback commit for uncommitted agent work — but refuse if the staged
59
+ // diff carries conflict markers (exit 10 CONFLICT_MARKERS), so a killed
60
+ // resolver can't commit `<<<<<<<` into the branch.
61
+ // 3. fetch origin.
62
+ // 4. decide:
63
+ // unborn remote → plain push.
64
+ // origin is ancestor → ahead ? plain push : NO_COMMITS.
65
+ // origin == preRunTip → --force-with-lease pinned to preRunTip
66
+ // (we only rewrote our own earlier commits).
67
+ // else (remote moved) → REMOTE_MOVED, exit 9 (fail closed, no clobber).
68
+ // `force` (/force-push) short-circuits to an unconditional leased push.
69
+ export function publishScript(workItemNumber, branch, extraHeader, preRunTip, force = false) {
70
+ const b = shq(branch);
71
+ const lease = `--force-with-lease=refs/heads/${branch}:${preRunTip}`;
72
+ const markerCheck = `if git diff --cached -U0 | grep -qE '^\\+.*(<<<<<<<|=======|>>>>>>>)'; then echo "CONFLICT_MARKERS"; exit 10; fi`;
73
+ const decide = force
74
+ ? `if ! git rev-parse --verify --quiet origin/${b} >/dev/null; then ${extraHeader} push origin ${b} && echo PUSHED; else ${extraHeader} push origin ${b} --force-with-lease && echo FORCE_PUSHED; fi`
75
+ : [
76
+ `if ! git rev-parse --verify --quiet origin/${b} >/dev/null; then`,
77
+ ` ${extraHeader} push origin ${b} && echo PUSHED;`,
78
+ `elif git merge-base --is-ancestor origin/${b} HEAD; then`,
79
+ ` if test -n "$(git log --format=%H origin/${b}..HEAD)"; then ${extraHeader} push origin ${b} && echo PUSHED; else echo NO_COMMITS; fi;`,
80
+ `elif test "$(git rev-parse origin/${b})" = ${shq(preRunTip)}; then`,
81
+ ` ${extraHeader} push origin ${b} ${lease} && echo FORCE_PUSHED;`,
82
+ `else echo REMOTE_MOVED; exit 9; fi`,
83
+ ].join("\n");
84
+ return [
85
+ "cd /workspace",
86
+ `test "$(git rev-parse --abbrev-ref HEAD)" = ${b} || { echo "WRONG_HEAD:$(git rev-parse --abbrev-ref HEAD)"; exit 7; }`,
87
+ `if test -n "$(git status --porcelain)"; then git add -A && { ${markerCheck}; } && git commit -m "chore: fallback commit for uncommitted agent work (#${workItemNumber})" --no-verify && echo FALLBACK_COMMITTED; fi`,
88
+ `${extraHeader} fetch origin`,
89
+ decide,
90
+ ].join(" && ");
91
+ }
92
+ export function parsePublishResult(stdout) {
93
+ return {
94
+ pushed: stdout.includes("PUSHED") || stdout.includes("FORCE_PUSHED"),
95
+ forced: stdout.includes("FORCE_PUSHED"),
96
+ noCommits: stdout.includes("NO_COMMITS"),
97
+ fallbackCommitted: stdout.includes("FALLBACK_COMMITTED"),
98
+ remoteMoved: stdout.includes("REMOTE_MOVED"),
99
+ rewritten: stdout.includes("REWRITTEN"),
100
+ };
101
+ }
102
+ // ---- Resolver --------------------------------------------------------------
103
+ // The direct resolver prompt. Rebase-first, merge fallback; never push or switch
104
+ // branches (DevBot owns the push); print a machine-readable terminal marker.
105
+ export function resolverPrompt(branch, target) {
106
+ return [
107
+ `The branch \`${branch}\` conflicts with its merge target \`origin/${target}\`.`,
108
+ "",
109
+ "Resolve the conflict so the branch merges cleanly. Do exactly this:",
110
+ "",
111
+ `1. DevBot has already fetched origin for this run, so \`origin/${target}\` is current — do not fetch yourself.`,
112
+ `2. Rebase the work branch onto the target: \`git rebase origin/${target}\`.`,
113
+ "3. Resolve every conflict, stage the files, and let the rebase finish.",
114
+ `4. If a clean rebase is not viable (e.g. an irresolvable or binary conflict), run \`git rebase --abort\`, then \`git merge origin/${target}\`, resolve and commit the merge.`,
115
+ "5. Keep the resolved tree building where you reasonably can, but do not run long builds.",
116
+ "",
117
+ "Hard rules:",
118
+ `- Work only on \`${branch}\` — never switch branches.`,
119
+ "- **Do not push or fetch**; DevBot does both host-side. DevBot pushes after you finish.",
120
+ "- Do not amend or rewrite commits beyond what rebase requires.",
121
+ "",
122
+ "When done, end your reply with exactly one marker:",
123
+ `- \`RESOLVED:rebase\` or \`RESOLVED:merge\` on success, or`,
124
+ `- \`UNRESOLVED: <comma-separated file paths>\` if you could not resolve it.`,
125
+ ].join("\n");
126
+ }
127
+ export function parseResolverResult(text) {
128
+ const resolved = text.match(/RESOLVED:(rebase|merge)/);
129
+ if (resolved)
130
+ return { resolved: true, strategy: resolved[1], files: [] };
131
+ const unresolved = text.match(/UNRESOLVED:\s*(.*)/);
132
+ const files = unresolved && unresolved[1]
133
+ ? [...new Set(unresolved[1].split(",").map((s) => s.trim()).filter(Boolean))]
134
+ : [];
135
+ return { resolved: false, strategy: "", files };
136
+ }
137
+ // ---- Notes -----------------------------------------------------------------
138
+ // Sanitize a repo-controlled path before wrapping it in an inline-code span: a
139
+ // stray backtick would break the Markdown and could inject a mention.
140
+ function sanitizePath(p) {
141
+ return p.replace(/[`\r\n]/g, "").trim();
142
+ }
143
+ export const CONFLICT_FILES_CAP = 10;
144
+ export function formatConflictFixNote(target, strategy) {
145
+ return `🔧 Auto-resolved merge conflicts with \`${target}\` (${strategy}).`;
146
+ }
147
+ export function formatUnresolvedNote(target, files) {
148
+ const shown = files.slice(0, CONFLICT_FILES_CAP).map((f) => `\`${sanitizePath(f)}\``);
149
+ const overflow = files.length - shown.length;
150
+ const list = shown.length > 0
151
+ ? shown.join(", ") + (overflow > 0 ? ` and ${overflow} more` : "")
152
+ : "the target branch";
153
+ return (`⚠️ Could not auto-resolve conflicts with \`${target}\`: ${list}. ` +
154
+ `The branch was pushed without the merge request. Resolve the conflicts manually and re-run \`/mr\`, ` +
155
+ `or disable conflict auto-fix in the project settings.`);
156
+ }
157
+ // Fix-only note for the standalone /fix-conflicts task: no publish happened, so
158
+ // the branch is untouched when resolution fails. Never mentions an MR step.
159
+ export function formatFixOnlyUnresolvedNote(target, files) {
160
+ const shown = files.slice(0, CONFLICT_FILES_CAP).map((f) => `\`${sanitizePath(f)}\``);
161
+ const overflow = files.length - shown.length;
162
+ const list = shown.length > 0
163
+ ? shown.join(", ") + (overflow > 0 ? ` and ${overflow} more` : "")
164
+ : "the target branch";
165
+ return (`⚠️ Could not fix conflicts with \`${target}\`: ${list}. ` +
166
+ `The branch was left unchanged. Resolve the conflicts manually, then re-run \`/fix-conflicts\`.`);
167
+ }
168
+ // Fix-only note when the branch already merges cleanly — nothing to do.
169
+ export function formatNoConflictsNote(target) {
170
+ return `ℹ️ No merge conflicts with \`${target}\` — nothing to fix.`;
171
+ }
172
+ // Extract the `TIP:<sha>` line a script echoes. null when the ref is unborn or
173
+ // the marker is absent (the publish then treats the remote as unborn).
174
+ export function parseBranchTip(stdout) {
175
+ const m = stdout.match(/TIP:([0-9a-f]{7,40})/);
176
+ return m ? m[1] : null;
177
+ }
178
+ //# sourceMappingURL=conflict-fix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-fix.js","sourceRoot":"","sources":["../src/conflict-fix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,4EAA4E;AAC5E,0EAA0E;AAC1E,iFAAiF;AACjF,8EAA8E;AAC9E,uEAAuE;AAEvE,+EAA+E;AAE/E,mDAAmD;AACnD,gFAAgF;AAChF,8EAA8E;AAC9E,kDAAkD;AAClD,2EAA2E;AAC3E,mEAAmE;AACnE,iFAAiF;AACjF,yCAAyC;AACzC,iFAAiF;AACjF,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,MAAc,EAAE,WAAmB;IACrF,OAAO;QACL,eAAe;QACf,wCAAwC;QACxC,uCAAuC;QACvC,GAAG,WAAW,eAAe;QAC7B,oCAAoC,GAAG,CAAC,MAAM,CAAC,wBAAwB;QACvE,yCAAyC,GAAG,CAAC,MAAM,CAAC,gEAAgE;QACpH,kDAAkD,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE;KAC/E,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAOD,8EAA8E;AAC9E,iFAAiF;AACjF,iFAAiF;AACjF,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAE,MAAc;IACnE,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACzD,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,2EAA2E;IAC3E,4EAA4E;IAC5E,uBAAuB;IACvB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACtE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,EAAE;YAAE,MAAM;QACvB,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACxC,CAAC;AAED,+EAA+E;AAE/E,+DAA+D;AAC/D,uCAAuC;AACvC,6EAA6E;AAC7E,6EAA6E;AAC7E,wDAAwD;AACxD,qBAAqB;AACrB,eAAe;AACf,4CAA4C;AAC5C,iEAAiE;AACjE,uEAAuE;AACvE,2EAA2E;AAC3E,gFAAgF;AAChF,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,cAAsB,EAAE,MAAc,EAAE,WAAmB,EAAE,SAAiB,EAAE,KAAK,GAAG,KAAK;IACzH,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,iCAAiC,MAAM,IAAI,SAAS,EAAE,CAAC;IACrE,MAAM,WAAW,GACf,kHAAkH,CAAC;IACrH,MAAM,MAAM,GAAG,KAAK;QAClB,CAAC,CAAC,8CAA8C,CAAC,qBAAqB,WAAW,gBAAgB,CAAC,yBAAyB,WAAW,gBAAgB,CAAC,8CAA8C;QACrM,CAAC,CAAC;YACE,8CAA8C,CAAC,mBAAmB;YAClE,KAAK,WAAW,gBAAgB,CAAC,kBAAkB;YACnD,4CAA4C,CAAC,aAAa;YAC1D,8CAA8C,CAAC,kBAAkB,WAAW,gBAAgB,CAAC,4CAA4C;YACzI,qCAAqC,CAAC,QAAQ,GAAG,CAAC,SAAS,CAAC,QAAQ;YACpE,KAAK,WAAW,gBAAgB,CAAC,IAAI,KAAK,wBAAwB;YAClE,oCAAoC;SACrC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO;QACL,eAAe;QACf,+CAA+C,CAAC,uEAAuE;QACvH,gEAAgE,WAAW,6EAA6E,cAAc,+CAA+C;QACrN,GAAG,WAAW,eAAe;QAC7B,MAAM;KACP,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAWD,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;QACpE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;QACvC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;QACxC,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QACxD,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC5C,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;KACxC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,iFAAiF;AACjF,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IAC3D,OAAO;QACL,gBAAgB,MAAM,+CAA+C,MAAM,KAAK;QAChF,EAAE;QACF,qEAAqE;QACrE,EAAE;QACF,kEAAkE,MAAM,wCAAwC;QAChH,kEAAkE,MAAM,KAAK;QAC7E,wEAAwE;QACxE,qIAAqI,MAAM,mCAAmC;QAC9K,0FAA0F;QAC1F,EAAE;QACF,aAAa;QACb,oBAAoB,MAAM,6BAA6B;QACvD,yFAAyF;QACzF,gEAAgE;QAChE,EAAE;QACF,oDAAoD;QACpD,4DAA4D;QAC5D,6EAA6E;KAC9E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAQD,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACvD,IAAI,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC3E,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC;AAED,+EAA+E;AAE/E,+EAA+E;AAC/E,wEAAwE;AACxE,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,QAAgB;IACpE,OAAO,2CAA2C,MAAM,OAAO,QAAQ,IAAI,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,KAAe;IAClE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;QAC3B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,QAAQ,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,CAAC,CAAC,mBAAmB,CAAC;IACxB,OAAO,CACL,8CAA8C,MAAM,OAAO,IAAI,IAAI;QACnE,sGAAsG;QACtG,uDAAuD,CACxD,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,8EAA8E;AAC9E,MAAM,UAAU,2BAA2B,CAAC,MAAc,EAAE,KAAe;IACzE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;QAC3B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,QAAQ,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,CAAC,CAAC,mBAAmB,CAAC;IACxB,OAAO,CACL,qCAAqC,MAAM,OAAO,IAAI,IAAI;QAC1D,gGAAgG,CACjG,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,OAAO,gCAAgC,MAAM,sBAAsB,CAAC;AACtE,CAAC;AAED,+EAA+E;AAC/E,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC/C,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1B,CAAC"}
package/dist/cookie.js ADDED
@@ -0,0 +1,70 @@
1
+ import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from "node:crypto";
2
+ import { config } from "./config.js";
3
+ // ponytail: fixed scrypt salt. Fine for this single-instance bot — the key
4
+ // derivation is deterministic so cookies survive a restart. Upgrade path:
5
+ // per-deployment random salt stored alongside the db, if multi-instance.
6
+ const KEY = scryptSync(config.SESSION_KEY, "devbot-session-salt", 32);
7
+ const ALGO = "aes-256-gcm";
8
+ export function signCookie(payload) {
9
+ const iv = randomBytes(12);
10
+ const cipher = createCipheriv(ALGO, KEY, iv);
11
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
12
+ const enc = Buffer.concat([cipher.update(plaintext), cipher.final()]);
13
+ const tag = cipher.getAuthTag();
14
+ return Buffer.concat([iv, tag, enc]).toString("base64");
15
+ }
16
+ export function readCookieWithReason(value) {
17
+ if (!value)
18
+ return { payload: null, reason: "missing" };
19
+ let buf;
20
+ try {
21
+ buf = Buffer.from(value, "base64");
22
+ }
23
+ catch {
24
+ return { payload: null, reason: "base64" };
25
+ }
26
+ if (buf.length < 12 + 16)
27
+ return { payload: null, reason: "too-short" };
28
+ const iv = buf.subarray(0, 12);
29
+ const tag = buf.subarray(12, 28);
30
+ const enc = buf.subarray(28);
31
+ try {
32
+ const decipher = createDecipheriv(ALGO, KEY, iv);
33
+ decipher.setAuthTag(tag);
34
+ const dec = Buffer.concat([decipher.update(enc), decipher.final()]);
35
+ let payload;
36
+ try {
37
+ const obj = JSON.parse(dec.toString("utf8"));
38
+ if (typeof obj !== "object" || obj === null ||
39
+ typeof obj.accessToken !== "string" ||
40
+ typeof obj.userId !== "number" ||
41
+ typeof obj.username !== "string" ||
42
+ typeof obj.exp !== "number") {
43
+ return { payload: null, reason: "json" };
44
+ }
45
+ payload = obj;
46
+ }
47
+ catch {
48
+ return { payload: null, reason: "json" };
49
+ }
50
+ if (payload.exp < Math.floor(Date.now() / 1000))
51
+ return { payload: null, reason: "expired" };
52
+ return { payload, reason: "ok" };
53
+ }
54
+ catch {
55
+ return { payload: null, reason: "decrypt" };
56
+ }
57
+ }
58
+ export const SESSION_COOKIE = "devbot_session";
59
+ export const STATE_COOKIE = "devbot_oauth_state";
60
+ export function cookieOptions(maxAge) {
61
+ return {
62
+ httpOnly: true,
63
+ secure: config.PUBLIC_URL.startsWith("https:"),
64
+ sameSite: "lax",
65
+ path: "/",
66
+ // ponytail: Express/cookie use ms; call sites pass seconds. Multiply here.
67
+ maxAge: maxAge * 1000,
68
+ };
69
+ }
70
+ //# sourceMappingURL=cookie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from "node:crypto";
2
+ import { config } from "./config.js";
3
+ // ponytail: fixed scrypt salt — same posture as cookie.ts. The key is derived
4
+ // from SESSION_KEY (same env var, different salt → different key). Fine for
5
+ // single-instance; upgrade path: per-deployment random salt if multi-instance.
6
+ const KEY = scryptSync(config.SESSION_KEY, "devbot-db-encryption-salt", 32);
7
+ const ALGO = "aes-256-gcm";
8
+ export function encrypt(plaintext) {
9
+ const iv = randomBytes(12);
10
+ const cipher = createCipheriv(ALGO, KEY, iv);
11
+ const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
12
+ const tag = cipher.getAuthTag();
13
+ return JSON.stringify({ iv: iv.toString("base64"), tag: tag.toString("base64"), data: enc.toString("base64") });
14
+ }
15
+ export function decrypt(blob) {
16
+ let parsed;
17
+ try {
18
+ parsed = JSON.parse(blob);
19
+ }
20
+ catch {
21
+ return null; // not an encrypted blob
22
+ }
23
+ try {
24
+ const decipher = createDecipheriv(ALGO, KEY, Buffer.from(parsed.iv, "base64"));
25
+ decipher.setAuthTag(Buffer.from(parsed.tag, "base64"));
26
+ const dec = Buffer.concat([decipher.update(Buffer.from(parsed.data, "base64")), decipher.final()]);
27
+ return dec.toString("utf8");
28
+ }
29
+ catch {
30
+ return null; // tampered or wrong key
31
+ }
32
+ }
33
+ // Try decrypt first; if it fails, treat as plaintext (transparent migration
34
+ // from pre-encryption rows). ponytail: an attacker who writes plaintext to
35
+ // the DB bypasses encryption — acceptable since the DB is 0600 single-operator.
36
+ export function decryptOrPlaintext(blob) {
37
+ const dec = decrypt(blob);
38
+ return dec !== null ? dec : blob;
39
+ }
40
+ //# sourceMappingURL=crypto-util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-util.js","sourceRoot":"","sources":["../src/crypto-util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,8EAA8E;AAC9E,8EAA8E;AAC9E,iFAAiF;AACjF,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,WAAW,EAAE,2BAA2B,EAAE,EAAE,CAAC,CAAC;AAC5E,MAAM,IAAI,GAAG,aAAa,CAAC;AAE3B,MAAM,UAAU,OAAO,CAAC,SAAiB;IACvC,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAClH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,IAAI,MAAiD,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC/E,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACnG,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,2EAA2E;AAC3E,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC"}
@@ -0,0 +1,90 @@
1
+ import { db } from "./db.js";
2
+ import { encrypt, decryptOrPlaintext } from "./crypto-util.js";
3
+ import { CustomRepoBodySchema, } from "./types/custom-repos.js";
4
+ export function initCustomReposTables() {
5
+ db.exec(`
6
+ CREATE TABLE IF NOT EXISTS custom_repos (
7
+ project_id INTEGER NOT NULL,
8
+ kind TEXT NOT NULL,
9
+ name TEXT NOT NULL,
10
+ url TEXT NOT NULL,
11
+ token TEXT,
12
+ enabled INTEGER NOT NULL DEFAULT 1,
13
+ created_at INTEGER NOT NULL,
14
+ updated_at INTEGER NOT NULL,
15
+ PRIMARY KEY (project_id, kind, name)
16
+ )
17
+ `);
18
+ }
19
+ function rowToRepo(row) {
20
+ return {
21
+ name: String(row.name),
22
+ url: String(row.url),
23
+ token: decryptOrPlaintext(String(row.token ?? "")),
24
+ enabled: row.enabled === 1,
25
+ };
26
+ }
27
+ export function listCustomRepoSummaries(projectId, kind) {
28
+ const rows = db
29
+ .prepare("SELECT name, url, token, enabled FROM custom_repos WHERE project_id = ? AND kind = ? ORDER BY name")
30
+ .all(projectId, kind);
31
+ return rows.map((r) => {
32
+ const repo = rowToRepo(r);
33
+ return { name: repo.name, url: repo.url, enabled: repo.enabled, hasToken: repo.token.length > 0 };
34
+ });
35
+ }
36
+ export function upsertCustomRepo(projectId, kind, name, body) {
37
+ const v = CustomRepoBodySchema.parse(body);
38
+ const existing = db
39
+ .prepare("SELECT token FROM custom_repos WHERE project_id = ? AND kind = ? AND name = ?")
40
+ .get(projectId, kind, name);
41
+ // Empty/absent token on edit keeps the stored value; a new public repo stores
42
+ // an empty token (encrypt("")).
43
+ const tokenBlob = v.token ? encrypt(v.token) : String(existing?.token ?? "");
44
+ const now = Math.floor(Date.now() / 1000);
45
+ db.prepare(`INSERT OR REPLACE INTO custom_repos
46
+ (project_id, kind, name, url, token, enabled, created_at, updated_at)
47
+ VALUES (?, ?, ?, ?, ?, ?, COALESCE((SELECT created_at FROM custom_repos WHERE project_id = ? AND kind = ? AND name = ?), ?), ?)`).run(projectId, kind, name, v.url, tokenBlob, v.enabled ? 1 : 0, projectId, kind, name, now, now);
48
+ }
49
+ export function deleteCustomRepo(projectId, kind, name) {
50
+ const r = db.prepare("DELETE FROM custom_repos WHERE project_id = ? AND kind = ? AND name = ?").run(projectId, kind, name);
51
+ return r.changes > 0;
52
+ }
53
+ export function deleteAllCustomRepos(projectId) {
54
+ try {
55
+ db.prepare("DELETE FROM custom_repos WHERE project_id = ?").run(projectId);
56
+ }
57
+ catch {
58
+ // Table may not exist in focused unit tests that skip init.
59
+ }
60
+ }
61
+ // Resolve the repos a run should sync: global first, then project overrides by
62
+ // name. A disabled project repo removes a global repo of the same name; disabled
63
+ // global repos are excluded. Deterministic name order.
64
+ export function resolveCustomRepos(projectId, kind) {
65
+ const load = (pid) => {
66
+ let rows;
67
+ try {
68
+ rows = db
69
+ .prepare("SELECT name, url, token, enabled FROM custom_repos WHERE project_id = ? AND kind = ? ORDER BY name")
70
+ .all(pid, kind);
71
+ }
72
+ catch {
73
+ // Table may not exist in focused unit tests that skip init.
74
+ return [];
75
+ }
76
+ return rows.map(rowToRepo);
77
+ };
78
+ const merged = new Map();
79
+ for (const r of load(0))
80
+ if (r.enabled)
81
+ merged.set(r.name, r);
82
+ for (const r of load(projectId)) {
83
+ if (r.enabled)
84
+ merged.set(r.name, r);
85
+ else
86
+ merged.delete(r.name);
87
+ }
88
+ return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
89
+ }
90
+ //# sourceMappingURL=custom-repos-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-repos-store.js","sourceRoot":"","sources":["../src/custom-repos-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
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, xdgVolumeName, envArgs, logDriverArgs } from "./git.js";
6
+ import { gitExtraHeaderFor } from "./platform/git-auth.js";
7
+ // Sync admin-registered skills/subagent repositories into a work item's
8
+ // volumes. Modeled on src/mcp/peer-sync.ts:
9
+ //
10
+ // - Skills land in /workspace/.devbot-skills/<name>/ (inside the repo tree but
11
+ // git-excluded) so the agent's read/glob/grep and the skill tool can reach a
12
+ // skill's reference files. The xdg volume is masked from the agent, so skills
13
+ // cannot live there.
14
+ // - Agents are cloned into an xdg staging dir, then every *.md is flattened
15
+ // into the opencode-scanned config agents dir. opencode derives an agent's
16
+ // name from its path under the config dir, so cloning straight into
17
+ // agents/<repo>/ would leak the repo's folder layout into the /agent name.
18
+ //
19
+ // Auth is a host-scoped git extraHeader (src/platform/git-auth.ts), so no
20
+ // platform branching leaks here and a private repo's token is only sent to its
21
+ // own host. Public repos omit the header entirely.
22
+ //
23
+ // Every repo is best-effort: a failure emits a marker line and the script still
24
+ // exits 0, so one bad repo never blocks a run.
25
+ export const SKILLS_DIR = ".devbot-skills";
26
+ export const SKILLS_ROOT = `/workspace/${SKILLS_DIR}`;
27
+ export const XDG_HOME = "/home/devbot/.xdg";
28
+ export const AGENT_STAGING_ROOT = `${XDG_HOME}/custom-repos/agents`;
29
+ export const AGENT_CONFIG_DIR = `${XDG_HOME}/config/opencode/agents`;
30
+ export const OK_MARKER = "DEVBOT_CUSTOM_REPO_OK";
31
+ export const FAIL_MARKER = "DEVBOT_CUSTOM_REPO_FAIL";
32
+ // The opencode `skills.paths` entries for a set of skill repos. Emits both the
33
+ // repo root and its `skills/` subdir so either layout is discovered. Pure.
34
+ export function skillsPathsFor(repos) {
35
+ return repos.flatMap((r) => [`${SKILLS_ROOT}/${r.name}`, `${SKILLS_ROOT}/${r.name}/skills`]);
36
+ }
37
+ function shellQuote(s) {
38
+ return "'" + s.replace(/'/g, "'\\''") + "'";
39
+ }
40
+ // Pure shell-script builder. `headerFor(url)` returns a trailing-spaced git
41
+ // prefix (or "") for the repo's URL. Exported so scripts/check-custom-repos.ts
42
+ // can pin clone-vs-fetch, destinations, prune, and auth scoping without podman.
43
+ export function customReposSyncScript(kind, repos, headerFor) {
44
+ const base = kind === "skills" ? SKILLS_ROOT : AGENT_STAGING_ROOT;
45
+ const lines = [`mkdir -p ${base}`];
46
+ if (kind === "skills") {
47
+ // Drop skill dirs no longer configured. The configured names are embedded
48
+ // as a space-delimited set; each existing dir is kept only if present.
49
+ const known = repos.map((r) => r.name).join(" ");
50
+ lines.push(`for d in ${SKILLS_ROOT}/*/; do [ -d "$d" ] || continue; n=$(basename "$d"); ` +
51
+ `case " ${known} " in *" $n "*) ;; *) rm -rf "$d" ;; esac; done`);
52
+ }
53
+ for (const r of repos) {
54
+ const dir = `${base}/${r.name}`;
55
+ const header = headerFor(r.url);
56
+ const clone = `${header}git clone --depth 1 ${shellQuote(r.url)} ${shellQuote(dir)}`;
57
+ const fetch = `(cd ${shellQuote(dir)} && ${header}git fetch --depth 1 origin && git reset --hard FETCH_HEAD)`;
58
+ lines.push(`if [ -d ${shellQuote(dir)}/.git ]; then ` +
59
+ `{ ${fetch} && echo "${OK_MARKER}:${r.name}"; } || echo "${FAIL_MARKER}:${r.name}"; ` +
60
+ `else { ${clone} && echo "${OK_MARKER}:${r.name}"; } || echo "${FAIL_MARKER}:${r.name}"; fi`);
61
+ }
62
+ if (kind === "agents")
63
+ lines.push(flattenAgentScript());
64
+ return lines.join("\n");
65
+ }
66
+ // Clear the scanned agents dir and flatten every markdown file from the staging
67
+ // tree into it. Prints the resulting basenames (one per line) so the caller can
68
+ // learn the installed agent names.
69
+ export function flattenAgentScript() {
70
+ return (`rm -rf ${AGENT_CONFIG_DIR} && mkdir -p ${AGENT_CONFIG_DIR} && ` +
71
+ `find ${AGENT_STAGING_ROOT} -name '*.md' -type f -print0 2>/dev/null | ` +
72
+ `while IFS= read -r -d '' f; do cp "$f" "${AGENT_CONFIG_DIR}/$(basename "$f")"; done && ` +
73
+ `ls ${AGENT_CONFIG_DIR} 2>/dev/null`);
74
+ }
75
+ export async function syncCustomRepos(input) {
76
+ const enabled = input.repos.filter((r) => r.enabled);
77
+ if (enabled.length === 0)
78
+ return { synced: [], failed: [], agents: [] };
79
+ const tokenByUrl = new Map(enabled.map((r) => [r.url, r.token]));
80
+ const script = customReposSyncScript(input.kind, enabled, (url) => {
81
+ const token = tokenByUrl.get(url) ?? "";
82
+ return token ? `${gitExtraHeaderFor(token, "git", url)} ` : "";
83
+ });
84
+ const r = await podman([
85
+ "run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${input.workItemId}-${input.kind}sync`,
86
+ "-v", `${volumeName(input.workItemId)}:/workspace:Z`,
87
+ "-v", `${xdgVolumeName(input.workItemId)}:${XDG_HOME}:Z`,
88
+ ...envArgs(input.envVars ?? []),
89
+ input.builderImage ?? config.BUILDER_IMAGE,
90
+ "sh", "-c", script,
91
+ ], { timeoutMs: input.timeoutMs ?? 120_000 });
92
+ const synced = [];
93
+ const failed = [];
94
+ for (const repo of enabled) {
95
+ if (r.stdout.includes(`${OK_MARKER}:${repo.name}`))
96
+ synced.push(repo.name);
97
+ else
98
+ failed.push(repo.name);
99
+ }
100
+ if (failed.length > 0) {
101
+ log.warn("custom repo sync failed", {
102
+ kind: input.kind,
103
+ workItemId: input.workItemId,
104
+ failed,
105
+ code: r.code,
106
+ stderr: redact(r.stderr.slice(-256)),
107
+ });
108
+ }
109
+ const agents = input.kind === "agents"
110
+ ? r.stdout
111
+ .split("\n")
112
+ .map((s) => s.trim())
113
+ .filter((s) => s.endsWith(".md"))
114
+ .map((s) => s.replace(/\.md$/, ""))
115
+ : [];
116
+ return { synced, failed, agents };
117
+ }
118
+ //# sourceMappingURL=custom-repos-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-repos-sync.js","sourceRoot":"","sources":["../src/custom-repos-sync.ts"],"names":[],"mappings":"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"}
package/dist/db.js ADDED
@@ -0,0 +1,12 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ // Shared DB handle. All stores import this `db` instead of opening their own
3
+ // DatabaseSync — one handle, one WAL pragma, one busy_timeout, one close.
4
+ // ponytail: DEVBOT_DB_PATH override exists for tests; production uses devbot.db.
5
+ const DB_PATH = process.env.DEVBOT_DB_PATH ?? "devbot.db";
6
+ export const db = new DatabaseSync(DB_PATH);
7
+ db.exec("PRAGMA journal_mode = WAL");
8
+ db.exec("PRAGMA busy_timeout = 5000");
9
+ export function closeDb() {
10
+ db.close();
11
+ }
12
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,6EAA6E;AAC7E,0EAA0E;AAC1E,iFAAiF;AACjF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,WAAW,CAAC;AAE1D,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;AAC5C,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;AACrC,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;AAEtC,MAAM,UAAU,OAAO;IACrB,EAAE,CAAC,KAAK,EAAE,CAAC;AACb,CAAC"}
@@ -0,0 +1,18 @@
1
+ // `.devbot/` scripts the runner executes inside the builder container.
2
+ // Today only `init.sh` exists (post-clone, first run only). Future scripts
3
+ // (e.g. `.devbot/post-sync.sh`, `.devbot/pre-run.sh`) slot in here without
4
+ // touching git.ts or runner.ts — add a phase function and call it from the
5
+ // appropriate runner step.
6
+ //
7
+ // All scripts are invoked via `sh` (not requiring the executable bit — common
8
+ // Windows-clone perms issue). A non-zero exit aborts the run with an
9
+ // actionable error note (see git.ts first-run branch).
10
+ // Shell snippet appended to the first-run clone command. Runs `.devbot/init.sh`
11
+ // if it exists (after clone, before opencode). The env vars configured per-
12
+ // project are already exported into the container via `--env NAME=VALUE` on
13
+ // the podman args, so init.sh sees them naturally. Exit 8 + DEVBOT_INIT_FAILED
14
+ // marker in stdout lets git.ts map to an actionable error reason.
15
+ export function buildPostCloneScript() {
16
+ return "if [ -f .devbot/init.sh ]; then sh .devbot/init.sh || { echo \"DEVBOT_INIT_FAILED:$?\"; exit 8; }; fi";
17
+ }
18
+ //# sourceMappingURL=devbot-scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devbot-scripts.js","sourceRoot":"","sources":["../src/devbot-scripts.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,2BAA2B;AAC3B,EAAE;AACF,8EAA8E;AAC9E,qEAAqE;AACrE,uDAAuD;AAEvD,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,kEAAkE;AAClE,MAAM,UAAU,oBAAoB;IAClC,OAAO,uGAAuG,CAAC;AACjH,CAAC"}