@sma1lboy/rove 0.9.110 → 0.9.148

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 (194) hide show
  1. package/README.md +0 -1
  2. package/dist/cli/chunk-003tvywc.js +3 -0
  3. package/dist/cli/chunk-0b48bpkd.js +2 -0
  4. package/dist/cli/chunk-0dv2xssg.js +19 -0
  5. package/dist/cli/chunk-0sm2k04y.js +3 -0
  6. package/dist/cli/chunk-0vmrteyg.js +3 -0
  7. package/dist/cli/chunk-13s8rnk5.js +3 -0
  8. package/dist/cli/chunk-1kktfmtg.js +4 -0
  9. package/dist/cli/chunk-1rkb0j1j.js +3 -0
  10. package/dist/cli/chunk-1tx1s5v6.js +2 -0
  11. package/dist/cli/chunk-1x0vahnn.js +2 -0
  12. package/dist/cli/chunk-20pt9yqh.js +4 -0
  13. package/dist/cli/chunk-2brn8e1y.js +3 -0
  14. package/dist/cli/chunk-2fypr41w.js +3 -0
  15. package/dist/cli/chunk-2jxdzw4r.js +3 -0
  16. package/dist/cli/chunk-2pqqvse3.js +2 -0
  17. package/dist/cli/chunk-39s1ndtm.js +106 -0
  18. package/dist/cli/chunk-3bfm4hrs.js +3 -0
  19. package/dist/cli/chunk-3rqa4pyt.js +28 -0
  20. package/dist/cli/chunk-3ry6prwn.js +3 -0
  21. package/dist/cli/chunk-3ztek08w.js +4 -0
  22. package/dist/cli/chunk-49s2zc0h.js +8 -0
  23. package/dist/cli/chunk-4wwx51m5.js +3 -0
  24. package/dist/cli/chunk-5ar3vspk.js +9 -0
  25. package/dist/cli/chunk-5bn91n19.js +3 -0
  26. package/dist/cli/chunk-5c3d1y0r.js +2 -0
  27. package/dist/cli/chunk-5hhvrvgm.js +8 -0
  28. package/dist/cli/chunk-6dn6h640.js +3 -0
  29. package/dist/cli/chunk-6frfschk.js +2 -0
  30. package/dist/cli/chunk-6xppe9bj.js +3 -0
  31. package/dist/cli/chunk-6zwg6me2.js +4 -0
  32. package/dist/cli/chunk-764bsyb0.js +1 -0
  33. package/dist/cli/chunk-79gqh5p2.js +5 -0
  34. package/dist/cli/chunk-7ngv7v9g.js +9 -0
  35. package/dist/cli/chunk-7nhdvr5v.js +4 -0
  36. package/dist/cli/chunk-7pfnda5z.js +12 -0
  37. package/dist/cli/chunk-7q67dz4c.js +3 -0
  38. package/dist/cli/chunk-8ddjfc2v.js +4 -0
  39. package/dist/cli/chunk-8g456mmt.js +3 -0
  40. package/dist/cli/chunk-8sbevss4.js +4 -0
  41. package/dist/cli/chunk-91j0an0j.js +2 -0
  42. package/dist/cli/chunk-99devyxr.js +2 -0
  43. package/dist/cli/chunk-9c1e5z6a.js +3 -0
  44. package/dist/cli/chunk-a4a4qaa2.js +3 -0
  45. package/dist/cli/chunk-adez6hq4.js +69 -0
  46. package/dist/cli/chunk-aq912vvw.js +3 -0
  47. package/dist/cli/chunk-arf294te.js +2 -0
  48. package/dist/cli/chunk-awsawfrm.js +2 -0
  49. package/dist/cli/chunk-aynx3d2a.js +2 -0
  50. package/dist/cli/chunk-b8d162tf.js +3 -0
  51. package/dist/cli/chunk-b99tf0jr.js +3 -0
  52. package/dist/cli/chunk-bj59a9mg.js +3 -0
  53. package/dist/cli/chunk-c6mthex1.js +5 -0
  54. package/dist/cli/chunk-cb2fpajx.js +3 -0
  55. package/dist/cli/chunk-cbkje3aw.js +2 -0
  56. package/dist/cli/chunk-df7qe9y7.js +4 -0
  57. package/dist/cli/chunk-dkxhmj9k.js +25 -0
  58. package/dist/cli/chunk-dqndd97s.js +7 -0
  59. package/dist/cli/chunk-dvabncrk.js +7 -0
  60. package/dist/cli/chunk-dvcb6mk4.js +3 -0
  61. package/dist/cli/chunk-dw853n4w.js +3 -0
  62. package/dist/cli/chunk-dx6b3dv8.js +7 -0
  63. package/dist/cli/chunk-e1g9mvww.js +11 -0
  64. package/dist/cli/chunk-ef11h2am.js +3 -0
  65. package/dist/cli/chunk-ef39ym8f.js +3 -0
  66. package/dist/cli/chunk-eq5f3z2f.js +3 -0
  67. package/dist/cli/chunk-ewdhz3dv.js +2 -0
  68. package/dist/cli/chunk-f6zsej8e.js +3 -0
  69. package/dist/cli/chunk-g1ybdgad.js +25 -0
  70. package/dist/cli/chunk-gbasmvsc.js +4 -0
  71. package/dist/cli/chunk-gjf604bb.js +3 -0
  72. package/dist/cli/chunk-gm3vj0fv.js +5 -0
  73. package/dist/cli/chunk-grh6dvfv.js +5 -0
  74. package/dist/cli/chunk-gwcneg6c.js +2 -0
  75. package/dist/cli/chunk-h5j8b87f.js +2 -0
  76. package/dist/cli/chunk-h79zfebh.js +7 -0
  77. package/dist/cli/chunk-h82kzdsn.js +3 -0
  78. package/dist/cli/chunk-hape5vwj.js +3 -0
  79. package/dist/cli/chunk-hg5x2wr0.js +3 -0
  80. package/dist/cli/chunk-j4xfsn9g.js +5 -0
  81. package/dist/cli/chunk-jfqbq12b.js +18 -0
  82. package/dist/cli/chunk-jsss3ze7.js +11 -0
  83. package/dist/cli/chunk-jvpzy378.js +3 -0
  84. package/dist/cli/chunk-jvxh93nz.js +3 -0
  85. package/dist/cli/chunk-jyv82gf0.js +258 -0
  86. package/dist/cli/chunk-k3ksts6g.js +3 -0
  87. package/dist/cli/chunk-kdrrw9tm.js +2 -0
  88. package/dist/cli/chunk-kg11c1py.js +3 -0
  89. package/dist/cli/chunk-kwe61yx7.js +3 -0
  90. package/dist/cli/chunk-m0fz6g32.js +16 -0
  91. package/dist/cli/chunk-mb5m7ex7.js +2 -0
  92. package/dist/cli/chunk-me77sx3d.js +3 -0
  93. package/dist/cli/chunk-mfqjdpd8.js +2 -0
  94. package/dist/cli/chunk-mggca3k4.js +4 -0
  95. package/dist/cli/chunk-mjvq39gf.js +4 -0
  96. package/dist/cli/chunk-mrwee8sj.js +3 -0
  97. package/dist/cli/chunk-my20q579.js +3 -0
  98. package/dist/cli/chunk-nceh1q3d.js +12 -0
  99. package/dist/cli/chunk-ncsgawg7.js +2 -0
  100. package/dist/cli/chunk-nm6adny4.js +4 -0
  101. package/dist/cli/chunk-np07kccr.js +4 -0
  102. package/dist/cli/chunk-nrnjt5b6.js +2 -0
  103. package/dist/cli/chunk-p7kztgsd.js +4 -0
  104. package/dist/cli/chunk-pbqwegjy.js +2 -0
  105. package/dist/cli/chunk-pcvk98rr.js +3 -0
  106. package/dist/cli/chunk-pdh5w4ht.js +3 -0
  107. package/dist/cli/chunk-pdj8kqa1.js +74 -0
  108. package/dist/cli/chunk-pw6yfj10.js +3 -0
  109. package/dist/cli/chunk-q6579rbz.js +3 -0
  110. package/dist/cli/chunk-q7dr9eh6.js +3 -0
  111. package/dist/cli/chunk-qkehg2c1.js +3 -0
  112. package/dist/cli/chunk-qtke4x42.js +3 -0
  113. package/dist/cli/chunk-rxvbm4vt.js +7 -0
  114. package/dist/cli/chunk-s66eh0pw.js +8 -0
  115. package/dist/cli/chunk-s76r56rj.js +3 -0
  116. package/dist/cli/chunk-sdyzxayr.js +3 -0
  117. package/dist/cli/chunk-svpn3av1.js +3 -0
  118. package/dist/cli/chunk-t0kta7rb.js +5 -0
  119. package/dist/cli/chunk-v5893dx3.js +2 -0
  120. package/dist/cli/chunk-vapjtyv9.js +7 -0
  121. package/dist/cli/chunk-vpzvcxc1.js +3 -0
  122. package/dist/cli/chunk-vqpa27cf.js +15 -0
  123. package/dist/cli/chunk-vws0j65e.js +4 -0
  124. package/dist/cli/chunk-vyb5a2rh.js +4 -0
  125. package/dist/cli/chunk-vzxbe80f.js +7 -0
  126. package/dist/cli/chunk-w5xy83ze.js +2 -0
  127. package/dist/cli/chunk-wqfp8d9w.js +3 -0
  128. package/dist/cli/chunk-wsvf9eka.js +33 -0
  129. package/dist/cli/chunk-x760j10f.js +3 -0
  130. package/dist/cli/chunk-x930m5fs.js +2 -0
  131. package/dist/cli/chunk-xbc3gcry.js +2 -0
  132. package/dist/cli/chunk-xy8be792.js +3 -0
  133. package/dist/cli/chunk-xzrqnpmh.js +4 -0
  134. package/dist/cli/chunk-y2yq6zca.js +3 -0
  135. package/dist/cli/chunk-yhcz2d2r.js +34 -0
  136. package/dist/cli/chunk-yk2ha5yj.js +3 -0
  137. package/dist/cli/chunk-ykvk2546.js +3 -0
  138. package/dist/cli/chunk-ympakh8w.js +6 -0
  139. package/dist/cli/chunk-yn5a7xcv.js +2 -0
  140. package/dist/cli/chunk-zb56ck5f.js +3 -0
  141. package/dist/cli/index.js +15 -850
  142. package/dist/cli/kobe-run.js +1 -858
  143. package/dist/cli/kobe.js +6 -6
  144. package/dist/cli/pty-host-node.mjs +167 -50
  145. package/dist/cli/rove-run.js +1 -858
  146. package/dist/cli/rove.js +6 -6
  147. package/dist/skills/rove/SKILL.md +29 -10
  148. package/dist/skills/rove/references/api-flags.md +35 -13
  149. package/dist/web-ui/assets/{ChatTerminal-BshL3M8G.js → harness-CYGJ4uoR.js} +19 -20
  150. package/dist/web-ui/assets/index-BTJ-X_4s.js +10 -0
  151. package/dist/web-ui/assets/index-Cs2vPLNB.css +1 -0
  152. package/dist/web-ui/index.html +2 -9
  153. package/dist/web-ui/pty-server.mjs +4 -5
  154. package/package.json +1 -1
  155. package/dist/web-ui/assets/AppShell-BGRIs5hW.js +0 -5
  156. package/dist/web-ui/assets/AppShell-HiyATJse.css +0 -1
  157. package/dist/web-ui/assets/Board-Bt3cwp25.js +0 -1
  158. package/dist/web-ui/assets/BoardPeek-BKR-tpS2.js +0 -2
  159. package/dist/web-ui/assets/ChatTranscript-B67_nnd6.js +0 -1
  160. package/dist/web-ui/assets/ConfirmDialog-kyA624OF.js +0 -1
  161. package/dist/web-ui/assets/IssueIntakePanel-BMP8Vc3E.js +0 -1
  162. package/dist/web-ui/assets/IssuePeek-C7afOYax.js +0 -1
  163. package/dist/web-ui/assets/IssuesPage-DwRaBB-m.js +0 -1
  164. package/dist/web-ui/assets/SettingsPage-CC1sDI8j.js +0 -2
  165. package/dist/web-ui/assets/SlideOver-B4sEOPyN.css +0 -1
  166. package/dist/web-ui/assets/SlideOver-Dq0ifiEm.js +0 -173
  167. package/dist/web-ui/assets/ViewToggle-mNsDLJp8.js +0 -1
  168. package/dist/web-ui/assets/WorkspaceTabs-DLzL-Tad.css +0 -1
  169. package/dist/web-ui/assets/WorkspaceTabs-ifAIZ5Fj.js +0 -3
  170. package/dist/web-ui/assets/active-task-gVYiAkLv.js +0 -1
  171. package/dist/web-ui/assets/arrow-left-CavMh90a.js +0 -1
  172. package/dist/web-ui/assets/board-DoArj55z.js +0 -1
  173. package/dist/web-ui/assets/chips-CS9PLtWy.js +0 -1
  174. package/dist/web-ui/assets/createLucideIcon-CuIQtyYV.js +0 -1
  175. package/dist/web-ui/assets/external-link-Du2dMe8S.js +0 -1
  176. package/dist/web-ui/assets/external-store-BCBJewGi.js +0 -1
  177. package/dist/web-ui/assets/harness-ClZMF1wx.js +0 -1
  178. package/dist/web-ui/assets/index-B4xJZEqf.js +0 -10
  179. package/dist/web-ui/assets/index-BnjD9Q-3.css +0 -1
  180. package/dist/web-ui/assets/issues-DwrKXp9c.js +0 -4
  181. package/dist/web-ui/assets/issues-ISAZBras.js +0 -1
  182. package/dist/web-ui/assets/lazy-issue-panels-DvXQJhKy.js +0 -6
  183. package/dist/web-ui/assets/rotate-cw-BXdpzm3v.js +0 -1
  184. package/dist/web-ui/assets/routes-DEEkL5gG.js +0 -1
  185. package/dist/web-ui/assets/store-BLM6nh6E.js +0 -2
  186. package/dist/web-ui/assets/tabs-CMZJhcSB.js +0 -1
  187. package/dist/web-ui/assets/task._taskId-BCIhg4IQ.js +0 -1
  188. package/dist/web-ui/assets/terminal-DOp1a9g6.js +0 -1
  189. package/dist/web-ui/assets/text-match-DW3sD-Rt.js +0 -1
  190. package/dist/web-ui/assets/time-kviT26Tm.js +0 -1
  191. package/dist/web-ui/assets/toast-C4yXq6hM.js +0 -1
  192. package/dist/web-ui/assets/web-transport-Beqz2mQ8.js +0 -1
  193. package/dist/web-ui/assets/worktrees-BGvJOG2b.js +0 -1
  194. /package/dist/web-ui/assets/{ChatTerminal-BrP-ENHg.css → harness-BrP-ENHg.css} +0 -0
