@sma1lboy/rove 0.9.238 → 0.9.240
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-0mj358p5.js +3 -0
- package/dist/cli/{chunk-nntg9d85.js → chunk-0y4qgnby.js} +1 -1
- package/dist/cli/chunk-1brfxbvg.js +4 -0
- 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-2vgc5cyj.js +2 -0
- package/dist/cli/chunk-2x9yc5nx.js +2 -0
- package/dist/cli/chunk-30kjq5m7.js +2 -0
- package/dist/cli/{chunk-b503z32q.js → chunk-31y92jm8.js} +3 -3
- package/dist/cli/{chunk-mkksdcsw.js → chunk-3342qzve.js} +1 -1
- package/dist/cli/{chunk-0wgheepg.js → chunk-34261s0m.js} +3 -3
- 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-pzbrj3gd.js → chunk-3vyk7bzb.js} +1 -1
- package/dist/cli/chunk-41x1hrqe.js +3 -0
- package/dist/cli/chunk-4dv0mgan.js +2 -0
- package/dist/cli/{chunk-mhg40es1.js → chunk-4hestsjp.js} +1 -1
- package/dist/cli/{chunk-77w83wgh.js → chunk-4tx2kayk.js} +1 -1
- package/dist/cli/{chunk-aj7fkrha.js → chunk-4xj52nx1.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-1bm6sryf.js → chunk-5nsvp4h7.js} +1 -1
- package/dist/cli/{chunk-zyd6k6ak.js → chunk-7q43tysm.js} +1 -1
- package/dist/cli/chunk-801h8bze.js +4 -0
- package/dist/cli/chunk-80ydqr3s.js +3 -0
- package/dist/cli/{chunk-cv1kaq8a.js → chunk-85zr7kph.js} +5 -5
- package/dist/cli/{chunk-zwgns5ce.js → chunk-8tadwbx9.js} +1 -1
- 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-a0ave3g6.js +3 -0
- package/dist/cli/{chunk-vnpqemb0.js → chunk-a7kq09ke.js} +1 -1
- package/dist/cli/chunk-abyk3w2j.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-grt1e2s8.js → chunk-bb5zqmea.js} +2 -2
- package/dist/cli/{chunk-g56km26e.js → chunk-bn73kd6z.js} +1 -1
- package/dist/cli/chunk-c2m55b72.js +4 -0
- 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-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-mb1e73jm.js → chunk-ekd274v3.js} +2 -2
- package/dist/cli/{chunk-gqphk56d.js → chunk-ekg3h5z3.js} +7 -7
- package/dist/cli/chunk-f1nf99hj.js +2 -0
- 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-hp32tbes.js → chunk-fg62y5fm.js} +1 -1
- package/dist/cli/{chunk-s9a974w9.js → chunk-fvmee0tr.js} +1 -1
- package/dist/cli/{chunk-8h0khkap.js → chunk-fwqd5ta2.js} +2 -2
- package/dist/cli/{chunk-acw32w13.js → chunk-g0eygcnq.js} +6 -5
- 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-dqndd97s.js → chunk-gkdfct7t.js} +2 -2
- 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-3es6spp4.js → chunk-hq9tbzqa.js} +2 -2
- package/dist/cli/{chunk-snjte1rd.js → chunk-hr2xky15.js} +1 -1
- package/dist/cli/chunk-jxqs4y9j.js +10 -0
- package/dist/cli/{chunk-s0snbj93.js → chunk-kb6xw0fw.js} +1 -1
- package/dist/cli/{chunk-vyb5a2rh.js → chunk-kg9kmwt9.js} +1 -1
- package/dist/cli/chunk-ktzbg4jg.js +2 -0
- package/dist/cli/chunk-kz4txj2e.js +8 -0
- package/dist/cli/{chunk-bhqracn8.js → chunk-m19q29wk.js} +1 -1
- package/dist/cli/chunk-m1ekbqyz.js +2 -0
- package/dist/cli/chunk-m4f5h05c.js +10 -0
- package/dist/cli/chunk-m5gx53yr.js +3 -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-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-prqeddy7.js +3 -0
- package/dist/cli/chunk-pvm3fc44.js +2 -0
- package/dist/cli/{chunk-2yd128k1.js → chunk-q5eee34y.js} +6 -6
- package/dist/cli/{chunk-z5fnv7nk.js → chunk-q7zzycy7.js} +25 -23
- package/dist/cli/chunk-qvfdqrbf.js +3 -0
- package/dist/cli/{chunk-2xsem13q.js → chunk-qzsahxd9.js} +1 -1
- 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-bav389wj.js → chunk-svm809bx.js} +1 -1
- package/dist/cli/chunk-szrfp0jq.js +2 -0
- 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-thsmzarw.js +3 -0
- 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-0wh2ff77.js → chunk-whbm14qr.js} +2 -2
- package/dist/cli/chunk-wp9bygh4.js +2 -0
- package/dist/cli/{chunk-ww4fbx8h.js → chunk-wtb8cycx.js} +2 -2
- package/dist/cli/chunk-x9s14ppv.js +4 -0
- package/dist/cli/{chunk-hk8mc43r.js → chunk-xckpezd9.js} +5 -5
- 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/index.js +12 -10
- package/dist/cli/pty-host-node.mjs +880 -169
- 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-CQ-1WI4q.js → harness-BHiciz5A.js} +2 -2
- package/dist/web-ui/assets/{index-_ttAn6pY.js → index-BkxJISAI.js} +2 -2
- package/dist/web-ui/assets/{index-C4dLQEqf.css → index-nMJ42IGm.css} +1 -1
- package/dist/web-ui/index.html +2 -2
- package/dist/web-ui/origin-policy.mjs +1 -1
- package/dist/web-ui/pty-server.mjs +24 -10
- package/dist/web-ui/pty-session-lifecycle.mjs +9 -13
- package/package.json +6 -8
- package/dist/cli/chunk-05jej26n.js +0 -3
- package/dist/cli/chunk-079cyb2e.js +0 -4
- package/dist/cli/chunk-1265fmzk.js +0 -3
- package/dist/cli/chunk-1da3bqjc.js +0 -4
- package/dist/cli/chunk-1tx1s5v6.js +0 -2
- package/dist/cli/chunk-507zn8rs.js +0 -3
- package/dist/cli/chunk-53afdybj.js +0 -3
- package/dist/cli/chunk-544bwsss.js +0 -4
- package/dist/cli/chunk-67ev8k22.js +0 -2
- 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-9ek7xm94.js +0 -8
- package/dist/cli/chunk-9k47xr7m.js +0 -2
- package/dist/cli/chunk-9mtxxsb3.js +0 -2
- package/dist/cli/chunk-a1k2z44b.js +0 -2
- package/dist/cli/chunk-a3yyrabb.js +0 -2
- package/dist/cli/chunk-ahnk6zx3.js +0 -2
- package/dist/cli/chunk-bc18gat5.js +0 -4
- package/dist/cli/chunk-be757xzj.js +0 -3
- 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-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-hnfp317x.js +0 -2
- package/dist/cli/chunk-hq4b3fca.js +0 -3
- package/dist/cli/chunk-janvvpv5.js +0 -8
- 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-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-r8xqvg26.js +0 -2
- package/dist/cli/chunk-rz8zs7j8.js +0 -3
- package/dist/cli/chunk-sb0ns0c7.js +0 -3
- package/dist/cli/chunk-tcj36cqg.js +0 -2
- package/dist/cli/chunk-vejgnmcp.js +0 -2
- 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-y05d79az.js +0 -2
- package/dist/cli/chunk-y4048296.js +0 -4
- package/dist/cli/chunk-y8fqdnh3.js +0 -3
- package/dist/cli/chunk-ygd572ee.js +0 -4
- package/dist/cli/chunk-yj5pjxx2.js +0 -2
- 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/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,7 @@ 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
|
|
762
941
|
var DEFAULT_MODES = {
|
|
763
942
|
applicationCursorKeys: false,
|
|
764
943
|
applicationKeypad: false,
|
|
@@ -1030,7 +1209,7 @@ function ansiModeValue(modes, mode) {
|
|
|
1030
1209
|
}
|
|
1031
1210
|
}
|
|
1032
1211
|
|
|
1033
|
-
// ../
|
|
1212
|
+
// ../rove-daemon/src/daemon/pty-freeze-store.ts
|
|
1034
1213
|
var FREEZE_VERSION = 1;
|
|
1035
1214
|
function freezeSession(session, now = new Date) {
|
|
1036
1215
|
return {
|
|
@@ -1071,7 +1250,7 @@ function thawSession(record, cap) {
|
|
|
1071
1250
|
scanTerminalModes(modes, chunk.toString("latin1"));
|
|
1072
1251
|
return {
|
|
1073
1252
|
key: record.key,
|
|
1074
|
-
generation:
|
|
1253
|
+
generation: randomUUID4(),
|
|
1075
1254
|
cwd: record.cwd,
|
|
1076
1255
|
proc: null,
|
|
1077
1256
|
alive: false,
|
|
@@ -1098,7 +1277,7 @@ function thawSession(record, cap) {
|
|
|
1098
1277
|
};
|
|
1099
1278
|
}
|
|
1100
1279
|
function recordFile(dir, key) {
|
|
1101
|
-
return
|
|
1280
|
+
return join7(dir, `${encodeURIComponent(key)}.json`);
|
|
1102
1281
|
}
|
|
1103
1282
|
function parseRecord(raw) {
|
|
1104
1283
|
try {
|
|
@@ -1136,7 +1315,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
|
|
|
1136
1315
|
if (!name.endsWith(".json"))
|
|
1137
1316
|
continue;
|
|
1138
1317
|
try {
|
|
1139
|
-
const st = statSync2(
|
|
1318
|
+
const st = statSync2(join7(dir, name));
|
|
1140
1319
|
entries.push({ name, size: st.size, mtimeMs: st.mtimeMs });
|
|
1141
1320
|
} catch {}
|
|
1142
1321
|
}
|
|
@@ -1157,7 +1336,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
|
|
|
1157
1336
|
}
|
|
1158
1337
|
let record = null;
|
|
1159
1338
|
try {
|
|
1160
|
-
record = parseRecord(readFileSync3(
|
|
1339
|
+
record = parseRecord(readFileSync3(join7(dir, entry.name), "utf8"));
|
|
1161
1340
|
} catch {}
|
|
1162
1341
|
bytesRead += entry.size;
|
|
1163
1342
|
if (!record) {
|
|
@@ -1171,7 +1350,7 @@ function loadFrozenSessions(dir = defaultPtyFreezeDir(), now = Date.now(), repor
|
|
|
1171
1350
|
}
|
|
1172
1351
|
for (const name of stale) {
|
|
1173
1352
|
try {
|
|
1174
|
-
|
|
1353
|
+
rmSync2(join7(dir, name), { force: true });
|
|
1175
1354
|
} catch {}
|
|
1176
1355
|
}
|
|
1177
1356
|
kept.sort((a, b) => updatedAtMs(b) - updatedAtMs(a));
|
|
@@ -1188,7 +1367,7 @@ function tightenExistingPermissions(dir) {
|
|
|
1188
1367
|
}
|
|
1189
1368
|
for (const name of names) {
|
|
1190
1369
|
if (name.endsWith(".json"))
|
|
1191
|
-
tightenFilePermissionsSync(
|
|
1370
|
+
tightenFilePermissionsSync(join7(dir, name));
|
|
1192
1371
|
}
|
|
1193
1372
|
}
|
|
1194
1373
|
function fileFreezeSink(dir = defaultPtyFreezeDir()) {
|
|
@@ -1196,31 +1375,31 @@ function fileFreezeSink(dir = defaultPtyFreezeDir()) {
|
|
|
1196
1375
|
return {
|
|
1197
1376
|
save(record) {
|
|
1198
1377
|
try {
|
|
1199
|
-
|
|
1378
|
+
mkdirSync4(dir, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
|
|
1200
1379
|
const target = recordFile(dir, record.key);
|
|
1201
1380
|
const staging = `${target}.${process.pid}.tmp`;
|
|
1202
|
-
|
|
1203
|
-
|
|
1381
|
+
writeFileSync3(staging, JSON.stringify(record), { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
|
|
1382
|
+
renameSync5(staging, target);
|
|
1204
1383
|
} catch {}
|
|
1205
1384
|
},
|
|
1206
1385
|
drop(key) {
|
|
1207
1386
|
try {
|
|
1208
|
-
|
|
1387
|
+
rmSync2(recordFile(dir, key), { force: true });
|
|
1209
1388
|
} catch {}
|
|
1210
1389
|
}
|
|
1211
1390
|
};
|
|
1212
1391
|
}
|
|
1213
1392
|
function clearFrozenSessions(dir = defaultPtyFreezeDir()) {
|
|
1214
1393
|
try {
|
|
1215
|
-
|
|
1394
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
1216
1395
|
} catch {}
|
|
1217
1396
|
}
|
|
1218
1397
|
|
|
1219
|
-
// ../
|
|
1220
|
-
import { randomUUID as
|
|
1398
|
+
// ../rove-daemon/src/daemon/pty-child-controller.ts
|
|
1399
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
1221
1400
|
|
|
1222
|
-
// ../
|
|
1223
|
-
import { existsSync as
|
|
1401
|
+
// ../rove-daemon/src/daemon/platform-shell.js
|
|
1402
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1224
1403
|
function windowsBashCandidates(env) {
|
|
1225
1404
|
const roots = [env.ProgramFiles, env["ProgramFiles(x86)"], env.LOCALAPPDATA && `${env.LOCALAPPDATA}\\Programs`];
|
|
1226
1405
|
return roots.filter(Boolean).map((root) => `${root}\\Git\\bin\\bash.exe`);
|
|
@@ -1239,7 +1418,7 @@ function resolveLoginShell(options = {}) {
|
|
|
1239
1418
|
const fallback = options.fallback ?? "/bin/bash";
|
|
1240
1419
|
const platform = options.platform ?? process.platform;
|
|
1241
1420
|
const env = options.env ?? process.env;
|
|
1242
|
-
const exists = options.exists ??
|
|
1421
|
+
const exists = options.exists ?? existsSync6;
|
|
1243
1422
|
const cacheable = platform === process.platform && env === process.env && options.exists === undefined;
|
|
1244
1423
|
if (cacheable) {
|
|
1245
1424
|
const hit = cache.get(fallback);
|
|
@@ -1264,7 +1443,7 @@ function resolveUncached(fallback, platform, env, exists) {
|
|
|
1264
1443
|
return WINDOWS_BASH_FALLBACK;
|
|
1265
1444
|
}
|
|
1266
1445
|
|
|
1267
|
-
// ../
|
|
1446
|
+
// ../rove-daemon/src/daemon/pty-env.js
|
|
1268
1447
|
var IDENTITY_VARS = new Set([
|
|
1269
1448
|
"TERM_PROGRAM",
|
|
1270
1449
|
"TERM_PROGRAM_VERSION",
|
|
@@ -1295,13 +1474,13 @@ function embeddedTerminalEnv(base, overrides = {}) {
|
|
|
1295
1474
|
return { ...env, ...overrides };
|
|
1296
1475
|
}
|
|
1297
1476
|
|
|
1298
|
-
// ../
|
|
1299
|
-
import { randomUUID as
|
|
1477
|
+
// ../rove-daemon/src/daemon/pty-host-types.ts
|
|
1478
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1300
1479
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
1301
1480
|
function freshSessionState(key, spec, argv) {
|
|
1302
1481
|
return {
|
|
1303
1482
|
key,
|
|
1304
|
-
generation:
|
|
1483
|
+
generation: randomUUID5(),
|
|
1305
1484
|
cwd: spec.cwd,
|
|
1306
1485
|
proc: null,
|
|
1307
1486
|
alive: true,
|
|
@@ -1329,7 +1508,7 @@ function freshSessionState(key, spec, argv) {
|
|
|
1329
1508
|
};
|
|
1330
1509
|
}
|
|
1331
1510
|
|
|
1332
|
-
// ../
|
|
1511
|
+
// ../rove-daemon/src/daemon/pty-observability.ts
|
|
1333
1512
|
function sessionInfo(s) {
|
|
1334
1513
|
return {
|
|
1335
1514
|
key: s.key,
|
|
@@ -1432,13 +1611,13 @@ function peekRing(session, sinceOffset) {
|
|
|
1432
1611
|
};
|
|
1433
1612
|
}
|
|
1434
1613
|
|
|
1435
|
-
// ../
|
|
1614
|
+
// ../rove-daemon/src/daemon/pty-termination.ts
|
|
1436
1615
|
async function settledWithin(exited, ms) {
|
|
1437
1616
|
let timer;
|
|
1438
1617
|
const settled = await Promise.race([
|
|
1439
1618
|
exited.then(() => true, () => true),
|
|
1440
|
-
new Promise((
|
|
1441
|
-
timer = setTimeout(() =>
|
|
1619
|
+
new Promise((resolve2) => {
|
|
1620
|
+
timer = setTimeout(() => resolve2(false), ms);
|
|
1442
1621
|
})
|
|
1443
1622
|
]);
|
|
1444
1623
|
if (timer)
|
|
@@ -1475,7 +1654,7 @@ async function terminatePtyChild(proc, onSettled, onSignal) {
|
|
|
1475
1654
|
onSettled();
|
|
1476
1655
|
}
|
|
1477
1656
|
|
|
1478
|
-
// ../
|
|
1657
|
+
// ../rove-daemon/src/daemon/pty-child-controller.ts
|
|
1479
1658
|
class PtyChildController {
|
|
1480
1659
|
deps;
|
|
1481
1660
|
constructor(deps) {
|
|
@@ -1492,7 +1671,7 @@ class PtyChildController {
|
|
|
1492
1671
|
return session;
|
|
1493
1672
|
}
|
|
1494
1673
|
startChild(session) {
|
|
1495
|
-
session.generation =
|
|
1674
|
+
session.generation = randomUUID6();
|
|
1496
1675
|
try {
|
|
1497
1676
|
session.proc = (this.deps.driver ?? bunTerminalDriver())({
|
|
1498
1677
|
argv: [...session.command],
|
|
@@ -1502,7 +1681,7 @@ class PtyChildController {
|
|
|
1502
1681
|
COLUMNS: String(session.cols),
|
|
1503
1682
|
LINES: String(session.rows),
|
|
1504
1683
|
BASH_SILENCE_DEPRECATION_WARNING: "1",
|
|
1505
|
-
|
|
1684
|
+
ROVE_TERMINAL_PTY: "1"
|
|
1506
1685
|
}),
|
|
1507
1686
|
cols: session.cols,
|
|
1508
1687
|
rows: session.rows,
|
|
@@ -1569,7 +1748,7 @@ class PtyChildController {
|
|
|
1569
1748
|
}
|
|
1570
1749
|
}
|
|
1571
1750
|
|
|
1572
|
-
// ../
|
|
1751
|
+
// ../rove-daemon/src/daemon/pty-freeze-policy.ts
|
|
1573
1752
|
var FREEZE_INTERVAL_MS = 5000;
|
|
1574
1753
|
var FREEZE_MIN_APPENDED_BYTES = 64 * 1024;
|
|
1575
1754
|
var FREEZE_STALE_MS = 60000;
|
|
@@ -1583,7 +1762,7 @@ function shouldFreeze(session, now) {
|
|
|
1583
1762
|
return appended >= FREEZE_MIN_APPENDED_BYTES || since >= FREEZE_STALE_MS;
|
|
1584
1763
|
}
|
|
1585
1764
|
|
|
1586
|
-
// ../
|
|
1765
|
+
// ../rove-daemon/src/daemon/pty-warm.ts
|
|
1587
1766
|
class WarmSpare {
|
|
1588
1767
|
deps;
|
|
1589
1768
|
spare = null;
|
|
@@ -1636,7 +1815,7 @@ class WarmSpare {
|
|
|
1636
1815
|
}
|
|
1637
1816
|
}
|
|
1638
1817
|
|
|
1639
|
-
// ../
|
|
1818
|
+
// ../rove-daemon/src/daemon/pty-host.ts
|
|
1640
1819
|
var LOGGED_WRITE_BYTES = 1024;
|
|
1641
1820
|
var DEFAULT_SCROLLBACK_CAP = 512 * 1024;
|
|
1642
1821
|
var EXIT_TAIL_BYTES = 16 * 1024;
|
|
@@ -1907,7 +2086,533 @@ class PtyHost {
|
|
|
1907
2086
|
}
|
|
1908
2087
|
}
|
|
1909
2088
|
|
|
1910
|
-
// ../
|
|
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
|
|
1911
2616
|
function objectPayload(payload) {
|
|
1912
2617
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1913
2618
|
return {};
|
|
@@ -1928,7 +2633,7 @@ function optionalBoolean(payload, key) {
|
|
|
1928
2633
|
return value;
|
|
1929
2634
|
}
|
|
1930
2635
|
|
|
1931
|
-
// ../
|
|
2636
|
+
// ../rove-daemon/src/daemon/pty-server-verbs.ts
|
|
1932
2637
|
function dispatchPtyRequest(req, client, deps) {
|
|
1933
2638
|
const { ptys } = deps;
|
|
1934
2639
|
switch (req.name) {
|
|
@@ -1996,6 +2701,14 @@ function dispatchPtyRequest(req, client, deps) {
|
|
|
1996
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);
|
|
1997
2702
|
return {};
|
|
1998
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
|
+
}
|
|
1999
2712
|
case "daemon.stop":
|
|
2000
2713
|
deps.requestStop();
|
|
2001
2714
|
return {};
|
|
@@ -2004,32 +2717,32 @@ function dispatchPtyRequest(req, client, deps) {
|
|
|
2004
2717
|
}
|
|
2005
2718
|
}
|
|
2006
2719
|
|
|
2007
|
-
// ../
|
|
2720
|
+
// ../rove-daemon/src/daemon/socket-guard.ts
|
|
2008
2721
|
async function listenOnUnixSocket(server, socketPath) {
|
|
2009
|
-
await new Promise((
|
|
2722
|
+
await new Promise((resolve2, reject) => {
|
|
2010
2723
|
const evented = server;
|
|
2011
2724
|
evented.once("error", reject);
|
|
2012
2725
|
server.listen(socketPath, () => {
|
|
2013
2726
|
evented.removeListener("error", reject);
|
|
2014
|
-
|
|
2727
|
+
resolve2();
|
|
2015
2728
|
});
|
|
2016
2729
|
});
|
|
2017
2730
|
if (!isWindowsPipePath(socketPath))
|
|
2018
2731
|
await tightenFilePermissions(socketPath);
|
|
2019
2732
|
}
|
|
2020
2733
|
|
|
2021
|
-
// ../
|
|
2734
|
+
// ../rove-daemon/src/daemon/pty-server.ts
|
|
2022
2735
|
var DEFAULT_IDLE_EXIT_MS = 60000;
|
|
2023
2736
|
var DEFAULT_ORPHAN_CHECK_MS = 30000;
|
|
2024
2737
|
function resolveMaxLifetimeMs() {
|
|
2025
|
-
const raw = process.env.
|
|
2738
|
+
const raw = process.env.ROVE_PTY_MAX_LIFETIME_MS;
|
|
2026
2739
|
if (raw === undefined)
|
|
2027
2740
|
return null;
|
|
2028
2741
|
const n = Number(raw);
|
|
2029
2742
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
2030
2743
|
}
|
|
2031
2744
|
function resolveIdleExitMs() {
|
|
2032
|
-
const raw = process.env.
|
|
2745
|
+
const raw = process.env.ROVE_PTY_IDLE_EXIT_MS;
|
|
2033
2746
|
if (raw === undefined)
|
|
2034
2747
|
return DEFAULT_IDLE_EXIT_MS;
|
|
2035
2748
|
const n = Number(raw);
|
|
@@ -2121,8 +2834,8 @@ async function startPtyHostServer(options = {}) {
|
|
|
2121
2834
|
const pipeSocket = isWindowsPipePath(socketPath);
|
|
2122
2835
|
await ensureOwnerOnlyStateDir(resolveDaemonHomeDir());
|
|
2123
2836
|
if (!pipeSocket)
|
|
2124
|
-
await
|
|
2125
|
-
await
|
|
2837
|
+
await mkdir4(dirname7(socketPath), { recursive: true });
|
|
2838
|
+
await mkdir4(dirname7(pidPath), { recursive: true });
|
|
2126
2839
|
const server = createServer((socket) => {
|
|
2127
2840
|
const client = {
|
|
2128
2841
|
socket,
|
|
@@ -2161,10 +2874,10 @@ async function startPtyHostServer(options = {}) {
|
|
|
2161
2874
|
clearFrozenSessions(freezeDir);
|
|
2162
2875
|
for (const client of Array.from(clients))
|
|
2163
2876
|
client.socket.destroy();
|
|
2164
|
-
await new Promise((
|
|
2877
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
2165
2878
|
if (!pipeSocket)
|
|
2166
|
-
await
|
|
2167
|
-
await
|
|
2879
|
+
await unlink(socketPath).catch(() => {});
|
|
2880
|
+
await unlink(pidPath).catch(() => {});
|
|
2168
2881
|
}
|
|
2169
2882
|
};
|
|
2170
2883
|
async function stop() {
|
|
@@ -2216,11 +2929,6 @@ async function startPtyHostServer(options = {}) {
|
|
|
2216
2929
|
await listenOnUnixSocket(server, socketPath);
|
|
2217
2930
|
await writeTextAtomic(pidPath, `${process.pid}
|
|
2218
2931
|
`);
|
|
2219
|
-
if (!pipeSocket) {
|
|
2220
|
-
const home = resolveDaemonHomeDir();
|
|
2221
|
-
await linkLegacyRuntimePath(socketPath, legacyPtyHostSocketPath(home));
|
|
2222
|
-
await linkLegacyRuntimePath(pidPath, legacyPtyHostPidPath(home));
|
|
2223
|
-
}
|
|
2224
2932
|
armIdle();
|
|
2225
2933
|
log("boot", `pty host listening on ${socketPath}`);
|
|
2226
2934
|
return api;
|
|
@@ -2229,15 +2937,18 @@ function writeFrame(client, frame) {
|
|
|
2229
2937
|
client.writer.write(frameToLine(frame));
|
|
2230
2938
|
}
|
|
2231
2939
|
|
|
2232
|
-
// ../
|
|
2940
|
+
// ../rove-daemon/src/daemon/pty-host-node-entry.ts
|
|
2233
2941
|
async function main() {
|
|
2234
2942
|
rotateLogIfNeeded(defaultPtyHostLogPath());
|
|
2235
2943
|
process.on("uncaughtException", (err) => console.error(formatPtyHostLine("crash", err?.stack ?? String(err))));
|
|
2236
2944
|
process.on("unhandledRejection", (err) => console.error(formatPtyHostLine("reject", String(err))));
|
|
2237
|
-
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);
|
|
2238
2949
|
const server = await startPtyHostServer({
|
|
2239
2950
|
driver,
|
|
2240
|
-
log
|
|
2951
|
+
log,
|
|
2241
2952
|
version: process.env.ROVE_PTY_HOST_VERSION,
|
|
2242
2953
|
onStop: () => process.exit(0)
|
|
2243
2954
|
});
|