@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.
- package/README.md +2 -2
- package/dist/cli/{chunk-63xy91mc.js → chunk-0k91029d.js} +1 -1
- package/dist/cli/chunk-0mj358p5.js +3 -0
- package/dist/cli/chunk-0n7w8t48.js +2 -0
- package/dist/cli/{chunk-v4y0sf0x.js → chunk-0wrh5s5y.js} +2 -2
- package/dist/cli/chunk-0y4qgnby.js +3 -0
- package/dist/cli/chunk-1brfxbvg.js +4 -0
- package/dist/cli/{chunk-tdze2mnn.js → chunk-20qm75rb.js} +1 -1
- package/dist/cli/chunk-21fm5tks.js +2 -0
- package/dist/cli/{chunk-bkbmqec2.js → chunk-28zg6p31.js} +1 -1
- package/dist/cli/chunk-2andp9gs.js +2 -0
- package/dist/cli/{chunk-r2pztgm6.js → chunk-2bgmby1d.js} +1 -1
- package/dist/cli/{chunk-5e39zhvj.js → chunk-2s2xy444.js} +5 -5
- package/dist/cli/chunk-2vgc5cyj.js +2 -0
- package/dist/cli/chunk-2x9yc5nx.js +2 -0
- package/dist/cli/chunk-30kjq5m7.js +2 -0
- package/dist/cli/{chunk-mkksdcsw.js → chunk-3342qzve.js} +1 -1
- package/dist/cli/chunk-3aqhytyy.js +3 -0
- package/dist/cli/chunk-3c58659p.js +2 -0
- package/dist/cli/chunk-3d1hjrq0.js +2 -0
- package/dist/cli/chunk-41x1hrqe.js +3 -0
- package/dist/cli/{chunk-mhg40es1.js → chunk-4hestsjp.js} +1 -1
- package/dist/cli/{chunk-s1p650mm.js → chunk-53cyns0f.js} +1 -1
- package/dist/cli/chunk-576r5nht.js +5 -0
- package/dist/cli/{chunk-rvx5zz72.js → chunk-5akk3wap.js} +6 -5
- package/dist/cli/chunk-7cce0st1.js +3 -0
- package/dist/cli/{chunk-zyd6k6ak.js → chunk-7q43tysm.js} +1 -1
- package/dist/cli/chunk-8bn6eg67.js +8 -0
- package/dist/cli/chunk-8rf7nzan.js +4 -0
- package/dist/cli/chunk-96cjpssp.js +3 -0
- package/dist/cli/{chunk-bs7v83x0.js → chunk-9aydqazr.js} +1 -1
- package/dist/cli/{chunk-7txq91d9.js → chunk-9jsm6h5y.js} +1 -1
- package/dist/cli/{chunk-pz907kve.js → chunk-9w5g7frx.js} +1 -1
- package/dist/cli/{chunk-vnpqemb0.js → chunk-a7kq09ke.js} +1 -1
- package/dist/cli/chunk-acxznk3y.js +2 -0
- package/dist/cli/chunk-adkva3ep.js +6 -0
- package/dist/cli/{chunk-nvqdmx7q.js → chunk-ag3txfg2.js} +1 -1
- package/dist/cli/{chunk-2rg6aw59.js → chunk-atmaakwp.js} +1 -1
- package/dist/cli/chunk-axsm0gn5.js +3 -0
- package/dist/cli/chunk-b0ssvpht.js +3 -0
- package/dist/cli/chunk-b39sdtt0.js +3 -0
- package/dist/cli/{chunk-qyzvcgva.js → chunk-b9q8c2qg.js} +6 -6
- package/dist/cli/{chunk-g56km26e.js → chunk-bn73kd6z.js} +1 -1
- package/dist/cli/chunk-c2m55b72.js +4 -0
- package/dist/cli/{chunk-3gmzrstj.js → chunk-c60qsnfw.js} +3 -3
- package/dist/cli/{chunk-ch0ehc06.js → chunk-cbc1h345.js} +1 -1
- package/dist/cli/{chunk-dnbh8hsm.js → chunk-cbjh43yc.js} +1 -1
- package/dist/cli/{chunk-kbje1cpy.js → chunk-cmcn8e8x.js} +1 -1
- package/dist/cli/{chunk-cv1kaq8a.js → chunk-cp89n425.js} +5 -5
- package/dist/cli/chunk-cst86kt3.js +2 -0
- package/dist/cli/chunk-des00203.js +2 -0
- package/dist/cli/chunk-dgs0m8r4.js +3 -0
- package/dist/cli/chunk-dkgxz9hf.js +5 -0
- package/dist/cli/{chunk-gqphk56d.js → chunk-ekg3h5z3.js} +7 -7
- package/dist/cli/chunk-fert44x2.js +2 -0
- package/dist/cli/chunk-ff1h2qxd.js +2 -0
- package/dist/cli/{chunk-cv6r89jp.js → chunk-ffs26p85.js} +1 -1
- package/dist/cli/chunk-fft50thp.js +3 -0
- package/dist/cli/{chunk-hp32tbes.js → chunk-fg62y5fm.js} +1 -1
- package/dist/cli/{chunk-s9a974w9.js → chunk-fvmee0tr.js} +1 -1
- package/dist/cli/chunk-g4c4ffqp.js +20 -0
- package/dist/cli/{chunk-k7g060qr.js → chunk-g4j41mky.js} +2 -2
- package/dist/cli/{chunk-j6kataan.js → chunk-g5c5jytr.js} +1 -1
- package/dist/cli/{chunk-4bmap7dg.js → chunk-ga1pb0mr.js} +1 -1
- package/dist/cli/{chunk-dqndd97s.js → chunk-gkdfct7t.js} +2 -2
- package/dist/cli/chunk-gqydm0q8.js +2 -0
- package/dist/cli/chunk-gvrtrvnz.js +96 -0
- package/dist/cli/chunk-h2m7em1e.js +3 -0
- package/dist/cli/chunk-h33m6cc6.js +8 -0
- package/dist/cli/{chunk-ynbfqbn9.js → chunk-h3fckxn3.js} +1 -1
- package/dist/cli/chunk-h7y9v8k1.js +4 -0
- package/dist/cli/{chunk-32mv1yy6.js → chunk-h9cg0j3a.js} +1 -1
- package/dist/cli/{chunk-h4qv19v8.js → chunk-hbn1t161.js} +1 -1
- package/dist/cli/{chunk-12kgbrj1.js → chunk-hen7jj2h.js} +2 -2
- package/dist/cli/{chunk-3es6spp4.js → chunk-hq9tbzqa.js} +2 -2
- package/dist/cli/{chunk-snjte1rd.js → chunk-hr2xky15.js} +1 -1
- package/dist/cli/{chunk-s0snbj93.js → chunk-kb6xw0fw.js} +1 -1
- package/dist/cli/{chunk-40dk2add.js → chunk-ker4971f.js} +2 -2
- package/dist/cli/{chunk-vyb5a2rh.js → chunk-kg9kmwt9.js} +1 -1
- package/dist/cli/chunk-ktzbg4jg.js +2 -0
- package/dist/cli/chunk-m4f5h05c.js +10 -0
- package/dist/cli/chunk-m6q44xc0.js +2 -0
- package/dist/cli/{chunk-hq2n27y1.js → chunk-mw0ysrpd.js} +1 -1
- package/dist/cli/{chunk-2cwb64fj.js → chunk-n5dea05t.js} +1 -1
- package/dist/cli/chunk-nc46gddg.js +3 -0
- package/dist/cli/{chunk-jhbc9nx2.js → chunk-nckmga7j.js} +1 -1
- package/dist/cli/chunk-nem2x959.js +2 -0
- package/dist/cli/chunk-ng12dprz.js +2 -0
- package/dist/cli/chunk-npwzmmv9.js +3 -0
- package/dist/cli/chunk-nzh30nty.js +4 -0
- package/dist/cli/{chunk-gqrh2gp1.js → chunk-p024scd4.js} +1 -1
- package/dist/cli/chunk-p18ehv9y.js +7 -0
- package/dist/cli/chunk-p6qr8sen.js +2 -0
- package/dist/cli/{chunk-3sg5v7cg.js → chunk-p83wf4zw.js} +2 -2
- package/dist/cli/{chunk-s278gjet.js → chunk-pdvyfz3n.js} +1 -1
- package/dist/cli/chunk-pe6wnsww.js +2 -0
- package/dist/cli/{chunk-dhtsd706.js → chunk-pqzbk1cz.js} +25 -23
- package/dist/cli/chunk-prqeddy7.js +3 -0
- package/dist/cli/chunk-pvm3fc44.js +2 -0
- package/dist/cli/chunk-qd5zgq91.js +10 -0
- package/dist/cli/chunk-qvfdqrbf.js +3 -0
- package/dist/cli/{chunk-2xsem13q.js → chunk-qzsahxd9.js} +1 -1
- package/dist/cli/{chunk-aj3fk7xz.js → chunk-rahv4nrm.js} +3 -3
- package/dist/cli/chunk-rqyaf0dw.js +4 -0
- package/dist/cli/chunk-s57vy3k3.js +2 -0
- package/dist/cli/{chunk-g2advs8t.js → chunk-sak1tphp.js} +1 -1
- package/dist/cli/{chunk-vdcn6h48.js → chunk-snhcvjdb.js} +1 -1
- package/dist/cli/chunk-ss8c3b25.js +3 -0
- package/dist/cli/{chunk-bav389wj.js → chunk-svm809bx.js} +1 -1
- package/dist/cli/chunk-t8v9rpdx.js +2 -0
- package/dist/cli/{chunk-j4xfsn9g.js → chunk-tbrkck2f.js} +1 -1
- package/dist/cli/chunk-tdhk4dz8.js +4 -0
- package/dist/cli/chunk-try1f2h3.js +3 -0
- package/dist/cli/{chunk-b9ywdsdj.js → chunk-tsj1054w.js} +1 -1
- package/dist/cli/{chunk-t3wwsz29.js → chunk-tvk46rfs.js} +1 -1
- package/dist/cli/{chunk-4d54zgc7.js → chunk-twyda1p8.js} +2 -2
- package/dist/cli/{chunk-s1e0e9hc.js → chunk-vej49hnt.js} +2 -2
- package/dist/cli/chunk-w2sp489k.js +3 -0
- package/dist/cli/chunk-wfhnbnbw.js +2 -0
- package/dist/cli/{chunk-0wh2ff77.js → chunk-whbm14qr.js} +2 -2
- package/dist/cli/{chunk-ww4fbx8h.js → chunk-wtb8cycx.js} +2 -2
- package/dist/cli/chunk-wvmjdnhk.js +2 -0
- package/dist/cli/chunk-x9s14ppv.js +4 -0
- package/dist/cli/{chunk-dq2mh3b3.js → chunk-xv8fhxxm.js} +1 -1
- package/dist/cli/{chunk-85pk7c74.js → chunk-xx166cdv.js} +1 -1
- package/dist/cli/{chunk-ssfj9td0.js → chunk-y8tysjc2.js} +15 -13
- package/dist/cli/{chunk-y3k1accw.js → chunk-yexx8nec.js} +1 -1
- package/dist/cli/{chunk-9prawccj.js → chunk-yt15y085.js} +1 -1
- package/dist/cli/{chunk-n0mg67wn.js → chunk-ytxk6zqc.js} +1 -1
- package/dist/cli/chunk-yvjgwnb9.js +2 -0
- package/dist/cli/{chunk-n24dmy2r.js → chunk-yxt1y0n3.js} +9 -9
- package/dist/cli/chunk-ze0htjr5.js +2 -0
- package/dist/cli/chunk-zeb0sttx.js +2 -0
- package/dist/cli/chunk-znjcesdr.js +5 -0
- package/dist/cli/chunk-zsfyj36g.js +3 -0
- package/dist/cli/chunk-zt49bbg9.js +3 -0
- package/dist/cli/chunk-zt7wfnva.js +3 -0
- package/dist/cli/{chunk-6rvzzrd7.js → chunk-zx2n95qe.js} +1 -1
- package/dist/cli/{chunk-c4dx3er1.js → chunk-zxsbz3kk.js} +1 -1
- package/dist/cli/index.js +12 -10
- package/dist/cli/pty-host-node.mjs +1189 -177
- package/dist/cli/rove-run.js +1 -1
- package/dist/cli/rove.js +6 -6
- package/dist/skills/rove/SKILL.md +1 -1
- package/dist/skills/rove/references/api-flags.md +5 -5
- package/dist/web-ui/assets/harness-BHiciz5A.js +87 -0
- package/dist/web-ui/assets/{index-D2wALq0b.js → index-BkxJISAI.js} +2 -2
- package/dist/web-ui/assets/index-nMJ42IGm.css +1 -0
- package/dist/web-ui/index.html +2 -2
- package/dist/web-ui/origin-policy.mjs +1 -1
- package/dist/web-ui/pty-server.mjs +66 -8
- package/dist/web-ui/pty-session-lifecycle.mjs +49 -14
- package/package.json +6 -8
- package/dist/cli/chunk-05jej26n.js +0 -3
- package/dist/cli/chunk-072x915d.js +0 -2
- package/dist/cli/chunk-079cyb2e.js +0 -4
- package/dist/cli/chunk-1da3bqjc.js +0 -4
- package/dist/cli/chunk-1m7cp5ze.js +0 -3
- package/dist/cli/chunk-1q8tbbpz.js +0 -2
- package/dist/cli/chunk-1tx1s5v6.js +0 -2
- package/dist/cli/chunk-4y1fdz34.js +0 -3
- package/dist/cli/chunk-53afdybj.js +0 -3
- package/dist/cli/chunk-544bwsss.js +0 -4
- package/dist/cli/chunk-5bsk7qmg.js +0 -8
- package/dist/cli/chunk-6s5mev2n.js +0 -4
- package/dist/cli/chunk-6x5vzzpx.js +0 -3
- package/dist/cli/chunk-7s1kx9ez.js +0 -2
- package/dist/cli/chunk-85mk6j0s.js +0 -3
- package/dist/cli/chunk-8hbtca1g.js +0 -3
- package/dist/cli/chunk-8pa1ph1s.js +0 -7
- package/dist/cli/chunk-9k47xr7m.js +0 -2
- package/dist/cli/chunk-9mtxxsb3.js +0 -2
- package/dist/cli/chunk-a3yyrabb.js +0 -2
- package/dist/cli/chunk-a8et9f63.js +0 -4
- package/dist/cli/chunk-ahnk6zx3.js +0 -2
- package/dist/cli/chunk-bc18gat5.js +0 -4
- package/dist/cli/chunk-bw73t1zw.js +0 -5
- package/dist/cli/chunk-c2tevj9f.js +0 -2
- package/dist/cli/chunk-d1agt94n.js +0 -8
- package/dist/cli/chunk-dc31fzrm.js +0 -2
- package/dist/cli/chunk-dw853n4w.js +0 -3
- package/dist/cli/chunk-eawt4n4g.js +0 -2
- package/dist/cli/chunk-efx1gqmp.js +0 -2
- package/dist/cli/chunk-egx76ah2.js +0 -5
- package/dist/cli/chunk-ex1a2djb.js +0 -2
- package/dist/cli/chunk-f5e3dcxe.js +0 -2
- package/dist/cli/chunk-fwb72vzc.js +0 -2
- package/dist/cli/chunk-fxs82dsx.js +0 -4
- package/dist/cli/chunk-g3r1dwy9.js +0 -3
- package/dist/cli/chunk-ge30p4s5.js +0 -3
- package/dist/cli/chunk-gmgsxdrf.js +0 -10
- package/dist/cli/chunk-he76emdm.js +0 -2
- package/dist/cli/chunk-hgx20x94.js +0 -2
- package/dist/cli/chunk-hkq7c6hy.js +0 -2
- package/dist/cli/chunk-hnfp317x.js +0 -2
- package/dist/cli/chunk-hq4b3fca.js +0 -3
- package/dist/cli/chunk-jvxh93nz.js +0 -3
- package/dist/cli/chunk-k6nxyxyh.js +0 -3
- package/dist/cli/chunk-k8tspshr.js +0 -2
- package/dist/cli/chunk-kavsx69v.js +0 -3
- package/dist/cli/chunk-md8k7c5w.js +0 -2
- package/dist/cli/chunk-mppda1wq.js +0 -3
- package/dist/cli/chunk-ppwpjm39.js +0 -3
- package/dist/cli/chunk-pvw2y93q.js +0 -25
- package/dist/cli/chunk-pw6yfj10.js +0 -3
- package/dist/cli/chunk-q4nehkj9.js +0 -2
- package/dist/cli/chunk-qagek3wg.js +0 -5
- package/dist/cli/chunk-rz8zs7j8.js +0 -3
- package/dist/cli/chunk-sb0ns0c7.js +0 -3
- package/dist/cli/chunk-sp99y6eb.js +0 -2
- package/dist/cli/chunk-vejgnmcp.js +0 -2
- package/dist/cli/chunk-vz5g1p3w.js +0 -3
- package/dist/cli/chunk-vzqrj32e.js +0 -2
- package/dist/cli/chunk-wpsbyq4s.js +0 -2
- package/dist/cli/chunk-wtqxv2rv.js +0 -39
- package/dist/cli/chunk-xhgmnvjz.js +0 -8
- package/dist/cli/chunk-y05d79az.js +0 -2
- package/dist/cli/chunk-y8fqdnh3.js +0 -3
- package/dist/cli/chunk-ygd572ee.js +0 -4
- package/dist/cli/chunk-zja9chrd.js +0 -4
- package/dist/cli/chunk-zwej00t0.js +0 -5
- package/dist/cli/kobe-run.js +0 -3
- package/dist/cli/kobe.js +0 -7
- package/dist/completions/kobe.bash +0 -25
- package/dist/completions/kobe.fish +0 -27
- package/dist/completions/kobe.zsh +0 -35
- package/dist/web-ui/assets/harness-CLsfHgXB.js +0 -86
- package/dist/web-ui/assets/index-B4knjCIs.css +0 -1
- /package/dist/cli/{chunk-hqbmzz4t.js → chunk-0422g8ac.js} +0 -0
- /package/dist/cli/{chunk-khj0apc7.js → chunk-2tfnscxk.js} +0 -0
- /package/dist/cli/{chunk-7q67dz4c.js → chunk-4250pw6r.js} +0 -0
- /package/dist/cli/{chunk-xsrcwcjc.js → chunk-5mhvjytn.js} +0 -0
- /package/dist/cli/{chunk-ef39ym8f.js → chunk-7bgbansg.js} +0 -0
- /package/dist/cli/{chunk-nh4wvyz3.js → chunk-atn06ncf.js} +0 -0
- /package/dist/cli/{chunk-h4n8ffqy.js → chunk-dmzg9zdc.js} +0 -0
- /package/dist/cli/{chunk-w25b7xxz.js → chunk-etfh8f4z.js} +0 -0
- /package/dist/cli/{chunk-hape5vwj.js → chunk-hpay0ax8.js} +0 -0
- /package/dist/cli/{chunk-f10n6063.js → chunk-pn5ygqn6.js} +0 -0
- /package/dist/cli/{chunk-rcyasw34.js → chunk-rxqdw6s5.js} +0 -0
- /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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
28
|
-
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
41
|
-
|
|
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
|
-
// ../
|
|
60
|
+
// ../rove-daemon/src/daemon/paths.ts
|
|
45
61
|
function stateDirs(homeDir) {
|
|
46
|
-
return { canonical:
|
|
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 =
|
|
62
|
-
if (
|
|
77
|
+
const canonicalPath = join2(canonical, name);
|
|
78
|
+
if (pidIsLive(join2(canonical, pidName)))
|
|
63
79
|
return canonicalPath;
|
|
64
|
-
const legacyPath =
|
|
65
|
-
if (existsSync2(legacyPath) && pidIsLive(
|
|
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
|
|
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 =
|
|
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\\
|
|
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(
|
|
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(
|
|
142
|
+
return fitSocketPath(join2(runtimeDir, "rove-pty.sock"), runtimeDir, "pty");
|
|
118
143
|
}
|
|
119
144
|
const home = homedir();
|
|
120
|
-
return fitSocketPath(
|
|
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
|
|
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
|
-
// ../
|
|
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
|
|
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 =
|
|
187
|
-
for (const candidate of [
|
|
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
|
-
// ../
|
|
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
|
|
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:
|
|
445
|
+
env: spawnEnv
|
|
248
446
|
});
|
|
249
447
|
let settle = () => {};
|
|
250
|
-
const exited = new Promise((
|
|
251
|
-
settle =
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
470
|
+
// ../rove-daemon/src/daemon/pty-server.ts
|
|
273
471
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
274
|
-
import { mkdir as
|
|
472
|
+
import { mkdir as mkdir4, unlink } from "node:fs/promises";
|
|
275
473
|
import { createServer } from "node:net";
|
|
276
|
-
import { dirname as
|
|
474
|
+
import { dirname as dirname7 } from "node:path";
|
|
277
475
|
|
|
278
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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}-${
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
|
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(
|
|
681
|
+
return ensureOwnerOnlyDir(join5(homeDir, ROVE_STATE_DIR_BASENAME));
|
|
505
682
|
}
|
|
506
683
|
|
|
507
|
-
// ../
|
|
684
|
+
// ../rove-daemon/src/daemon/automation-contracts.ts
|
|
508
685
|
var ROUTINE_RESPONSE_WINDOW_MS = 2 * 60 * 60000;
|
|
509
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
715
|
+
// ../rove-daemon/src/daemon/protocol-compat.ts
|
|
539
716
|
var DAEMON_PROTOCOL_VERSION = 5;
|
|
540
717
|
|
|
541
|
-
// ../
|
|
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
|
-
// ../
|
|
560
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
573
|
-
const legacyDir =
|
|
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 =
|
|
579
|
-
const legacy =
|
|
755
|
+
const canonical = join6(canonicalDir, name);
|
|
756
|
+
const legacy = join6(legacyDir, name);
|
|
580
757
|
try {
|
|
581
|
-
if (
|
|
758
|
+
if (existsSync5(canonical))
|
|
582
759
|
continue;
|
|
583
760
|
const stat = lstatIfExists(legacy);
|
|
584
761
|
if (!stat || stat.isSymbolicLink())
|
|
585
762
|
continue;
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
// ../
|
|
598
|
-
import { randomUUID as
|
|
599
|
-
import { mkdirSync as
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
|
697
|
-
|
|
698
|
-
const tmp = `${path}.tmp-${process.pid}-${
|
|
699
|
-
|
|
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
|
-
|
|
882
|
+
renameSync4(tmp, path);
|
|
704
883
|
}
|
|
705
884
|
|
|
706
|
-
// ../
|
|
707
|
-
import { randomUUID as
|
|
708
|
-
import { mkdirSync as
|
|
709
|
-
import { join as
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
|
786
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
920
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1394
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
933
1395
|
} catch {}
|
|
934
1396
|
}
|
|
935
1397
|
|
|
936
|
-
// ../
|
|
937
|
-
import { randomUUID as
|
|
1398
|
+
// ../rove-daemon/src/daemon/pty-child-controller.ts
|
|
1399
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
938
1400
|
|
|
939
|
-
// ../
|
|
940
|
-
import { existsSync as
|
|
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 ??
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
1016
|
-
import { randomUUID as
|
|
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:
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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((
|
|
1155
|
-
timer = setTimeout(() =>
|
|
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
|
-
// ../
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1724
|
+
const text = buf.toString("latin1");
|
|
1725
|
+
const colorQueries = foldDefaultColorQueries(session.colorQueryCarry, text);
|
|
1260
1726
|
session.colorQueryCarry = colorQueries.carry;
|
|
1261
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
2720
|
+
// ../rove-daemon/src/daemon/socket-guard.ts
|
|
1707
2721
|
async function listenOnUnixSocket(server, socketPath) {
|
|
1708
|
-
await new Promise((
|
|
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
|
-
|
|
2727
|
+
resolve2();
|
|
1714
2728
|
});
|
|
1715
2729
|
});
|
|
1716
2730
|
if (!isWindowsPipePath(socketPath))
|
|
1717
2731
|
await tightenFilePermissions(socketPath);
|
|
1718
2732
|
}
|
|
1719
2733
|
|
|
1720
|
-
// ../
|
|
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.
|
|
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.
|
|
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
|
|
1824
|
-
await
|
|
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((
|
|
2877
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
1864
2878
|
if (!pipeSocket)
|
|
1865
|
-
await
|
|
1866
|
-
await
|
|
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
|
-
// ../
|
|
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
|
|
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
|
|
2951
|
+
log,
|
|
1940
2952
|
version: process.env.ROVE_PTY_HOST_VERSION,
|
|
1941
2953
|
onStop: () => process.exit(0)
|
|
1942
2954
|
});
|