package/dist/cli/kobe.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{join as $1}from"node:path";import{createInterface as J1}from"node:readline/promises";import{pathToFileURL as Q1}from"node:url";import{spawnSync as L}from"node:child_process";import{constants as u,accessSync as l}from"node:fs";import{homedir as t}from"node:os";import{basename as o,delimiter as r,dirname as i,join as Y}from"node:path";import{fileURLToPath as C}from"node:url";var U={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.110",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{kobe:"dist/cli/kobe.js",rove:"dist/cli/rove.js"},files:["dist/cli","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:kobe":"KOBE_DEV=1 bun --conditions=browser ./src/cli/kobe.ts","dev:mock":"KOBE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"KOBE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"KOBE_INCLUDE_SOCKET=1 KOBE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"KOBE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun test test/render --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage-render",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8",ws:"^8.18.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/kobe-daemon":"0.7.18","@tsconfig/bun":"1.0.10","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var x="rove",j="kobe";var E="ROVE_INVOKED_AS";function F(W=process.env){return W[E]===x?x:j}var k="ROVE_BUN",a="ROVE_NO_BUN_BOOTSTRAP",M="ROVE_SKIP_BUN_CHECK",G=U.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",T=(W)=>{try{return l(W,u.X_OK),!0}catch{return!1}},n=(W)=>W==="win32"?"bun.exe":"bun",e=(W)=>{let b=L(W,["--version"],{encoding:"utf8",timeout:5000});if(b.error||b.status!==0)return null;return b.stdout?.split(`
3
- `)[0]?.trim()??""};function K(W){return W.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function P(W,b=G){let z=K(W),Q=K(b);if(!z||!Q)return!0;let J=z.split(".").map(Number),$=Q.split(".").map(Number);for(let X=0;X<3;X++){let q=J[X]??0,H=$[X]??0;if(q!==H)return q>H}return!0}function g(W=process.env){return W[M]==="1"}function p(W={}){let b=W.env??process.env,z=W.platform??process.platform,Q=W.home??b.HOME??b.USERPROFILE??t(),J=n(z),$=[],X=b[k]?.trim();if(X)$.push(X);for(let D of(b.PATH??b.Path??"").split(r))if(D)$.push(Y(D,J));let q=b.BUN_INSTALL?.trim();if(q)$.push(Y(q,"bin",J));if(Q)$.push(Y(Q,".bun","bin",J));let H=W.launcherDir;if(H)$.push(Y(H,"..","..","node_modules","bun","bin",J)),$.push(Y(H,"..","..","..","bun","bin",J));return $}function W1(W={}){let b=W.isExecutable??T;return p(W).find((z)=>b(z))??null}function w(W={}){let b=W.isExecutable??T,z=W.bunVersionOf??e;if(g(W.env??process.env))return{bun:W1(W),stale:null,unusable:null};let Q=null,J=null;for(let $ of p(W)){if(!b($))continue;let X=z($);if(X===null){J??=$;continue}if(P(X))return{bun:$,stale:Q,unusable:J};Q??={path:$,version:K(X)??X}}return{bun:null,stale:Q,unusable:J}}function B(W){return i(C(W))}function O(W){return o(C(W)).replace(/\.[cm]?[jt]s$/,"")}function b1(W=process.platform){return W==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function y(W=F(),b=process.platform){let z=b==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${W}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${z}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${k}=/path/to/bun`,""].join(`
4
- `)}function h(W,b,z=F()){return[`${z}: this machine's Bun is too old for Rove — found ${b} at ${W}, need ${G} or newer.`,"","Rove's terminals are built on Bun's PTY API. An older Bun ignores it silently,","so every terminal and engine tab would open empty and stay empty.","","Upgrade Bun, then run this command again:"," bun upgrade # Bun installed itself (~/.bun)"," brew upgrade bun # Homebrew"," npm install -g bun@latest # npm-managed Bun","",`Have a newer Bun elsewhere? Point Rove at it: ${k}=/path/to/bun`,`Run on this one anyway (unsupported, terminals will be blank): ${M}=1`,""].join(`
5
- `)}function A(W,b=F()){return[`${b}: the Bun at ${W} could not be run — \`${W} --version\` failed or never returned.`,"","Rove skipped it rather than starting through it, which would hang or fail","with a message from that binary instead of this one.","","Reinstall Bun, then run this command again:"," curl -fsSL https://bun.sh/install | bash","",`Have a working Bun elsewhere? Point Rove at it: ${k}=/path/to/bun`,""].join(`
6
- `)}function f(W=process.env,b=process.stdin,z=process.stdout){if(W[a]==="1")return!1;if(W.CI==="true"||W.CI==="1")return!1;return Boolean(b.isTTY&&z.isTTY)}function I(W={},b=L){let z=W.platform??process.platform,[Q,...J]=b1(z);if(!Q)return null;let $=b(Q,J,{stdio:"inherit"});if($.error||$.status!==0)return null;return w(W).bun}function z1(W){if(typeof W.status==="number")return W.status;if(W.signal)return 128+(Object.hasOwn(V,W.signal)?V[W.signal]:0);return 1}var V={SIGINT:2,SIGQUIT:3,SIGKILL:9,SIGTERM:15};function R(W,b,z,Q=L){let J=Q(W,[b,...z],{stdio:"inherit"});if(J.error)return process.stderr.write(`rove: could not start Bun at ${W}: ${J.error.message}
7
- `),1;return z1(J)}var N=B(import.meta.url),Z=O(import.meta.url),c=$1(N,`${Z}-run.js`);async function X1(W){let b=J1({input:process.stdin,output:process.stdout});try{let z=(await b.question(W)).trim().toLowerCase();return z===""||z==="y"||z==="yes"}finally{b.close()}}async function Z1(){let W={launcherDir:N},b=w(W);if(b.bun||!f())return b;let z=b.stale?`${Z}: Bun ${b.stale.version} is too old for Rove (needs ${G}+). Install the latest Bun now? [Y/n] `:`${Z}: Rove runs on the Bun runtime, and none is installed. Install Bun now? [Y/n] `;if(!await X1(z))return b;return{bun:I(W),stale:b.stale,unusable:b.unusable}}var v=globalThis.Bun;if(v){let W=v.version;if(W&&!g()&&!P(W))process.stderr.write(h(process.execPath,W,Z)),process.exit(1);await import(Q1(c).href)}else{let{bun:W,stale:b,unusable:z}=await Z1();if(!W){let Q=b?h(b.path,b.version,Z):z?A(z,Z):y(Z);process.stderr.write(Q),process.exit(1)}process.exit(R(W,c,process.argv.slice(2)))}
2
+ import{join as z1}from"node:path";import{createInterface as $1}from"node:readline/promises";import{pathToFileURL as J1}from"node:url";import{spawnSync as L}from"node:child_process";import{constants as m,accessSync as u}from"node:fs";import{homedir as l}from"node:os";import{basename as t,delimiter as o,dirname as r,join as Y}from"node:path";import{fileURLToPath as T}from"node:url";var h={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.148",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{kobe:"dist/cli/kobe.js",rove:"dist/cli/rove.js"},files:["dist/cli","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:kobe":"KOBE_DEV=1 bun --conditions=browser ./src/cli/kobe.ts","dev:mock":"KOBE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"KOBE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"KOBE_INCLUDE_SOCKET=1 KOBE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"KOBE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun test test/render --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage-render",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8",ws:"^8.18.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/kobe-daemon":"0.7.18","@tsconfig/bun":"1.0.10","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var U="rove",C="kobe";var V=C;var k=U;var E="ROVE_INVOKED_AS";function x(W=process.env){return W[E]===k?k:V}var F="ROVE_BUN",i="ROVE_NO_BUN_BOOTSTRAP",O="ROVE_SKIP_BUN_CHECK",G=h.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",B=(W)=>{try{return u(W,m.X_OK),!0}catch{return!1}},a=(W)=>W==="win32"?"bun.exe":"bun",n=(W)=>{let b=L(W,["--version"],{encoding:"utf8",timeout:5000});if(b.error||b.status!==0)return null;return b.stdout?.split(`
3
+ `)[0]?.trim()??""};function K(W){return W.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function j(W,b=G){let z=K(W),Q=K(b);if(!z||!Q)return!0;let J=z.split(".").map(Number),$=Q.split(".").map(Number);for(let X=0;X<3;X++){let q=J[X]??0,H=$[X]??0;if(q!==H)return q>H}return!0}function P(W=process.env){return W[O]==="1"}function p(W={}){let b=W.env??process.env,z=W.platform??process.platform,Q=W.home??b.HOME??b.USERPROFILE??l(),J=a(z),$=[],X=b[F]?.trim();if(X)$.push(X);for(let w of(b.PATH??b.Path??"").split(o))if(w)$.push(Y(w,J));let q=b.BUN_INSTALL?.trim();if(q)$.push(Y(q,"bin",J));if(Q)$.push(Y(Q,".bun","bin",J));let H=W.launcherDir;if(H)$.push(Y(H,"..","..","node_modules","bun","bin",J)),$.push(Y(H,"..","..","..","bun","bin",J));return $}function e(W={}){let b=W.isExecutable??B;return p(W).find((z)=>b(z))??null}function D(W={}){let b=W.isExecutable??B,z=W.bunVersionOf??n;if(P(W.env??process.env))return{bun:e(W),stale:null,unusable:null};let Q=null,J=null;for(let $ of p(W)){if(!b($))continue;let X=z($);if(X===null){J??=$;continue}if(j(X))return{bun:$,stale:Q,unusable:J};Q??={path:$,version:K(X)??X}}return{bun:null,stale:Q,unusable:J}}function A(W){return r(T(W))}function R(W){return t(T(W)).replace(/\.[cm]?[jt]s$/,"")}function W1(W=process.platform){return W==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function y(W=x(),b=process.platform){let z=b==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${W}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${z}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${F}=/path/to/bun`,""].join(`
4
+ `)}function g(W,b,z=x()){return[`${z}: this machine's Bun is too old for Rove — found ${b} at ${W}, need ${G} or newer.`,"","Rove's terminals are built on Bun's PTY API. An older Bun ignores it silently,","so every terminal and engine tab would open empty and stay empty.","","Upgrade Bun, then run this command again:"," bun upgrade # Bun installed itself (~/.bun)"," brew upgrade bun # Homebrew"," npm install -g bun@latest # npm-managed Bun","",`Have a newer Bun elsewhere? Point Rove at it: ${F}=/path/to/bun`,`Run on this one anyway (unsupported, terminals will be blank): ${O}=1`,""].join(`
5
+ `)}function I(W,b=x()){return[`${b}: the Bun at ${W} could not be run — \`${W} --version\` failed or never returned.`,"","Rove skipped it rather than starting through it, which would hang or fail","with a message from that binary instead of this one.","","Reinstall Bun, then run this command again:"," curl -fsSL https://bun.sh/install | bash","",`Have a working Bun elsewhere? Point Rove at it: ${F}=/path/to/bun`,""].join(`
6
+ `)}function f(W=process.env,b=process.stdin,z=process.stdout){if(W[i]==="1")return!1;if(W.CI==="true"||W.CI==="1")return!1;return Boolean(b.isTTY&&z.isTTY)}function c(W={},b=L){let z=W.platform??process.platform,[Q,...J]=W1(z);if(!Q)return null;let $=b(Q,J,{stdio:"inherit"});if($.error||$.status!==0)return null;return D(W).bun}function b1(W){if(typeof W.status==="number")return W.status;if(W.signal)return 128+(Object.hasOwn(M,W.signal)?M[W.signal]:0);return 1}var M={SIGINT:2,SIGQUIT:3,SIGKILL:9,SIGTERM:15};function _(W,b,z,Q=L){let J=Q(W,[b,...z],{stdio:"inherit"});if(J.error)return process.stderr.write(`rove: could not start Bun at ${W}: ${J.error.message}
7
+ `),1;return b1(J)}var S=A(import.meta.url),Z=R(import.meta.url),N=z1(S,`${Z}-run.js`);async function Q1(W){let b=$1({input:process.stdin,output:process.stdout});try{let z=(await b.question(W)).trim().toLowerCase();return z===""||z==="y"||z==="yes"}finally{b.close()}}async function X1(){let W={launcherDir:S},b=D(W);if(b.bun||!f())return b;let z=b.stale?`${Z}: Bun ${b.stale.version} is too old for Rove (needs ${G}+). Install the latest Bun now? [Y/n] `:`${Z}: Rove runs on the Bun runtime, and none is installed. Install Bun now? [Y/n] `;if(!await Q1(z))return b;return{bun:c(W),stale:b.stale,unusable:b.unusable}}var v=globalThis.Bun;if(v){let W=v.version;if(W&&!P()&&!j(W))process.stderr.write(g(process.execPath,W,Z)),process.exit(1);await import(J1(N).href)}else{let{bun:W,stale:b,unusable:z}=await X1();if(!W){let Q=b?g(b.path,b.version,Z):z?I(z,Z):y(Z);process.stderr.write(Q),process.exit(1)}process.exit(_(W,N,process.argv.slice(2)))}
@@ -32,6 +32,14 @@ var COMPAT_STATE_DIR_BASENAME = ".kobe";
32
32
  function readRoveEnv(suffix, env = process.env) {
33
33
  return env[`${ROVE_ENV_PREFIX}${suffix}`] ?? env[`${LEGACY_KOBE_ENV_PREFIX}${suffix}`];
34
34
  }
