@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.
Files changed (436) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -0
  3. package/bin/hearth-agentd.js +4 -0
  4. package/bin/hearth.js +4 -0
  5. package/bin/run-entrypoint.js +20 -0
  6. package/dist/agent-tools.js +738 -0
  7. package/dist/app-version.js +16 -0
  8. package/dist/apply-patch.js +402 -0
  9. package/dist/artifact-error.js +8 -0
  10. package/dist/artifact-tools.js +377 -0
  11. package/dist/cli-workspace.js +36 -0
  12. package/dist/cli.js +1086 -0
  13. package/dist/config-migration.js +82 -0
  14. package/dist/config-schema.js +136 -0
  15. package/dist/config.js +115 -0
  16. package/dist/context/context-tools.js +150 -0
  17. package/dist/context/repo-map.js +102 -0
  18. package/dist/dashboard/app.js +226 -0
  19. package/dist/dashboard/index.html +45 -0
  20. package/dist/dashboard/landing.html +57 -0
  21. package/dist/dashboard/styles.css +23 -0
  22. package/dist/dashboard.js +271 -0
  23. package/dist/db/client.js +29 -0
  24. package/dist/db/migrations.js +262 -0
  25. package/dist/db/schema.js +85 -0
  26. package/dist/device-tokens.js +61 -0
  27. package/dist/git-worktrees.js +134 -0
  28. package/dist/git.js +41 -0
  29. package/dist/incoming-artifacts.js +378 -0
  30. package/dist/local-agent-acp.js +768 -0
  31. package/dist/local-agent-adapters.js +20 -0
  32. package/dist/local-agent-availability.js +92 -0
  33. package/dist/local-agent-catalog.js +51 -0
  34. package/dist/local-agent-claude.js +334 -0
  35. package/dist/local-agent-client.js +519 -0
  36. package/dist/local-agent-codex.js +532 -0
  37. package/dist/local-agent-config.js +34 -0
  38. package/dist/local-agent-daemon-lifecycle.js +195 -0
  39. package/dist/local-agent-daemon-main.js +86 -0
  40. package/dist/local-agent-daemon-protocol.js +302 -0
  41. package/dist/local-agent-daemon.js +407 -0
  42. package/dist/local-agent-errors.js +378 -0
  43. package/dist/local-agent-grok.js +202 -0
  44. package/dist/local-agent-manager.js +715 -0
  45. package/dist/local-agent-opencode.js +463 -0
  46. package/dist/local-agent-path.js +24 -0
  47. package/dist/local-agent-pi-sandbox.js +472 -0
  48. package/dist/local-agent-pi.js +298 -0
  49. package/dist/local-agent-presentation.js +104 -0
  50. package/dist/local-agent-profiles.js +127 -0
  51. package/dist/local-agent-runtime-pool.js +481 -0
  52. package/dist/local-agent-runtime.js +1 -0
  53. package/dist/local-agent-store.js +193 -0
  54. package/dist/local-agent-targets.js +97 -0
  55. package/dist/logger.js +97 -0
  56. package/dist/machine-id.js +35 -0
  57. package/dist/mcp-modern-server.js +65 -0
  58. package/dist/oauth-provider.js +331 -0
  59. package/dist/oauth-store.js +144 -0
  60. package/dist/onboarding.js +35 -0
  61. package/dist/orchestration/brief.js +70 -0
  62. package/dist/orchestration/lanes.js +145 -0
  63. package/dist/orchestration/result.js +46 -0
  64. package/dist/orchestration/scope.js +61 -0
  65. package/dist/pi-tools.js +58 -0
  66. package/dist/policy/command-policy.js +104 -0
  67. package/dist/policy/env-allowlist.js +96 -0
  68. package/dist/policy/sandbox.js +68 -0
  69. package/dist/policy/workspace-profiles.js +47 -0
  70. package/dist/process-journal.js +116 -0
  71. package/dist/process-platform.js +49 -0
  72. package/dist/process-sessions.js +388 -0
  73. package/dist/rate-limit.js +77 -0
  74. package/dist/request-meta.js +9 -0
  75. package/dist/review-checkpoints.js +349 -0
  76. package/dist/roots.js +37 -0
  77. package/dist/server-shutdown.js +12 -0
  78. package/dist/server.js +980 -0
  79. package/dist/skills.js +84 -0
  80. package/dist/snapshot-manager.js +123 -0
  81. package/dist/stdio-server.js +80 -0
  82. package/dist/task-store.js +203 -0
  83. package/dist/task-tools.js +455 -0
  84. package/dist/tool-surfaces/claude.js +220 -0
  85. package/dist/tool-surfaces/codex.js +255 -0
  86. package/dist/tool-surfaces/index.js +15 -0
  87. package/dist/tool-surfaces/shared.js +228 -0
  88. package/dist/tool-surfaces/types.js +27 -0
  89. package/dist/ui/.vite/manifest.json +2544 -0
  90. package/dist/ui/assets/abap-CLvhMVsD.js +1 -0
  91. package/dist/ui/assets/actionscript-3--17pq3dv.js +1 -0
  92. package/dist/ui/assets/ada-C5qYipkI.js +1 -0
  93. package/dist/ui/assets/andromeeda-vGVdxbeo.js +1 -0
  94. package/dist/ui/assets/angular-html-DrbO3_Xe.js +1 -0
  95. package/dist/ui/assets/angular-ts-CmTVzHJx.js +1 -0
  96. package/dist/ui/assets/apache-U0d_L8uA.js +1 -0
  97. package/dist/ui/assets/apex-CGTLDQj6.js +1 -0
  98. package/dist/ui/assets/apl-BOpCpqXf.js +1 -0
  99. package/dist/ui/assets/applescript-CCn79oCD.js +1 -0
  100. package/dist/ui/assets/ara-4CJ0cIlV.js +1 -0
  101. package/dist/ui/assets/asciidoc-DE70LPWp.js +1 -0
  102. package/dist/ui/assets/asm-Cmm7eHzH.js +1 -0
  103. package/dist/ui/assets/astro-CxPeBwei.js +1 -0
  104. package/dist/ui/assets/aurora-x-CDeNXAV0.js +1 -0
  105. package/dist/ui/assets/awk-BWXHIvNe.js +1 -0
  106. package/dist/ui/assets/ayu-dark-DluEY0Gj.js +1 -0
  107. package/dist/ui/assets/ayu-light-C3h-C4tm.js +1 -0
  108. package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  109. package/dist/ui/assets/ballerina-B7ZEbQpA.js +1 -0
  110. package/dist/ui/assets/bat-Bo4NYOV-.js +1 -0
  111. package/dist/ui/assets/beancount-D-usSTwE.js +1 -0
  112. package/dist/ui/assets/berry-DKpUyyne.js +1 -0
  113. package/dist/ui/assets/bibtex-Ci_nEsc7.js +1 -0
  114. package/dist/ui/assets/bicep-CUHmPFLl.js +1 -0
  115. package/dist/ui/assets/bird2-C2hNVINV.js +1 -0
  116. package/dist/ui/assets/blade-DtsAAG4P.js +1 -0
  117. package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
  118. package/dist/ui/assets/c-D9hLjNUM.js +1 -0
  119. package/dist/ui/assets/c3-CnJL0r0V.js +1 -0
  120. package/dist/ui/assets/cadence-CQ2zXKGN.js +1 -0
  121. package/dist/ui/assets/cairo-DLTphjLi.js +1 -0
  122. package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
  123. package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
  124. package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
  125. package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
  126. package/dist/ui/assets/clarity-SemFz856.js +1 -0
  127. package/dist/ui/assets/clojure-DqKBuwfJ.js +1 -0
  128. package/dist/ui/assets/cmake-Bj61d0ZC.js +1 -0
  129. package/dist/ui/assets/cobol-C1SWlvZm.js +1 -0
  130. package/dist/ui/assets/codeowners-C8r90Shi.js +1 -0
  131. package/dist/ui/assets/codeql-oeQT6MSM.js +1 -0
  132. package/dist/ui/assets/coffee-B71R3v2U.js +1 -0
  133. package/dist/ui/assets/common-lisp-Cv5bFMCO.js +1 -0
  134. package/dist/ui/assets/coq-BrsZFFmf.js +1 -0
  135. package/dist/ui/assets/cpp--5a4c3v4.js +1 -0
  136. package/dist/ui/assets/crystal-AtqWYtE0.js +1 -0
  137. package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
  138. package/dist/ui/assets/css-CbqKSHI-.js +1 -0
  139. package/dist/ui/assets/csv-Dx-8-gkx.js +1 -0
  140. package/dist/ui/assets/cue-CE9AQfxI.js +1 -0
  141. package/dist/ui/assets/cypher-ClKdZ_lG.js +1 -0
  142. package/dist/ui/assets/d-qD-0Kul2.js +1 -0
  143. package/dist/ui/assets/dark-plus-Cs2F2srj.js +1 -0
  144. package/dist/ui/assets/dart-DkHntEIa.js +1 -0
  145. package/dist/ui/assets/dax-BkyTk9wS.js +1 -0
  146. package/dist/ui/assets/desktop-Dlh5hvp9.js +1 -0
  147. package/dist/ui/assets/diff-woXpYk--.js +1 -0
  148. package/dist/ui/assets/docker-IyjqRm3v.js +1 -0
  149. package/dist/ui/assets/dotenv-_5a1GRtc.js +1 -0
  150. package/dist/ui/assets/dracula-BHWKrbxM.js +1 -0
  151. package/dist/ui/assets/dracula-soft-5eyTD99u.js +1 -0
  152. package/dist/ui/assets/dream-maker-DW3nJb8Q.js +1 -0
  153. package/dist/ui/assets/edge-BPwY_RrJ.js +1 -0
  154. package/dist/ui/assets/elixir-DVH2yLJf.js +1 -0
  155. package/dist/ui/assets/elm-BrleA6gS.js +1 -0
  156. package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
  157. package/dist/ui/assets/erb-2SHiYe30.js +1 -0
  158. package/dist/ui/assets/erlang-Cphh6RMH.js +1 -0
  159. package/dist/ui/assets/everforest-dark-sB-x3p7T.js +1 -0
  160. package/dist/ui/assets/everforest-light-Df2xbC6M.js +1 -0
  161. package/dist/ui/assets/fennel-DQxkIbk2.js +1 -0
  162. package/dist/ui/assets/fish-BJitypiv.js +1 -0
  163. package/dist/ui/assets/fluent-C03EYrpw.js +1 -0
  164. package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  165. package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +1 -0
  166. package/dist/ui/assets/fsharp-D13ZGOAj.js +1 -0
  167. package/dist/ui/assets/gdresource-C0sCabJj.js +1 -0
  168. package/dist/ui/assets/gdscript-Cp2uCuqX.js +1 -0
  169. package/dist/ui/assets/gdshader-CBce3t8t.js +1 -0
  170. package/dist/ui/assets/genie-CV2tkWYe.js +1 -0
  171. package/dist/ui/assets/gherkin-DExj1W_8.js +1 -0
  172. package/dist/ui/assets/git-commit-BSykSTBG.js +1 -0
  173. package/dist/ui/assets/git-rebase-Bc7atMaV.js +1 -0
  174. package/dist/ui/assets/github-dark-C-LZuMrd.js +1 -0
  175. package/dist/ui/assets/github-dark-default-DXG-b-1a.js +1 -0
  176. package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  177. package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  178. package/dist/ui/assets/github-light-EUqPIrTm.js +1 -0
  179. package/dist/ui/assets/github-light-default-BXViO-2h.js +1 -0
  180. package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  181. package/dist/ui/assets/gleam-CSRkHgEL.js +1 -0
  182. package/dist/ui/assets/glimmer-js-DXeTDPtW.js +1 -0
  183. package/dist/ui/assets/glimmer-ts-CEOoXHU4.js +1 -0
  184. package/dist/ui/assets/glsl-5-43-Gsg.js +1 -0
  185. package/dist/ui/assets/gn-ilITqXS6.js +1 -0
  186. package/dist/ui/assets/gnuplot-7GGW24-e.js +1 -0
  187. package/dist/ui/assets/go-BJwz_mda.js +1 -0
  188. package/dist/ui/assets/graphql-Bk7Ah1KA.js +1 -0
  189. package/dist/ui/assets/groovy-CacY0gHj.js +1 -0
  190. package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  191. package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  192. package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  193. package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  194. package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  195. package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  196. package/dist/ui/assets/hack-DUHz4rCJ.js +1 -0
  197. package/dist/ui/assets/haml-BPCuuTPR.js +1 -0
  198. package/dist/ui/assets/handlebars-CHxmUsnU.js +1 -0
  199. package/dist/ui/assets/haskell-D8IpX4py.js +1 -0
  200. package/dist/ui/assets/haxe-OTjmBuCE.js +1 -0
  201. package/dist/ui/assets/hcl-Dh228itO.js +1 -0
  202. package/dist/ui/assets/hjson-CxZEssPk.js +1 -0
  203. package/dist/ui/assets/hlsl-Cvrh5tZx.js +1 -0
  204. package/dist/ui/assets/horizon-CE9ld1lL.js +1 -0
  205. package/dist/ui/assets/horizon-bright-Br1oVSNq.js +1 -0
  206. package/dist/ui/assets/houston-CsvMBhTu.js +1 -0
  207. package/dist/ui/assets/html-BBJqizz0.js +1 -0
  208. package/dist/ui/assets/html-derivative-ShTPCcKN.js +1 -0
  209. package/dist/ui/assets/http-VFTyziwA.js +1 -0
  210. package/dist/ui/assets/hurl-B8yxTa4r.js +1 -0
  211. package/dist/ui/assets/hxml-B0Qn7Nwc.js +1 -0
  212. package/dist/ui/assets/hy-CZbG8q4J.js +1 -0
  213. package/dist/ui/assets/imba-DsUTQ-LC.js +1 -0
  214. package/dist/ui/assets/ini-B5eOa1yu.js +1 -0
  215. package/dist/ui/assets/java-BZ5DAgJZ.js +1 -0
  216. package/dist/ui/assets/javascript-C_zuIEaC.js +1 -0
  217. package/dist/ui/assets/jinja-DWd6xfNr.js +1 -0
  218. package/dist/ui/assets/jison-BbY63d1V.js +1 -0
  219. package/dist/ui/assets/json-C4DnajZs.js +1 -0
  220. package/dist/ui/assets/json5-BR5RXkoi.js +1 -0
  221. package/dist/ui/assets/jsonc-CYpm1nAK.js +1 -0
  222. package/dist/ui/assets/jsonl-CmCQp5Yx.js +1 -0
  223. package/dist/ui/assets/jsonnet-CJTPZ8u_.js +1 -0
  224. package/dist/ui/assets/jssm-DXw9l8Rf.js +1 -0
  225. package/dist/ui/assets/jsx-C4AACieZ.js +1 -0
  226. package/dist/ui/assets/julia-sRu7EFMW.js +1 -0
  227. package/dist/ui/assets/just-DpZ9Sx1v.js +1 -0
  228. package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
  229. package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +1 -0
  230. package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +1 -0
  231. package/dist/ui/assets/kdl-CsD5j6eV.js +1 -0
  232. package/dist/ui/assets/kotlin-DhhofPvG.js +1 -0
  233. package/dist/ui/assets/kusto-BUv0MjJC.js +1 -0
  234. package/dist/ui/assets/laserwave-C_8bwKvT.js +1 -0
  235. package/dist/ui/assets/latex-B6a_uum7.js +1 -0
  236. package/dist/ui/assets/lean-CewbzKMR.js +1 -0
  237. package/dist/ui/assets/less-DVTAwKKz.js +1 -0
  238. package/dist/ui/assets/light-plus-DVQuIRkW.js +1 -0
  239. package/dist/ui/assets/liquid-B2sVpjjr.js +1 -0
  240. package/dist/ui/assets/llvm-Cm23YOpf.js +1 -0
  241. package/dist/ui/assets/log-BNLmms1o.js +1 -0
  242. package/dist/ui/assets/logo-Cluzi2Zq.js +1 -0
  243. package/dist/ui/assets/lua-DxJJll61.js +1 -0
  244. package/dist/ui/assets/luau-CNKltnaQ.js +1 -0
  245. package/dist/ui/assets/make-Dixweg8N.js +1 -0
  246. package/dist/ui/assets/markdown-BYOwaDjH.js +1 -0
  247. package/dist/ui/assets/marko-Dx8mw-Wx.js +1 -0
  248. package/dist/ui/assets/material-theme-Bm3Qr25_.js +1 -0
  249. package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +1 -0
  250. package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +1 -0
  251. package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  252. package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  253. package/dist/ui/assets/matlab-D7qyCx1q.js +1 -0
  254. package/dist/ui/assets/mdc-vNKeXEwR.js +1 -0
  255. package/dist/ui/assets/mdx-DQZ5AkYe.js +1 -0
  256. package/dist/ui/assets/mermaid-Bk4SNUv9.js +1 -0
  257. package/dist/ui/assets/min-dark-BSWPekZh.js +1 -0
  258. package/dist/ui/assets/min-light-DDpmG2fV.js +1 -0
  259. package/dist/ui/assets/mipsasm-BMqwQI7S.js +1 -0
  260. package/dist/ui/assets/mojo-BgCJLMeH.js +1 -0
  261. package/dist/ui/assets/monokai-CdkpiU2Y.js +1 -0
  262. package/dist/ui/assets/moonbit-CaWjb8XO.js +1 -0
  263. package/dist/ui/assets/move-B1IS1UjX.js +1 -0
  264. package/dist/ui/assets/narrat-_X_XdTYD.js +1 -0
  265. package/dist/ui/assets/nextflow-Bbiyy34d.js +1 -0
  266. package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +1 -0
  267. package/dist/ui/assets/nginx-DScQAGLT.js +1 -0
  268. package/dist/ui/assets/night-owl-DhmEMT88.js +1 -0
  269. package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +1 -0
  270. package/dist/ui/assets/nim-lAJch9uT.js +1 -0
  271. package/dist/ui/assets/nix-IvuFDN5E.js +1 -0
  272. package/dist/ui/assets/nord-Cb4Vim4T.js +1 -0
  273. package/dist/ui/assets/nushell-DcLAeLz5.js +1 -0
  274. package/dist/ui/assets/objective-c-D1A_Heim.js +1 -0
  275. package/dist/ui/assets/objective-cpp-BsSzOQcm.js +1 -0
  276. package/dist/ui/assets/ocaml-O90oeIOV.js +1 -0
  277. package/dist/ui/assets/odin-B1RWQWA5.js +1 -0
  278. package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +1 -0
  279. package/dist/ui/assets/one-light-D7Lr4KcI.js +1 -0
  280. package/dist/ui/assets/openscad-BUDT5pXO.js +1 -0
  281. package/dist/ui/assets/pascal-4ZHwLPI5.js +1 -0
  282. package/dist/ui/assets/perl-C_GkVo_E.js +1 -0
  283. package/dist/ui/assets/php-DziV3K6J.js +1 -0
  284. package/dist/ui/assets/pierre-dark-CpLgRqie.js +1 -0
  285. package/dist/ui/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  286. package/dist/ui/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  287. package/dist/ui/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  288. package/dist/ui/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  289. package/dist/ui/assets/pierre-light-CoaEpmwp.js +1 -0
  290. package/dist/ui/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  291. package/dist/ui/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  292. package/dist/ui/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  293. package/dist/ui/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  294. package/dist/ui/assets/pkl-ot-7Btpt.js +1 -0
  295. package/dist/ui/assets/plastic-DQwYfKfQ.js +1 -0
  296. package/dist/ui/assets/plsql-DGHpHOYJ.js +1 -0
  297. package/dist/ui/assets/po-BiJDBrnU.js +1 -0
  298. package/dist/ui/assets/poimandres-DRFjx7u4.js +1 -0
  299. package/dist/ui/assets/polar-C7UOKdEL.js +1 -0
  300. package/dist/ui/assets/postcss-BXeXVLqQ.js +1 -0
  301. package/dist/ui/assets/powerquery-DNMTfnFr.js +1 -0
  302. package/dist/ui/assets/powershell-DshXNtvi.js +1 -0
  303. package/dist/ui/assets/prisma-BsRQq5mF.js +1 -0
  304. package/dist/ui/assets/prolog-iXnhIJG7.js +1 -0
  305. package/dist/ui/assets/proto-DB4EqR-F.js +1 -0
  306. package/dist/ui/assets/pug-z75UIWVK.js +1 -0
  307. package/dist/ui/assets/puppet-CDv2pdJW.js +1 -0
  308. package/dist/ui/assets/purescript-9MfHhQsQ.js +1 -0
  309. package/dist/ui/assets/python-gzcpVVnB.js +1 -0
  310. package/dist/ui/assets/qml-Cmm4coz1.js +1 -0
  311. package/dist/ui/assets/qmldir-DCQb3MpD.js +1 -0
  312. package/dist/ui/assets/qss-Fe1Jh2GI.js +1 -0
  313. package/dist/ui/assets/r-Brd0axBE.js +1 -0
  314. package/dist/ui/assets/racket-DcIDlBhZ.js +1 -0
  315. package/dist/ui/assets/raku-B3gFvitq.js +1 -0
  316. package/dist/ui/assets/razor-DSbwmoiu.js +1 -0
  317. package/dist/ui/assets/red-CJ3rzSJv.js +1 -0
  318. package/dist/ui/assets/reg-CRGYupPL.js +1 -0
  319. package/dist/ui/assets/regexp-Bs3Uk-pY.js +1 -0
  320. package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
  321. package/dist/ui/assets/review-payload-CbwCBKB1.js +1580 -0
  322. package/dist/ui/assets/riscv-Ckw8ddFX.js +1 -0
  323. package/dist/ui/assets/ron-VUp2lXgN.js +1 -0
  324. package/dist/ui/assets/rose-pine-BthvhNj6.js +1 -0
  325. package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
  326. package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +1 -0
  327. package/dist/ui/assets/rosmsg-CAekHB0j.js +1 -0
  328. package/dist/ui/assets/rst-ChIyKpUC.js +1 -0
  329. package/dist/ui/assets/ruby-BdAmvOMO.js +1 -0
  330. package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
  331. package/dist/ui/assets/sas-DpXn8hc5.js +1 -0
  332. package/dist/ui/assets/sass-DXrisJhu.js +1 -0
  333. package/dist/ui/assets/scala-DKOlJaKm.js +1 -0
  334. package/dist/ui/assets/scheme-DQCgrYNe.js +1 -0
  335. package/dist/ui/assets/scss-atX1Uuef.js +1 -0
  336. package/dist/ui/assets/sdbl-bTVj8UrX.js +1 -0
  337. package/dist/ui/assets/shaderlab-TOUzSsQk.js +1 -0
  338. package/dist/ui/assets/shellscript-BqgcAeMK.js +1 -0
  339. package/dist/ui/assets/shellsession-DfQSMRN5.js +1 -0
  340. package/dist/ui/assets/slack-dark-DnToyrRv.js +1 -0
  341. package/dist/ui/assets/slack-ochin-B2OO5cIa.js +1 -0
  342. package/dist/ui/assets/smalltalk-B16xEiuN.js +1 -0
  343. package/dist/ui/assets/snazzy-light-4G7pJPwS.js +1 -0
  344. package/dist/ui/assets/solarized-dark-DV17i1UV.js +1 -0
  345. package/dist/ui/assets/solarized-light-DSh2HLQt.js +1 -0
  346. package/dist/ui/assets/solidity-CKzVLygQ.js +1 -0
  347. package/dist/ui/assets/soy-DzP7zZTB.js +1 -0
  348. package/dist/ui/assets/sparql-D_iOobhT.js +1 -0
  349. package/dist/ui/assets/splunk-BC2Px7Mm.js +1 -0
  350. package/dist/ui/assets/sql-gmaLk66z.js +1 -0
  351. package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
  352. package/dist/ui/assets/stata-CJ0YTMcV.js +1 -0
  353. package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
  354. package/dist/ui/assets/surrealql-LH7FciQz.js +1 -0
  355. package/dist/ui/assets/svelte-BME2PU6V.js +1 -0
  356. package/dist/ui/assets/swift-DonLKvLd.js +1 -0
  357. package/dist/ui/assets/synthwave-84-nFMaYfgc.js +1 -0
  358. package/dist/ui/assets/system-verilog-DJ5XKQeo.js +1 -0
  359. package/dist/ui/assets/systemd-BxMlprV5.js +1 -0
  360. package/dist/ui/assets/talonscript-CohzipZa.js +1 -0
  361. package/dist/ui/assets/tasl-DMoTqEGO.js +1 -0
  362. package/dist/ui/assets/tcl-CZd0xW_V.js +1 -0
  363. package/dist/ui/assets/templ-DHBNIfYW.js +1 -0
  364. package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
  365. package/dist/ui/assets/tex-C8-kPEzO.js +1 -0
  366. package/dist/ui/assets/tokyo-night-oM2G3aXe.js +1 -0
  367. package/dist/ui/assets/toml-CcmNWLt0.js +1 -0
  368. package/dist/ui/assets/ts-tags-CiewV0vR.js +1 -0
  369. package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
  370. package/dist/ui/assets/tsx-CUYPqmnq.js +1 -0
  371. package/dist/ui/assets/turtle-ByJddavk.js +1 -0
  372. package/dist/ui/assets/twig-BTkvsl0b.js +1 -0
  373. package/dist/ui/assets/typescript-DDQMaPwj.js +1 -0
  374. package/dist/ui/assets/typespec-B88KGewJ.js +1 -0
  375. package/dist/ui/assets/typst-DI99ib-x.js +1 -0
  376. package/dist/ui/assets/v-DETTlOr0.js +1 -0
  377. package/dist/ui/assets/vala-zf12oZj6.js +1 -0
  378. package/dist/ui/assets/vb-Djn5o6TS.js +1 -0
  379. package/dist/ui/assets/verilog-CiiDBU1e.js +1 -0
  380. package/dist/ui/assets/vesper-D5bVUKB1.js +1 -0
  381. package/dist/ui/assets/vhdl-BroJfC0k.js +1 -0
  382. package/dist/ui/assets/viml-DvXPmvsu.js +1 -0
  383. package/dist/ui/assets/vitesse-black-fwtXNY1n.js +1 -0
  384. package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +1 -0
  385. package/dist/ui/assets/vitesse-light-VbXTXTou.js +1 -0
  386. package/dist/ui/assets/vue-BH5SEWRH.js +1 -0
  387. package/dist/ui/assets/vue-html-BT7yeYez.js +1 -0
  388. package/dist/ui/assets/vue-vine-Cs5IN2rs.js +1 -0
  389. package/dist/ui/assets/vyper-CgoNMtux.js +1 -0
  390. package/dist/ui/assets/wasm-BnjxR4X6.js +1 -0
  391. package/dist/ui/assets/wasm-ByWQv1Qj.js +1 -0
  392. package/dist/ui/assets/wenyan-C8pVoKbM.js +1 -0
  393. package/dist/ui/assets/wgsl-BsKzXJz4.js +1 -0
  394. package/dist/ui/assets/wikitext-ClFFjSW2.js +1 -0
  395. package/dist/ui/assets/wit-DdvCle-K.js +1 -0
  396. package/dist/ui/assets/wolfram-DLL8P-h_.js +1 -0
  397. package/dist/ui/assets/workspace-app-B_BnSEHO.css +1 -0
  398. package/dist/ui/assets/workspace-app-DSFpnrIC.js +115 -0
  399. package/dist/ui/assets/xml-D3_1BEcO.js +1 -0
  400. package/dist/ui/assets/xsl-BBOCDn4Y.js +1 -0
  401. package/dist/ui/assets/yaml-lThBDT45.js +1 -0
  402. package/dist/ui/assets/zenscript-BnlCZFoB.js +1 -0
  403. package/dist/ui/assets/zig-CMLA9XwU.js +1 -0
  404. package/dist/ui/workspace-app.html +15 -0
  405. package/dist/user-config.js +228 -0
  406. package/dist/verification.js +74 -0
  407. package/dist/workspace-store.js +133 -0
  408. package/dist/workspaces.js +433 -0
  409. package/docs/adr/devspace-x-implementation-adrs.md +159 -0
  410. package/docs/agent-profile-schema.md +203 -0
  411. package/docs/artifact-exchange.md +44 -0
  412. package/docs/assets/devspace-logo-light.png +0 -0
  413. package/docs/assets/devspace-screenshot.png +0 -0
  414. package/docs/assets/v11-review-ui-after.png +0 -0
  415. package/docs/assets/v11-review-ui-before.png +0 -0
  416. package/docs/chatgpt-coding-workflow.md +215 -0
  417. package/docs/configuration.md +217 -0
  418. package/docs/gotchas.md +294 -0
  419. package/docs/local-agent-daemon.md +101 -0
  420. package/docs/security.md +197 -0
  421. package/docs/setup.md +174 -0
  422. package/examples/agents/claude-implementer.md +30 -0
  423. package/examples/agents/codex-explorer.md +27 -0
  424. package/examples/agents/codex-qa-tester.md +29 -0
  425. package/examples/agents/codex-worker.md +25 -0
  426. package/examples/agents/copilot-reviewer.md +25 -0
  427. package/examples/agents/cursor-agent-worker.md +26 -0
  428. package/examples/agents/grok-builder.md +10 -0
  429. package/examples/agents/opencode-explorer.md +27 -0
  430. package/examples/agents/pi-reviewer.md +27 -0
  431. package/package.json +102 -0
  432. package/schema/v1/hearth.schema.json +529 -0
  433. package/scripts/copy-dashboard.mjs +9 -0
  434. package/scripts/fix-node-pty-permissions.mjs +22 -0
  435. package/scripts/generate-config-schema.ts +9 -0
  436. package/skills/subagents/SKILL.md +57 -0
