@sma1lboy/rove 0.9.110 → 0.9.147

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-0m9j3yap.js +7 -0
  4. package/dist/cli/chunk-0r8333z3.js +3 -0
  5. package/dist/cli/chunk-0sm2k04y.js +3 -0
  6. package/dist/cli/chunk-0vmrteyg.js +3 -0
  7. package/dist/cli/chunk-0vy8351p.js +18 -0
  8. package/dist/cli/chunk-13s8rnk5.js +3 -0
  9. package/dist/cli/chunk-1kktfmtg.js +4 -0
  10. package/dist/cli/chunk-1rkb0j1j.js +3 -0
  11. package/dist/cli/chunk-1tx1s5v6.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-3bfm4hrs.js +3 -0
  18. package/dist/cli/chunk-3ry6prwn.js +3 -0
  19. package/dist/cli/chunk-3ztek08w.js +4 -0
  20. package/dist/cli/chunk-49s2zc0h.js +8 -0
  21. package/dist/cli/chunk-4wwx51m5.js +3 -0
  22. package/dist/cli/chunk-5bn91n19.js +3 -0
  23. package/dist/cli/chunk-5c3d1y0r.js +2 -0
  24. package/dist/cli/chunk-5hhvrvgm.js +8 -0
  25. package/dist/cli/chunk-6dn6h640.js +3 -0
  26. package/dist/cli/chunk-6frfschk.js +2 -0
  27. package/dist/cli/chunk-6rkc7efm.js +5 -0
  28. package/dist/cli/chunk-6xppe9bj.js +3 -0
  29. package/dist/cli/chunk-6zwg6me2.js +4 -0
  30. package/dist/cli/chunk-764bsyb0.js +1 -0
  31. package/dist/cli/chunk-79gqh5p2.js +5 -0
  32. package/dist/cli/chunk-7nhdvr5v.js +4 -0
  33. package/dist/cli/chunk-7q67dz4c.js +3 -0
  34. package/dist/cli/chunk-7ydwn358.js +3 -0
  35. package/dist/cli/chunk-86j25g7y.js +7 -0
  36. package/dist/cli/chunk-88qjhytd.js +12 -0
  37. package/dist/cli/chunk-8arm7k43.js +69 -0
  38. package/dist/cli/chunk-8ddjfc2v.js +4 -0
  39. package/dist/cli/chunk-8r1shxz0.js +16 -0
  40. package/dist/cli/chunk-99devyxr.js +2 -0
  41. package/dist/cli/chunk-9c1e5z6a.js +3 -0
  42. package/dist/cli/chunk-9n458sdy.js +2 -0
  43. package/dist/cli/chunk-aq912vvw.js +3 -0
  44. package/dist/cli/chunk-arf294te.js +2 -0
  45. package/dist/cli/chunk-awsawfrm.js +2 -0
  46. package/dist/cli/chunk-aynx3d2a.js +2 -0
  47. package/dist/cli/chunk-b99tf0jr.js +3 -0
  48. package/dist/cli/chunk-bdbgmjt7.js +3 -0
  49. package/dist/cli/chunk-c6mthex1.js +5 -0
  50. package/dist/cli/chunk-cb0pgyq1.js +8 -0
  51. package/dist/cli/chunk-cb2fpajx.js +3 -0
  52. package/dist/cli/chunk-cehhk94g.js +2 -0
  53. package/dist/cli/chunk-d4bqay6d.js +9 -0
  54. package/dist/cli/chunk-dcb59tsn.js +2 -0
  55. package/dist/cli/chunk-df7qe9y7.js +4 -0
  56. package/dist/cli/chunk-dkxhmj9k.js +25 -0
  57. package/dist/cli/chunk-dnb2bmtb.js +3 -0
  58. package/dist/cli/chunk-dqndd97s.js +7 -0
  59. package/dist/cli/chunk-dvcb6mk4.js +3 -0
  60. package/dist/cli/chunk-dw853n4w.js +3 -0
  61. package/dist/cli/chunk-dwsgeq06.js +15 -0
  62. package/dist/cli/chunk-e1g9mvww.js +11 -0
  63. package/dist/cli/chunk-e3ye4kmk.js +2 -0
  64. package/dist/cli/chunk-ef11h2am.js +3 -0
  65. package/dist/cli/chunk-ef39ym8f.js +3 -0
  66. package/dist/cli/chunk-ewdhz3dv.js +2 -0
  67. package/dist/cli/chunk-f6zsej8e.js +3 -0
  68. package/dist/cli/chunk-fe34e0w7.js +28 -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-hgq2gtxx.js +4 -0
  81. package/dist/cli/chunk-j4xfsn9g.js +5 -0
  82. package/dist/cli/chunk-j9g1m4dy.js +2 -0
  83. package/dist/cli/chunk-jdbfawrm.js +3 -0
  84. package/dist/cli/chunk-jsss3ze7.js +11 -0
  85. package/dist/cli/chunk-jvssy49v.js +2 -0
  86. package/dist/cli/chunk-jvxh93nz.js +3 -0
  87. package/dist/cli/chunk-jyv82gf0.js +258 -0
  88. package/dist/cli/chunk-k3ksts6g.js +3 -0
  89. package/dist/cli/chunk-kdrrw9tm.js +2 -0
  90. package/dist/cli/chunk-kg11c1py.js +3 -0
  91. package/dist/cli/chunk-kwe61yx7.js +3 -0
  92. package/dist/cli/chunk-mb5m7ex7.js +2 -0
  93. package/dist/cli/chunk-mfqjdpd8.js +2 -0
  94. package/dist/cli/chunk-mjvq39gf.js +4 -0
  95. package/dist/cli/chunk-mrwee8sj.js +3 -0
  96. package/dist/cli/chunk-my20q579.js +3 -0
  97. package/dist/cli/chunk-myfjt6dw.js +7 -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-p56s3ekm.js +3 -0
  103. package/dist/cli/chunk-p5spmf7e.js +2 -0
  104. package/dist/cli/chunk-p7kztgsd.js +4 -0
  105. package/dist/cli/chunk-pdh5w4ht.js +3 -0
  106. package/dist/cli/chunk-pdj8kqa1.js +74 -0
  107. package/dist/cli/chunk-psx9tk3x.js +4 -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-q9x6d0k6.js +3 -0
  112. package/dist/cli/chunk-qkehg2c1.js +3 -0
  113. package/dist/cli/chunk-qtke4x42.js +3 -0
  114. package/dist/cli/chunk-rgcv1wcs.js +4 -0
  115. package/dist/cli/chunk-rxvbm4vt.js +7 -0
  116. package/dist/cli/chunk-ryhrw647.js +3 -0
  117. package/dist/cli/chunk-s76r56rj.js +3 -0
  118. package/dist/cli/chunk-sdyzxayr.js +3 -0
  119. package/dist/cli/chunk-svpn3av1.js +3 -0
  120. package/dist/cli/chunk-v5893dx3.js +2 -0
  121. package/dist/cli/chunk-vapjtyv9.js +7 -0
  122. package/dist/cli/chunk-vkch2spv.js +33 -0
  123. package/dist/cli/chunk-vpzvcxc1.js +3 -0
  124. package/dist/cli/chunk-vws0j65e.js +4 -0
  125. package/dist/cli/chunk-vyb5a2rh.js +4 -0
  126. package/dist/cli/chunk-w298ayjc.js +3 -0
  127. package/dist/cli/chunk-w48zjgxv.js +9 -0
  128. package/dist/cli/chunk-w5xy83ze.js +2 -0
  129. package/dist/cli/chunk-wpgmq5z6.js +106 -0
  130. package/dist/cli/chunk-wqfp8d9w.js +3 -0
  131. package/dist/cli/chunk-x760j10f.js +3 -0
  132. package/dist/cli/chunk-xbc3gcry.js +2 -0
  133. package/dist/cli/chunk-xy8be792.js +3 -0
  134. package/dist/cli/chunk-yhcz2d2r.js +34 -0
  135. package/dist/cli/chunk-yk2ha5yj.js +3 -0
  136. package/dist/cli/chunk-ykvk2546.js +3 -0
  137. package/dist/cli/chunk-ympakh8w.js +6 -0
  138. package/dist/cli/chunk-zb56ck5f.js +3 -0
  139. package/dist/cli/chunk-zwzkve1e.js +2 -0
  140. package/dist/cli/chunk-zy6vgjdp.js +19 -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 +150 -48
  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.147",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;
