@neuralnomads/codenomad-dev 0.10.3-dev-20260213-ba418a85

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 (462) hide show
  1. package/README.md +126 -0
  2. package/dist/api-types.js +1 -0
  3. package/dist/auth/auth-store.js +134 -0
  4. package/dist/auth/http-auth.js +37 -0
  5. package/dist/auth/manager.js +128 -0
  6. package/dist/auth/password-hash.js +32 -0
  7. package/dist/auth/session-manager.js +17 -0
  8. package/dist/auth/token-manager.js +27 -0
  9. package/dist/background-processes/manager.js +437 -0
  10. package/dist/bin.js +24 -0
  11. package/dist/config/binaries.js +148 -0
  12. package/dist/config/location.js +57 -0
  13. package/dist/config/schema.js +70 -0
  14. package/dist/config/store.js +200 -0
  15. package/dist/events/bus.js +41 -0
  16. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  17. package/dist/filesystem/browser.js +285 -0
  18. package/dist/filesystem/search-cache.js +43 -0
  19. package/dist/filesystem/search.js +135 -0
  20. package/dist/index.js +410 -0
  21. package/dist/integrations/github/bot-signature.js +11 -0
  22. package/dist/integrations/github/git-ops.js +133 -0
  23. package/dist/integrations/github/github-types.js +1 -0
  24. package/dist/integrations/github/job-runner.js +608 -0
  25. package/dist/integrations/github/octokit.js +58 -0
  26. package/dist/integrations/github/sanitize-webhook.js +42 -0
  27. package/dist/integrations/github/webhook-verify.js +21 -0
  28. package/dist/integrations/github/workspace-context.js +10 -0
  29. package/dist/integrations/github/worktree-context.js +15 -0
  30. package/dist/launcher.js +149 -0
  31. package/dist/loader.js +21 -0
  32. package/dist/logger.js +109 -0
  33. package/dist/opencode/request-context.js +39 -0
  34. package/dist/opencode/worktree-directory.js +42 -0
  35. package/dist/opencode-config/README.md +32 -0
  36. package/dist/opencode-config/opencode.jsonc +3 -0
  37. package/dist/opencode-config/package.json +9 -0
  38. package/dist/opencode-config/plugin/codenomad.ts +32 -0
  39. package/dist/opencode-config/plugin/lib/background-process.ts +253 -0
  40. package/dist/opencode-config/plugin/lib/client.ts +133 -0
  41. package/dist/opencode-config/plugin/lib/request.ts +214 -0
  42. package/dist/opencode-config-template/README.md +32 -0
  43. package/dist/opencode-config-template/opencode.jsonc +3 -0
  44. package/dist/opencode-config-template/plugin/codenomad.ts +40 -0
  45. package/dist/opencode-config-template/plugin/lib/background-process.ts +160 -0
  46. package/dist/opencode-config-template/plugin/lib/client.ts +165 -0
  47. package/dist/opencode-config.js +26 -0
  48. package/dist/plugins/channel.js +40 -0
  49. package/dist/plugins/handlers.js +17 -0
  50. package/dist/releases/dev-release-monitor.js +75 -0
  51. package/dist/releases/release-monitor.js +107 -0
  52. package/dist/server/http-server.js +547 -0
  53. package/dist/server/network-addresses.js +72 -0
  54. package/dist/server/routes/auth-pages/login.html +134 -0
  55. package/dist/server/routes/auth-pages/token.html +93 -0
  56. package/dist/server/routes/auth.js +134 -0
  57. package/dist/server/routes/background-processes.js +60 -0
  58. package/dist/server/routes/config.js +59 -0
  59. package/dist/server/routes/events.js +43 -0
  60. package/dist/server/routes/filesystem.js +43 -0
  61. package/dist/server/routes/github-plugin.js +215 -0
  62. package/dist/server/routes/github-webhook.js +32 -0
  63. package/dist/server/routes/meta.js +47 -0
  64. package/dist/server/routes/plugin.js +52 -0
  65. package/dist/server/routes/storage.js +52 -0
  66. package/dist/server/routes/workspaces.js +89 -0
  67. package/dist/server/routes/worktrees.js +156 -0
  68. package/dist/server/tls.js +224 -0
  69. package/dist/storage/instance-store.js +56 -0
  70. package/dist/ui/__tests__/remote-ui.test.js +46 -0
  71. package/dist/ui/remote-ui.js +462 -0
  72. package/dist/workspaces/git-worktrees.js +199 -0
  73. package/dist/workspaces/instance-events.js +180 -0
  74. package/dist/workspaces/manager.js +375 -0
  75. package/dist/workspaces/opencode-auth.js +16 -0
  76. package/dist/workspaces/runtime.js +346 -0
  77. package/dist/workspaces/worktree-map.js +116 -0
  78. package/package.json +49 -0
  79. package/public/apple-touch-icon-180x180.png +0 -0
  80. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  81. package/public/assets/abap-BdImnpbu.js +1 -0
  82. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  83. package/public/assets/ada-bCR0ucgS.js +1 -0
  84. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  85. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  86. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  87. package/public/assets/apache-Pmp26Uib.js +1 -0
  88. package/public/assets/apex-DhZLUxFE.js +1 -0
  89. package/public/assets/apl-dKokRX4l.js +1 -0
  90. package/public/assets/applescript-Co6uUVPk.js +1 -0
  91. package/public/assets/ara-BRHolxvo.js +1 -0
  92. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  93. package/public/assets/asm-D_Q5rh1f.js +1 -0
  94. package/public/assets/astro-CbQHKStN.js +1 -0
  95. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  96. package/public/assets/awk-DMzUqQB5.js +1 -0
  97. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  98. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  99. package/public/assets/bat-BkioyH1T.js +1 -0
  100. package/public/assets/beancount-k_qm7-4y.js +1 -0
  101. package/public/assets/berry-D08WgyRC.js +1 -0
  102. package/public/assets/bibtex-CHM0blh-.js +1 -0
  103. package/public/assets/bicep-Bmn6On1c.js +1 -0
  104. package/public/assets/blade-DVc8C-J4.js +1 -0
  105. package/public/assets/bsl-BO_Y6i37.js +1 -0
  106. package/public/assets/c-BIGW1oBm.js +1 -0
  107. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  108. package/public/assets/cairo-KRGpt6FW.js +1 -0
  109. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  110. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  111. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  112. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  113. package/public/assets/clarity-D53aC0YG.js +1 -0
  114. package/public/assets/clojure-P80f7IUj.js +1 -0
  115. package/public/assets/cmake-D1j8_8rp.js +1 -0
  116. package/public/assets/cobol-nwyudZeR.js +1 -0
  117. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  118. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  119. package/public/assets/coffee-Ch7k5sss.js +1 -0
  120. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  121. package/public/assets/coq-DkFqJrB1.js +1 -0
  122. package/public/assets/core-BSTVzpXI.js +1 -0
  123. package/public/assets/cpp-CofmeUqb.js +1 -0
  124. package/public/assets/crystal-tKQVLTB8.js +1 -0
  125. package/public/assets/csharp-CX12Zw3r.js +1 -0
  126. package/public/assets/css-DPfMkruS.js +1 -0
  127. package/public/assets/csv-fuZLfV_i.js +1 -0
  128. package/public/assets/cue-D82EKSYY.js +1 -0
  129. package/public/assets/cypher-COkxafJQ.js +1 -0
  130. package/public/assets/d-85-TOEBH.js +1 -0
  131. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  132. package/public/assets/dart-CF10PKvl.js +1 -0
  133. package/public/assets/dax-CEL-wOlO.js +1 -0
  134. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  135. package/public/assets/diff-D97Zzqfu.js +1 -0
  136. package/public/assets/docker-BcOcwvcX.js +1 -0
  137. package/public/assets/dotenv-Da5cRb03.js +1 -0
  138. package/public/assets/dracula-BzJJZx-M.js +1 -0
  139. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  140. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  141. package/public/assets/edge-BkV0erSs.js +1 -0
  142. package/public/assets/elixir-CDX3lj18.js +1 -0
  143. package/public/assets/elm-DbKCFpqz.js +1 -0
  144. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  145. package/public/assets/erb-BOJIQeun.js +1 -0
  146. package/public/assets/erlang-DsQrWhSR.js +1 -0
  147. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  148. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  149. package/public/assets/fennel-BYunw83y.js +1 -0
  150. package/public/assets/fish-BvzEVeQv.js +1 -0
  151. package/public/assets/fluent-C4IJs8-o.js +1 -0
  152. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  153. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  154. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  155. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  156. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  157. package/public/assets/gdshader-DkwncUOv.js +1 -0
  158. package/public/assets/genie-D0YGMca9.js +1 -0
  159. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  160. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  161. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  162. package/public/assets/github-dark-DHJKELXO.js +1 -0
  163. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  164. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  165. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  166. package/public/assets/github-light-DAi9KRSo.js +1 -0
  167. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  168. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  169. package/public/assets/gleam-BspZqrRM.js +1 -0
  170. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  171. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  172. package/public/assets/glsl-DplSGwfg.js +1 -0
  173. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  174. package/public/assets/go-Dn2_MT6a.js +1 -0
  175. package/public/assets/graphql-ChdNCCLP.js +1 -0
  176. package/public/assets/groovy-gcz8RCvz.js +1 -0
  177. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  178. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  179. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  180. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  181. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  182. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  183. package/public/assets/hack-CaT9iCJl.js +1 -0
  184. package/public/assets/haml-B8DHNrY2.js +1 -0
  185. package/public/assets/handlebars-BL8al0AC.js +1 -0
  186. package/public/assets/haskell-Df6bDoY_.js +1 -0
  187. package/public/assets/haxe-CzTSHFRz.js +1 -0
  188. package/public/assets/hcl-BWvSN4gD.js +1 -0
  189. package/public/assets/hjson-D5-asLiD.js +1 -0
  190. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  191. package/public/assets/houston-DnULxvSX.js +1 -0
  192. package/public/assets/html-GMplVEZG.js +1 -0
  193. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  194. package/public/assets/http-jrhK8wxY.js +1 -0
  195. package/public/assets/hurl-irOxFIW8.js +1 -0
  196. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  197. package/public/assets/hy-DFXneXwc.js +1 -0
  198. package/public/assets/imba-DGztddWO.js +1 -0
  199. package/public/assets/index-D4PT0yE4.js +1 -0
  200. package/public/assets/index-DN20ggb1.js +1 -0
  201. package/public/assets/index-DdQ7zIzB.js +1 -0
  202. package/public/assets/index-Dl-rJJuP.js +1 -0
  203. package/public/assets/index-Dlo2gDiy.css +1 -0
  204. package/public/assets/ini-BEwlwnbL.js +1 -0
  205. package/public/assets/java-CylS5w8V.js +1 -0
  206. package/public/assets/javascript-wDzz0qaB.js +1 -0
  207. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  208. package/public/assets/jison-wvAkD_A8.js +1 -0
  209. package/public/assets/json-Cp-IABpG.js +1 -0
  210. package/public/assets/json5-C9tS-k6U.js +1 -0
  211. package/public/assets/jsonc-Des-eS-w.js +1 -0
  212. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  213. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  214. package/public/assets/jssm-C2t-YnRu.js +1 -0
  215. package/public/assets/jsx-g9-lgVsj.js +1 -0
  216. package/public/assets/julia-C8NyazO9.js +1 -0
  217. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  218. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  219. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  220. package/public/assets/kdl-DV7GczEv.js +1 -0
  221. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  222. package/public/assets/kusto-BvAqAH-y.js +1 -0
  223. package/public/assets/laserwave-DUszq2jm.js +1 -0
  224. package/public/assets/latex-BUKiar2Z.js +1 -0
  225. package/public/assets/lean-DP1Csr6i.js +1 -0
  226. package/public/assets/less-B1dDrJ26.js +1 -0
  227. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  228. package/public/assets/liquid-DYVedYrR.js +1 -0
  229. package/public/assets/llvm-BtvRca6l.js +1 -0
  230. package/public/assets/loading-CmEVQgyj.css +1 -0
  231. package/public/assets/loading-DgqIiz-T.js +1 -0
  232. package/public/assets/log-2UxHyX5q.js +1 -0
  233. package/public/assets/logo-BtOb2qkB.js +1 -0
  234. package/public/assets/lua-BbnMAYS6.js +1 -0
  235. package/public/assets/luau-CXu1NL6O.js +1 -0
  236. package/public/assets/main-CSlDZj4f.js +188 -0
  237. package/public/assets/main-HAZkIolJ.css +19 -0
  238. package/public/assets/make-CHLpvVh8.js +1 -0
  239. package/public/assets/markdown-Cvjx9yec.js +1 -0
  240. package/public/assets/marko-CPi9NSCl.js +1 -0
  241. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  242. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  243. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  244. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  245. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  246. package/public/assets/matlab-D7o27uSR.js +1 -0
  247. package/public/assets/mdc-DUICxH0z.js +1 -0
  248. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  249. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  250. package/public/assets/min-dark-CafNBF8u.js +1 -0
  251. package/public/assets/min-light-CTRr51gU.js +1 -0
  252. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  253. package/public/assets/mojo-1DNp92w6.js +1 -0
  254. package/public/assets/monokai-D4h5O-jR.js +1 -0
  255. package/public/assets/move-Bu9oaDYs.js +1 -0
  256. package/public/assets/narrat-DRg8JJMk.js +1 -0
  257. package/public/assets/nextflow-CUEJCptM.js +1 -0
  258. package/public/assets/nginx-DknmC5AR.js +1 -0
  259. package/public/assets/night-owl-C39BiMTA.js +1 -0
  260. package/public/assets/nim-CVrawwO9.js +1 -0
  261. package/public/assets/nix-BbRYJGeE.js +1 -0
  262. package/public/assets/nord-Ddv68eIx.js +1 -0
  263. package/public/assets/nushell-C-sUppwS.js +1 -0
  264. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  265. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  266. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  267. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  268. package/public/assets/one-light-PoHY5YXO.js +1 -0
  269. package/public/assets/pascal-D93ZcfNL.js +1 -0
  270. package/public/assets/perl-C0TMdlhV.js +1 -0
  271. package/public/assets/php-CDn_0X-4.js +1 -0
  272. package/public/assets/pkl-u5AG7uiY.js +1 -0
  273. package/public/assets/plastic-3e1v2bzS.js +1 -0
  274. package/public/assets/plsql-ChMvpjG-.js +1 -0
  275. package/public/assets/po-BTJTHyun.js +1 -0
  276. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  277. package/public/assets/polar-C0HS_06l.js +1 -0
  278. package/public/assets/postcss-CXtECtnM.js +1 -0
  279. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  280. package/public/assets/powershell-Dpen1YoG.js +1 -0
  281. package/public/assets/prisma-Dd19v3D-.js +1 -0
  282. package/public/assets/prolog-CbFg5uaA.js +1 -0
  283. package/public/assets/proto-DyJlTyXw.js +1 -0
  284. package/public/assets/pug-CGlum2m_.js +1 -0
  285. package/public/assets/puppet-BMWR74SV.js +1 -0
  286. package/public/assets/purescript-CklMAg4u.js +1 -0
  287. package/public/assets/python-B6aJPvgy.js +1 -0
  288. package/public/assets/qml-3beO22l8.js +1 -0
  289. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  290. package/public/assets/qss-IeuSbFQv.js +1 -0
  291. package/public/assets/r-DiinP2Uv.js +1 -0
  292. package/public/assets/racket-BqYA7rlc.js +1 -0
  293. package/public/assets/raku-DXvB9xmW.js +1 -0
  294. package/public/assets/razor-WgofotgN.js +1 -0
  295. package/public/assets/red-bN70gL4F.js +1 -0
  296. package/public/assets/reg-C-SQnVFl.js +1 -0
  297. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  298. package/public/assets/rel-C3B-1QV4.js +1 -0
  299. package/public/assets/riscv-BM1_JUlF.js +1 -0
  300. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  301. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  302. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  303. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  304. package/public/assets/rst-B0xPkSld.js +1 -0
  305. package/public/assets/ruby-BvKwtOVI.js +1 -0
  306. package/public/assets/rust-B1yitclQ.js +1 -0
  307. package/public/assets/sas-cz2c8ADy.js +1 -0
  308. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  309. package/public/assets/scala-C151Ov-r.js +1 -0
  310. package/public/assets/scheme-C98Dy4si.js +1 -0
  311. package/public/assets/scss-OYdSNvt2.js +1 -0
  312. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  313. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  314. package/public/assets/shellscript-Yzrsuije.js +1 -0
  315. package/public/assets/shellsession-BADoaaVG.js +1 -0
  316. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  317. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  318. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  319. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  320. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  321. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  322. package/public/assets/solidity-BbcW6ACK.js +1 -0
  323. package/public/assets/soy-Brmx7dQM.js +1 -0
  324. package/public/assets/sparql-rVzFXLq3.js +1 -0
  325. package/public/assets/splunk-BtCnVYZw.js +1 -0
  326. package/public/assets/sql-BLtJtn59.js +1 -0
  327. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  328. package/public/assets/stata-BH5u7GGu.js +1 -0
  329. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  330. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  331. package/public/assets/swift-Dg5xB15N.js +1 -0
  332. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  333. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  334. package/public/assets/systemd-4A_iFExJ.js +1 -0
  335. package/public/assets/talonscript-CkByrt1z.js +1 -0
  336. package/public/assets/tasl-QIJgUcNo.js +1 -0
  337. package/public/assets/tcl-dwOrl1Do.js +1 -0
  338. package/public/assets/templ-W15q3VgB.js +1 -0
  339. package/public/assets/terraform-BETggiCN.js +1 -0
  340. package/public/assets/tex-Cppo0RY3.js +1 -0
  341. package/public/assets/tokyo-night-hegEt444.js +1 -0
  342. package/public/assets/toml-vGWfd6FD.js +1 -0
  343. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  344. package/public/assets/tsv-B_m7g4N7.js +1 -0
  345. package/public/assets/tsx-COt5Ahok.js +1 -0
  346. package/public/assets/turtle-BsS91CYL.js +1 -0
  347. package/public/assets/twig-CO9l9SDP.js +1 -0
  348. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  349. package/public/assets/typespec-Df68jz8_.js +1 -0
  350. package/public/assets/typst-DHCkPAjA.js +1 -0
  351. package/public/assets/v-BcVCzyr7.js +1 -0
  352. package/public/assets/vala-CsfeWuGM.js +1 -0
  353. package/public/assets/vb-D17OF-Vu.js +1 -0
  354. package/public/assets/verilog-BQ8w6xss.js +1 -0
  355. package/public/assets/vesper-DU1UobuO.js +1 -0
  356. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  357. package/public/assets/viml-CJc9bBzg.js +1 -0
  358. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  359. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  360. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  361. package/public/assets/vue-CCoi5OLL.js +1 -0
  362. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  363. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  364. package/public/assets/vyper-CDx5xZoG.js +1 -0
  365. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  366. package/public/assets/wasm-MzD3tlZU.js +1 -0
  367. package/public/assets/wenyan-BV7otONQ.js +1 -0
  368. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  369. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  370. package/public/assets/wit-5i3qLPDT.js +1 -0
  371. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  372. package/public/assets/xml-sdJ4AIDG.js +1 -0
  373. package/public/assets/xsl-CtQFsRM5.js +1 -0
  374. package/public/assets/yaml-Buea-lGh.js +1 -0
  375. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  376. package/public/assets/zig-VOosw3JB.js +1 -0
  377. package/public/favicon.ico +0 -0
  378. package/public/index.html +38 -0
  379. package/public/loading.html +28 -0
  380. package/public/logo.png +0 -0
  381. package/public/manifest.webmanifest +1 -0
  382. package/public/maskable-icon-512x512.png +0 -0
  383. package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  384. package/public/monaco/vs/base/worker/workerMain.js +31 -0
  385. package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  386. package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  387. package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  388. package/public/monaco/vs/basic-languages/python/python.js +10 -0
  389. package/public/monaco/vs/editor/editor.main.css +8 -0
  390. package/public/monaco/vs/editor/editor.main.js +798 -0
  391. package/public/monaco/vs/language/css/cssMode.js +13 -0
  392. package/public/monaco/vs/language/css/cssWorker.js +77 -0
  393. package/public/monaco/vs/language/html/htmlMode.js +13 -0
  394. package/public/monaco/vs/language/html/htmlWorker.js +454 -0
  395. package/public/monaco/vs/language/json/jsonMode.js +19 -0
  396. package/public/monaco/vs/language/json/jsonWorker.js +42 -0
  397. package/public/monaco/vs/language/typescript/tsMode.js +20 -0
  398. package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
  399. package/public/monaco/vs/loader.js +11 -0
  400. package/public/monaco.worker.js +7 -0
  401. package/public/pwa-192x192.png +0 -0
  402. package/public/pwa-512x512.png +0 -0
  403. package/public/pwa-64x64.png +0 -0
  404. package/public/registerSW.js +1 -0
  405. package/public/sw.js +1 -0
  406. package/public/ui-version.json +3 -0
  407. package/public/workbox-60d14903.js +1 -0
  408. package/scripts/copy-auth-pages.mjs +22 -0
  409. package/scripts/copy-opencode-config.mjs +61 -0
  410. package/scripts/copy-ui-dist.mjs +21 -0
  411. package/src/api-types.ts +326 -0
  412. package/src/auth/auth-store.ts +175 -0
  413. package/src/auth/http-auth.ts +38 -0
  414. package/src/auth/manager.ts +163 -0
  415. package/src/auth/password-hash.ts +49 -0
  416. package/src/auth/session-manager.ts +23 -0
  417. package/src/auth/token-manager.ts +32 -0
  418. package/src/background-processes/manager.ts +519 -0
  419. package/src/bin.ts +29 -0
  420. package/src/config/binaries.ts +192 -0
  421. package/src/config/location.ts +78 -0
  422. package/src/config/schema.ts +104 -0
  423. package/src/config/store.ts +244 -0
  424. package/src/events/bus.ts +45 -0
  425. package/src/filesystem/__tests__/search-cache.test.ts +61 -0
  426. package/src/filesystem/browser.ts +353 -0
  427. package/src/filesystem/search-cache.ts +66 -0
  428. package/src/filesystem/search.ts +184 -0
  429. package/src/index.ts +540 -0
  430. package/src/launcher.ts +177 -0
  431. package/src/loader.ts +21 -0
  432. package/src/logger.ts +133 -0
  433. package/src/opencode-config.ts +31 -0
  434. package/src/plugins/channel.ts +55 -0
  435. package/src/plugins/handlers.ts +36 -0
  436. package/src/releases/dev-release-monitor.ts +118 -0
  437. package/src/releases/release-monitor.ts +149 -0
  438. package/src/server/http-server.ts +693 -0
  439. package/src/server/network-addresses.ts +75 -0
  440. package/src/server/routes/auth-pages/login.html +134 -0
  441. package/src/server/routes/auth-pages/token.html +93 -0
  442. package/src/server/routes/auth.ts +164 -0
  443. package/src/server/routes/background-processes.ts +85 -0
  444. package/src/server/routes/config.ts +76 -0
  445. package/src/server/routes/events.ts +61 -0
  446. package/src/server/routes/filesystem.ts +54 -0
  447. package/src/server/routes/meta.ts +58 -0
  448. package/src/server/routes/plugin.ts +75 -0
  449. package/src/server/routes/storage.ts +66 -0
  450. package/src/server/routes/workspaces.ts +113 -0
  451. package/src/server/routes/worktrees.ts +195 -0
  452. package/src/server/tls.ts +283 -0
  453. package/src/storage/instance-store.ts +64 -0
  454. package/src/ui/__tests__/remote-ui.test.ts +58 -0
  455. package/src/ui/remote-ui.ts +571 -0
  456. package/src/workspaces/git-worktrees.ts +241 -0
  457. package/src/workspaces/instance-events.ts +226 -0
  458. package/src/workspaces/manager.ts +493 -0
  459. package/src/workspaces/opencode-auth.ts +22 -0
  460. package/src/workspaces/runtime.ts +428 -0
  461. package/src/workspaces/worktree-map.ts +129 -0
  462. package/tsconfig.json +17 -0
