@sunshinelife83/hearth 1.0.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/LICENSE +21 -0
- package/README.md +323 -0
- package/bin/hearth-agentd.js +4 -0
- package/bin/hearth.js +4 -0
- package/bin/run-entrypoint.js +20 -0
- package/dist/agent-tools.js +738 -0
- package/dist/app-version.js +16 -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-workspace.js +36 -0
- package/dist/cli.js +1086 -0
- package/dist/config-migration.js +82 -0
- package/dist/config-schema.js +136 -0
- package/dist/config.js +115 -0
- package/dist/context/context-tools.js +150 -0
- package/dist/context/repo-map.js +102 -0
- package/dist/dashboard/app.js +226 -0
- package/dist/dashboard/index.html +45 -0
- package/dist/dashboard/landing.html +57 -0
- package/dist/dashboard/styles.css +23 -0
- package/dist/dashboard.js +271 -0
- package/dist/db/client.js +29 -0
- package/dist/db/migrations.js +262 -0
- package/dist/db/schema.js +85 -0
- package/dist/device-tokens.js +61 -0
- package/dist/git-worktrees.js +134 -0
- package/dist/git.js +41 -0
- package/dist/incoming-artifacts.js +378 -0
- package/dist/local-agent-acp.js +768 -0
- package/dist/local-agent-adapters.js +20 -0
- package/dist/local-agent-availability.js +92 -0
- package/dist/local-agent-catalog.js +51 -0
- package/dist/local-agent-claude.js +334 -0
- package/dist/local-agent-client.js +519 -0
- package/dist/local-agent-codex.js +532 -0
- package/dist/local-agent-config.js +34 -0
- package/dist/local-agent-daemon-lifecycle.js +195 -0
- package/dist/local-agent-daemon-main.js +86 -0
- package/dist/local-agent-daemon-protocol.js +302 -0
- package/dist/local-agent-daemon.js +407 -0
- package/dist/local-agent-errors.js +378 -0
- package/dist/local-agent-grok.js +202 -0
- package/dist/local-agent-manager.js +715 -0
- package/dist/local-agent-opencode.js +463 -0
- package/dist/local-agent-path.js +24 -0
- package/dist/local-agent-pi-sandbox.js +472 -0
- package/dist/local-agent-pi.js +298 -0
- package/dist/local-agent-presentation.js +104 -0
- package/dist/local-agent-profiles.js +127 -0
- package/dist/local-agent-runtime-pool.js +481 -0
- package/dist/local-agent-runtime.js +1 -0
- package/dist/local-agent-store.js +193 -0
- package/dist/local-agent-targets.js +97 -0
- package/dist/logger.js +97 -0
- package/dist/machine-id.js +35 -0
- package/dist/mcp-modern-server.js +65 -0
- package/dist/oauth-provider.js +331 -0
- package/dist/oauth-store.js +144 -0
- package/dist/onboarding.js +35 -0
- package/dist/orchestration/brief.js +70 -0
- package/dist/orchestration/lanes.js +145 -0
- package/dist/orchestration/result.js +46 -0
- package/dist/orchestration/scope.js +61 -0
- package/dist/pi-tools.js +58 -0
- package/dist/policy/command-policy.js +104 -0
- package/dist/policy/env-allowlist.js +96 -0
- package/dist/policy/sandbox.js +68 -0
- package/dist/policy/workspace-profiles.js +47 -0
- package/dist/process-journal.js +116 -0
- package/dist/process-platform.js +49 -0
- package/dist/process-sessions.js +388 -0
- package/dist/rate-limit.js +77 -0
- package/dist/request-meta.js +9 -0
- package/dist/review-checkpoints.js +349 -0
- package/dist/roots.js +37 -0
- package/dist/server-shutdown.js +12 -0
- package/dist/server.js +980 -0
- package/dist/skills.js +84 -0
- package/dist/snapshot-manager.js +123 -0
- package/dist/stdio-server.js +80 -0
- package/dist/task-store.js +203 -0
- package/dist/task-tools.js +455 -0
- package/dist/tool-surfaces/claude.js +220 -0
- package/dist/tool-surfaces/codex.js +255 -0
- package/dist/tool-surfaces/index.js +15 -0
- package/dist/tool-surfaces/shared.js +228 -0
- package/dist/tool-surfaces/types.js +27 -0
- package/dist/ui/.vite/manifest.json +2544 -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-DrbO3_Xe.js +1 -0
- package/dist/ui/assets/angular-ts-CmTVzHJx.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-BOpCpqXf.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-CxPeBwei.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-DtsAAG4P.js +1 -0
- package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
- package/dist/ui/assets/c-D9hLjNUM.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-C1SWlvZm.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-B71R3v2U.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--5a4c3v4.js +1 -0
- package/dist/ui/assets/crystal-AtqWYtE0.js +1 -0
- package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
- package/dist/ui/assets/css-CbqKSHI-.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-BPwY_RrJ.js +1 -0
- package/dist/ui/assets/elixir-DVH2yLJf.js +1 -0
- package/dist/ui/assets/elm-BrleA6gS.js +1 -0
- package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
- package/dist/ui/assets/erb-2SHiYe30.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-Bc7atMaV.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-DXeTDPtW.js +1 -0
- package/dist/ui/assets/glimmer-ts-CEOoXHU4.js +1 -0
- package/dist/ui/assets/glsl-5-43-Gsg.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-Bk7Ah1KA.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-DUHz4rCJ.js +1 -0
- package/dist/ui/assets/haml-BPCuuTPR.js +1 -0
- package/dist/ui/assets/handlebars-CHxmUsnU.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/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-BBJqizz0.js +1 -0
- package/dist/ui/assets/html-derivative-ShTPCcKN.js +1 -0
- package/dist/ui/assets/http-VFTyziwA.js +1 -0
- package/dist/ui/assets/hurl-B8yxTa4r.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-BZ5DAgJZ.js +1 -0
- package/dist/ui/assets/javascript-C_zuIEaC.js +1 -0
- package/dist/ui/assets/jinja-DWd6xfNr.js +1 -0
- package/dist/ui/assets/jison-BbY63d1V.js +1 -0
- package/dist/ui/assets/json-C4DnajZs.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-C4AACieZ.js +1 -0
- package/dist/ui/assets/julia-sRu7EFMW.js +1 -0
- package/dist/ui/assets/just-DpZ9Sx1v.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-B6a_uum7.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-B2sVpjjr.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-DxJJll61.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-Dx8mw-Wx.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-vNKeXEwR.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-DScQAGLT.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-lAJch9uT.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-C_GkVo_E.js +1 -0
- package/dist/ui/assets/php-DziV3K6J.js +1 -0
- package/dist/ui/assets/pierre-dark-CpLgRqie.js +1 -0
- package/dist/ui/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/dist/ui/assets/pierre-dark-soft-kZQmAZld.js +1 -0
- package/dist/ui/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/dist/ui/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/dist/ui/assets/pierre-light-CoaEpmwp.js +1 -0
- package/dist/ui/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/dist/ui/assets/pierre-light-soft-lWLdNTOI.js +1 -0
- package/dist/ui/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/dist/ui/assets/pierre-light-vibrant-D80Fkn33.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-z75UIWVK.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-Cmm4coz1.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-Brd0axBE.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-DSbwmoiu.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-Bs3Uk-pY.js +1 -0
- package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
- package/dist/ui/assets/review-payload-CbwCBKB1.js +1580 -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-ChIyKpUC.js +1 -0
- package/dist/ui/assets/ruby-BdAmvOMO.js +1 -0
- package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
- package/dist/ui/assets/sas-DpXn8hc5.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/scss-atX1Uuef.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-BqgcAeMK.js +1 -0
- package/dist/ui/assets/shellsession-DfQSMRN5.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-DzP7zZTB.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-gmaLk66z.js +1 -0
- package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
- package/dist/ui/assets/stata-CJ0YTMcV.js +1 -0
- package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
- package/dist/ui/assets/surrealql-LH7FciQz.js +1 -0
- package/dist/ui/assets/svelte-BME2PU6V.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-DHBNIfYW.js +1 -0
- package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
- package/dist/ui/assets/tex-C8-kPEzO.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-CiewV0vR.js +1 -0
- package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
- package/dist/ui/assets/tsx-CUYPqmnq.js +1 -0
- package/dist/ui/assets/turtle-ByJddavk.js +1 -0
- package/dist/ui/assets/twig-BTkvsl0b.js +1 -0
- package/dist/ui/assets/typescript-DDQMaPwj.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-BH5SEWRH.js +1 -0
- package/dist/ui/assets/vue-html-BT7yeYez.js +1 -0
- package/dist/ui/assets/vue-vine-Cs5IN2rs.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-B_BnSEHO.css +1 -0
- package/dist/ui/assets/workspace-app-DSFpnrIC.js +115 -0
- package/dist/ui/assets/xml-D3_1BEcO.js +1 -0
- package/dist/ui/assets/xsl-BBOCDn4Y.js +1 -0
- package/dist/ui/assets/yaml-lThBDT45.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 +228 -0
- package/dist/verification.js +74 -0
- package/dist/workspace-store.js +133 -0
- package/dist/workspaces.js +433 -0
- package/docs/adr/devspace-x-implementation-adrs.md +159 -0
- package/docs/agent-profile-schema.md +203 -0
- package/docs/artifact-exchange.md +44 -0
- package/docs/assets/devspace-logo-light.png +0 -0
- package/docs/assets/devspace-screenshot.png +0 -0
- package/docs/assets/v11-review-ui-after.png +0 -0
- package/docs/assets/v11-review-ui-before.png +0 -0
- package/docs/chatgpt-coding-workflow.md +215 -0
- package/docs/configuration.md +217 -0
- package/docs/gotchas.md +294 -0
- package/docs/local-agent-daemon.md +101 -0
- package/docs/security.md +197 -0
- package/docs/setup.md +174 -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/grok-builder.md +10 -0
- package/examples/agents/opencode-explorer.md +27 -0
- package/examples/agents/pi-reviewer.md +27 -0
- package/package.json +102 -0
- package/schema/v1/hearth.schema.json +529 -0
- package/scripts/copy-dashboard.mjs +9 -0
- package/scripts/fix-node-pty-permissions.mjs +22 -0
- package/scripts/generate-config-schema.ts +9 -0
- package/skills/subagents/SKILL.md +57 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createLocalAgentClient } from "./local-agent-client.js";
|
|
5
|
+
import { logToolCall, resultOutputSchema, textBlock } from "./tool-surfaces/shared.js";
|
|
6
|
+
import { workspaceIdDescription } from "./tool-surfaces/types.js";
|
|
7
|
+
import { resolveExecutionForWorkspace } from "./policy/workspace-profiles.js";
|
|
8
|
+
import { contentText } from "./tool-surfaces/shared.js";
|
|
9
|
+
import { buildDelegationBrief } from "./orchestration/brief.js";
|
|
10
|
+
import { collectTouchedFiles, buildAgentResultEvidence, formatTouchedFiles, AGENT_RESULT_MARKER } from "./orchestration/result.js";
|
|
11
|
+
import { normalizeScopePaths, createOverlapGuard } from "./orchestration/scope.js";
|
|
12
|
+
import { createSnapshot } from "./snapshot-manager.js";
|
|
13
|
+
import { logEvent } from "./logger.js";
|
|
14
|
+
import { resolveLane, effectiveLanes, loadProjectFleet, approveProjectFleet, validateLaneName, } from "./orchestration/lanes.js";
|
|
15
|
+
import { isSubagentProviderEnabled } from "./local-agent-config.js";
|
|
16
|
+
import { buildRepoMap, formatRepoMap } from "./context/repo-map.js";
|
|
17
|
+
import { detectVerificationGates } from "./verification.js";
|
|
18
|
+
/**
|
|
19
|
+
* MCP surface for Hearth's local agent lifecycle (correction #5): external
|
|
20
|
+
* CLI coding agents (Codex, Claude Code, OpenCode, Pi, Cursor, Copilot, Grok)
|
|
21
|
+
* run in the background under the agent daemon and are managed from any MCP
|
|
22
|
+
* client through start/status/output/send/pause/resume/stop/cancel/list.
|
|
23
|
+
*
|
|
24
|
+
* Every tool is workspace-scoped: the caller passes a workspaceId opened via
|
|
25
|
+
* open_workspace and the daemon call is bound to that workspace's root, so
|
|
26
|
+
* MCP clients can never target arbitrary paths.
|
|
27
|
+
*/
|
|
28
|
+
const agentErrorPayloadSchema = z.object({
|
|
29
|
+
code: z.string(),
|
|
30
|
+
message: z.string(),
|
|
31
|
+
retryable: z.boolean().optional(),
|
|
32
|
+
agentId: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
const agentRecordOutputSchema = resultOutputSchema({
|
|
35
|
+
agentId: z.string(),
|
|
36
|
+
status: z.string(),
|
|
37
|
+
provider: z.string(),
|
|
38
|
+
profileName: z.string(),
|
|
39
|
+
model: z.string().optional(),
|
|
40
|
+
latestOutput: z.string().optional(),
|
|
41
|
+
latestResponse: z.string().optional(),
|
|
42
|
+
error: z.string().optional(),
|
|
43
|
+
errorCode: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
class AgentToolsBlockedError extends Error {
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Advisory stall note for long-quiet running turns (uses updatedAt as the
|
|
49
|
+
* liveness heartbeat: every output delta persists and bumps it). Pure and
|
|
50
|
+
* exported for tests; advisory only, never auto-kills.
|
|
51
|
+
*/
|
|
52
|
+
export function stallAdvisory(record, options = {}) {
|
|
53
|
+
if (record.status !== "running" && record.status !== "starting")
|
|
54
|
+
return undefined;
|
|
55
|
+
const quietAfterMs = options.quietAfterMs ?? 5 * 60 * 1000;
|
|
56
|
+
const quietMs = (options.nowMs ?? Date.now()) - Date.parse(record.updatedAt);
|
|
57
|
+
if (!Number.isFinite(quietMs) || quietMs < quietAfterMs)
|
|
58
|
+
return undefined;
|
|
59
|
+
return `Stall suspected: no turn activity for ${Math.round(quietMs / 60000)} minute(s) (last update ${record.updatedAt}). The turn is still tracked; use agent_output to inspect, or agent_stop/agent_cancel to end it.`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Record agent→task ownership in the task's evidence trail. Returns an error
|
|
63
|
+
* message when the task does not exist; the caller turns it into an isError
|
|
64
|
+
* tool result so unknown taskIds fail loudly instead of silently detaching.
|
|
65
|
+
*/
|
|
66
|
+
export function linkAgentToTask(taskStore, taskId, agentId, extra) {
|
|
67
|
+
const record = taskStore.get(taskId);
|
|
68
|
+
if (!record)
|
|
69
|
+
return `Unknown task: ${taskId}. Pass a task_create taskId or omit taskId.`;
|
|
70
|
+
taskStore.appendEvidence(taskId, {
|
|
71
|
+
ts: new Date().toISOString(),
|
|
72
|
+
kind: "note",
|
|
73
|
+
summary: `agent ${agentId} started for task${extra?.correlationId ? ` (correlation ${extra.correlationId})` : ""}`,
|
|
74
|
+
});
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
export function registerAgentTools(target, context) {
|
|
78
|
+
const { config, workspaces, taskStore } = context;
|
|
79
|
+
let client;
|
|
80
|
+
const agentClient = () => {
|
|
81
|
+
client ??= createLocalAgentClient(config);
|
|
82
|
+
return client;
|
|
83
|
+
};
|
|
84
|
+
/** Best-effort concurrent-write guard for this server process (see scope.ts). */
|
|
85
|
+
const overlapGuard = createOverlapGuard();
|
|
86
|
+
const scopeOf = (workspaceId) => {
|
|
87
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
88
|
+
return { workspaceId, workspaceRoot: workspace.root };
|
|
89
|
+
};
|
|
90
|
+
/** Workspace security profile gate for every agent tool invocation.
|
|
91
|
+
* Returns an isError tool result when blocked, so MCP clients see a normal
|
|
92
|
+
* tool error (not a JSON-RPC protocol error from a thrown exception). */
|
|
93
|
+
const blocked = (message) => ({
|
|
94
|
+
content: [textBlock(message)],
|
|
95
|
+
isError: true,
|
|
96
|
+
structuredContent: { result: message },
|
|
97
|
+
});
|
|
98
|
+
const checkAgentsAllowed = (workspaceId) => {
|
|
99
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
100
|
+
const resolved = resolveExecutionForWorkspace(config, workspace.root);
|
|
101
|
+
if (!resolved.agentsAllowed) {
|
|
102
|
+
return `Agent tools are disabled for this workspace by its security profile.`;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
};
|
|
106
|
+
const checkWritableAgentOp = (workspaceId) => {
|
|
107
|
+
const blockedByProfile = checkAgentsAllowed(workspaceId);
|
|
108
|
+
if (blockedByProfile)
|
|
109
|
+
return blockedByProfile;
|
|
110
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
111
|
+
const resolved = resolveExecutionForWorkspace(config, workspace.root);
|
|
112
|
+
if (resolved.mode === "readonly") {
|
|
113
|
+
return "The workspace is in readonly mode; agent delegation requires supervised or autonomous mode.";
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
};
|
|
117
|
+
/** Default watchdog budget recorded in briefs (enforced in Phase 3). */
|
|
118
|
+
const DEFAULT_DELEGATION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
119
|
+
/** Bounded read of repo instruction files, quoted as untrusted brief context. */
|
|
120
|
+
const readRepoInstructions = async (workspaceRoot) => {
|
|
121
|
+
const chunks = [];
|
|
122
|
+
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
123
|
+
try {
|
|
124
|
+
const text = await readFile(join(workspaceRoot, name), "utf8");
|
|
125
|
+
chunks.push(`# ${name}\n${text.slice(0, 750)}`);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Absent or unreadable: no instructions from this file.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return chunks.length > 0 ? chunks.join("\n\n").slice(0, 1500) : undefined;
|
|
132
|
+
};
|
|
133
|
+
/** Compose the self-contained delegation brief for a task-owned run. */
|
|
134
|
+
const composeBrief = async (input) => {
|
|
135
|
+
const task = taskStore.get(input.taskId);
|
|
136
|
+
const resolved = resolveExecutionForWorkspace(config, input.workspaceRoot);
|
|
137
|
+
let repoMapSummary;
|
|
138
|
+
try {
|
|
139
|
+
repoMapSummary = formatRepoMap(await buildRepoMap(input.workspaceRoot, { maxFiles: 1000 }));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
repoMapSummary = undefined;
|
|
143
|
+
}
|
|
144
|
+
const priorFailures = task.evidence
|
|
145
|
+
.filter((entry) => entry.summary.includes("FAILED") || entry.kind === "verification")
|
|
146
|
+
.slice(-5)
|
|
147
|
+
.map((entry) => entry.summary);
|
|
148
|
+
if (task.error)
|
|
149
|
+
priorFailures.push(`task error: ${task.error}`);
|
|
150
|
+
return buildDelegationBrief({
|
|
151
|
+
taskId: input.taskId,
|
|
152
|
+
workspaceId: input.workspaceId,
|
|
153
|
+
workspaceRoot: input.workspaceRoot,
|
|
154
|
+
goal: task.goal,
|
|
155
|
+
callerPrompt: input.callerPrompt,
|
|
156
|
+
mode: resolved.mode,
|
|
157
|
+
writeMode: input.writeMode,
|
|
158
|
+
repoMapSummary,
|
|
159
|
+
repoInstructions: await readRepoInstructions(input.workspaceRoot),
|
|
160
|
+
verificationGates: detectVerificationGates(input.workspaceRoot).map((gate) => ({ name: gate.name, command: gate.command })),
|
|
161
|
+
planSteps: task.plan,
|
|
162
|
+
priorFailures,
|
|
163
|
+
timeoutMs: input.timeoutMs,
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
/** Reverse lookup: which task (if any) owns this agent, via evidence trail. */
|
|
167
|
+
const findTaskForAgent = (workspaceId, agentId) => {
|
|
168
|
+
if (!taskStore)
|
|
169
|
+
return undefined;
|
|
170
|
+
const needle = `agent ${agentId} started for task`;
|
|
171
|
+
for (const task of taskStore.list({ workspaceId })) {
|
|
172
|
+
if (task.evidence.some((entry) => entry.summary.includes(needle)))
|
|
173
|
+
return task.id;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Append structured agent_result evidence for settled turns, deduped per
|
|
179
|
+
* agent+status so polling status/output does not spam the trail. The fresh
|
|
180
|
+
* touched-files scope is always returned to the caller regardless.
|
|
181
|
+
*/
|
|
182
|
+
const maybeRecordAgentResult = async (input) => {
|
|
183
|
+
const touched = await collectTouchedFiles(workspaces.getWorkspace(input.workspaceId).root);
|
|
184
|
+
const settled = new Set(["idle", "error", "stopped", "paused"]);
|
|
185
|
+
if (settled.has(input.record.status)) {
|
|
186
|
+
overlapGuard.release(input.record.id);
|
|
187
|
+
}
|
|
188
|
+
if (taskStore && settled.has(input.record.status)) {
|
|
189
|
+
const taskId = findTaskForAgent(input.workspaceId, input.record.id);
|
|
190
|
+
if (taskId) {
|
|
191
|
+
const task = taskStore.get(taskId);
|
|
192
|
+
const marker = `${AGENT_RESULT_MARKER} ${input.record.id} ${input.record.status}`;
|
|
193
|
+
const already = task?.evidence.some((entry) => entry.summary.startsWith(marker));
|
|
194
|
+
if (!already) {
|
|
195
|
+
const evidence = buildAgentResultEvidence({
|
|
196
|
+
agentId: input.record.id,
|
|
197
|
+
provider: input.record.provider,
|
|
198
|
+
profileName: input.record.profileName,
|
|
199
|
+
status: input.record.status,
|
|
200
|
+
touched,
|
|
201
|
+
finalResponse: input.record.latestResponse ?? input.record.latestOutput ?? "",
|
|
202
|
+
...(input.correlationId ? { correlationId: input.correlationId } : {}),
|
|
203
|
+
});
|
|
204
|
+
taskStore.appendEvidence(taskId, {
|
|
205
|
+
ts: new Date().toISOString(),
|
|
206
|
+
kind: "note",
|
|
207
|
+
summary: evidence.summary,
|
|
208
|
+
details: evidence.details,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return formatTouchedFiles(touched);
|
|
214
|
+
};
|
|
215
|
+
const withTouchedScope = (response, touchedScope) => {
|
|
216
|
+
const prior = typeof response.structuredContent.result === "string" ? response.structuredContent.result : "";
|
|
217
|
+
const result = `${prior}\n\n${touchedScope}`;
|
|
218
|
+
return {
|
|
219
|
+
content: [textBlock(result)],
|
|
220
|
+
structuredContent: { ...response.structuredContent, result },
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
const toResult = (record) => {
|
|
224
|
+
const lines = [
|
|
225
|
+
`Agent ${record.id} (${record.provider}/${record.profileName}) status: ${record.status}.`,
|
|
226
|
+
record.latestOutput ? `Latest output:\n${record.latestOutput}` : undefined,
|
|
227
|
+
record.latestResponse ? `Last response:\n${record.latestResponse}` : undefined,
|
|
228
|
+
record.error ? `Error ${record.errorCode ?? ""}: ${record.error}` : undefined,
|
|
229
|
+
].filter(Boolean).join("\n\n");
|
|
230
|
+
return {
|
|
231
|
+
content: [textBlock(lines)],
|
|
232
|
+
structuredContent: {
|
|
233
|
+
result: lines,
|
|
234
|
+
agentId: record.id,
|
|
235
|
+
status: record.status,
|
|
236
|
+
provider: record.provider,
|
|
237
|
+
profileName: record.profileName,
|
|
238
|
+
...(record.model ? { model: record.model } : {}),
|
|
239
|
+
...(record.latestOutput ? { latestOutput: record.latestOutput } : {}),
|
|
240
|
+
...(record.latestResponse ? { latestResponse: record.latestResponse } : {}),
|
|
241
|
+
...(record.error ? { error: record.error, errorCode: record.errorCode } : {}),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
const toError = (tool, startedAt, workspaceId, error) => {
|
|
246
|
+
logToolCall(config, {
|
|
247
|
+
tool,
|
|
248
|
+
workspaceId,
|
|
249
|
+
success: false,
|
|
250
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
251
|
+
error: `${error.code}: ${error.message}`,
|
|
252
|
+
});
|
|
253
|
+
const parsed = agentErrorPayloadSchema.safeParse(error);
|
|
254
|
+
const message = parsed.success ? error.message : "Local agent operation failed.";
|
|
255
|
+
return {
|
|
256
|
+
content: [textBlock(`${error.code}: ${message}`)],
|
|
257
|
+
isError: true,
|
|
258
|
+
structuredContent: {
|
|
259
|
+
result: `${error.code}: ${message}`,
|
|
260
|
+
errorCode: error.code,
|
|
261
|
+
retryable: parsed.success ? error.retryable === true : false,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
const overridesOf = (input) => {
|
|
266
|
+
const overrides = {
|
|
267
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
268
|
+
...(input.effort !== undefined ? { effort: input.effort } : {}),
|
|
269
|
+
...(input.writeMode !== undefined ? { writeMode: input.writeMode } : {}),
|
|
270
|
+
};
|
|
271
|
+
return Object.keys(overrides).length > 0 ? overrides : undefined;
|
|
272
|
+
};
|
|
273
|
+
target.registerTool("agent_start", {
|
|
274
|
+
title: "Start coding agent",
|
|
275
|
+
description: "Start a background coding agent (provider or profile: codex, claude, opencode, pi, cursor, copilot, grok, or a configured profile name) on this workspace. Returns immediately with an agentId; poll agent_status / agent_output while it runs. Providers must be enabled in Hearth subagents config.",
|
|
276
|
+
inputSchema: {
|
|
277
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
278
|
+
target: z.string().min(1).optional().describe("Provider id or configured agent profile name. Optional when lane selects the implementer."),
|
|
279
|
+
prompt: z.string().min(1).describe("Task brief for the agent."),
|
|
280
|
+
model: z.string().optional().describe("Optional model override."),
|
|
281
|
+
effort: z.string().optional().describe("Optional reasoning effort override."),
|
|
282
|
+
writeMode: z.enum(["read_only", "allowed", "full_access"]).optional()
|
|
283
|
+
.describe("Workspace write permission for the agent. Defaults to allowed."),
|
|
284
|
+
taskId: z.string().optional()
|
|
285
|
+
.describe("Optional task_create taskId that owns this agent; recorded in the task evidence trail."),
|
|
286
|
+
correlationId: z.string().max(120).optional()
|
|
287
|
+
.describe("Optional caller correlation ID, audit-logged with the start."),
|
|
288
|
+
lane: z.string().max(64).optional()
|
|
289
|
+
.describe("Optional fleet lane (e.g. feature, tests, review). Lane dials apply as defaults; explicit flags win. A target contradicting the lane provider is rejected."),
|
|
290
|
+
timeoutMs: z.number().int().positive().max(7 * 24 * 3600 * 1000).optional()
|
|
291
|
+
.describe("Watchdog budget for this delegation in milliseconds (default 30 minutes)."),
|
|
292
|
+
scopePaths: z.array(z.string().min(1)).max(20).optional()
|
|
293
|
+
.describe("File scopes for concurrent-write protection (workspace-relative or absolute). Default: whole workspace. Overlapping live delegations are rejected unless allowConcurrentWrites is set."),
|
|
294
|
+
allowConcurrentWrites: z.boolean().optional()
|
|
295
|
+
.describe("Explicitly allow overlapping writes with another live delegation on this workspace."),
|
|
296
|
+
},
|
|
297
|
+
outputSchema: agentRecordOutputSchema,
|
|
298
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
299
|
+
}, async (input) => {
|
|
300
|
+
const startedAt = performance.now();
|
|
301
|
+
const gate = checkWritableAgentOp(input.workspaceId);
|
|
302
|
+
if (gate)
|
|
303
|
+
return blocked(gate);
|
|
304
|
+
if (input.taskId && !taskStore) {
|
|
305
|
+
return blocked("taskId linkage is unavailable: task runtime is not enabled on this server.");
|
|
306
|
+
}
|
|
307
|
+
if (input.taskId && taskStore && !taskStore.get(input.taskId)) {
|
|
308
|
+
return blocked(`Unknown task: ${input.taskId}. Pass a task_create taskId or omit taskId.`);
|
|
309
|
+
}
|
|
310
|
+
const scope = scopeOf(input.workspaceId);
|
|
311
|
+
// Fleet lane resolution (explicit flags win; contradictions fail loud).
|
|
312
|
+
// Resolved once here and reused below for dials and the brief.
|
|
313
|
+
let laneProvider;
|
|
314
|
+
let laneModel;
|
|
315
|
+
let laneEffort;
|
|
316
|
+
let laneWriteMode;
|
|
317
|
+
let laneTimeoutMs;
|
|
318
|
+
if (input.lane) {
|
|
319
|
+
const badName = validateLaneName(input.lane);
|
|
320
|
+
if (badName)
|
|
321
|
+
return blocked(badName);
|
|
322
|
+
const project = await loadProjectFleet(scope.workspaceRoot, config.stateDir);
|
|
323
|
+
const resolved = resolveLane(config.fleet, project, input.lane);
|
|
324
|
+
if (!resolved.ok)
|
|
325
|
+
return blocked(resolved.error);
|
|
326
|
+
laneProvider = resolved.lane.provider;
|
|
327
|
+
laneModel = resolved.lane.model;
|
|
328
|
+
laneEffort = resolved.lane.effort;
|
|
329
|
+
laneWriteMode = resolved.lane.writeMode;
|
|
330
|
+
laneTimeoutMs = resolved.lane.timeoutMs;
|
|
331
|
+
if (input.target && input.target !== laneProvider) {
|
|
332
|
+
return blocked(`Lane ${JSON.stringify(resolved.lane.name)} binds implementer ${JSON.stringify(laneProvider)}, but target ${JSON.stringify(input.target)} was requested. Use the lane's implementer or a different lane.`);
|
|
333
|
+
}
|
|
334
|
+
if (!isSubagentProviderEnabled(config.subagents, laneProvider)) {
|
|
335
|
+
return blocked(`Lane ${JSON.stringify(resolved.lane.name)} needs provider ${JSON.stringify(laneProvider)}, which is not enabled in subagents config.`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const target = laneProvider ?? input.target;
|
|
339
|
+
if (!target) {
|
|
340
|
+
return blocked("Pass a target provider/profile or a fleet lane.");
|
|
341
|
+
}
|
|
342
|
+
// Concurrent-write protection: sequential by default. Scopes resolve
|
|
343
|
+
// against the workspace root; escapes are rejected, not sanitized.
|
|
344
|
+
const normalizedScope = normalizeScopePaths(scope.workspaceRoot, input.scopePaths);
|
|
345
|
+
if (!normalizedScope.ok)
|
|
346
|
+
return blocked(normalizedScope.error);
|
|
347
|
+
if (!input.allowConcurrentWrites) {
|
|
348
|
+
const conflict = await overlapGuard.check(input.workspaceId, normalizedScope.paths, async (wsId) => {
|
|
349
|
+
const listed = await agentClient().list(scopeOf(wsId));
|
|
350
|
+
if (listed.isErr())
|
|
351
|
+
throw new Error(listed.error.message);
|
|
352
|
+
return listed.value.map((record) => ({ id: record.id, status: record.status }));
|
|
353
|
+
});
|
|
354
|
+
if (conflict) {
|
|
355
|
+
return blocked(`Scope overlaps live delegation ${conflict} on this workspace. Wait for it to finish, narrow scopePaths, or pass allowConcurrentWrites:true to coordinate explicitly.`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Task-owned delegations run from a self-contained brief: the backend
|
|
359
|
+
// sees everything it needs with no orchestrator history. Ad-hoc runs
|
|
360
|
+
// (no taskId) pass the caller prompt through unchanged.
|
|
361
|
+
const writeMode = input.writeMode ?? laneWriteMode ?? "allowed";
|
|
362
|
+
const timeoutMs = input.timeoutMs ?? laneTimeoutMs ?? DEFAULT_DELEGATION_TIMEOUT_MS;
|
|
363
|
+
const prompt = input.taskId && taskStore
|
|
364
|
+
? await composeBrief({
|
|
365
|
+
taskId: input.taskId,
|
|
366
|
+
workspaceId: input.workspaceId,
|
|
367
|
+
workspaceRoot: scope.workspaceRoot,
|
|
368
|
+
callerPrompt: input.prompt,
|
|
369
|
+
writeMode,
|
|
370
|
+
timeoutMs,
|
|
371
|
+
})
|
|
372
|
+
: input.prompt;
|
|
373
|
+
// Snapshot-before-delegation: best-effort safety net for write-capable
|
|
374
|
+
// runs. Never blocks: non-git workspaces and snapshot failures log and
|
|
375
|
+
// continue, because the policy engine remains the real boundary.
|
|
376
|
+
let preSnapshotId;
|
|
377
|
+
if (writeMode !== "read_only") {
|
|
378
|
+
try {
|
|
379
|
+
const workspace = workspaces.getWorkspace(input.workspaceId);
|
|
380
|
+
const snapshot = await createSnapshot({
|
|
381
|
+
root: scope.workspaceRoot,
|
|
382
|
+
workspaceId: workspace.id,
|
|
383
|
+
label: `pre-delegation ${input.taskId ?? "ad-hoc"}`,
|
|
384
|
+
});
|
|
385
|
+
preSnapshotId = snapshot.snapshotId;
|
|
386
|
+
logEvent(config.logging, "info", "snapshot_created", {
|
|
387
|
+
workspaceId: input.workspaceId,
|
|
388
|
+
snapshotId: snapshot.snapshotId,
|
|
389
|
+
reason: "pre_delegation",
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
logEvent(config.logging, "warn", "snapshot_failed", {
|
|
394
|
+
workspaceId: input.workspaceId,
|
|
395
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const result = await agentClient().start({
|
|
400
|
+
target,
|
|
401
|
+
prompt,
|
|
402
|
+
workspaceRoot: scope.workspaceRoot,
|
|
403
|
+
workspaceId: input.workspaceId,
|
|
404
|
+
model: input.model ?? laneModel,
|
|
405
|
+
effort: input.effort ?? laneEffort,
|
|
406
|
+
writeMode,
|
|
407
|
+
timeoutMs,
|
|
408
|
+
});
|
|
409
|
+
if (result.isErr())
|
|
410
|
+
return toError("agent_start", startedAt, input.workspaceId, result.error);
|
|
411
|
+
overlapGuard.track({ agentId: result.value.id, workspaceId: input.workspaceId, scopePaths: normalizedScope.paths });
|
|
412
|
+
if (preSnapshotId && input.taskId && taskStore) {
|
|
413
|
+
taskStore.appendEvidence(input.taskId, {
|
|
414
|
+
ts: new Date().toISOString(),
|
|
415
|
+
kind: "snapshot",
|
|
416
|
+
summary: `pre-delegation snapshot ${preSnapshotId.slice(0, 12)} before agent ${result.value.id}`,
|
|
417
|
+
details: { snapshotId: preSnapshotId },
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (input.taskId && taskStore) {
|
|
421
|
+
const linkError = linkAgentToTask(taskStore, input.taskId, result.value.id, { correlationId: input.correlationId });
|
|
422
|
+
if (linkError)
|
|
423
|
+
return blocked(linkError);
|
|
424
|
+
}
|
|
425
|
+
logToolCall(config, {
|
|
426
|
+
tool: "agent_start",
|
|
427
|
+
workspaceId: input.workspaceId,
|
|
428
|
+
success: true,
|
|
429
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
430
|
+
});
|
|
431
|
+
return toResult(result.value);
|
|
432
|
+
});
|
|
433
|
+
target.registerTool("agent_status", {
|
|
434
|
+
title: "Agent status",
|
|
435
|
+
description: "Fetch a background agent's record: status, last response, and error details.",
|
|
436
|
+
inputSchema: {
|
|
437
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
438
|
+
agentId: z.string().min(1),
|
|
439
|
+
},
|
|
440
|
+
outputSchema: agentRecordOutputSchema,
|
|
441
|
+
annotations: { readOnlyHint: true },
|
|
442
|
+
}, async ({ workspaceId, agentId }) => {
|
|
443
|
+
const startedAt = performance.now();
|
|
444
|
+
const gate = checkAgentsAllowed(workspaceId);
|
|
445
|
+
if (gate)
|
|
446
|
+
return blocked(gate);
|
|
447
|
+
const result = await agentClient().get(agentId, scopeOf(workspaceId));
|
|
448
|
+
if (result.isErr())
|
|
449
|
+
return toError("agent_status", startedAt, workspaceId, result.error);
|
|
450
|
+
const touchedScope = await maybeRecordAgentResult({ workspaceId, record: result.value });
|
|
451
|
+
const stall = stallAdvisory(result.value);
|
|
452
|
+
logToolCall(config, {
|
|
453
|
+
tool: "agent_status",
|
|
454
|
+
workspaceId,
|
|
455
|
+
success: true,
|
|
456
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
457
|
+
});
|
|
458
|
+
return withTouchedScope(toResult(result.value), stall ? `${touchedScope}\n\n${stall}` : touchedScope);
|
|
459
|
+
});
|
|
460
|
+
target.registerTool("agent_output", {
|
|
461
|
+
title: "Agent output",
|
|
462
|
+
description: "Read a background agent's latest incremental output while its turn runs (plus its last completed response when idle).",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
465
|
+
agentId: z.string().min(1),
|
|
466
|
+
},
|
|
467
|
+
outputSchema: agentRecordOutputSchema,
|
|
468
|
+
annotations: { readOnlyHint: true },
|
|
469
|
+
}, async ({ workspaceId, agentId }) => {
|
|
470
|
+
const startedAt = performance.now();
|
|
471
|
+
const gate = checkAgentsAllowed(workspaceId);
|
|
472
|
+
if (gate)
|
|
473
|
+
return blocked(gate);
|
|
474
|
+
const result = await agentClient().get(agentId, scopeOf(workspaceId));
|
|
475
|
+
if (result.isErr())
|
|
476
|
+
return toError("agent_output", startedAt, workspaceId, result.error);
|
|
477
|
+
const touchedScope = await maybeRecordAgentResult({ workspaceId, record: result.value });
|
|
478
|
+
logToolCall(config, {
|
|
479
|
+
tool: "agent_output",
|
|
480
|
+
workspaceId,
|
|
481
|
+
success: true,
|
|
482
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
483
|
+
});
|
|
484
|
+
return withTouchedScope(toResult(result.value), touchedScope);
|
|
485
|
+
});
|
|
486
|
+
const timeoutMsSchema = z.number().int().positive().max(7 * 24 * 3600 * 1000).optional()
|
|
487
|
+
.describe("Watchdog budget for the new turn in milliseconds (default 30 minutes).");
|
|
488
|
+
target.registerTool("agent_send", {
|
|
489
|
+
title: "Send to agent",
|
|
490
|
+
description: "Continue an existing background agent with a follow-up prompt on its durable provider session.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
493
|
+
agentId: z.string().min(1),
|
|
494
|
+
prompt: z.string().min(1),
|
|
495
|
+
model: z.string().optional(),
|
|
496
|
+
effort: z.string().optional(),
|
|
497
|
+
writeMode: z.enum(["read_only", "allowed", "full_access"]).optional(),
|
|
498
|
+
timeoutMs: timeoutMsSchema,
|
|
499
|
+
},
|
|
500
|
+
outputSchema: agentRecordOutputSchema,
|
|
501
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
502
|
+
}, async (input) => {
|
|
503
|
+
const startedAt = performance.now();
|
|
504
|
+
const gate = checkWritableAgentOp(input.workspaceId);
|
|
505
|
+
if (gate)
|
|
506
|
+
return blocked(gate);
|
|
507
|
+
const scope = scopeOf(input.workspaceId);
|
|
508
|
+
const result = await agentClient().continue(input.agentId, input.prompt, overridesOf(input) ?? {}, scope, input.timeoutMs);
|
|
509
|
+
if (result.isErr())
|
|
510
|
+
return toError("agent_send", startedAt, input.workspaceId, result.error);
|
|
511
|
+
logToolCall(config, {
|
|
512
|
+
tool: "agent_send",
|
|
513
|
+
workspaceId: input.workspaceId,
|
|
514
|
+
success: true,
|
|
515
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
516
|
+
});
|
|
517
|
+
return toResult(result.value);
|
|
518
|
+
});
|
|
519
|
+
target.registerTool("agent_pause", {
|
|
520
|
+
title: "Pause agent",
|
|
521
|
+
description: "Pause a background agent. Pausing between turns is instant; pass force to interrupt a running turn.",
|
|
522
|
+
inputSchema: {
|
|
523
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
524
|
+
agentId: z.string().min(1),
|
|
525
|
+
force: z.boolean().optional(),
|
|
526
|
+
},
|
|
527
|
+
outputSchema: agentRecordOutputSchema,
|
|
528
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
529
|
+
}, async ({ workspaceId, agentId, force }) => {
|
|
530
|
+
const startedAt = performance.now();
|
|
531
|
+
const gate = checkWritableAgentOp(workspaceId);
|
|
532
|
+
if (gate)
|
|
533
|
+
return blocked(gate);
|
|
534
|
+
const result = await agentClient().pause(agentId, scopeOf(workspaceId), { force });
|
|
535
|
+
if (result.isErr())
|
|
536
|
+
return toError("agent_pause", startedAt, workspaceId, result.error);
|
|
537
|
+
logToolCall(config, {
|
|
538
|
+
tool: "agent_pause",
|
|
539
|
+
workspaceId,
|
|
540
|
+
success: true,
|
|
541
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
542
|
+
});
|
|
543
|
+
return toResult(result.value);
|
|
544
|
+
});
|
|
545
|
+
target.registerTool("agent_resume", {
|
|
546
|
+
title: "Resume agent",
|
|
547
|
+
description: "Resume a paused (or previously failed) background agent with an optional continuation prompt.",
|
|
548
|
+
inputSchema: {
|
|
549
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
550
|
+
agentId: z.string().min(1),
|
|
551
|
+
prompt: z.string().optional(),
|
|
552
|
+
model: z.string().optional(),
|
|
553
|
+
effort: z.string().optional(),
|
|
554
|
+
writeMode: z.enum(["read_only", "allowed", "full_access"]).optional(),
|
|
555
|
+
timeoutMs: timeoutMsSchema,
|
|
556
|
+
},
|
|
557
|
+
outputSchema: agentRecordOutputSchema,
|
|
558
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
559
|
+
}, async (input) => {
|
|
560
|
+
const startedAt = performance.now();
|
|
561
|
+
const gate = checkWritableAgentOp(input.workspaceId);
|
|
562
|
+
if (gate)
|
|
563
|
+
return blocked(gate);
|
|
564
|
+
const scope = scopeOf(input.workspaceId);
|
|
565
|
+
const result = await agentClient().resume(input.agentId, scope, { prompt: input.prompt, overrides: overridesOf(input), timeoutMs: input.timeoutMs });
|
|
566
|
+
if (result.isErr())
|
|
567
|
+
return toError("agent_resume", startedAt, input.workspaceId, result.error);
|
|
568
|
+
logToolCall(config, {
|
|
569
|
+
tool: "agent_resume",
|
|
570
|
+
workspaceId: input.workspaceId,
|
|
571
|
+
success: true,
|
|
572
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
573
|
+
});
|
|
574
|
+
return toResult(result.value);
|
|
575
|
+
});
|
|
576
|
+
target.registerTool("agent_stop", {
|
|
577
|
+
title: "Stop agent",
|
|
578
|
+
description: "Stop a background agent. Stopping between turns is instant; pass force to interrupt a running turn.",
|
|
579
|
+
inputSchema: {
|
|
580
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
581
|
+
agentId: z.string().min(1),
|
|
582
|
+
force: z.boolean().optional(),
|
|
583
|
+
},
|
|
584
|
+
outputSchema: agentRecordOutputSchema,
|
|
585
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
586
|
+
}, async ({ workspaceId, agentId, force }) => {
|
|
587
|
+
const startedAt = performance.now();
|
|
588
|
+
const gate = checkWritableAgentOp(workspaceId);
|
|
589
|
+
if (gate)
|
|
590
|
+
return blocked(gate);
|
|
591
|
+
const result = await agentClient().stopAgent(agentId, scopeOf(workspaceId), { force });
|
|
592
|
+
if (result.isErr())
|
|
593
|
+
return toError("agent_stop", startedAt, workspaceId, result.error);
|
|
594
|
+
overlapGuard.release(agentId);
|
|
595
|
+
logToolCall(config, {
|
|
596
|
+
tool: "agent_stop",
|
|
597
|
+
workspaceId,
|
|
598
|
+
success: true,
|
|
599
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
600
|
+
});
|
|
601
|
+
return toResult(result.value);
|
|
602
|
+
});
|
|
603
|
+
target.registerTool("agent_cancel", {
|
|
604
|
+
title: "Cancel agent turn",
|
|
605
|
+
description: "Immediately interrupt a background agent's running turn (unconditional stop).",
|
|
606
|
+
inputSchema: {
|
|
607
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
608
|
+
agentId: z.string().min(1),
|
|
609
|
+
},
|
|
610
|
+
outputSchema: agentRecordOutputSchema,
|
|
611
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
612
|
+
}, async ({ workspaceId, agentId }) => {
|
|
613
|
+
const startedAt = performance.now();
|
|
614
|
+
const gate = checkWritableAgentOp(workspaceId);
|
|
615
|
+
if (gate)
|
|
616
|
+
return blocked(gate);
|
|
617
|
+
const result = await agentClient().stopAgent(agentId, scopeOf(workspaceId), { force: true });
|
|
618
|
+
if (result.isErr())
|
|
619
|
+
return toError("agent_cancel", startedAt, workspaceId, result.error);
|
|
620
|
+
overlapGuard.release(agentId);
|
|
621
|
+
logToolCall(config, {
|
|
622
|
+
tool: "agent_cancel",
|
|
623
|
+
workspaceId,
|
|
624
|
+
success: true,
|
|
625
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
626
|
+
});
|
|
627
|
+
return toResult(result.value);
|
|
628
|
+
});
|
|
629
|
+
target.registerTool("agent_list", {
|
|
630
|
+
title: "List agents",
|
|
631
|
+
description: "List background agents recorded for this workspace.",
|
|
632
|
+
inputSchema: {
|
|
633
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
634
|
+
},
|
|
635
|
+
outputSchema: resultOutputSchema(),
|
|
636
|
+
annotations: { readOnlyHint: true },
|
|
637
|
+
}, async ({ workspaceId }) => {
|
|
638
|
+
const startedAt = performance.now();
|
|
639
|
+
const gate = checkAgentsAllowed(workspaceId);
|
|
640
|
+
if (gate)
|
|
641
|
+
return blocked(gate);
|
|
642
|
+
const result = await agentClient().list(scopeOf(workspaceId));
|
|
643
|
+
if (result.isErr())
|
|
644
|
+
return toError("agent_list", startedAt, workspaceId, result.error);
|
|
645
|
+
logToolCall(config, {
|
|
646
|
+
tool: "agent_list",
|
|
647
|
+
workspaceId,
|
|
648
|
+
success: true,
|
|
649
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
650
|
+
});
|
|
651
|
+
const records = result.value;
|
|
652
|
+
const lines = records.length > 0
|
|
653
|
+
? records.map((record) => `${record.id} ${record.provider}/${record.profileName} ${record.status}`
|
|
654
|
+
+ (record.latestResponse ? ` ${record.latestResponse.split("\n")[0]?.slice(0, 120)}` : "")).join("\n")
|
|
655
|
+
: "No background agents for this workspace yet.";
|
|
656
|
+
return {
|
|
657
|
+
content: [textBlock(lines)],
|
|
658
|
+
structuredContent: {
|
|
659
|
+
result: contentText([textBlock(lines)]),
|
|
660
|
+
agents: records.map((record) => ({
|
|
661
|
+
agentId: record.id,
|
|
662
|
+
status: record.status,
|
|
663
|
+
provider: record.provider,
|
|
664
|
+
profileName: record.profileName,
|
|
665
|
+
})),
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
});
|
|
669
|
+
target.registerTool("fleet_status", {
|
|
670
|
+
title: "Fleet status",
|
|
671
|
+
description: "Show the effective delegation fleet for this workspace: global lanes plus the approved project overlay, with trust state. Project lanes apply only after fleet_approve.",
|
|
672
|
+
inputSchema: {
|
|
673
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
674
|
+
},
|
|
675
|
+
outputSchema: resultOutputSchema(),
|
|
676
|
+
annotations: { readOnlyHint: true },
|
|
677
|
+
}, async ({ workspaceId }) => {
|
|
678
|
+
const startedAt = performance.now();
|
|
679
|
+
const gate = checkAgentsAllowed(workspaceId);
|
|
680
|
+
if (gate)
|
|
681
|
+
return blocked(gate);
|
|
682
|
+
const scope = scopeOf(workspaceId);
|
|
683
|
+
const project = await loadProjectFleet(scope.workspaceRoot, config.stateDir);
|
|
684
|
+
const lanes = effectiveLanes(config.fleet, project);
|
|
685
|
+
const lines = lanes.length > 0
|
|
686
|
+
? lanes.map((lane) => `${lane.name} -> ${lane.provider} [${lane.source}]`
|
|
687
|
+
+ (lane.model ? ` model=${lane.model}` : "")
|
|
688
|
+
+ (lane.effort ? ` effort=${lane.effort}` : "")
|
|
689
|
+
+ (lane.writeMode ? ` write=${lane.writeMode}` : "")
|
|
690
|
+
+ (lane.timeoutMs ? ` timeout=${Math.round(lane.timeoutMs / 60000)}m` : "")).join("\n")
|
|
691
|
+
: "No fleet lanes configured.";
|
|
692
|
+
const trust = project.present
|
|
693
|
+
? (project.trusted ? "Project overlay: approved." : `Project overlay: NOT approved (${project.error ?? "untrusted"}).`)
|
|
694
|
+
: "Project overlay: none.";
|
|
695
|
+
const result = `${lines}\n\n${trust}`;
|
|
696
|
+
logToolCall(config, { tool: "fleet_status", workspaceId, success: true, durationMs: Math.round(performance.now() - startedAt) });
|
|
697
|
+
return {
|
|
698
|
+
content: [textBlock(result)],
|
|
699
|
+
structuredContent: {
|
|
700
|
+
result,
|
|
701
|
+
lanes: lanes.map((lane) => ({ name: lane.name, provider: lane.provider, source: lane.source })),
|
|
702
|
+
projectTrusted: !project.present || project.trusted,
|
|
703
|
+
},
|
|
704
|
+
};
|
|
705
|
+
});
|
|
706
|
+
target.registerTool("fleet_approve", {
|
|
707
|
+
title: "Approve project fleet",
|
|
708
|
+
description: "Approve the current content of this workspace's project fleet file (.hearth/fleet.json) after reviewing it. Any later edit invalidates the approval until re-approved.",
|
|
709
|
+
inputSchema: {
|
|
710
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
711
|
+
},
|
|
712
|
+
outputSchema: resultOutputSchema(),
|
|
713
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
714
|
+
}, async ({ workspaceId }) => {
|
|
715
|
+
const startedAt = performance.now();
|
|
716
|
+
const gate = checkAgentsAllowed(workspaceId);
|
|
717
|
+
if (gate)
|
|
718
|
+
return blocked(gate);
|
|
719
|
+
const scope = scopeOf(workspaceId);
|
|
720
|
+
const approved = await approveProjectFleet(scope.workspaceRoot, config.stateDir);
|
|
721
|
+
logToolCall(config, {
|
|
722
|
+
tool: "fleet_approve",
|
|
723
|
+
workspaceId,
|
|
724
|
+
success: approved.ok,
|
|
725
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
726
|
+
...(!approved.ok ? { error: approved.error } : {}),
|
|
727
|
+
});
|
|
728
|
+
if (!approved.ok) {
|
|
729
|
+
return {
|
|
730
|
+
content: [textBlock(approved.error)],
|
|
731
|
+
isError: true,
|
|
732
|
+
structuredContent: { result: approved.error },
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
const result = "Project fleet file approved for its current content. Future edits require re-approval.";
|
|
736
|
+
return { content: [textBlock(result)], structuredContent: { result } };
|
|
737
|
+
});
|
|
738
|
+
}
|