461
509
  try {
462
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
510
+ raw = readFileSync2(path, "utf8");
511
+ } catch (err) {
512
+ return read(err.code === "ENOENT" ? "absent" : "unreadable");
513
+ }
514
+ try {
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) {
@@ -1358,6 +1438,20 @@ class PtyHost {
1358
1438
  }
1359
1439
  }
1360
1440
 
1441
+ // ../kobe-daemon/src/daemon/socket-guard.ts
1442
+ async function listenOnUnixSocket(server, socketPath) {
1443
+ await new Promise((resolve, reject) => {
1444
+ const evented = server;
1445
+ evented.once("error", reject);
1446
+ server.listen(socketPath, () => {
1447
+ evented.removeListener("error", reject);
1448
+ resolve();
1449
+ });
1450
+ });
1451
+ if (!isWindowsPipePath(socketPath))
1452
+ await tightenFilePermissions(socketPath);
1453
+ }
1454
+
1361
1455
  // ../kobe-daemon/src/daemon/pty-server.ts
1362
1456
  var DEFAULT_IDLE_EXIT_MS = 60000;
1363
1457
  var DEFAULT_ORPHAN_CHECK_MS = 30000;
@@ -1447,11 +1541,22 @@ async function startPtyHostServer(options = {}) {
1447
1541
  driver: options.driver,
1448
1542
  log
1449
1543
  });
