@sma1lboy/rove 0.9.237 → 0.9.239

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 (240) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/{chunk-63xy91mc.js → chunk-0k91029d.js} +1 -1
  3. package/dist/cli/chunk-0mj358p5.js +3 -0
  4. package/dist/cli/chunk-0n7w8t48.js +2 -0
  5. package/dist/cli/{chunk-v4y0sf0x.js → chunk-0wrh5s5y.js} +2 -2
  6. package/dist/cli/chunk-0y4qgnby.js +3 -0
  7. package/dist/cli/chunk-1brfxbvg.js +4 -0
  8. package/dist/cli/{chunk-tdze2mnn.js → chunk-20qm75rb.js} +1 -1
  9. package/dist/cli/chunk-21fm5tks.js +2 -0
  10. package/dist/cli/{chunk-bkbmqec2.js → chunk-28zg6p31.js} +1 -1
  11. package/dist/cli/chunk-2andp9gs.js +2 -0
  12. package/dist/cli/{chunk-r2pztgm6.js → chunk-2bgmby1d.js} +1 -1
  13. package/dist/cli/{chunk-5e39zhvj.js → chunk-2s2xy444.js} +5 -5
  14. package/dist/cli/chunk-2vgc5cyj.js +2 -0
  15. package/dist/cli/chunk-2x9yc5nx.js +2 -0
  16. package/dist/cli/chunk-30kjq5m7.js +2 -0
  17. package/dist/cli/{chunk-mkksdcsw.js → chunk-3342qzve.js} +1 -1
  18. package/dist/cli/chunk-3aqhytyy.js +3 -0
  19. package/dist/cli/chunk-3c58659p.js +2 -0
  20. package/dist/cli/chunk-3d1hjrq0.js +2 -0
  21. package/dist/cli/chunk-41x1hrqe.js +3 -0
  22. package/dist/cli/{chunk-mhg40es1.js → chunk-4hestsjp.js} +1 -1
  23. package/dist/cli/{chunk-s1p650mm.js → chunk-53cyns0f.js} +1 -1
  24. package/dist/cli/chunk-576r5nht.js +5 -0
  25. package/dist/cli/{chunk-rvx5zz72.js → chunk-5akk3wap.js} +6 -5
  26. package/dist/cli/chunk-7cce0st1.js +3 -0
  27. package/dist/cli/{chunk-zyd6k6ak.js → chunk-7q43tysm.js} +1 -1
  28. package/dist/cli/chunk-8bn6eg67.js +8 -0
  29. package/dist/cli/chunk-8rf7nzan.js +4 -0
  30. package/dist/cli/chunk-96cjpssp.js +3 -0
  31. package/dist/cli/{chunk-bs7v83x0.js → chunk-9aydqazr.js} +1 -1
  32. package/dist/cli/{chunk-7txq91d9.js → chunk-9jsm6h5y.js} +1 -1
  33. package/dist/cli/{chunk-pz907kve.js → chunk-9w5g7frx.js} +1 -1
  34. package/dist/cli/{chunk-vnpqemb0.js → chunk-a7kq09ke.js} +1 -1
  35. package/dist/cli/chunk-acxznk3y.js +2 -0
  36. package/dist/cli/chunk-adkva3ep.js +6 -0
  37. package/dist/cli/{chunk-nvqdmx7q.js → chunk-ag3txfg2.js} +1 -1
  38. package/dist/cli/{chunk-2rg6aw59.js → chunk-atmaakwp.js} +1 -1
  39. package/dist/cli/chunk-axsm0gn5.js +3 -0
  40. package/dist/cli/chunk-b0ssvpht.js +3 -0
  41. package/dist/cli/chunk-b39sdtt0.js +3 -0
  42. package/dist/cli/{chunk-qyzvcgva.js → chunk-b9q8c2qg.js} +6 -6
  43. package/dist/cli/{chunk-g56km26e.js → chunk-bn73kd6z.js} +1 -1
  44. package/dist/cli/chunk-c2m55b72.js +4 -0
  45. package/dist/cli/{chunk-3gmzrstj.js → chunk-c60qsnfw.js} +3 -3
  46. package/dist/cli/{chunk-ch0ehc06.js → chunk-cbc1h345.js} +1 -1
  47. package/dist/cli/{chunk-dnbh8hsm.js → chunk-cbjh43yc.js} +1 -1
  48. package/dist/cli/{chunk-kbje1cpy.js → chunk-cmcn8e8x.js} +1 -1
  49. package/dist/cli/{chunk-cv1kaq8a.js → chunk-cp89n425.js} +5 -5
  50. package/dist/cli/chunk-cst86kt3.js +2 -0
  51. package/dist/cli/chunk-des00203.js +2 -0
  52. package/dist/cli/chunk-dgs0m8r4.js +3 -0
  53. package/dist/cli/chunk-dkgxz9hf.js +5 -0
  54. package/dist/cli/{chunk-gqphk56d.js → chunk-ekg3h5z3.js} +7 -7
  55. package/dist/cli/chunk-fert44x2.js +2 -0
  56. package/dist/cli/chunk-ff1h2qxd.js +2 -0
  57. package/dist/cli/{chunk-cv6r89jp.js → chunk-ffs26p85.js} +1 -1
  58. package/dist/cli/chunk-fft50thp.js +3 -0
  59. package/dist/cli/{chunk-hp32tbes.js → chunk-fg62y5fm.js} +1 -1
  60. package/dist/cli/{chunk-s9a974w9.js → chunk-fvmee0tr.js} +1 -1
  61. package/dist/cli/chunk-g4c4ffqp.js +20 -0
  62. package/dist/cli/{chunk-k7g060qr.js → chunk-g4j41mky.js} +2 -2
  63. package/dist/cli/{chunk-j6kataan.js → chunk-g5c5jytr.js} +1 -1
  64. package/dist/cli/{chunk-4bmap7dg.js → chunk-ga1pb0mr.js} +1 -1
  65. package/dist/cli/{chunk-dqndd97s.js → chunk-gkdfct7t.js} +2 -2
  66. package/dist/cli/chunk-gqydm0q8.js +2 -0
  67. package/dist/cli/chunk-gvrtrvnz.js +96 -0
  68. package/dist/cli/chunk-h2m7em1e.js +3 -0
  69. package/dist/cli/chunk-h33m6cc6.js +8 -0
  70. package/dist/cli/{chunk-ynbfqbn9.js → chunk-h3fckxn3.js} +1 -1
  71. package/dist/cli/chunk-h7y9v8k1.js +4 -0
  72. package/dist/cli/{chunk-32mv1yy6.js → chunk-h9cg0j3a.js} +1 -1
  73. package/dist/cli/{chunk-h4qv19v8.js → chunk-hbn1t161.js} +1 -1
  74. package/dist/cli/{chunk-12kgbrj1.js → chunk-hen7jj2h.js} +2 -2
  75. package/dist/cli/{chunk-3es6spp4.js → chunk-hq9tbzqa.js} +2 -2
  76. package/dist/cli/{chunk-snjte1rd.js → chunk-hr2xky15.js} +1 -1
  77. package/dist/cli/{chunk-s0snbj93.js → chunk-kb6xw0fw.js} +1 -1
  78. package/dist/cli/{chunk-40dk2add.js → chunk-ker4971f.js} +2 -2
  79. package/dist/cli/{chunk-vyb5a2rh.js → chunk-kg9kmwt9.js} +1 -1
  80. package/dist/cli/chunk-ktzbg4jg.js +2 -0
  81. package/dist/cli/chunk-m4f5h05c.js +10 -0
  82. package/dist/cli/chunk-m6q44xc0.js +2 -0
  83. package/dist/cli/{chunk-hq2n27y1.js → chunk-mw0ysrpd.js} +1 -1
  84. package/dist/cli/{chunk-2cwb64fj.js → chunk-n5dea05t.js} +1 -1
  85. package/dist/cli/chunk-nc46gddg.js +3 -0
  86. package/dist/cli/{chunk-jhbc9nx2.js → chunk-nckmga7j.js} +1 -1
  87. package/dist/cli/chunk-nem2x959.js +2 -0
  88. package/dist/cli/chunk-ng12dprz.js +2 -0
  89. package/dist/cli/chunk-npwzmmv9.js +3 -0
  90. package/dist/cli/chunk-nzh30nty.js +4 -0
  91. package/dist/cli/{chunk-gqrh2gp1.js → chunk-p024scd4.js} +1 -1
  92. package/dist/cli/chunk-p18ehv9y.js +7 -0
  93. package/dist/cli/chunk-p6qr8sen.js +2 -0
  94. package/dist/cli/{chunk-3sg5v7cg.js → chunk-p83wf4zw.js} +2 -2
  95. package/dist/cli/{chunk-s278gjet.js → chunk-pdvyfz3n.js} +1 -1
  96. package/dist/cli/chunk-pe6wnsww.js +2 -0
  97. package/dist/cli/{chunk-dhtsd706.js → chunk-pqzbk1cz.js} +25 -23
  98. package/dist/cli/chunk-prqeddy7.js +3 -0
  99. package/dist/cli/chunk-pvm3fc44.js +2 -0
  100. package/dist/cli/chunk-qd5zgq91.js +10 -0
  101. package/dist/cli/chunk-qvfdqrbf.js +3 -0
  102. package/dist/cli/{chunk-2xsem13q.js → chunk-qzsahxd9.js} +1 -1
  103. package/dist/cli/{chunk-aj3fk7xz.js → chunk-rahv4nrm.js} +3 -3
  104. package/dist/cli/chunk-rqyaf0dw.js +4 -0
  105. package/dist/cli/chunk-s57vy3k3.js +2 -0
  106. package/dist/cli/{chunk-g2advs8t.js → chunk-sak1tphp.js} +1 -1
  107. package/dist/cli/{chunk-vdcn6h48.js → chunk-snhcvjdb.js} +1 -1
  108. package/dist/cli/chunk-ss8c3b25.js +3 -0
  109. package/dist/cli/{chunk-bav389wj.js → chunk-svm809bx.js} +1 -1
  110. package/dist/cli/chunk-t8v9rpdx.js +2 -0
  111. package/dist/cli/{chunk-j4xfsn9g.js → chunk-tbrkck2f.js} +1 -1
  112. package/dist/cli/chunk-tdhk4dz8.js +4 -0
  113. package/dist/cli/chunk-try1f2h3.js +3 -0
  114. package/dist/cli/{chunk-b9ywdsdj.js → chunk-tsj1054w.js} +1 -1
  115. package/dist/cli/{chunk-t3wwsz29.js → chunk-tvk46rfs.js} +1 -1
  116. package/dist/cli/{chunk-4d54zgc7.js → chunk-twyda1p8.js} +2 -2
  117. package/dist/cli/{chunk-s1e0e9hc.js → chunk-vej49hnt.js} +2 -2
  118. package/dist/cli/chunk-w2sp489k.js +3 -0
  119. package/dist/cli/chunk-wfhnbnbw.js +2 -0
  120. package/dist/cli/{chunk-0wh2ff77.js → chunk-whbm14qr.js} +2 -2
  121. package/dist/cli/{chunk-ww4fbx8h.js → chunk-wtb8cycx.js} +2 -2
  122. package/dist/cli/chunk-wvmjdnhk.js +2 -0
  123. package/dist/cli/chunk-x9s14ppv.js +4 -0
  124. package/dist/cli/{chunk-dq2mh3b3.js → chunk-xv8fhxxm.js} +1 -1
  125. package/dist/cli/{chunk-85pk7c74.js → chunk-xx166cdv.js} +1 -1
  126. package/dist/cli/{chunk-ssfj9td0.js → chunk-y8tysjc2.js} +15 -13
  127. package/dist/cli/{chunk-y3k1accw.js → chunk-yexx8nec.js} +1 -1
  128. package/dist/cli/{chunk-9prawccj.js → chunk-yt15y085.js} +1 -1
  129. package/dist/cli/{chunk-n0mg67wn.js → chunk-ytxk6zqc.js} +1 -1
  130. package/dist/cli/chunk-yvjgwnb9.js +2 -0
  131. package/dist/cli/{chunk-n24dmy2r.js → chunk-yxt1y0n3.js} +9 -9
  132. package/dist/cli/chunk-ze0htjr5.js +2 -0
  133. package/dist/cli/chunk-zeb0sttx.js +2 -0
  134. package/dist/cli/chunk-znjcesdr.js +5 -0
  135. package/dist/cli/chunk-zsfyj36g.js +3 -0
  136. package/dist/cli/chunk-zt49bbg9.js +3 -0
  137. package/dist/cli/chunk-zt7wfnva.js +3 -0
  138. package/dist/cli/{chunk-6rvzzrd7.js → chunk-zx2n95qe.js} +1 -1
  139. package/dist/cli/{chunk-c4dx3er1.js → chunk-zxsbz3kk.js} +1 -1
  140. package/dist/cli/index.js +12 -10
  141. package/dist/cli/pty-host-node.mjs +1189 -177
  142. package/dist/cli/rove-run.js +1 -1
  143. package/dist/cli/rove.js +6 -6
  144. package/dist/skills/rove/SKILL.md +1 -1
  145. package/dist/skills/rove/references/api-flags.md +5 -5
  146. package/dist/web-ui/assets/harness-BHiciz5A.js +87 -0
  147. package/dist/web-ui/assets/{index-D2wALq0b.js → index-BkxJISAI.js} +2 -2
  148. package/dist/web-ui/assets/index-nMJ42IGm.css +1 -0
  149. package/dist/web-ui/index.html +2 -2
  150. package/dist/web-ui/origin-policy.mjs +1 -1
  151. package/dist/web-ui/pty-server.mjs +66 -8
  152. package/dist/web-ui/pty-session-lifecycle.mjs +49 -14
  153. package/package.json +6 -8
  154. package/dist/cli/chunk-05jej26n.js +0 -3
  155. package/dist/cli/chunk-072x915d.js +0 -2
  156. package/dist/cli/chunk-079cyb2e.js +0 -4
  157. package/dist/cli/chunk-1da3bqjc.js +0 -4
  158. package/dist/cli/chunk-1m7cp5ze.js +0 -3
  159. package/dist/cli/chunk-1q8tbbpz.js +0 -2
  160. package/dist/cli/chunk-1tx1s5v6.js +0 -2
  161. package/dist/cli/chunk-4y1fdz34.js +0 -3
  162. package/dist/cli/chunk-53afdybj.js +0 -3
  163. package/dist/cli/chunk-544bwsss.js +0 -4
  164. package/dist/cli/chunk-5bsk7qmg.js +0 -8
  165. package/dist/cli/chunk-6s5mev2n.js +0 -4
  166. package/dist/cli/chunk-6x5vzzpx.js +0 -3
  167. package/dist/cli/chunk-7s1kx9ez.js +0 -2
  168. package/dist/cli/chunk-85mk6j0s.js +0 -3
  169. package/dist/cli/chunk-8hbtca1g.js +0 -3
  170. package/dist/cli/chunk-8pa1ph1s.js +0 -7
  171. package/dist/cli/chunk-9k47xr7m.js +0 -2
  172. package/dist/cli/chunk-9mtxxsb3.js +0 -2
  173. package/dist/cli/chunk-a3yyrabb.js +0 -2
  174. package/dist/cli/chunk-a8et9f63.js +0 -4
  175. package/dist/cli/chunk-ahnk6zx3.js +0 -2
  176. package/dist/cli/chunk-bc18gat5.js +0 -4
  177. package/dist/cli/chunk-bw73t1zw.js +0 -5
  178. package/dist/cli/chunk-c2tevj9f.js +0 -2
  179. package/dist/cli/chunk-d1agt94n.js +0 -8
  180. package/dist/cli/chunk-dc31fzrm.js +0 -2
  181. package/dist/cli/chunk-dw853n4w.js +0 -3
  182. package/dist/cli/chunk-eawt4n4g.js +0 -2
  183. package/dist/cli/chunk-efx1gqmp.js +0 -2
  184. package/dist/cli/chunk-egx76ah2.js +0 -5
  185. package/dist/cli/chunk-ex1a2djb.js +0 -2
  186. package/dist/cli/chunk-f5e3dcxe.js +0 -2
  187. package/dist/cli/chunk-fwb72vzc.js +0 -2
  188. package/dist/cli/chunk-fxs82dsx.js +0 -4
  189. package/dist/cli/chunk-g3r1dwy9.js +0 -3
  190. package/dist/cli/chunk-ge30p4s5.js +0 -3
  191. package/dist/cli/chunk-gmgsxdrf.js +0 -10
  192. package/dist/cli/chunk-he76emdm.js +0 -2
  193. package/dist/cli/chunk-hgx20x94.js +0 -2
  194. package/dist/cli/chunk-hkq7c6hy.js +0 -2
  195. package/dist/cli/chunk-hnfp317x.js +0 -2
  196. package/dist/cli/chunk-hq4b3fca.js +0 -3
  197. package/dist/cli/chunk-jvxh93nz.js +0 -3
  198. package/dist/cli/chunk-k6nxyxyh.js +0 -3
  199. package/dist/cli/chunk-k8tspshr.js +0 -2
  200. package/dist/cli/chunk-kavsx69v.js +0 -3
  201. package/dist/cli/chunk-md8k7c5w.js +0 -2
  202. package/dist/cli/chunk-mppda1wq.js +0 -3
  203. package/dist/cli/chunk-ppwpjm39.js +0 -3
  204. package/dist/cli/chunk-pvw2y93q.js +0 -25
  205. package/dist/cli/chunk-pw6yfj10.js +0 -3
  206. package/dist/cli/chunk-q4nehkj9.js +0 -2
  207. package/dist/cli/chunk-qagek3wg.js +0 -5
  208. package/dist/cli/chunk-rz8zs7j8.js +0 -3
  209. package/dist/cli/chunk-sb0ns0c7.js +0 -3
  210. package/dist/cli/chunk-sp99y6eb.js +0 -2
  211. package/dist/cli/chunk-vejgnmcp.js +0 -2
  212. package/dist/cli/chunk-vz5g1p3w.js +0 -3
  213. package/dist/cli/chunk-vzqrj32e.js +0 -2
  214. package/dist/cli/chunk-wpsbyq4s.js +0 -2
  215. package/dist/cli/chunk-wtqxv2rv.js +0 -39
  216. package/dist/cli/chunk-xhgmnvjz.js +0 -8
  217. package/dist/cli/chunk-y05d79az.js +0 -2
  218. package/dist/cli/chunk-y8fqdnh3.js +0 -3
  219. package/dist/cli/chunk-ygd572ee.js +0 -4
  220. package/dist/cli/chunk-zja9chrd.js +0 -4
  221. package/dist/cli/chunk-zwej00t0.js +0 -5
  222. package/dist/cli/kobe-run.js +0 -3
  223. package/dist/cli/kobe.js +0 -7
  224. package/dist/completions/kobe.bash +0 -25
  225. package/dist/completions/kobe.fish +0 -27
  226. package/dist/completions/kobe.zsh +0 -35
  227. package/dist/web-ui/assets/harness-CLsfHgXB.js +0 -86
  228. package/dist/web-ui/assets/index-B4knjCIs.css +0 -1
  229. /package/dist/cli/{chunk-hqbmzz4t.js → chunk-0422g8ac.js} +0 -0
  230. /package/dist/cli/{chunk-khj0apc7.js → chunk-2tfnscxk.js} +0 -0
  231. /package/dist/cli/{chunk-7q67dz4c.js → chunk-4250pw6r.js} +0 -0
  232. /package/dist/cli/{chunk-xsrcwcjc.js → chunk-5mhvjytn.js} +0 -0
  233. /package/dist/cli/{chunk-ef39ym8f.js → chunk-7bgbansg.js} +0 -0
  234. /package/dist/cli/{chunk-nh4wvyz3.js → chunk-atn06ncf.js} +0 -0
  235. /package/dist/cli/{chunk-h4n8ffqy.js → chunk-dmzg9zdc.js} +0 -0
  236. /package/dist/cli/{chunk-w25b7xxz.js → chunk-etfh8f4z.js} +0 -0
  237. /package/dist/cli/{chunk-hape5vwj.js → chunk-hpay0ax8.js} +0 -0
  238. /package/dist/cli/{chunk-f10n6063.js → chunk-pn5ygqn6.js} +0 -0
  239. /package/dist/cli/{chunk-rcyasw34.js → chunk-rxqdw6s5.js} +0 -0
  240. /package/dist/cli/{chunk-exfyqsdd.js → chunk-vjvkkrzc.js} +0 -0
