@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,528 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, opendir, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { loadProjectContextFiles } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { closeManagedWorktree, createManagedWorktree, resolveManagedWorktreeBase, } from "./git-worktrees.js";
|
|
6
|
+
import { AccessDeniedError, assertAllowedPath, isPathInsideRoot, resolveAllowedPath, } from "./roots.js";
|
|
7
|
+
import { loadWorkspaceSkills, markSkillActivated, resolveSkillReadPath, } from "./skills.js";
|
|
8
|
+
import { loadLocalAgentProfiles, } from "./local-agent-profiles.js";
|
|
9
|
+
export class WorkspaceRegistry {
|
|
10
|
+
config;
|
|
11
|
+
store;
|
|
12
|
+
workspaces = new Map();
|
|
13
|
+
pendingOpens = new Map();
|
|
14
|
+
constructor(config, store) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.store = store;
|
|
17
|
+
}
|
|
18
|
+
async openWorkspace(input, openOptions = {}) {
|
|
19
|
+
const workspaceInput = typeof input === "string" ? { path: input } : input;
|
|
20
|
+
const mode = workspaceInput.mode ?? "checkout";
|
|
21
|
+
if (mode === "worktree") {
|
|
22
|
+
return this.openReusableWorktree(workspaceInput, openOptions.conversationScopeId);
|
|
23
|
+
}
|
|
24
|
+
return this.openReusableCheckout(workspaceInput.path, openOptions.conversationScopeId);
|
|
25
|
+
}
|
|
26
|
+
async listKnownWorktrees(workspace) {
|
|
27
|
+
const sourceRoot = workspace.mode === "worktree" ? workspace.sourceRoot : workspace.root;
|
|
28
|
+
if (!sourceRoot)
|
|
29
|
+
return [];
|
|
30
|
+
const sourceKey = await canonicalPath(sourceRoot);
|
|
31
|
+
const results = [];
|
|
32
|
+
for (const session of this.activeSessions("worktree")) {
|
|
33
|
+
if (!session.sourceRoot)
|
|
34
|
+
continue;
|
|
35
|
+
if (await canonicalPath(session.sourceRoot) !== sourceKey)
|
|
36
|
+
continue;
|
|
37
|
+
const root = await this.validSessionRoot(session);
|
|
38
|
+
if (!root)
|
|
39
|
+
continue;
|
|
40
|
+
results.push({
|
|
41
|
+
workspaceId: session.id,
|
|
42
|
+
path: root,
|
|
43
|
+
baseRef: session.baseRef ?? "HEAD",
|
|
44
|
+
baseSha: session.baseSha ?? "",
|
|
45
|
+
branch: session.branch,
|
|
46
|
+
targetBranch: session.targetBranch,
|
|
47
|
+
managed: session.managed,
|
|
48
|
+
current: session.id === workspace.id,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
async closeWorktree(workspaceId, commitMessage) {
|
|
54
|
+
const workspace = this.getWorkspace(workspaceId);
|
|
55
|
+
if (workspace.mode !== "worktree" || !workspace.worktree?.managed || !workspace.sourceRoot) {
|
|
56
|
+
throw new Error(`Workspace ${workspaceId} is not a managed worktree workspace.`);
|
|
57
|
+
}
|
|
58
|
+
if (workspace.worktree.detached ||
|
|
59
|
+
!workspace.worktree.branch ||
|
|
60
|
+
!workspace.worktree.targetBranch) {
|
|
61
|
+
throw new Error(`Workspace ${workspaceId} is a legacy detached worktree and cannot use the managed close lifecycle. Create a branch for it explicitly or open a new managed worktree.`);
|
|
62
|
+
}
|
|
63
|
+
const managedWorktree = {
|
|
64
|
+
sourceRoot: workspace.sourceRoot,
|
|
65
|
+
path: workspace.worktree.path,
|
|
66
|
+
baseRef: workspace.worktree.baseRef,
|
|
67
|
+
baseSha: workspace.worktree.baseSha,
|
|
68
|
+
branch: workspace.worktree.branch,
|
|
69
|
+
targetBranch: workspace.worktree.targetBranch,
|
|
70
|
+
dirtySource: workspace.worktree.dirtySource,
|
|
71
|
+
detached: false,
|
|
72
|
+
managed: true,
|
|
73
|
+
};
|
|
74
|
+
const result = await closeManagedWorktree({
|
|
75
|
+
worktree: managedWorktree,
|
|
76
|
+
commitMessage,
|
|
77
|
+
config: this.config,
|
|
78
|
+
});
|
|
79
|
+
this.store?.setSessionStatus(workspace.id, "closed");
|
|
80
|
+
this.workspaces.delete(workspace.id);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
async openReusableCheckout(path, conversationScopeId) {
|
|
84
|
+
const allowedPath = assertAllowedPath(path, this.config.allowedRoots);
|
|
85
|
+
const projectKey = await canonicalPath(allowedPath);
|
|
86
|
+
const targetKey = JSON.stringify(["checkout", projectKey, null]);
|
|
87
|
+
const context = await this.openOnce(targetKey, async () => {
|
|
88
|
+
const reusableWorkspace = await this.findReusableWorkspaceByDirectory(projectKey, "checkout");
|
|
89
|
+
return reusableWorkspace
|
|
90
|
+
? this.reusedWorkspaceContext(reusableWorkspace)
|
|
91
|
+
: this.openCheckoutWorkspace(path);
|
|
92
|
+
});
|
|
93
|
+
return this.withConversationContext(context, conversationScopeId, targetKey);
|
|
94
|
+
}
|
|
95
|
+
async openReusableWorktree(input, conversationScopeId) {
|
|
96
|
+
const managedPath = this.tryManagedWorktreePath(input.path);
|
|
97
|
+
if (managedPath) {
|
|
98
|
+
const worktreeKey = await canonicalPath(managedPath);
|
|
99
|
+
const targetKey = JSON.stringify(["worktree-path", worktreeKey]);
|
|
100
|
+
const context = await this.openOnce(targetKey, async () => {
|
|
101
|
+
const reusableWorkspace = await this.findReusableWorkspaceByDirectory(worktreeKey, "worktree");
|
|
102
|
+
if (!reusableWorkspace) {
|
|
103
|
+
throw new Error(`Managed worktree is not registered as an active ForgeRelay workspace: ${managedPath}. Open the source project in worktree mode to create or recover a managed worktree first.`);
|
|
104
|
+
}
|
|
105
|
+
return this.reusedWorkspaceContext(reusableWorkspace);
|
|
106
|
+
});
|
|
107
|
+
return this.withConversationContext(context, conversationScopeId, targetKey);
|
|
108
|
+
}
|
|
109
|
+
const resolvedBase = await resolveManagedWorktreeBase({
|
|
110
|
+
sourcePath: input.path,
|
|
111
|
+
baseRef: input.baseRef,
|
|
112
|
+
config: this.config,
|
|
113
|
+
});
|
|
114
|
+
const sourceKey = await canonicalPath(resolvedBase.sourceRoot);
|
|
115
|
+
const targetKey = JSON.stringify(["worktree", sourceKey, resolvedBase.targetBranch]);
|
|
116
|
+
if (input.newWorktree) {
|
|
117
|
+
const context = await this.openWorktreeWorkspace(input.path, input.baseRef);
|
|
118
|
+
return this.withConversationContext(context, conversationScopeId, targetKey);
|
|
119
|
+
}
|
|
120
|
+
const context = await this.openOnce(targetKey, async () => {
|
|
121
|
+
const reusableWorkspace = await this.findReusableWorktreeBySource(sourceKey, resolvedBase.targetBranch);
|
|
122
|
+
return reusableWorkspace
|
|
123
|
+
? this.reusedWorkspaceContext(reusableWorkspace)
|
|
124
|
+
: this.openWorktreeWorkspace(input.path, input.baseRef);
|
|
125
|
+
});
|
|
126
|
+
return this.withConversationContext(context, conversationScopeId, targetKey);
|
|
127
|
+
}
|
|
128
|
+
async openOnce(operationKey, open) {
|
|
129
|
+
const pending = this.pendingOpens.get(operationKey);
|
|
130
|
+
if (pending) {
|
|
131
|
+
const context = await pending;
|
|
132
|
+
return { ...context, workspaceReused: true };
|
|
133
|
+
}
|
|
134
|
+
const operation = open();
|
|
135
|
+
this.pendingOpens.set(operationKey, operation);
|
|
136
|
+
try {
|
|
137
|
+
return await operation;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
if (this.pendingOpens.get(operationKey) === operation) {
|
|
141
|
+
this.pendingOpens.delete(operationKey);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
withConversationContext(context, conversationScopeId, targetKey) {
|
|
146
|
+
if (!conversationScopeId || !this.store) {
|
|
147
|
+
return { ...context, includeBootstrapContext: true };
|
|
148
|
+
}
|
|
149
|
+
const binding = this.store.getConversationBinding(conversationScopeId, targetKey);
|
|
150
|
+
if (binding?.workspaceSessionId === context.workspace.id) {
|
|
151
|
+
this.store.touchConversationBinding(conversationScopeId, targetKey);
|
|
152
|
+
return { ...context, includeBootstrapContext: false };
|
|
153
|
+
}
|
|
154
|
+
this.store.setConversationBinding({
|
|
155
|
+
conversationScopeId,
|
|
156
|
+
targetKey,
|
|
157
|
+
workspaceSessionId: context.workspace.id,
|
|
158
|
+
});
|
|
159
|
+
return { ...context, includeBootstrapContext: true };
|
|
160
|
+
}
|
|
161
|
+
async findReusableWorkspaceByDirectory(directoryKey, mode) {
|
|
162
|
+
for (const session of this.activeSessions(mode)) {
|
|
163
|
+
const root = await this.validSessionRoot(session);
|
|
164
|
+
if (!root)
|
|
165
|
+
continue;
|
|
166
|
+
if (await canonicalPath(root) !== directoryKey)
|
|
167
|
+
continue;
|
|
168
|
+
return this.getWorkspace(session.id);
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
async findReusableWorktreeBySource(sourceKey, targetBranch) {
|
|
173
|
+
for (const session of this.activeSessions("worktree")) {
|
|
174
|
+
if (!session.sourceRoot || session.targetBranch !== targetBranch)
|
|
175
|
+
continue;
|
|
176
|
+
if (await canonicalPath(session.sourceRoot) !== sourceKey)
|
|
177
|
+
continue;
|
|
178
|
+
const root = await this.validSessionRoot(session);
|
|
179
|
+
if (!root)
|
|
180
|
+
continue;
|
|
181
|
+
return this.getWorkspace(session.id);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
activeSessions(mode) {
|
|
186
|
+
if (this.store) {
|
|
187
|
+
return this.store.listSessions({ status: "active", mode });
|
|
188
|
+
}
|
|
189
|
+
return [...this.workspaces.values()]
|
|
190
|
+
.filter((workspace) => workspace.mode === mode)
|
|
191
|
+
.map((workspace) => ({
|
|
192
|
+
id: workspace.id,
|
|
193
|
+
root: workspace.root,
|
|
194
|
+
status: "active",
|
|
195
|
+
mode: workspace.mode,
|
|
196
|
+
sourceRoot: workspace.sourceRoot,
|
|
197
|
+
baseRef: workspace.worktree?.baseRef,
|
|
198
|
+
baseSha: workspace.worktree?.baseSha,
|
|
199
|
+
branch: workspace.worktree?.branch,
|
|
200
|
+
targetBranch: workspace.worktree?.targetBranch,
|
|
201
|
+
managed: workspace.worktree?.managed ?? false,
|
|
202
|
+
createdAt: "",
|
|
203
|
+
lastUsedAt: "",
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
async validSessionRoot(session) {
|
|
207
|
+
try {
|
|
208
|
+
const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
|
|
209
|
+
const rootStats = await stat(root);
|
|
210
|
+
return rootStats.isDirectory() ? root : undefined;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error instanceof AccessDeniedError ||
|
|
214
|
+
(isErrnoException(error) && (error.code === "ENOENT" || error.code === "ENOTDIR"))) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
tryManagedWorktreePath(path) {
|
|
221
|
+
try {
|
|
222
|
+
return assertAllowedPath(path, [this.config.worktreeRoot]);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (error instanceof AccessDeniedError)
|
|
226
|
+
return undefined;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async reusedWorkspaceContext(workspace) {
|
|
231
|
+
workspace.agentProfiles = await loadLocalAgentProfiles(this.config, workspace.root);
|
|
232
|
+
const agentsFiles = await this.loadInitialAgentsFiles(workspace.root);
|
|
233
|
+
const availableAgentsFiles = await this.findAvailableAgentsFiles(workspace.root, agentsFiles);
|
|
234
|
+
return {
|
|
235
|
+
workspace,
|
|
236
|
+
agentsFiles,
|
|
237
|
+
availableAgentsFiles,
|
|
238
|
+
workspaceReused: true,
|
|
239
|
+
includeBootstrapContext: true,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
getWorkspace(workspaceId) {
|
|
243
|
+
const workspace = this.workspaces.get(workspaceId);
|
|
244
|
+
if (workspace) {
|
|
245
|
+
this.store?.touchSession(workspaceId);
|
|
246
|
+
return workspace;
|
|
247
|
+
}
|
|
248
|
+
const session = this.store?.getSession(workspaceId);
|
|
249
|
+
if (!session || session.status !== "active") {
|
|
250
|
+
throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`);
|
|
251
|
+
}
|
|
252
|
+
const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
|
|
253
|
+
const restoredWorkspace = {
|
|
254
|
+
id: session.id,
|
|
255
|
+
root,
|
|
256
|
+
mode: session.mode,
|
|
257
|
+
sourceRoot: session.sourceRoot,
|
|
258
|
+
worktree: session.mode === "worktree"
|
|
259
|
+
? {
|
|
260
|
+
path: root,
|
|
261
|
+
baseRef: session.baseRef ?? "HEAD",
|
|
262
|
+
baseSha: session.baseSha ?? "",
|
|
263
|
+
branch: session.branch,
|
|
264
|
+
targetBranch: session.targetBranch,
|
|
265
|
+
dirtySource: false,
|
|
266
|
+
detached: !session.branch,
|
|
267
|
+
managed: session.managed,
|
|
268
|
+
}
|
|
269
|
+
: undefined,
|
|
270
|
+
...this.loadSkillsForWorkspace(root),
|
|
271
|
+
agentProfiles: [],
|
|
272
|
+
activatedSkillDirs: new Set(),
|
|
273
|
+
};
|
|
274
|
+
this.store?.touchSession(workspaceId);
|
|
275
|
+
this.workspaces.set(restoredWorkspace.id, restoredWorkspace);
|
|
276
|
+
return restoredWorkspace;
|
|
277
|
+
}
|
|
278
|
+
resolvePath(workspace, inputPath) {
|
|
279
|
+
const absolutePath = resolveAllowedPath(inputPath, workspace.root, [workspace.root]);
|
|
280
|
+
if (!isPathInsideRoot(absolutePath, workspace.root)) {
|
|
281
|
+
throw new Error(`Path is outside workspace root: ${inputPath}`);
|
|
282
|
+
}
|
|
283
|
+
return absolutePath;
|
|
284
|
+
}
|
|
285
|
+
resolveReadPath(workspace, inputPath) {
|
|
286
|
+
try {
|
|
287
|
+
return {
|
|
288
|
+
absolutePath: this.resolvePath(workspace, inputPath),
|
|
289
|
+
readRoots: [workspace.root],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (workspaceError) {
|
|
293
|
+
const skillRead = resolveSkillReadPath(workspace.skills, workspace.activatedSkillDirs, inputPath);
|
|
294
|
+
if (!skillRead)
|
|
295
|
+
throw workspaceError;
|
|
296
|
+
return {
|
|
297
|
+
absolutePath: skillRead.absolutePath,
|
|
298
|
+
readRoots: [workspace.root, skillRead.skill.baseDir],
|
|
299
|
+
skillRead,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
markReadPathLoaded(workspace, readPath) {
|
|
304
|
+
if (readPath.skillRead?.isSkillFile) {
|
|
305
|
+
markSkillActivated(workspace.activatedSkillDirs, readPath.skillRead.skill);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
resolveWorkingDirectory(workspace, workingDirectory) {
|
|
309
|
+
const directory = workingDirectory ? this.resolvePath(workspace, workingDirectory) : workspace.root;
|
|
310
|
+
return assertAllowedPath(directory, [workspace.root]);
|
|
311
|
+
}
|
|
312
|
+
async openCheckoutWorkspace(path) {
|
|
313
|
+
const root = assertAllowedPath(path, this.config.allowedRoots);
|
|
314
|
+
const rootStats = await ensureCheckoutWorkspaceRoot(root);
|
|
315
|
+
if (!rootStats.isDirectory()) {
|
|
316
|
+
throw new Error(`Workspace root must be a directory: ${path}`);
|
|
317
|
+
}
|
|
318
|
+
return this.createWorkspaceContext({ root, mode: "checkout" });
|
|
319
|
+
}
|
|
320
|
+
async openWorktreeWorkspace(path, baseRef) {
|
|
321
|
+
const worktree = await createManagedWorktree({
|
|
322
|
+
sourcePath: path,
|
|
323
|
+
baseRef,
|
|
324
|
+
config: this.config,
|
|
325
|
+
});
|
|
326
|
+
return this.createWorkspaceContext({
|
|
327
|
+
root: worktree.path,
|
|
328
|
+
mode: "worktree",
|
|
329
|
+
sourceRoot: worktree.sourceRoot,
|
|
330
|
+
worktree,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async createWorkspaceContext(input) {
|
|
334
|
+
const workspace = {
|
|
335
|
+
id: `ws_${randomBytes(5).toString("hex")}`,
|
|
336
|
+
root: input.root,
|
|
337
|
+
mode: input.mode,
|
|
338
|
+
sourceRoot: input.sourceRoot,
|
|
339
|
+
worktree: input.worktree,
|
|
340
|
+
...this.loadSkillsForWorkspace(input.root),
|
|
341
|
+
agentProfiles: await loadLocalAgentProfiles(this.config, input.root),
|
|
342
|
+
activatedSkillDirs: new Set(),
|
|
343
|
+
};
|
|
344
|
+
this.store?.createSession({
|
|
345
|
+
id: workspace.id,
|
|
346
|
+
root: workspace.root,
|
|
347
|
+
mode: workspace.mode,
|
|
348
|
+
sourceRoot: workspace.sourceRoot,
|
|
349
|
+
baseRef: workspace.worktree?.baseRef,
|
|
350
|
+
baseSha: workspace.worktree?.baseSha,
|
|
351
|
+
branch: workspace.worktree?.branch,
|
|
352
|
+
targetBranch: workspace.worktree?.targetBranch,
|
|
353
|
+
managed: workspace.worktree?.managed,
|
|
354
|
+
});
|
|
355
|
+
this.workspaces.set(workspace.id, workspace);
|
|
356
|
+
const agentsFiles = await this.loadInitialAgentsFiles(workspace.root);
|
|
357
|
+
const availableAgentsFiles = await this.findAvailableAgentsFiles(workspace.root, agentsFiles);
|
|
358
|
+
return {
|
|
359
|
+
workspace,
|
|
360
|
+
agentsFiles,
|
|
361
|
+
availableAgentsFiles,
|
|
362
|
+
workspaceReused: false,
|
|
363
|
+
includeBootstrapContext: true,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
loadSkillsForWorkspace(root) {
|
|
367
|
+
const result = loadWorkspaceSkills(this.config, root);
|
|
368
|
+
return {
|
|
369
|
+
skills: result.skills,
|
|
370
|
+
skillDiagnostics: result.diagnostics,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
assertWorkspaceRootAllowed(root, mode, sourceRoot) {
|
|
374
|
+
if (mode === "worktree") {
|
|
375
|
+
if (!sourceRoot) {
|
|
376
|
+
throw new Error(`Stored worktree workspace is missing sourceRoot: ${root}`);
|
|
377
|
+
}
|
|
378
|
+
assertAllowedPath(sourceRoot, this.config.allowedRoots);
|
|
379
|
+
return assertAllowedPath(root, [this.config.worktreeRoot]);
|
|
380
|
+
}
|
|
381
|
+
return assertAllowedPath(root, this.config.allowedRoots);
|
|
382
|
+
}
|
|
383
|
+
async loadInitialAgentsFiles(root) {
|
|
384
|
+
const agentDir = resolve(this.config.agentDir);
|
|
385
|
+
const resolvedRoot = (await tryRealpath(root)) ?? root;
|
|
386
|
+
const resolvedAgentDir = (await tryRealpath(agentDir)) ?? agentDir;
|
|
387
|
+
const loadedFiles = [];
|
|
388
|
+
for (const file of loadProjectContextFiles({ cwd: root, agentDir })) {
|
|
389
|
+
const path = resolve(file.path);
|
|
390
|
+
if (!isInitialAgentsFilePath(path, root, agentDir))
|
|
391
|
+
continue;
|
|
392
|
+
const content = await readResolvedContextFile(path, file.content, resolvedRoot, resolvedAgentDir);
|
|
393
|
+
if (content === undefined)
|
|
394
|
+
continue;
|
|
395
|
+
loadedFiles.push({
|
|
396
|
+
path,
|
|
397
|
+
content,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return loadedFiles;
|
|
401
|
+
}
|
|
402
|
+
async findAvailableAgentsFiles(root, loadedFiles) {
|
|
403
|
+
const loadedPaths = new Set(loadedFiles.map((file) => resolve(file.path)));
|
|
404
|
+
const loadedRealPaths = new Set();
|
|
405
|
+
for (const file of loadedFiles) {
|
|
406
|
+
const realPath = await tryRealpath(file.path);
|
|
407
|
+
if (realPath)
|
|
408
|
+
loadedRealPaths.add(realPath);
|
|
409
|
+
}
|
|
410
|
+
const discovered = [];
|
|
411
|
+
await walkWorkspace(root, async (path, entry) => {
|
|
412
|
+
if (!entry.isFile())
|
|
413
|
+
return;
|
|
414
|
+
if (!CONTEXT_FILE_NAMES.has(entry.name))
|
|
415
|
+
return;
|
|
416
|
+
if (loadedPaths.has(path))
|
|
417
|
+
return;
|
|
418
|
+
const realPath = await tryRealpath(path);
|
|
419
|
+
if (realPath && loadedRealPaths.has(realPath))
|
|
420
|
+
return;
|
|
421
|
+
discovered.push({ path });
|
|
422
|
+
});
|
|
423
|
+
return discovered.sort((a, b) => a.path.localeCompare(b.path));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function canonicalPath(path) {
|
|
427
|
+
const missingSegments = [];
|
|
428
|
+
let candidate = path;
|
|
429
|
+
while (true) {
|
|
430
|
+
try {
|
|
431
|
+
return resolve(await realpath(candidate), ...missingSegments.slice().reverse());
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
if (!isErrnoException(error) || (error.code !== "ENOENT" && error.code !== "ENOTDIR")) {
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
const parent = dirname(candidate);
|
|
438
|
+
if (parent === candidate)
|
|
439
|
+
return path;
|
|
440
|
+
missingSegments.push(basename(candidate));
|
|
441
|
+
candidate = parent;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
export async function ensureCheckoutWorkspaceRoot(path, ops = { stat, mkdir }) {
|
|
446
|
+
try {
|
|
447
|
+
return await ops.stat(path);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
if (!isErrnoException(error) || error.code !== "ENOENT") {
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
await ops.mkdir(path, { recursive: true });
|
|
455
|
+
return await ops.stat(path);
|
|
456
|
+
}
|
|
457
|
+
const CONTEXT_FILE_NAMES = new Set(["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"]);
|
|
458
|
+
const SKIPPED_CONTEXT_DIRS = new Set([
|
|
459
|
+
".git",
|
|
460
|
+
".hg",
|
|
461
|
+
".svn",
|
|
462
|
+
".forgerelay",
|
|
463
|
+
".devspace",
|
|
464
|
+
"node_modules",
|
|
465
|
+
"dist",
|
|
466
|
+
"build",
|
|
467
|
+
".next",
|
|
468
|
+
".turbo",
|
|
469
|
+
".cache",
|
|
470
|
+
]);
|
|
471
|
+
export function formatAgentsPath(path, workspaceRoot) {
|
|
472
|
+
if (!workspaceRoot)
|
|
473
|
+
return path.split(sep).join("/");
|
|
474
|
+
const relationship = relative(workspaceRoot, path);
|
|
475
|
+
if (relationship === "" ||
|
|
476
|
+
relationship.startsWith("..") ||
|
|
477
|
+
relationship === ".." ||
|
|
478
|
+
relationship.includes(`..${sep}`)) {
|
|
479
|
+
return path.split(sep).join("/");
|
|
480
|
+
}
|
|
481
|
+
return relationship.split(sep).join("/");
|
|
482
|
+
}
|
|
483
|
+
function isInitialAgentsFilePath(path, root, agentDir) {
|
|
484
|
+
if (isPathInsideRoot(path, agentDir))
|
|
485
|
+
return true;
|
|
486
|
+
return isPathInsideRoot(path, root) && dirname(path) === root;
|
|
487
|
+
}
|
|
488
|
+
async function readResolvedContextFile(path, fallbackContent, root, agentDir) {
|
|
489
|
+
try {
|
|
490
|
+
const resolvedPath = await realpath(path);
|
|
491
|
+
if (!isInitialAgentsFilePath(resolvedPath, root, agentDir))
|
|
492
|
+
return undefined;
|
|
493
|
+
return await readFile(resolvedPath, "utf8");
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return fallbackContent;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function tryRealpath(path) {
|
|
500
|
+
try {
|
|
501
|
+
return await realpath(path);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function walkWorkspace(directory, visit) {
|
|
508
|
+
let entries;
|
|
509
|
+
try {
|
|
510
|
+
entries = await opendir(directory);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
for await (const entry of entries) {
|
|
516
|
+
const path = join(directory, entry.name);
|
|
517
|
+
if (entry.isDirectory()) {
|
|
518
|
+
if (!SKIPPED_CONTEXT_DIRS.has(entry.name)) {
|
|
519
|
+
await walkWorkspace(path, visit);
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
await visit(path, entry);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function isErrnoException(error) {
|
|
527
|
+
return error instanceof Error && "code" in error;
|
|
528
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ForgeRelay Agent Profile Schema
|
|
2
|
+
|
|
3
|
+
ForgeRelay agent profiles are user-owned Markdown files with YAML frontmatter.
|
|
4
|
+
ForgeRelay owns provider invocation; the profile describes which local coding
|
|
5
|
+
runtime to use and what prompt prefix/configuration to apply.
|
|
6
|
+
|
|
7
|
+
## Locations
|
|
8
|
+
|
|
9
|
+
New locations:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
~/.forgerelay/agents/*.md
|
|
13
|
+
.forgerelay/agents/*.md
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Migration compatibility:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
~/.devspace/agents/*.md
|
|
20
|
+
.devspace/agents/*.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
When an existing legacy config directory is reused, its global `agents` folder
|
|
24
|
+
remains active automatically.
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
---
|
|
30
|
+
schema: forgerelay-agent/v1
|
|
31
|
+
name: reviewer
|
|
32
|
+
description: Review changes for correctness and regressions.
|
|
33
|
+
provider: codex
|
|
34
|
+
model: gpt-5.6-codex
|
|
35
|
+
thinking: high
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Review the requested implementation. Focus on correctness, regression risk,
|
|
39
|
+
and missing tests. Return concrete findings before optional suggestions.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The `schema` field is descriptive/versioning metadata for profile authors. The
|
|
43
|
+
current parser focuses on the supported operational fields below.
|
|
44
|
+
|
|
45
|
+
## Fields
|
|
46
|
+
|
|
47
|
+
### `name`
|
|
48
|
+
|
|
49
|
+
Optional. Use lowercase kebab-case when practical. If omitted, ForgeRelay uses
|
|
50
|
+
the filename without `.md`.
|
|
51
|
+
|
|
52
|
+
### `description`
|
|
53
|
+
|
|
54
|
+
Optional but recommended. This is shown in the compact profile catalog returned
|
|
55
|
+
to the MCP host.
|
|
56
|
+
|
|
57
|
+
### `provider`
|
|
58
|
+
|
|
59
|
+
Required. Currently supported provider identifiers are:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
codex
|
|
63
|
+
claude
|
|
64
|
+
opencode
|
|
65
|
+
pi
|
|
66
|
+
cursor
|
|
67
|
+
copilot
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Custom provider strings are rejected. ForgeRelay maps these identifiers through
|
|
71
|
+
its local provider adapter registry.
|
|
72
|
+
|
|
73
|
+
### `model`
|
|
74
|
+
|
|
75
|
+
Optional provider-specific model value. If omitted, the provider default
|
|
76
|
+
applies.
|
|
77
|
+
|
|
78
|
+
### `thinking`
|
|
79
|
+
|
|
80
|
+
Optional provider-specific reasoning/thinking control. ForgeRelay passes through
|
|
81
|
+
supported values rather than translating names between providers.
|
|
82
|
+
|
|
83
|
+
### `disabled`
|
|
84
|
+
|
|
85
|
+
Optional boolean. `true` excludes the profile from the active catalog.
|
|
86
|
+
|
|
87
|
+
## Prompt body
|
|
88
|
+
|
|
89
|
+
The Markdown body after frontmatter is the profile prompt prefix ForgeRelay
|
|
90
|
+
prepends when launching the provider-backed worker.
|
|
91
|
+
|
|
92
|
+
Keep reusable worker policy here; keep task-specific instructions in the prompt
|
|
93
|
+
supplied to the `run` command/tool.
|
|
94
|
+
|
|
95
|
+
## Current CLI workflow
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
forgerelay agents ls
|
|
99
|
+
forgerelay agents run <profile-or-provider-or-id> "<prompt>"
|
|
100
|
+
forgerelay agents show <id>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Optional provider overrides include:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
forgerelay agents run <profile> --model <model> "<prompt>"
|
|
107
|
+
forgerelay agents run <profile> --thinking <level> "<prompt>"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`agents ls` lists existing subagent sessions for the current workspace; it is
|
|
111
|
+
not the profile-definition catalog. The compact profile catalog is returned by
|
|
112
|
+
`open_workspace` when subagents are enabled.
|
|
113
|
+
|
|
114
|
+
## MCP direction
|
|
115
|
+
|
|
116
|
+
Profiles and provider execution are intentionally independent of the current
|
|
117
|
+
CLI transport. A planned first-class MCP subagent tool will wrap the same
|
|
118
|
+
provider adapter/session registry rather than introducing a second agent
|
|
119
|
+
runtime.
|