@akira-tl/forgerelay 0.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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +22 -0
- package/NOTICE.md +19 -0
- package/README.md +270 -0
- package/dist/apply-patch.js +402 -0
- package/dist/artifact-error.js +8 -0
- package/dist/artifact-tools.js +377 -0
- package/dist/cli.js +597 -0
- package/dist/config.js +224 -0
- package/dist/db/client.js +29 -0
- package/dist/db/migrations.js +189 -0
- package/dist/db/schema.js +85 -0
- package/dist/git-worktrees.js +255 -0
- package/dist/git.js +41 -0
- package/dist/incoming-artifacts.js +378 -0
- package/dist/local-agent-adapters.js +653 -0
- package/dist/local-agent-availability.js +119 -0
- package/dist/local-agent-path.js +25 -0
- package/dist/local-agent-profiles.js +136 -0
- package/dist/local-agent-runtime.js +48 -0
- package/dist/local-agent-store.js +145 -0
- package/dist/local-agent-targets.js +80 -0
- package/dist/logger.js +69 -0
- package/dist/mcp/server-instructions.js +78 -0
- package/dist/mcp-sessions.js +56 -0
- package/dist/oauth-provider.js +237 -0
- package/dist/oauth-store.js +138 -0
- package/dist/pi-tools.js +81 -0
- package/dist/process-platform.js +49 -0
- package/dist/process-sessions.js +337 -0
- package/dist/request-meta.js +9 -0
- package/dist/review-checkpoints.js +219 -0
- package/dist/roots.js +37 -0
- package/dist/server-shutdown.js +12 -0
- package/dist/server.js +1596 -0
- package/dist/skills.js +89 -0
- package/dist/ui/.vite/manifest.json +2521 -0
- package/dist/ui/assets/abap-CLvhMVsD.js +1 -0
- package/dist/ui/assets/actionscript-3--17pq3dv.js +1 -0
- package/dist/ui/assets/ada-C5qYipkI.js +1 -0
- package/dist/ui/assets/andromeeda-vGVdxbeo.js +1 -0
- package/dist/ui/assets/angular-html-CovppXt5.js +1 -0
- package/dist/ui/assets/angular-ts-0Y9l6YXu.js +1 -0
- package/dist/ui/assets/apache-U0d_L8uA.js +1 -0
- package/dist/ui/assets/apex-CGTLDQj6.js +1 -0
- package/dist/ui/assets/apl-BmBhiklA.js +1 -0
- package/dist/ui/assets/applescript-CCn79oCD.js +1 -0
- package/dist/ui/assets/ara-4CJ0cIlV.js +1 -0
- package/dist/ui/assets/asciidoc-DE70LPWp.js +1 -0
- package/dist/ui/assets/asm-Cmm7eHzH.js +1 -0
- package/dist/ui/assets/astro-BAmqcqyV.js +1 -0
- package/dist/ui/assets/aurora-x-CDeNXAV0.js +1 -0
- package/dist/ui/assets/awk-BWXHIvNe.js +1 -0
- package/dist/ui/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/dist/ui/assets/ayu-light-C3h-C4tm.js +1 -0
- package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/dist/ui/assets/ballerina-B7ZEbQpA.js +1 -0
- package/dist/ui/assets/bat-Bo4NYOV-.js +1 -0
- package/dist/ui/assets/beancount-D-usSTwE.js +1 -0
- package/dist/ui/assets/berry-DKpUyyne.js +1 -0
- package/dist/ui/assets/bibtex-Ci_nEsc7.js +1 -0
- package/dist/ui/assets/bicep-CUHmPFLl.js +1 -0
- package/dist/ui/assets/bird2-C2hNVINV.js +1 -0
- package/dist/ui/assets/blade-BjFcmRJo.js +1 -0
- package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
- package/dist/ui/assets/c-CaOJn4x0.js +1 -0
- package/dist/ui/assets/c3-CnJL0r0V.js +1 -0
- package/dist/ui/assets/cadence-CQ2zXKGN.js +1 -0
- package/dist/ui/assets/cairo-DLTphjLi.js +1 -0
- package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
- package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
- package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
- package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
- package/dist/ui/assets/clarity-SemFz856.js +1 -0
- package/dist/ui/assets/clojure-DqKBuwfJ.js +1 -0
- package/dist/ui/assets/cmake-Bj61d0ZC.js +1 -0
- package/dist/ui/assets/cobol-DR4zWFMt.js +1 -0
- package/dist/ui/assets/codeowners-C8r90Shi.js +1 -0
- package/dist/ui/assets/codeql-oeQT6MSM.js +1 -0
- package/dist/ui/assets/coffee-ReGSThmM.js +1 -0
- package/dist/ui/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/dist/ui/assets/coq-BrsZFFmf.js +1 -0
- package/dist/ui/assets/cpp-CnlQh6MO.js +1 -0
- package/dist/ui/assets/crystal-BrpPbH1U.js +1 -0
- package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
- package/dist/ui/assets/css-DRBEvyPU.js +1 -0
- package/dist/ui/assets/csv-Dx-8-gkx.js +1 -0
- package/dist/ui/assets/cue-CE9AQfxI.js +1 -0
- package/dist/ui/assets/cypher-ClKdZ_lG.js +1 -0
- package/dist/ui/assets/d-qD-0Kul2.js +1 -0
- package/dist/ui/assets/dark-plus-Cs2F2srj.js +1 -0
- package/dist/ui/assets/dart-DkHntEIa.js +1 -0
- package/dist/ui/assets/dax-BkyTk9wS.js +1 -0
- package/dist/ui/assets/desktop-Dlh5hvp9.js +1 -0
- package/dist/ui/assets/diff-woXpYk--.js +1 -0
- package/dist/ui/assets/docker-IyjqRm3v.js +1 -0
- package/dist/ui/assets/dotenv-_5a1GRtc.js +1 -0
- package/dist/ui/assets/dracula-BHWKrbxM.js +1 -0
- package/dist/ui/assets/dracula-soft-5eyTD99u.js +1 -0
- package/dist/ui/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/dist/ui/assets/edge-cfwpdvl9.js +1 -0
- package/dist/ui/assets/elixir-7owrJxb6.js +1 -0
- package/dist/ui/assets/elm-udiOjODk.js +1 -0
- package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
- package/dist/ui/assets/erb-8ctXHMkS.js +1 -0
- package/dist/ui/assets/erlang-Cphh6RMH.js +1 -0
- package/dist/ui/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/dist/ui/assets/everforest-light-Df2xbC6M.js +1 -0
- package/dist/ui/assets/fennel-DQxkIbk2.js +1 -0
- package/dist/ui/assets/fish-BJitypiv.js +1 -0
- package/dist/ui/assets/fluent-C03EYrpw.js +1 -0
- package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/dist/ui/assets/fsharp-D13ZGOAj.js +1 -0
- package/dist/ui/assets/gdresource-C0sCabJj.js +1 -0
- package/dist/ui/assets/gdscript-Cp2uCuqX.js +1 -0
- package/dist/ui/assets/gdshader-CBce3t8t.js +1 -0
- package/dist/ui/assets/genie-CV2tkWYe.js +1 -0
- package/dist/ui/assets/gherkin-DExj1W_8.js +1 -0
- package/dist/ui/assets/git-commit-BSykSTBG.js +1 -0
- package/dist/ui/assets/git-rebase-P0V_qK1w.js +1 -0
- package/dist/ui/assets/github-dark-C-LZuMrd.js +1 -0
- package/dist/ui/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/dist/ui/assets/github-light-EUqPIrTm.js +1 -0
- package/dist/ui/assets/github-light-default-BXViO-2h.js +1 -0
- package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/dist/ui/assets/gleam-CSRkHgEL.js +1 -0
- package/dist/ui/assets/glimmer-js-CZ14ED7U.js +1 -0
- package/dist/ui/assets/glimmer-ts-DtBSFP-o.js +1 -0
- package/dist/ui/assets/glsl-BX2BkZJK.js +1 -0
- package/dist/ui/assets/gn-ilITqXS6.js +1 -0
- package/dist/ui/assets/gnuplot-7GGW24-e.js +1 -0
- package/dist/ui/assets/go-BJwz_mda.js +1 -0
- package/dist/ui/assets/graphql-Dj_cPv4w.js +1 -0
- package/dist/ui/assets/groovy-CacY0gHj.js +1 -0
- package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/dist/ui/assets/hack-DGRkd8xn.js +1 -0
- package/dist/ui/assets/haml-BAPSvujW.js +1 -0
- package/dist/ui/assets/handlebars-PYksdULG.js +1 -0
- package/dist/ui/assets/haskell-D8IpX4py.js +1 -0
- package/dist/ui/assets/haxe-OTjmBuCE.js +1 -0
- package/dist/ui/assets/hcl-Dh228itO.js +1 -0
- package/dist/ui/assets/heavy-payload-BjE0LSUi.js +4 -0
- package/dist/ui/assets/hjson-CxZEssPk.js +1 -0
- package/dist/ui/assets/hlsl-Cvrh5tZx.js +1 -0
- package/dist/ui/assets/horizon-CE9ld1lL.js +1 -0
- package/dist/ui/assets/horizon-bright-Br1oVSNq.js +1 -0
- package/dist/ui/assets/houston-CsvMBhTu.js +1 -0
- package/dist/ui/assets/html-DloAPGZ1.js +1 -0
- package/dist/ui/assets/html-derivative-C6q86ZrS.js +1 -0
- package/dist/ui/assets/http-fPvYDvHA.js +1 -0
- package/dist/ui/assets/hurl-Dbir4WvE.js +1 -0
- package/dist/ui/assets/hxml-B0Qn7Nwc.js +1 -0
- package/dist/ui/assets/hy-CZbG8q4J.js +1 -0
- package/dist/ui/assets/imba-DsUTQ-LC.js +1 -0
- package/dist/ui/assets/ini-B5eOa1yu.js +1 -0
- package/dist/ui/assets/java-DvkqXg3z.js +1 -0
- package/dist/ui/assets/javascript-TONTV6Ty.js +1 -0
- package/dist/ui/assets/jinja-OM1F5Gct.js +1 -0
- package/dist/ui/assets/jison-CBttugts.js +1 -0
- package/dist/ui/assets/json-Db5oY-7J.js +1 -0
- package/dist/ui/assets/json5-BR5RXkoi.js +1 -0
- package/dist/ui/assets/jsonc-CYpm1nAK.js +1 -0
- package/dist/ui/assets/jsonl-CmCQp5Yx.js +1 -0
- package/dist/ui/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/dist/ui/assets/jssm-DXw9l8Rf.js +1 -0
- package/dist/ui/assets/jsx-DKeJCgsc.js +1 -0
- package/dist/ui/assets/julia-NlIPhXaL.js +1 -0
- package/dist/ui/assets/just-BXBJB-mX.js +1 -0
- package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
- package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +1 -0
- package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +1 -0
- package/dist/ui/assets/kdl-CsD5j6eV.js +1 -0
- package/dist/ui/assets/kotlin-DhhofPvG.js +1 -0
- package/dist/ui/assets/kusto-BUv0MjJC.js +1 -0
- package/dist/ui/assets/laserwave-C_8bwKvT.js +1 -0
- package/dist/ui/assets/latex-CGK-6yDy.js +1 -0
- package/dist/ui/assets/lean-CewbzKMR.js +1 -0
- package/dist/ui/assets/less-DVTAwKKz.js +1 -0
- package/dist/ui/assets/light-plus-DVQuIRkW.js +1 -0
- package/dist/ui/assets/liquid-D7waQ0MY.js +1 -0
- package/dist/ui/assets/llvm-Cm23YOpf.js +1 -0
- package/dist/ui/assets/log-BNLmms1o.js +1 -0
- package/dist/ui/assets/logo-Cluzi2Zq.js +1 -0
- package/dist/ui/assets/lua-C_kkt4h1.js +1 -0
- package/dist/ui/assets/luau-CNKltnaQ.js +1 -0
- package/dist/ui/assets/make-Dixweg8N.js +1 -0
- package/dist/ui/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/ui/assets/marko-BokXuCts.js +1 -0
- package/dist/ui/assets/material-theme-Bm3Qr25_.js +1 -0
- package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/dist/ui/assets/matlab-D7qyCx1q.js +1 -0
- package/dist/ui/assets/mdc-Bk0nnUJA.js +1 -0
- package/dist/ui/assets/mdx-DQZ5AkYe.js +1 -0
- package/dist/ui/assets/mermaid-Bk4SNUv9.js +1 -0
- package/dist/ui/assets/min-dark-BSWPekZh.js +1 -0
- package/dist/ui/assets/min-light-DDpmG2fV.js +1 -0
- package/dist/ui/assets/mipsasm-BMqwQI7S.js +1 -0
- package/dist/ui/assets/mojo-BgCJLMeH.js +1 -0
- package/dist/ui/assets/monokai-CdkpiU2Y.js +1 -0
- package/dist/ui/assets/moonbit-CaWjb8XO.js +1 -0
- package/dist/ui/assets/move-B1IS1UjX.js +1 -0
- package/dist/ui/assets/narrat-_X_XdTYD.js +1 -0
- package/dist/ui/assets/nextflow-Bbiyy34d.js +1 -0
- package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +1 -0
- package/dist/ui/assets/nginx-jM6NF9TV.js +1 -0
- package/dist/ui/assets/night-owl-DhmEMT88.js +1 -0
- package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/dist/ui/assets/nim-hYsL-WKb.js +1 -0
- package/dist/ui/assets/nix-IvuFDN5E.js +1 -0
- package/dist/ui/assets/nord-Cb4Vim4T.js +1 -0
- package/dist/ui/assets/nushell-DcLAeLz5.js +1 -0
- package/dist/ui/assets/objective-c-D1A_Heim.js +1 -0
- package/dist/ui/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/dist/ui/assets/ocaml-O90oeIOV.js +1 -0
- package/dist/ui/assets/odin-B1RWQWA5.js +1 -0
- package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/dist/ui/assets/one-light-D7Lr4KcI.js +1 -0
- package/dist/ui/assets/openscad-BUDT5pXO.js +1 -0
- package/dist/ui/assets/pascal-4ZHwLPI5.js +1 -0
- package/dist/ui/assets/perl-DBI_cUHJ.js +1 -0
- package/dist/ui/assets/php-BX_aQhFl.js +1 -0
- package/dist/ui/assets/pierre-dark-sU4Zdns8.js +1 -0
- package/dist/ui/assets/pierre-dark-soft-HXgun5vs.js +1 -0
- package/dist/ui/assets/pierre-light-DWBk51d8.js +1 -0
- package/dist/ui/assets/pierre-light-soft-CodEzPxf.js +1 -0
- package/dist/ui/assets/pkl-ot-7Btpt.js +1 -0
- package/dist/ui/assets/plastic-DQwYfKfQ.js +1 -0
- package/dist/ui/assets/plsql-DGHpHOYJ.js +1 -0
- package/dist/ui/assets/po-BiJDBrnU.js +1 -0
- package/dist/ui/assets/poimandres-DRFjx7u4.js +1 -0
- package/dist/ui/assets/polar-C7UOKdEL.js +1 -0
- package/dist/ui/assets/postcss-BXeXVLqQ.js +1 -0
- package/dist/ui/assets/powerquery-DNMTfnFr.js +1 -0
- package/dist/ui/assets/powershell-DshXNtvi.js +1 -0
- package/dist/ui/assets/prisma-BsRQq5mF.js +1 -0
- package/dist/ui/assets/prolog-iXnhIJG7.js +1 -0
- package/dist/ui/assets/proto-DB4EqR-F.js +1 -0
- package/dist/ui/assets/pug-DwFAYy1e.js +1 -0
- package/dist/ui/assets/puppet-CDv2pdJW.js +1 -0
- package/dist/ui/assets/purescript-9MfHhQsQ.js +1 -0
- package/dist/ui/assets/python-gzcpVVnB.js +1 -0
- package/dist/ui/assets/qml-eLSwQUKt.js +1 -0
- package/dist/ui/assets/qmldir-DCQb3MpD.js +1 -0
- package/dist/ui/assets/qss-Fe1Jh2GI.js +1 -0
- package/dist/ui/assets/r-BMnQsMLf.js +1 -0
- package/dist/ui/assets/racket-DcIDlBhZ.js +1 -0
- package/dist/ui/assets/raku-B3gFvitq.js +1 -0
- package/dist/ui/assets/razor-CgciiMyw.js +1 -0
- package/dist/ui/assets/red-CJ3rzSJv.js +1 -0
- package/dist/ui/assets/reg-CRGYupPL.js +1 -0
- package/dist/ui/assets/regexp-ZrURNrn5.js +1 -0
- package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
- package/dist/ui/assets/review-payload-HGkpJjjm.js +1 -0
- package/dist/ui/assets/riscv-Ckw8ddFX.js +1 -0
- package/dist/ui/assets/ron-VUp2lXgN.js +1 -0
- package/dist/ui/assets/rose-pine-BthvhNj6.js +1 -0
- package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
- package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +1 -0
- package/dist/ui/assets/rosmsg-CAekHB0j.js +1 -0
- package/dist/ui/assets/rst-EoAOr3M6.js +1 -0
- package/dist/ui/assets/ruby-Dqx5_Rqw.js +1 -0
- package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
- package/dist/ui/assets/sas-Bf6xPLSX.js +1 -0
- package/dist/ui/assets/sass-DXrisJhu.js +1 -0
- package/dist/ui/assets/scala-DKOlJaKm.js +1 -0
- package/dist/ui/assets/scheme-DQCgrYNe.js +1 -0
- package/dist/ui/assets/scrollbar-DX4Ujotb.js +287 -0
- package/dist/ui/assets/scss-DDpW3yTd.js +1 -0
- package/dist/ui/assets/sdbl-bTVj8UrX.js +1 -0
- package/dist/ui/assets/shaderlab-TOUzSsQk.js +1 -0
- package/dist/ui/assets/shellscript-DayAQ-W9.js +1 -0
- package/dist/ui/assets/shellsession-jPr_lSnA.js +1 -0
- package/dist/ui/assets/slack-dark-DnToyrRv.js +1 -0
- package/dist/ui/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/dist/ui/assets/smalltalk-B16xEiuN.js +1 -0
- package/dist/ui/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/dist/ui/assets/solarized-dark-DV17i1UV.js +1 -0
- package/dist/ui/assets/solarized-light-DSh2HLQt.js +1 -0
- package/dist/ui/assets/solidity-CKzVLygQ.js +1 -0
- package/dist/ui/assets/soy-X9adks8G.js +1 -0
- package/dist/ui/assets/sparql-D_iOobhT.js +1 -0
- package/dist/ui/assets/splunk-BC2Px7Mm.js +1 -0
- package/dist/ui/assets/sql-Bmt09AIR.js +1 -0
- package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
- package/dist/ui/assets/stata-DVkNpH8r.js +1 -0
- package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
- package/dist/ui/assets/surrealql-eEXzMb8j.js +1 -0
- package/dist/ui/assets/svelte-CFoBxZF3.js +1 -0
- package/dist/ui/assets/swift-DonLKvLd.js +1 -0
- package/dist/ui/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/dist/ui/assets/system-verilog-DJ5XKQeo.js +1 -0
- package/dist/ui/assets/systemd-BxMlprV5.js +1 -0
- package/dist/ui/assets/talonscript-CohzipZa.js +1 -0
- package/dist/ui/assets/tasl-DMoTqEGO.js +1 -0
- package/dist/ui/assets/tcl-CZd0xW_V.js +1 -0
- package/dist/ui/assets/templ-Da_324JF.js +1 -0
- package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
- package/dist/ui/assets/tex-HAzlmKJ0.js +1 -0
- package/dist/ui/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/dist/ui/assets/toml-CcmNWLt0.js +1 -0
- package/dist/ui/assets/ts-tags-7iLrNH8p.js +1 -0
- package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
- package/dist/ui/assets/tsx-gZaXf8PJ.js +1 -0
- package/dist/ui/assets/turtle-ByJddavk.js +1 -0
- package/dist/ui/assets/twig-CLpQ7p1N.js +1 -0
- package/dist/ui/assets/typescript-B2WWCRI2.js +1 -0
- package/dist/ui/assets/typespec-B88KGewJ.js +1 -0
- package/dist/ui/assets/typst-DI99ib-x.js +1 -0
- package/dist/ui/assets/v-DETTlOr0.js +1 -0
- package/dist/ui/assets/vala-zf12oZj6.js +1 -0
- package/dist/ui/assets/vb-Djn5o6TS.js +1 -0
- package/dist/ui/assets/verilog-CiiDBU1e.js +1 -0
- package/dist/ui/assets/vesper-D5bVUKB1.js +1 -0
- package/dist/ui/assets/vhdl-BroJfC0k.js +1 -0
- package/dist/ui/assets/viml-DvXPmvsu.js +1 -0
- package/dist/ui/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/dist/ui/assets/vitesse-light-VbXTXTou.js +1 -0
- package/dist/ui/assets/vue-No_8Jygd.js +1 -0
- package/dist/ui/assets/vue-html-kHJg98Kk.js +1 -0
- package/dist/ui/assets/vue-vine-Df4cEYaN.js +1 -0
- package/dist/ui/assets/vyper-CgoNMtux.js +1 -0
- package/dist/ui/assets/wasm-BnjxR4X6.js +1 -0
- package/dist/ui/assets/wasm-ByWQv1Qj.js +1 -0
- package/dist/ui/assets/wenyan-C8pVoKbM.js +1 -0
- package/dist/ui/assets/wgsl-BsKzXJz4.js +1 -0
- package/dist/ui/assets/wikitext-ClFFjSW2.js +1 -0
- package/dist/ui/assets/wit-DdvCle-K.js +1 -0
- package/dist/ui/assets/wolfram-DLL8P-h_.js +1 -0
- package/dist/ui/assets/workspace-app-C_wsC6VL.js +117 -0
- package/dist/ui/assets/workspace-app-DHvE2ojW.css +1 -0
- package/dist/ui/assets/xml-DUZ05dpG.js +1 -0
- package/dist/ui/assets/xsl-Bwp3w2OF.js +1 -0
- package/dist/ui/assets/yaml-IA_-TEM1.js +1 -0
- package/dist/ui/assets/zenscript-BnlCZFoB.js +1 -0
- package/dist/ui/assets/zig-CMLA9XwU.js +1 -0
- package/dist/ui/workspace-app.html +15 -0
- package/dist/user-config.js +97 -0
- package/dist/workspace-store.js +162 -0
- package/dist/workspaces.js +528 -0
- package/docs/agent-profile-schema.md +119 -0
- package/docs/artifact-exchange.md +63 -0
- package/docs/chatgpt-coding-workflow.md +183 -0
- package/docs/configuration.md +197 -0
- package/docs/gotchas.md +243 -0
- package/docs/roadmap.md +195 -0
- package/docs/security.md +142 -0
- package/docs/setup.md +132 -0
- package/docs/versioning.md +189 -0
- package/examples/agents/claude-implementer.md +30 -0
- package/examples/agents/codex-explorer.md +27 -0
- package/examples/agents/codex-qa-tester.md +29 -0
- package/examples/agents/codex-worker.md +25 -0
- package/examples/agents/copilot-reviewer.md +25 -0
- package/examples/agents/cursor-agent-worker.md +26 -0
- package/examples/agents/opencode-explorer.md +27 -0
- package/examples/agents/pi-reviewer.md +27 -0
- package/package.json +102 -0
- package/scripts/dev-server.mjs +120 -0
- package/scripts/fix-node-pty-permissions.mjs +22 -0
- package/scripts/release-version.mjs +207 -0
- package/skills/subagent-delegation/SKILL.md +132 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveShellCommand, terminateProcessTree } from "./process-platform.js";
|
|
3
|
+
const DEFAULT_EXEC_YIELD_MS = 10_000;
|
|
4
|
+
const DEFAULT_INTERACTIVE_YIELD_MS = 250;
|
|
5
|
+
const DEFAULT_POLL_YIELD_MS = 5_000;
|
|
6
|
+
const MAX_COMMAND_YIELD_MS = 30_000;
|
|
7
|
+
const MAX_POLL_YIELD_MS = 110_000;
|
|
8
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;
|
|
9
|
+
const DEFAULT_BUFFER_CHARACTERS = 1_000_000;
|
|
10
|
+
const COMPLETED_SESSION_TTL_MS = 5 * 60 * 1_000;
|
|
11
|
+
const DEFAULT_COLUMNS = 80;
|
|
12
|
+
const DEFAULT_ROWS = 24;
|
|
13
|
+
function boundedInteger(value, fallback, maximum) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return fallback;
|
|
16
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
17
|
+
throw new Error("Duration and output limits must be non-negative.");
|
|
18
|
+
}
|
|
19
|
+
return Math.min(Math.floor(value), maximum);
|
|
20
|
+
}
|
|
21
|
+
function terminalSize(value, fallback) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return fallback;
|
|
24
|
+
if (!Number.isInteger(value) || value < 1 || value > 1_000) {
|
|
25
|
+
throw new Error("Terminal dimensions must be integers between 1 and 1000.");
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function processEnvironment(input) {
|
|
30
|
+
return {
|
|
31
|
+
...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)),
|
|
32
|
+
NO_COLOR: "1",
|
|
33
|
+
TERM: "dumb",
|
|
34
|
+
PAGER: "cat",
|
|
35
|
+
GIT_PAGER: "cat",
|
|
36
|
+
GH_PAGER: "cat",
|
|
37
|
+
CODEX_CI: "1",
|
|
38
|
+
LANG: process.env.LANG ?? "C.UTF-8",
|
|
39
|
+
LC_ALL: process.env.LC_ALL ?? "C.UTF-8",
|
|
40
|
+
...(input?.workspaceId ? { FORGERELAY_WORKSPACE_ID: input.workspaceId, DEVSPACE_WORKSPACE_ID: input.workspaceId } : {}),
|
|
41
|
+
...(input?.workspaceRoot ? { FORGERELAY_WORKSPACE_ROOT: input.workspaceRoot, DEVSPACE_WORKSPACE_ROOT: input.workspaceRoot } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function codePointLength(value) {
|
|
45
|
+
return Array.from(value).length;
|
|
46
|
+
}
|
|
47
|
+
function sliceCodePoints(value, start, end) {
|
|
48
|
+
return Array.from(value).slice(start, end).join("");
|
|
49
|
+
}
|
|
50
|
+
function takeHead(value, count) {
|
|
51
|
+
if (count <= 0)
|
|
52
|
+
return "";
|
|
53
|
+
return sliceCodePoints(value, 0, count);
|
|
54
|
+
}
|
|
55
|
+
function takeTail(value, count) {
|
|
56
|
+
if (count <= 0)
|
|
57
|
+
return "";
|
|
58
|
+
const characters = Array.from(value);
|
|
59
|
+
return characters.slice(Math.max(0, characters.length - count)).join("");
|
|
60
|
+
}
|
|
61
|
+
function splitBudget(maxCharacters) {
|
|
62
|
+
return {
|
|
63
|
+
head: Math.ceil(maxCharacters / 2),
|
|
64
|
+
tail: Math.floor(maxCharacters / 2),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function formatHeadTail(head, tail, omittedCharacters) {
|
|
68
|
+
if (omittedCharacters <= 0)
|
|
69
|
+
return head + tail;
|
|
70
|
+
return `${head}\n... output truncated (${omittedCharacters} characters omitted) ...\n${tail}`;
|
|
71
|
+
}
|
|
72
|
+
export class HeadTailBuffer {
|
|
73
|
+
maxCharacters;
|
|
74
|
+
head = "";
|
|
75
|
+
tail = "";
|
|
76
|
+
totalCharacters = 0;
|
|
77
|
+
constructor(maxCharacters) {
|
|
78
|
+
this.maxCharacters = maxCharacters;
|
|
79
|
+
if (!Number.isInteger(maxCharacters) || maxCharacters < 1) {
|
|
80
|
+
throw new Error("Head/tail buffer limit must be a positive integer.");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
append(output) {
|
|
84
|
+
if (!output)
|
|
85
|
+
return;
|
|
86
|
+
const previousTotal = this.totalCharacters;
|
|
87
|
+
this.totalCharacters += codePointLength(output);
|
|
88
|
+
if (this.totalCharacters <= this.maxCharacters) {
|
|
89
|
+
this.head += output;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const budget = splitBudget(this.maxCharacters);
|
|
93
|
+
if (previousTotal <= this.maxCharacters) {
|
|
94
|
+
const fullOutput = this.head + output;
|
|
95
|
+
this.head = takeHead(fullOutput, budget.head);
|
|
96
|
+
this.tail = takeTail(fullOutput, budget.tail);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.tail = takeTail(this.tail + output, budget.tail);
|
|
100
|
+
}
|
|
101
|
+
hasOutput() {
|
|
102
|
+
return this.totalCharacters > 0;
|
|
103
|
+
}
|
|
104
|
+
drain(maxCharacters) {
|
|
105
|
+
if (!Number.isInteger(maxCharacters) || maxCharacters < 1) {
|
|
106
|
+
throw new Error("Output limit must be a positive integer.");
|
|
107
|
+
}
|
|
108
|
+
const omittedByBuffer = Math.max(0, this.totalCharacters - codePointLength(this.head) - codePointLength(this.tail));
|
|
109
|
+
const retained = formatHeadTail(this.head, this.tail, omittedByBuffer);
|
|
110
|
+
const output = truncateOutput(retained, maxCharacters);
|
|
111
|
+
const truncated = omittedByBuffer > 0 || output.truncated;
|
|
112
|
+
this.head = "";
|
|
113
|
+
this.tail = "";
|
|
114
|
+
this.totalCharacters = 0;
|
|
115
|
+
return { output: output.output, truncated };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function truncateOutput(output, maxCharacters) {
|
|
119
|
+
const outputCharacters = codePointLength(output);
|
|
120
|
+
if (outputCharacters <= maxCharacters)
|
|
121
|
+
return { output, truncated: false };
|
|
122
|
+
const marker = "\n... output truncated ...\n";
|
|
123
|
+
const markerCharacters = codePointLength(marker);
|
|
124
|
+
const available = Math.max(0, maxCharacters - markerCharacters);
|
|
125
|
+
const budget = splitBudget(available);
|
|
126
|
+
return {
|
|
127
|
+
output: takeHead(output, budget.head) + marker + takeTail(output, budget.tail),
|
|
128
|
+
truncated: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export class ProcessSessionManager {
|
|
132
|
+
sessions = new Map();
|
|
133
|
+
maxBufferCharacters;
|
|
134
|
+
completedSessionTtlMs;
|
|
135
|
+
nextSessionId = 1;
|
|
136
|
+
constructor(options = {}) {
|
|
137
|
+
this.maxBufferCharacters = options.maxBufferCharacters ?? DEFAULT_BUFFER_CHARACTERS;
|
|
138
|
+
this.completedSessionTtlMs = options.completedSessionTtlMs ?? COMPLETED_SESSION_TTL_MS;
|
|
139
|
+
}
|
|
140
|
+
async start(input) {
|
|
141
|
+
const session = this.createSession(input);
|
|
142
|
+
this.sessions.set(session.id, session);
|
|
143
|
+
try {
|
|
144
|
+
if (input.tty && process.platform !== "win32")
|
|
145
|
+
await this.startPty(session, input);
|
|
146
|
+
else
|
|
147
|
+
this.startPipe(session, input);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
this.sessions.delete(session.id);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
const yieldTimeMs = boundedInteger(input.yieldTimeMs, DEFAULT_EXEC_YIELD_MS, MAX_COMMAND_YIELD_MS);
|
|
154
|
+
await this.waitForExit(session, yieldTimeMs);
|
|
155
|
+
const snapshot = this.consume(session, input.maxOutputTokens);
|
|
156
|
+
if (!session.running)
|
|
157
|
+
this.removeSession(session.id);
|
|
158
|
+
return snapshot;
|
|
159
|
+
}
|
|
160
|
+
async write(input) {
|
|
161
|
+
const session = this.getOwnedSession(input.workspaceId, input.sessionId);
|
|
162
|
+
const chars = input.chars ?? "";
|
|
163
|
+
const interactionRequested = chars.length > 0 || input.columns !== undefined || input.rows !== undefined;
|
|
164
|
+
if (input.columns !== undefined || input.rows !== undefined) {
|
|
165
|
+
session.columns = terminalSize(input.columns, session.columns);
|
|
166
|
+
session.rows = terminalSize(input.rows, session.rows);
|
|
167
|
+
if (!session.process?.resize) {
|
|
168
|
+
throw new Error(`Process session ${session.id} is not a PTY and cannot be resized.`);
|
|
169
|
+
}
|
|
170
|
+
session.process.resize(session.columns, session.rows);
|
|
171
|
+
}
|
|
172
|
+
const interruptRequested = chars.includes("\u0003") && session.running;
|
|
173
|
+
if (interruptRequested) {
|
|
174
|
+
session.process?.kill("SIGINT");
|
|
175
|
+
}
|
|
176
|
+
const writableChars = chars.replaceAll("\u0003", "");
|
|
177
|
+
if (writableChars && session.running)
|
|
178
|
+
session.process?.write(writableChars);
|
|
179
|
+
if ((interactionRequested || !session.buffer.hasOutput()) && session.running) {
|
|
180
|
+
const fallback = interactionRequested ? DEFAULT_INTERACTIVE_YIELD_MS : DEFAULT_POLL_YIELD_MS;
|
|
181
|
+
const maximum = interactionRequested ? MAX_COMMAND_YIELD_MS : MAX_POLL_YIELD_MS;
|
|
182
|
+
const yieldTimeMs = boundedInteger(input.yieldTimeMs, fallback, maximum);
|
|
183
|
+
await this.waitForExit(session, yieldTimeMs);
|
|
184
|
+
}
|
|
185
|
+
const snapshot = this.consume(session, input.maxOutputTokens);
|
|
186
|
+
if (!session.running)
|
|
187
|
+
this.removeSession(session.id);
|
|
188
|
+
return snapshot;
|
|
189
|
+
}
|
|
190
|
+
terminate(workspaceId, sessionId) {
|
|
191
|
+
const session = this.getOwnedSession(workspaceId, sessionId);
|
|
192
|
+
if (session.running)
|
|
193
|
+
session.process?.kill("SIGTERM");
|
|
194
|
+
}
|
|
195
|
+
shutdown() {
|
|
196
|
+
for (const session of this.sessions.values()) {
|
|
197
|
+
if (session.cleanupTimer)
|
|
198
|
+
clearTimeout(session.cleanupTimer);
|
|
199
|
+
if (session.running)
|
|
200
|
+
session.process?.kill("SIGTERM");
|
|
201
|
+
}
|
|
202
|
+
this.sessions.clear();
|
|
203
|
+
}
|
|
204
|
+
async waitForExit(session, yieldTimeMs) {
|
|
205
|
+
let timer;
|
|
206
|
+
try {
|
|
207
|
+
await Promise.race([
|
|
208
|
+
session.exitPromise,
|
|
209
|
+
new Promise((resolve) => {
|
|
210
|
+
timer = setTimeout(resolve, yieldTimeMs);
|
|
211
|
+
}),
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (timer)
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
createSession(input) {
|
|
220
|
+
let resolveExit = () => undefined;
|
|
221
|
+
const exitPromise = new Promise((resolve) => {
|
|
222
|
+
resolveExit = resolve;
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
id: this.nextSessionId++,
|
|
226
|
+
workspaceId: input.workspaceId,
|
|
227
|
+
startedAt: Date.now(),
|
|
228
|
+
columns: terminalSize(input.columns, DEFAULT_COLUMNS),
|
|
229
|
+
rows: terminalSize(input.rows, DEFAULT_ROWS),
|
|
230
|
+
buffer: new HeadTailBuffer(this.maxBufferCharacters),
|
|
231
|
+
running: true,
|
|
232
|
+
exitPromise,
|
|
233
|
+
resolveExit,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
startPipe(session, input) {
|
|
237
|
+
const shell = resolveShellCommand(input.command);
|
|
238
|
+
const detached = process.platform !== "win32";
|
|
239
|
+
const child = spawn(input.command, {
|
|
240
|
+
cwd: input.cwd,
|
|
241
|
+
env: processEnvironment({
|
|
242
|
+
workspaceId: input.workspaceId,
|
|
243
|
+
workspaceRoot: input.workspaceRoot,
|
|
244
|
+
}),
|
|
245
|
+
stdio: "pipe",
|
|
246
|
+
windowsHide: true,
|
|
247
|
+
detached,
|
|
248
|
+
shell: shell.executable,
|
|
249
|
+
});
|
|
250
|
+
session.process = {
|
|
251
|
+
write: (data) => child.stdin.write(data),
|
|
252
|
+
kill: (signal = "SIGTERM") => terminateProcessTree(child, signal, detached),
|
|
253
|
+
resize: input.tty ? () => undefined : undefined,
|
|
254
|
+
};
|
|
255
|
+
child.stdout.on("data", (data) => this.append(session, data.toString("utf8")));
|
|
256
|
+
child.stderr.on("data", (data) => this.append(session, data.toString("utf8")));
|
|
257
|
+
child.on("error", (error) => this.append(session, `${error.message}\n`));
|
|
258
|
+
child.on("close", (code, signal) => this.finish(session, code ?? undefined, signal ?? undefined));
|
|
259
|
+
}
|
|
260
|
+
async startPty(session, input) {
|
|
261
|
+
let nodePty;
|
|
262
|
+
try {
|
|
263
|
+
nodePty = await import("node-pty");
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
throw new Error("PTY support requires the optional node-pty dependency.");
|
|
267
|
+
}
|
|
268
|
+
const shell = resolveShellCommand(input.command);
|
|
269
|
+
let pty;
|
|
270
|
+
try {
|
|
271
|
+
pty = nodePty.spawn(shell.executable, shell.args, {
|
|
272
|
+
cwd: input.cwd,
|
|
273
|
+
env: processEnvironment({
|
|
274
|
+
workspaceId: input.workspaceId,
|
|
275
|
+
workspaceRoot: input.workspaceRoot,
|
|
276
|
+
}),
|
|
277
|
+
name: "xterm-256color",
|
|
278
|
+
cols: session.columns,
|
|
279
|
+
rows: session.rows,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
session.process = {
|
|
286
|
+
write: (data) => pty.write(data),
|
|
287
|
+
kill: (signal) => pty.kill(signal),
|
|
288
|
+
resize: (columns, rows) => pty.resize(columns, rows),
|
|
289
|
+
};
|
|
290
|
+
pty.onData((data) => this.append(session, data));
|
|
291
|
+
pty.onExit(({ exitCode, signal }) => {
|
|
292
|
+
this.finish(session, exitCode, signal === 0 ? undefined : String(signal));
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
finish(session, exitCode, signal) {
|
|
296
|
+
if (!session.running)
|
|
297
|
+
return;
|
|
298
|
+
session.running = false;
|
|
299
|
+
session.exitCode = exitCode;
|
|
300
|
+
session.signal = signal;
|
|
301
|
+
session.resolveExit();
|
|
302
|
+
session.cleanupTimer = setTimeout(() => this.sessions.delete(session.id), this.completedSessionTtlMs);
|
|
303
|
+
session.cleanupTimer.unref();
|
|
304
|
+
}
|
|
305
|
+
append(session, output) {
|
|
306
|
+
session.buffer.append(output);
|
|
307
|
+
}
|
|
308
|
+
consume(session, maxOutputTokens) {
|
|
309
|
+
const limit = boundedInteger(maxOutputTokens, DEFAULT_MAX_OUTPUT_TOKENS, 100_000);
|
|
310
|
+
const maxCharacters = Math.max(256, limit * 4);
|
|
311
|
+
const buffered = session.buffer.drain(maxCharacters);
|
|
312
|
+
return {
|
|
313
|
+
sessionId: session.running ? session.id : undefined,
|
|
314
|
+
output: buffered.output,
|
|
315
|
+
outputTruncated: buffered.truncated,
|
|
316
|
+
running: session.running,
|
|
317
|
+
exitCode: session.exitCode,
|
|
318
|
+
signal: session.signal,
|
|
319
|
+
wallTimeMs: Date.now() - session.startedAt,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
getOwnedSession(workspaceId, sessionId) {
|
|
323
|
+
const session = this.sessions.get(sessionId);
|
|
324
|
+
if (!session)
|
|
325
|
+
throw new Error(`Unknown process session: ${sessionId}`);
|
|
326
|
+
if (session.workspaceId !== workspaceId) {
|
|
327
|
+
throw new Error(`Process session ${sessionId} does not belong to workspace ${workspaceId}.`);
|
|
328
|
+
}
|
|
329
|
+
return session;
|
|
330
|
+
}
|
|
331
|
+
removeSession(sessionId) {
|
|
332
|
+
const session = this.sessions.get(sessionId);
|
|
333
|
+
if (session?.cleanupTimer)
|
|
334
|
+
clearTimeout(session.cleanupTimer);
|
|
335
|
+
this.sessions.delete(sessionId);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function metadataString(meta, key) {
|
|
2
|
+
if (typeof meta !== "object" || meta === null)
|
|
3
|
+
return undefined;
|
|
4
|
+
const value = meta[key];
|
|
5
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
6
|
+
}
|
|
7
|
+
export function openAiConversationScopeId(meta) {
|
|
8
|
+
return metadataString(meta, "openai/session");
|
|
9
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { git, getGitEligibility, safeWorkspaceRefSegment } from "./git.js";
|
|
5
|
+
const REVIEW_REF_PREFIX = "refs/devspace/review";
|
|
6
|
+
export function createReviewCheckpointManager() {
|
|
7
|
+
const states = new Map();
|
|
8
|
+
const initializations = new Map();
|
|
9
|
+
return {
|
|
10
|
+
async initializeWorkspace({ workspaceId, root }) {
|
|
11
|
+
const existingState = states.get(workspaceId);
|
|
12
|
+
assertWorkspaceRoot(existingState, workspaceId, root);
|
|
13
|
+
if (existingState?.root === root && existingState.gitRoot !== undefined) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const pending = initializations.get(workspaceId);
|
|
17
|
+
if (pending) {
|
|
18
|
+
await pending;
|
|
19
|
+
assertWorkspaceRoot(states.get(workspaceId), workspaceId, root);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const initialize = initializeWorkspaceState(states, workspaceId, root);
|
|
23
|
+
initializations.set(workspaceId, initialize);
|
|
24
|
+
try {
|
|
25
|
+
await initialize;
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
if (initializations.get(workspaceId) === initialize) {
|
|
29
|
+
initializations.delete(workspaceId);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
async reviewChanges({ workspaceId, root, since = "last_shown", markReviewed = true }) {
|
|
34
|
+
let state = states.get(workspaceId);
|
|
35
|
+
assertWorkspaceRoot(state, workspaceId, root);
|
|
36
|
+
if (!isReadyState(state)) {
|
|
37
|
+
await this.initializeWorkspace({ workspaceId, root });
|
|
38
|
+
state = states.get(workspaceId);
|
|
39
|
+
}
|
|
40
|
+
assertWorkspaceRoot(state, workspaceId, root);
|
|
41
|
+
if (!state?.gitRoot) {
|
|
42
|
+
throw new Error(state?.diagnostic ?? "show_changes requires a Git workspace in this version.");
|
|
43
|
+
}
|
|
44
|
+
let effectiveSince = since;
|
|
45
|
+
let usedWorkspaceOpenFallback = false;
|
|
46
|
+
if (since === "last_shown" && !state.baselineRefAvailable) {
|
|
47
|
+
if (!state.openRefAvailable) {
|
|
48
|
+
throw new Error("Review checkpoints are missing; show_changes cannot reconstruct that history safely.");
|
|
49
|
+
}
|
|
50
|
+
effectiveSince = "workspace_open";
|
|
51
|
+
usedWorkspaceOpenFallback = true;
|
|
52
|
+
}
|
|
53
|
+
else if (since === "workspace_open" && !state.openRefAvailable) {
|
|
54
|
+
throw new Error("The workspace-open review checkpoint is missing; show_changes cannot reconstruct that history safely.");
|
|
55
|
+
}
|
|
56
|
+
const baselineRef = effectiveSince === "workspace_open" ? state.openRef : state.baselineRef;
|
|
57
|
+
const baseline = (await git(state.gitRoot, ["rev-parse", "--verify", `${baselineRef}^{commit}`])).stdout.trim();
|
|
58
|
+
const current = await createWorkingTreeSnapshot(state.gitRoot);
|
|
59
|
+
const patch = (await git(state.gitRoot, ["diff", "--binary", "--no-color", baseline, current], {
|
|
60
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
61
|
+
})).stdout;
|
|
62
|
+
const numstat = (await git(state.gitRoot, ["diff", "--numstat", "-z", baseline, current], {
|
|
63
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
64
|
+
})).stdout;
|
|
65
|
+
const files = parseNumstat(numstat);
|
|
66
|
+
const summary = summarizeFiles(files);
|
|
67
|
+
if (markReviewed) {
|
|
68
|
+
await git(state.gitRoot, ["update-ref", state.baselineRef, current]);
|
|
69
|
+
state.baselineRefAvailable = true;
|
|
70
|
+
}
|
|
71
|
+
const fallbackNote = usedWorkspaceOpenFallback
|
|
72
|
+
? ` The last-shown checkpoint was missing, so changes were compared from workspace open${markReviewed ? " and the baseline was re-established" : ""}.`
|
|
73
|
+
: "";
|
|
74
|
+
return {
|
|
75
|
+
result: `${summary.files === 0
|
|
76
|
+
? `No changes since ${effectiveSince === "workspace_open" ? "workspace open" : "last shown changes"}.`
|
|
77
|
+
: `Changed ${summary.files} ${summary.files === 1 ? "file" : "files"} (+${summary.additions} -${summary.removals}).`}${fallbackNote}`,
|
|
78
|
+
summary,
|
|
79
|
+
files,
|
|
80
|
+
patch,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function assertWorkspaceRoot(state, workspaceId, root) {
|
|
86
|
+
if (state && state.root !== root) {
|
|
87
|
+
throw new Error(`Review checkpoint workspace root mismatch for ${workspaceId}.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function initializeWorkspaceState(states, workspaceId, root) {
|
|
91
|
+
const refs = reviewRefs(workspaceId);
|
|
92
|
+
const state = {
|
|
93
|
+
root,
|
|
94
|
+
...refs,
|
|
95
|
+
openRefAvailable: false,
|
|
96
|
+
baselineRefAvailable: false,
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const eligibility = await getGitEligibility(root);
|
|
100
|
+
if (!eligibility.ok || !eligibility.gitRoot) {
|
|
101
|
+
state.diagnostic = eligibility.message ?? "show_changes requires a Git workspace in this version.";
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const [openCommit, baselineCommit] = await Promise.all([
|
|
105
|
+
commitForRef(eligibility.gitRoot, state.openRef),
|
|
106
|
+
commitForRef(eligibility.gitRoot, state.baselineRef),
|
|
107
|
+
]);
|
|
108
|
+
if (!openCommit && !baselineCommit) {
|
|
109
|
+
const initialCommit = await createWorkingTreeSnapshot(eligibility.gitRoot);
|
|
110
|
+
await git(eligibility.gitRoot, ["update-ref", state.openRef, initialCommit]);
|
|
111
|
+
await git(eligibility.gitRoot, ["update-ref", state.baselineRef, initialCommit]);
|
|
112
|
+
state.openRefAvailable = true;
|
|
113
|
+
state.baselineRefAvailable = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
state.openRefAvailable = openCommit !== undefined;
|
|
117
|
+
state.baselineRefAvailable = baselineCommit !== undefined;
|
|
118
|
+
}
|
|
119
|
+
state.gitRoot = eligibility.gitRoot;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
state.diagnostic = error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
states.set(workspaceId, state);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function isReadyState(state) {
|
|
129
|
+
return state?.gitRoot !== undefined;
|
|
130
|
+
}
|
|
131
|
+
async function commitForRef(gitRoot, ref) {
|
|
132
|
+
try {
|
|
133
|
+
return (await git(gitRoot, ["rev-parse", "--verify", `${ref}^{commit}`])).stdout.trim();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function reviewRefs(workspaceId) {
|
|
140
|
+
const segment = safeWorkspaceRefSegment(workspaceId);
|
|
141
|
+
return {
|
|
142
|
+
openRef: `${REVIEW_REF_PREFIX}/${segment}/open`,
|
|
143
|
+
baselineRef: `${REVIEW_REF_PREFIX}/${segment}/baseline`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function createWorkingTreeSnapshot(gitRoot) {
|
|
147
|
+
const tempDir = await mkdtemp(join(tmpdir(), "forgerelay-review-index-"));
|
|
148
|
+
const indexPath = join(tempDir, "index");
|
|
149
|
+
const env = checkpointEnv(indexPath);
|
|
150
|
+
try {
|
|
151
|
+
await git(gitRoot, ["read-tree", "HEAD"], { env });
|
|
152
|
+
await git(gitRoot, ["add", "-A", "--", "."], { env });
|
|
153
|
+
const tree = (await git(gitRoot, ["write-tree"], { env })).stdout.trim();
|
|
154
|
+
const parent = (await git(gitRoot, ["rev-parse", "--verify", "HEAD^{commit}"])).stdout.trim();
|
|
155
|
+
return (await git(gitRoot, ["commit-tree", tree, "-p", parent, "-m", "ForgeRelay review snapshot"], { env })).stdout.trim();
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function checkpointEnv(indexPath) {
|
|
162
|
+
return {
|
|
163
|
+
GIT_INDEX_FILE: indexPath,
|
|
164
|
+
GIT_AUTHOR_NAME: "ForgeRelay",
|
|
165
|
+
GIT_AUTHOR_EMAIL: "forgerelay@users.noreply.local",
|
|
166
|
+
GIT_COMMITTER_NAME: "ForgeRelay",
|
|
167
|
+
GIT_COMMITTER_EMAIL: "forgerelay@users.noreply.local",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function parseNumstat(output) {
|
|
171
|
+
const fields = output.split("\0").filter((field) => field.length > 0);
|
|
172
|
+
const files = [];
|
|
173
|
+
for (let index = 0; index < fields.length;) {
|
|
174
|
+
const header = fields[index++] ?? "";
|
|
175
|
+
const parts = header.split("\t");
|
|
176
|
+
const additions = parseStatNumber(parts[0]);
|
|
177
|
+
const removals = parseStatNumber(parts[1]);
|
|
178
|
+
if (parts.length >= 3) {
|
|
179
|
+
const path = parts[2] ?? "";
|
|
180
|
+
if (path)
|
|
181
|
+
files.push({ path, type: fileType(path, undefined, additions, removals), additions, removals });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const previousPath = fields[index++];
|
|
185
|
+
const path = fields[index++];
|
|
186
|
+
if (!path)
|
|
187
|
+
continue;
|
|
188
|
+
files.push({
|
|
189
|
+
path,
|
|
190
|
+
previousPath,
|
|
191
|
+
type: fileType(path, previousPath, additions, removals),
|
|
192
|
+
additions,
|
|
193
|
+
removals,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return files;
|
|
197
|
+
}
|
|
198
|
+
function parseStatNumber(value) {
|
|
199
|
+
if (!value || value === "-")
|
|
200
|
+
return 0;
|
|
201
|
+
const parsed = Number(value);
|
|
202
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
203
|
+
}
|
|
204
|
+
function fileType(path, previousPath, additions, removals) {
|
|
205
|
+
if (previousPath)
|
|
206
|
+
return additions === 0 && removals === 0 ? "rename-pure" : "rename-changed";
|
|
207
|
+
if (additions > 0 && removals === 0)
|
|
208
|
+
return "new";
|
|
209
|
+
if (additions === 0 && removals > 0)
|
|
210
|
+
return "deleted";
|
|
211
|
+
return "change";
|
|
212
|
+
}
|
|
213
|
+
function summarizeFiles(files) {
|
|
214
|
+
return files.reduce((summary, file) => ({
|
|
215
|
+
files: summary.files + 1,
|
|
216
|
+
additions: summary.additions + file.additions,
|
|
217
|
+
removals: summary.removals + file.removals,
|
|
218
|
+
}), { files: 0, additions: 0, removals: 0 });
|
|
219
|
+
}
|
package/dist/roots.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
export class AccessDeniedError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "AccessDeniedError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function expandHomePath(path) {
|
|
10
|
+
if (path === "~")
|
|
11
|
+
return homedir();
|
|
12
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
|
13
|
+
return resolve(homedir(), path.slice(2));
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
}
|
|
17
|
+
export function isPathInsideRoot(path, root) {
|
|
18
|
+
const resolvedPath = resolve(expandHomePath(path));
|
|
19
|
+
const resolvedRoot = resolve(expandHomePath(root));
|
|
20
|
+
const relationship = relative(resolvedRoot, resolvedPath);
|
|
21
|
+
return (relationship === "" ||
|
|
22
|
+
(!isAbsolute(relationship) &&
|
|
23
|
+
!relationship.startsWith("..") &&
|
|
24
|
+
relationship !== ".." &&
|
|
25
|
+
!relationship.includes(`..${sep}`)));
|
|
26
|
+
}
|
|
27
|
+
export function assertAllowedPath(path, allowedRoots) {
|
|
28
|
+
const resolvedPath = resolve(expandHomePath(path));
|
|
29
|
+
if (allowedRoots.some((root) => isPathInsideRoot(resolvedPath, root))) {
|
|
30
|
+
return resolvedPath;
|
|
31
|
+
}
|
|
32
|
+
throw new AccessDeniedError(`Path is outside allowed roots: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
export function resolveAllowedPath(inputPath, cwd, allowedRoots) {
|
|
35
|
+
const absolutePath = resolve(cwd, inputPath);
|
|
36
|
+
return assertAllowedPath(absolutePath, allowedRoots);
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function shutdownHttpServer(httpServer, closeApplication) {
|
|
2
|
+
const httpClosed = new Promise((resolve, reject) => {
|
|
3
|
+
httpServer.close((error) => {
|
|
4
|
+
if (error)
|
|
5
|
+
reject(error);
|
|
6
|
+
else
|
|
7
|
+
resolve();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
await closeApplication();
|
|
11
|
+
await httpClosed;
|
|
12
|
+
}
|