@@ -1,7 +1,20 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
- // ../kobe-daemon/src/daemon/log-rotate.ts
4
+ // ../rove-daemon/src/daemon/pty-host-node-entry.ts
5
+ import { join as join9 } from "node:path";
6
+
7
+ // ../rove-daemon/src/compat-env.ts
8
+ var ROVE_ENV_PREFIX = "ROVE_";
9
+ var ROVE_STATE_DIR_BASENAME = ".rove";
10
+ function readRoveEnv(suffix, env = process.env) {
11
+ return env[`${ROVE_ENV_PREFIX}${suffix}`]?.trim() || undefined;
12
+ }
13
+ function readRoveHomeDirEnv(env = process.env) {
14
+ return readRoveEnv("HOME_DIR", env);
15
+ }
16
+
17
+ // ../rove-daemon/src/daemon/log-rotate.ts
5
18
  import { existsSync, renameSync, statSync } from "node:fs";
6
19
  var DEFAULT_LOG_ROTATE_CAP_BYTES = 10 * 1024 * 1024;
7
20
  function shouldRotateLog(sizeBytes, capBytes = DEFAULT_LOG_ROTATE_CAP_BYTES) {
@@ -18,32 +31,35 @@ function rotateLogIfNeeded(path, capBytes = DEFAULT_LOG_ROTATE_CAP_BYTES) {
18
31
  } catch {}
19
32
  }
20
33
 
21
- // ../kobe-daemon/src/daemon/paths.ts
34
+ // ../rove-daemon/src/daemon/paths.ts
22
35
  import { createHash } from "node:crypto";
23
36
  import { existsSync as existsSync2, readFileSync } from "node:fs";
24
37
  import { homedir, tmpdir } from "node:os";
25
- import { join } from "node:path";
38
+ import { join as join2 } from "node:path";
26
39
 