35
+ function readRoveHomeDirEnv(env = process.env) {
36
+ for (const key of [`${ROVE_ENV_PREFIX}HOME_DIR`, `${LEGACY_KOBE_ENV_PREFIX}HOME_DIR`]) {
37
+ const value = env[key]?.trim();
38
+ if (value)
39
+ return value;
40
+ }
41
+ return;
42
+ }
35
43
 
36
44
  // ../kobe-daemon/src/daemon/paths.ts
37
45
  function stateDirs(homeDir) {
@@ -85,7 +93,7 @@ function fitSocketPath(naturalPath, homeDir, role, pidTag) {
85
93
  throw new Error(`daemon socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
86
94
  }
87
95
  function resolveDaemonHomeDir(homeDir) {
88
- const explicit = homeDir ?? readRoveEnv("HOME_DIR");
96
+ const explicit = homeDir ?? readRoveHomeDirEnv();
89
97
  return explicit && explicit.length > 0 ? explicit : homedir();
90
98
  }
91
99
  function isWindowsPipePath(path) {
@@ -98,7 +106,7 @@ function defaultPtyHostSocketPath(homeDir, platform = process.platform) {
98
106
  const override = readRoveEnv("PTY_SOCKET_PATH");
99
107
  if (override && override.length > 0)
100
108
  return override;
101
- const explicit = homeDir ?? readRoveEnv("HOME_DIR");
109
+ const explicit = homeDir ?? readRoveHomeDirEnv();
102
110
  if (platform === "win32")
103
111
  return windowsPipePath(explicit || homedir(), "pty");
104
112
  if (explicit && explicit.length > 0) {
@@ -111,19 +119,19 @@ function defaultPtyHostSocketPath(homeDir, platform = process.platform) {
111
119
  const home = homedir();
112
120
  return fitSocketPath(runtimePath(home, "pty.sock", "pty.pid"), home, "pty");
113
121
  }
114
- function defaultPtyHostPidPath(homeDir = readRoveEnv("HOME_DIR") ?? homedir()) {
122
+ function defaultPtyHostPidPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
115
123
  const override = readRoveEnv("PTY_PID_PATH");
116
124
  if (override && override.length > 0)
117
125
  return override;
118
126
  return runtimePath(homeDir, "pty.pid", "pty.pid");
119
127
  }
120
- function defaultPtyHostLogPath(homeDir = readRoveEnv("HOME_DIR") ?? homedir()) {
128
+ function defaultPtyHostLogPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
121
129
  return join(homeDir, ROVE_STATE_DIR_BASENAME, "pty.log");
122
130
  }
123
- function defaultPtyExitsPath(homeDir = readRoveEnv("HOME_DIR") ?? homedir()) {
131
+ function defaultPtyExitsPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
124
132
  return runtimeDataPath(homeDir, "pty-exits.json");
125
133
  }
126
- function defaultPtyFreezeDir(homeDir = readRoveEnv("HOME_DIR") ?? homedir()) {
134
+ function defaultPtyFreezeDir(homeDir = readRoveHomeDirEnv() ?? homedir()) {
127
135
  return runtimeDataPath(homeDir, "pty-sessions");
128
136
  }
129
137
 
@@ -182,7 +190,7 @@ async function nodePtyDriver(spawn) {
182
190
 
183
191
  // ../kobe-daemon/src/daemon/pty-server.ts
184
192
  import { readFileSync as readFileSync4 } from "node:fs";
185
- import { mkdir, unlink as unlink2, writeFile } from "node:fs/promises";
193
+ import { mkdir as mkdir2, unlink as unlink2, writeFile } from "node:fs/promises";
186
194
  import { createServer } from "node:net";
187
195
  import { dirname as dirname3 } from "node:path";
188
196
  import { StringDecoder as StringDecoder3 } from "node:string_decoder";
@@ -336,6 +344,30 @@ function requireString(payload, key) {
336
344
  throw new Error(`${key} is required`);
337
345
  return value;
338
346
  }
347
+
348
+ // ../kobe-daemon/src/daemon/owner-only.ts
349
+ import { chmod, mkdir } from "node:fs/promises";
350
+ import { join as join2 } from "node:path";
351
+ var OWNER_ONLY_DIR_MODE = 448;
352
+ var OWNER_ONLY_FILE_MODE = 384;
353
+ async function tighten(path, mode) {
354
+ try {
355
+ await chmod(path, mode);
356
+ } catch {}
357
+ }
358
+ function tightenDirPermissions(dir) {
359
+ return tighten(dir, OWNER_ONLY_DIR_MODE);
360
+ }
361
+ function tightenFilePermissions(file) {
362
+ return tighten(file, OWNER_ONLY_FILE_MODE);
363
+ }
364
+ async function ensureOwnerOnlyDir(dir) {
365
+ await mkdir(dir, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
366
+ await tightenDirPermissions(dir);
367
+ }
368
+ function ensureOwnerOnlyStateDir(homeDir) {
369
+ return ensureOwnerOnlyDir(join2(homeDir, ROVE_STATE_DIR_BASENAME));
370
+ }
339
371
  // ../kobe-plugin-sdk/dist/contract.js
340
372
  var DAEMON_CHANNELS = [
341
373
  "task.snapshot",
@@ -388,7 +420,7 @@ function frameToLine(frame) {
388
420
  // ../kobe-daemon/src/daemon/pty-data-migration.ts
389
421
  import { existsSync as existsSync3, lstatSync, mkdirSync, renameSync as renameSync2, symlinkSync } from "node:fs";
390
422
  import { homedir as homedir2 } from "node:os";
391
- import { join as join2 } from "node:path";
423
+ import { join as join3 } from "node:path";
392
424
  var PTY_HOST_DATA_ENTRIES = ["pty-exits.json", "pty-sessions"];
393
425
  function lstatIfExists(path) {
394
426
  try {
@@ -397,15 +429,15 @@ function lstatIfExists(path) {
397
429
  return;
398
430
  }
399
431
  }
400
- function migrateLegacyPtyHostData(homeDir = readRoveEnv("HOME_DIR") ?? homedir2()) {
401
- const canonicalDir = join2(homeDir, ROVE_STATE_DIR_BASENAME);
402
- const legacyDir = join2(homeDir, COMPAT_STATE_DIR_BASENAME);
432
+ function migrateLegacyPtyHostData(homeDir = readRoveHomeDirEnv() ?? homedir2()) {
433
+ const canonicalDir = join3(homeDir, ROVE_STATE_DIR_BASENAME);
434
+ const legacyDir = join3(homeDir, COMPAT_STATE_DIR_BASENAME);
403
435
  if (canonicalDir === legacyDir)
404
436
  return [];
405
437
  const moved = [];
406
438
  for (const name of PTY_HOST_DATA_ENTRIES) {
407
- const canonical = join2(canonicalDir, name);
408
- const legacy = join2(legacyDir, name);
439
+ const canonical = join3(canonicalDir, name);
440
+ const legacy = join3(legacyDir, name);
409
441
  try {
410
442
  if (existsSync3(canonical))
411
443
  continue;
@@ -424,7 +456,8 @@ function migrateLegacyPtyHostData(homeDir = readRoveEnv("HOME_DIR") ?? homedir2(
424
456
  }
425
457
 
426
458
  // ../kobe-daemon/src/daemon/pty-exit-store.ts
427
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
459
+ import { randomUUID } from "node:crypto";
460
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync3, writeFileSync } from "node:fs";
428
461
  import { dirname as dirname2 } from "node:path";
429
462
 
430
463
  // ../kobe-daemon/src/daemon/terminal-rows.ts
@@ -457,14 +490,34 @@ function plainTail(raw) {
457
490
  lines.pop();
458
491
  return lines.slice(-TAIL_LINES);
459
492
  }
460
- function readPtyExitRecords(path = defaultPtyExitsPath()) {
493
+ function engineExitCodeFromTail(tail) {
494
+ for (let i = tail.length - 1;i >= 0; i--) {
495
+ const m = ENGINE_EXIT_BANNER.exec(tail[i] ?? "");
496
+ if (m?.[1])
497
+ return Number.parseInt(m[1], 10);
498
+ }
499
+ return null;
500
+ }
501
+ var ENGINE_EXIT_BANNER = /Engine exited \(code (\d+)\)/;
502
+ function readPtyExitStore(path = defaultPtyExitsPath()) {
503
+ const read = (status, records = {}) => ({
504
+ records,
505
+ status,
506
+ readable: status === "ok" || status === "absent"
507
+ });
508
+ let raw;
509
+ try {
510
+ raw = readFileSync2(path, "utf8");
511
+ } catch (err) {
512
+ return read(err.code === "ENOENT" ? "absent" : "unreadable");
513
+ }
461
514
  try {
462
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
515
+ const parsed = JSON.parse(raw);
463
516
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
464
- return {};
465
- return parsed;
517
+ return read("unparsable");
518
+ return read("ok", parsed);
466
519
  } catch {
467
- return {};
520
+ return read("unparsable");
468
521
  }
469
522
  }
470
523
  function recordPtyExit(info, path = defaultPtyExitsPath()) {
@@ -472,27 +525,33 @@ function recordPtyExit(info, path = defaultPtyExitsPath()) {
472
525
  return;
473
526
  if (info.exit.code === 0 && info.exit.signal === null)
474
527
  return;
528
+ const tail = plainTail(info.tail);
475
529
  writeRecord(info.key, {
476
530
  key: info.key,
477
531
  pid: info.pid,
478
- code: info.exit.code,
532
+ code: info.exit.code ?? engineExitCodeFromTail(tail),
479
533
  signal: info.exit.signal,
480
534
  at: info.exit.at,
481
- tail: plainTail(info.tail),
535
+ tail,
482
536
  layer: "pty"
483
537
  }, path);
484
538
  }
485
539
  function writeRecord(storeKey, record, path) {
486
- const records = readPtyExitRecords(path);
540
+ const store = readPtyExitStore(path);
541
+ if (store.status === "unreadable")
542
+ throw new Error(`pty-exits store is unreadable (${path}) — refusing to overwrite it`);
543
+ const records = store.records;
487
544
  records[storeKey] = record;
488
545
  const newest = Object.entries(records).sort(([, a], [, b]) => a.at < b.at ? 1 : -1).slice(0, MAX_RECORDS);
489
546
  mkdirSync2(dirname2(path), { recursive: true });
490
- writeFileSync(path, JSON.stringify(Object.fromEntries(newest), null, 2), { encoding: "utf8", mode: 384 });
547
+ const tmp = `${path}.tmp-${process.pid}-${randomUUID()}`;
548
+ writeFileSync(tmp, JSON.stringify(Object.fromEntries(newest), null, 2), { encoding: "utf8", mode: 384 });
549
+ renameSync3(tmp, path);
491
550
  }
492
551
 
493
552
  // ../kobe-daemon/src/daemon/pty-freeze-store.ts
494
- import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
495
- import { join as join3 } from "node:path";
553
+ import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, renameSync as renameSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
554
+ import { join as join4 } from "node:path";
496
555
  import { StringDecoder } from "node:string_decoder";
497
556
 
498
557
  // ../kobe-daemon/src/daemon/terminal-colors.ts
@@ -602,7 +661,7 @@ function thawSession(record, cap) {
602
661
  };
603
662
  }
604
663
  function recordFile(dir, key) {
605
- return join3(dir, `${encodeURIComponent(key)}.json`);
664
+ return join4(dir, `${encodeURIComponent(key)}.json`);
606
665
  }
607
666
  function parseRecord(raw) {
608
667
  try {
@@ -623,43 +682,64 @@ function parseRecord(raw) {
623
682
  }
624
683
  }
625
684
  var FREEZE_TTL_MS = 14 * 24 * 60 * 60 * 1000;
626
- var FREEZE_MAX_RECORDS = 64;
685
+ var FREEZE_RESTORE_MAX_BYTES = 64 * 1024 * 1024;
627
686
  function updatedAtMs(record) {
628
687
  const t = Date.parse(record.updatedAt);
629
688
  return Number.isFinite(t) ? t : 0;
630
689
  }
631
- function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now()) {
690
+ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), report) {
632
691
  let names;
633
692
  try {
634
693
  names = readdirSync(dir);
635
694
  } catch {
636
695
  return [];
637
696
  }
638
- const kept = [];
639
- const stale = [];
697
+ const entries = [];
640
698
  for (const name of names) {
641
699
  if (!name.endsWith(".json"))
642
700
  continue;
701
+ try {
702
+ const st = statSync2(join4(dir, name));
703
+ entries.push({ name, size: st.size, mtimeMs: st.mtimeMs });
704
+ } catch {}
705
+ }
706
+ entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
707
+ const kept = [];
708
+ const stale = [];
709
+ let bytesRead = 0;
710
+ let deferred = 0;
711
+ let unreadable = 0;
712
+ for (const entry of entries) {
713
+ if (now - entry.mtimeMs > FREEZE_TTL_MS) {
714
+ stale.push(entry.name);
715
+ continue;
716
+ }
717
+ if (bytesRead + entry.size > FREEZE_RESTORE_MAX_BYTES && kept.length > 0) {
718
+ deferred++;
719
+ continue;
720
+ }
643
721
  let record = null;
644
722
  try {
645
- record = parseRecord(readFileSync3(join3(dir, name), "utf8"));
723
+ record = parseRecord(readFileSync3(join4(dir, entry.name), "utf8"));
646
724
  } catch {}
647
- if (!record)
725
+ bytesRead += entry.size;
726
+ if (!record) {
727
+ unreadable++;
648
728
  continue;
729
+ }
649
730
  if (now - updatedAtMs(record) > FREEZE_TTL_MS)
650
- stale.push(name);
731
+ stale.push(entry.name);
651
732
  else
652
- kept.push({ name, record });
733
+ kept.push(record);
653
734
  }
654
- kept.sort((a, b) => updatedAtMs(b.record) - updatedAtMs(a.record));
655
- for (const over of kept.splice(FREEZE_MAX_RECORDS))
656
- stale.push(over.name);
657
735
  for (const name of stale) {
658
736
  try {
659
- rmSync(join3(dir, name), { force: true });
737
+ rmSync(join4(dir, name), { force: true });
660
738
  } catch {}
661
739
  }
662
- return kept.map((entry) => entry.record);
740
+ kept.sort((a, b) => updatedAtMs(b) - updatedAtMs(a));
741
+ report?.({ restored: kept.length, bytesRead, deferred, expired: stale.length, unreadable });
742
+ return kept;
663
743
  }
664
744
  var DIR_MODE = 448;
665
745
  var FILE_MODE = 384;
@@ -677,7 +757,7 @@ function tightenExistingPermissions(dir) {
677
757
  if (!name.endsWith(".json"))
678
758
  continue;
679
759
  try {
680
- chmodSync(join3(dir, name), FILE_MODE);
760
+ chmodSync(join4(dir, name), FILE_MODE);
681
761
  } catch {}
682
762
  }
683
763
  }
@@ -690,7 +770,7 @@ function fileFreezeSink(dir = defaultPtyFreezeDir()) {
690
770
  const target = recordFile(dir, record.key);
691
771
  const staging = `${target}.${process.pid}.tmp`;
692
772
  writeFileSync2(staging, JSON.stringify(record), { encoding: "utf8", mode: FILE_MODE });
693
- renameSync3(staging, target);
773
+ renameSync4(staging, target);
694
774
  } catch {}
695
775
  },
696
776
  drop(key) {
@@ -802,6 +882,7 @@ function freshSessionState(key, spec, argv) {
802
882
  exit: null,
803
883
  restored: false,
804
884
  lastFreezeAtMs: 0,
885
+ frozenTotalBytes: 0,
805
886
  lastHumanWriteMs: 0
806
887
  };
807
888
  }
@@ -1031,6 +1112,20 @@ class PtyChildController {
1031
1112
  }
1032
1113
  }
1033
1114
 
1115
+ // ../kobe-daemon/src/daemon/pty-freeze-policy.ts
1116
+ var FREEZE_INTERVAL_MS = 5000;
1117
+ var FREEZE_MIN_APPENDED_BYTES = 64 * 1024;
1118
+ var FREEZE_STALE_MS = 60000;
1119
+ function shouldFreeze(session, now) {
1120
+ const appended = session.totalBytes - (session.frozenTotalBytes ?? 0);
1121
+ if (appended <= 0)
1122
+ return false;
1123
+ const since = now - session.lastFreezeAtMs;
1124
+ if (since < FREEZE_INTERVAL_MS)
1125
+ return false;
1126
+ return appended >= FREEZE_MIN_APPENDED_BYTES || since >= FREEZE_STALE_MS;
1127
+ }
1128
+
1034
1129
  // ../kobe-daemon/src/daemon/pty-warm.ts
1035
1130
  class WarmSpare {
1036
1131
  deps;
@@ -1096,7 +1191,6 @@ function resolveHumanWriteQuietMs() {
1096
1191
  const n = Number(raw);
1097
1192
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_HUMAN_WRITE_QUIET_MS;
1098
1193
  }
1099
- var FREEZE_INTERVAL_MS = 5000;
1100
1194
 
1101
1195
  class PtyHost {
1102
1196
  sessions = new Map;
@@ -1344,9 +1438,10 @@ class PtyHost {
1344
1438
  if (session.closedByRequest)
1345
1439
  return;
1346
1440
  const now = Date.now();
1347
- if (!force && now - session.lastFreezeAtMs < FREEZE_INTERVAL_MS)
1441
+ if (!force && !shouldFreeze(session, now))
1348
1442
  return;
1349
1443
  session.lastFreezeAtMs = now;
1444
+ session.frozenTotalBytes = session.totalBytes;
1350
1445
  freeze.save(freezeSession(session));
1351
1446
  }
1352
1447
  liveCount() {
@@ -1358,6 +1453,20 @@ class PtyHost {
1358
1453
  }
1359
1454
  }
1360
1455
 
1456
+ // ../kobe-daemon/src/daemon/socket-guard.ts
1457
+ async function listenOnUnixSocket(server, socketPath) {
1458
+ await new Promise((resolve, reject) => {
1459
+ const evented = server;
1460
+ evented.once("error", reject);
1461
+ server.listen(socketPath, () => {
1462
+ evented.removeListener("error", reject);
1463
+ resolve();
1464
+ });
1465
+ });
1466
+ if (!isWindowsPipePath(socketPath))
1467
+ await tightenFilePermissions(socketPath);
1468
+ }
1469
+
1361
1470
  // ../kobe-daemon/src/daemon/pty-server.ts
1362
1471
  var DEFAULT_IDLE_EXIT_MS = 60000;
1363
1472
  var DEFAULT_ORPHAN_CHECK_MS = 30000;
@@ -1447,11 +1556,22 @@ async function startPtyHostServer(options = {}) {
1447
1556
  driver: options.driver,
1448
1557
  log
1449
1558
  });
1450
- ptys.restoreFrozen(loadFrozenSessions(freezeDir));
1559
+ ptys.restoreFrozen(loadFrozenSessions(freezeDir, Date.now(), (s) => {
1560
+ const mib = (bytes) => `${Math.round(bytes / (1024 * 1024))}MB`;
1561
+ const notes = [`restored ${s.restored} (${mib(s.bytesRead)})`];
1562
+ if (s.deferred > 0)
1563
+ notes.push(`${s.deferred} left unread past the ${mib(FREEZE_RESTORE_MAX_BYTES)} budget`);
1564
+ if (s.expired > 0)
1565
+ notes.push(`${s.expired} deleted past the ${FREEZE_TTL_MS / 86400000}-day TTL`);
1566
+ if (s.unreadable > 0)
1567
+ notes.push(`${s.unreadable} unreadable`);
1568
+ log("freeze", notes.join(", "));
1569
+ }));
1451
1570
  const pipeSocket = isWindowsPipePath(socketPath);
1571
+ await ensureOwnerOnlyStateDir(resolveDaemonHomeDir());
1452
1572
  if (!pipeSocket)
1453
- await mkdir(dirname3(socketPath), { recursive: true });
1454
- await mkdir(dirname3(pidPath), { recursive: true });
1573
+ await mkdir2(dirname3(socketPath), { recursive: true });
1574
+ await mkdir2(dirname3(pidPath), { recursive: true });
1455
1575
  const server = createServer((socket) => {
1456
1576
  const client = {
1457
1577
  socket,
@@ -1596,10 +1716,7 @@ async function startPtyHostServer(options = {}) {
1596
1716
  `);
1597
1717
  }
1598
1718
  }
1599
- await new Promise((resolve, reject) => {
1600
- server.once("error", reject);
1601
- server.listen(socketPath, () => resolve());
1602
- });
1719
+ await listenOnUnixSocket(server, socketPath);
1603
1720
  await writeFile(pidPath, `${process.pid}
1604
1721
  `, "utf8");
1605
1722
  if (!pipeSocket) {