@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,29 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
5
|
+
import * as schema from "./schema.js";
|
|
6
|
+
import { migrateDatabase } from "./migrations.js";
|
|
7
|
+
export function databasePath(stateDir) {
|
|
8
|
+
return join(stateDir, "hearth.sqlite");
|
|
9
|
+
}
|
|
10
|
+
export function openDatabase(stateDir) {
|
|
11
|
+
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
12
|
+
chmodSync(stateDir, 0o700);
|
|
13
|
+
const path = databasePath(stateDir);
|
|
14
|
+
const sqlite = new Database(path);
|
|
15
|
+
chmodSync(path, 0o600);
|
|
16
|
+
sqlite.pragma("journal_mode = WAL");
|
|
17
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
18
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
19
|
+
sqlite.pragma("foreign_keys = ON");
|
|
20
|
+
migrateDatabase(sqlite);
|
|
21
|
+
return {
|
|
22
|
+
sqlite,
|
|
23
|
+
db: createDrizzleDatabase(sqlite),
|
|
24
|
+
close: () => sqlite.close(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createDrizzleDatabase(sqlite) {
|
|
28
|
+
return drizzle(sqlite, { schema });
|
|
29
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const migrations = [
|
|
2
|
+
{
|
|
3
|
+
version: 1,
|
|
4
|
+
name: "workspace-state",
|
|
5
|
+
up: migrateWorkspaceState,
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
version: 2,
|
|
9
|
+
name: "oauth-state",
|
|
10
|
+
up: migrateOAuthState,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
version: 3,
|
|
14
|
+
name: "local-agent-sessions",
|
|
15
|
+
up: migrateLocalAgentSessions,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
version: 4,
|
|
19
|
+
name: "workspace-conversation-bindings",
|
|
20
|
+
up: migrateWorkspaceConversationBindings,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
version: 5,
|
|
24
|
+
name: "local-agent-structured-errors",
|
|
25
|
+
up: migrateLocalAgentStructuredErrors,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
version: 6,
|
|
29
|
+
name: "local-agent-effort-rename",
|
|
30
|
+
up: migrateLocalAgentEffortRename,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
version: 7,
|
|
34
|
+
name: "local-agent-lifecycle-output",
|
|
35
|
+
up: migrateLocalAgentLifecycleOutput,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
version: 8,
|
|
39
|
+
name: "device-tokens",
|
|
40
|
+
up: migrateDeviceTokens,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
version: 9,
|
|
44
|
+
name: "task-runtime",
|
|
45
|
+
up: migrateTaskRuntime,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
export function migrateDatabase(sqlite) {
|
|
49
|
+
const migrate = sqlite.transaction(() => {
|
|
50
|
+
sqlite.exec(`
|
|
51
|
+
create table if not exists hearth_schema_migrations (
|
|
52
|
+
version integer primary key,
|
|
53
|
+
name text not null,
|
|
54
|
+
applied_at text not null
|
|
55
|
+
);
|
|
56
|
+
`);
|
|
57
|
+
const applied = new Set(sqlite.prepare("select version from hearth_schema_migrations").all().map((row) => row.version));
|
|
58
|
+
const recordMigration = sqlite.prepare("insert into hearth_schema_migrations (version, name, applied_at) values (?, ?, ?)");
|
|
59
|
+
for (const migration of migrations) {
|
|
60
|
+
if (applied.has(migration.version))
|
|
61
|
+
continue;
|
|
62
|
+
migration.up(sqlite);
|
|
63
|
+
recordMigration.run(migration.version, migration.name, new Date().toISOString());
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
migrate.immediate();
|
|
67
|
+
}
|
|
68
|
+
function migrateWorkspaceState(sqlite) {
|
|
69
|
+
sqlite.exec(`
|
|
70
|
+
create table if not exists workspace_sessions (
|
|
71
|
+
id text primary key,
|
|
72
|
+
root text not null,
|
|
73
|
+
status text not null default 'active',
|
|
74
|
+
mode text not null default 'checkout',
|
|
75
|
+
source_root text,
|
|
76
|
+
base_ref text,
|
|
77
|
+
base_sha text,
|
|
78
|
+
managed text not null default 'false',
|
|
79
|
+
created_at text not null,
|
|
80
|
+
last_used_at text not null
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
create index if not exists workspace_sessions_root_idx
|
|
84
|
+
on workspace_sessions(root, last_used_at desc);
|
|
85
|
+
|
|
86
|
+
create index if not exists workspace_sessions_status_idx
|
|
87
|
+
on workspace_sessions(status, last_used_at desc);
|
|
88
|
+
|
|
89
|
+
create table if not exists loaded_agent_files (
|
|
90
|
+
workspace_session_id text not null,
|
|
91
|
+
path text not null,
|
|
92
|
+
content_hash text not null,
|
|
93
|
+
content text not null,
|
|
94
|
+
loaded_at text not null,
|
|
95
|
+
last_seen_at text not null,
|
|
96
|
+
primary key (workspace_session_id, path),
|
|
97
|
+
foreign key (workspace_session_id)
|
|
98
|
+
references workspace_sessions(id)
|
|
99
|
+
on delete cascade
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
create index if not exists loaded_agent_files_path_idx
|
|
103
|
+
on loaded_agent_files(path);
|
|
104
|
+
`);
|
|
105
|
+
addColumnIfMissing(sqlite, "workspace_sessions", "mode", "text not null default 'checkout'");
|
|
106
|
+
addColumnIfMissing(sqlite, "workspace_sessions", "source_root", "text");
|
|
107
|
+
addColumnIfMissing(sqlite, "workspace_sessions", "base_ref", "text");
|
|
108
|
+
addColumnIfMissing(sqlite, "workspace_sessions", "base_sha", "text");
|
|
109
|
+
addColumnIfMissing(sqlite, "workspace_sessions", "managed", "text not null default 'false'");
|
|
110
|
+
}
|
|
111
|
+
function migrateOAuthState(sqlite) {
|
|
112
|
+
sqlite.exec(`
|
|
113
|
+
create table if not exists oauth_clients (
|
|
114
|
+
client_id text primary key,
|
|
115
|
+
client_json text not null,
|
|
116
|
+
issued_at integer not null
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
create index if not exists oauth_clients_issued_at_idx
|
|
120
|
+
on oauth_clients(issued_at desc);
|
|
121
|
+
|
|
122
|
+
create table if not exists oauth_access_tokens (
|
|
123
|
+
token_hash text primary key,
|
|
124
|
+
client_id text not null,
|
|
125
|
+
scopes_json text not null,
|
|
126
|
+
expires_at integer not null,
|
|
127
|
+
resource text,
|
|
128
|
+
foreign key (client_id) references oauth_clients(client_id) on delete cascade
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
create index if not exists oauth_access_tokens_client_id_idx
|
|
132
|
+
on oauth_access_tokens(client_id);
|
|
133
|
+
|
|
134
|
+
create index if not exists oauth_access_tokens_expires_at_idx
|
|
135
|
+
on oauth_access_tokens(expires_at);
|
|
136
|
+
|
|
137
|
+
create table if not exists oauth_refresh_tokens (
|
|
138
|
+
token_hash text primary key,
|
|
139
|
+
client_id text not null,
|
|
140
|
+
scopes_json text not null,
|
|
141
|
+
expires_at integer not null,
|
|
142
|
+
resource text,
|
|
143
|
+
foreign key (client_id) references oauth_clients(client_id) on delete cascade
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
create index if not exists oauth_refresh_tokens_client_id_idx
|
|
147
|
+
on oauth_refresh_tokens(client_id);
|
|
148
|
+
|
|
149
|
+
create index if not exists oauth_refresh_tokens_expires_at_idx
|
|
150
|
+
on oauth_refresh_tokens(expires_at);
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
function migrateLocalAgentSessions(sqlite) {
|
|
154
|
+
sqlite.exec(`
|
|
155
|
+
create table if not exists local_agent_sessions (
|
|
156
|
+
id text primary key,
|
|
157
|
+
workspace_id text,
|
|
158
|
+
workspace_root text not null,
|
|
159
|
+
profile_name text not null,
|
|
160
|
+
provider text not null,
|
|
161
|
+
model text,
|
|
162
|
+
effort text,
|
|
163
|
+
provider_session_id text,
|
|
164
|
+
status text not null,
|
|
165
|
+
latest_response text,
|
|
166
|
+
error text,
|
|
167
|
+
created_at text not null,
|
|
168
|
+
updated_at text not null
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
create index if not exists local_agent_sessions_workspace_id_idx
|
|
172
|
+
on local_agent_sessions(workspace_id, updated_at desc);
|
|
173
|
+
|
|
174
|
+
create index if not exists local_agent_sessions_workspace_root_idx
|
|
175
|
+
on local_agent_sessions(workspace_root, updated_at desc);
|
|
176
|
+
|
|
177
|
+
create index if not exists local_agent_sessions_provider_session_id_idx
|
|
178
|
+
on local_agent_sessions(provider_session_id);
|
|
179
|
+
`);
|
|
180
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "effort", "text");
|
|
181
|
+
}
|
|
182
|
+
function migrateWorkspaceConversationBindings(sqlite) {
|
|
183
|
+
sqlite.exec(`
|
|
184
|
+
create table if not exists workspace_conversation_bindings (
|
|
185
|
+
conversation_scope_id text not null,
|
|
186
|
+
target_key text not null,
|
|
187
|
+
workspace_session_id text not null,
|
|
188
|
+
created_at text not null,
|
|
189
|
+
last_used_at text not null,
|
|
190
|
+
primary key (conversation_scope_id, target_key),
|
|
191
|
+
foreign key (workspace_session_id)
|
|
192
|
+
references workspace_sessions(id)
|
|
193
|
+
on delete cascade
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
create index if not exists workspace_conversation_bindings_workspace_idx
|
|
197
|
+
on workspace_conversation_bindings(workspace_session_id);
|
|
198
|
+
`);
|
|
199
|
+
}
|
|
200
|
+
function migrateLocalAgentStructuredErrors(sqlite) {
|
|
201
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "error_code", "text");
|
|
202
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "error_retryable", "text");
|
|
203
|
+
}
|
|
204
|
+
function migrateLocalAgentEffortRename(sqlite) {
|
|
205
|
+
const columns = sqlite.prepare("pragma table_info(local_agent_sessions)").all();
|
|
206
|
+
const names = new Set(columns.map((column) => column.name));
|
|
207
|
+
if (names.has("effort")) {
|
|
208
|
+
if (names.has("thinking")) {
|
|
209
|
+
sqlite.exec(`
|
|
210
|
+
update local_agent_sessions
|
|
211
|
+
set effort = thinking
|
|
212
|
+
where effort is null and thinking is not null
|
|
213
|
+
`);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!names.has("thinking")) {
|
|
218
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "effort", "text");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
sqlite.exec("alter table local_agent_sessions rename column thinking to effort");
|
|
222
|
+
}
|
|
223
|
+
function migrateLocalAgentLifecycleOutput(sqlite) {
|
|
224
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "latest_output", "text");
|
|
225
|
+
}
|
|
226
|
+
function migrateDeviceTokens(sqlite) {
|
|
227
|
+
sqlite.exec(`
|
|
228
|
+
create table if not exists device_tokens (
|
|
229
|
+
token_hash text primary key,
|
|
230
|
+
name text not null,
|
|
231
|
+
created_at text not null,
|
|
232
|
+
last_used_at text
|
|
233
|
+
);
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
function migrateTaskRuntime(sqlite) {
|
|
237
|
+
sqlite.exec(`
|
|
238
|
+
create table if not exists tasks (
|
|
239
|
+
id text primary key,
|
|
240
|
+
workspace_id text,
|
|
241
|
+
workspace_root text not null,
|
|
242
|
+
goal text not null,
|
|
243
|
+
status text not null,
|
|
244
|
+
completion_state text,
|
|
245
|
+
mode text not null,
|
|
246
|
+
plan text,
|
|
247
|
+
evidence text,
|
|
248
|
+
error text,
|
|
249
|
+
tool_calls integer not null default 0,
|
|
250
|
+
created_at text not null,
|
|
251
|
+
updated_at text not null
|
|
252
|
+
);
|
|
253
|
+
create index if not exists tasks_workspace_idx on tasks(workspace_id, updated_at desc);
|
|
254
|
+
create index if not exists tasks_status_idx on tasks(status, updated_at desc);
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
function addColumnIfMissing(sqlite, table, column, definition) {
|
|
258
|
+
const columns = sqlite.prepare(`pragma table_info(${table})`).all();
|
|
259
|
+
if (columns.some((existingColumn) => existingColumn.name === column))
|
|
260
|
+
return;
|
|
261
|
+
sqlite.exec(`alter table ${table} add column ${column} ${definition}`);
|
|
262
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { index, integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
export const workspaceSessions = sqliteTable("workspace_sessions", {
|
|
3
|
+
id: text("id").primaryKey(),
|
|
4
|
+
root: text("root").notNull(),
|
|
5
|
+
status: text("status").notNull().default("active"),
|
|
6
|
+
mode: text("mode").notNull().default("checkout"),
|
|
7
|
+
sourceRoot: text("source_root"),
|
|
8
|
+
baseRef: text("base_ref"),
|
|
9
|
+
baseSha: text("base_sha"),
|
|
10
|
+
managed: text("managed").notNull().default("false"),
|
|
11
|
+
createdAt: text("created_at").notNull(),
|
|
12
|
+
lastUsedAt: text("last_used_at").notNull(),
|
|
13
|
+
}, (table) => [
|
|
14
|
+
index("workspace_sessions_root_idx").on(table.root, table.lastUsedAt),
|
|
15
|
+
index("workspace_sessions_status_idx").on(table.status, table.lastUsedAt),
|
|
16
|
+
]);
|
|
17
|
+
export const loadedAgentFiles = sqliteTable("loaded_agent_files", {
|
|
18
|
+
workspaceSessionId: text("workspace_session_id")
|
|
19
|
+
.notNull()
|
|
20
|
+
.references(() => workspaceSessions.id, { onDelete: "cascade" }),
|
|
21
|
+
path: text("path").notNull(),
|
|
22
|
+
contentHash: text("content_hash").notNull(),
|
|
23
|
+
content: text("content").notNull(),
|
|
24
|
+
loadedAt: text("loaded_at").notNull(),
|
|
25
|
+
lastSeenAt: text("last_seen_at").notNull(),
|
|
26
|
+
}, (table) => [
|
|
27
|
+
primaryKey({ columns: [table.workspaceSessionId, table.path] }),
|
|
28
|
+
index("loaded_agent_files_path_idx").on(table.path),
|
|
29
|
+
]);
|
|
30
|
+
export const workspaceConversationBindings = sqliteTable("workspace_conversation_bindings", {
|
|
31
|
+
conversationScopeId: text("conversation_scope_id").notNull(),
|
|
32
|
+
targetKey: text("target_key").notNull(),
|
|
33
|
+
workspaceSessionId: text("workspace_session_id")
|
|
34
|
+
.notNull()
|
|
35
|
+
.references(() => workspaceSessions.id, { onDelete: "cascade" }),
|
|
36
|
+
createdAt: text("created_at").notNull(),
|
|
37
|
+
lastUsedAt: text("last_used_at").notNull(),
|
|
38
|
+
}, (table) => [
|
|
39
|
+
primaryKey({ columns: [table.conversationScopeId, table.targetKey] }),
|
|
40
|
+
index("workspace_conversation_bindings_workspace_idx").on(table.workspaceSessionId),
|
|
41
|
+
]);
|
|
42
|
+
export const oauthClients = sqliteTable("oauth_clients", {
|
|
43
|
+
clientId: text("client_id").primaryKey(),
|
|
44
|
+
clientJson: text("client_json").notNull(),
|
|
45
|
+
issuedAt: integer("issued_at").notNull(),
|
|
46
|
+
});
|
|
47
|
+
export const oauthAccessTokens = sqliteTable("oauth_access_tokens", {
|
|
48
|
+
tokenHash: text("token_hash").primaryKey(),
|
|
49
|
+
clientId: text("client_id")
|
|
50
|
+
.notNull()
|
|
51
|
+
.references(() => oauthClients.clientId, { onDelete: "cascade" }),
|
|
52
|
+
scopesJson: text("scopes_json").notNull(),
|
|
53
|
+
expiresAt: integer("expires_at").notNull(),
|
|
54
|
+
resource: text("resource"),
|
|
55
|
+
});
|
|
56
|
+
export const oauthRefreshTokens = sqliteTable("oauth_refresh_tokens", {
|
|
57
|
+
tokenHash: text("token_hash").primaryKey(),
|
|
58
|
+
clientId: text("client_id")
|
|
59
|
+
.notNull()
|
|
60
|
+
.references(() => oauthClients.clientId, { onDelete: "cascade" }),
|
|
61
|
+
scopesJson: text("scopes_json").notNull(),
|
|
62
|
+
expiresAt: integer("expires_at").notNull(),
|
|
63
|
+
resource: text("resource"),
|
|
64
|
+
});
|
|
65
|
+
export const localAgentSessions = sqliteTable("local_agent_sessions", {
|
|
66
|
+
id: text("id").primaryKey(),
|
|
67
|
+
workspaceId: text("workspace_id"),
|
|
68
|
+
workspaceRoot: text("workspace_root").notNull(),
|
|
69
|
+
profileName: text("profile_name").notNull(),
|
|
70
|
+
provider: text("provider").notNull(),
|
|
71
|
+
model: text("model"),
|
|
72
|
+
effort: text("effort"),
|
|
73
|
+
providerSessionId: text("provider_session_id"),
|
|
74
|
+
status: text("status").notNull(),
|
|
75
|
+
latestResponse: text("latest_response"),
|
|
76
|
+
error: text("error"),
|
|
77
|
+
errorCode: text("error_code"),
|
|
78
|
+
errorRetryable: text("error_retryable"),
|
|
79
|
+
createdAt: text("created_at").notNull(),
|
|
80
|
+
updatedAt: text("updated_at").notNull(),
|
|
81
|
+
}, (table) => [
|
|
82
|
+
index("local_agent_sessions_workspace_id_idx").on(table.workspaceId, table.updatedAt),
|
|
83
|
+
index("local_agent_sessions_workspace_root_idx").on(table.workspaceRoot, table.updatedAt),
|
|
84
|
+
index("local_agent_sessions_provider_session_id_idx").on(table.providerSessionId),
|
|
85
|
+
]);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { openDatabase } from "./db/client.js";
|
|
4
|
+
const DEVICE_TOKEN_PREFIX = "dvst_";
|
|
5
|
+
export class DeviceTokenStore {
|
|
6
|
+
database;
|
|
7
|
+
constructor(stateDir) {
|
|
8
|
+
this.database = openDatabase(stateDir);
|
|
9
|
+
}
|
|
10
|
+
/** Returns the plaintext token exactly once; only the hash is persisted. */
|
|
11
|
+
create(name) {
|
|
12
|
+
const token = `${DEVICE_TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
|
|
13
|
+
const now = new Date().toISOString();
|
|
14
|
+
this.database.sqlite
|
|
15
|
+
.prepare(`insert into device_tokens (token_hash, name, created_at) values (?, ?, ?)
|
|
16
|
+
on conflict(token_hash) do nothing`)
|
|
17
|
+
.run(hashToken(token), name, now);
|
|
18
|
+
return { token, record: { name, createdAt: now } };
|
|
19
|
+
}
|
|
20
|
+
/** Verifies a presented token, refreshing last_used_at. */
|
|
21
|
+
verify(token) {
|
|
22
|
+
if (!token.startsWith(DEVICE_TOKEN_PREFIX))
|
|
23
|
+
return undefined;
|
|
24
|
+
const hash = hashToken(token);
|
|
25
|
+
const row = this.database.sqlite
|
|
26
|
+
.prepare("select name, created_at, last_used_at from device_tokens where token_hash = ?")
|
|
27
|
+
.get(hash);
|
|
28
|
+
if (!row)
|
|
29
|
+
return undefined;
|
|
30
|
+
this.database.sqlite
|
|
31
|
+
.prepare("update device_tokens set last_used_at = ? where token_hash = ?")
|
|
32
|
+
.run(new Date().toISOString(), hash);
|
|
33
|
+
return {
|
|
34
|
+
name: row.name,
|
|
35
|
+
createdAt: row.created_at,
|
|
36
|
+
...(row.last_used_at ? { lastUsedAt: row.last_used_at } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
const rows = this.database.sqlite
|
|
41
|
+
.prepare("select name, created_at, last_used_at from device_tokens order by created_at desc")
|
|
42
|
+
.all();
|
|
43
|
+
return rows.map((row) => ({
|
|
44
|
+
name: row.name,
|
|
45
|
+
createdAt: row.created_at,
|
|
46
|
+
...(row.last_used_at ? { lastUsedAt: row.last_used_at } : {}),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
revoke(name) {
|
|
50
|
+
const result = this.database.sqlite
|
|
51
|
+
.prepare("delete from device_tokens where name = ?")
|
|
52
|
+
.run(name);
|
|
53
|
+
return Number(result.changes) > 0;
|
|
54
|
+
}
|
|
55
|
+
close() {
|
|
56
|
+
this.database.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function hashToken(token) {
|
|
60
|
+
return createHash("sha256").update(token).digest("base64url");
|
|
61
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { mkdir, realpath, rm, stat } from "node:fs/promises";
|
|
5
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
6
|
+
import { assertAllowedPath, isPathInsideRoot } from "./roots.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export class GitWorktreeError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "GitWorktreeError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function createManagedWorktree(input) {
|
|
17
|
+
const sourcePath = assertAllowedPath(input.sourcePath, input.config.allowedRoots);
|
|
18
|
+
try {
|
|
19
|
+
const sourceStats = await stat(sourcePath);
|
|
20
|
+
if (!sourceStats.isDirectory()) {
|
|
21
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path is not a directory: ${input.sourcePath}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof GitWorktreeError)
|
|
26
|
+
throw error;
|
|
27
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path does not exist: ${input.sourcePath}`);
|
|
28
|
+
}
|
|
29
|
+
const sourceRoot = await resolveGitRoot(sourcePath, input.config.allowedRoots);
|
|
30
|
+
const baseRef = input.baseRef ?? "HEAD";
|
|
31
|
+
const baseSha = await resolveBaseCommit(sourceRoot, baseRef);
|
|
32
|
+
const dirtySource = (await git(["status", "--porcelain=v1"], sourceRoot)).trim().length > 0;
|
|
33
|
+
const worktreePath = managedWorktreePath({
|
|
34
|
+
worktreeRoot: input.config.worktreeRoot,
|
|
35
|
+
repoRoot: sourceRoot,
|
|
36
|
+
});
|
|
37
|
+
await mkdir(input.config.worktreeRoot, { recursive: true });
|
|
38
|
+
assertAllowedPath(worktreePath, [input.config.worktreeRoot]);
|
|
39
|
+
try {
|
|
40
|
+
await git(["worktree", "add", "--detach", worktreePath, baseSha], sourceRoot);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await rm(worktreePath, { recursive: true, force: true });
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
throw new GitWorktreeError("GIT_WORKTREE_CREATE_FAILED", `Git failed to create the managed worktree. ${message}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
sourceRoot,
|
|
49
|
+
path: worktreePath,
|
|
50
|
+
baseRef,
|
|
51
|
+
baseSha,
|
|
52
|
+
dirtySource,
|
|
53
|
+
detached: true,
|
|
54
|
+
managed: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function resolveGitRoot(path, allowedRoots) {
|
|
58
|
+
try {
|
|
59
|
+
const output = await git(["rev-parse", "--show-toplevel"], path);
|
|
60
|
+
return await assertGitRootAllowed(output.trim(), allowedRoots);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isGitUnavailable(error)) {
|
|
64
|
+
throw new GitWorktreeError("GIT_NOT_AVAILABLE", "Cannot open workspace in worktree mode because Git is not available on this machine.");
|
|
65
|
+
}
|
|
66
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because this path is not inside a Git repository: ${path}. Use mode=\"checkout\" to work directly in this directory, or initialize Git and create an initial commit first.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function assertGitRootAllowed(gitRoot, allowedRoots) {
|
|
70
|
+
try {
|
|
71
|
+
return assertAllowedPath(gitRoot, allowedRoots);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
const canonicalGitRoot = await realpath(gitRoot);
|
|
75
|
+
for (const allowedRoot of allowedRoots) {
|
|
76
|
+
const canonicalAllowedRoot = await realpath(allowedRoot).catch(() => undefined);
|
|
77
|
+
if (!canonicalAllowedRoot || !isPathInsideRoot(canonicalGitRoot, canonicalAllowedRoot)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const logicalGitRoot = resolve(allowedRoot, relative(canonicalAllowedRoot, canonicalGitRoot));
|
|
81
|
+
return assertAllowedPath(logicalGitRoot, allowedRoots);
|
|
82
|
+
}
|
|
83
|
+
return assertAllowedPath(canonicalGitRoot, allowedRoots);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function resolveBaseCommit(sourceRoot, baseRef) {
|
|
87
|
+
try {
|
|
88
|
+
return (await git(["rev-parse", "--verify", `${baseRef}^{commit}`], sourceRoot)).trim();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (baseRef === "HEAD") {
|
|
92
|
+
throw new GitWorktreeError("GIT_REPOSITORY_HAS_NO_COMMITS", "Cannot open workspace in worktree mode because the repository has no commits yet. Create an initial commit first, or use mode=\"checkout\".");
|
|
93
|
+
}
|
|
94
|
+
throw new GitWorktreeError("GIT_INVALID_BASE_REF", `Cannot open workspace in worktree mode because baseRef ${JSON.stringify(baseRef)} does not resolve to a commit.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function managedWorktreePath(input) {
|
|
98
|
+
const repoName = sanitizePathSegment(basename(input.repoRoot)) || "repo";
|
|
99
|
+
const worktreeId = randomBytes(4).toString("hex");
|
|
100
|
+
return join(input.worktreeRoot, `${repoName}-${worktreeId}`);
|
|
101
|
+
}
|
|
102
|
+
function sanitizePathSegment(value) {
|
|
103
|
+
return value
|
|
104
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
105
|
+
.replace(/^-+|-+$/g, "")
|
|
106
|
+
.slice(0, 80);
|
|
107
|
+
}
|
|
108
|
+
async function git(args, cwd) {
|
|
109
|
+
try {
|
|
110
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
111
|
+
cwd,
|
|
112
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
113
|
+
});
|
|
114
|
+
return stdout;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (isGitUnavailable(error))
|
|
118
|
+
throw error;
|
|
119
|
+
const stderr = typeof error === "object" && error && "stderr" in error
|
|
120
|
+
? String(error.stderr ?? "").trim()
|
|
121
|
+
: "";
|
|
122
|
+
const stdout = typeof error === "object" && error && "stdout" in error
|
|
123
|
+
? String(error.stdout ?? "").trim()
|
|
124
|
+
: "";
|
|
125
|
+
const details = stderr || stdout || (error instanceof Error ? error.message : String(error));
|
|
126
|
+
throw new Error(details);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function isGitUnavailable(error) {
|
|
130
|
+
return Boolean(typeof error === "object" &&
|
|
131
|
+
error &&
|
|
132
|
+
"code" in error &&
|
|
133
|
+
error.code === "ENOENT");
|
|
134
|
+
}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export async function git(cwd, args, options = {}) {
|
|
6
|
+
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
7
|
+
cwd,
|
|
8
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
9
|
+
maxBuffer: options.maxBuffer ?? 10 * 1024 * 1024,
|
|
10
|
+
});
|
|
11
|
+
return { stdout, stderr };
|
|
12
|
+
}
|
|
13
|
+
export async function getGitEligibility(cwd) {
|
|
14
|
+
try {
|
|
15
|
+
await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
reason: "not_git",
|
|
21
|
+
message: "workspace is not inside a git repository",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const gitRoot = (await git(cwd, ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
25
|
+
try {
|
|
26
|
+
await git(gitRoot, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
gitRoot,
|
|
32
|
+
reason: "no_head",
|
|
33
|
+
message: "repository has no HEAD commit",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, gitRoot };
|
|
37
|
+
}
|
|
38
|
+
export function safeWorkspaceRefSegment(workspaceId) {
|
|
39
|
+
const safe = workspaceId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
40
|
+
return safe.length > 0 ? safe : createHash("sha256").update(workspaceId).digest("hex").slice(0, 16);
|
|
41
|
+
}
|