@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,455 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { TaskTransitionError } from "./task-store.js";
|
|
3
|
+
import { detectVerificationGates, tailOutput } from "./verification.js";
|
|
4
|
+
import { classifyCommand, decideExecution } from "./policy/command-policy.js";
|
|
5
|
+
import { commandListVerdict, resolveExecutionForWorkspace } from "./policy/workspace-profiles.js";
|
|
6
|
+
import { logEvent, commandPreview } from "./logger.js";
|
|
7
|
+
import { logToolCall, resultOutputSchema, sandboxDecision, textBlock } from "./tool-surfaces/shared.js";
|
|
8
|
+
import { workspaceIdDescription } from "./tool-surfaces/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Task tools: the Agent Runtime's MCP surface. The driving AI client moves a
|
|
11
|
+
* durable task through plan → execute → verify → repair; completion is a
|
|
12
|
+
* system verdict (verified_complete), never just a model claim.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_TASK_BUDGET_MS = 30 * 60 * 1000;
|
|
15
|
+
const GATE_POLL_MS = 30_000;
|
|
16
|
+
const MAX_GATE_WALL_MS = 10 * 60 * 1000;
|
|
17
|
+
/** Repair loops are bounded: after this many failed verify rounds the task fails instead of repairing forever. */
|
|
18
|
+
const MAX_REPAIR_CYCLES = 5;
|
|
19
|
+
export function registerTaskTools(target, context) {
|
|
20
|
+
const { config, workspaces, processSessions, taskStore } = context;
|
|
21
|
+
const taskOutputSchema = resultOutputSchema({
|
|
22
|
+
taskId: z.string(),
|
|
23
|
+
status: z.string(),
|
|
24
|
+
completionState: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const evidenceLines = (entries, limit = 8) => entries.slice(-limit).map((entry) => `- [${entry.kind}] ${entry.summary}`).join("\n");
|
|
27
|
+
const taskText = (record, extra) => {
|
|
28
|
+
const lines = [
|
|
29
|
+
`Task ${record.id} — ${record.status}${record.completionState ? ` (${record.completionState})` : ""}.`,
|
|
30
|
+
`Goal: ${record.goal}`,
|
|
31
|
+
record.plan?.length ? `Plan:\n${record.plan.map((step, index) => `${index + 1}. ${step}`).join("\n")}` : undefined,
|
|
32
|
+
record.error ? `Error: ${record.error}` : undefined,
|
|
33
|
+
record.evidence.length > 0 ? `Evidence:\n${evidenceLines(record.evidence)}` : undefined,
|
|
34
|
+
extra,
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
return lines.join("\n\n");
|
|
37
|
+
};
|
|
38
|
+
const structuredTask = (record) => ({
|
|
39
|
+
result: taskText(record),
|
|
40
|
+
taskId: record.id,
|
|
41
|
+
status: record.status,
|
|
42
|
+
...(record.completionState ? { completionState: record.completionState } : {}),
|
|
43
|
+
});
|
|
44
|
+
const assertBudget = (taskId) => {
|
|
45
|
+
const record = taskStore.get(taskId);
|
|
46
|
+
if (!record)
|
|
47
|
+
throw new Error(`Unknown task: ${taskId}`);
|
|
48
|
+
const age = Date.now() - Date.parse(record.createdAt);
|
|
49
|
+
if (age > DEFAULT_TASK_BUDGET_MS) {
|
|
50
|
+
taskStore.transition(taskId, "failed", {
|
|
51
|
+
error: `Task budget exceeded (${Math.round(age / 60000)} minutes).`,
|
|
52
|
+
});
|
|
53
|
+
throw new Error(`Task ${taskId} exceeded its wall-clock budget and was marked failed.`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** Run one gate command under the policy gate, waiting for completion. */
|
|
57
|
+
const runGate = async (workspaceId, cwd, gate) => {
|
|
58
|
+
const startedAt = performance.now();
|
|
59
|
+
const resolved = resolveExecutionForWorkspace(config, cwd);
|
|
60
|
+
const classification = classifyCommand(gate.command);
|
|
61
|
+
// Workspace command lists apply to verification gates too: a denied gate
|
|
62
|
+
// command fails closed rather than running outside the profile.
|
|
63
|
+
const listVerdict = commandListVerdict(resolved, gate.command);
|
|
64
|
+
if (!listVerdict.allowed) {
|
|
65
|
+
return {
|
|
66
|
+
name: gate.name,
|
|
67
|
+
command: gate.command,
|
|
68
|
+
kind: gate.kind,
|
|
69
|
+
passed: false,
|
|
70
|
+
outputTail: `Blocked by workspace policy: ${listVerdict.rule}`,
|
|
71
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const decision = decideExecution({ mode: resolved.mode, tier: classification.tier });
|
|
75
|
+
logEvent(config.logging, decision.decision === "allow" ? "info" : "warn", "policy_decision", {
|
|
76
|
+
tool: "task_verify",
|
|
77
|
+
workspaceId,
|
|
78
|
+
tier: classification.tier,
|
|
79
|
+
rules: classification.rules,
|
|
80
|
+
decision: decision.decision,
|
|
81
|
+
mode: resolved.mode,
|
|
82
|
+
commandPreview: commandPreview(gate.command),
|
|
83
|
+
});
|
|
84
|
+
if (decision.decision !== "allow") {
|
|
85
|
+
return {
|
|
86
|
+
name: gate.name,
|
|
87
|
+
command: gate.command,
|
|
88
|
+
kind: gate.kind,
|
|
89
|
+
passed: false,
|
|
90
|
+
outputTail: `Policy ${decision.decision}: ${decision.reason}`,
|
|
91
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
let snapshot = await processSessions.start({
|
|
95
|
+
workspaceId,
|
|
96
|
+
command: gate.command,
|
|
97
|
+
cwd,
|
|
98
|
+
workspaceRoot: cwd,
|
|
99
|
+
sandbox: sandboxDecision(config, classification.tier, cwd).enabled,
|
|
100
|
+
yieldTimeMs: GATE_POLL_MS,
|
|
101
|
+
maxOutputTokens: 4000,
|
|
102
|
+
});
|
|
103
|
+
const deadline = Date.now() + MAX_GATE_WALL_MS;
|
|
104
|
+
while (snapshot.running && Date.now() < deadline) {
|
|
105
|
+
snapshot = await processSessions.write({
|
|
106
|
+
workspaceId,
|
|
107
|
+
sessionId: snapshot.sessionId,
|
|
108
|
+
chars: "",
|
|
109
|
+
yieldTimeMs: GATE_POLL_MS,
|
|
110
|
+
maxOutputTokens: 4000,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const failedToFinish = snapshot.running;
|
|
114
|
+
if (failedToFinish) {
|
|
115
|
+
processSessions.terminate(workspaceId, snapshot.sessionId);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
name: gate.name,
|
|
119
|
+
command: gate.command,
|
|
120
|
+
kind: gate.kind,
|
|
121
|
+
exitCode: snapshot.exitCode,
|
|
122
|
+
signal: snapshot.signal,
|
|
123
|
+
passed: !failedToFinish && snapshot.exitCode === 0,
|
|
124
|
+
outputTail: tailOutput(snapshot.output || (failedToFinish ? "gate timed out and was terminated" : "")),
|
|
125
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const runVerification = async (input) => {
|
|
129
|
+
const gates = input.explicitGates?.length
|
|
130
|
+
? input.explicitGates
|
|
131
|
+
: detectVerificationGates(input.workspaceRoot);
|
|
132
|
+
const results = [];
|
|
133
|
+
for (const gate of gates) {
|
|
134
|
+
const result = await runGate(input.workspaceId, input.workspaceRoot, gate);
|
|
135
|
+
results.push(result);
|
|
136
|
+
taskStore.appendEvidence(input.taskId, {
|
|
137
|
+
ts: new Date().toISOString(),
|
|
138
|
+
kind: "verification",
|
|
139
|
+
summary: `gate ${result.name}: ${result.passed ? "passed" : "FAILED"} (exit ${result.exitCode ?? "signal"})`,
|
|
140
|
+
details: { command: result.command, outputTail: result.outputTail },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return { passed: results.length > 0 && results.every((result) => result.passed), gates: results };
|
|
144
|
+
};
|
|
145
|
+
const failuresOf = (result) => result.gates.filter((gate) => !gate.passed)
|
|
146
|
+
.map((gate) => ({ gate: gate.name, exitCode: gate.exitCode, outputTail: gate.outputTail }));
|
|
147
|
+
/** Repair loops are bounded: count prior verify→repairing moves from evidence. */
|
|
148
|
+
const repairCyclesUsed = (taskId) => {
|
|
149
|
+
const record = taskStore.get(taskId);
|
|
150
|
+
if (!record)
|
|
151
|
+
return 0;
|
|
152
|
+
return record.evidence.filter((entry) => entry.kind === "note" && entry.summary.includes("verifying → repairing")).length;
|
|
153
|
+
};
|
|
154
|
+
const workspaceOf = (workspaceId) => workspaces.getWorkspace(workspaceId);
|
|
155
|
+
/**
|
|
156
|
+
* Anti-bypass rule: once a gate has failed for a task it stays in the
|
|
157
|
+
* verification set until it passes, so a completion claim can never
|
|
158
|
+
* satisfy itself with a narrower or easier gate list.
|
|
159
|
+
*/
|
|
160
|
+
function mergeWithPreviouslyFailedGates(current, explicit) {
|
|
161
|
+
const failed = new Map();
|
|
162
|
+
for (const entry of current.evidence) {
|
|
163
|
+
if (entry.kind !== "verification" || !entry.summary.includes("FAILED"))
|
|
164
|
+
continue;
|
|
165
|
+
const details = entry.details;
|
|
166
|
+
const command = details?.command;
|
|
167
|
+
const name = entry.summary.slice("gate ".length).split(":")[0]?.trim();
|
|
168
|
+
if (command && name)
|
|
169
|
+
failed.set(command, { name, command });
|
|
170
|
+
}
|
|
171
|
+
if (failed.size === 0)
|
|
172
|
+
return explicit;
|
|
173
|
+
const merged = new Map(failed);
|
|
174
|
+
for (const gate of explicit ?? [])
|
|
175
|
+
merged.set(gate.command, gate);
|
|
176
|
+
return [...merged.values()];
|
|
177
|
+
}
|
|
178
|
+
target.registerTool("task_create", {
|
|
179
|
+
title: "Create task",
|
|
180
|
+
description: "Create a durable engineering task for this workspace with a goal contract. The task state machine (planning → executing → verifying → repair → completed) is the system's source of truth; completion requires verification evidence, not just a model claim.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
183
|
+
goal: z.string().min(1).max(8000).describe("What must be true when this task is done."),
|
|
184
|
+
},
|
|
185
|
+
outputSchema: taskOutputSchema,
|
|
186
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
187
|
+
}, async ({ workspaceId, goal }) => {
|
|
188
|
+
const workspace = workspaceOf(workspaceId);
|
|
189
|
+
const resolved = resolveExecutionForWorkspace(config, workspace.root);
|
|
190
|
+
const record = taskStore.create({
|
|
191
|
+
workspaceId,
|
|
192
|
+
workspaceRoot: workspace.root,
|
|
193
|
+
goal,
|
|
194
|
+
mode: resolved.mode,
|
|
195
|
+
});
|
|
196
|
+
logToolCall(config, { tool: "task_create", workspaceId, success: true, durationMs: 0 });
|
|
197
|
+
return {
|
|
198
|
+
content: [textBlock(taskText(record))],
|
|
199
|
+
structuredContent: structuredTask(record),
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
target.registerTool("task_plan", {
|
|
203
|
+
title: "Set task plan",
|
|
204
|
+
description: "Record the execution plan for a planning-stage task and move it to executing. One concrete step per entry, in order.",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
taskId: z.string().min(1),
|
|
207
|
+
steps: z.array(z.string().min(1)).min(1).max(50).describe("Ordered plan steps."),
|
|
208
|
+
},
|
|
209
|
+
outputSchema: taskOutputSchema,
|
|
210
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
211
|
+
}, async ({ taskId, steps }) => {
|
|
212
|
+
assertBudget(taskId);
|
|
213
|
+
const record = taskStore.transition(taskId, "executing", { plan: steps });
|
|
214
|
+
logToolCall(config, { tool: "task_plan", success: true, durationMs: 0 });
|
|
215
|
+
return {
|
|
216
|
+
content: [textBlock(taskText(record))],
|
|
217
|
+
structuredContent: structuredTask(record),
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
target.registerTool("task_status", {
|
|
221
|
+
title: "Task status",
|
|
222
|
+
description: "Fetch a task's state machine status, plan, and evidence trail.",
|
|
223
|
+
inputSchema: { taskId: z.string().min(1) },
|
|
224
|
+
outputSchema: taskOutputSchema,
|
|
225
|
+
annotations: { readOnlyHint: true },
|
|
226
|
+
}, async ({ taskId }) => {
|
|
227
|
+
const record = taskStore.get(taskId);
|
|
228
|
+
if (!record)
|
|
229
|
+
return taskNotFound(taskId);
|
|
230
|
+
return {
|
|
231
|
+
content: [textBlock(taskText(record))],
|
|
232
|
+
structuredContent: structuredTask(record),
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
target.registerTool("task_list", {
|
|
236
|
+
title: "List tasks",
|
|
237
|
+
description: "List tasks recorded for this workspace.",
|
|
238
|
+
inputSchema: { workspaceId: z.string().describe(workspaceIdDescription) },
|
|
239
|
+
outputSchema: resultOutputSchema(),
|
|
240
|
+
annotations: { readOnlyHint: true },
|
|
241
|
+
}, async ({ workspaceId }) => {
|
|
242
|
+
workspaceOf(workspaceId);
|
|
243
|
+
const records = taskStore.list({ workspaceId });
|
|
244
|
+
const lines = records.length > 0
|
|
245
|
+
? records.map((record) => `${record.id} ${record.status}${record.completionState ? ` (${record.completionState})` : ""} ${record.goal.split("\n")[0]?.slice(0, 100)}`).join("\n")
|
|
246
|
+
: "No tasks for this workspace yet.";
|
|
247
|
+
return {
|
|
248
|
+
content: [textBlock(lines)],
|
|
249
|
+
structuredContent: {
|
|
250
|
+
result: lines,
|
|
251
|
+
tasks: records.map((record) => ({ taskId: record.id, status: record.status })),
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
target.registerTool("task_verify", {
|
|
256
|
+
title: "Verify task",
|
|
257
|
+
description: "Run the workspace's verification gates (tests, typecheck, build — auto-detected or explicit) and record the evidence. Passing gates move the task toward completion; failures move it to repairing with the failing output.",
|
|
258
|
+
inputSchema: {
|
|
259
|
+
taskId: z.string().min(1),
|
|
260
|
+
gates: z.array(z.object({
|
|
261
|
+
name: z.string().min(1),
|
|
262
|
+
command: z.string().min(1),
|
|
263
|
+
})).max(10).optional().describe("Explicit gates; defaults to auto-detected ones."),
|
|
264
|
+
},
|
|
265
|
+
outputSchema: taskOutputSchema,
|
|
266
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
267
|
+
}, async ({ taskId, gates }) => {
|
|
268
|
+
assertBudget(taskId);
|
|
269
|
+
const current = taskStore.get(taskId);
|
|
270
|
+
if (!current)
|
|
271
|
+
return taskNotFound(taskId);
|
|
272
|
+
let record;
|
|
273
|
+
try {
|
|
274
|
+
record = taskStore.transition(taskId, "verifying");
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
return transitionError(error, taskId);
|
|
278
|
+
}
|
|
279
|
+
void record;
|
|
280
|
+
const verification = await runVerification({
|
|
281
|
+
workspaceId: current.workspaceId ?? "",
|
|
282
|
+
workspaceRoot: current.workspaceRoot,
|
|
283
|
+
taskId,
|
|
284
|
+
explicitGates: mergeWithPreviouslyFailedGates(current, gates)?.map((gate) => ({ ...gate, kind: "custom" })),
|
|
285
|
+
});
|
|
286
|
+
taskStore.countToolCall(taskId);
|
|
287
|
+
if (!verification.passed) {
|
|
288
|
+
const failures = failuresOf(verification);
|
|
289
|
+
if (repairCyclesUsed(taskId) >= MAX_REPAIR_CYCLES) {
|
|
290
|
+
const failed = taskStore.transition(taskId, "failed", {
|
|
291
|
+
error: `Repair budget exhausted after ${MAX_REPAIR_CYCLES} failed verification rounds: ${failures.map((failure) => failure.gate).join(", ")}. Inspect evidence and create a new task.`,
|
|
292
|
+
});
|
|
293
|
+
const extra = `Repair budget exhausted (${MAX_REPAIR_CYCLES} rounds). Task marked failed — no more automatic repair loops.\n${failures.map((failure) => `— ${failure.gate} (exit ${failure.exitCode ?? "?"})\n${failure.outputTail}`).join("\n\n")}`;
|
|
294
|
+
return {
|
|
295
|
+
content: [textBlock(taskText(failed, extra))],
|
|
296
|
+
structuredContent: { ...structuredTask(failed), result: taskText(failed, extra) },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const failed = taskStore.transition(taskId, "repairing", {
|
|
300
|
+
error: `Verification failed: ${failures.map((failure) => failure.gate).join(", ")}`,
|
|
301
|
+
});
|
|
302
|
+
const extra = `Verification FAILED. Fix the failures, then call task_verify again.\n${failures.map((failure) => `— ${failure.gate} (exit ${failure.exitCode ?? "?"})\n${failure.outputTail}`).join("\n\n")}`;
|
|
303
|
+
return {
|
|
304
|
+
content: [textBlock(taskText(failed, extra))],
|
|
305
|
+
structuredContent: { ...structuredTask(failed), result: taskText(failed, extra) },
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const completed = taskStore.transition(taskId, "completed", { completionState: "verified_complete" });
|
|
309
|
+
return {
|
|
310
|
+
content: [textBlock(taskText(completed, "System verified: all gates passed."))],
|
|
311
|
+
structuredContent: structuredTask(completed),
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
target.registerTool("task_complete", {
|
|
315
|
+
title: "Complete task",
|
|
316
|
+
description: "Claim a task complete. The claim alone is never trusted: verification gates run first and only a passing run marks the task verified_complete. With no detected gates, pass acceptUnverified to explicitly record a model_complete verdict (audited as unverified).",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
taskId: z.string().min(1),
|
|
319
|
+
acceptUnverified: z
|
|
320
|
+
.boolean()
|
|
321
|
+
.optional()
|
|
322
|
+
.describe("Explicitly mark the task model_complete when no verification gates exist."),
|
|
323
|
+
},
|
|
324
|
+
outputSchema: taskOutputSchema,
|
|
325
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
326
|
+
}, async ({ taskId, acceptUnverified }) => {
|
|
327
|
+
assertBudget(taskId);
|
|
328
|
+
const current = taskStore.get(taskId);
|
|
329
|
+
if (!current)
|
|
330
|
+
return taskNotFound(taskId);
|
|
331
|
+
if (current.status === "planning") {
|
|
332
|
+
return {
|
|
333
|
+
content: [textBlock(`Task ${taskId} is still planning. Provide a plan (task_plan) and do the work before completing.`)],
|
|
334
|
+
isError: true,
|
|
335
|
+
structuredContent: { result: `Task ${taskId} is still planning.`, taskId, status: current.status },
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (current.status === "repairing") {
|
|
339
|
+
// Failed-gate evidence exists; a fresh claim must not swap in an
|
|
340
|
+
// easier gate set. The failing gates themselves must pass again.
|
|
341
|
+
return {
|
|
342
|
+
content: [textBlock(`Task ${taskId} is in repairing: earlier verification gates failed. ` +
|
|
343
|
+
`Repair the failures and call task_verify (it re-runs the failing gates); completion claims are rejected until they pass.`)],
|
|
344
|
+
isError: true,
|
|
345
|
+
structuredContent: { result: `Task ${taskId} is in repairing; use task_verify.`, taskId, status: current.status },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const detected = detectVerificationGates(current.workspaceRoot);
|
|
349
|
+
if (detected.length === 0 && acceptUnverified !== true) {
|
|
350
|
+
return {
|
|
351
|
+
content: [textBlock(`No verification gates were detected for this workspace, so the claim cannot be verified. ` +
|
|
352
|
+
`Run explicit gates via task_verify with a gates list, or pass acceptUnverified=true to record an explicitly unverified (model_complete) verdict.`)],
|
|
353
|
+
isError: true,
|
|
354
|
+
structuredContent: { result: "No verification gates available.", taskId, status: current.status },
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
taskStore.transition(taskId, "verifying");
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
return transitionError(error, taskId);
|
|
362
|
+
}
|
|
363
|
+
taskStore.appendEvidence(taskId, {
|
|
364
|
+
ts: new Date().toISOString(),
|
|
365
|
+
kind: "model_claim",
|
|
366
|
+
summary: "model claimed completion",
|
|
367
|
+
});
|
|
368
|
+
if (detected.length === 0) {
|
|
369
|
+
const completed = taskStore.transition(taskId, "completed", { completionState: "model_complete" });
|
|
370
|
+
return {
|
|
371
|
+
content: [textBlock(taskText(completed, "Marked model_complete (explicitly unverified — no gates were available)."))],
|
|
372
|
+
structuredContent: structuredTask(completed),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const verification = await runVerification({
|
|
376
|
+
workspaceId: current.workspaceId ?? "",
|
|
377
|
+
workspaceRoot: current.workspaceRoot,
|
|
378
|
+
taskId,
|
|
379
|
+
});
|
|
380
|
+
taskStore.countToolCall(taskId);
|
|
381
|
+
if (!verification.passed) {
|
|
382
|
+
const failures = failuresOf(verification);
|
|
383
|
+
const failed = taskStore.transition(taskId, "repairing", {
|
|
384
|
+
error: `Verification failed: ${failures.map((failure) => failure.gate).join(", ")}`,
|
|
385
|
+
});
|
|
386
|
+
const extra = `Completion rejected: gates failed. Repair, then task_verify.\n${failures.map((failure) => `— ${failure.gate} (exit ${failure.exitCode ?? "?"})\n${failure.outputTail}`).join("\n\n")}`;
|
|
387
|
+
return {
|
|
388
|
+
content: [textBlock(taskText(failed, extra))],
|
|
389
|
+
structuredContent: { ...structuredTask(failed), result: taskText(failed, extra) },
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const completed = taskStore.transition(taskId, "completed", { completionState: "verified_complete" });
|
|
393
|
+
return {
|
|
394
|
+
content: [textBlock(taskText(completed, "System verified: all gates passed."))],
|
|
395
|
+
structuredContent: structuredTask(completed),
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
target.registerTool("task_cancel", {
|
|
399
|
+
title: "Cancel task",
|
|
400
|
+
description: "Cancel an unfinished task (terminal).",
|
|
401
|
+
inputSchema: { taskId: z.string().min(1) },
|
|
402
|
+
outputSchema: taskOutputSchema,
|
|
403
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
404
|
+
}, async ({ taskId }) => {
|
|
405
|
+
const record = taskStore.get(taskId);
|
|
406
|
+
if (!record)
|
|
407
|
+
return taskNotFound(taskId);
|
|
408
|
+
try {
|
|
409
|
+
const cancelled = taskStore.transition(taskId, "cancelled", { error: "Cancelled by client." });
|
|
410
|
+
return { content: [textBlock(taskText(cancelled))], structuredContent: structuredTask(cancelled) };
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
return transitionError(error, taskId);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
target.registerTool("task_resume", {
|
|
417
|
+
title: "Resume task",
|
|
418
|
+
description: "Resume a failed task (crash recovery, repair-budget exhaustion) back to planning. Completed/cancelled tasks are terminal; in-flight tasks use the normal transitions.",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
taskId: z.string().min(1),
|
|
421
|
+
reason: z.string().max(500).optional(),
|
|
422
|
+
},
|
|
423
|
+
outputSchema: taskOutputSchema,
|
|
424
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
425
|
+
}, async ({ taskId, reason }) => {
|
|
426
|
+
const record = taskStore.get(taskId);
|
|
427
|
+
if (!record)
|
|
428
|
+
return taskNotFound(taskId);
|
|
429
|
+
try {
|
|
430
|
+
const resumed = taskStore.replan(taskId, reason ?? "Resumed by client.");
|
|
431
|
+
logToolCall(config, { tool: "task_resume", success: true, durationMs: 0 });
|
|
432
|
+
return { content: [textBlock(taskText(resumed))], structuredContent: structuredTask(resumed) };
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
return transitionError(error, taskId);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
function taskNotFound(taskId) {
|
|
439
|
+
return {
|
|
440
|
+
content: [textBlock(`Unknown task: ${taskId}`)],
|
|
441
|
+
isError: true,
|
|
442
|
+
structuredContent: { result: `Unknown task: ${taskId}` },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function transitionError(error, taskId) {
|
|
446
|
+
if (error instanceof TaskTransitionError) {
|
|
447
|
+
return {
|
|
448
|
+
content: [textBlock(`${error.message}. Use task_status to see the current state.`)],
|
|
449
|
+
isError: true,
|
|
450
|
+
structuredContent: { result: error.message, taskId },
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { editFileTool, writeFileTool, } from "../pi-tools.js";
|
|
3
|
+
import { EDIT_TOOL_ANNOTATIONS, SHELL_TOOL_ANNOTATIONS, WRITE_TOOL_ANNOTATIONS, toolNames, workspaceIdDescription, } from "./types.js";
|
|
4
|
+
import { contentText, countDiffStats, enforceShellPolicy, logFailedToolResponse, logToolCall, resultOutputSchema, sandboxDecision, snapshotBeforeRiskyExecution, textBlock, } from "./shared.js";
|
|
5
|
+
const CLAUDE_INSTRUCTIONS = `Use ${toolNames.read} for direct file reads, ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for inspection, tests, builds, and other commands. Shell commands run with the local user's authority and are not sandboxed; workspace validation only selects their initial working directory. Command policy: inspection and ordinary workspace work (builds, tests, local edits) run freely; commands with effects outside the workspace (network fetches, package installs, recursive deletes, git push, permission changes) require explicit user approval in the conversation and the approvedByUser flag; privilege escalation, system management, and piping remote scripts into a shell are always blocked. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.`;
|
|
6
|
+
export function claudeInstructions({ agents, skills, }) {
|
|
7
|
+
return `${agents}${skills}${CLAUDE_INSTRUCTIONS}`;
|
|
8
|
+
}
|
|
9
|
+
export function registerClaudeTools(context) {
|
|
10
|
+
registerClaudeMutationTools(context);
|
|
11
|
+
registerShellTool(context);
|
|
12
|
+
}
|
|
13
|
+
const CLAUDE_SHELL_DESCRIPTION = `Run a shell command with the local user's authority. Commands are not sandboxed; workspace validation only selects the initial working directory. Use this for file inspection, tests, builds, package scripts, and other commands.`;
|
|
14
|
+
function registerClaudeMutationTools(context) {
|
|
15
|
+
const { server, config, workspaces } = context;
|
|
16
|
+
server.registerTool(toolNames.write, {
|
|
17
|
+
title: "Write file",
|
|
18
|
+
description: `Create or completely overwrite a file in a workspace. Prefer ${toolNames.edit} for targeted changes to existing files.`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
21
|
+
path: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe("File path to write, relative to the workspace root."),
|
|
24
|
+
content: z.string().describe("Complete new file content."),
|
|
25
|
+
},
|
|
26
|
+
outputSchema: resultOutputSchema(),
|
|
27
|
+
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
28
|
+
}, async ({ workspaceId, ...input }) => {
|
|
29
|
+
const startedAt = performance.now();
|
|
30
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
31
|
+
workspaces.resolvePath(workspace, input.path);
|
|
32
|
+
const response = await writeFileTool(input, {
|
|
33
|
+
cwd: workspace.root,
|
|
34
|
+
root: workspace.root,
|
|
35
|
+
});
|
|
36
|
+
if (response.isError) {
|
|
37
|
+
logFailedToolResponse(config, {
|
|
38
|
+
tool: toolNames.write,
|
|
39
|
+
workspaceId,
|
|
40
|
+
path: input.path,
|
|
41
|
+
}, response.content, startedAt);
|
|
42
|
+
return response;
|
|
43
|
+
}
|
|
44
|
+
logToolCall(config, {
|
|
45
|
+
tool: toolNames.write,
|
|
46
|
+
workspaceId,
|
|
47
|
+
path: input.path,
|
|
48
|
+
success: true,
|
|
49
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
...response,
|
|
53
|
+
structuredContent: {
|
|
54
|
+
result: contentText(response.content),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
server.registerTool(toolNames.edit, {
|
|
59
|
+
title: "Edit file",
|
|
60
|
+
description: `Edit one file in a workspace by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique.`,
|
|
61
|
+
inputSchema: {
|
|
62
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
63
|
+
path: z
|
|
64
|
+
.string()
|
|
65
|
+
.describe("File path to edit, relative to the workspace root."),
|
|
66
|
+
edits: z
|
|
67
|
+
.array(z.object({
|
|
68
|
+
oldText: z
|
|
69
|
+
.string()
|
|
70
|
+
.describe("Exact text to replace. Must match uniquely in the original file."),
|
|
71
|
+
newText: z.string().describe("Replacement text."),
|
|
72
|
+
}))
|
|
73
|
+
.min(1),
|
|
74
|
+
},
|
|
75
|
+
outputSchema: resultOutputSchema({
|
|
76
|
+
status: z.literal("applied"),
|
|
77
|
+
}),
|
|
78
|
+
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
79
|
+
}, async ({ workspaceId, ...input }) => {
|
|
80
|
+
const startedAt = performance.now();
|
|
81
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
82
|
+
workspaces.resolvePath(workspace, input.path);
|
|
83
|
+
const response = await editFileTool(input, {
|
|
84
|
+
cwd: workspace.root,
|
|
85
|
+
root: workspace.root,
|
|
86
|
+
});
|
|
87
|
+
if (response.isError) {
|
|
88
|
+
logFailedToolResponse(config, {
|
|
89
|
+
tool: toolNames.edit,
|
|
90
|
+
workspaceId,
|
|
91
|
+
path: input.path,
|
|
92
|
+
}, response.content, startedAt);
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
96
|
+
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
97
|
+
const editContent = [textBlock(editResultText)];
|
|
98
|
+
logToolCall(config, {
|
|
99
|
+
tool: toolNames.edit,
|
|
100
|
+
workspaceId,
|
|
101
|
+
path: input.path,
|
|
102
|
+
success: true,
|
|
103
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
content: editContent,
|
|
107
|
+
structuredContent: {
|
|
108
|
+
status: "applied",
|
|
109
|
+
result: contentText(editContent),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function registerShellTool(context) {
|
|
115
|
+
const { server, config, workspaces, processSessions } = context;
|
|
116
|
+
server.registerTool(toolNames.shell, {
|
|
117
|
+
title: "Bash",
|
|
118
|
+
description: CLAUDE_SHELL_DESCRIPTION,
|
|
119
|
+
inputSchema: {
|
|
120
|
+
workspaceId: z.string().describe(workspaceIdDescription),
|
|
121
|
+
command: z
|
|
122
|
+
.string()
|
|
123
|
+
.describe("Shell command to execute."),
|
|
124
|
+
workingDirectory: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Optional working directory relative to the workspace root. Defaults to the workspace root."),
|
|
128
|
+
timeout: z
|
|
129
|
+
.number()
|
|
130
|
+
.positive()
|
|
131
|
+
.max(300)
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Timeout in seconds. Defaults to 30, max 300."),
|
|
134
|
+
approvedByUser: z
|
|
135
|
+
.boolean()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Set to true only after the user explicitly approved this exact command in the conversation. Required for tier-2 commands (network, installs, recursive deletes, pushes) unless the workspace runs in autonomous mode."),
|
|
138
|
+
},
|
|
139
|
+
outputSchema: resultOutputSchema(),
|
|
140
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
141
|
+
}, async ({ workspaceId, workingDirectory, approvedByUser, ...input }) => {
|
|
142
|
+
const startedAt = performance.now();
|
|
143
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
144
|
+
const verdict = enforceShellPolicy(config, { tool: toolNames.shell, workspaceId, command: input.command, workspaceRoot: workspace.root }, approvedByUser);
|
|
145
|
+
if (verdict.denial) {
|
|
146
|
+
logFailedToolResponse(config, {
|
|
147
|
+
tool: toolNames.shell,
|
|
148
|
+
workspaceId,
|
|
149
|
+
workingDirectory: workingDirectory ?? ".",
|
|
150
|
+
command: input.command,
|
|
151
|
+
commandLength: input.command.length,
|
|
152
|
+
}, verdict.denial.content, startedAt);
|
|
153
|
+
return verdict.denial;
|
|
154
|
+
}
|
|
155
|
+
const sandbox = sandboxDecision(config, verdict.classification.tier, workspace.root);
|
|
156
|
+
if (sandbox.denialReason) {
|
|
157
|
+
logFailedToolResponse(config, {
|
|
158
|
+
tool: toolNames.shell,
|
|
159
|
+
workspaceId,
|
|
160
|
+
workingDirectory: workingDirectory ?? ".",
|
|
161
|
+
command: input.command,
|
|
162
|
+
commandLength: input.command.length,
|
|
163
|
+
}, [textBlock(sandbox.denialReason)], startedAt);
|
|
164
|
+
return {
|
|
165
|
+
content: [textBlock(sandbox.denialReason)],
|
|
166
|
+
isError: true,
|
|
167
|
+
structuredContent: { result: sandbox.denialReason },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
171
|
+
await snapshotBeforeRiskyExecution(config, workspace, input.command);
|
|
172
|
+
// Security boundary: bash executes through the same process manager as
|
|
173
|
+
// every other shell tool — policy gate above, environment allowlist,
|
|
174
|
+
// OS-sandbox wrapper, and process journal all apply. The former Pi
|
|
175
|
+
// spawn path (which bypassed the sandbox wrapper) is gone.
|
|
176
|
+
const timeoutSeconds = Math.min(input.timeout ?? 30, 300);
|
|
177
|
+
let snapshot = await processSessions.start({
|
|
178
|
+
workspaceId,
|
|
179
|
+
command: input.command,
|
|
180
|
+
cwd,
|
|
181
|
+
workspaceRoot: workspace.root,
|
|
182
|
+
sandbox: sandbox.enabled,
|
|
183
|
+
yieldTimeMs: timeoutSeconds * 1000,
|
|
184
|
+
maxOutputTokens: 10_000,
|
|
185
|
+
});
|
|
186
|
+
let timedOut = false;
|
|
187
|
+
if (snapshot.running) {
|
|
188
|
+
timedOut = true;
|
|
189
|
+
processSessions.terminate(workspaceId, snapshot.sessionId);
|
|
190
|
+
snapshot = await processSessions.write({
|
|
191
|
+
workspaceId,
|
|
192
|
+
sessionId: snapshot.sessionId,
|
|
193
|
+
chars: "",
|
|
194
|
+
yieldTimeMs: 5_000,
|
|
195
|
+
maxOutputTokens: 10_000,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const statusLine = timedOut
|
|
199
|
+
? `Command timed out after ${timeoutSeconds}s and was terminated.`
|
|
200
|
+
: snapshot.signal
|
|
201
|
+
? `Process exited after signal ${snapshot.signal}.`
|
|
202
|
+
: `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
|
|
203
|
+
const result = snapshot.output
|
|
204
|
+
? `${snapshot.output.replace(/\n$/, "")}\n${statusLine}`
|
|
205
|
+
: statusLine;
|
|
206
|
+
logToolCall(config, {
|
|
207
|
+
tool: toolNames.shell,
|
|
208
|
+
workspaceId,
|
|
209
|
+
workingDirectory: workingDirectory ?? ".",
|
|
210
|
+
command: input.command,
|
|
211
|
+
commandLength: input.command.length,
|
|
212
|
+
success: !timedOut && snapshot.exitCode === 0,
|
|
213
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
content: [textBlock(result)],
|
|
217
|
+
structuredContent: { result },
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|