1450
- ptys.restoreFrozen(loadFrozenSessions(freezeDir));
1544
+ ptys.restoreFrozen(loadFrozenSessions(freezeDir, Date.now(), (s) => {
1545
+ const mib = (bytes) => `${Math.round(bytes / (1024 * 1024))}MB`;
1546
+ const notes = [`restored ${s.restored} (${mib(s.bytesRead)})`];
1547
+ if (s.deferred > 0)
1548
+ notes.push(`${s.deferred} left unread past the ${mib(FREEZE_RESTORE_MAX_BYTES)} budget`);
1549
+ if (s.expired > 0)
1550
+ notes.push(`${s.expired} deleted past the ${FREEZE_TTL_MS / 86400000}-day TTL`);
1551
+ if (s.unreadable > 0)
1552
+ notes.push(`${s.unreadable} unreadable`);
1553
+ log("freeze", notes.join(", "));
1554
+ }));
1451
1555
  const pipeSocket = isWindowsPipePath(socketPath);
1556
+ await ensureOwnerOnlyStateDir(resolveDaemonHomeDir());
1452
1557
  if (!pipeSocket)
1453
- await mkdir(dirname3(socketPath), { recursive: true });
1454
- await mkdir(dirname3(pidPath), { recursive: true });
1558
+ await mkdir2(dirname3(socketPath), { recursive: true });
1559
+ await mkdir2(dirname3(pidPath), { recursive: true });
1455
1560
  const server = createServer((socket) => {
1456
1561
  const client = {
1457
1562
  socket,
@@ -1596,10 +1701,7 @@ async function startPtyHostServer(options = {}) {
1596
1701
  `);
1597
1702
  }
1598
1703
  }
1599
- await new Promise((resolve, reject) => {
1600
- server.once("error", reject);
1601
- server.listen(socketPath, () => resolve());
1602
- });
1704
+ await listenOnUnixSocket(server, socketPath);
1603
1705
  await writeFile(pidPath, `${process.pid}
1604
1706
  `, "utf8");
1605
1707
  if (!pipeSocket) {