27
- // ../kobe-daemon/src/compat-env.ts
28
- var ROVE_ENV_PREFIX = "ROVE_";
29
- var LEGACY_KOBE_ENV_PREFIX = "KOBE_";
30
- var ROVE_STATE_DIR_BASENAME = ".rove";
31
- var COMPAT_STATE_DIR_BASENAME = ".kobe";
32
- function readRoveEnv(suffix, env = process.env) {
33
- for (const key of [`${ROVE_ENV_PREFIX}${suffix}`, `${LEGACY_KOBE_ENV_PREFIX}${suffix}`]) {
34
- const value = env[key]?.trim();
35
- if (value)
36
- return value;
37
- }
38
- return;
40
+ // ../rove-daemon/src/daemon/pre-rename-runtime.ts
41
+ import { basename, dirname, join } from "node:path";
42
+ var LEGACY_PRE_RENAME_RUNTIME_NAME = "kobe";
43
+ function preRenameStateDir(home) {
44
+ return join(home, `.${LEGACY_PRE_RENAME_RUNTIME_NAME}`);
39
45
  }
40
- function readRoveHomeDirEnv(env = process.env) {
41
- return readRoveEnv("HOME_DIR", env);
46
+ function preRenameRuntimePaths(canonical) {
47
+ const name = basename(canonical);
48
+ if (canonical.startsWith("\\\\.\\pipe\\rove-") && canonical.endsWith("-pty")) {
49
+ return [canonical.replace("\\pipe\\rove-", `\\pipe\\${LEGACY_PRE_RENAME_RUNTIME_NAME}-`)];
50
+ }
51
+ if (/^rove(?:-[a-f0-9]{8})?-(?:pty|daemon)(?:-\d+)?\.sock$/.test(name) || name === "rove.sock") {
52
+ return [join(dirname(canonical), name.replace(/^rove/, LEGACY_PRE_RENAME_RUNTIME_NAME))];
53
+ }
54
+ if (["pty.sock", "pty.pid", "daemon.sock", "daemon.pid"].includes(name) && basename(dirname(canonical)) === ".rove") {
55
+ return [join(preRenameStateDir(dirname(dirname(canonical))), name)];
56
+ }
57
+ return [];
42
58
  }
43
59
 
44
- // ../kobe-daemon/src/daemon/paths.ts
60
+ // ../rove-daemon/src/daemon/paths.ts
45
61
  function stateDirs(homeDir) {
46
- return { canonical: join(homeDir, ROVE_STATE_DIR_BASENAME), legacy: join(homeDir, COMPAT_STATE_DIR_BASENAME) };
62
+ return { canonical: join2(homeDir, ROVE_STATE_DIR_BASENAME), legacy: preRenameStateDir(homeDir) };
47
63
  }
48
64
  function pidIsLive(pidPath) {
49
65
  try {
@@ -58,25 +74,16 @@ function pidIsLive(pidPath) {
58
74
  }
59
75
  function runtimePath(homeDir, name, pidName) {
60
76
  const { canonical, legacy } = stateDirs(homeDir);
61
- const canonicalPath = join(canonical, name);
62
- if (existsSync2(canonicalPath))
77
+ const canonicalPath = join2(canonical, name);
78
+ if (pidIsLive(join2(canonical, pidName)))
63
79
  return canonicalPath;
64
- const legacyPath = join(legacy, name);
65
- if (existsSync2(legacyPath) && pidIsLive(join(legacy, pidName)))
80
+ const legacyPath = join2(legacy, name);
81
+ if (existsSync2(legacyPath) && pidIsLive(join2(legacy, pidName)))
66
82
  return legacyPath;
67
83
  return canonicalPath;
68
84
  }
69
- function legacyRuntimePath(homeDir, name) {
70
- return join(homeDir, COMPAT_STATE_DIR_BASENAME, name);
71
- }
72
- function legacyPtyHostSocketPath(homeDir) {
73
- return legacyRuntimePath(homeDir, "pty.sock");
74
- }
75
- function legacyPtyHostPidPath(homeDir) {
76
- return legacyRuntimePath(homeDir, "pty.pid");
77
- }
78
85
  function runtimeDataPath(homeDir, name) {
79
- return join(stateDirs(homeDir).canonical, name);
86
+ return join2(stateDirs(homeDir).canonical, name);
80
87
  }
81
88
  var SOCKET_PATH_SAFETY_LIMIT = 100;
82
89
  function shortHomeTag(homeDir) {
@@ -87,20 +94,38 @@ function fitSocketPath(naturalPath, homeDir, role, pidTag) {
87
94
  return naturalPath;
88
95
  const tag = shortHomeTag(homeDir);
89
96
  const suffix = pidTag === undefined ? "" : `-${pidTag}`;
90
- const fallback = join(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
97
+ const fallback = join2(tmpdir(), `rove-${tag}-${role}${suffix}.sock`);
91
98
  if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
92
99
  return fallback;
93
100
  throw new Error(`daemon socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
94
101
  }
102
+ function defaultDaemonSocketPath(homeDir) {
103
+ const override = readRoveEnv("DAEMON_SOCKET_PATH");
104
+ if (override)
105
+ return override;
106
+ const explicit = homeDir ?? readRoveHomeDirEnv();
107
+ if (explicit && explicit.length > 0) {
108
+ return fitSocketPath(join2(explicit, ROVE_STATE_DIR_BASENAME, "daemon.sock"), explicit, "daemon");
109
+ }
110
+ const runtimeDir = process.env.XDG_RUNTIME_DIR;
111
+ if (runtimeDir && runtimeDir.length > 0) {
112
+ return fitSocketPath(join2(runtimeDir, "rove.sock"), runtimeDir, "daemon");
113
+ }
114
+ const home = homedir();
115
+ return fitSocketPath(join2(home, ROVE_STATE_DIR_BASENAME, "daemon.sock"), home, "daemon");
116
+ }
95
117
  function resolveDaemonHomeDir(homeDir) {
96
118
  const explicit = homeDir ?? readRoveHomeDirEnv();
97
119
  return explicit && explicit.length > 0 ? explicit : homedir();
98
120
  }
121
+ function defaultClientLogPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
122
+ return join2(homeDir, ROVE_STATE_DIR_BASENAME, "client.log");
123
+ }
99
124
  function isWindowsPipePath(path) {
100
125
  return path.startsWith("\\\\.\\pipe\\") || path.startsWith("//./pipe/");
101
126
  }
102
127
  function windowsPipePath(homeDir, role) {
103
- return `\\\\.\\pipe\\kobe-${shortHomeTag(homeDir)}-${role}`;
128
+ return `\\\\.\\pipe\\rove-${shortHomeTag(homeDir)}-${role}`;
104
129
  }
105
130
  function defaultPtyHostSocketPath(homeDir, platform = process.platform) {
106
131
  const override = readRoveEnv("PTY_SOCKET_PATH");
@@ -110,14 +135,14 @@ function defaultPtyHostSocketPath(homeDir, platform = process.platform) {
110
135
  if (platform === "win32")
111
136
  return windowsPipePath(explicit || homedir(), "pty");
112
137
  if (explicit && explicit.length > 0) {
113
- return fitSocketPath(runtimePath(explicit, "pty.sock", "pty.pid"), explicit, "pty");
138
+ return fitSocketPath(join2(explicit, ROVE_STATE_DIR_BASENAME, "pty.sock"), explicit, "pty");
114
139
  }
115
140
  const runtimeDir = process.env.XDG_RUNTIME_DIR;
116
141
  if (runtimeDir && runtimeDir.length > 0) {
117
- return fitSocketPath(join(runtimeDir, "kobe-pty.sock"), runtimeDir, "pty");
142
+ return fitSocketPath(join2(runtimeDir, "rove-pty.sock"), runtimeDir, "pty");
118
143
  }
119
144
  const home = homedir();
120
- return fitSocketPath(runtimePath(home, "pty.sock", "pty.pid"), home, "pty");
145
+ return fitSocketPath(join2(home, ROVE_STATE_DIR_BASENAME, "pty.sock"), home, "pty");
121
146
  }
122
147
  function defaultPtyHostPidPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
123
148
  const override = readRoveEnv("PTY_PID_PATH");
@@ -126,7 +151,7 @@ function defaultPtyHostPidPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
126
151
  return runtimePath(homeDir, "pty.pid", "pty.pid");
127
152
  }
128
153
  function defaultPtyHostLogPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
129
- return join(homeDir, ROVE_STATE_DIR_BASENAME, "pty.log");
154
+ return join2(homeDir, ROVE_STATE_DIR_BASENAME, "pty.log");
130
155
  }
131
156
  function defaultPtyExitsPath(homeDir = readRoveHomeDirEnv() ?? homedir()) {
132
157
  return runtimeDataPath(homeDir, "pty-exits.json");
@@ -135,10 +160,10 @@ function defaultPtyFreezeDir(homeDir = readRoveHomeDirEnv() ?? homedir()) {
135
160
  return runtimeDataPath(homeDir, "pty-sessions");
136
161
  }
137
162
 
138
- // ../kobe-daemon/src/daemon/process-tree.ts
163
+ // ../rove-daemon/src/daemon/process-tree.ts
139
164
  import { execFile } from "node:child_process";
140
165
  import { existsSync as existsSync3 } from "node:fs";
141
- import { dirname, join as join2 } from "node:path";
166
+ import { dirname as dirname2, join as join3 } from "node:path";
142
167
  var TASKKILL_TIMEOUT_MS = 5000;
143
168
  var MSYS_PS_TIMEOUT_MS = 5000;
144
169
  function parseMsysPs(output) {
@@ -183,8 +208,8 @@ function msysDescendantWinpids(rows, rootWinpid) {
183
208
  function msysPsFor(shellFile) {
184
209
  if (!shellFile || !/bash(\.exe)?$/i.test(shellFile))
185
210
  return null;
186
- const dir = dirname(shellFile);
187
- for (const candidate of [join2(dir, "ps.exe"), join2(dir, "..", "usr", "bin", "ps.exe")]) {
211
+ const dir = dirname2(shellFile);
212
+ for (const candidate of [join3(dir, "ps.exe"), join3(dir, "..", "usr", "bin", "ps.exe")]) {
188
213
  if (existsSync3(candidate))
189
214
  return candidate;
190
215
  }
@@ -210,7 +235,174 @@ async function taskkillProcessTree(pid, shellFile) {
210
235
  });
211
236
  }
212
237
 
213
- // ../kobe-daemon/src/daemon/pty-driver.ts
238
+ // ../rove-daemon/src/daemon/win-pty-job.ts
239
+ import { execFile as execFile2 } from "node:child_process";
240
+ import { createHash as createHash2, randomUUID } from "node:crypto";
241
+ import { existsSync as existsSync4, mkdirSync, renameSync as renameSync2, rmSync, writeFileSync } from "node:fs";
242
+ import { join as join4, resolve } from "node:path";
243
+ var PTY_JOB_ENV = "ROVE_PTY_JOB";
244
+ var PTY_JOB_OWNER_ENV = "ROVE_PTY_JOB_OWNER";
245
+ var PTY_JOB_LAUNCHER_CS = `
246
+ using System;
247
+ using System.Runtime.InteropServices;
248
+ using System.Text;
249
+ static class RovePtyJob {
250
+ [StructLayout(LayoutKind.Sequential)] struct BASIC { public long PerProcessUserTimeLimit, PerJobUserTimeLimit; public uint LimitFlags; public UIntPtr MinimumWorkingSetSize, MaximumWorkingSetSize; public uint ActiveProcessLimit; public UIntPtr Affinity; public uint PriorityClass, SchedulingClass; }
251
+ [StructLayout(LayoutKind.Sequential)] struct IO { public ulong R, W, O, RB, WB, OB; }
252
+ [StructLayout(LayoutKind.Sequential)] struct EXT { public BASIC Basic; public IO Io; public UIntPtr ProcessMemoryLimit, JobMemoryLimit, PeakProcessMemoryUsed, PeakJobMemoryUsed; }
253
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] struct SI { public int cb; public string lpReserved, lpDesktop, lpTitle; public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags; public short wShowWindow, cbReserved2; public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError; }
254
+ [StructLayout(LayoutKind.Sequential)] struct PI { public IntPtr hProcess, hThread; public int dwProcessId, dwThreadId; }
255
+ delegate bool CtrlHandler(uint type);
256
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern IntPtr CreateJobObject(IntPtr sa, string name);
257
+ [DllImport("kernel32.dll", SetLastError=true)] static extern bool SetInformationJobObject(IntPtr job, int cls, ref EXT info, int len);
258
+ [DllImport("kernel32.dll", SetLastError=true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr proc);
259
+ [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
260
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] static extern IntPtr GetCommandLine();
261
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern bool CreateProcess(string app, StringBuilder cmd, IntPtr pa, IntPtr ta, bool inherit, uint flags, IntPtr env, string cwd, ref SI si, out PI pi);
262
+ [DllImport("kernel32.dll")] static extern uint WaitForSingleObject(IntPtr h, uint ms);
263
+ [DllImport("kernel32.dll")] static extern bool GetExitCodeProcess(IntPtr h, out uint code);
264
+ [DllImport("kernel32.dll")] static extern bool SetConsoleCtrlHandler(CtrlHandler h, bool add);
265
+ [DllImport("kernel32.dll")] static extern bool SetProcessWorkingSetSize(IntPtr p, IntPtr min, IntPtr max);
266
+ static CtrlHandler keep;
267
+ static int SkipToken(string s, int i) {
268
+ if (i < s.Length && s[i] == '"') { int end = s.IndexOf('"', i + 1); return end < 0 ? s.Length : end + 1; }
269
+ while (i < s.Length && s[i] != ' ' && s[i] != '\\t') i++;
270
+ return i;
271
+ }
272
+ static int SkipSpace(string s, int i) { while (i < s.Length && (s[i] == ' ' || s[i] == '\\t')) i++; return i; }
273
+ static int Main() {
274
+ string raw = Marshal.PtrToStringUni(GetCommandLine());
275
+ string rest = raw.Substring(SkipSpace(raw, SkipToken(raw, 0)));
276
+ if (rest.Length == 0) { Console.Error.WriteLine("rove-pty-job: usage: <command line> (job name in ${PTY_JOB_ENV})"); return 2; }
277
+ string name = Environment.GetEnvironmentVariable("${PTY_JOB_ENV}");
278
+ IntPtr job = String.IsNullOrEmpty(name) ? IntPtr.Zero : CreateJobObject(IntPtr.Zero, name);
279
+ if (job != IntPtr.Zero) {
280
+ EXT info = new EXT();
281
+ info.Basic.LimitFlags = 0x2000; // KILL_ON_JOB_CLOSE; no BREAKAWAY_OK (see module doc)
282
+ SetInformationJobObject(job, 9, ref info, Marshal.SizeOf(typeof(EXT)));
283
+ AssignProcessToJobObject(job, GetCurrentProcess());
284
+ }
285
+ keep = delegate (uint type) { return type <= 1; }; // CTRL_C, CTRL_BREAK: the shell's to handle
286
+ SetConsoleCtrlHandler(keep, true);
287
+ SI si = new SI(); si.cb = Marshal.SizeOf(typeof(SI));
288
+ PI pi;
289
+ if (!CreateProcess(null, new StringBuilder(rest), IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref si, out pi)) {
290
+ Console.Error.WriteLine("rove-pty-job: could not start " + rest + " (error " + Marshal.GetLastWin32Error() + ")");
291
+ return 1;
292
+ }
293
+ // From here it only waits: hand its pages back (14 MB resident → 2 MB per tab, measured).
294
+ SetProcessWorkingSetSize(GetCurrentProcess(), (IntPtr)(-1), (IntPtr)(-1));
295
+ WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
296
+ uint code;
297
+ GetExitCodeProcess(pi.hProcess, out code);
298
+ GC.KeepAlive(keep);
299
+ return (int)code;
300
+ }
301
+ }
302
+ `;
303
+ var COMPILE_TIMEOUT_MS = 30000;
304
+ var SELF_TEST_TIMEOUT_MS = 1e4;
305
+ var SELF_TEST_EXIT_CODE = 7;
306
+ function run(file, args, timeout, env) {
307
+ return new Promise((done) => {
308
+ try {
309
+ const child = execFile2(file, [...args], { windowsHide: true, timeout, env }, (err, stdout, stderr) => {
310
+ const output = `${stdout}${stderr}`.trim() || err?.message || "";
311
+ done({ code: child.exitCode, output });
312
+ });
313
+ } catch (err) {
314
+ done({ code: null, output: err.message });
315
+ }
316
+ });
317
+ }
318
+ function findCsc(env = process.env) {
319
+ const root = env.SystemRoot || env.windir || "C:\\Windows";
320
+ for (const framework of ["Framework64", "Framework"]) {
321
+ const csc = join4(root, "Microsoft.NET", framework, "v4.0.30319", "csc.exe");
322
+ if (existsSync4(csc))
323
+ return csc;
324
+ }
325
+ return null;
326
+ }
327
+ function launcherFileName(source = PTY_JOB_LAUNCHER_CS) {
328
+ return `rove-pty-job-${createHash2("sha256").update(source).digest("hex").slice(0, 12)}.exe`;
329
+ }
330
+ function newPtyJobName() {
331
+ return `Local\\rove-pty-${randomUUID()}`;
332
+ }
333
+ async function compileLauncher(binDir, exe) {
334
+ const csc = findCsc();
335
+ if (!csc)
336
+ return "no .NET Framework csc.exe";
337
+ mkdirSync(binDir, { recursive: true });
338
+ const tag = `${process.pid}-${randomUUID().slice(0, 8)}`;
339
+ const src = join4(binDir, `rove-pty-job-${tag}.cs`);
340
+ const tmp = join4(binDir, `rove-pty-job-${tag}.exe`);
341
+ try {
342
+ writeFileSync(src, PTY_JOB_LAUNCHER_CS);
343
+ const built = await run(csc, ["/nologo", "/target:exe", "/optimize+", `/out:${tmp}`, src], COMPILE_TIMEOUT_MS);
344
+ if (built.code !== 0 || !existsSync4(tmp))
345
+ return `csc failed: ${built.output}`;
346
+ try {
347
+ renameSync2(tmp, exe);
348
+ } catch (err) {
349
+ if (!existsSync4(exe))
350
+ return `could not place ${exe}: ${err.message}`;
351
+ }
352
+ return null;
353
+ } finally {
354
+ rmSync(src, { force: true });
355
+ rmSync(tmp, { force: true });
356
+ }
357
+ }
358
+ async function selfTest(exe) {
359
+ const comspec = process.env.ComSpec || "cmd.exe";
360
+ const probe = await run(exe, [comspec, "/d", "/c", `exit ${SELF_TEST_EXIT_CODE}`], SELF_TEST_TIMEOUT_MS, {
361
+ ...process.env,
362
+ [PTY_JOB_ENV]: newPtyJobName()
363
+ });
364
+ return probe.code === SELF_TEST_EXIT_CODE ? null : `self-test exited ${probe.code}: ${probe.output}`;
365
+ }
366
+ async function ensurePtyJobLauncher(binDir) {
367
+ try {
368
+ const exe = join4(binDir, launcherFileName());
369
+ let cachedFailure = null;
370
+ if (existsSync4(exe)) {
371
+ cachedFailure = await selfTest(exe);
372
+ if (cachedFailure === null)
373
+ return { path: exe };
374
+ rmSync(exe, { force: true });
375
+ }
376
+ const compileFailure = await compileLauncher(binDir, exe);
377
+ if (compileFailure)
378
+ return { path: null, reason: compileFailure };
379
+ const failure = await selfTest(exe);
380
+ if (failure === null)
381
+ return { path: exe };
382
+ return { path: null, reason: cachedFailure ? `${failure} (rebuilt after: ${cachedFailure})` : failure };
383
+ } catch (err) {
384
+ return { path: null, reason: `launcher setup failed: ${err.message}` };
385
+ }
386
+ }
387
+ function sameSocket(a, b) {
388
+ const norm = (p) => resolve(p.replace(/\//g, "\\")).toLowerCase();
389
+ return norm(a) === norm(b);
390
+ }
391
+ function withoutPtyJob(env) {
392
+ const out = { ...env };
393
+ delete out[PTY_JOB_ENV];
394
+ delete out[PTY_JOB_OWNER_ENV];
395
+ return out;
396
+ }
397
+ function ptyJobLaunch(env, ownDaemonSocket) {
398
+ const job = env[PTY_JOB_ENV];
399
+ const owner = env[PTY_JOB_OWNER_ENV];
400
+ if (!job || !owner)
401
+ return null;
402
+ return { job, mode: sameSocket(owner, ownDaemonSocket) ? "escape" : "join" };
403
+ }
404
+
405
+ // ../rove-daemon/src/daemon/pty-driver.ts
214
406
  var TERMINAL_NAME = "xterm-256color";
215
407
  function bunTerminalDriver(spawn) {
216
408
  const spawnTerminal = spawn ?? ((argv, options) => Bun.spawn(argv, options));
@@ -235,20 +427,26 @@ function bunTerminalDriver(spawn) {
235
427
  };
236
428
  };
237
429
  }
238
- async function nodePtyDriver(spawn, endTree = taskkillProcessTree) {
430
+ async function nodePtyDriver(spawn, endTree = taskkillProcessTree, job) {
239
431
  const spawnPty = spawn ?? (await import("node-pty")).spawn;
240
432
  return (request) => {
241
433
  const [file, ...args] = request.argv;
242
- const child = spawnPty(file ?? "", args, {
434
+ const env = Object.fromEntries(Object.entries(request.env).filter((entry) => entry[1] !== undefined));
435
+ const [spawnFile, spawnArgs, spawnEnv] = job ? [
436
+ job.launcher,
437
+ [file ?? "", ...args],
438
+ { ...env, [PTY_JOB_ENV]: newPtyJobName(), [PTY_JOB_OWNER_ENV]: job.owner }
439
+ ] : [file ?? "", args, env];
440
+ const child = spawnPty(spawnFile, spawnArgs, {
243
441
  name: TERMINAL_NAME,
244
442
  cols: request.cols,
245
443
  rows: request.rows,
246
444
  cwd: request.cwd,
247
- env: Object.fromEntries(Object.entries(request.env).filter((entry) => entry[1] !== undefined))
445
+ env: spawnEnv
248
446
  });
249
447
  let settle = () => {};
250
- const exited = new Promise((resolve) => {
251
- settle = resolve;
448
+ const exited = new Promise((resolve2) => {
449
+ settle = resolve2;
252
450
  });
253
451
  child.onData((data) => request.onData(data));
254
452
  child.onExit(({ exitCode }) => settle({ code: exitCode, signal: null }));
@@ -264,18 +462,18 @@ async function nodePtyDriver(spawn, endTree = taskkillProcessTree) {
264
462
  };
265
463
  }
266
464
 
267
- // ../kobe-daemon/src/daemon/pty-host-log.ts
465
+ // ../rove-daemon/src/daemon/pty-host-log.ts
268
466
  function formatPtyHostLine(event, message, now = new Date) {
269
467
  return `[${now.toISOString()}] pty-host [${event}]: ${message}`;
270
468
  }
271
469
 
272
- // ../kobe-daemon/src/daemon/pty-server.ts
470
+ // ../rove-daemon/src/daemon/pty-server.ts
273
471
  import { readFileSync as readFileSync4 } from "node:fs";
274
- import { mkdir as mkdir3, unlink as unlink2 } from "node:fs/promises";
472
+ import { mkdir as mkdir4, unlink } from "node:fs/promises";
275
473
  import { createServer } from "node:net";
276
- import { dirname as dirname5 } from "node:path";
474
+ import { dirname as dirname7 } from "node:path";
277
475
 
278
- // ../kobe-daemon/src/daemon/client-writer.ts
476
+ // ../rove-daemon/src/daemon/client-writer.ts
279
477
  var DEFAULT_WRITE_HIGH_WATER_MARK = 8 * 1024 * 1024;
280
478
 
281
479
  class ClientWriter {
@@ -368,28 +566,7 @@ class ClientWriter {
368
566
  }
369
567
  }
370
568
 
371
- // ../kobe-daemon/src/daemon/compat-link.ts
372
- import { lstat, symlink, unlink } from "node:fs/promises";
373
- import { dirname as dirname2 } from "node:path";
374
- async function linkLegacyRuntimePath(canonical, legacy) {
375
- if (canonical === legacy)
376
- return false;
377
- try {
378
- if (!await lstat(dirname2(legacy)).catch(() => null))
379
- return false;
380
- const existing = await lstat(legacy).catch(() => null);
381
- if (existing && !existing.isSymbolicLink())
382
- return false;
383
- if (existing)
384
- await unlink(legacy).catch(() => {});
385
- await symlink(canonical, legacy);
386
- return true;
387
- } catch {
388
- return false;
389
- }
390
- }
391
-
392
- // ../kobe-daemon/src/daemon/crash-log.ts
569
+ // ../rove-daemon/src/daemon/crash-log.ts
393
570
  function toError(err) {
394
571
  if (err instanceof Error)
395
572
  return err;
@@ -410,19 +587,19 @@ function logDaemonError(subsystem, err) {
410
587
  process.stderr.write(formatDaemonError(subsystem, err));
411
588
  }
412
589
 
413
- // ../kobe-daemon/src/daemon/json-file.ts
414
- import { randomUUID } from "node:crypto";
590
+ // ../rove-daemon/src/daemon/json-file.ts
591
+ import { randomUUID as randomUUID2 } from "node:crypto";
415
592
  import { mkdir, rename, writeFile } from "node:fs/promises";
416
593
  import { dirname as dirname3 } from "node:path";
417
594
  async function writeTextAtomic(path, text, mode) {
418
595
  await mkdir(dirname3(path), { recursive: true });
419
- const tmp = `${path}.tmp-${process.pid}-${randomUUID()}`;
596
+ const tmp = `${path}.tmp-${process.pid}-${randomUUID2()}`;
420
597
  await writeFile(tmp, text, { encoding: "utf8", mode });
421
598
  await rename(tmp, path);
422
599
  }
423
600
  var locks = new Map;
424
601
 
425
- // ../kobe-daemon/src/daemon/line-receiver.ts
602
+ // ../rove-daemon/src/daemon/line-receiver.ts
426
603
  var MAX_REQUEST_FRAME_BYTES = 8 * 1024 * 1024;
427
604
 
428
605
  class LineReceiver {
@@ -469,10 +646,10 @@ class LineReceiver {
469
646
  }
470
647
  }
471
648
 
472
- // ../kobe-daemon/src/daemon/owner-only.ts
649
+ // ../rove-daemon/src/daemon/owner-only.ts
473
650
  import { chmodSync } from "node:fs";
474
651
  import { chmod, mkdir as mkdir2 } from "node:fs/promises";
475
- import { join as join3 } from "node:path";
652
+ import { join as join5 } from "node:path";
476
653
  var OWNER_ONLY_DIR_MODE = 448;
477
654
  var OWNER_ONLY_FILE_MODE = 384;
478
655
  async function tighten(path, mode) {
@@ -501,12 +678,12 @@ async function ensureOwnerOnlyDir(dir) {
501
678
  await tightenDirPermissions(dir);
502
679
  }
503
680
  function ensureOwnerOnlyStateDir(homeDir) {
504
- return ensureOwnerOnlyDir(join3(homeDir, ROVE_STATE_DIR_BASENAME));
681
+ return ensureOwnerOnlyDir(join5(homeDir, ROVE_STATE_DIR_BASENAME));
505
682
  }
506
683
 
507
- // ../kobe-daemon/src/daemon/automation-contracts.ts
684
+ // ../rove-daemon/src/daemon/automation-contracts.ts
508
685
  var ROUTINE_RESPONSE_WINDOW_MS = 2 * 60 * 60000;
509
- // ../kobe-plugin-sdk/dist/contract.js
686
+ // ../rove-plugin-sdk/dist/contract.js
510
687
  var DAEMON_CHANNELS = [
511
688
  "task.snapshot",
512
689
  "issue.snapshot",
@@ -532,13 +709,13 @@ var DAEMON_CHANNELS = [
532
709
  "graphics.write"
533
710
  ];
534
711
 
535
- // ../kobe-daemon/src/daemon/channels.ts
712
+ // ../rove-daemon/src/daemon/channels.ts
536
713
  var CHANNEL_NAMES = DAEMON_CHANNELS;
537
714
  var CHANNEL_NAME_SET = new Set(CHANNEL_NAMES);
538
- // ../kobe-daemon/src/daemon/protocol-compat.ts
715
+ // ../rove-daemon/src/daemon/protocol-compat.ts
539
716
  var DAEMON_PROTOCOL_VERSION = 5;
540
717
 
541
- // ../kobe-daemon/src/daemon/protocol.ts
718
+ // ../rove-daemon/src/daemon/protocol.ts
542
719
  var BLOCKING_RPCS = new Set([
543
720
  "ui.prompt",
544
721
  "task.land",
@@ -556,10 +733,10 @@ function frameToLine(frame) {
556
733
  `;
557
734
  }
558
735
 
559
- // ../kobe-daemon/src/daemon/pty-data-migration.ts
560
- import { existsSync as existsSync4, lstatSync, mkdirSync, renameSync as renameSync2, symlinkSync } from "node:fs";
736
+ // ../rove-daemon/src/daemon/pty-data-migration.ts
737
+ import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync2, renameSync as renameSync3 } from "node:fs";
561
738
  import { homedir as homedir2 } from "node:os";
562
- import { join as join4 } from "node:path";
739
+ import { join as join6 } from "node:path";
563
740
  var PTY_HOST_DATA_ENTRIES = ["pty-exits.json", "pty-sessions"];
564
741
  function lstatIfExists(path) {
565
742
  try {
@@ -569,37 +746,34 @@ function lstatIfExists(path) {
569
746
  }
570
747
  }
571
748
  function migrateLegacyPtyHostData(homeDir = readRoveHomeDirEnv() ?? homedir2()) {
572
- const canonicalDir = join4(homeDir, ROVE_STATE_DIR_BASENAME);
573
- const legacyDir = join4(homeDir, COMPAT_STATE_DIR_BASENAME);
749
+ const canonicalDir = join6(homeDir, ROVE_STATE_DIR_BASENAME);
750
+ const legacyDir = preRenameStateDir(homeDir);
574
751
  if (canonicalDir === legacyDir)
575
752
  return [];
576
753
  const moved = [];
577
754
  for (const name of PTY_HOST_DATA_ENTRIES) {
578
- const canonical = join4(canonicalDir, name);
579
- const legacy = join4(legacyDir, name);
755
+ const canonical = join6(canonicalDir, name);
756
+ const legacy = join6(legacyDir, name);
580
757
  try {
581
- if (existsSync4(canonical))
758
+ if (existsSync5(canonical))
582
759
  continue;
583
760
  const stat = lstatIfExists(legacy);
584
761
  if (!stat || stat.isSymbolicLink())
585
762
  continue;
586
- mkdirSync(canonicalDir, { recursive: true });
587
- renameSync2(legacy, canonical);
763
+ mkdirSync2(canonicalDir, { recursive: true });
764
+ renameSync3(legacy, canonical);
588
765
  moved.push(name);
589
- try {
590
- symlinkSync(canonical, legacy);
591
- } catch {}
592
766
  } catch {}
593
767
  }
594
768
  return moved;
595
769
  }
596
770
 
597
- // ../kobe-daemon/src/daemon/pty-exit-store.ts
598
- import { randomUUID as randomUUID2 } from "node:crypto";
599
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync3, writeFileSync } from "node:fs";
771
+ // ../rove-daemon/src/daemon/pty-exit-store.ts
772
+ import { randomUUID as randomUUID3 } from "node:crypto";
773
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync4, writeFileSync as writeFileSync2 } from "node:fs";
600
774
  import { dirname as dirname4 } from "node:path";
601
775
 
602
- // ../kobe-daemon/src/daemon/terminal-rows.ts
776
+ // ../rove-daemon/src/daemon/terminal-rows.ts
603
777
  var CURSOR_DOWN_RE = /\x1b\[(\d*)[BE]/g;
604
778
  var CURSOR_POSITION_RE = /\x1b\[[\d;]*[Hf]/g;
605
779
  var MAX_ROWS_PER_ESCAPE = 200;
@@ -623,7 +797,7 @@ function terminalRows(raw, maxLineChars) {
623
797
  });
624
798
  }
625
799
 
626
- // ../kobe-daemon/src/daemon/pty-exit-store.ts
800
+ // ../rove-daemon/src/daemon/pty-exit-store.ts
627
801
  var MAX_RECORDS = 50;
628
802
  var TAIL_LINES = 40;
629
803
  var TAIL_LINE_CHARS = 500;
@@ -650,6 +824,11 @@ function healTails(records) {
650
824
  }
651
825
  return out;
652
826
  }
827
+ function compareByExitAtDesc(a, b) {
828
+ if (a.at === b.at)
829
+ return 0;
830
+ return a.at < b.at ? 1 : -1;
831
+ }
653
832
  function readPtyExitStore(path = defaultPtyExitsPath()) {
654
833
  const read = (status, records = {}) => ({
655
834
  records,
@@ -693,23 +872,23 @@ function writeRecord(storeKey, record, path) {
693
872
  throw new Error(`pty-exits store is unreadable (${path}) — refusing to overwrite it`);
694
873
  const records = store.records;
695
874
  records[storeKey] = record;
696
- const newest = Object.entries(records).sort(([, a], [, b]) => a.at < b.at ? 1 : -1).slice(0, MAX_RECORDS);
697
- mkdirSync2(dirname4(path), { recursive: true });
698
- const tmp = `${path}.tmp-${process.pid}-${randomUUID2()}`;
699
- writeFileSync(tmp, JSON.stringify(Object.fromEntries(newest), null, 2), {
875
+ const newest = Object.entries(records).sort(([, a], [, b]) => compareByExitAtDesc(a, b)).slice(0, MAX_RECORDS);
876
+ mkdirSync3(dirname4(path), { recursive: true });
877
+ const tmp = `${path}.tmp-${process.pid}-${randomUUID3()}`;
878
+ writeFileSync2(tmp, JSON.stringify(Object.fromEntries(newest), null, 2), {
700
879
  encoding: "utf8",
701
880
  mode: OWNER_ONLY_FILE_MODE
702
881
  });
703
- renameSync3(tmp, path);
882
+ renameSync4(tmp, path);
704
883
  }
705
884
 
706
- // ../kobe-daemon/src/daemon/pty-freeze-store.ts
707
- import { randomUUID as randomUUID3 } from "node:crypto";
708
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, renameSync as renameSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
709
- import { join as join5 } from "node:path";
885
+ // ../rove-daemon/src/daemon/pty-freeze-store.ts
886
+ import { randomUUID as randomUUID4 } from "node:crypto";
887
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, readdirSync, renameSync as renameSync5, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
888
+ import { join as join7 } from "node:path";
710
889
  import { StringDecoder } from "node:string_decoder";
711
890
 
712
- // ../kobe-daemon/src/daemon/terminal-colors.ts
891
+ // ../rove-daemon/src/daemon/terminal-colors.ts
713
892
  var DEFAULT_TERMINAL_COLORS = {
714
893
  foreground: "#eae7df",
715
894
  background: "#141413"
@@ -758,7 +937,279 @@ function foldDefaultColorQueries(previousCarry, chunkText) {
758
937
  return { slots, carry: trailingQueryPrefix(text.slice(end)) };
759
938
  }
760
939
 
761
- // ../kobe-daemon/src/daemon/pty-freeze-store.ts
940
+ // ../rove-daemon/src/daemon/terminal-modes.ts
941
+ var DEFAULT_MODES = {
942
+ applicationCursorKeys: false,
943
+ applicationKeypad: false,
944
+ origin: false,
945
+ wraparound: true,
946
+ reverseWraparound: false,
947
+ cursorBlink: false,
948
+ cursorVisible: true,
949
+ insertMode: false,
950
+ sendFocus: false,
951
+ bracketedPaste: false,
952
+ synchronizedOutput: false,
953
+ altScreen: false,
954
+ mouseProtocol: "none",
955
+ mouseEncoding: "default"
956
+ };
957
+ var MOUSE_PROTOCOLS = { 9: "x10", 1000: "vt200", 1002: "drag", 1003: "any" };
958
+ var MOUSE_ENCODINGS = { 1006: "sgr", 1016: "sgr-pixels" };
959
+ var MAX_CARRY = 64;
960
+ var DA1_REPLY = "\x1B[?1;2c";
961
+ function freshModeTrack(modes = { ...DEFAULT_MODES }) {
962
+ return { modes, carry: "" };
963
+ }
964
+ function normalizeTerminalModes(raw) {
965
+ const modes = { ...DEFAULT_MODES };
966
+ if (typeof raw !== "object" || raw === null)
967
+ return modes;
968
+ const record = raw;
969
+ for (const key of Object.keys(DEFAULT_MODES)) {
970
+ if (typeof record[key] === "boolean" && typeof modes[key] === "boolean")
971
+ modes[key] = record[key];
972
+ }
973
+ if (Object.values(MOUSE_PROTOCOLS).includes(record.mouseProtocol))
974
+ modes.mouseProtocol = record.mouseProtocol;
975
+ if (Object.values(MOUSE_ENCODINGS).includes(record.mouseEncoding))
976
+ modes.mouseEncoding = record.mouseEncoding;
977
+ return modes;
978
+ }
979
+ function scanTerminalModes(track, chunk) {
980
+ const text = track.carry + chunk;
981
+ track.carry = "";
982
+ const replies = [];
983
+ let i = text.indexOf("\x1B");
984
+ while (i !== -1) {
985
+ if (i + 1 >= text.length) {
986
+ track.carry = "\x1B";
987
+ break;
988
+ }
989
+ const next = text.charCodeAt(i + 1);
990
+ if (next === 91) {
991
+ const end = csiEnd(text, i + 2);
992
+ if (end === -1) {
993
+ if (text.length - i <= MAX_CARRY)
994
+ track.carry = text.slice(i);
995
+ break;
996
+ }
997
+ if (end >= 0)
998
+ applyCsi(track.modes, text.slice(i + 2, end), text[end], replies);
999
+ i = text.indexOf("\x1B", end >= 0 ? end + 1 : i + 2);
1000
+ continue;
1001
+ }
1002
+ if (next === 61)
1003
+ track.modes.applicationKeypad = true;
1004
+ else if (next === 62)
1005
+ track.modes.applicationKeypad = false;
1006
+ else if (next === 99)
1007
+ track.modes = { ...DEFAULT_MODES };
1008
+ i = text.indexOf("\x1B", i + 1);
1009
+ }
1010
+ return replies;
1011
+ }
1012
+ function terminalModePreamble(modes) {
1013
+ const set = [];
1014
+ const reset = [];
1015
+ if (modes.applicationCursorKeys)
1016
+ set.push(1);
1017
+ if (modes.origin)
1018
+ set.push(6);
1019
+ if (!modes.wraparound)
1020
+ reset.push(7);
1021
+ if (modes.cursorBlink)
1022
+ set.push(12);
1023
+ if (!modes.cursorVisible)
1024
+ reset.push(25);
1025
+ if (modes.reverseWraparound)
1026
+ set.push(45);
1027
+ for (const [mode, protocol] of Object.entries(MOUSE_PROTOCOLS))
1028
+ if (modes.mouseProtocol === protocol)
1029
+ set.push(+mode);
1030
+ for (const [mode, encoding] of Object.entries(MOUSE_ENCODINGS))
1031
+ if (modes.mouseEncoding === encoding)
1032
+ set.push(+mode);
1033
+ if (modes.sendFocus)
1034
+ set.push(1004);
1035
+ if (modes.bracketedPaste)
1036
+ set.push(2004);
1037
+ let out = modes.altScreen ? "\x1B[?1049h" : "";
1038
+ if (set.length > 0)
1039
+ out += `\x1B[?${set.join(";")}h`;
1040
+ if (reset.length > 0)
1041
+ out += `\x1B[?${reset.join(";")}l`;
1042
+ if (modes.applicationKeypad)
1043
+ out += "\x1B=";
1044
+ if (modes.insertMode)
1045
+ out += "\x1B[4h";
1046
+ return out;
1047
+ }
1048
+ function csiEnd(text, from) {
1049
+ for (let j = from;j < text.length; j++) {
1050
+ const c = text.charCodeAt(j);
1051
+ if (c >= 64 && c <= 126)
1052
+ return j;
1053
+ if (c < 32 || c > 63)
1054
+ return -2;
1055
+ }
1056
+ return -1;
1057
+ }
1058
+ function applyCsi(modes, body, final, replies) {
1059
+ let start = 0;
1060
+ let prefix = "";
1061
+ const first = body.charCodeAt(0);
1062
+ if (first >= 60 && first <= 63) {
1063
+ prefix = body[0];
1064
+ start = 1;
1065
+ }
1066
+ let end = body.length;
1067
+ while (end > start && body.charCodeAt(end - 1) <= 47)
1068
+ end--;
1069
+ const intermediates = body.slice(end);
1070
+ const params = body.slice(start, end).split(";").map((param) => Number.parseInt(param.split(":")[0] ?? "", 10) || 0);
1071
+ if (intermediates === "" && (final === "h" || final === "l")) {
1072
+ const on = final === "h";
1073
+ if (prefix === "?")
1074
+ for (const mode of params)
1075
+ setPrivateMode(modes, mode, on);
1076
+ else if (prefix === "" && params.includes(4))
1077
+ modes.insertMode = on;
1078
+ } else if (intermediates === "$" && final === "p" && (prefix === "?" || prefix === "")) {
1079
+ const mode = params[0] ?? 0;
1080
+ const value = prefix === "?" ? privateModeValue(modes, mode) : ansiModeValue(modes, mode);
1081
+ replies.push(`\x1B[${prefix}${mode};${value}$y`);
1082
+ } else if (intermediates === "!" && final === "p" && prefix === "") {
1083
+ softReset(modes);
1084
+ } else if (intermediates === "" && final === "c" && prefix === "" && (params[0] ?? 0) === 0) {
1085
+ replies.push(DA1_REPLY);
1086
+ }
1087
+ }
1088
+ function setPrivateMode(modes, mode, on) {
1089
+ if (mode in MOUSE_PROTOCOLS) {
1090
+ modes.mouseProtocol = on ? MOUSE_PROTOCOLS[mode] : "none";
1091
+ return;
1092
+ }
1093
+ if (mode in MOUSE_ENCODINGS) {
1094
+ modes.mouseEncoding = on ? MOUSE_ENCODINGS[mode] : "default";
1095
+ return;
1096
+ }
1097
+ switch (mode) {
1098
+ case 1:
1099
+ modes.applicationCursorKeys = on;
1100
+ break;
1101
+ case 6:
1102
+ modes.origin = on;
1103
+ break;
1104
+ case 7:
1105
+ modes.wraparound = on;
1106
+ break;
1107
+ case 12:
1108
+ modes.cursorBlink = on;
1109
+ break;
1110
+ case 25:
1111
+ modes.cursorVisible = on;
1112
+ break;
1113
+ case 45:
1114
+ modes.reverseWraparound = on;
1115
+ break;
1116
+ case 66:
1117
+ modes.applicationKeypad = on;
1118
+ break;
1119
+ case 1004:
1120
+ modes.sendFocus = on;
1121
+ break;
1122
+ case 47:
1123
+ case 1047:
1124
+ case 1049:
1125
+ modes.altScreen = on;
1126
+ break;
1127
+ case 2004:
1128
+ modes.bracketedPaste = on;
1129
+ break;
1130
+ case 2026:
1131
+ modes.synchronizedOutput = on;
1132
+ break;
1133
+ }
1134
+ }
1135
+ function softReset(modes) {
1136
+ Object.assign(modes, {
1137
+ applicationCursorKeys: false,
1138
+ applicationKeypad: false,
1139
+ origin: false,
1140
+ wraparound: true,
1141
+ reverseWraparound: false,
1142
+ cursorVisible: true,
1143
+ insertMode: false,
1144
+ sendFocus: false,
1145
+ bracketedPaste: false,
1146
+ synchronizedOutput: false
1147
+ });
1148
+ }
1149
+ var NOT_RECOGNIZED = 0;
1150
+ var SET = 1;
1151
+ var RESET = 2;
1152
+ var PERMANENTLY_SET = 3;
1153
+ var PERMANENTLY_RESET = 4;
1154
+ var flag = (on) => on ? SET : RESET;
1155
+ function privateModeValue(modes, mode) {
1156
+ if (mode in MOUSE_PROTOCOLS)
1157
+ return flag(modes.mouseProtocol === MOUSE_PROTOCOLS[mode]);
1158
+ if (mode in MOUSE_ENCODINGS)
1159
+ return flag(modes.mouseEncoding === MOUSE_ENCODINGS[mode]);
1160
+ switch (mode) {
1161
+ case 1:
1162
+ return flag(modes.applicationCursorKeys);
1163
+ case 6:
1164
+ return flag(modes.origin);
1165
+ case 7:
1166
+ return flag(modes.wraparound);
1167
+ case 8:
1168
+ return PERMANENTLY_SET;
1169
+ case 12:
1170
+ return flag(modes.cursorBlink);
1171
+ case 25:
1172
+ return flag(modes.cursorVisible);
1173
+ case 45:
1174
+ return flag(modes.reverseWraparound);
1175
+ case 66:
1176
+ return flag(modes.applicationKeypad);
1177
+ case 67:
1178
+ case 1005:
1179
+ case 1015:
1180
+ return PERMANENTLY_RESET;
1181
+ case 1004:
1182
+ return flag(modes.sendFocus);
1183
+ case 1048:
1184
+ return SET;
1185
+ case 47:
1186
+ case 1047:
1187
+ case 1049:
1188
+ return flag(modes.altScreen);
1189
+ case 2004:
1190
+ return flag(modes.bracketedPaste);
1191
+ case 2026:
1192
+ return flag(modes.synchronizedOutput);
1193
+ default:
1194
+ return NOT_RECOGNIZED;
1195
+ }
1196
+ }
1197
+ function ansiModeValue(modes, mode) {
1198
+ switch (mode) {
1199
+ case 2:
1200
+ return PERMANENTLY_RESET;
1201
+ case 4:
1202
+ return flag(modes.insertMode);
1203
+ case 12:
1204
+ return PERMANENTLY_SET;
1205
+ case 20:
1206
+ return RESET;
1207
+ default:
1208
+ return NOT_RECOGNIZED;
1209
+ }
1210
+ }
1211
+
1212
+ // ../rove-daemon/src/daemon/pty-freeze-store.ts
762
1213
  var FREEZE_VERSION = 1;
763
1214
  function freezeSession(session, now = new Date) {
764
1215
  return {
@@ -772,6 +1223,7 @@ function freezeSession(session, now = new Date) {
772
1223
  totalBytes: session.totalBytes,
773
1224
  exit: session.exit,
774
1225
  ringB64: Buffer.concat(session.chunks).toString("base64"),
1226
+ ...session.ringModes ? { ringModes: session.ringModes.modes } : {},
775
1227
  updatedAt: now.toISOString()
776
1228
  };
777
1229
  }
@@ -782,16 +1234,23 @@ function thawRing(record, cap) {
782
1234
  } catch {
783
1235
  return null;
784
1236
  }
785
- const trimmed = ring.byteLength > cap ? ring.subarray(ring.byteLength - cap) : ring;
786
- return { chunks: trimmed.byteLength > 0 ? [trimmed] : [], bytes: trimmed.byteLength };
1237
+ const cut = Math.max(0, ring.byteLength - cap);
1238
+ const trimmed = ring.subarray(cut);
1239
+ const ringModes = freshModeTrack(normalizeTerminalModes(record.ringModes));
1240
+ if (cut > 0)
1241
+ scanTerminalModes(ringModes, ring.subarray(0, cut).toString("latin1"));
1242
+ return { chunks: trimmed.byteLength > 0 ? [trimmed] : [], bytes: trimmed.byteLength, ringModes };
787
1243
  }
788
1244
  function thawSession(record, cap) {
789
1245
  const ring = thawRing(record, cap);
790
1246
  if (!ring)
791
1247
  return null;
1248
+ const modes = freshModeTrack({ ...ring.ringModes.modes });
1249
+ for (const chunk of ring.chunks)
1250
+ scanTerminalModes(modes, chunk.toString("latin1"));
792
1251
  return {
793
1252
  key: record.key,
794
- generation: randomUUID3(),
1253
+ generation: randomUUID4(),
795
1254
  cwd: record.cwd,
796
1255
  proc: null,
797
1256
  alive: false,
@@ -806,7 +1265,10 @@ function thawSession(record, cap) {
806
1265
  titleDecoder: new StringDecoder("utf8"),
807
1266
  colorQueryCarry: "",
808
1267
  defaultColors: DEFAULT_TERMINAL_COLORS,
1268
+ modes,
1269
+ ringModes: ring.ringModes,
809
1270
  sinks: new Map,
1271
+ emulatorSinks: new Set,
810
1272
  parked: false,
811
1273
  parkedScreenBytes: 0,
812
1274
  exit: record.exit,
@@ -815,7 +1277,7 @@ function thawSession(record, cap) {
815
1277
  };
816
1278
  }
817
1279
  function recordFile(dir, key) {
818
- return join5(dir, `${encodeURIComponent(key)}.json`);
1280
+ return join7(dir, `${encodeURIComponent(key)}.json`);
819
1281
  }
820
1282
  function parseRecord(raw) {
821
1283
  try {
@@ -853,7 +1315,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
853
1315
  if (!name.endsWith(".json"))
854
1316
  continue;
855
1317
  try {
856
- const st = statSync2(join5(dir, name));
1318
+ const st = statSync2(join7(dir, name));
857
1319
  entries.push({ name, size: st.size, mtimeMs: st.mtimeMs });
858
1320
  } catch {}
859
1321
  }
@@ -874,7 +1336,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
874
1336
  }
875
1337
  let record = null;
876
1338
  try {
877
- record = parseRecord(readFileSync3(join5(dir, entry.name), "utf8"));
1339
+ record = parseRecord(readFileSync3(join7(dir, entry.name), "utf8"));
878
1340
  } catch {}
879
1341
  bytesRead += entry.size;
880
1342
  if (!record) {
@@ -888,7 +1350,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
888
1350
  }
889
1351
  for (const name of stale) {
890
1352
  try {
891
- rmSync(join5(dir, name), { force: true });
1353
+ rmSync2(join7(dir, name), { force: true });
892
1354
  } catch {}
893
1355
  }
894
1356
  kept.sort((a, b) => updatedAtMs(b) - updatedAtMs(a));
@@ -905,7 +1367,7 @@ function tightenExistingPermissions(dir) {
905
1367
  }
906
1368
  for (const name of names) {
907
1369
  if (name.endsWith(".json"))
908
- tightenFilePermissionsSync(join5(dir, name));
1370
+ tightenFilePermissionsSync(join7(dir, name));
909
1371
  }
910
1372
  }
911
1373
  function fileFreezeSink(dir = defaultPtyFreezeDir()) {
@@ -913,31 +1375,31 @@ function fileFreezeSink(dir = defaultPtyFreezeDir()) {
913
1375
  return {
914
1376
  save(record) {
915
1377
  try {
916
- mkdirSync3(dir, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
1378
+ mkdirSync4(dir, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
917
1379
  const target = recordFile(dir, record.key);
918
1380
  const staging = `${target}.${process.pid}.tmp`;
919
- writeFileSync2(staging, JSON.stringify(record), { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
920
- renameSync4(staging, target);
1381
+ writeFileSync3(staging, JSON.stringify(record), { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
1382
+ renameSync5(staging, target);
921
1383
  } catch {}
922
1384
  },
923
1385
  drop(key) {
924
1386
  try {
925
- rmSync(recordFile(dir, key), { force: true });
1387
+ rmSync2(recordFile(dir, key), { force: true });
926
1388
  } catch {}
927
1389
  }
928
1390
  };
929
1391
  }
930
1392
  function clearFrozenSessions(dir = defaultPtyFreezeDir()) {
931
1393
  try {
932
- rmSync(dir, { recursive: true, force: true });
1394
+ rmSync2(dir, { recursive: true, force: true });
933
1395
  } catch {}
934
1396
  }
935
1397
 
936
- // ../kobe-daemon/src/daemon/pty-child-controller.ts
937
- import { randomUUID as randomUUID5 } from "node:crypto";
1398
+ // ../rove-daemon/src/daemon/pty-child-controller.ts
1399
+ import { randomUUID as randomUUID6 } from "node:crypto";
938
1400
 
939
- // ../kobe-daemon/src/daemon/platform-shell.js
940
- import { existsSync as existsSync5 } from "node:fs";
1401
+ // ../rove-daemon/src/daemon/platform-shell.js
1402
+ import { existsSync as existsSync6 } from "node:fs";
941
1403
  function windowsBashCandidates(env) {
942
1404
  const roots = [env.ProgramFiles, env["ProgramFiles(x86)"], env.LOCALAPPDATA && `${env.LOCALAPPDATA}\\Programs`];
943
1405
  return roots.filter(Boolean).map((root) => `${root}\\Git\\bin\\bash.exe`);
@@ -956,7 +1418,7 @@ function resolveLoginShell(options = {}) {
956
1418
  const fallback = options.fallback ?? "/bin/bash";
957
1419
  const platform = options.platform ?? process.platform;
958
1420
  const env = options.env ?? process.env;
959
- const exists = options.exists ?? existsSync5;
1421
+ const exists = options.exists ?? existsSync6;
960
1422
  const cacheable = platform === process.platform && env === process.env && options.exists === undefined;
961
1423
  if (cacheable) {
962
1424
  const hit = cache.get(fallback);
@@ -981,7 +1443,7 @@ function resolveUncached(fallback, platform, env, exists) {
981
1443
  return WINDOWS_BASH_FALLBACK;
982
1444
  }
983
1445
 
984
- // ../kobe-daemon/src/daemon/pty-env.js
1446
+ // ../rove-daemon/src/daemon/pty-env.js
985
1447
  var IDENTITY_VARS = new Set([
986
1448
  "TERM_PROGRAM",
987
1449
  "TERM_PROGRAM_VERSION",
@@ -1012,13 +1474,13 @@ function embeddedTerminalEnv(base, overrides = {}) {
1012
1474
  return { ...env, ...overrides };
1013
1475
  }
1014
1476
 
1015
- // ../kobe-daemon/src/daemon/pty-host-types.ts
1016
- import { randomUUID as randomUUID4 } from "node:crypto";
1477
+ // ../rove-daemon/src/daemon/pty-host-types.ts
1478
+ import { randomUUID as randomUUID5 } from "node:crypto";
1017
1479
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
1018
1480
  function freshSessionState(key, spec, argv) {
1019
1481
  return {
1020
1482
  key,
1021
- generation: randomUUID4(),
1483
+ generation: randomUUID5(),
1022
1484
  cwd: spec.cwd,
1023
1485
  proc: null,
1024
1486
  alive: true,
@@ -1033,7 +1495,10 @@ function freshSessionState(key, spec, argv) {
1033
1495
  titleDecoder: new StringDecoder2("utf8"),
1034
1496
  colorQueryCarry: "",
1035
1497
  defaultColors: parseTerminalDefaultColors(spec.defaultColors) ?? DEFAULT_TERMINAL_COLORS,
1498
+ modes: freshModeTrack(),
1499
+ ringModes: freshModeTrack(),
1036
1500
  sinks: new Map,
1501
+ emulatorSinks: new Set,
1037
1502
  parked: false,
1038
1503
  parkedScreenBytes: 0,
1039
1504
  exit: null,
@@ -1043,7 +1508,7 @@ function freshSessionState(key, spec, argv) {
1043
1508
  };
1044
1509
  }
1045
1510
 
1046
- // ../kobe-daemon/src/daemon/pty-observability.ts
1511
+ // ../rove-daemon/src/daemon/pty-observability.ts
1047
1512
  function sessionInfo(s) {
1048
1513
  return {
1049
1514
  key: s.key,
@@ -1146,13 +1611,13 @@ function peekRing(session, sinceOffset) {
1146
1611
  };
1147
1612
  }
1148
1613
 
1149
- // ../kobe-daemon/src/daemon/pty-termination.ts
1614
+ // ../rove-daemon/src/daemon/pty-termination.ts
1150
1615
  async function settledWithin(exited, ms) {
1151
1616
  let timer;
1152
1617
  const settled = await Promise.race([
1153
1618
  exited.then(() => true, () => true),
1154
- new Promise((resolve) => {
1155
- timer = setTimeout(() => resolve(false), ms);
1619
+ new Promise((resolve2) => {
1620
+ timer = setTimeout(() => resolve2(false), ms);
1156
1621
  })
1157
1622
  ]);
1158
1623
  if (timer)
@@ -1189,7 +1654,7 @@ async function terminatePtyChild(proc, onSettled, onSignal) {
1189
1654
  onSettled();
1190
1655
  }
1191
1656
 
1192
- // ../kobe-daemon/src/daemon/pty-child-controller.ts
1657
+ // ../rove-daemon/src/daemon/pty-child-controller.ts
1193
1658
  class PtyChildController {
1194
1659
  deps;
1195
1660
  constructor(deps) {
@@ -1206,7 +1671,7 @@ class PtyChildController {
1206
1671
  return session;
1207
1672
  }
1208
1673
  startChild(session) {
1209
- session.generation = randomUUID5();
1674
+ session.generation = randomUUID6();
1210
1675
  try {
1211
1676
  session.proc = (this.deps.driver ?? bunTerminalDriver())({
1212
1677
  argv: [...session.command],
@@ -1216,7 +1681,7 @@ class PtyChildController {
1216
1681
  COLUMNS: String(session.cols),
1217
1682
  LINES: String(session.rows),
1218
1683
  BASH_SILENCE_DEPRECATION_WARNING: "1",
1219
- KOBE_TERMINAL_PTY: "1"
1684
+ ROVE_TERMINAL_PTY: "1"
1220
1685
  }),
1221
1686
  cols: session.cols,
1222
1687
  rows: session.rows,
@@ -1256,11 +1721,16 @@ class PtyChildController {
1256
1721
  }
1257
1722
  onData(session, data) {
1258
1723
  const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
1259
- const colorQueries = foldDefaultColorQueries(session.colorQueryCarry, buf.toString("latin1"));
1724
+ const text = buf.toString("latin1");
1725
+ const colorQueries = foldDefaultColorQueries(session.colorQueryCarry, text);
1260
1726
  session.colorQueryCarry = colorQueries.carry;
1261
- for (const slot of colorQueries.slots) {
1727
+ const replies = colorQueries.slots.map((slot) => formatDefaultColorReply(slot, session.defaultColors));
1728
+ const modeReplies = scanTerminalModes(session.modes, text);
1729
+ if (session.emulatorSinks.size === 0)
1730
+ replies.push(...modeReplies);
1731
+ if (replies.length > 0) {
1262
1732
  try {
1263
- session.proc?.write(formatDefaultColorReply(slot, session.defaultColors));
1733
+ session.proc?.write(replies.join(""));
1264
1734
  } catch {}
1265
1735
  }
1266
1736
  scanOscTitle(session, buf);
@@ -1269,14 +1739,16 @@ class PtyChildController {
1269
1739
  session.totalBytes += buf.byteLength;
1270
1740
  while (session.bytes > this.deps.scrollbackCap && session.chunks.length > 1) {
1271
1741
  const dropped = session.chunks.shift();
1272
- if (dropped)
1273
- session.bytes -= dropped.byteLength;
1742
+ if (!dropped)
1743
+ break;
1744
+ session.bytes -= dropped.byteLength;
1745
+ scanTerminalModes(session.ringModes, dropped.toString("latin1"));
1274
1746
  }
1275
1747
  this.deps.onOutput?.(session, buf);
1276
1748
  }
1277
1749
  }
1278
1750
 
1279
- // ../kobe-daemon/src/daemon/pty-freeze-policy.ts
1751
+ // ../rove-daemon/src/daemon/pty-freeze-policy.ts
1280
1752
  var FREEZE_INTERVAL_MS = 5000;
1281
1753
  var FREEZE_MIN_APPENDED_BYTES = 64 * 1024;
1282
1754
  var FREEZE_STALE_MS = 60000;
@@ -1290,7 +1762,7 @@ function shouldFreeze(session, now) {
1290
1762
  return appended >= FREEZE_MIN_APPENDED_BYTES || since >= FREEZE_STALE_MS;
1291
1763
  }
1292
1764
 
1293
- // ../kobe-daemon/src/daemon/pty-warm.ts
1765
+ // ../rove-daemon/src/daemon/pty-warm.ts
1294
1766
  class WarmSpare {
1295
1767
  deps;
1296
1768
  spare = null;
@@ -1343,7 +1815,7 @@ class WarmSpare {
1343
1815
  }
1344
1816
  }
1345
1817
 
1346
- // ../kobe-daemon/src/daemon/pty-host.ts
1818
+ // ../rove-daemon/src/daemon/pty-host.ts
1347
1819
  var LOGGED_WRITE_BYTES = 1024;
1348
1820
  var DEFAULT_SCROLLBACK_CAP = 512 * 1024;
1349
1821
  var EXIT_TAIL_BYTES = 16 * 1024;
@@ -1412,7 +1884,7 @@ class PtyHost {
1412
1884
  onSessionStart: () => this.opts.onSessionStart?.()
1413
1885
  });
1414
1886
  }
1415
- open(key, spec, token, sink, sinceOffset, sincePid) {
1887
+ open(key, spec, token, sink, sinceOffset, sincePid, answersQueries = false) {
1416
1888
  let session = this.sessions.get(key);
1417
1889
  let created = false;
1418
1890
  let respawned = false;
@@ -1430,6 +1902,10 @@ class PtyHost {
1430
1902
  if (defaultColors)
1431
1903
  session.defaultColors = defaultColors;
1432
1904
  session.sinks.set(token, sink);
1905
+ if (answersQueries)
1906
+ session.emulatorSinks.add(token);
1907
+ else
1908
+ session.emulatorSinks.delete(token);
1433
1909
  session.parked = false;
1434
1910
  session.parkedScreenBytes = 0;
1435
1911
  const windowStart = session.totalBytes - session.bytes;
@@ -1445,6 +1921,8 @@ class PtyHost {
1445
1921
  else
1446
1922
  this.parkRestoreFallbacks++;
1447
1923
  }
1924
+ if (!sinceValid)
1925
+ replay = Buffer.concat([Buffer.from(terminalModePreamble(session.ringModes.modes), "latin1"), replay]);
1448
1926
  return {
1449
1927
  replay: replay.toString("base64"),
1450
1928
  alive: session.alive,
@@ -1516,10 +1994,12 @@ class PtyHost {
1516
1994
  if (!session)
1517
1995
  return;
1518
1996
  session.sinks.delete(token);
1997
+ session.emulatorSinks.delete(token);
1519
1998
  this.applyParkedOnDetach(session, parked, parkedScreenBytes);
1520
1999
  }
1521
2000
  detachClient(token) {
1522
2001
  for (const session of this.sessions.values()) {
2002
+ session.emulatorSinks.delete(token);
1523
2003
  if (!session.sinks.delete(token))
1524
2004
  continue;
1525
2005
  this.applyParkedOnDetach(session);
@@ -1606,7 +2086,533 @@ class PtyHost {
1606
2086
  }
1607
2087
  }
1608
2088
 
1609
- // ../kobe-daemon/src/daemon/handler-validators.ts
2089
+ // ../rove-daemon/src/client/detached-spawn.ts
2090
+ import { spawn as spawn2 } from "node:child_process";
2091
+ import { appendFileSync, closeSync, mkdirSync as mkdirSync5, openSync } from "node:fs";
2092
+ import { dirname as dirname6 } from "node:path";
2093
+
2094
+ // ../rove-daemon/src/client/index.ts
2095
+ import { connect } from "node:net";
2096
+ import { StringDecoder as StringDecoder3 } from "node:string_decoder";
2097
+
2098
+ // ../rove-daemon/src/client/client-log.ts
2099
+ import { appendFile, mkdir as mkdir3, rename as rename2, stat } from "node:fs/promises";
2100
+ import { dirname as dirname5 } from "node:path";
2101
+ var context = "client";
2102
+ function coerceError(err) {
2103
+ if (err instanceof Error)
2104
+ return { message: err.message, stack: err.stack };
2105
+ let text;
2106
+ try {
2107
+ text = typeof err === "string" ? err : JSON.stringify(err);
2108
+ } catch {
2109
+ text = String(err);
2110
+ }
2111
+ return { message: text };
2112
+ }
2113
+ function formatClientEntry(subsystem, message, now = new Date) {
2114
+ return `[${now.toISOString()}] client ${context} [${subsystem}] pid=${process.pid}: ${message}
2115
+ `;
2116
+ }
2117
+ var warnedOnce = false;
2118
+ function warnOnce() {
2119
+ if (warnedOnce)
2120
+ return;
2121
+ warnedOnce = true;
2122
+ try {
2123
+ process.stderr.write(`[rove] client log write failed; continuing without it
2124
+ `);
2125
+ } catch {}
2126
+ }
2127
+ var writeChain = Promise.resolve();
2128
+ async function rotateClientLogIfNeeded(path) {
2129
+ try {
2130
+ const { size } = await stat(path);
2131
+ if (!shouldRotateLog(size, DEFAULT_LOG_ROTATE_CAP_BYTES))
2132
+ return;
2133
+ await rename2(path, `${path}.old`);
2134
+ } catch {}
2135
+ }
2136
+ function append(line, logPath) {
2137
+ const path = logPath ?? defaultClientLogPath();
2138
+ writeChain = writeChain.then(async () => {
2139
+ await rotateClientLogIfNeeded(path);
2140
+ try {
2141
+ await appendFile(path, line);
2142
+ } catch (err) {
2143
+ if (err?.code === "ENOENT") {
2144
+ await mkdir3(dirname5(path), { recursive: true });
2145
+ await appendFile(path, line);
2146
+ } else {
2147
+ throw err;
2148
+ }
2149
+ }
2150
+ }).catch(() => warnOnce());
2151
+ }
2152
+ function logClientError(subsystem, err) {
2153
+ const e = coerceError(err);
2154
+ append(formatClientEntry(subsystem, e.stack ?? e.message));
2155
+ }
2156
+
2157
+ // ../rove-daemon/src/client/index.ts
2158
+ class RpcTimeoutError extends Error {
2159
+ constructor(name, timeoutMs) {
2160
+ super(`daemon rpc "${name}" timed out after ${timeoutMs}ms (daemon wedged?)`);
2161
+ this.name = "RpcTimeoutError";
2162
+ }
2163
+ }
2164
+ function rpcTimeoutMs() {
2165
+ const raw = readRoveEnv("RPC_TIMEOUT_MS")?.trim();
2166
+ if (raw) {
2167
+ const parsed = Number.parseInt(raw, 10);
2168
+ if (Number.isFinite(parsed))
2169
+ return parsed;
2170
+ }
2171
+ return 20000;
2172
+ }
2173
+
2174
+ class ConnectTimeoutError extends Error {
2175
+ constructor(socketPath, timeoutMs) {
2176
+ super(`connecting to ${socketPath} timed out after ${timeoutMs}ms — nothing accepted the connection (daemon or PTY host not listening at that address)`);
2177
+ this.name = "ConnectTimeoutError";
2178
+ }
2179
+ }
2180
+ function connectTimeoutMs() {
2181
+ const raw = readRoveEnv("CONNECT_TIMEOUT_MS")?.trim();
2182
+ if (raw) {
2183
+ const parsed = Number.parseInt(raw, 10);
2184
+ if (Number.isFinite(parsed))
2185
+ return parsed;
2186
+ }
2187
+ return 5000;
2188
+ }
2189
+
2190
+ class RoveDaemonClient {
2191
+ socketPath;
2192
+ socket = null;
2193
+ buffer = "";
2194
+ nextId = 1;
2195
+ pending = new Map;
2196
+ handlers = new Map;
2197
+ lifecycleHandlers = new Map;
2198
+ connecting = null;
2199
+ disposed = false;
2200
+ constructor(socketPath) {
2201
+ this.socketPath = socketPath;
2202
+ }
2203
+ connect() {
2204
+ if (this.socket)
2205
+ return Promise.resolve();
2206
+ if (this.disposed)
2207
+ return Promise.reject(new Error("daemon client disposed"));
2208
+ if (this.connecting)
2209
+ return this.connecting;
2210
+ const p = this.openSocketWithFallback();
2211
+ this.connecting = p;
2212
+ const cleanup = () => {
2213
+ if (this.connecting === p)
2214
+ this.connecting = null;
2215
+ };
2216
+ p.then(cleanup, cleanup);
2217
+ return p;
2218
+ }
2219
+ get isDisposed() {
2220
+ return this.disposed;
2221
+ }
2222
+ close() {
2223
+ this.disposed = true;
2224
+ this.socket?.end();
2225
+ this.socket = null;
2226
+ this.failPending();
2227
+ }
2228
+ forceDisconnect() {
2229
+ const socket = this.socket;
2230
+ if (!socket)
2231
+ return;
2232
+ this.socket = null;
2233
+ socket.destroy();
2234
+ this.failPending();
2235
+ }
2236
+ failPending() {
2237
+ if (this.pending.size === 0)
2238
+ return;
2239
+ const err = new Error("daemon connection closed");
2240
+ for (const pending of this.pending.values()) {
2241
+ if (pending.timer)
2242
+ clearTimeout(pending.timer);
2243
+ pending.reject(err);
2244
+ }
2245
+ this.pending.clear();
2246
+ }
2247
+ on(name, handler) {
2248
+ let set = this.handlers.get(name);
2249
+ if (!set) {
2250
+ set = new Set;
2251
+ this.handlers.set(name, set);
2252
+ }
2253
+ set.add(handler);
2254
+ return () => {
2255
+ set?.delete(handler);
2256
+ if (set?.size === 0)
2257
+ this.handlers.delete(name);
2258
+ };
2259
+ }
2260
+ onChannel(channel, handler) {
2261
+ return this.on(channel, (frame) => handler(frame.payload));
2262
+ }
2263
+ subscribe(opts = {}) {
2264
+ const payload = {};
2265
+ if (opts.channels)
2266
+ payload.channels = opts.channels;
2267
+ if (opts.role)
2268
+ payload.role = opts.role;
2269
+ if (opts.cellPixelSize) {
2270
+ payload.cellPixelWidth = opts.cellPixelSize.width;
2271
+ payload.cellPixelHeight = opts.cellPixelSize.height;
2272
+ }
2273
+ return this.request("subscribe", payload);
2274
+ }
2275
+ onLifecycle(name, handler) {
2276
+ let set = this.lifecycleHandlers.get(name);
2277
+ if (!set) {
2278
+ set = new Set;
2279
+ this.lifecycleHandlers.set(name, set);
2280
+ }
2281
+ set.add(handler);
2282
+ return () => {
2283
+ set?.delete(handler);
2284
+ if (set?.size === 0)
2285
+ this.lifecycleHandlers.delete(name);
2286
+ };
2287
+ }
2288
+ async request(name, payload) {
2289
+ await this.connect();
2290
+ const socket = this.socket;
2291
+ if (!socket)
2292
+ throw new Error("daemon connection is not open");
2293
+ const id = String(this.nextId++);
2294
+ const promise = new Promise((resolve2, reject) => {
2295
+ const entry = { resolve: (value) => resolve2(value), reject };
2296
+ const timeoutMs = rpcTimeoutMs();
2297
+ if (timeoutMs > 0 && !BLOCKING_RPCS.has(name)) {
2298
+ entry.timer = setTimeout(() => this.onRequestTimeout(id, name, timeoutMs), timeoutMs);
2299
+ }
2300
+ this.pending.set(id, entry);
2301
+ });
2302
+ socket.write(frameToLine({ type: "request", id, name, payload }));
2303
+ return promise;
2304
+ }
2305
+ onRequestTimeout(id, name, timeoutMs) {
2306
+ const pending = this.pending.get(id);
2307
+ if (!pending)
2308
+ return;
2309
+ this.pending.delete(id);
2310
+ pending.reject(new RpcTimeoutError(name, timeoutMs));
2311
+ this.forceDisconnect();
2312
+ this.emitLifecycle("close");
2313
+ }
2314
+ async openSocketWithFallback() {
2315
+ try {
2316
+ await this.openSocket(this.socketPath);
2317
+ return;
2318
+ } catch (err) {
2319
+ if (!(err instanceof Error) || !("code" in err) || !["ENOENT", "ECONNREFUSED"].includes(String(err.code)))
2320
+ throw err;
2321
+ for (const path of preRenameRuntimePaths(this.socketPath)) {
2322
+ if (this.disposed)
2323
+ throw err;
2324
+ try {
2325
+ await this.openSocket(path);
2326
+ return;
2327
+ } catch {}
2328
+ }
2329
+ throw err;
2330
+ }
2331
+ }
2332
+ openSocket(path) {
2333
+ return new Promise((resolve2, reject) => {
2334
+ const socket = connect(path);
2335
+ this.socket = socket;
2336
+ const timeoutMs = connectTimeoutMs();
2337
+ let timer;
2338
+ if (timeoutMs > 0) {
2339
+ timer = setTimeout(() => {
2340
+ socket.off("connect", onConnect);
2341
+ socket.off("error", onError);
2342
+ socket.destroy();
2343
+ if (this.socket === socket)
2344
+ this.socket = null;
2345
+ reject(new ConnectTimeoutError(this.socketPath, timeoutMs));
2346
+ }, timeoutMs);
2347
+ }
2348
+ const onConnect = () => {
2349
+ if (timer)
2350
+ clearTimeout(timer);
2351
+ socket.off("error", onError);
2352
+ socket.on("error", () => socket.destroy());
2353
+ resolve2();
2354
+ };
2355
+ const onError = (err) => {
2356
+ if (timer)
2357
+ clearTimeout(timer);
2358
+ socket.off("connect", onConnect);
2359
+ if (this.socket === socket)
2360
+ this.socket = null;
2361
+ reject(err);
2362
+ };
2363
+ socket.once("connect", onConnect);
2364
+ socket.once("error", onError);
2365
+ const decoder = new StringDecoder3("utf8");
2366
+ this.buffer = "";
2367
+ socket.on("data", (chunk) => this.onData(decoder.write(chunk)));
2368
+ socket.on("close", () => this.onSocketClose(socket));
2369
+ });
2370
+ }
2371
+ onSocketClose(which) {
2372
+ if (this.socket !== which)
2373
+ return;
2374
+ this.socket = null;
2375
+ this.failPending();
2376
+ this.emitLifecycle("close");
2377
+ }
2378
+ emitLifecycle(name) {
2379
+ for (const handler of this.lifecycleHandlers.get(name) ?? []) {
2380
+ try {
2381
+ handler();
2382
+ } catch (err) {
2383
+ console.error(`[rove] lifecycle handler for "${name}" threw:`, err);
2384
+ }
2385
+ }
2386
+ }
2387
+ onData(chunk) {
2388
+ this.buffer += chunk;
2389
+ let nl = this.buffer.indexOf(`
2390
+ `);
2391
+ while (nl !== -1) {
2392
+ const line = this.buffer.slice(0, nl);
2393
+ this.buffer = this.buffer.slice(nl + 1);
2394
+ if (line.trim().length > 0)
2395
+ this.onLine(line);
2396
+ nl = this.buffer.indexOf(`
2397
+ `);
2398
+ }
2399
+ }
2400
+ onLine(line) {
2401
+ let frame;
2402
+ try {
2403
+ frame = JSON.parse(line);
2404
+ } catch (err) {
2405
+ logClientError("client-frame", err);
2406
+ return;
2407
+ }
2408
+ if (frame.type === "event") {
2409
+ this.emit(frame);
2410
+ return;
2411
+ }
2412
+ if (frame.type !== "response")
2413
+ return;
2414
+ const pending = this.pending.get(frame.id);
2415
+ if (!pending)
2416
+ return;
2417
+ this.pending.delete(frame.id);
2418
+ if (pending.timer)
2419
+ clearTimeout(pending.timer);
2420
+ if (frame.error) {
2421
+ const err = new Error(frame.error.message);
2422
+ if (frame.error.name)
2423
+ err.name = frame.error.name;
2424
+ pending.reject(err);
2425
+ } else
2426
+ pending.resolve(frame.payload);
2427
+ }
2428
+ emit(frame) {
2429
+ for (const handler of this.handlers.get(frame.name) ?? []) {
2430
+ try {
2431
+ handler(frame);
2432
+ } catch (err) {
2433
+ logClientError("client-event", err);
2434
+ }
2435
+ }
2436
+ for (const handler of this.handlers.get("*") ?? []) {
2437
+ try {
2438
+ handler(frame);
2439
+ } catch (err) {
2440
+ logClientError("client-event", err);
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+
2446
+ // ../rove-daemon/src/client/win-detached-launch.ts
2447
+ import { spawn } from "node:child_process";
2448
+ import { existsSync as existsSync7 } from "node:fs";
2449
+ import { join as join8 } from "node:path";
2450
+ var LAUNCH_CMD_ENV = "ROVE_LAUNCH_CMD";
2451
+ var LAUNCH_LOG_ENV = "ROVE_LAUNCH_LOG";
2452
+ var WIN_DETACHED_LAUNCHER_PS = `
2453
+ $ErrorActionPreference = 'Stop'
2454
+ $cmd = $env:${LAUNCH_CMD_ENV}
2455
+ $log = $env:${LAUNCH_LOG_ENV}
2456
+ if (-not $cmd -or -not $log) { [Console]::Error.WriteLine('rove launcher: ${LAUNCH_CMD_ENV}/${LAUNCH_LOG_ENV} not set'); exit 1 }
2457
+ Remove-Item Env:${LAUNCH_CMD_ENV}, Env:${LAUNCH_LOG_ENV}
2458
+ Add-Type -TypeDefinition @'
2459
+ using System;
2460
+ using System.Runtime.InteropServices;
2461
+ public static class RoveDetachedLaunch {
2462
+ [StructLayout(LayoutKind.Sequential)] public struct SA { public int nLength; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; }
2463
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct SI { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags; public short wShowWindow, cbReserved2; public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError; }
2464
+ [StructLayout(LayoutKind.Sequential)] public struct PI { public IntPtr hProcess, hThread; public int dwProcessId, dwThreadId; }
2465
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern IntPtr CreateFile(string name, uint access, uint share, ref SA sa, uint disposition, uint flags, IntPtr template);
2466
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern bool CreateProcess(string app, string cmd, IntPtr pa, IntPtr ta, bool inherit, uint flags, IntPtr env, string cwd, ref SI si, out PI pi);
2467
+ [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
2468
+ public static int Launch(string cmd, string log) {
2469
+ SA sa = new SA(); sa.nLength = Marshal.SizeOf(sa); sa.bInheritHandle = true;
2470
+ // FILE_APPEND_DATA | GENERIC_READ, shared read/write, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL
2471
+ IntPtr h = CreateFile(log, 0x0004 | 0x80000000, 0x1 | 0x2, ref sa, 4, 0x80, IntPtr.Zero);
2472
+ if (h == (IntPtr)(-1)) throw new Exception("CreateFile(" + log + ") failed: " + Marshal.GetLastWin32Error());
2473
+ IntPtr nul = CreateFile("NUL", 0x80000000, 0x1 | 0x2, ref sa, 3, 0x80, IntPtr.Zero);
2474
+ SI si = new SI(); si.cb = Marshal.SizeOf(si);
2475
+ si.dwFlags = 0x100 | 0x1; // STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
2476
+ si.wShowWindow = 0; // SW_HIDE
2477
+ si.hStdInput = nul; si.hStdOutput = h; si.hStdError = h;
2478
+ PI pi;
2479
+ uint flags = 0x00000010 | 0x01000000 | 0x00000200; // CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP
2480
+ if (!CreateProcess(null, cmd, IntPtr.Zero, IntPtr.Zero, true, flags, IntPtr.Zero, null, ref si, out pi)) throw new Exception("CreateProcess failed: " + Marshal.GetLastWin32Error());
2481
+ CloseHandle(h); CloseHandle(nul); CloseHandle(pi.hThread); CloseHandle(pi.hProcess);
2482
+ return pi.dwProcessId;
2483
+ }
2484
+ }
2485
+ '@
2486
+ [RoveDetachedLaunch]::Launch($cmd, $log) | Out-Null
2487
+ `;
2488
+ function windowsCommandLine(argv) {
2489
+ return argv.map(quoteWindowsArg).join(" ");
2490
+ }
2491
+ function quoteWindowsArg(arg) {
2492
+ if (arg !== "" && !/[\s"]/.test(arg))
2493
+ return arg;
2494
+ let out = '"';
2495
+ let backslashes = 0;
2496
+ for (const ch of arg) {
2497
+ if (ch === "\\") {
2498
+ backslashes++;
2499
+ continue;
2500
+ }
2501
+ if (ch === '"') {
2502
+ out += `${"\\".repeat(backslashes * 2 + 1)}"`;
2503
+ backslashes = 0;
2504
+ continue;
2505
+ }
2506
+ out += "\\".repeat(backslashes) + ch;
2507
+ backslashes = 0;
2508
+ }
2509
+ return `${out}${"\\".repeat(backslashes * 2)}"`;
2510
+ }
2511
+ function windowsPowershellPath(env = process.env, exists = existsSync7) {
2512
+ const root = env.SystemRoot || env.windir || "C:\\Windows";
2513
+ const absolute = join8(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2514
+ return exists(absolute) ? absolute : "powershell.exe";
2515
+ }
2516
+ function encodePowershellCommand(script) {
2517
+ return Buffer.from(script, "utf16le").toString("base64");
2518
+ }
2519
+ function windowsDetachedLauncherArgs() {
2520
+ return [
2521
+ "-NoProfile",
2522
+ "-NonInteractive",
2523
+ "-ExecutionPolicy",
2524
+ "Bypass",
2525
+ "-EncodedCommand",
2526
+ encodePowershellCommand(WIN_DETACHED_LAUNCHER_PS)
2527
+ ];
2528
+ }
2529
+ function spawnWindowsDetached(command, args, env, logPath, spawnImpl = spawn) {
2530
+ const child = spawnImpl(windowsPowershellPath(env), windowsDetachedLauncherArgs(), {
2531
+ windowsHide: true,
2532
+ stdio: "ignore",
2533
+ env: { ...env, [LAUNCH_CMD_ENV]: windowsCommandLine([command, ...args]), [LAUNCH_LOG_ENV]: logPath }
2534
+ });
2535
+ child.unref();
2536
+ return child;
2537
+ }
2538
+
2539
+ // ../rove-daemon/src/client/detached-spawn.ts
2540
+ var ESCAPE_REQUEST_TIMEOUT_MS = 5000;
2541
+ function appendLaunchLog(logPath, line) {
2542
+ try {
2543
+ appendFileSync(logPath, `[${new Date().toISOString()}] rove: ${line}
2544
+ `);
2545
+ } catch {}
2546
+ }
2547
+ async function launchViaPtyHost(command, args, env, logPath, socketPath) {
2548
+ const client = new RoveDaemonClient(socketPath);
2549
+ let timer;
2550
+ try {
2551
+ await Promise.race([
2552
+ client.request("spawn.detached", { command, args: [...args], env, logPath }),
2553
+ new Promise((_, reject) => {
2554
+ timer = setTimeout(() => reject(new Error("timed out")), ESCAPE_REQUEST_TIMEOUT_MS);
2555
+ })
2556
+ ]);
2557
+ } finally {
2558
+ if (timer)
2559
+ clearTimeout(timer);
2560
+ client.close();
2561
+ }
2562
+ }
2563
+ function detachOptions(platform = process.platform) {
2564
+ return platform === "win32" ? { windowsHide: true } : { detached: true };
2565
+ }
2566
+ function spawnDetachedDaemon(command, args, env, logPath, platform = process.platform) {
2567
+ try {
2568
+ mkdirSync5(dirname6(logPath), { recursive: true });
2569
+ } catch {}
2570
+ if (platform === "win32") {
2571
+ if (ptyJobLaunch(env, defaultDaemonSocketPath())?.mode === "escape") {
2572
+ const free = withoutPtyJob(env);
2573
+ launchViaPtyHost(command, args, free, logPath, defaultPtyHostSocketPath()).catch((err) => {
2574
+ appendLaunchLog(logPath, `PTY host could not launch outside the tab (${err.message}) — it ends with the tab`);
2575
+ launchWindowsDetached(command, args, free, logPath, platform);
2576
+ });
2577
+ return;
2578
+ }
2579
+ launchWindowsDetached(command, args, env, logPath, platform);
2580
+ return;
2581
+ }
2582
+ spawnDirect(command, args, env, logPath, platform);
2583
+ }
2584
+ function launchWindowsDetached(command, args, env, logPath, platform) {
2585
+ const launcher = spawnWindowsDetached(command, args, env, logPath);
2586
+ const fallback = (reason) => {
2587
+ appendLaunchLog(logPath, `detached launcher ${reason} — spawning directly; this process will not outlive its parent`);
2588
+ spawnDirect(command, args, env, logPath, platform);
2589
+ };
2590
+ launcher.once("error", (err) => fallback(`could not start (${err.message})`));
2591
+ launcher.once("exit", (code, signal) => {
2592
+ if (code !== 0)
2593
+ fallback(`exited ${code ?? signal}`);
2594
+ });
2595
+ }
2596
+ function spawnDirect(command, args, env, logPath, platform) {
2597
+ let stdio = "ignore";
2598
+ let logFd;
2599
+ try {
2600
+ mkdirSync5(dirname6(logPath), { recursive: true });
2601
+ logFd = openSync(logPath, "a");
2602
+ stdio = ["ignore", logFd, logFd];
2603
+ } catch {
2604
+ stdio = "ignore";
2605
+ }
2606
+ const child = spawn2(command, [...args], { ...detachOptions(platform), stdio, env });
2607
+ child.unref();
2608
+ if (logFd !== undefined) {
2609
+ try {
2610
+ closeSync(logFd);
2611
+ } catch {}
2612
+ }
2613
+ }
2614
+
2615
+ // ../rove-daemon/src/daemon/handler-validators.ts
1610
2616
  function objectPayload(payload) {
1611
2617
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
1612
2618
  return {};
@@ -1627,7 +2633,7 @@ function optionalBoolean(payload, key) {
1627
2633
  return value;
1628
2634
  }
1629
2635
 
1630
- // ../kobe-daemon/src/daemon/pty-server-verbs.ts
2636
+ // ../rove-daemon/src/daemon/pty-server-verbs.ts
1631
2637
  function dispatchPtyRequest(req, client, deps) {
1632
2638
  const { ptys } = deps;
1633
2639
  switch (req.name) {
@@ -1642,7 +2648,7 @@ function dispatchPtyRequest(req, client, deps) {
1642
2648
  cols: typeof payload.cols === "number" ? payload.cols : undefined,
1643
2649
  rows: typeof payload.rows === "number" ? payload.rows : undefined,
1644
2650
  defaultColors: parseTerminalDefaultColors(payload.defaultColors) ?? undefined
1645
- }, client, (frame) => deps.writeFrame(client, frame), typeof payload.sinceOffset === "number" ? payload.sinceOffset : undefined, typeof payload.sincePid === "number" ? payload.sincePid : undefined);
2651
+ }, client, (frame) => deps.writeFrame(client, frame), typeof payload.sinceOffset === "number" ? payload.sinceOffset : undefined, typeof payload.sincePid === "number" ? payload.sincePid : undefined, payload.answersQueries === true);
1646
2652
  }
1647
2653
  case "pty.write": {
1648
2654
  const payload = objectPayload(req.payload);
@@ -1695,6 +2701,14 @@ function dispatchPtyRequest(req, client, deps) {
1695
2701
  ptys.warm(requireString(payload, "cwd"), typeof payload.shell === "string" ? payload.shell : undefined, typeof payload.cols === "number" ? payload.cols : undefined, typeof payload.rows === "number" ? payload.rows : undefined);
1696
2702
  return {};
1697
2703
  }
2704
+ case "spawn.detached": {
2705
+ if (process.platform !== "win32")
2706
+ throw new Error("spawn.detached is Windows-only");
2707
+ const payload = objectPayload(req.payload);
2708
+ const env = objectPayload(payload.env);
2709
+ spawnDetachedDaemon(requireString(payload, "command"), Array.isArray(payload.args) ? payload.args.filter((a) => typeof a === "string") : [], withoutPtyJob(Object.fromEntries(Object.entries(env).filter((e) => typeof e[1] === "string"))), requireString(payload, "logPath"));
2710
+ return {};
2711
+ }
1698
2712
  case "daemon.stop":
1699
2713
  deps.requestStop();
1700
2714
  return {};
@@ -1703,32 +2717,32 @@ function dispatchPtyRequest(req, client, deps) {
1703
2717
  }
1704
2718
  }
1705
2719
 
1706
- // ../kobe-daemon/src/daemon/socket-guard.ts
2720
+ // ../rove-daemon/src/daemon/socket-guard.ts
1707
2721
  async function listenOnUnixSocket(server, socketPath) {
1708
- await new Promise((resolve, reject) => {
2722
+ await new Promise((resolve2, reject) => {
1709
2723
  const evented = server;
1710
2724
  evented.once("error", reject);
1711
2725
  server.listen(socketPath, () => {
1712
2726
  evented.removeListener("error", reject);
1713
- resolve();
2727
+ resolve2();
1714
2728
  });
1715
2729
  });
1716
2730
  if (!isWindowsPipePath(socketPath))
1717
2731
  await tightenFilePermissions(socketPath);
1718
2732
  }
1719
2733
 
1720
- // ../kobe-daemon/src/daemon/pty-server.ts
2734
+ // ../rove-daemon/src/daemon/pty-server.ts
1721
2735
  var DEFAULT_IDLE_EXIT_MS = 60000;
1722
2736
  var DEFAULT_ORPHAN_CHECK_MS = 30000;
1723
2737
  function resolveMaxLifetimeMs() {
1724
- const raw = process.env.KOBE_PTY_MAX_LIFETIME_MS;
2738
+ const raw = process.env.ROVE_PTY_MAX_LIFETIME_MS;
1725
2739
  if (raw === undefined)
1726
2740
  return null;
1727
2741
  const n = Number(raw);
1728
2742
  return Number.isFinite(n) && n > 0 ? n : null;
1729
2743
  }
1730
2744
  function resolveIdleExitMs() {
1731
- const raw = process.env.KOBE_PTY_IDLE_EXIT_MS;
2745
+ const raw = process.env.ROVE_PTY_IDLE_EXIT_MS;
1732
2746
  if (raw === undefined)
1733
2747
  return DEFAULT_IDLE_EXIT_MS;
1734
2748
  const n = Number(raw);
@@ -1820,8 +2834,8 @@ async function startPtyHostServer(options = {}) {
1820
2834
  const pipeSocket = isWindowsPipePath(socketPath);
1821
2835
  await ensureOwnerOnlyStateDir(resolveDaemonHomeDir());
1822
2836
  if (!pipeSocket)
1823
- await mkdir3(dirname5(socketPath), { recursive: true });
1824
- await mkdir3(dirname5(pidPath), { recursive: true });
2837
+ await mkdir4(dirname7(socketPath), { recursive: true });
2838
+ await mkdir4(dirname7(pidPath), { recursive: true });
1825
2839
  const server = createServer((socket) => {
1826
2840
  const client = {
1827
2841
  socket,
@@ -1860,10 +2874,10 @@ async function startPtyHostServer(options = {}) {
1860
2874
  clearFrozenSessions(freezeDir);
1861
2875
  for (const client of Array.from(clients))
1862
2876
  client.socket.destroy();
1863
- await new Promise((resolve) => server.close(() => resolve()));
2877
+ await new Promise((resolve2) => server.close(() => resolve2()));
1864
2878
  if (!pipeSocket)
1865
- await unlink2(socketPath).catch(() => {});
1866
- await unlink2(pidPath).catch(() => {});
2879
+ await unlink(socketPath).catch(() => {});
2880
+ await unlink(pidPath).catch(() => {});
1867
2881
  }
1868
2882
  };
1869
2883
  async function stop() {
@@ -1915,11 +2929,6 @@ async function startPtyHostServer(options = {}) {
1915
2929
  await listenOnUnixSocket(server, socketPath);
1916
2930
  await writeTextAtomic(pidPath, `${process.pid}
1917
2931
  `);
1918
- if (!pipeSocket) {
1919
- const home = resolveDaemonHomeDir();
1920
- await linkLegacyRuntimePath(socketPath, legacyPtyHostSocketPath(home));
1921
- await linkLegacyRuntimePath(pidPath, legacyPtyHostPidPath(home));
1922
- }
1923
2932
  armIdle();
1924
2933
  log("boot", `pty host listening on ${socketPath}`);
1925
2934
  return api;
@@ -1928,15 +2937,18 @@ function writeFrame(client, frame) {
1928
2937
  client.writer.write(frameToLine(frame));
1929
2938
  }
1930
2939
 
1931
- // ../kobe-daemon/src/daemon/pty-host-node-entry.ts
2940
+ // ../rove-daemon/src/daemon/pty-host-node-entry.ts
1932
2941
  async function main() {
1933
2942
  rotateLogIfNeeded(defaultPtyHostLogPath());
1934
2943
  process.on("uncaughtException", (err) => console.error(formatPtyHostLine("crash", err?.stack ?? String(err))));
1935
2944
  process.on("unhandledRejection", (err) => console.error(formatPtyHostLine("reject", String(err))));
1936
- const driver = await nodePtyDriver();
2945
+ const log = (event, message) => console.log(formatPtyHostLine(event, message));
2946
+ const launcher = await ensurePtyJobLauncher(join9(resolveDaemonHomeDir(), ROVE_STATE_DIR_BASENAME, "bin")).catch((err) => ({ path: null, reason: String(err) }));
2947
+ log("pty", launcher.path ? `session job launcher ${launcher.path}` : `session jobs off: ${launcher.reason}`);
2948
+ const driver = await nodePtyDriver(undefined, undefined, launcher.path ? { launcher: launcher.path, owner: defaultDaemonSocketPath() } : undefined);
1937
2949
  const server = await startPtyHostServer({
1938
2950
  driver,
1939
- log: (event, message) => console.log(formatPtyHostLine(event, message)),
2951
+ log,
1940
2952
  version: process.env.ROVE_PTY_HOST_VERSION,
1941
2953
  onStop: () => process.exit(0)
1942
2954
  });