@@ -0,0 +1,608 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import { createManagedWorktree, listWorktrees, resolveRepoRoot } from "../../workspaces/git-worktrees";
4
+ import { ensureCodenomadGitExclude } from "../../workspaces/worktree-map";
5
+ import { verifyGitHubWebhookSignature } from "./webhook-verify";
6
+ import { createAppOctokit, createInstallationOctokit, getInstallationToken } from "./octokit";
7
+ import { ensureClonedAndUpdated, gitFetchAndResetToRemote, gitRemoteRefExists } from "./git-ops";
8
+ import { clearGitHubWorkspaceContext, setGitHubWorkspaceContext } from "./workspace-context";
9
+ import { clearGitHubWorktreeContext, setGitHubWorktreeContext } from "./worktree-context";
10
+ import { buildOpencodeRequestContext } from "../../opencode/request-context";
11
+ import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
12
+ import { sanitizeGitHubWebhookPayload } from "./sanitize-webhook";
13
+ import { appendCodeNomadBotSignature } from "./bot-signature";
14
+ export class GitHubJobRunner {
15
+ constructor(deps) {
16
+ this.deps = deps;
17
+ this.repoWorkspaceId = new Map();
18
+ this.activeByRepo = new Map();
19
+ this.stopTimersByWorkspace = new Map();
20
+ this.seenDeliveries = new Set();
21
+ this.cachedBotLogin = null;
22
+ this.threads = new Map();
23
+ }
24
+ enqueue(job) {
25
+ this.deps.logger.debug({ deliveryId: job.deliveryId, event: job.event }, "Enqueued GitHub webhook");
26
+ if (job.deliveryId) {
27
+ if (this.seenDeliveries.has(job.deliveryId)) {
28
+ this.deps.logger.debug({ deliveryId: job.deliveryId }, "Dropping duplicate GitHub delivery");
29
+ return;
30
+ }
31
+ this.seenDeliveries.add(job.deliveryId);
32
+ if (this.seenDeliveries.size > 5000) {
33
+ // best-effort cap
34
+ this.seenDeliveries.clear();
35
+ }
36
+ }
37
+ const settings = this.readSettings();
38
+ if (!settings.enabled) {
39
+ this.deps.logger.warn("GitHub integration disabled; ignoring webhook");
40
+ return;
41
+ }
42
+ if (!settings.webhookSecret) {
43
+ this.deps.logger.error("GitHub integration misconfigured (missing webhookSecret)");
44
+ return;
45
+ }
46
+ const valid = verifyGitHubWebhookSignature({
47
+ secret: settings.webhookSecret,
48
+ signatureHeader: job.signature,
49
+ body: job.body,
50
+ });
51
+ if (!valid) {
52
+ this.deps.logger.warn({ deliveryId: job.deliveryId }, "Invalid GitHub webhook signature");
53
+ return;
54
+ }
55
+ if (job.event !== "issue_comment") {
56
+ return;
57
+ }
58
+ let payload;
59
+ try {
60
+ payload = JSON.parse(job.body.toString("utf-8"));
61
+ }
62
+ catch (error) {
63
+ this.deps.logger.warn({ err: error }, "Failed to parse GitHub webhook JSON");
64
+ return;
65
+ }
66
+ if (payload.action !== "created") {
67
+ return;
68
+ }
69
+ const owner = payload.repository?.owner?.login;
70
+ const repo = payload.repository?.name;
71
+ const issueNumber = payload.issue?.number;
72
+ if (!owner || !repo || !issueNumber) {
73
+ this.deps.logger.warn("GitHub webhook missing repository/issue info");
74
+ return;
75
+ }
76
+ if (!this.isAllowedAuthor(settings, payload)) {
77
+ this.deps.logger.info({
78
+ owner,
79
+ repo,
80
+ issueNumber,
81
+ author: payload.comment?.user?.login,
82
+ authorAssociation: payload.comment?.author_association,
83
+ }, "GitHub mention ignored (author not allowed)");
84
+ return;
85
+ }
86
+ const key = `${owner}/${repo}#${issueNumber}`;
87
+ this.submitToThread(key, { job, payload, settings });
88
+ }
89
+ submitToThread(key, item) {
90
+ const state = this.threads.get(key) ?? { running: false };
91
+ if (!state.running) {
92
+ state.running = true;
93
+ this.threads.set(key, state);
94
+ void this.runThread(key, item);
95
+ return;
96
+ }
97
+ // Supersede current run with the latest comment.
98
+ this.deps.logger.info({
99
+ key,
100
+ newDeliveryId: item.job.deliveryId,
101
+ newCommentId: item.payload.comment?.id,
102
+ }, "New comment received; cancelling active GitHub run");
103
+ state.pending = item;
104
+ if (state.current) {
105
+ state.current.cancelRequested = true;
106
+ state.current.controller.abort();
107
+ if (state.current.abortSession) {
108
+ void state.current.abortSession().catch(() => undefined);
109
+ }
110
+ }
111
+ this.threads.set(key, state);
112
+ }
113
+ async runThread(key, item) {
114
+ const state = this.threads.get(key);
115
+ if (!state)
116
+ return;
117
+ const controller = new AbortController();
118
+ state.current = { controller, cancelRequested: false };
119
+ this.threads.set(key, state);
120
+ try {
121
+ await this.handleVerified(item.job, item.payload, item.settings, {
122
+ signal: controller.signal,
123
+ registerAbortSession: (fn) => {
124
+ const current = this.threads.get(key)?.current;
125
+ if (!current)
126
+ return;
127
+ current.abortSession = fn;
128
+ if (current.cancelRequested) {
129
+ void fn().catch(() => undefined);
130
+ }
131
+ },
132
+ });
133
+ }
134
+ finally {
135
+ const latest = this.threads.get(key);
136
+ if (!latest)
137
+ return;
138
+ latest.current = undefined;
139
+ const pending = latest.pending;
140
+ latest.pending = undefined;
141
+ if (pending) {
142
+ this.threads.set(key, latest);
143
+ await this.runThread(key, pending);
144
+ return;
145
+ }
146
+ latest.running = false;
147
+ this.threads.delete(key);
148
+ }
149
+ }
150
+ readSettings() {
151
+ const cfg = this.deps.configStore.get().integrations?.github;
152
+ const webhook = cfg?.webhook;
153
+ return {
154
+ enabled: Boolean(cfg?.enabled),
155
+ appId: cfg?.appId,
156
+ privateKeyPath: cfg?.privateKeyPath,
157
+ webhookSecret: cfg?.webhookSecret,
158
+ workspaceRoot: cfg?.workspaceRoot,
159
+ mentionHandle: cfg?.mentionHandle,
160
+ webhookAgent: typeof webhook?.agent === "string" ? webhook.agent : undefined,
161
+ webhookModel: webhook?.model && typeof webhook.model === "object" && typeof webhook.model.providerId === "string" && typeof webhook.model.modelId === "string"
162
+ ? { providerId: webhook.model.providerId, modelId: webhook.model.modelId }
163
+ : undefined,
164
+ webhookVariant: typeof webhook?.variant === "string" ? webhook.variant : undefined,
165
+ allowedOwners: cfg?.allowedOwners ?? [],
166
+ allowedRepos: cfg?.allowedRepos ?? [],
167
+ allowedUsers: cfg?.allowedUsers ?? [],
168
+ allowedAuthorAssociations: cfg?.allowedAuthorAssociations ?? ["OWNER", "COLLABORATOR"],
169
+ botLogin: cfg?.botLogin,
170
+ };
171
+ }
172
+ isAllowedAuthor(settings, payload) {
173
+ const login = (payload.comment?.user?.login ?? "").trim().toLowerCase();
174
+ if (login) {
175
+ const allowedUsers = (settings.allowedUsers ?? []).map((u) => u.trim().toLowerCase()).filter(Boolean);
176
+ if (allowedUsers.includes(login)) {
177
+ return true;
178
+ }
179
+ }
180
+ const association = payload.comment?.author_association;
181
+ if (!association || typeof association !== "string") {
182
+ return false;
183
+ }
184
+ const allowed = new Set((settings.allowedAuthorAssociations ?? []).map((v) => v.trim().toUpperCase()).filter(Boolean));
185
+ return allowed.has(association.trim().toUpperCase());
186
+ }
187
+ isAllowedRepo(settings, owner, repo) {
188
+ const full = `${owner}/${repo}`;
189
+ if (settings.allowedRepos.length > 0) {
190
+ return settings.allowedRepos.includes(full);
191
+ }
192
+ if (settings.allowedOwners.length > 0) {
193
+ return settings.allowedOwners.includes(owner);
194
+ }
195
+ return true;
196
+ }
197
+ async ensureBotLogin(settings) {
198
+ if (settings.botLogin)
199
+ return settings.botLogin;
200
+ if (this.cachedBotLogin)
201
+ return this.cachedBotLogin;
202
+ if (!settings.appId || !settings.privateKeyPath)
203
+ return null;
204
+ try {
205
+ const appOctokit = createAppOctokit({ appId: settings.appId, privateKeyPath: settings.privateKeyPath });
206
+ const response = await appOctokit.request("GET /app");
207
+ const slug = response.data?.slug;
208
+ if (typeof slug === "string" && slug.trim()) {
209
+ this.cachedBotLogin = `${slug}[bot]`;
210
+ return this.cachedBotLogin;
211
+ }
212
+ }
213
+ catch (error) {
214
+ this.deps.logger.debug({ err: error }, "Failed to resolve GitHub App bot login");
215
+ }
216
+ return null;
217
+ }
218
+ async handleVerified(_job, payload, settings, cancel) {
219
+ if (cancel.signal.aborted) {
220
+ return;
221
+ }
222
+ if (!settings.appId || !settings.privateKeyPath || !settings.webhookSecret) {
223
+ this.deps.logger.error("GitHub integration misconfigured (missing appId/privateKeyPath/webhookSecret)");
224
+ return;
225
+ }
226
+ const installationId = payload.installation?.id;
227
+ if (!installationId || typeof installationId !== "number") {
228
+ this.deps.logger.warn("GitHub webhook missing installation id");
229
+ return;
230
+ }
231
+ const owner = payload.repository?.owner?.login;
232
+ const repo = payload.repository?.name;
233
+ if (!owner || !repo) {
234
+ this.deps.logger.warn("GitHub webhook missing repository info");
235
+ return;
236
+ }
237
+ this.deps.logger.info({ owner, repo }, "GitHub webhook accepted");
238
+ if (!this.isAllowedRepo(settings, owner, repo)) {
239
+ this.deps.logger.info({ owner, repo }, "GitHub repo not allowed; ignoring");
240
+ return;
241
+ }
242
+ const botLogin = await this.ensureBotLogin(settings);
243
+ const authorLogin = payload.comment?.user?.login;
244
+ if (botLogin && authorLogin && authorLogin === botLogin) {
245
+ this.deps.logger.debug({ owner, repo, botLogin }, "Ignoring comment from our own bot");
246
+ return;
247
+ }
248
+ const body = (payload.comment?.body ?? "").trim();
249
+ if (!this.hasTrigger(body, settings)) {
250
+ this.deps.logger.debug({ owner, repo }, "GitHub webhook ignored (no mention trigger)");
251
+ return;
252
+ }
253
+ this.deps.logger.info({ owner, repo, issueNumber: payload.issue?.number, commentId: payload.comment?.id }, "GitHub mention triggered");
254
+ const defaultBranch = payload.repository?.default_branch?.trim() || "main";
255
+ const repoUrl = payload.repository?.clone_url?.trim() || `https://github.com/${owner}/${repo}.git`;
256
+ const workspaceRoot = this.resolveWorkspaceRoot(settings);
257
+ const repoDir = path.join(workspaceRoot, owner, repo);
258
+ const token = await getInstallationToken({ appId: settings.appId, privateKeyPath: settings.privateKeyPath }, installationId);
259
+ const octokit = createInstallationOctokit({ appId: settings.appId, privateKeyPath: settings.privateKeyPath }, installationId);
260
+ // Mark as read.
261
+ await this.addEyesReaction(octokit, owner, repo, payload.comment.id).catch((error) => {
262
+ this.deps.logger.warn({ err: error }, "Failed to add eyes reaction");
263
+ });
264
+ this.deps.logger.info({ owner, repo, commentId: payload.comment.id }, "Added eyes reaction");
265
+ await ensureClonedAndUpdated({ repoUrl, directory: repoDir, defaultBranch, token });
266
+ this.deps.logger.info({ owner, repo, repoDir, defaultBranch }, "Repo ready");
267
+ await ensureCodenomadGitExclude(repoDir, this.deps.logger).catch(() => undefined);
268
+ const fullName = `${owner}/${repo}`;
269
+ const workspaceId = await this.ensureWorkspace(fullName, repoDir);
270
+ this.deps.logger.info({ owner, repo, workspaceId }, "Workspace ready");
271
+ this.acquireWorkspace(fullName, workspaceId);
272
+ try {
273
+ setGitHubWorkspaceContext(workspaceId, {
274
+ installationId,
275
+ owner,
276
+ repo,
277
+ defaultBranch,
278
+ repoUrl,
279
+ botLogin: botLogin ?? undefined,
280
+ });
281
+ const issueNumber = payload.issue.number;
282
+ const commentId = payload.comment.id;
283
+ const isPullRequest = Boolean(payload.issue?.pull_request);
284
+ let worktreeSlug;
285
+ let worktreeDir;
286
+ if (isPullRequest) {
287
+ // For PR comments, use a stable bot worktree/branch keyed by PR number.
288
+ const pr = await octokit.request("GET /repos/{owner}/{repo}/pulls/{pull_number}", {
289
+ owner,
290
+ repo,
291
+ pull_number: issueNumber,
292
+ });
293
+ const prAuthorLogin = pr.data?.user?.login;
294
+ const headRef = pr.data?.head?.ref;
295
+ const headRepoFullName = pr.data?.head?.repo?.full_name;
296
+ const baseRepoFullName = pr.data?.base?.repo?.full_name;
297
+ const baseRef = pr.data?.base?.ref;
298
+ if (typeof baseRef !== "string" || !baseRef.trim()) {
299
+ throw new Error("PR payload missing base ref");
300
+ }
301
+ const isFromFork = Boolean(headRepoFullName && baseRepoFullName && headRepoFullName !== baseRepoFullName);
302
+ const botOwned = Boolean(botLogin && prAuthorLogin && prAuthorLogin === botLogin);
303
+ // If the PR is bot-owned, operate directly on its head branch to avoid creating duplicate PRs.
304
+ // Otherwise, operate on a bot-controlled branch keyed by PR number.
305
+ worktreeSlug = botOwned && typeof headRef === "string" && headRef.trim()
306
+ ? headRef.trim()
307
+ : `codenomad/pr-${issueNumber}`;
308
+ const ensured = await this.ensureWorktree({ repoDir, workspaceFolder: repoDir, slug: worktreeSlug });
309
+ worktreeDir = ensured.directory;
310
+ // Prefer resetting to the remote branch for this worktree.
311
+ // If it doesn't exist yet, initialize from the PR head commit.
312
+ const remoteRef = `refs/heads/${worktreeSlug}`;
313
+ const hasRemoteBranch = await gitRemoteRefExists({ cwd: worktreeDir, remoteUrl: repoUrl, ref: remoteRef, token });
314
+ const resetRef = hasRemoteBranch ? worktreeSlug : `pull/${issueNumber}/head`;
315
+ await gitFetchAndResetToRemote({ cwd: worktreeDir, remoteUrl: repoUrl, ref: resetRef, token });
316
+ const publishBase = botOwned
317
+ ? baseRef.trim()
318
+ : typeof headRef === "string" && headRef.trim() && !isFromFork
319
+ ? headRef.trim()
320
+ : baseRef.trim();
321
+ setGitHubWorktreeContext(workspaceId, worktreeSlug, {
322
+ issueNumber,
323
+ isPullRequest: true,
324
+ publishBase,
325
+ prNumber: issueNumber,
326
+ prFromFork: isFromFork,
327
+ prAuthorLogin: typeof prAuthorLogin === "string" ? prAuthorLogin : undefined,
328
+ });
329
+ this.deps.logger.info({ owner, repo, workspaceId, worktreeSlug, publishBase, botOwned, isFromFork }, "PR worktree ready");
330
+ }
331
+ else {
332
+ // For issue comments, use a stable worktree per issue number.
333
+ worktreeSlug = `codenomad/issue-${issueNumber}`;
334
+ const ensured = await this.ensureWorktree({ repoDir, workspaceFolder: repoDir, slug: worktreeSlug });
335
+ worktreeDir = ensured.directory;
336
+ // Prefer resetting to the remote bot branch (accumulates prior runs).
337
+ // If it doesn't exist yet, initialize from the default branch.
338
+ const remoteBotRef = `refs/heads/${worktreeSlug}`;
339
+ const hasRemoteBotBranch = await gitRemoteRefExists({ cwd: worktreeDir, remoteUrl: repoUrl, ref: remoteBotRef, token });
340
+ const resetRef = hasRemoteBotBranch ? worktreeSlug : defaultBranch;
341
+ await gitFetchAndResetToRemote({ cwd: worktreeDir, remoteUrl: repoUrl, ref: resetRef, token });
342
+ setGitHubWorktreeContext(workspaceId, worktreeSlug, {
343
+ issueNumber,
344
+ isPullRequest: false,
345
+ publishBase: defaultBranch,
346
+ });
347
+ this.deps.logger.info({ owner, repo, workspaceId, worktreeSlug }, "Issue worktree ready");
348
+ }
349
+ await this.runOpencode({
350
+ workspaceId,
351
+ worktreeSlug,
352
+ payload,
353
+ agent: settings.webhookAgent,
354
+ model: settings.webhookModel,
355
+ variant: settings.webhookVariant,
356
+ logger: this.deps.logger,
357
+ signal: cancel.signal,
358
+ registerAbortSession: cancel.registerAbortSession,
359
+ });
360
+ // Success path: the agent is responsible for commenting via the GitHub tool.
361
+ this.deps.logger.info({ owner, repo, issueNumber }, "GitHub job completed");
362
+ }
363
+ catch (error) {
364
+ const name = error?.name;
365
+ if (cancel.signal.aborted || name === "AbortError") {
366
+ this.deps.logger.info({ owner, repo, issueNumber: payload.issue.number }, "GitHub run superseded; skipping failure comment");
367
+ return;
368
+ }
369
+ const message = error instanceof Error ? error.message : String(error);
370
+ this.deps.logger.error({ err: error, owner, repo }, "GitHub job failed");
371
+ // Failure path: comment back with an error so the user knows automation did not complete.
372
+ // Do not @mention the bot in this comment to avoid loops.
373
+ await octokit
374
+ .request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
375
+ owner,
376
+ repo,
377
+ issue_number: payload.issue.number,
378
+ body: appendCodeNomadBotSignature(`Automation failed.\n\n${message}`),
379
+ })
380
+ .catch((commentError) => {
381
+ this.deps.logger.warn({ err: commentError, owner, repo }, "Failed to post failure comment");
382
+ });
383
+ }
384
+ finally {
385
+ this.releaseWorkspace(fullName, workspaceId);
386
+ }
387
+ }
388
+ async ensureWorktree(params) {
389
+ const { repoRoot, isGitRepo } = await resolveRepoRoot(params.workspaceFolder, this.deps.logger);
390
+ if (!isGitRepo) {
391
+ throw new Error("Workspace is not a Git repository");
392
+ }
393
+ const existing = await listWorktrees({ repoRoot, workspaceFolder: params.workspaceFolder, logger: this.deps.logger });
394
+ const match = existing.find((wt) => wt.slug === params.slug);
395
+ if (match) {
396
+ return { slug: match.slug, directory: match.directory, branch: match.branch };
397
+ }
398
+ return await createManagedWorktree({
399
+ repoRoot,
400
+ workspaceFolder: params.workspaceFolder,
401
+ slug: params.slug,
402
+ logger: this.deps.logger,
403
+ });
404
+ }
405
+ resolveWorkspaceRoot(settings) {
406
+ if (settings.workspaceRoot) {
407
+ return settings.workspaceRoot.startsWith("~/")
408
+ ? path.join(process.env.HOME ?? os.homedir(), settings.workspaceRoot.slice(2))
409
+ : settings.workspaceRoot;
410
+ }
411
+ return path.join(process.env.HOME ?? os.homedir(), ".config", "codenomad", "github-workspace");
412
+ }
413
+ hasTrigger(body, settings) {
414
+ const text = (body ?? "").trim();
415
+ if (!text)
416
+ return false;
417
+ const handles = [];
418
+ const configured = (settings.mentionHandle ?? "").trim();
419
+ if (configured)
420
+ handles.push(configured);
421
+ const botLogin = (settings.botLogin ?? "").trim();
422
+ if (botLogin) {
423
+ handles.push(botLogin.replace(/\[bot\]$/i, ""));
424
+ handles.push(botLogin);
425
+ }
426
+ if (handles.length === 0) {
427
+ handles.push("codenomad");
428
+ }
429
+ const unique = Array.from(new Set(handles.map((h) => h.trim()).filter(Boolean)));
430
+ for (const handle of unique) {
431
+ const escaped = handle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
432
+ const re = new RegExp(`(^|\\s)@${escaped}(\\b|\\s|$)`, "i");
433
+ if (re.test(text))
434
+ return true;
435
+ }
436
+ return false;
437
+ }
438
+ async ensureWorkspace(repoFullName, repoDir) {
439
+ const existingId = this.repoWorkspaceId.get(repoFullName);
440
+ if (existingId) {
441
+ const existing = this.deps.workspaceManager.get(existingId);
442
+ if (existing && existing.status === "ready") {
443
+ return existingId;
444
+ }
445
+ }
446
+ const workspace = await this.deps.workspaceManager.create(repoDir, repoFullName, {
447
+ extraEnvironment: {
448
+ CODENOMAD_MODE: "github",
449
+ },
450
+ });
451
+ this.repoWorkspaceId.set(repoFullName, workspace.id);
452
+ return workspace.id;
453
+ }
454
+ acquireWorkspace(repoFullName, workspaceId) {
455
+ const current = this.activeByRepo.get(repoFullName) ?? 0;
456
+ this.activeByRepo.set(repoFullName, current + 1);
457
+ const timer = this.stopTimersByWorkspace.get(workspaceId);
458
+ if (timer) {
459
+ clearTimeout(timer);
460
+ this.stopTimersByWorkspace.delete(workspaceId);
461
+ }
462
+ }
463
+ releaseWorkspace(repoFullName, workspaceId) {
464
+ const current = this.activeByRepo.get(repoFullName) ?? 0;
465
+ const next = Math.max(0, current - 1);
466
+ if (next === 0) {
467
+ this.activeByRepo.delete(repoFullName);
468
+ const timer = setTimeout(() => {
469
+ void this.deps.workspaceManager
470
+ .delete(workspaceId)
471
+ .catch((error) => this.deps.logger.warn({ err: error, workspaceId }, "Failed to stop GitHub workspace"))
472
+ .finally(() => {
473
+ this.stopTimersByWorkspace.delete(workspaceId);
474
+ clearGitHubWorkspaceContext(workspaceId);
475
+ clearGitHubWorktreeContext(workspaceId);
476
+ const mapped = this.repoWorkspaceId.get(repoFullName);
477
+ if (mapped === workspaceId) {
478
+ this.repoWorkspaceId.delete(repoFullName);
479
+ }
480
+ });
481
+ }, 30000);
482
+ this.stopTimersByWorkspace.set(workspaceId, timer);
483
+ return;
484
+ }
485
+ this.activeByRepo.set(repoFullName, next);
486
+ }
487
+ async addEyesReaction(octokit, owner, repo, commentId) {
488
+ await octokit.request("POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions", {
489
+ owner,
490
+ repo,
491
+ comment_id: commentId,
492
+ content: "eyes",
493
+ headers: {
494
+ accept: "application/vnd.github+json",
495
+ },
496
+ });
497
+ }
498
+ async runOpencode(params) {
499
+ if (params.signal.aborted) {
500
+ const err = new Error("Run superseded");
501
+ err.name = "AbortError";
502
+ throw err;
503
+ }
504
+ const ctx = await buildOpencodeRequestContext({
505
+ workspaceManager: this.deps.workspaceManager,
506
+ workspaceId: params.workspaceId,
507
+ worktreeSlug: params.worktreeSlug,
508
+ logger: params.logger,
509
+ });
510
+ const unwrap = async (promise, label) => {
511
+ const result = await promise;
512
+ if (!result) {
513
+ throw new Error(`${label} returned no result`);
514
+ }
515
+ if (result.error) {
516
+ const err = result.error;
517
+ const errName = err && typeof err === "object" ? err.name : undefined;
518
+ if (errName === "AbortError") {
519
+ throw err;
520
+ }
521
+ const msg = err instanceof Error ? err.message : String(err);
522
+ throw new Error(`${label} failed: ${msg}`);
523
+ }
524
+ return result.data;
525
+ };
526
+ // Use the OpenCode SDK with CodeNomad-managed auth + worktree scoping.
527
+ const client = createOpencodeClient({
528
+ baseUrl: ctx.baseUrl,
529
+ headers: ctx.headers,
530
+ });
531
+ const session = await unwrap(client.session.create({}, { signal: params.signal }), "session.create");
532
+ const sessionId = session?.id;
533
+ if (!sessionId || typeof sessionId !== "string") {
534
+ throw new Error("OpenCode session.create returned no id");
535
+ }
536
+ // OpenCode expects message IDs to start with "msg".
537
+ const messageId = `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
538
+ params.logger.info({ workspaceId: params.workspaceId, sessionId }, "Sending OpenCode message");
539
+ params.registerAbortSession(async () => {
540
+ try {
541
+ await client.session.abort({
542
+ sessionID: sessionId,
543
+ }, { throwOnError: false });
544
+ }
545
+ catch {
546
+ // ignore
547
+ }
548
+ });
549
+ const sanitized = sanitizeGitHubWebhookPayload(params.payload);
550
+ const webhookJson = JSON.stringify(sanitized, null, 2);
551
+ const promptText = [
552
+ "You are a GitHub App bot running inside CodeNomad.",
553
+ "You are running in headless mode.",
554
+ "The only way to ask clarifying questions is by posting a GitHub comment using the github tool.",
555
+ "If your confidence/understanding is below 90%, post a clarifying comment on the same issue/PR and end the task.",
556
+ "Use the github tool for all GitHub operations.",
557
+ "Do NOT use gh CLI. It will not be authenticated.",
558
+ "Before taking any action, call github(op=list_issue_comments, issueNumber=<webhook.issue.number>) to read the full thread context.",
559
+ "Prefer using github tool operations:",
560
+ "- github(op=list_issue_comments, issueNumber): list all issue/PR conversation comments (read background)",
561
+ "- github(op=post_issue_comment, issueNumber, body): post a comment (also works for PRs; PRs are issues)",
562
+ "- github(op=add_reaction, commentId, content): add a reaction to an issue comment (content: eyes/rocket/+1/-1/laugh/confused/heart/hooray)",
563
+ "- github(op=publish_pr, title, body?, base?, draft?): push current branch; opens a PR if needed, otherwise returns the existing PR for this branch. Requires clean git state; commit changes first.",
564
+ "The github tool returns a JSON object with op-specific data; prefer using returned fields like number/url for follow-up actions.",
565
+ "If you omit base when calling publish_pr, CodeNomad will pick a default base appropriate for this thread (PR vs issue).",
566
+ "Below is the webhook payload we received (sanitized). Perform tasks based on the user request in comment.body.",
567
+ "At the end, post a comment in the same issue/PR using github(op=post_issue_comment).",
568
+ "",
569
+ "Webhook payload:",
570
+ "```json",
571
+ webhookJson,
572
+ "```",
573
+ "",
574
+ ].join("\n");
575
+ const response = await unwrap(client.session.prompt({
576
+ sessionID: sessionId,
577
+ messageID: messageId,
578
+ ...(params.agent ? { agent: params.agent } : {}),
579
+ ...(params.model
580
+ ? {
581
+ model: {
582
+ providerID: params.model.providerId,
583
+ modelID: params.model.modelId,
584
+ },
585
+ }
586
+ : {}),
587
+ ...(params.variant ? { variant: params.variant } : {}),
588
+ parts: [
589
+ {
590
+ type: "text",
591
+ text: promptText,
592
+ },
593
+ ],
594
+ }, { signal: params.signal }), "session.prompt");
595
+ const infoError = response?.info?.error;
596
+ if (infoError) {
597
+ const message = typeof infoError?.data?.message === "string"
598
+ ? infoError.data.message
599
+ : typeof infoError?.message === "string"
600
+ ? infoError.message
601
+ : undefined;
602
+ const errorText = message?.trim() || JSON.stringify(infoError);
603
+ throw new Error(`OpenCode assistant error: ${errorText}`);
604
+ }
605
+ // Success path: agent posts GitHub comments via tools.
606
+ void response;
607
+ }
608
+ }
@@ -0,0 +1,58 @@
1
+ import fs from "fs";
2
+ import { Octokit } from "@octokit/rest";
3
+ import { createAppAuth } from "@octokit/auth-app";
4
+ let cachedKey = null;
5
+ function readPrivateKey(privateKeyPath) {
6
+ const resolved = privateKeyPath.startsWith("~/")
7
+ ? `${process.env.HOME ?? ""}/${privateKeyPath.slice(2)}`
8
+ : privateKeyPath;
9
+ if (cachedKey && cachedKey.path === resolved) {
10
+ return cachedKey.value;
11
+ }
12
+ const value = fs.readFileSync(resolved, "utf-8");
13
+ cachedKey = { path: resolved, value };
14
+ return value;
15
+ }
16
+ export function createAppOctokit(config) {
17
+ const privateKey = readPrivateKey(config.privateKeyPath);
18
+ const appId = Number(config.appId);
19
+ if (!Number.isFinite(appId)) {
20
+ throw new Error("Invalid GitHub App ID");
21
+ }
22
+ return new Octokit({
23
+ authStrategy: createAppAuth,
24
+ auth: {
25
+ appId,
26
+ privateKey,
27
+ },
28
+ });
29
+ }
30
+ export function createInstallationOctokit(config, installationId) {
31
+ const privateKey = readPrivateKey(config.privateKeyPath);
32
+ const appId = Number(config.appId);
33
+ if (!Number.isFinite(appId)) {
34
+ throw new Error("Invalid GitHub App ID");
35
+ }
36
+ return new Octokit({
37
+ authStrategy: createAppAuth,
38
+ auth: {
39
+ appId,
40
+ privateKey,
41
+ installationId,
42
+ },
43
+ });
44
+ }
45
+ export async function getInstallationToken(config, installationId) {
46
+ const privateKey = readPrivateKey(config.privateKeyPath);
47
+ const appId = Number(config.appId);
48
+ if (!Number.isFinite(appId)) {
49
+ throw new Error("Invalid GitHub App ID");
50
+ }
51
+ const auth = createAppAuth({
52
+ appId,
53
+ privateKey,
54
+ installationId,
55
+ });
56
+ const result = await auth({ type: "installation" });
57
+ return result.token;
58
+ }