@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,279 @@
1
+ import { log } from "./logger.js";
2
+ import { podman } from "./podman.js";
3
+ import { workItemContainerPrefix, phaseLabel } from "./git.js";
4
+ import { redact } from "./redact.js";
5
+ // Shared container-log helpers. `parseLogLines`/`formatLogBody` turn opencode's
6
+ // NDJSON stdout (one event object per line, see types/opencode.ts) into readable
7
+ // lines while passing non-JSON lines (init scripts, git, stderr) through
8
+ // verbatim, prefixing each line with a server-local `HH:MM:SS` clock when one
9
+ // can be derived. `fetchRunLogs` snapshots the running opencode container's logs
10
+ // on demand via `podman logs --timestamps`; the container is short-lived and
11
+ // `--rm`, so logs exist only while the run is in flight.
12
+ const MAX_LOG_CHARS = 200_000;
13
+ const TAIL_LINES = 500;
14
+ // Per-field caps for verbose tool rendering. A single huge tool input/output
15
+ // must not crowd every other event out under the whole-body cap. Applied to the
16
+ // rendered string (after JSON pretty-compaction), independently per field.
17
+ const MAX_TOOL_INPUT_CHARS = 1000;
18
+ const MAX_TOOL_OUTPUT_CHARS = 1000;
19
+ const MAX_UNKNOWN_PART_CHARS = 2000;
20
+ function capField(s, max) {
21
+ return s.length > max ? s.slice(0, max) + "…[truncated]" : s;
22
+ }
23
+ function compactJson(value, max) {
24
+ let s;
25
+ if (typeof value === "string") {
26
+ s = value;
27
+ }
28
+ else {
29
+ try {
30
+ s = JSON.stringify(value);
31
+ }
32
+ catch {
33
+ s = String(value);
34
+ }
35
+ if (s === undefined)
36
+ s = "";
37
+ }
38
+ return capField(s, max);
39
+ }
40
+ // Indent every line of a multi-line block by `pad` spaces.
41
+ function indentLines(s, pad) {
42
+ return s.split("\n").map((line) => pad + line).join("\n");
43
+ }
44
+ // podman `logs --timestamps` prefixes each line with an RFC3339 timestamp
45
+ // (e.g. `2026-09-13T21:26:11.628901234Z `). Split it from the log body.
46
+ const PODMAN_TS_RE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))\s+([\s\S]*)$/;
47
+ function truncate(s, max) {
48
+ if (s.length <= max)
49
+ return { text: s, truncated: false };
50
+ return { text: s.slice(0, max) + "\n…[truncated]", truncated: true };
51
+ }
52
+ function pad2(n) {
53
+ return n < 10 ? `0${n}` : String(n);
54
+ }
55
+ // Parse an RFC3339/ISO timestamp to a server-local `HH:MM:SS` clock. Returns
56
+ // null when the value isn't a valid date so the caller can skip the prefix.
57
+ export function formatClock(iso) {
58
+ const d = new Date(iso);
59
+ if (Number.isNaN(d.getTime()))
60
+ return null;
61
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
62
+ }
63
+ // Pull opencode's top-level event `timestamp` off a JSON line. Used as a
64
+ // fallback when the line carries no podman timestamp (e.g. persisted error-log
65
+ // bodies captured from container stdout, which has no podman prefix). Accepts
66
+ // an ISO string or epoch-ms number; returns null when neither is present.
67
+ function eventClock(trimmed) {
68
+ let ev;
69
+ try {
70
+ ev = JSON.parse(trimmed);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ if (typeof ev !== "object" || ev === null)
76
+ return null;
77
+ const ts = ev.timestamp;
78
+ if (typeof ts === "string")
79
+ return formatClock(ts);
80
+ if (typeof ts === "number" && Number.isFinite(ts))
81
+ return formatClock(new Date(ts).toISOString());
82
+ return null;
83
+ }
84
+ // Format one NDJSON line. Unknown/corrupt JSON or non-JSON is returned verbatim.
85
+ function formatLine(trimmed) {
86
+ let ev;
87
+ try {
88
+ ev = JSON.parse(trimmed);
89
+ }
90
+ catch {
91
+ return trimmed;
92
+ }
93
+ if (typeof ev !== "object" || ev === null)
94
+ return trimmed;
95
+ const e = ev;
96
+ if (e.type === "error") {
97
+ const err = e.error;
98
+ const msg = err?.data?.message ?? err?.message;
99
+ return msg ? `[error] ${msg}` : "[error]";
100
+ }
101
+ const part = e.part;
102
+ if (!part || typeof part.type !== "string")
103
+ return trimmed;
104
+ const ptype = part.type;
105
+ switch (ptype) {
106
+ case "text":
107
+ return `[text] ${typeof part.text === "string" ? part.text : ""}`;
108
+ case "reasoning":
109
+ return `[reasoning] ${typeof part.text === "string" ? part.text : ""}`;
110
+ case "tool": {
111
+ const st = part.state;
112
+ const tool = typeof part.tool === "string" ? part.tool : "tool";
113
+ const title = typeof st?.title === "string" && st.title ? `: ${st.title}` : "";
114
+ const lines = [`[tool] ${tool}${st?.status ? ` ${st.status}` : ""}${title}`];
115
+ if (st?.input !== undefined) {
116
+ lines.push(` input: ${compactJson(st.input, MAX_TOOL_INPUT_CHARS)}`);
117
+ }
118
+ if (st?.error) {
119
+ lines.push(` error: ${capField(st.error, MAX_TOOL_OUTPUT_CHARS)}`);
120
+ }
121
+ else if (st?.output) {
122
+ const out = capField(st.output, MAX_TOOL_OUTPUT_CHARS);
123
+ lines.push(" output:");
124
+ lines.push(indentLines(out, " "));
125
+ }
126
+ return lines.join("\n");
127
+ }
128
+ case "step-start":
129
+ return "[step] start";
130
+ case "step-finish": {
131
+ const reason = typeof part.reason === "string" ? ` (${part.reason})` : "";
132
+ const tokens = part.tokens;
133
+ const parts = [];
134
+ if (tokens && (tokens.input !== undefined || tokens.output !== undefined || tokens.reasoning !== undefined)) {
135
+ const t = [];
136
+ if (tokens.input !== undefined)
137
+ t.push(`in=${tokens.input}`);
138
+ if (tokens.output !== undefined)
139
+ t.push(`out=${tokens.output}`);
140
+ if (tokens.reasoning !== undefined)
141
+ t.push(`reasoning=${tokens.reasoning}`);
142
+ parts.push(`tokens ${t.join(" ")}`);
143
+ }
144
+ if (typeof part.cost === "number")
145
+ parts.push(`cost=${part.cost}`);
146
+ return `[step] finish${reason}${parts.length ? ` ${parts.join(" ")}` : ""}`;
147
+ }
148
+ case "file": {
149
+ const mime = typeof part.mime === "string" ? ` (${part.mime})` : "";
150
+ const url = typeof part.url === "string" && part.url ? ` ${part.url}` : "";
151
+ return `[file] ${typeof part.filename === "string" ? part.filename : ""}${mime}${url}`;
152
+ }
153
+ case "subtask": {
154
+ const agent = typeof part.agent === "string" ? part.agent : "";
155
+ const desc = typeof part.description === "string" && part.description ? `: ${part.description}` : "";
156
+ return `[subtask] ${agent}${desc}`;
157
+ }
158
+ case "agent":
159
+ return `[agent] ${typeof part.name === "string" ? part.name : ""}`;
160
+ case "retry": {
161
+ const attempt = typeof part.attempt === "number" ? part.attempt : "?";
162
+ const err = part.error;
163
+ const msg = typeof err?.message === "string" && err.message ? `: ${err.message}` : "";
164
+ return `[retry] attempt ${attempt}${msg}`;
165
+ }
166
+ default:
167
+ // Unknown part type: dump the body so no event is silently dropped.
168
+ return `[${ptype}] ${capField(JSON.stringify(part), MAX_UNKNOWN_PART_CHARS)}`;
169
+ }
170
+ }
171
+ // Render a log body to readable lines, prefixing each with a server-local
172
+ // `HH:MM:SS` clock when one can be derived. Resolution per line:
173
+ // 1. podman `--timestamps` RFC3339 prefix (live logs) — every line carries it;
174
+ // 2. the opencode event's top-level `timestamp` (JSON lines without a prefix,
175
+ // e.g. persisted error-log bodies captured from container stdout);
176
+ // 3. none — the line passes through unprefixed (shell-only clone/push output).
177
+ // No truncate/redact here: `parseLogLines` applies those on top, while the
178
+ // error-log route formats an already-redacted, already-capped stored body.
179
+ export function formatLogBody(raw) {
180
+ const out = [];
181
+ for (const line of raw.split("\n")) {
182
+ const prefixed = PODMAN_TS_RE.exec(line);
183
+ let body = line;
184
+ let clock = null;
185
+ if (prefixed) {
186
+ clock = formatClock(prefixed[1]);
187
+ body = prefixed[2];
188
+ }
189
+ const trimmed = body.trim();
190
+ if (!trimmed)
191
+ continue;
192
+ if (!clock)
193
+ clock = eventClock(trimmed);
194
+ const rendered = formatLine(trimmed);
195
+ out.push(clock ? `${clock} ${rendered}` : rendered);
196
+ }
197
+ return out.join("\n");
198
+ }
199
+ export function parseLogLines(raw) {
200
+ return truncate(redact(formatLogBody(raw)), MAX_LOG_CHARS);
201
+ }
202
+ // Phase containers are short-lived and named `devbot-wi-<id>-<phase>`. When
203
+ // several run at once (the agent's `-run` plus an MCP-triggered `-peersync` or
204
+ // `-attachread`/`-attachdl`), prefer the primary run phase so the viewer keeps
205
+ // showing the agent transcript rather than flickering to a momentary side
206
+ // operation.
207
+ const PHASE_PRIORITY = [
208
+ "run",
209
+ "conflictcheck",
210
+ "resolve",
211
+ "tip",
212
+ "peersync",
213
+ "peerinit",
214
+ "attachread",
215
+ "attachdl",
216
+ "gitexclude",
217
+ "auth",
218
+ "podman-init",
219
+ "sync",
220
+ "clone",
221
+ "detect",
222
+ "push",
223
+ "forcepush",
224
+ ];
225
+ // List the work item's running phase containers via one `podman ps`. The name
226
+ // filter is a substring match; startsWith re-checks so `devbot-wi-5-` can never
227
+ // pick up `devbot-wi-50-…`.
228
+ async function listRunningPhases(workItemId) {
229
+ const prefix = workItemContainerPrefix(workItemId);
230
+ const r = await podman(["ps", "--filter", `name=${prefix}`, "--format", "{{.Names}}"]);
231
+ if (r.code !== 0)
232
+ return [];
233
+ const found = [];
234
+ for (const line of r.stdout.split("\n")) {
235
+ const name = line.trim();
236
+ if (!name.startsWith(prefix))
237
+ continue;
238
+ const phase = name.slice(prefix.length);
239
+ if (phase)
240
+ found.push({ phase, name });
241
+ }
242
+ return found;
243
+ }
244
+ // Pick the phase container to stream: the primary `run` phase when present,
245
+ // else the highest-priority phase by lifecycle order.
246
+ function pickPhase(containers) {
247
+ if (containers.length === 0)
248
+ return null;
249
+ for (const phase of PHASE_PRIORITY) {
250
+ const hit = containers.find((c) => c.phase === phase);
251
+ if (hit)
252
+ return hit;
253
+ }
254
+ return containers[0];
255
+ }
256
+ // Snapshot the currently-active phase container's logs. The active phase is
257
+ // discovered from the running containers (not just `-run`) so clone/sync/auth/
258
+ // push output is visible too. A non-zero podman exit (container gone: `--rm`
259
+ // after completion, or not yet started) returns available:false.
260
+ export async function fetchTaskLogs(workItemId) {
261
+ const active = pickPhase(await listRunningPhases(workItemId));
262
+ if (!active) {
263
+ log.debug("task logs: no active phase container", { workItemId });
264
+ return { available: false, text: "", truncated: false, phase: null, phaseLabel: "" };
265
+ }
266
+ const label = phaseLabel(active.phase);
267
+ // --timestamps prefixes every line (NDJSON and non-JSON) with an RFC3339
268
+ // time so the viewer can show a clock per line.
269
+ const r = await podman(["logs", "--timestamps", "--tail", String(TAIL_LINES), active.name]);
270
+ if (r.code !== 0) {
271
+ log.debug("task logs unavailable", { workItemId, container: active.name, code: r.code });
272
+ return { available: false, text: "", truncated: false, phase: active.phase, phaseLabel: label };
273
+ }
274
+ // podman multiplexes stdout/stderr; combine so init/git errors aren't lost.
275
+ const combined = r.stdout + (r.stderr ? `\n${r.stderr}` : "");
276
+ const { text, truncated } = parseLogLines(combined);
277
+ return { available: true, text, truncated, phase: active.phase, phaseLabel: label };
278
+ }
279
+ //# sourceMappingURL=task-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-log.js","sourceRoot":"","sources":["../src/task-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ function asNumber(v, fallback = 0) {
2
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
3
+ }
4
+ function asString(v) {
5
+ return typeof v === "string" ? v : "";
6
+ }
7
+ // Shape the in-memory gate depth + configured cap into the header's quota
8
+ // object. Pure so it is unit-testable without a DB or config side effects.
9
+ export function buildTaskQuota(depth, max) {
10
+ return { active: depth.active, max, waiting: depth.waiting };
11
+ }
12
+ // `resolveProjectPath(projectId)` returns the managed project's path or "".
13
+ // `resolveMode(projectId, mode)` returns the resolved model + variant for the
14
+ // row's plan/build mode (defaults to empty, so store-free callers/tests stay
15
+ // valid). `resolveWorkItemUrl(projectId, number)` builds the browser link to
16
+ // the work item (defaults to empty for store-free callers/tests). All three
17
+ // are passed in (rather than imported) so this module stays free of DB/config
18
+ // side effects and is cheap to unit test.
19
+ //
20
+ // `resolveWaitingPosition(seq)` returns the task's live global-gate position
21
+ // (1-based) when it is claimed but blocked on the concurrency gate, else null.
22
+ // A DB-`running` row that is gate-blocked is surfaced as status `waiting` with
23
+ // that position; a running row holding a slot stays `running` with position
24
+ // null. `queued` rows keep their 0-based DB backlog rank (a distinct ordering:
25
+ // backlog rank vs admission position). Defaults to null (store-free callers).
26
+ export function buildTaskViews(rows, resolveProjectPath, resolveMode = () => ({ model: "", variant: "" }), resolveWorkItemUrl = () => "", resolveWaitingPosition = () => null) {
27
+ return rows.map((r) => {
28
+ let payload = {};
29
+ try {
30
+ payload = JSON.parse(r.payload);
31
+ }
32
+ catch {
33
+ payload = {};
34
+ }
35
+ const projectId = asNumber(payload.projectId);
36
+ const mode = asString(payload.mode);
37
+ const workItemIid = asNumber(payload.workItemNumber ?? payload.workItemIid);
38
+ const resolved = resolveMode(projectId, mode);
39
+ // A claimed row that is blocked on the global gate is logically "waiting",
40
+ // not running: prefer the gate position over the raw DB status.
41
+ const waitingPosition = r.status === "running" ? resolveWaitingPosition(r.seq) : null;
42
+ return {
43
+ seq: r.seq,
44
+ projectId,
45
+ projectPath: resolveProjectPath(projectId),
46
+ workItemIid,
47
+ workItemId: r.work_item_id,
48
+ workItemUrl: resolveWorkItemUrl(projectId, workItemIid),
49
+ title: asString(payload.workItemTitle),
50
+ mode,
51
+ model: resolved.model,
52
+ variant: resolved.variant,
53
+ status: waitingPosition !== null ? "waiting" : r.status,
54
+ enqueuedAt: r.enqueued_at,
55
+ claimedAt: r.claimed_at,
56
+ attempts: r.attempts,
57
+ lastError: r.last_error,
58
+ position: waitingPosition !== null ? waitingPosition : r.position,
59
+ };
60
+ });
61
+ }
62
+ //# sourceMappingURL=task-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-view.js","sourceRoot":"","sources":["../src/task-view.ts"],"names":[],"mappings":"AAyDA,SAAS,QAAQ,CAAC,CAAU,EAAE,QAAQ,GAAG,CAAC;IACxC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC;AAED,0EAA0E;AAC1E,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAAuB,EAAE,GAAW;IACjE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;AAC/D,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAC5B,IAAoB,EACpB,kBAAiD,EACjD,cAAiE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EACnG,qBAAoE,GAAG,EAAE,CAAC,EAAE,EAC5E,yBAAyD,GAAG,EAAE,CAAC,IAAI;IAEnE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACpB,IAAI,OAAO,GAAgB,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAgB,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,EAAE,CAAC;QACf,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC9C,2EAA2E;QAC3E,gEAAgE;QAChE,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtF,OAAO;YACL,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,SAAS;YACT,WAAW,EAAE,kBAAkB,CAAC,SAAS,CAAC;YAC1C,WAAW;YACX,UAAU,EAAE,CAAC,CAAC,YAAY;YAC1B,WAAW,EAAE,kBAAkB,CAAC,SAAS,EAAE,WAAW,CAAC;YACvD,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC;YACtC,IAAI;YACJ,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;YACvD,UAAU,EAAE,CAAC,CAAC,WAAW;YACzB,SAAS,EAAE,CAAC,CAAC,UAAU;YACvB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,SAAS,EAAE,CAAC,CAAC,UAAU;YACvB,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ;SAClE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { db } from "./db.js";
2
+ import { encrypt, decryptOrPlaintext } from "./crypto-util.js";
3
+ export function initDb() {
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS project_tokens (
6
+ project_id INTEGER PRIMARY KEY,
7
+ project_path TEXT NOT NULL,
8
+ signing_token TEXT NOT NULL,
9
+ webhook_id INTEGER NOT NULL,
10
+ created_at INTEGER NOT NULL
11
+ )
12
+ `);
13
+ }
14
+ export function upsertToken(input) {
15
+ db.prepare(`
16
+ INSERT OR REPLACE INTO project_tokens
17
+ (project_id, project_path, signing_token, webhook_id, created_at)
18
+ VALUES (?, ?, ?, ?, ?)
19
+ `).run(input.projectId, input.projectPath, encrypt(input.signingToken), input.webhookId, Math.floor(Date.now() / 1000));
20
+ }
21
+ export function getToken(projectId) {
22
+ const row = db.prepare("SELECT signing_token FROM project_tokens WHERE project_id = ?").get(projectId);
23
+ return row ? { signingToken: decryptOrPlaintext(row.signing_token) } : null;
24
+ }
25
+ // `limit === undefined` returns every row (used by callers that need the full
26
+ // list); the WebUI pager passes limit/offset. `q` is an optional
27
+ // case-insensitive substring of the project path (work item #88).
28
+ export function listTokens(limit, offset, q) {
29
+ const where = q !== undefined && q.length > 0 ? " WHERE project_path LIKE ? ESCAPE '\\'" : "";
30
+ const params = where ? [`%${q.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`] : [];
31
+ const sql = `SELECT project_id, project_path, webhook_id, created_at FROM project_tokens${where} ORDER BY created_at DESC` +
32
+ (limit === undefined ? "" : " LIMIT ? OFFSET ?");
33
+ const args = limit === undefined ? params : [...params, limit, offset ?? 0];
34
+ const rows = db.prepare(sql).all(...args);
35
+ return rows.map((r) => ({
36
+ projectId: r.project_id,
37
+ projectPath: r.project_path,
38
+ webhookId: r.webhook_id,
39
+ createdAt: r.created_at,
40
+ }));
41
+ }
42
+ export function countTokens(q) {
43
+ const where = q !== undefined && q.length > 0 ? " WHERE project_path LIKE ? ESCAPE '\\'" : "";
44
+ const params = where ? [`%${q.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`] : [];
45
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM project_tokens${where}`).get(...params);
46
+ return row.c;
47
+ }
48
+ export function getProject(projectId) {
49
+ const row = db
50
+ .prepare("SELECT project_id, project_path, webhook_id FROM project_tokens WHERE project_id = ?")
51
+ .get(projectId);
52
+ return row
53
+ ? { projectId: row.project_id, projectPath: row.project_path, webhookId: row.webhook_id }
54
+ : null;
55
+ }
56
+ // Update the stored forge path after a rename/transfer. Returns true when a row
57
+ // actually changed. Signing token, webhook id and created_at are untouched, so
58
+ // reconciliation can never reset an onboarded project.
59
+ export function updateProjectPath(projectId, projectPath) {
60
+ const r = db
61
+ .prepare("UPDATE project_tokens SET project_path = ? WHERE project_id = ? AND project_path <> ?")
62
+ .run(projectPath, projectId, projectPath);
63
+ return r.changes > 0;
64
+ }
65
+ export function deleteToken(projectId) {
66
+ db.prepare("DELETE FROM project_tokens WHERE project_id = ?").run(projectId);
67
+ }
68
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { z } from "zod";
2
+ // Trigger policy and per-project access rules.
3
+ //
4
+ // The baseline trigger policy has two configurable gates, resolved
5
+ // project -> global -> default:
6
+ //
7
+ // 1. requireAuthorOrAssignee (default true) — the actor must be the work
8
+ // item's author or assignee.
9
+ // 2. minRole (default "write") — the actor's normalized project permission
10
+ // must be at least this role (none < read < write < admin < owner).
11
+ //
12
+ // Both are global settings, each overridable per project. Setting
13
+ // requireAuthorOrAssignee=false and minRole="none" allows any project member to
14
+ // trigger.
15
+ //
16
+ // A per-project access rule refines the SECOND (role) gate for one subject: a
17
+ // normalized project role or a specific forge user (numeric id). It runs after
18
+ // the author/assignee gate and replaces the role gate for that subject, so it
19
+ // can broaden (e.g. allow a Reporter to run plans) or narrow (e.g. deny a
20
+ // specific user). It cannot bypass the author/assignee gate. Levels:
21
+ //
22
+ // full — plan and build runs.
23
+ // plan — plan mode only; a build mention is ignored silently.
24
+ // denied — ignored silently (no reply, no run).
25
+ //
26
+ // Precedence: an exact username rule beats a role rule; the configured role
27
+ // gate applies when neither matches.
28
+ export const ACCESS_LEVELS = ["full", "plan", "denied"];
29
+ // Normalized project permission (the Platform port's Permission).
30
+ export const ACCESS_ROLES = ["none", "read", "write", "admin", "owner"];
31
+ // Ordered least -> most privileged, for the configurable minimum-role gate.
32
+ const ROLE_ORDER = ["none", "read", "write", "admin", "owner"];
33
+ // True when `permission` is at least `minRole` in the normalized role order.
34
+ export function permissionAtLeast(permission, minRole) {
35
+ return ROLE_ORDER.indexOf(permission) >= ROLE_ORDER.indexOf(minRole);
36
+ }
37
+ // Human-facing role label for the denial note.
38
+ export function roleLabel(role) {
39
+ switch (role) {
40
+ case "none":
41
+ return "a project member";
42
+ case "read":
43
+ return "Reporter";
44
+ case "write":
45
+ return "Developer";
46
+ case "admin":
47
+ return "Maintainer";
48
+ case "owner":
49
+ return "Owner";
50
+ }
51
+ }
52
+ // Global setting: required, parsed with a default so older rows stay valid.
53
+ export const globalMinRoleField = z.enum(ACCESS_ROLES).default("write");
54
+ export const globalRequireAuthorOrAssigneeField = z.boolean().default(true);
55
+ // Project setting: "" = inherit the global value; otherwise an explicit
56
+ // override. Booleans are stored as text to keep the ""-means-inherit convention.
57
+ export const projectMinRoleField = z.union([z.enum(ACCESS_ROLES), z.literal("")]).default("");
58
+ export const InheritableBoolSchema = z.union([z.literal(""), z.literal("true"), z.literal("false")]).default("");
59
+ export const USERNAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
60
+ export const USERNAME_MAX = 255;
61
+ const levelField = z.enum(ACCESS_LEVELS);
62
+ // Body for POST /api/projects/:id/access-rules. A role rule carries `role`; a
63
+ // user rule carries a `username` that the server resolves to a stable id.
64
+ export const AccessRuleAddSchema = z.discriminatedUnion("subjectType", [
65
+ z.object({ subjectType: z.literal("role"), role: z.enum(ACCESS_ROLES), level: levelField }).strict(),
66
+ z
67
+ .object({
68
+ subjectType: z.literal("user"),
69
+ username: z.string().min(1).max(USERNAME_MAX).transform((v) => v.trim()),
70
+ level: levelField,
71
+ })
72
+ .strict(),
73
+ ]);
74
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/types/access.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ // Admin-registered git repositories of opencode skills and/or subagents. Two
3
+ // kinds share one table/store; `project_id = 0` is the global scope (same
4
+ // sentinel as MCP servers). Tokens are optional (public repos) and encrypted at
5
+ // rest by the store.
6
+ export const CUSTOM_REPO_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
7
+ export const CUSTOM_REPO_NAME_MAX = 64;
8
+ export const CUSTOM_REPO_URL_MAX = 2048;
9
+ export const CUSTOM_REPO_TOKEN_MAX = 4096;
10
+ export const CUSTOM_REPO_KINDS = ["skills", "agents"];
11
+ const urlField = z
12
+ .string()
13
+ .max(CUSTOM_REPO_URL_MAX)
14
+ .refine((v) => /^https?:\/\/\S+$/.test(v), { message: "must be an http(s) URL" });
15
+ export const CustomRepoBodySchema = z
16
+ .object({
17
+ url: urlField,
18
+ token: z.string().max(CUSTOM_REPO_TOKEN_MAX).optional(),
19
+ enabled: z.boolean(),
20
+ })
21
+ .strict();
22
+ export function isValidCustomRepoName(name) {
23
+ return name.length >= 1 && name.length <= CUSTOM_REPO_NAME_MAX && CUSTOM_REPO_NAME_RE.test(name);
24
+ }
25
+ export function isCustomRepoKind(kind) {
26
+ return CUSTOM_REPO_KINDS.includes(kind);
27
+ }
28
+ //# sourceMappingURL=custom-repos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-repos.js","sourceRoot":"","sources":["../../src/types/custom-repos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6EAA6E;AAC7E,0EAA0E;AAC1E,gFAAgF;AAChF,qBAAqB;AAErB,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACxC,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAG/D,MAAM,QAAQ,GAAG,CAAC;KACf,MAAM,EAAE;KACR,GAAG,CAAC,mBAAmB,CAAC;KACxB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;AAEpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,GAAG,EAAE,QAAQ;IACb,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IACvD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC;KACD,MAAM,EAAE,CAAC;AAWZ,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAoB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnG,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ // One per-project environment variable. Values are secrets (API keys, DB
3
+ // passwords, etc.) — encrypted at rest in the store, never returned to the
4
+ // browser (only the summary shape below). The runner injects each enabled
5
+ // var into every builder container via `--env NAME=VALUE`.
6
+ export const ENV_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
7
+ export const ENV_NAME_MAX = 64;
8
+ export const ENV_VALUE_MAX = 4096;
9
+ export const EnvVarSchema = z
10
+ .object({
11
+ name: z.string().regex(ENV_NAME_RE).max(ENV_NAME_MAX),
12
+ value: z.string().max(ENV_VALUE_MAX),
13
+ enabled: z.boolean(),
14
+ })
15
+ .strict();
16
+ // Body for PUT /api/projects/:id/env/:name — name comes from the URL param,
17
+ // so the body only carries value + enabled. An empty value on edit means
18
+ // "leave unchanged" (the store keeps the existing encrypted value).
19
+ export const EnvVarBodySchema = z
20
+ .object({
21
+ value: z.string().max(ENV_VALUE_MAX),
22
+ enabled: z.boolean(),
23
+ })
24
+ .strict();
25
+ //# sourceMappingURL=env-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-config.js","sourceRoot":"","sources":["../../src/types/env-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,yEAAyE;AACzE,6EAA6E;AAC7E,4EAA4E;AAC5E,6DAA6D;AAE7D,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAChD,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC;KAC1B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;IACrD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC;KACD,MAAM,EAAE,CAAC;AAGZ,4EAA4E;AAC5E,2EAA2E;AAC3E,sEAAsE;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC;KACD,MAAM,EAAE,CAAC"}