@@ -0,0 +1,715 @@
1
+ import { resolve } from "node:path";
2
+ import { Result } from "better-result";
3
+ import { AgentConflictError, AgentScopeError, AgentTargetError, isLocalAgentError, isProgrammerDefect, } from "./local-agent-errors.js";
4
+ import { isLocalAgentProvider, } from "./local-agent-profiles.js";
5
+ import { resolveLocalAgentTarget, } from "./local-agent-targets.js";
6
+ import { assertAllowedPath } from "./roots.js";
7
+ import { isSubagentProviderEnabled, } from "./local-agent-config.js";
8
+ /** Default watchdog budget per agent turn (30 minutes). */
9
+ export const DEFAULT_AGENT_TURN_TIMEOUT_MS = 30 * 60 * 1000;
10
+ /** Watchdog ceiling (7 days); larger values are clamped, not rejected. */
11
+ export const MAX_AGENT_TURN_TIMEOUT_MS = 7 * 24 * 3600 * 1000;
12
+ /** Defensive clamp: non-finite/non-positive fall back to the default. */
13
+ export function normalizeTimeoutMs(value) {
14
+ if (value === undefined || !Number.isFinite(value) || value <= 0)
15
+ return DEFAULT_AGENT_TURN_TIMEOUT_MS;
16
+ return Math.min(Math.floor(value), MAX_AGENT_TURN_TIMEOUT_MS);
17
+ }
18
+ /**
19
+ * Bound for the persisted in-flight output tail (characters).
20
+ */
21
+ const MAX_OUTPUT_CHARS = 64_000;
22
+ /**
23
+ * Owns one durable Hearth agent's turn lifecycle. Provider runtimes remain
24
+ * below this seam; this class only translates records into provider inputs and
25
+ * persists the result.
26
+ */
27
+ export class LocalAgentManager {
28
+ store;
29
+ drivers = new Map();
30
+ pool;
31
+ loadProfiles;
32
+ agentDir;
33
+ allowedRoots;
34
+ logger;
35
+ subagents;
36
+ activeTurns = new Map();
37
+ activeRuntimeKeys = new Map();
38
+ outputBuffers = new Map();
39
+ /**
40
+ * Stop/pause requests for agents with an in-flight turn. When the provider
41
+ * run fails because its runtime was force-closed, the terminal status is
42
+ * taken from here instead of persisting a provider error.
43
+ */
44
+ interruptedAgents = new Map();
45
+ /**
46
+ * Agents whose watchdog budget expired. Checked before interruptedAgents:
47
+ * a turn that outlived its budget is a timeout error even if a stop/pause
48
+ * landed in the same window.
49
+ */
50
+ timedOutAgents = new Set();
51
+ /** Budget recorded per tracked turn so timeout errors report the real value. */
52
+ turnTimeoutMs = new Map();
53
+ accepting = true;
54
+ closePromise;
55
+ constructor(options) {
56
+ this.store = options.store;
57
+ for (const driver of options.drivers)
58
+ this.drivers.set(driver.provider, driver);
59
+ this.pool = options.pool;
60
+ this.loadProfiles = options.loadProfiles;
61
+ this.agentDir = options.agentDir;
62
+ this.allowedRoots = options.allowedRoots;
63
+ this.logger = options.logger;
64
+ this.subagents = options.subagents;
65
+ }
66
+ reconcileActiveRuns(message) {
67
+ return this.store.reconcileActiveRunsResult(message);
68
+ }
69
+ async start(input) {
70
+ const manager = this;
71
+ return Result.gen(async function* () {
72
+ yield* manager.acceptingResult("start");
73
+ const workspaceRoot = yield* manager.authorizeWorkspace(input.workspaceRoot, input.workspaceId, "start");
74
+ const profiles = yield* Result.await(manager.loadProfilesResult(workspaceRoot, input.target));
75
+ const target = resolveLocalAgentTarget(input.target, profiles, input.model, input.effort, manager.subagents.providers);
76
+ if (!target) {
77
+ return Result.err(new AgentTargetError({
78
+ code: "UNKNOWN_TARGET",
79
+ target: input.target,
80
+ retryable: false,
81
+ message: `Unknown subagent profile or provider: ${input.target}.`,
82
+ }));
83
+ }
84
+ if (target.kind === "profile" && target.profile.disabled) {
85
+ return Result.err(new AgentTargetError({
86
+ code: "PROVIDER_DISABLED",
87
+ target: target.name,
88
+ provider: target.provider,
89
+ retryable: false,
90
+ message: `Subagent profile is disabled: ${target.name}.`,
91
+ }));
92
+ }
93
+ yield* manager.providerEnabledResult(target.provider, target.name, "start");
94
+ yield* manager.driverResult(target.provider, "start");
95
+ const record = yield* manager.store.createResult({
96
+ workspaceId: input.workspaceId,
97
+ workspaceRoot,
98
+ profileName: target.name,
99
+ provider: target.provider,
100
+ model: target.model,
101
+ effort: target.effort,
102
+ });
103
+ return manager.begin(record, input.prompt, {
104
+ model: target.model,
105
+ effort: target.effort,
106
+ writeMode: input.writeMode,
107
+ }, input.workspaceId, input.timeoutMs);
108
+ });
109
+ }
110
+ async continue(agentId, prompt, overrides = {}, scope, timeoutMs) {
111
+ const manager = this;
112
+ return Result.gen(async function* () {
113
+ yield* manager.acceptingResult("continue", agentId);
114
+ const record = yield* manager.store.getByIdResult(agentId);
115
+ if (!record)
116
+ return Result.err(agentNotFound(agentId));
117
+ yield* manager.agentWorkspaceResult(record, scope, "continue");
118
+ const profiles = yield* Result.await(manager.loadProfilesResult(record.workspaceRoot, record.profileName));
119
+ yield* manager.profileForRecordResult(record, profiles);
120
+ yield* manager.providerEnabledResult(record.provider, record.profileName, "continue");
121
+ yield* manager.driverResult(record.provider, "continue", agentId);
122
+ return manager.begin(record, prompt, overrides, scope.workspaceId, timeoutMs);
123
+ });
124
+ }
125
+ get(agentId, scope) {
126
+ const lookup = this.store.getByIdResult(agentId);
127
+ if (lookup.isErr())
128
+ return lookup;
129
+ const record = lookup.value;
130
+ if (!record)
131
+ return Result.err(agentNotFound(agentId));
132
+ const scoped = this.agentWorkspaceResult(record, scope, "get");
133
+ if (scoped.isErr())
134
+ return scoped;
135
+ return Result.ok(record);
136
+ }
137
+ list(scope) {
138
+ return this.authorizeWorkspace(scope.workspaceRoot, scope.workspaceId, "list").andThen((workspaceRoot) => (this.store.listResult({
139
+ workspaceId: scope.workspaceId,
140
+ workspaceRoot,
141
+ })));
142
+ }
143
+ /**
144
+ * Pause an agent. Between turns this only flips the durable status; with
145
+ * `force` a running turn is interrupted by closing its runtime.
146
+ */
147
+ async pause(agentId, scope, options = {}) {
148
+ const lookup = this.store.getByIdResult(agentId);
149
+ if (lookup.isErr())
150
+ return lookup;
151
+ const record = lookup.value;
152
+ if (!record)
153
+ return Result.err(agentNotFound(agentId));
154
+ const scoped = this.agentWorkspaceResult(record, scope, "pause");
155
+ if (scoped.isErr())
156
+ return scoped;
157
+ if (this.activeTurns.has(agentId)) {
158
+ if (!options.force) {
159
+ return Result.err(new AgentConflictError({
160
+ code: "AGENT_TURN_ACTIVE",
161
+ agentId,
162
+ operation: "pause",
163
+ retryable: true,
164
+ message: `Agent ${agentId} has a running turn. Wait for it to finish or pause with force.`,
165
+ }));
166
+ }
167
+ await this.interruptTurn(agentId, "paused");
168
+ }
169
+ return this.store.updateResult(agentId, {
170
+ status: "paused",
171
+ error: undefined,
172
+ errorCode: undefined,
173
+ errorRetryable: undefined,
174
+ });
175
+ }
176
+ /**
177
+ * Resume a paused agent (idle/error agents also resume) by starting a new
178
+ * turn on the durable provider session.
179
+ */
180
+ async resume(agentId, prompt, overrides = {}, scope, timeoutMs) {
181
+ const manager = this;
182
+ return Result.gen(async function* () {
183
+ yield* manager.acceptingResult("resume", agentId);
184
+ const record = yield* manager.store.getByIdResult(agentId);
185
+ if (!record)
186
+ return Result.err(agentNotFound(agentId));
187
+ yield* manager.agentWorkspaceResult(record, scope, "resume");
188
+ if (manager.activeTurns.has(agentId)) {
189
+ return Result.err(new AgentConflictError({
190
+ code: "AGENT_CONFLICT",
191
+ agentId,
192
+ operation: "resume",
193
+ retryable: true,
194
+ message: `Agent ${agentId} already has a running turn.`,
195
+ }));
196
+ }
197
+ if (record.status === "starting" || record.status === "running") {
198
+ return Result.err(new AgentConflictError({
199
+ code: "AGENT_CONFLICT",
200
+ agentId,
201
+ operation: "resume",
202
+ retryable: true,
203
+ message: `Agent ${agentId} is ${record.status}; resume applies to paused, idle, or error agents.`,
204
+ }));
205
+ }
206
+ const profiles = yield* Result.await(manager.loadProfilesResult(record.workspaceRoot, record.profileName));
207
+ yield* manager.profileForRecordResult(record, profiles);
208
+ yield* manager.providerEnabledResult(record.provider, record.profileName, "resume");
209
+ yield* manager.driverResult(record.provider, "resume", agentId);
210
+ return manager.begin(record, prompt ?? "Resume the task.", overrides, scope.workspaceId, timeoutMs);
211
+ });
212
+ }
213
+ /**
214
+ * Stop an agent. Between turns this flips the durable status to stopped;
215
+ * with `force` (or via cancel()) a running turn is interrupted.
216
+ */
217
+ async stop(agentId, scope, options = {}) {
218
+ const lookup = this.store.getByIdResult(agentId);
219
+ if (lookup.isErr())
220
+ return lookup;
221
+ const record = lookup.value;
222
+ if (!record)
223
+ return Result.err(agentNotFound(agentId));
224
+ const scoped = this.agentWorkspaceResult(record, scope, "stop");
225
+ if (scoped.isErr())
226
+ return scoped;
227
+ if (this.activeTurns.has(agentId)) {
228
+ if (!options.force) {
229
+ return Result.err(new AgentConflictError({
230
+ code: "AGENT_TURN_ACTIVE",
231
+ agentId,
232
+ operation: "stop",
233
+ retryable: true,
234
+ message: `Agent ${agentId} has a running turn. Wait for it to finish or stop with force.`,
235
+ }));
236
+ }
237
+ await this.interruptTurn(agentId, "stopped");
238
+ }
239
+ return this.store.updateResult(agentId, {
240
+ status: "stopped",
241
+ error: undefined,
242
+ errorCode: undefined,
243
+ errorRetryable: undefined,
244
+ });
245
+ }
246
+ /** Unconditional stop: interrupts a running turn when one exists. */
247
+ async cancel(agentId, scope) {
248
+ return this.stop(agentId, scope, { force: true });
249
+ }
250
+ async interruptTurn(agentId, target) {
251
+ this.interruptedAgents.set(agentId, target);
252
+ const runtimeKey = this.activeRuntimeKeys.get(agentId);
253
+ if (runtimeKey) {
254
+ try {
255
+ await this.pool.cancelRuntimeKey(runtimeKey, `agent_${target}`);
256
+ }
257
+ finally {
258
+ this.activeRuntimeKeys.delete(agentId);
259
+ }
260
+ }
261
+ }
262
+ async close() {
263
+ if (this.closePromise)
264
+ return this.closePromise;
265
+ this.accepting = false;
266
+ const turns = Array.from(this.activeTurns.values());
267
+ this.closePromise = (async () => {
268
+ // Closing pooled runtimes is what interrupts provider turns. Waiting for
269
+ // those turns first can strand a provider process indefinitely.
270
+ await this.pool.close();
271
+ const turnResults = await Promise.allSettled(turns);
272
+ for (const result of turnResults) {
273
+ if (result.status === "rejected") {
274
+ this.log("warn", "local_agent_close_failed", { error: errorMessage(result.reason) });
275
+ }
276
+ }
277
+ this.store.close();
278
+ })();
279
+ return this.closePromise;
280
+ }
281
+ get activeTurnCount() {
282
+ return this.activeTurns.size;
283
+ }
284
+ get runtimeCount() {
285
+ return this.pool.size;
286
+ }
287
+ async evictIdle(now) {
288
+ await this.pool.evictIdle(now);
289
+ }
290
+ begin(record, prompt, overrides, workspaceId, timeoutMs) {
291
+ if (this.activeTurns.has(record.id)) {
292
+ return Result.err(new AgentConflictError({
293
+ code: "AGENT_CONFLICT",
294
+ agentId: record.id,
295
+ operation: "continue",
296
+ retryable: true,
297
+ message: `Agent ${record.id} already has a running turn.`,
298
+ }));
299
+ }
300
+ const updated = this.store.updateResult(record.id, {
301
+ status: "running",
302
+ model: overrides.model ?? record.model,
303
+ effort: overrides.effort ?? record.effort,
304
+ latestResponse: undefined,
305
+ latestOutput: undefined,
306
+ error: undefined,
307
+ errorCode: undefined,
308
+ errorRetryable: undefined,
309
+ });
310
+ if (updated.isErr())
311
+ return updated;
312
+ this.outputBuffers.set(record.id, "");
313
+ // Defer invocation until after the tracking entry is visible. This keeps
314
+ // cleanup correct even if runTurn later gains a synchronous completion path.
315
+ const turn = Promise.resolve().then(() => (this.runTurn(updated.value, prompt, overrides, workspaceId, normalizeTimeoutMs(timeoutMs))));
316
+ this.turnTimeoutMs.set(record.id, normalizeTimeoutMs(timeoutMs));
317
+ this.activeTurns.set(record.id, turn);
318
+ void turn.catch(() => undefined);
319
+ return updated;
320
+ }
321
+ async runTurn(record, prompt, overrides, workspaceId, timeoutMs = DEFAULT_AGENT_TURN_TIMEOUT_MS) {
322
+ const startedAt = Date.now();
323
+ const watchdog = this.armWatchdog(record.id, timeoutMs);
324
+ this.log("info", "agent_run_started", {
325
+ provider: record.provider,
326
+ agentId: record.id,
327
+ providerSessionIdPrefix: record.providerSessionId?.slice(0, 8),
328
+ });
329
+ try {
330
+ const authorized = this.authorizeWorkspace(record.workspaceRoot, workspaceId, "run");
331
+ if (authorized.isErr()) {
332
+ this.persistRunError(record, authorized.error, startedAt);
333
+ return;
334
+ }
335
+ const workspaceRoot = authorized.value;
336
+ const authorizedRecord = workspaceRoot === record.workspaceRoot
337
+ ? record
338
+ : { ...record, workspaceRoot };
339
+ const profiles = await this.loadProfilesResult(workspaceRoot, record.profileName);
340
+ if (profiles.isErr()) {
341
+ this.persistRunError(record, profiles.error, startedAt);
342
+ return;
343
+ }
344
+ const profile = this.profileForRecordResult(record, profiles.value);
345
+ if (profile.isErr()) {
346
+ this.persistRunError(record, profile.error, startedAt);
347
+ return;
348
+ }
349
+ const input = this.buildRunInputResult(authorizedRecord, profile.value, prompt, overrides);
350
+ if (input.isErr()) {
351
+ this.persistRunError(record, input.error, startedAt);
352
+ return;
353
+ }
354
+ const driver = this.driverResult(record.provider, "run", record.id);
355
+ if (driver.isErr()) {
356
+ this.persistRunError(record, driver.error, startedAt);
357
+ return;
358
+ }
359
+ const context = {
360
+ agentId: record.id,
361
+ provider: driver.value.provider,
362
+ workspaceRoot,
363
+ providerSessionId: record.providerSessionId,
364
+ writeMode: input.value.writeMode,
365
+ model: input.value.model,
366
+ effort: input.value.effort,
367
+ agentDir: this.agentDir,
368
+ };
369
+ this.activeRuntimeKeys.set(record.id, driver.value.runtimeKey(context));
370
+ const callbacks = {
371
+ onSessionId: (providerSessionId) => {
372
+ const current = this.store.getByIdResult(record.id);
373
+ if (current.isErr())
374
+ throw current.error;
375
+ if (!current.value || current.value.providerSessionId === providerSessionId)
376
+ return;
377
+ const updated = this.store.updateResult(record.id, { providerSessionId });
378
+ if (updated.isErr())
379
+ throw updated.error;
380
+ },
381
+ onOutput: (delta) => {
382
+ if (!delta)
383
+ return;
384
+ const buffer = ((this.outputBuffers.get(record.id) ?? "") + delta).slice(-MAX_OUTPUT_CHARS);
385
+ this.outputBuffers.set(record.id, buffer);
386
+ const persisted = this.store.updateResult(record.id, { latestOutput: buffer });
387
+ if (persisted.isErr()) {
388
+ this.log("warn", "agent_output_persist_failed", { agentId: record.id });
389
+ }
390
+ },
391
+ };
392
+ const result = await this.pool.run(driver.value, context, input.value, callbacks);
393
+ if (result.isErr()) {
394
+ this.persistRunError(record, result.error, startedAt);
395
+ return;
396
+ }
397
+ const runResult = result.value;
398
+ const current = this.store.getByIdResult(record.id);
399
+ if (current.isErr())
400
+ throw current.error;
401
+ if (!current.value)
402
+ return;
403
+ const interrupted = this.interruptedAgents.get(record.id);
404
+ if (this.timedOutAgents.has(record.id)) {
405
+ this.persistTimeoutError(record, startedAt, timeoutMs);
406
+ return;
407
+ }
408
+ if (interrupted) {
409
+ this.store.updateResult(record.id, {
410
+ providerSessionId: runResult.providerSessionId ?? current.value.providerSessionId,
411
+ status: interrupted,
412
+ error: undefined,
413
+ errorCode: undefined,
414
+ errorRetryable: undefined,
415
+ });
416
+ return;
417
+ }
418
+ const updated = this.store.updateResult(record.id, {
419
+ providerSessionId: runResult.providerSessionId ?? current.value.providerSessionId,
420
+ status: "idle",
421
+ latestResponse: runResult.finalResponse,
422
+ error: undefined,
423
+ errorCode: undefined,
424
+ errorRetryable: undefined,
425
+ });
426
+ if (updated.isErr())
427
+ throw updated.error;
428
+ this.log("info", "agent_run_completed", {
429
+ provider: updated.value.provider,
430
+ agentId: updated.value.id,
431
+ providerSessionIdPrefix: updated.value.providerSessionId?.slice(0, 8),
432
+ durationMs: Math.max(0, Date.now() - startedAt),
433
+ });
434
+ }
435
+ catch (error) {
436
+ if (isLocalAgentError(error)) {
437
+ this.persistRunError(record, error, startedAt);
438
+ return;
439
+ }
440
+ const persisted = this.store.updateResult(record.id, {
441
+ status: "error",
442
+ error: "Unexpected internal subagent failure.",
443
+ errorCode: "AGENT_INTERNAL_ERROR",
444
+ errorRetryable: false,
445
+ });
446
+ this.log("error", "agent_run_failed", {
447
+ provider: record.provider,
448
+ agentId: record.id,
449
+ providerSessionIdPrefix: record.providerSessionId?.slice(0, 8),
450
+ durationMs: Math.max(0, Date.now() - startedAt),
451
+ error: "Unexpected internal subagent failure.",
452
+ errorType: error instanceof Error ? error.name : typeof error,
453
+ persistenceFailed: persisted.isErr(),
454
+ });
455
+ throw error;
456
+ }
457
+ finally {
458
+ clearTimeout(watchdog);
459
+ this.timedOutAgents.delete(record.id);
460
+ this.turnTimeoutMs.delete(record.id);
461
+ this.activeTurns.delete(record.id);
462
+ this.activeRuntimeKeys.delete(record.id);
463
+ this.outputBuffers.delete(record.id);
464
+ this.interruptedAgents.delete(record.id);
465
+ }
466
+ }
467
+ /**
468
+ * Watchdog: when the budget expires while the turn is still tracked,
469
+ * interrupt the runtime; completion paths then persist a timeout error.
470
+ * The timer is unref'd so an idle manager never holds the process open.
471
+ */
472
+ armWatchdog(agentId, timeoutMs) {
473
+ const timer = setTimeout(() => {
474
+ if (!this.activeTurns.has(agentId))
475
+ return;
476
+ this.timedOutAgents.add(agentId);
477
+ void this.interruptTurn(agentId, "stopped").catch(() => undefined);
478
+ }, timeoutMs);
479
+ timer.unref?.();
480
+ return timer;
481
+ }
482
+ persistTimeoutError(record, startedAt, timeoutMs) {
483
+ const minutes = Math.round(timeoutMs / 60000);
484
+ const persisted = this.store.updateResult(record.id, {
485
+ status: "error",
486
+ error: `Agent turn exceeded its timeout budget of ${minutes} minute(s). Resume with a smaller task or a larger timeout.`,
487
+ errorCode: "AGENT_TIMEOUT",
488
+ errorRetryable: true,
489
+ });
490
+ this.log("warn", "agent_turn_timeout", {
491
+ provider: record.provider,
492
+ agentId: record.id,
493
+ timeoutMs,
494
+ durationMs: Math.max(0, Date.now() - startedAt),
495
+ persistenceFailed: persisted.isErr(),
496
+ });
497
+ }
498
+ persistRunError(record, error, startedAt) {
499
+ if (this.timedOutAgents.has(record.id)) {
500
+ this.persistTimeoutError(record, startedAt, this.turnTimeoutMs.get(record.id) ?? DEFAULT_AGENT_TURN_TIMEOUT_MS);
501
+ return;
502
+ }
503
+ const interrupted = this.interruptedAgents.get(record.id);
504
+ if (interrupted) {
505
+ const persisted = this.store.updateResult(record.id, {
506
+ status: interrupted,
507
+ error: undefined,
508
+ errorCode: undefined,
509
+ errorRetryable: undefined,
510
+ });
511
+ this.log("info", "agent_turn_interrupted", {
512
+ provider: record.provider,
513
+ agentId: record.id,
514
+ status: interrupted,
515
+ durationMs: Math.max(0, Date.now() - startedAt),
516
+ persistenceFailed: persisted.isErr(),
517
+ });
518
+ return;
519
+ }
520
+ const persisted = this.store.updateResult(record.id, {
521
+ status: "error",
522
+ error: error.message,
523
+ errorCode: error.code,
524
+ errorRetryable: error.retryable,
525
+ });
526
+ this.log("error", "agent_run_failed", {
527
+ provider: record.provider,
528
+ agentId: record.id,
529
+ providerSessionIdPrefix: record.providerSessionId?.slice(0, 8),
530
+ durationMs: Math.max(0, Date.now() - startedAt),
531
+ errorCode: error.code,
532
+ error: error.message,
533
+ causeType: safeCauseType("cause" in error ? error.cause : undefined),
534
+ persistenceFailed: persisted.isErr(),
535
+ });
536
+ }
537
+ buildRunInputResult(record, profile, prompt, overrides) {
538
+ const isRawProvider = record.profileName === record.provider;
539
+ if (!profile && !isRawProvider) {
540
+ return Result.err(new AgentTargetError({
541
+ code: "UNKNOWN_TARGET",
542
+ target: record.profileName,
543
+ provider: isLocalAgentProvider(record.provider) ? record.provider : undefined,
544
+ retryable: false,
545
+ message: `Subagent profile not found: ${record.profileName}.`,
546
+ }));
547
+ }
548
+ const body = profile?.body.trim();
549
+ const fullPrompt = body ? `${body}\n\nTask:\n${prompt}` : prompt;
550
+ return Result.ok({
551
+ prompt: fullPrompt,
552
+ workspaceRoot: record.workspaceRoot,
553
+ providerSessionId: record.providerSessionId,
554
+ writeMode: overrides.writeMode ?? "allowed",
555
+ model: record.model ?? profile?.model,
556
+ effort: record.effort ?? profile?.effort,
557
+ modelOverrideRequested: overrides.model !== undefined,
558
+ effortOverrideRequested: overrides.effort !== undefined,
559
+ });
560
+ }
561
+ profileForRecordResult(record, profiles) {
562
+ if (record.profileName === record.provider)
563
+ return Result.ok(undefined);
564
+ const profile = profiles.find((candidate) => candidate.name === record.profileName);
565
+ if (!profile) {
566
+ return Result.err(new AgentTargetError({
567
+ code: "UNKNOWN_TARGET",
568
+ target: record.profileName,
569
+ provider: isLocalAgentProvider(record.provider) ? record.provider : undefined,
570
+ retryable: false,
571
+ message: `Subagent profile not found: ${record.profileName}.`,
572
+ }));
573
+ }
574
+ if (profile.disabled) {
575
+ return Result.err(new AgentTargetError({
576
+ code: "PROVIDER_DISABLED",
577
+ target: profile.name,
578
+ provider: profile.provider,
579
+ retryable: false,
580
+ message: `Subagent profile is disabled: ${profile.name}.`,
581
+ }));
582
+ }
583
+ return Result.ok(profile);
584
+ }
585
+ driverResult(provider, operation, agentId) {
586
+ if (!isLocalAgentProvider(provider)) {
587
+ return Result.err(new AgentTargetError({
588
+ code: "PROVIDER_NOT_CONFIGURED",
589
+ target: provider,
590
+ operation,
591
+ retryable: false,
592
+ message: `No local agent driver is configured for provider: ${provider}.`,
593
+ }));
594
+ }
595
+ const driver = this.drivers.get(provider);
596
+ if (!driver) {
597
+ return Result.err(new AgentTargetError({
598
+ code: "PROVIDER_NOT_CONFIGURED",
599
+ target: agentId ?? provider,
600
+ provider,
601
+ operation,
602
+ retryable: false,
603
+ message: `No local agent driver is configured for provider: ${provider}.`,
604
+ }));
605
+ }
606
+ return Result.ok(driver);
607
+ }
608
+ providerEnabledResult(provider, target, operation) {
609
+ if (!isLocalAgentProvider(provider))
610
+ return Result.ok(undefined);
611
+ if (isSubagentProviderEnabled(this.subagents, provider))
612
+ return Result.ok(undefined);
613
+ return Result.err(new AgentTargetError({
614
+ code: "PROVIDER_DISABLED",
615
+ target,
616
+ provider,
617
+ operation,
618
+ retryable: false,
619
+ message: `Subagent provider is disabled: ${provider}.`,
620
+ }));
621
+ }
622
+ acceptingResult(operation, agentId) {
623
+ if (this.accepting)
624
+ return Result.ok(undefined);
625
+ return Result.err(new AgentConflictError({
626
+ code: "AGENT_CONFLICT",
627
+ agentId,
628
+ operation,
629
+ retryable: false,
630
+ message: "Local agent manager is closed.",
631
+ }));
632
+ }
633
+ authorizeWorkspace(workspaceRoot, workspaceId, operation) {
634
+ const normalized = resolve(workspaceRoot);
635
+ // Security: allowed roots apply to every caller. The previous behavior
636
+ // skipped the check when workspaceId was absent, which let MCP-shell
637
+ // invocations (`hearth agents run` via a shell tool) run agents
638
+ // anywhere on disk (audit finding SEC-04).
639
+ void workspaceId;
640
+ if (!this.allowedRoots)
641
+ return Result.ok(normalized);
642
+ try {
643
+ return Result.ok(assertAllowedPath(normalized, [...this.allowedRoots]));
644
+ }
645
+ catch (cause) {
646
+ return Result.err(new AgentScopeError({
647
+ code: "WORKSPACE_NOT_ALLOWED",
648
+ operation,
649
+ retryable: false,
650
+ cause,
651
+ message: "Workspace root is outside configured allowed roots.",
652
+ }));
653
+ }
654
+ }
655
+ agentWorkspaceResult(record, scope, operation) {
656
+ const workspaceRoot = this.authorizeWorkspace(scope.workspaceRoot, scope.workspaceId, operation);
657
+ if (workspaceRoot.isErr())
658
+ return workspaceRoot;
659
+ const idMismatch = scope.workspaceId !== undefined && record.workspaceId !== scope.workspaceId;
660
+ if (workspaceRoot.value !== record.workspaceRoot || idMismatch) {
661
+ return Result.err(new AgentScopeError({
662
+ code: "WORKSPACE_MISMATCH",
663
+ agentId: record.id,
664
+ workspaceId: scope.workspaceId,
665
+ operation,
666
+ retryable: false,
667
+ message: `Subagent ${record.id} belongs to a different workspace.`,
668
+ }));
669
+ }
670
+ return Result.ok(undefined);
671
+ }
672
+ async loadProfilesResult(workspaceRoot, target) {
673
+ try {
674
+ return Result.ok(await this.loadProfiles(workspaceRoot));
675
+ }
676
+ catch (cause) {
677
+ if (isProgrammerDefect(cause))
678
+ throw cause;
679
+ return Result.err(new AgentTargetError({
680
+ code: "TARGET_RESOLUTION_FAILED",
681
+ target,
682
+ retryable: false,
683
+ cause,
684
+ message: "Unable to load subagent profiles.",
685
+ }));
686
+ }
687
+ }
688
+ log(level, event, fields) {
689
+ this.logger?.(level, event, fields);
690
+ }
691
+ }
692
+ export function createLocalAgentManager(options) {
693
+ return new LocalAgentManager(options);
694
+ }
695
+ function errorMessage(error) {
696
+ return error instanceof Error ? error.message : String(error);
697
+ }
698
+ function safeCauseType(cause) {
699
+ if (cause instanceof Error)
700
+ return cause.name;
701
+ if (cause && typeof cause === "object" && "error" in cause) {
702
+ const nested = cause.error;
703
+ if (nested instanceof Error)
704
+ return nested.name;
705
+ }
706
+ return cause === undefined ? undefined : typeof cause;
707
+ }
708
+ function agentNotFound(agentId) {
709
+ return new AgentTargetError({
710
+ code: "AGENT_NOT_FOUND",
711
+ target: agentId,
712
+ retryable: false,
713
+ message: `Unknown subagent id: ${agentId}.`,
714
+ });
715
+ }