@slayzone/computer 0.36.0-beta.21 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bin.cjs +2131 -1054
  2. package/dist/bin.cjs.map +4 -4
  3. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -6,11 +6,20 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ var __esm = (fn, res, err) => function __init() {
10
+ if (err) throw err[0];
11
+ try {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ } catch (e) {
14
+ throw err = [e], e;
15
+ }
11
16
  };
12
17
  var __commonJS = (cb, mod) => function __require() {
13
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
+ try {
19
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
20
+ } catch (e) {
21
+ throw mod = 0, e;
22
+ }
14
23
  };
15
24
  var __export = (target, all) => {
16
25
  for (var name in all)
@@ -98,10 +107,10 @@ var require_constants = __commonJS({
98
107
  EMPTY_BUFFER: Buffer.alloc(0),
99
108
  GUID: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
100
109
  hasBlob,
101
- kForOnEventAttribute: Symbol("kIsForOnEventAttribute"),
102
- kListener: Symbol("kListener"),
103
- kStatusCode: Symbol("status-code"),
104
- kWebSocket: Symbol("websocket"),
110
+ kForOnEventAttribute: /* @__PURE__ */ Symbol("kIsForOnEventAttribute"),
111
+ kListener: /* @__PURE__ */ Symbol("kListener"),
112
+ kStatusCode: /* @__PURE__ */ Symbol("status-code"),
113
+ kWebSocket: /* @__PURE__ */ Symbol("websocket"),
105
114
  NOOP: () => {
106
115
  }
107
116
  };
@@ -187,8 +196,8 @@ var require_buffer_util = __commonJS({
187
196
  var require_limiter = __commonJS({
188
197
  "../../../node_modules/ws/lib/limiter.js"(exports2, module2) {
189
198
  "use strict";
190
- var kDone = Symbol("kDone");
191
- var kRun = Symbol("kRun");
199
+ var kDone = /* @__PURE__ */ Symbol("kDone");
200
+ var kRun = /* @__PURE__ */ Symbol("kRun");
192
201
  var Limiter = class {
193
202
  /**
194
203
  * Creates a new `Limiter`.
@@ -243,11 +252,11 @@ var require_permessage_deflate = __commonJS({
243
252
  var { kStatusCode } = require_constants();
244
253
  var FastBuffer = Buffer[Symbol.species];
245
254
  var TRAILER = Buffer.from([0, 0, 255, 255]);
246
- var kPerMessageDeflate = Symbol("permessage-deflate");
247
- var kTotalLength = Symbol("total-length");
248
- var kCallback = Symbol("callback");
249
- var kBuffers = Symbol("buffers");
250
- var kError = Symbol("error");
255
+ var kPerMessageDeflate = /* @__PURE__ */ Symbol("permessage-deflate");
256
+ var kTotalLength = /* @__PURE__ */ Symbol("total-length");
257
+ var kCallback = /* @__PURE__ */ Symbol("callback");
258
+ var kBuffers = /* @__PURE__ */ Symbol("buffers");
259
+ var kError = /* @__PURE__ */ Symbol("error");
251
260
  var zlibLimiter;
252
261
  var PerMessageDeflate2 = class {
253
262
  /**
@@ -1453,7 +1462,7 @@ var require_sender = __commonJS({
1453
1462
  var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants();
1454
1463
  var { isBlob, isValidStatusCode } = require_validation();
1455
1464
  var { mask: applyMask, toBuffer } = require_buffer_util();
1456
- var kByteLength = Symbol("kByteLength");
1465
+ var kByteLength = /* @__PURE__ */ Symbol("kByteLength");
1457
1466
  var maskBuffer = Buffer.alloc(4);
1458
1467
  var RANDOM_POOL_SIZE = 8 * 1024;
1459
1468
  var randomPool;
@@ -1938,14 +1947,14 @@ var require_event_target = __commonJS({
1938
1947
  "../../../node_modules/ws/lib/event-target.js"(exports2, module2) {
1939
1948
  "use strict";
1940
1949
  var { kForOnEventAttribute, kListener } = require_constants();
1941
- var kCode = Symbol("kCode");
1942
- var kData = Symbol("kData");
1943
- var kError = Symbol("kError");
1944
- var kMessage = Symbol("kMessage");
1945
- var kReason = Symbol("kReason");
1946
- var kTarget = Symbol("kTarget");
1947
- var kType = Symbol("kType");
1948
- var kWasClean = Symbol("kWasClean");
1950
+ var kCode = /* @__PURE__ */ Symbol("kCode");
1951
+ var kData = /* @__PURE__ */ Symbol("kData");
1952
+ var kError = /* @__PURE__ */ Symbol("kError");
1953
+ var kMessage = /* @__PURE__ */ Symbol("kMessage");
1954
+ var kReason = /* @__PURE__ */ Symbol("kReason");
1955
+ var kTarget = /* @__PURE__ */ Symbol("kTarget");
1956
+ var kType = /* @__PURE__ */ Symbol("kType");
1957
+ var kWasClean = /* @__PURE__ */ Symbol("kWasClean");
1949
1958
  var Event = class {
1950
1959
  /**
1951
1960
  * Create a new `Event`.
@@ -2347,7 +2356,7 @@ var require_websocket = __commonJS({
2347
2356
  } = require_event_target();
2348
2357
  var { format, parse: parse3 } = require_extension();
2349
2358
  var { toBuffer } = require_buffer_util();
2350
- var kAborted = Symbol("kAborted");
2359
+ var kAborted = /* @__PURE__ */ Symbol("kAborted");
2351
2360
  var protocolVersions = [8, 13];
2352
2361
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
2353
2362
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
@@ -2380,11 +2389,23 @@ var require_websocket = __commonJS({
2380
2389
  this._isServer = false;
2381
2390
  this._redirects = 0;
2382
2391
  if (protocols === void 0) {
2383
- protocols = [];
2392
+ if (!options || options.protocols === void 0) {
2393
+ protocols = [];
2394
+ } else if (Array.isArray(options.protocols)) {
2395
+ protocols = options.protocols;
2396
+ } else {
2397
+ protocols = [options.protocols];
2398
+ }
2384
2399
  } else if (!Array.isArray(protocols)) {
2385
2400
  if (typeof protocols === "object" && protocols !== null) {
2386
2401
  options = protocols;
2387
- protocols = [];
2402
+ if (options.protocols === void 0) {
2403
+ protocols = [];
2404
+ } else if (Array.isArray(options.protocols)) {
2405
+ protocols = options.protocols;
2406
+ } else {
2407
+ protocols = [options.protocols];
2408
+ }
2388
2409
  } else {
2389
2410
  protocols = [protocols];
2390
2411
  }
@@ -2581,7 +2602,6 @@ var require_websocket = __commonJS({
2581
2602
  }
2582
2603
  return;
2583
2604
  }
2584
- this._readyState = _WebSocket.CLOSING;
2585
2605
  this._sender.close(code, data, !this._isServer, (err) => {
2586
2606
  if (err) return;
2587
2607
  this._closeFrameSent = true;
@@ -2589,6 +2609,7 @@ var require_websocket = __commonJS({
2589
2609
  this._socket.end();
2590
2610
  }
2591
2611
  });
2612
+ this._readyState = _WebSocket.CLOSING;
2592
2613
  setCloseTimer(this);
2593
2614
  }
2594
2615
  /**
@@ -2813,6 +2834,7 @@ var require_websocket = __commonJS({
2813
2834
  socketPath: void 0,
2814
2835
  hostname: void 0,
2815
2836
  protocol: void 0,
2837
+ protocols: void 0,
2816
2838
  timeout: void 0,
2817
2839
  method: "GET",
2818
2840
  host: void 0,
@@ -3755,61 +3777,347 @@ var require_websocket_server = __commonJS({
3755
3777
  }
3756
3778
  });
3757
3779
 
3758
- // ../../../node_modules/ignore/index.js
3780
+ // ../../shared/platform/src/fs-utils.ts
3781
+ async function writeFileIfChanged(filePath, content, mode) {
3782
+ const target = await resolveTarget(filePath);
3783
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
3784
+ const existing = await safeReadFile(target);
3785
+ if (existing && existing.equals(buf)) return false;
3786
+ await import_promises3.default.mkdir(import_path2.default.dirname(target), { recursive: true });
3787
+ const tmp = `${target}.tmp.${import_crypto.default.randomBytes(6).toString("hex")}`;
3788
+ await import_promises3.default.writeFile(tmp, buf);
3789
+ if (mode != null && process.platform !== "win32") {
3790
+ await import_promises3.default.chmod(tmp, mode);
3791
+ }
3792
+ await import_promises3.default.rename(tmp, target);
3793
+ return true;
3794
+ }
3795
+ async function updateFileAtomically(filePath, merge2, opts = {}) {
3796
+ const { mode, retries = 5 } = opts;
3797
+ const target = await resolveTarget(filePath);
3798
+ await import_promises3.default.mkdir(import_path2.default.dirname(target), { recursive: true });
3799
+ const release = await acquireLock(`${target}.lock`);
3800
+ try {
3801
+ for (let attempt = 0; ; attempt++) {
3802
+ const baseline = await safeReadFile(target);
3803
+ const merged = await merge2(baseline);
3804
+ if (merged == null) return false;
3805
+ const buf = Buffer.isBuffer(merged) ? merged : Buffer.from(merged, "utf8");
3806
+ if (baseline && baseline.equals(buf)) return false;
3807
+ const tmp = `${target}.tmp.${import_crypto.default.randomBytes(6).toString("hex")}`;
3808
+ await import_promises3.default.writeFile(tmp, buf);
3809
+ if (mode != null && process.platform !== "win32") {
3810
+ await import_promises3.default.chmod(tmp, mode);
3811
+ }
3812
+ if (sameBytes(baseline, await safeReadFile(target))) {
3813
+ await import_promises3.default.rename(tmp, target);
3814
+ return true;
3815
+ }
3816
+ await import_promises3.default.rm(tmp, { force: true });
3817
+ if (attempt >= retries) {
3818
+ throw new Error(
3819
+ `[fs-utils] updateFileAtomically: ${target} kept changing underneath ${retries + 1} merge attempts \u2014 giving up rather than writing a stale merge`
3820
+ );
3821
+ }
3822
+ await sleep(2 ** attempt + Math.floor(Math.random() * 5));
3823
+ }
3824
+ } finally {
3825
+ await release();
3826
+ }
3827
+ }
3828
+ async function acquireLock(lockPath) {
3829
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
3830
+ for (; ; ) {
3831
+ try {
3832
+ const handle = await import_promises3.default.open(lockPath, "wx");
3833
+ await handle.writeFile(`${process.pid} ${(/* @__PURE__ */ new Date()).toISOString()}
3834
+ `);
3835
+ await handle.close();
3836
+ return async () => {
3837
+ await import_promises3.default.rm(lockPath, { force: true });
3838
+ };
3839
+ } catch (err) {
3840
+ if (!isCode(err, "EEXIST")) throw err;
3841
+ const age = await lockAgeMs(lockPath);
3842
+ if (age === null) continue;
3843
+ if (age > LOCK_STALE_MS) {
3844
+ await import_promises3.default.rm(lockPath, { force: true });
3845
+ continue;
3846
+ }
3847
+ if (Date.now() > deadline) {
3848
+ throw new Error(`[fs-utils] timed out after ${LOCK_TIMEOUT_MS}ms waiting for ${lockPath}`);
3849
+ }
3850
+ await sleep(5 + Math.floor(Math.random() * 10));
3851
+ }
3852
+ }
3853
+ }
3854
+ async function lockAgeMs(lockPath) {
3855
+ try {
3856
+ return Date.now() - (await import_promises3.default.stat(lockPath)).mtimeMs;
3857
+ } catch (err) {
3858
+ if (isCode(err, "ENOENT")) return null;
3859
+ throw err;
3860
+ }
3861
+ }
3862
+ function sameBytes(a, b) {
3863
+ if (a === null || b === null) return a === b;
3864
+ return a.equals(b);
3865
+ }
3866
+ function sleep(ms) {
3867
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
3868
+ }
3869
+ async function safeReadFile(p) {
3870
+ try {
3871
+ return await import_promises3.default.readFile(p);
3872
+ } catch (err) {
3873
+ if (isENOENT(err)) return null;
3874
+ throw err;
3875
+ }
3876
+ }
3877
+ async function resolveTarget(p) {
3878
+ try {
3879
+ const stat2 = await import_promises3.default.lstat(p);
3880
+ if (stat2.isSymbolicLink()) return await import_promises3.default.realpath(p);
3881
+ } catch (err) {
3882
+ if (!isENOENT(err)) throw err;
3883
+ }
3884
+ return p;
3885
+ }
3886
+ function isENOENT(err) {
3887
+ return isCode(err, "ENOENT");
3888
+ }
3889
+ function isCode(err, code) {
3890
+ return typeof err === "object" && err != null && err.code === code;
3891
+ }
3892
+ var import_promises3, import_path2, import_crypto, LOCK_STALE_MS, LOCK_TIMEOUT_MS;
3893
+ var init_fs_utils = __esm({
3894
+ "../../shared/platform/src/fs-utils.ts"() {
3895
+ "use strict";
3896
+ import_promises3 = __toESM(require("fs/promises"), 1);
3897
+ import_path2 = __toESM(require("path"), 1);
3898
+ import_crypto = __toESM(require("crypto"), 1);
3899
+ LOCK_STALE_MS = 1e4;
3900
+ LOCK_TIMEOUT_MS = 5e3;
3901
+ }
3902
+ });
3903
+
3904
+ // ../../domains/file-editor/node_modules/ignore/index.js
3759
3905
  var require_ignore = __commonJS({
3760
- "../../../node_modules/ignore/index.js"(exports2, module2) {
3906
+ "../../domains/file-editor/node_modules/ignore/index.js"(exports2, module2) {
3761
3907
  function makeArray(subject) {
3762
3908
  return Array.isArray(subject) ? subject : [subject];
3763
3909
  }
3910
+ var UNDEFINED = void 0;
3764
3911
  var EMPTY = "";
3765
3912
  var SPACE = " ";
3766
3913
  var ESCAPE = "\\";
3767
- var REGEX_TEST_BLANK_LINE = /^\s+$/;
3914
+ var REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/;
3915
+ var REGEX_TEST_BLANK_LINE = /^\uFEFF? *$/;
3768
3916
  var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
3769
3917
  var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
3770
3918
  var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
3771
3919
  var REGEX_SPLITALL_CRLF = /\r?\n/g;
3772
- var REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
3920
+ var DOUBLE_SLASH = "//";
3921
+ var SLASH_CODE = 47;
3922
+ var DOT_CODE = 46;
3773
3923
  var SLASH = "/";
3774
3924
  var TMP_KEY_IGNORE = "node-ignore";
3775
3925
  if (typeof Symbol !== "undefined") {
3776
- TMP_KEY_IGNORE = Symbol.for("node-ignore");
3926
+ TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
3777
3927
  }
3778
3928
  var KEY_IGNORE = TMP_KEY_IGNORE;
3779
- var define = (object2, key, value) => Object.defineProperty(object2, key, { value });
3780
- var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
3929
+ var define = (object2, key, value) => {
3930
+ Object.defineProperty(object2, key, { value });
3931
+ return value;
3932
+ };
3781
3933
  var RETURN_FALSE = () => false;
3782
- var sanitizeRange = (range) => range.replace(
3783
- REGEX_REGEXP_RANGE,
3784
- (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
3785
- );
3786
3934
  var cleanRangeBackSlash = (slashes) => {
3787
3935
  const { length } = slashes;
3788
3936
  return slashes.slice(0, length - length % 2);
3789
3937
  };
3938
+ var POSIX_CLASSES = {
3939
+ alnum: "0-9A-Za-z",
3940
+ alpha: "A-Za-z",
3941
+ blank: " \\t",
3942
+ cntrl: "\\x00-\\x1f\\x7f",
3943
+ digit: "0-9",
3944
+ graph: "!-.0-~",
3945
+ lower: "a-z",
3946
+ print: " -.0-~",
3947
+ punct: "!-.:-@\\[-`{-~",
3948
+ // git's `sane-ctype.h` classifies \v and \f as control, not space,
3949
+ // unlike C's `isspace`
3950
+ space: " \\t\\n\\r",
3951
+ upper: "A-Z",
3952
+ xdigit: "0-9A-Fa-f"
3953
+ };
3954
+ var CLASS_MEMBERS_TO_ESCAPE = "\\]^-[";
3955
+ var escapeMember = (char) => CLASS_MEMBERS_TO_ESCAPE.indexOf(char) < 0 ? char : ESCAPE + char;
3956
+ var NON_SLASH = "(?!\\/)";
3957
+ var classSource = (negated, body) => {
3958
+ if (negated) {
3959
+ return `[^\\/${body}]`;
3960
+ }
3961
+ const source = `[${body}]`;
3962
+ return new RegExp(source).test("/") ? NON_SLASH + source : source;
3963
+ };
3964
+ var scanBracket = (pattern, start) => {
3965
+ const { length } = pattern;
3966
+ let index = start + 1;
3967
+ let negated = EMPTY;
3968
+ const lead = pattern[index];
3969
+ if (lead === "!" || lead === "^") {
3970
+ negated = "^";
3971
+ index++;
3972
+ }
3973
+ let body = EMPTY;
3974
+ let prev = EMPTY;
3975
+ for (; ; ) {
3976
+ const char = pattern[index];
3977
+ if (char === UNDEFINED) {
3978
+ return null;
3979
+ }
3980
+ if (char === ESCAPE) {
3981
+ const escaped = pattern[index + 1];
3982
+ if (escaped === UNDEFINED) {
3983
+ return null;
3984
+ }
3985
+ body += escapeMember(escaped);
3986
+ prev = escaped;
3987
+ index++;
3988
+ } else if (char === "-" && prev && index + 1 < length && pattern[index + 1] !== "]") {
3989
+ index++;
3990
+ let to = pattern[index];
3991
+ if (to === ESCAPE) {
3992
+ to = pattern[index += 1];
3993
+ }
3994
+ if (prev <= to) {
3995
+ body += `-${escapeMember(to)}`;
3996
+ }
3997
+ prev = EMPTY;
3998
+ } else if (char === "[" && pattern[index + 1] === ":") {
3999
+ const nameStart = index + 2;
4000
+ let end = nameStart;
4001
+ while (end < length && pattern[end] !== "]") {
4002
+ end++;
4003
+ }
4004
+ if (end === length) {
4005
+ return null;
4006
+ }
4007
+ if (end > nameStart && pattern[end - 1] === ":") {
4008
+ const expanded = POSIX_CLASSES[pattern.slice(nameStart, end - 1)];
4009
+ if (expanded === UNDEFINED) {
4010
+ return null;
4011
+ }
4012
+ body += expanded;
4013
+ prev = EMPTY;
4014
+ index = end;
4015
+ } else {
4016
+ body += escapeMember("[");
4017
+ prev = "[";
4018
+ index = nameStart - 2;
4019
+ }
4020
+ } else {
4021
+ body += escapeMember(char);
4022
+ prev = char;
4023
+ }
4024
+ index++;
4025
+ if (pattern[index] === "]") {
4026
+ return {
4027
+ end: index,
4028
+ source: classSource(negated, body)
4029
+ };
4030
+ }
4031
+ }
4032
+ };
4033
+ var NEVER_MATCH = "[]";
4034
+ var PLACEHOLDER = "\0";
4035
+ var REGEX_RESTORE_PLACEHOLDER = new RegExp(
4036
+ `${PLACEHOLDER}(\\d+)${PLACEHOLDER}`,
4037
+ "g"
4038
+ );
4039
+ var TRAILING_WILDCARD = "\uE000";
4040
+ var extractBrackets = (pattern) => {
4041
+ const sources = [];
4042
+ const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
4043
+ const { length } = pattern;
4044
+ let out = EMPTY;
4045
+ let index = 0;
4046
+ while (index < length) {
4047
+ const char = pattern[index];
4048
+ if (char === ESCAPE) {
4049
+ const escaped = pattern[index + 1];
4050
+ if (escaped === "*" || escaped === "[" || escaped === SPACE || escaped === ESCAPE) {
4051
+ out += pattern.slice(index, index + 2);
4052
+ } else {
4053
+ out += hold(
4054
+ REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : escaped
4055
+ );
4056
+ }
4057
+ index += 2;
4058
+ } else if (char === PLACEHOLDER) {
4059
+ out += hold(`[${PLACEHOLDER}]`);
4060
+ index++;
4061
+ } else if (char === "[") {
4062
+ const scanned = scanBracket(pattern, index);
4063
+ if (scanned === null) {
4064
+ out += hold(NEVER_MATCH);
4065
+ index = length;
4066
+ } else {
4067
+ out += hold(scanned.source);
4068
+ index = scanned.end + 1;
4069
+ }
4070
+ } else {
4071
+ out += char;
4072
+ index++;
4073
+ }
4074
+ }
4075
+ return {
4076
+ source: out,
4077
+ sources
4078
+ };
4079
+ };
4080
+ var DIRECT = null;
4081
+ var REGEX_INNER_SLASH = /\/(?!$)/;
3790
4082
  var REPLACERS = [
3791
4083
  [
3792
- // remove BOM
4084
+ // Remove BOM
3793
4085
  // TODO:
3794
4086
  // Other similar zero-width characters?
3795
4087
  /^\uFEFF/,
4088
+ () => EMPTY,
4089
+ "\uFEFF"
4090
+ ],
4091
+ [
4092
+ // A trailing line terminator, left on when a whole file's contents are
4093
+ // added as one pattern rather than split into lines. git never sees one
4094
+ // -- it reads a `.gitignore` line by line -- so it is not part of the
4095
+ // pattern and is dropped here, apart from the trailing-space trimming,
4096
+ // which follows git in touching spaces and nothing else.
4097
+ /[\r\n]+$/,
3796
4098
  () => EMPTY
3797
4099
  ],
3798
4100
  // > Trailing spaces are ignored unless they are quoted with backslash ("\")
3799
4101
  [
4102
+ // Only spaces, never tabs or other whitespace: git trims a trailing run
4103
+ // of `' '` and nothing else (dir.c, `trim_trailing_spaces`, a single
4104
+ // `case ' '`), so a pattern ending in a tab keeps it as a literal.
3800
4105
  // (a\ ) -> (a )
3801
4106
  // (a ) -> (a)
3802
4107
  // (a ) -> (a)
3803
4108
  // (a \ ) -> (a )
3804
- /((?:\\\\)*?)(\\?\s+)$/,
4109
+ /((?:\\\\)*?)(\\? +)$/,
3805
4110
  (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
3806
4111
  ],
3807
- // replace (\ ) with ' '
4112
+ // Replace (\ ) with ' '
4113
+ // Only a space: an escaped tab or other whitespace is already a literal by
4114
+ // the time it reaches here, and a bare tab must be left as one, not turned
4115
+ // into a space.
3808
4116
  // (\ ) -> ' '
3809
4117
  // (\\ ) -> '\\ '
3810
4118
  // (\\\ ) -> '\\ '
3811
4119
  [
3812
- /(\\+?)\s/g,
4120
+ /(\\+?) /g,
3813
4121
  (_, m1) => {
3814
4122
  const { length } = m1;
3815
4123
  return m1.slice(0, length - length % 2) + SPACE;
@@ -3838,7 +4146,8 @@ var require_ignore = __commonJS({
3838
4146
  [
3839
4147
  // > a question mark (?) matches a single character
3840
4148
  /(?!\\)\?/g,
3841
- () => "[^/]"
4149
+ () => "[^/]",
4150
+ "?"
3842
4151
  ],
3843
4152
  // leading slash
3844
4153
  [
@@ -3846,12 +4155,14 @@ var require_ignore = __commonJS({
3846
4155
  // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
3847
4156
  // A leading slash matches the beginning of the pathname
3848
4157
  /^\//,
3849
- () => "^"
4158
+ () => "^",
4159
+ SLASH
3850
4160
  ],
3851
4161
  // replace special metacharacter slash after the leading slash
3852
4162
  [
3853
4163
  /\//g,
3854
- () => "\\/"
4164
+ () => "\\/",
4165
+ SLASH
3855
4166
  ],
3856
4167
  [
3857
4168
  // > A leading "**" followed by a slash means match in all directories.
@@ -3860,18 +4171,23 @@ var require_ignore = __commonJS({
3860
4171
  // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
3861
4172
  // > under directory "foo".
3862
4173
  // Notice that the '*'s have been replaced as '\\*'
3863
- /^\^*\\\*\\\*\\\//,
4174
+ /^\^*(?:\\\*\\\*\\\/)+/,
3864
4175
  // '**/foo' <-> 'foo'
3865
- () => "^(?:.*\\/)?"
4176
+ () => "^(?:.*\\/)?",
4177
+ "*"
3866
4178
  ],
3867
4179
  // starting
3868
4180
  [
3869
4181
  // there will be no leading '/'
3870
4182
  // (which has been replaced by section "leading slash")
3871
4183
  // If starts with '**', adding a '^' to the regular expression also works
3872
- /^(?=[^^])/,
3873
- function startingReplacer() {
3874
- return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
4184
+ DIRECT,
4185
+ (source, pattern) => {
4186
+ if (!source || source[0] === "^") {
4187
+ return source;
4188
+ }
4189
+ const anchor2 = !REGEX_INNER_SLASH.test(pattern) ? "(?:^|\\/)" : "^";
4190
+ return anchor2 + source;
3875
4191
  }
3876
4192
  ],
3877
4193
  // two globstars
@@ -3881,7 +4197,8 @@ var require_ignore = __commonJS({
3881
4197
  // Zero, one or several directories
3882
4198
  // should not use '*', or it will be replaced by the next replacer
3883
4199
  // Check if it is not the last `'/**'`
3884
- (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
4200
+ (_, index, str) => index + 6 < str.length ? str.slice(index + 6) === "\\/" ? "(?:\\/[^\\/]+)+" : "(?:\\/[^\\/]+)*" : "\\/.+",
4201
+ "*"
3885
4202
  ],
3886
4203
  // normal intermediate wildcards
3887
4204
  [
@@ -3896,32 +4213,56 @@ var require_ignore = __commonJS({
3896
4213
  (_, p1, p2) => {
3897
4214
  const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
3898
4215
  return p1 + unescaped;
3899
- }
4216
+ },
4217
+ "*"
4218
+ ],
4219
+ // trailing wildcard, held apart from a literal star
4220
+ [
4221
+ // The step above leaves a trailing `*` alone, so a single `\*` is all that
4222
+ // can be left at the end here. Whether it is a wildcard or a literal
4223
+ // turns on the backslashes the user put in front of it: the escaper has
4224
+ // since doubled every one, so what stands here is those `2N` doubled
4225
+ // backslashes and then the star's own escape. An even number of the
4226
+ // original `N` leaves the star unescaped -- a wildcard -- and an odd
4227
+ // number escapes it -- a literal. This runs while the two are still
4228
+ // distinct, before the unescape steps below collapse the literal onto
4229
+ // the very `\*` a wildcard leaves behind.
4230
+ /(^|[^\\])((?:\\\\)*)\\\*$/,
4231
+ (match, p1, p2) => (
4232
+ // `p2` holds the doubled user backslashes; half of them is `N`.
4233
+ p2.length / 2 % 2 === 0 ? p1 + p2 + TRAILING_WILDCARD : match
4234
+ ),
4235
+ "*"
3900
4236
  ],
3901
4237
  [
3902
4238
  // unescape, revert step 3 except for back slash
3903
4239
  // For example, if a user escape a '\\*',
3904
4240
  // after step 3, the result will be '\\\\\\*'
3905
4241
  /\\\\\\(?=[$.|*+(){^])/g,
3906
- () => ESCAPE
4242
+ () => ESCAPE,
4243
+ ESCAPE + ESCAPE
3907
4244
  ],
3908
4245
  [
3909
4246
  // '\\\\' -> '\\'
3910
4247
  /\\\\/g,
3911
- () => ESCAPE
4248
+ () => ESCAPE,
4249
+ ESCAPE + ESCAPE
3912
4250
  ],
3913
4251
  [
3914
- // > The range notation, e.g. [a-zA-Z],
3915
- // > can be used to match one of the characters in a range.
4252
+ // Every real bracket expression -- POSIX classes included -- has already
4253
+ // been held aside by `extractBrackets`, so the only `[` left in the
4254
+ // pattern is an escaped, literal one.
3916
4255
  // `\` is escaped by step 3
3917
- /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
3918
- (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
4256
+ /\\\[([^\]/]*?)(\\*)($|\])/g,
4257
+ // '\\[bar]' -> '\\\\[bar\\]'
4258
+ (match, range, endEscape, close) => `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`,
4259
+ "["
3919
4260
  ],
3920
4261
  // ending
3921
4262
  [
3922
4263
  // 'js' will not match 'js.'
3923
4264
  // 'ab' will not match 'abc'
3924
- /(?:[^*])$/,
4265
+ DIRECT,
3925
4266
  // WTF!
3926
4267
  // https://git-scm.com/docs/gitignore
3927
4268
  // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
@@ -3932,70 +4273,320 @@ var require_ignore = __commonJS({
3932
4273
  // 'js*' will not match 'a.js'
3933
4274
  // 'js/' will not match 'a.js'
3934
4275
  // 'js' will match 'a.js' and 'a.js/'
3935
- (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
3936
- ],
3937
- // trailing wildcard
3938
- [
3939
- /(\^|\\\/)?\\\*$/,
3940
- (_, p1) => {
3941
- const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
3942
- return `${prefix}(?=$|\\/$)`;
4276
+ (source) => {
4277
+ const last = source[source.length - 1];
4278
+ if (!last || last === TRAILING_WILDCARD) {
4279
+ return source;
4280
+ }
4281
+ return last === SLASH ? `${source}$` : `${source}(?=$|\\/$)`;
3943
4282
  }
3944
4283
  ]
3945
4284
  ];
3946
- var regexCache = /* @__PURE__ */ Object.create(null);
3947
- var makeRegex = (pattern, ignoreCase) => {
3948
- let source = regexCache[pattern];
3949
- if (!source) {
3950
- source = REPLACERS.reduce(
3951
- (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
3952
- pattern
3953
- );
3954
- regexCache[pattern] = source;
4285
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\uE000$/;
4286
+ var MODE_IGNORE = "regex";
4287
+ var MODE_CHECK_IGNORE = "checkRegex";
4288
+ var UNDERSCORE = "_";
4289
+ var TRAILING_WILD_CARD_REPLACERS = {
4290
+ [MODE_IGNORE](_, p1) {
4291
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
4292
+ return `${prefix}(?=$|\\/$)`;
4293
+ },
4294
+ [MODE_CHECK_IGNORE](_, p1) {
4295
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
4296
+ return `${prefix}(?=$|\\/$)`;
4297
+ }
4298
+ };
4299
+ var WILDCARD = "[^\\/]*";
4300
+ var separatorAfter = (run, at) => {
4301
+ let separator = EMPTY;
4302
+ for (let index = at + 1; index < run.length && !run[index].wildcard; index++) {
4303
+ separator += run[index].single;
4304
+ }
4305
+ return separator;
4306
+ };
4307
+ var pinWildcards = (source) => {
4308
+ if (source.indexOf(WILDCARD) < 0) {
4309
+ return source;
4310
+ }
4311
+ const tokens = [];
4312
+ const { length } = source;
4313
+ let index = 0;
4314
+ while (index < length) {
4315
+ const char = source[index];
4316
+ if (source.startsWith(WILDCARD, index)) {
4317
+ tokens.push({ wildcard: true });
4318
+ index += WILDCARD.length;
4319
+ } else if (char === "[") {
4320
+ let end = index + 1;
4321
+ if (source[end] === "^") {
4322
+ end++;
4323
+ }
4324
+ if (source[end] === "]") {
4325
+ end++;
4326
+ }
4327
+ while (end < length && source[end] !== "]") {
4328
+ end += source[end] === ESCAPE ? 2 : 1;
4329
+ }
4330
+ end++;
4331
+ tokens.push({ single: source.slice(index, end) });
4332
+ index = end;
4333
+ } else if (char === ESCAPE) {
4334
+ tokens.push({ single: source.slice(index, index + 2) });
4335
+ index += 2;
4336
+ } else if (char === "(") {
4337
+ let depth = 0;
4338
+ let end = index;
4339
+ do {
4340
+ if (source[end] === ESCAPE) {
4341
+ end++;
4342
+ } else if (source[end] === "(") {
4343
+ depth++;
4344
+ } else if (source[end] === ")") {
4345
+ depth--;
4346
+ }
4347
+ end++;
4348
+ } while (end < length && depth > 0);
4349
+ if ("*+?".indexOf(source[end]) >= 0) {
4350
+ end++;
4351
+ }
4352
+ tokens.push({ boundary: source.slice(index, end) });
4353
+ index = end;
4354
+ } else if (char === "^" || char === "$") {
4355
+ tokens.push({ boundary: char });
4356
+ index++;
4357
+ } else {
4358
+ tokens.push({ single: char });
4359
+ index++;
4360
+ }
4361
+ }
4362
+ let out = EMPTY;
4363
+ let run = [];
4364
+ const flush = () => {
4365
+ let lastWildcard;
4366
+ run.forEach((token, at) => {
4367
+ if (token.wildcard) {
4368
+ lastWildcard = at;
4369
+ }
4370
+ });
4371
+ run.forEach((token, at) => {
4372
+ if (!token.wildcard) {
4373
+ out += token.single;
4374
+ return;
4375
+ }
4376
+ out += at === lastWildcard ? WILDCARD : `(?:(?!${separatorAfter(run, at)})[^\\/])*`;
4377
+ });
4378
+ run = [];
4379
+ };
4380
+ tokens.forEach((token) => {
4381
+ if (token.boundary === void 0) {
4382
+ run.push(token);
4383
+ return;
4384
+ }
4385
+ flush();
4386
+ out += token.boundary;
4387
+ });
4388
+ flush();
4389
+ return out;
4390
+ };
4391
+ var makeRegexPrefix = (pattern) => {
4392
+ const { source, sources } = extractBrackets(pattern);
4393
+ const replaced = REPLACERS.reduce(
4394
+ // A pass whose matcher finds nothing hands back the very string it was
4395
+ // given, so asking first costs a search and saves a rewrite. Ten of the
4396
+ // fifteen passes never fire for a typical .gitignore line, and between
4397
+ // them they were 45% of this chain.
4398
+ (prev, [matcher, replacer, required2]) => {
4399
+ if (matcher === DIRECT) {
4400
+ return replacer(prev, pattern);
4401
+ }
4402
+ if (required2 !== UNDEFINED && prev.indexOf(required2) < 0) {
4403
+ return prev;
4404
+ }
4405
+ return matcher.test(prev) ? prev.replace(matcher, replacer.bind(pattern)) : prev;
4406
+ },
4407
+ source
4408
+ );
4409
+ return sources.length ? replaced.replace(
4410
+ REGEX_RESTORE_PLACEHOLDER,
4411
+ (match, index) => sources[index]
4412
+ ) : replaced;
4413
+ };
4414
+ var matchesBasename = (body) => {
4415
+ const index = body.indexOf(SLASH);
4416
+ return index < 0 || index === body.length - 1;
4417
+ };
4418
+ var basenameOf = (path9) => {
4419
+ const end = path9.length - 1;
4420
+ const index = path9.lastIndexOf(
4421
+ SLASH,
4422
+ path9[end] === SLASH ? end - 1 : end
4423
+ );
4424
+ return index < 0 ? path9 : path9.slice(index + 1);
4425
+ };
4426
+ var parentOf = (path9) => {
4427
+ if (path9.charCodeAt(0) === SLASH_CODE || path9.indexOf(DOUBLE_SLASH) >= 0) {
4428
+ const slices = path9.split(SLASH).filter(Boolean);
4429
+ slices.pop();
4430
+ return slices.length ? slices.join(SLASH) + SLASH : EMPTY;
3955
4431
  }
3956
- return ignoreCase ? new RegExp(source, "i") : new RegExp(source);
4432
+ const end = path9.length - 1;
4433
+ const cut = path9.lastIndexOf(
4434
+ SLASH,
4435
+ path9.charCodeAt(end) === SLASH_CODE ? end - 1 : end
4436
+ );
4437
+ return cut < 0 ? EMPTY : path9.slice(0, cut + 1);
3957
4438
  };
3958
4439
  var isString = (subject) => typeof subject === "string";
3959
4440
  var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
3960
- var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF);
4441
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
3961
4442
  var IgnoreRule = class {
3962
- constructor(origin, pattern, negative, regex) {
3963
- this.origin = origin;
4443
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
3964
4444
  this.pattern = pattern;
4445
+ this.mark = mark;
3965
4446
  this.negative = negative;
3966
- this.regex = regex;
4447
+ define(this, "body", body);
4448
+ define(this, "ignoreCase", ignoreCase);
4449
+ define(this, "regexPrefix", prefix);
4450
+ }
4451
+ // Worked out on first use and kept behind an own property, the way `regex`
4452
+ // caches itself in `_regex`. Deciding it in the constructor instead would
4453
+ // add a fourth `defineProperty` to every rule ever built, which cost 4% of
4454
+ // every compile -- including the compiles of rules that are never matched
4455
+ // against anything.
4456
+ get _basenameOnly() {
4457
+ return define(this, "_basenameOnly", matchesBasename(this.body));
4458
+ }
4459
+ get regex() {
4460
+ const key = UNDERSCORE + MODE_IGNORE;
4461
+ if (this[key]) {
4462
+ return this[key];
4463
+ }
4464
+ return this._make(MODE_IGNORE, key);
4465
+ }
4466
+ get checkRegex() {
4467
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
4468
+ if (this[key]) {
4469
+ return this[key];
4470
+ }
4471
+ return this._make(MODE_CHECK_IGNORE, key);
4472
+ }
4473
+ _make(mode, key) {
4474
+ const str = pinWildcards(this.regexPrefix.replace(
4475
+ REGEX_REPLACE_TRAILING_WILDCARD,
4476
+ // It does not need to bind pattern
4477
+ TRAILING_WILD_CARD_REPLACERS[mode]
4478
+ ));
4479
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
4480
+ return define(this, key, regex);
3967
4481
  }
3968
4482
  };
3969
- var createRule = (pattern, ignoreCase) => {
3970
- const origin = pattern;
4483
+ var createRule = ({
4484
+ pattern,
4485
+ mark
4486
+ }, ignoreCase) => {
3971
4487
  let negative = false;
3972
- if (pattern.indexOf("!") === 0) {
4488
+ let body = pattern;
4489
+ if (body.indexOf("!") === 0) {
3973
4490
  negative = true;
3974
- pattern = pattern.substr(1);
4491
+ body = body.substr(1);
3975
4492
  }
3976
- pattern = pattern.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
3977
- const regex = makeRegex(pattern, ignoreCase);
4493
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
4494
+ const regexPrefix = makeRegexPrefix(body);
3978
4495
  return new IgnoreRule(
3979
- origin,
3980
4496
  pattern,
4497
+ mark,
4498
+ body,
4499
+ ignoreCase,
3981
4500
  negative,
3982
- regex
4501
+ regexPrefix
3983
4502
  );
3984
4503
  };
4504
+ var RuleManager = class {
4505
+ constructor(ignoreCase) {
4506
+ this._ignoreCase = ignoreCase;
4507
+ this._rules = [];
4508
+ this._basenameCount = 0;
4509
+ }
4510
+ _add(pattern) {
4511
+ if (pattern && pattern[KEY_IGNORE]) {
4512
+ this._rules = this._rules.concat(pattern._rules._rules);
4513
+ this._basenameCount += pattern._rules._basenameCount;
4514
+ this._added = true;
4515
+ return;
4516
+ }
4517
+ if (isString(pattern)) {
4518
+ pattern = {
4519
+ pattern
4520
+ };
4521
+ }
4522
+ if (checkPattern(pattern.pattern)) {
4523
+ const rule = createRule(pattern, this._ignoreCase);
4524
+ this._added = true;
4525
+ this._rules.push(rule);
4526
+ if (matchesBasename(rule.body)) {
4527
+ this._basenameCount++;
4528
+ }
4529
+ }
4530
+ }
4531
+ // @param {Array<string> | string | Ignore} pattern
4532
+ add(pattern) {
4533
+ this._added = false;
4534
+ makeArray(
4535
+ isString(pattern) ? splitPattern(pattern) : pattern
4536
+ ).forEach(this._add, this);
4537
+ return this._added;
4538
+ }
4539
+ // Test one single path without recursively checking parent directories
4540
+ //
4541
+ // - checkUnignored `boolean` whether should check if the path is unignored,
4542
+ // setting `checkUnignored` to `false` could reduce additional
4543
+ // path matching.
4544
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
4545
+ // @returns {TestResult} true if a file is ignored
4546
+ test(path9, checkUnignored, mode) {
4547
+ let ignored = false;
4548
+ let unignored = false;
4549
+ let matchedRule;
4550
+ const rules = this._rules;
4551
+ const { length } = rules;
4552
+ const shortcut = this._basenameCount * 2 >= length;
4553
+ const basename5 = shortcut ? basenameOf(path9) : path9;
4554
+ for (let index = 0; index < length; index++) {
4555
+ const rule = rules[index];
4556
+ const { negative } = rule;
4557
+ const skip = unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored;
4558
+ if (!skip && rule[mode].test(
4559
+ shortcut && rule._basenameOnly ? basename5 : path9
4560
+ )) {
4561
+ ignored = !negative;
4562
+ unignored = negative;
4563
+ matchedRule = negative ? UNDEFINED : rule;
4564
+ }
4565
+ }
4566
+ const ret = {
4567
+ ignored,
4568
+ unignored
4569
+ };
4570
+ if (matchedRule) {
4571
+ ret.rule = matchedRule;
4572
+ }
4573
+ return ret;
4574
+ }
4575
+ };
3985
4576
  var throwError = (message, Ctor) => {
3986
4577
  throw new Ctor(message);
3987
4578
  };
3988
- var checkPath = (path8, originalPath, doThrow) => {
3989
- if (!isString(path8)) {
4579
+ var checkPath = (path9, originalPath, doThrow) => {
4580
+ if (!isString(path9)) {
3990
4581
  return doThrow(
3991
4582
  `path must be a string, but got \`${originalPath}\``,
3992
4583
  TypeError
3993
4584
  );
3994
4585
  }
3995
- if (!path8) {
4586
+ if (!path9) {
3996
4587
  return doThrow(`path must not be empty`, TypeError);
3997
4588
  }
3998
- if (checkPath.isNotRelative(path8)) {
4589
+ if (checkPath.isNotRelative(path9)) {
3999
4590
  const r = "`path.relative()`d";
4000
4591
  return doThrow(
4001
4592
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -4004,7 +4595,26 @@ var require_ignore = __commonJS({
4004
4595
  }
4005
4596
  return true;
4006
4597
  };
4007
- var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
4598
+ var isNotRelative = (path9) => {
4599
+ const first = path9.charCodeAt(0);
4600
+ if (first === SLASH_CODE) {
4601
+ return true;
4602
+ }
4603
+ if (first !== DOT_CODE) {
4604
+ return false;
4605
+ }
4606
+ if (path9.length === 1) {
4607
+ return true;
4608
+ }
4609
+ const second = path9.charCodeAt(1);
4610
+ if (second === SLASH_CODE) {
4611
+ return true;
4612
+ }
4613
+ if (second !== DOT_CODE) {
4614
+ return false;
4615
+ }
4616
+ return path9.length === 2 || path9.charCodeAt(2) === SLASH_CODE;
4617
+ };
4008
4618
  checkPath.isNotRelative = isNotRelative;
4009
4619
  checkPath.convert = (p) => p;
4010
4620
  var Ignore = class {
@@ -4014,255 +4624,87 @@ var require_ignore = __commonJS({
4014
4624
  allowRelativePaths = false
4015
4625
  } = {}) {
4016
4626
  define(this, KEY_IGNORE, true);
4017
- this._rules = [];
4018
- this._ignoreCase = ignoreCase;
4019
- this._allowRelativePaths = allowRelativePaths;
4627
+ this._rules = new RuleManager(ignoreCase);
4628
+ this._strictPathCheck = !allowRelativePaths;
4020
4629
  this._initCache();
4021
4630
  }
4022
4631
  _initCache() {
4023
4632
  this._ignoreCache = /* @__PURE__ */ Object.create(null);
4024
4633
  this._testCache = /* @__PURE__ */ Object.create(null);
4025
4634
  }
4026
- _addPattern(pattern) {
4027
- if (pattern && pattern[KEY_IGNORE]) {
4028
- this._rules = this._rules.concat(pattern._rules);
4029
- this._added = true;
4030
- return;
4031
- }
4032
- if (checkPattern(pattern)) {
4033
- const rule = createRule(pattern, this._ignoreCase);
4034
- this._added = true;
4035
- this._rules.push(rule);
4635
+ add(pattern) {
4636
+ if (this._rules.add(pattern)) {
4637
+ this._initCache();
4036
4638
  }
4037
- }
4038
- // @param {Array<string> | string | Ignore} pattern
4039
- add(pattern) {
4040
- this._added = false;
4041
- makeArray(
4042
- isString(pattern) ? splitPattern(pattern) : pattern
4043
- ).forEach(this._addPattern, this);
4044
- if (this._added) {
4045
- this._initCache();
4046
- }
4047
- return this;
4639
+ return this;
4048
4640
  }
4049
4641
  // legacy
4050
4642
  addPattern(pattern) {
4051
4643
  return this.add(pattern);
4052
4644
  }
4053
- // | ignored : unignored
4054
- // negative | 0:0 | 0:1 | 1:0 | 1:1
4055
- // -------- | ------- | ------- | ------- | --------
4056
- // 0 | TEST | TEST | SKIP | X
4057
- // 1 | TESTIF | SKIP | TEST | X
4058
- // - SKIP: always skip
4059
- // - TEST: always test
4060
- // - TESTIF: only test if checkUnignored
4061
- // - X: that never happen
4062
- // @param {boolean} whether should check if the path is unignored,
4063
- // setting `checkUnignored` to `false` could reduce additional
4064
- // path matching.
4065
- // @returns {TestResult} true if a file is ignored
4066
- _testOne(path8, checkUnignored) {
4067
- let ignored = false;
4068
- let unignored = false;
4069
- this._rules.forEach((rule) => {
4070
- const { negative } = rule;
4071
- if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
4072
- return;
4073
- }
4074
- const matched = rule.regex.test(path8);
4075
- if (matched) {
4076
- ignored = !negative;
4077
- unignored = negative;
4078
- }
4079
- });
4080
- return {
4081
- ignored,
4082
- unignored
4083
- };
4084
- }
4085
4645
  // @returns {TestResult}
4086
- _test(originalPath, cache, checkUnignored, slices) {
4087
- const path8 = originalPath && checkPath.convert(originalPath);
4646
+ _test(originalPath, cache, checkUnignored) {
4647
+ const path9 = originalPath && checkPath.convert(originalPath);
4088
4648
  checkPath(
4089
- path8,
4649
+ path9,
4090
4650
  originalPath,
4091
- this._allowRelativePaths ? RETURN_FALSE : throwError
4651
+ this._strictPathCheck ? throwError : RETURN_FALSE
4092
4652
  );
4093
- return this._t(path8, cache, checkUnignored, slices);
4653
+ return this._t(path9, cache, checkUnignored);
4094
4654
  }
4095
- _t(path8, cache, checkUnignored, slices) {
4096
- if (path8 in cache) {
4097
- return cache[path8];
4655
+ checkIgnore(path9) {
4656
+ if (path9.charCodeAt(path9.length - 1) !== SLASH_CODE) {
4657
+ return this.test(path9);
4098
4658
  }
4099
- if (!slices) {
4100
- slices = path8.split(SLASH);
4659
+ const parentPath = parentOf(path9);
4660
+ if (parentPath) {
4661
+ const parent = this._t(parentPath, this._testCache, true);
4662
+ if (parent.ignored) {
4663
+ return parent;
4664
+ }
4101
4665
  }
4102
- slices.pop();
4103
- if (!slices.length) {
4104
- return cache[path8] = this._testOne(path8, checkUnignored);
4105
- }
4106
- const parent = this._t(
4107
- slices.join(SLASH) + SLASH,
4108
- cache,
4109
- checkUnignored,
4110
- slices
4111
- );
4112
- return cache[path8] = parent.ignored ? parent : this._testOne(path8, checkUnignored);
4666
+ return this._rules.test(path9, false, MODE_CHECK_IGNORE);
4667
+ }
4668
+ _t(path9, cache, checkUnignored) {
4669
+ if (path9 in cache) {
4670
+ return cache[path9];
4671
+ }
4672
+ const parentPath = parentOf(path9);
4673
+ const parent = parentPath ? this._t(parentPath, cache, checkUnignored) : UNDEFINED;
4674
+ return cache[path9] = parent && parent.ignored ? parent : this._rules.test(path9, checkUnignored, MODE_IGNORE);
4113
4675
  }
4114
- ignores(path8) {
4115
- return this._test(path8, this._ignoreCache, false).ignored;
4676
+ ignores(path9) {
4677
+ return this._test(path9, this._ignoreCache, false).ignored;
4116
4678
  }
4117
4679
  createFilter() {
4118
- return (path8) => !this.ignores(path8);
4680
+ return (path9) => !this.ignores(path9);
4119
4681
  }
4120
4682
  filter(paths) {
4121
4683
  return makeArray(paths).filter(this.createFilter());
4122
4684
  }
4123
4685
  // @returns {TestResult}
4124
- test(path8) {
4125
- return this._test(path8, this._testCache, true);
4686
+ test(path9) {
4687
+ return this._test(path9, this._testCache, true);
4126
4688
  }
4127
4689
  };
4128
4690
  var factory = (options) => new Ignore(options);
4129
- var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, RETURN_FALSE);
4130
- factory.isPathValid = isPathValid;
4131
- factory.default = factory;
4132
- module2.exports = factory;
4691
+ var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
4692
+ var setupWindows = () => {
4693
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
4694
+ checkPath.convert = makePosix;
4695
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
4696
+ checkPath.isNotRelative = (path9) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
4697
+ };
4133
4698
  if (
4134
4699
  // Detect `process` so that it can run in browsers.
4135
- typeof process !== "undefined" && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === "win32")
4700
+ typeof process !== "undefined" && process.platform === "win32"
4136
4701
  ) {
4137
- const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
4138
- checkPath.convert = makePosix;
4139
- const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
4140
- checkPath.isNotRelative = (path8) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
4141
- }
4142
- }
4143
- });
4144
-
4145
- // ../../shared/platform/src/fs-utils.ts
4146
- async function writeFileIfChanged(filePath, content, mode) {
4147
- const target = await resolveTarget(filePath);
4148
- const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
4149
- const existing = await safeReadFile(target);
4150
- if (existing && existing.equals(buf)) return false;
4151
- await import_promises7.default.mkdir(import_path2.default.dirname(target), { recursive: true });
4152
- const tmp = `${target}.tmp.${import_crypto.default.randomBytes(6).toString("hex")}`;
4153
- await import_promises7.default.writeFile(tmp, buf);
4154
- if (mode != null && process.platform !== "win32") {
4155
- await import_promises7.default.chmod(tmp, mode);
4156
- }
4157
- await import_promises7.default.rename(tmp, target);
4158
- return true;
4159
- }
4160
- async function updateFileAtomically(filePath, merge2, opts = {}) {
4161
- const { mode, retries = 5 } = opts;
4162
- const target = await resolveTarget(filePath);
4163
- await import_promises7.default.mkdir(import_path2.default.dirname(target), { recursive: true });
4164
- const release = await acquireLock(`${target}.lock`);
4165
- try {
4166
- for (let attempt = 0; ; attempt++) {
4167
- const baseline = await safeReadFile(target);
4168
- const merged = await merge2(baseline);
4169
- if (merged == null) return false;
4170
- const buf = Buffer.isBuffer(merged) ? merged : Buffer.from(merged, "utf8");
4171
- if (baseline && baseline.equals(buf)) return false;
4172
- const tmp = `${target}.tmp.${import_crypto.default.randomBytes(6).toString("hex")}`;
4173
- await import_promises7.default.writeFile(tmp, buf);
4174
- if (mode != null && process.platform !== "win32") {
4175
- await import_promises7.default.chmod(tmp, mode);
4176
- }
4177
- if (sameBytes(baseline, await safeReadFile(target))) {
4178
- await import_promises7.default.rename(tmp, target);
4179
- return true;
4180
- }
4181
- await import_promises7.default.rm(tmp, { force: true });
4182
- if (attempt >= retries) {
4183
- throw new Error(
4184
- `[fs-utils] updateFileAtomically: ${target} kept changing underneath ${retries + 1} merge attempts \u2014 giving up rather than writing a stale merge`
4185
- );
4186
- }
4187
- await sleep(2 ** attempt + Math.floor(Math.random() * 5));
4188
- }
4189
- } finally {
4190
- await release();
4191
- }
4192
- }
4193
- async function acquireLock(lockPath) {
4194
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
4195
- for (; ; ) {
4196
- try {
4197
- const handle = await import_promises7.default.open(lockPath, "wx");
4198
- await handle.writeFile(`${process.pid} ${(/* @__PURE__ */ new Date()).toISOString()}
4199
- `);
4200
- await handle.close();
4201
- return async () => {
4202
- await import_promises7.default.rm(lockPath, { force: true });
4203
- };
4204
- } catch (err) {
4205
- if (!isCode(err, "EEXIST")) throw err;
4206
- const age = await lockAgeMs(lockPath);
4207
- if (age === null) continue;
4208
- if (age > LOCK_STALE_MS) {
4209
- await import_promises7.default.rm(lockPath, { force: true });
4210
- continue;
4211
- }
4212
- if (Date.now() > deadline) {
4213
- throw new Error(`[fs-utils] timed out after ${LOCK_TIMEOUT_MS}ms waiting for ${lockPath}`);
4214
- }
4215
- await sleep(5 + Math.floor(Math.random() * 10));
4702
+ setupWindows();
4216
4703
  }
4217
- }
4218
- }
4219
- async function lockAgeMs(lockPath) {
4220
- try {
4221
- return Date.now() - (await import_promises7.default.stat(lockPath)).mtimeMs;
4222
- } catch (err) {
4223
- if (isCode(err, "ENOENT")) return null;
4224
- throw err;
4225
- }
4226
- }
4227
- function sameBytes(a, b) {
4228
- if (a === null || b === null) return a === b;
4229
- return a.equals(b);
4230
- }
4231
- function sleep(ms) {
4232
- return new Promise((resolve7) => setTimeout(resolve7, ms));
4233
- }
4234
- async function safeReadFile(p) {
4235
- try {
4236
- return await import_promises7.default.readFile(p);
4237
- } catch (err) {
4238
- if (isENOENT(err)) return null;
4239
- throw err;
4240
- }
4241
- }
4242
- async function resolveTarget(p) {
4243
- try {
4244
- const stat2 = await import_promises7.default.lstat(p);
4245
- if (stat2.isSymbolicLink()) return await import_promises7.default.realpath(p);
4246
- } catch (err) {
4247
- if (!isENOENT(err)) throw err;
4248
- }
4249
- return p;
4250
- }
4251
- function isENOENT(err) {
4252
- return isCode(err, "ENOENT");
4253
- }
4254
- function isCode(err, code) {
4255
- return typeof err === "object" && err != null && err.code === code;
4256
- }
4257
- var import_promises7, import_path2, import_crypto, LOCK_STALE_MS, LOCK_TIMEOUT_MS;
4258
- var init_fs_utils = __esm({
4259
- "../../shared/platform/src/fs-utils.ts"() {
4260
- "use strict";
4261
- import_promises7 = __toESM(require("fs/promises"), 1);
4262
- import_path2 = __toESM(require("path"), 1);
4263
- import_crypto = __toESM(require("crypto"), 1);
4264
- LOCK_STALE_MS = 1e4;
4265
- LOCK_TIMEOUT_MS = 5e3;
4704
+ module2.exports = factory;
4705
+ factory.default = factory;
4706
+ module2.exports.isPathValid = isPathValid;
4707
+ define(module2.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
4266
4708
  }
4267
4709
  });
4268
4710
 
@@ -4412,7 +4854,7 @@ var init_claude_hook_installer = __esm({
4412
4854
  // ../../shared/platform/src/agent-hooks/gemini-hook-installer.ts
4413
4855
  async function isGeminiInstalled() {
4414
4856
  try {
4415
- await execFileAsync2("gemini", ["--version"], { timeout: 2e3 });
4857
+ await execFileAsync3("gemini", ["--version"], { timeout: 2e3 });
4416
4858
  return true;
4417
4859
  } catch {
4418
4860
  return false;
@@ -4493,16 +4935,16 @@ function isManagedSlayzoneHook2(hook) {
4493
4935
  const cmd = typeof h.command === "string" ? h.command : "";
4494
4936
  return cmd.includes(".slayzone/hooks/notify.sh") || cmd.includes("/slayzone/hooks/notify.sh");
4495
4937
  }
4496
- var import_node_child_process7, import_node_util, execFileAsync2, MARKER_KEY2, GEMINI_HOOK_EVENTS, TOOL_MATCHED_EVENTS2;
4938
+ var import_node_child_process10, import_node_util2, execFileAsync3, MARKER_KEY2, GEMINI_HOOK_EVENTS, TOOL_MATCHED_EVENTS2;
4497
4939
  var init_gemini_hook_installer = __esm({
4498
4940
  "../../shared/platform/src/agent-hooks/gemini-hook-installer.ts"() {
4499
4941
  "use strict";
4500
- import_node_child_process7 = require("node:child_process");
4501
- import_node_util = require("node:util");
4942
+ import_node_child_process10 = require("node:child_process");
4943
+ import_node_util2 = require("node:util");
4502
4944
  init_dirs();
4503
4945
  init_fs_utils();
4504
4946
  init_hook_paths();
4505
- execFileAsync2 = (0, import_node_util.promisify)(import_node_child_process7.execFile);
4947
+ execFileAsync3 = (0, import_node_util2.promisify)(import_node_child_process10.execFile);
4506
4948
  MARKER_KEY2 = "_slayzoneManaged";
4507
4949
  GEMINI_HOOK_EVENTS = [
4508
4950
  "SessionStart",
@@ -4601,9 +5043,9 @@ function isManagedSlayzoneHook3(hook) {
4601
5043
  async function uninstallCodexWrapper(opts = {}) {
4602
5044
  const target = opts.wrapperPath ?? import_path4.default.join(getSlayzoneBinDir(), "codex");
4603
5045
  try {
4604
- const content = await import_promises9.default.readFile(target, "utf8");
5046
+ const content = await import_promises10.default.readFile(target, "utf8");
4605
5047
  if (!content.includes("slayzone codex wrapper")) return false;
4606
- await import_promises9.default.unlink(target);
5048
+ await import_promises10.default.unlink(target);
4607
5049
  return true;
4608
5050
  } catch (err) {
4609
5051
  if (isENOENT2(err)) return false;
@@ -4638,11 +5080,11 @@ function isAtLeast(v, min) {
4638
5080
  function isENOENT2(err) {
4639
5081
  return typeof err === "object" && err != null && err.code === "ENOENT";
4640
5082
  }
4641
- var import_promises9, import_path4, import_child_process2, import_util10, execP, MARKER_KEY3, CODEX_HOOK_EVENTS, TOOL_MATCHED_EVENTS3, MIN_CODEX_VERSION;
5083
+ var import_promises10, import_path4, import_child_process2, import_util10, execP, MARKER_KEY3, CODEX_HOOK_EVENTS, TOOL_MATCHED_EVENTS3, MIN_CODEX_VERSION;
4642
5084
  var init_codex_hook_installer = __esm({
4643
5085
  "../../shared/platform/src/agent-hooks/codex-hook-installer.ts"() {
4644
5086
  "use strict";
4645
- import_promises9 = __toESM(require("fs/promises"), 1);
5087
+ import_promises10 = __toESM(require("fs/promises"), 1);
4646
5088
  import_path4 = __toESM(require("path"), 1);
4647
5089
  import_child_process2 = require("child_process");
4648
5090
  import_util10 = require("util");
@@ -4667,7 +5109,7 @@ var init_codex_hook_installer = __esm({
4667
5109
  // ../../shared/platform/src/agent-hooks/antigravity-hook-installer.ts
4668
5110
  async function isAntigravityInstalled() {
4669
5111
  try {
4670
- await execFileAsync3("agy", ["--version"], { timeout: 2e3 });
5112
+ await execFileAsync4("agy", ["--version"], { timeout: 2e3 });
4671
5113
  return true;
4672
5114
  } catch {
4673
5115
  return false;
@@ -4729,16 +5171,16 @@ function buildEntry(event, scriptPath) {
4729
5171
  if (TOOL_MATCHED_EVENTS4.has(event)) entry.matcher = "*";
4730
5172
  return entry;
4731
5173
  }
4732
- var import_node_child_process8, import_node_util2, execFileAsync3, SLAYZONE_HOOK_NAME, ANTIGRAVITY_HOOK_EVENTS, TOOL_MATCHED_EVENTS4;
5174
+ var import_node_child_process11, import_node_util3, execFileAsync4, SLAYZONE_HOOK_NAME, ANTIGRAVITY_HOOK_EVENTS, TOOL_MATCHED_EVENTS4;
4733
5175
  var init_antigravity_hook_installer = __esm({
4734
5176
  "../../shared/platform/src/agent-hooks/antigravity-hook-installer.ts"() {
4735
5177
  "use strict";
4736
- import_node_child_process8 = require("node:child_process");
4737
- import_node_util2 = require("node:util");
5178
+ import_node_child_process11 = require("node:child_process");
5179
+ import_node_util3 = require("node:util");
4738
5180
  init_dirs();
4739
5181
  init_fs_utils();
4740
5182
  init_hook_paths();
4741
- execFileAsync3 = (0, import_node_util2.promisify)(import_node_child_process8.execFile);
5183
+ execFileAsync4 = (0, import_node_util3.promisify)(import_node_child_process11.execFile);
4742
5184
  SLAYZONE_HOOK_NAME = "slayzone-notify";
4743
5185
  ANTIGRAVITY_HOOK_EVENTS = ["PreInvocation", "PostToolUse", "Stop"];
4744
5186
  TOOL_MATCHED_EVENTS4 = /* @__PURE__ */ new Set(["PostToolUse"]);
@@ -4778,10 +5220,10 @@ var init_agent_hooks = __esm({
4778
5220
  }
4779
5221
  });
4780
5222
 
4781
- // raw-import:/tmp/slz-publish-wt.f69qAs/packages/shared/hooks/src/notify.sh
5223
+ // raw-import:/tmp/slz-publish-wt.C8azA7/packages/shared/hooks/src/notify.sh
4782
5224
  var notify_default;
4783
5225
  var init_notify = __esm({
4784
- "raw-import:/tmp/slz-publish-wt.f69qAs/packages/shared/hooks/src/notify.sh"() {
5226
+ "raw-import:/tmp/slz-publish-wt.C8azA7/packages/shared/hooks/src/notify.sh"() {
4785
5227
  notify_default = `#!/bin/sh
4786
5228
  # SlayZone agent lifecycle hook \u2014 BENIGN DUMB FORWARDER.
4787
5229
  #
@@ -4825,7 +5267,7 @@ var init_notify = __esm({
4825
5267
  #
4826
5268
  # Required env (injected at PTY/chat spawn by buildMcpEnv):
4827
5269
  # SLAYZONE_AGENT_HOOK_URL - target URL, e.g. http://127.0.0.1:PORT/api/agent-hook
4828
- # (a remote runner overlays its OWN loopback URL)
5270
+ # (a remote computer overlays its OWN loopback URL)
4829
5271
  # SLAYZONE_AGENT_ID - claude-code | codex | gemini | antigravity | opencode
4830
5272
  # Optional:
4831
5273
  # SLAYZONE_AGENT_HOOK_CONTEXT - opaque identity blob (see above); sent as \`ctx\`
@@ -4918,7 +5360,7 @@ ENVELOPE="{\\"ctx\\":$CTX,\\"raw\\":$RAW_FIELD,\\"arg\\":$ARG_FIELD,\\"agentId\\
4918
5360
  # curl is the primary path \u2014 present on macOS, most Linux, and bundled with
4919
5361
  # Git for Windows (whose bash runs this script). wget is a fallback for minimal
4920
5362
  # environments; if neither exists the POST is skipped silently. NO auth header:
4921
- # the target is always loopback (local sidecar, or the runner's own loopback),
5363
+ # the target is always loopback (local sidecar, or the computer's own loopback),
4922
5364
  # so no per-agent bearer is ever attached.
4923
5365
  if command -v curl >/dev/null 2>&1; then
4924
5366
  curl -s \\
@@ -4943,10 +5385,10 @@ exit 0
4943
5385
  }
4944
5386
  });
4945
5387
 
4946
- // raw-import:/tmp/slz-publish-wt.f69qAs/packages/shared/hooks/src/opencode-plugin.js
5388
+ // raw-import:/tmp/slz-publish-wt.C8azA7/packages/shared/hooks/src/opencode-plugin.js
4947
5389
  var opencode_plugin_default;
4948
5390
  var init_opencode_plugin = __esm({
4949
- "raw-import:/tmp/slz-publish-wt.f69qAs/packages/shared/hooks/src/opencode-plugin.js"() {
5391
+ "raw-import:/tmp/slz-publish-wt.C8azA7/packages/shared/hooks/src/opencode-plugin.js"() {
4950
5392
  opencode_plugin_default = "// SlayZone opencode plugin v1\n//\n// Forwards OpenCode SDK session lifecycle events to the SlayZone agent-hook\n// notify script over a local shell exec. Plugin is loaded by OpenCode from\n// ${XDG_CONFIG_HOME:-~/.config}/opencode/plugin/slayzone-notify.js\n//\n// The {{NOTIFY_PATH}} placeholder is substituted by the SlayZone installer\n// (packages/apps/app/src/main/agent-hooks/opencode-plugin-installer.ts) at\n// install time with the absolute path to `~/.slayzone/hooks/notify.sh`.\n//\n// Behavior:\n// - No-op when SLAYZONE_TASK_ID is unset (plugin only fires inside SlayZone PTYs).\n// - Subagent (child session) events are suppressed \u2014 oh-my-opencode + similar\n// tools spawn many child sessions and would otherwise flap Start/Stop.\n// - On client.session.list() error, sessions are assumed CHILD (safer to\n// suppress than emit a false-positive Start/Stop).\n// - Handles both modern (session.status w/ nested type) and legacy\n// (session.busy / session.idle) event shapes for forward compat.\n// - Singleton guard prevents double-load if OpenCode re-imports the plugin.\n\nexport const SlayzoneNotifyPlugin = async ({ $, client }) => {\n if (globalThis.__slayzoneOpencodePluginV1) return {}\n globalThis.__slayzoneOpencodePluginV1 = true\n if (!process?.env?.SLAYZONE_TASK_ID) return {}\n\n const notifyPath = '{{NOTIFY_PATH}}'\n let currentState = 'idle' // 'idle' | 'busy'\n let rootSessionID = null // first non-child session we see\n let stopSent = false // dedup Stop emits\n const childSessionCache = new Map() // sessionID -> isChild bool\n\n const notify = async (eventName) => {\n const payload = JSON.stringify({ hook_event_name: eventName })\n try {\n await $`bash ${notifyPath} ${payload}`\n } catch {\n // Hook failures must never bubble into the OpenCode TUI.\n }\n }\n\n const isChildSession = async (sessionID) => {\n if (!sessionID) return true\n if (!client?.session?.list) return true\n if (childSessionCache.has(sessionID)) return childSessionCache.get(sessionID)\n try {\n const sessions = await client.session.list()\n const session = sessions.data?.find((s) => s.id === sessionID)\n const isChild = !!session?.parentID\n childSessionCache.set(sessionID, isChild)\n return isChild\n } catch {\n // On error, assume CHILD \u2014 safer than emitting a false-positive Start/Stop.\n return true\n }\n }\n\n const handleBusy = async (sessionID) => {\n if (!rootSessionID) rootSessionID = sessionID\n if (sessionID !== rootSessionID) return\n if (currentState === 'idle') {\n currentState = 'busy'\n stopSent = false\n await notify('Start')\n }\n }\n\n const handleStop = async (sessionID) => {\n if (rootSessionID && sessionID !== rootSessionID) return\n if (currentState === 'busy' && !stopSent) {\n currentState = 'idle'\n stopSent = true\n await notify('Stop')\n rootSessionID = null // reset for next session\n }\n }\n\n return {\n event: async ({ event }) => {\n const sessionID = event.properties?.sessionID ?? event.properties?.info?.id ?? null\n\n if (event.type === 'session.created') {\n const isChild = Boolean(event.properties?.info?.parentID)\n if (sessionID) childSessionCache.set(sessionID, isChild)\n if (!isChild) await notify('SessionStart')\n return\n }\n\n if (event.type === 'session.deleted') {\n const cached = sessionID ? childSessionCache.get(sessionID) : undefined\n const isChild = cached !== undefined ? cached : await isChildSession(sessionID)\n if (!isChild) await notify('SessionEnd')\n if (sessionID) childSessionCache.delete(sessionID)\n return\n }\n\n if (await isChildSession(sessionID)) return\n\n if (event.type === 'session.status') {\n const status = event.properties?.status\n if (status?.type === 'busy') await handleBusy(sessionID)\n else if (status?.type === 'idle') await handleStop(sessionID)\n }\n // Backwards-compat: some OpenCode versions emit busy/idle as top-level events.\n if (event.type === 'session.busy') await handleBusy(sessionID)\n if (event.type === 'session.idle') await handleStop(sessionID)\n if (event.type === 'session.error') await handleStop(sessionID)\n },\n 'permission.ask': async (_permission, output) => {\n if (output.status === 'ask') await notify('PermissionRequest')\n }\n }\n}\n";
4951
5393
  }
4952
5394
  });
@@ -4989,8 +5431,8 @@ var init_agent_hooks2 = __esm({
4989
5431
  });
4990
5432
 
4991
5433
  // src/bin.ts
4992
- var import_node_os7 = require("node:os");
4993
- var import_node_path10 = require("node:path");
5434
+ var import_node_os8 = require("node:os");
5435
+ var import_node_path14 = require("node:path");
4994
5436
 
4995
5437
  // ../../shared/platform/src/config-prompt.ts
4996
5438
  var import_promises = require("node:readline/promises");
@@ -5025,14 +5467,14 @@ function coerceComputerConfig(raw) {
5025
5467
  }
5026
5468
  return cfg;
5027
5469
  }
5028
- function readJsonObject(path8) {
5470
+ function readJsonObject(path9) {
5029
5471
  let raw;
5030
5472
  try {
5031
- raw = (0, import_node_fs.readFileSync)(path8, "utf8");
5473
+ raw = (0, import_node_fs.readFileSync)(path9, "utf8");
5032
5474
  } catch (err) {
5033
5475
  if (err.code === "ENOENT") return null;
5034
5476
  process.stderr.write(
5035
- `[slayzone-config] cannot read ${path8}: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5477
+ `[slayzone-config] cannot read ${path9}: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5036
5478
  `
5037
5479
  );
5038
5480
  return null;
@@ -5042,61 +5484,61 @@ function readJsonObject(path8) {
5042
5484
  parsed = JSON.parse(raw);
5043
5485
  } catch (err) {
5044
5486
  process.stderr.write(
5045
- `[slayzone-config] ${path8} is not valid JSON: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5487
+ `[slayzone-config] ${path9} is not valid JSON: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5046
5488
  `
5047
5489
  );
5048
5490
  return null;
5049
5491
  }
5050
5492
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5051
- process.stderr.write(`[slayzone-config] ${path8} is not a JSON object \u2014 using empty config
5493
+ process.stderr.write(`[slayzone-config] ${path9} is not a JSON object \u2014 using empty config
5052
5494
  `);
5053
5495
  return null;
5054
5496
  }
5055
5497
  return parsed;
5056
5498
  }
5057
- function writeJsonAtomic(path8, data) {
5058
- const dir = (0, import_node_path.dirname)(path8);
5499
+ function writeJsonAtomic(path9, data) {
5500
+ const dir = (0, import_node_path.dirname)(path9);
5059
5501
  (0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
5060
- const tmpPath = `${path8}.tmp-${process.pid}-${Date.now()}`;
5502
+ const tmpPath = `${path9}.tmp-${process.pid}-${Date.now()}`;
5061
5503
  (0, import_node_fs.writeFileSync)(tmpPath, `${JSON.stringify(data, null, 2)}
5062
5504
  `, { mode: 384 });
5063
5505
  try {
5064
- (0, import_node_fs.renameSync)(tmpPath, path8);
5506
+ (0, import_node_fs.renameSync)(tmpPath, path9);
5065
5507
  } catch (err) {
5066
5508
  (0, import_node_fs.rmSync)(tmpPath, { force: true });
5067
5509
  throw err;
5068
5510
  }
5069
5511
  }
5070
- function loadComputerConfigFile(path8 = getComputerConfigFilePath()) {
5071
- const raw = readJsonObject(path8);
5512
+ function loadComputerConfigFile(path9 = getComputerConfigFilePath()) {
5513
+ const raw = readJsonObject(path9);
5072
5514
  if (raw) return coerceComputerConfig(raw);
5073
- const legacy = readJsonObject(legacyConfigFilePathFor(path8));
5515
+ const legacy = readJsonObject(legacyConfigFilePathFor(path9));
5074
5516
  return legacy ? coerceComputerConfig(legacy) : {};
5075
5517
  }
5076
- function ignoredComputerConfigKeys(path8 = getComputerConfigFilePath()) {
5077
- const raw = readJsonObject(path8) ?? readJsonObject(legacyConfigFilePathFor(path8));
5518
+ function ignoredComputerConfigKeys(path9 = getComputerConfigFilePath()) {
5519
+ const raw = readJsonObject(path9) ?? readJsonObject(legacyConfigFilePathFor(path9));
5078
5520
  if (!raw) return [];
5079
5521
  return IGNORED_COMPUTER_CONFIG_KEYS.filter((key) => raw[key] !== void 0);
5080
5522
  }
5081
- function saveComputerConfigFile(cfg, path8 = getComputerConfigFilePath()) {
5082
- writeJsonAtomic(path8, cfg);
5523
+ function saveComputerConfigFile(cfg, path9 = getComputerConfigFilePath()) {
5524
+ writeJsonAtomic(path9, cfg);
5083
5525
  }
5084
- function updateComputerConfigFile(patch, path8 = getComputerConfigFilePath()) {
5085
- const current = readJsonObject(path8);
5526
+ function updateComputerConfigFile(patch, path9 = getComputerConfigFilePath()) {
5527
+ const current = readJsonObject(path9);
5086
5528
  const merged = current ? coerceComputerConfig(current) : {};
5087
5529
  for (const [k, v] of Object.entries(patch)) {
5088
5530
  if (v !== void 0) merged[k] = v;
5089
5531
  }
5090
- saveComputerConfigFile(merged, path8);
5532
+ saveComputerConfigFile(merged, path9);
5091
5533
  return merged;
5092
5534
  }
5093
- function updateJsonFile(patch, path8) {
5094
- const current = readJsonObject(path8) ?? {};
5535
+ function updateJsonFile(patch, path9) {
5536
+ const current = readJsonObject(path9) ?? {};
5095
5537
  const merged = { ...current };
5096
5538
  for (const [k, v] of Object.entries(patch)) {
5097
5539
  if (v !== void 0) merged[k] = v;
5098
5540
  }
5099
- writeJsonAtomic(path8, merged);
5541
+ writeJsonAtomic(path9, merged);
5100
5542
  }
5101
5543
 
5102
5544
  // ../../shared/platform/src/config-prompt.ts
@@ -5168,10 +5610,10 @@ function isLoopbackAuthority(addr) {
5168
5610
  return false;
5169
5611
  }
5170
5612
  }
5171
- function hubUrlFromAddr(addr, kind, path8 = "") {
5613
+ function hubUrlFromAddr(addr, kind, path9 = "") {
5172
5614
  const secure = !isLoopbackAuthority(addr);
5173
5615
  const scheme = kind === "ws" ? secure ? "wss" : "ws" : secure ? "https" : "http";
5174
- return `${scheme}://${addr}${path8}`;
5616
+ return `${scheme}://${addr}${path9}`;
5175
5617
  }
5176
5618
  function isLoopbackComputerUrl(url2) {
5177
5619
  let host;
@@ -5183,6 +5625,68 @@ function isLoopbackComputerUrl(url2) {
5183
5625
  return LOOPBACK_HOSTS.has(host) || host.startsWith("127.");
5184
5626
  }
5185
5627
 
5628
+ // ../../shared/platform/src/parent-watch.ts
5629
+ var PARENT_POLL_INTERVAL_MS = 1e3;
5630
+ var SUPERVISED_STOP_DEADLINE_MS = 5e3;
5631
+ var SUPERVISED_STOP_SIGKILL_GRACE_MS = SUPERVISED_STOP_DEADLINE_MS + 1e3;
5632
+ function pidExists(pid) {
5633
+ try {
5634
+ process.kill(pid, 0);
5635
+ return true;
5636
+ } catch (err) {
5637
+ return err.code === "EPERM";
5638
+ }
5639
+ }
5640
+ function isBrokenPipe(err) {
5641
+ return err?.code === "EPIPE";
5642
+ }
5643
+ function watchParent(opts) {
5644
+ const stdin2 = opts.stdin ?? process.stdin;
5645
+ const outputs = opts.outputs ?? [process.stdout, process.stderr];
5646
+ const getPpid = opts.getPpid ?? (() => process.ppid);
5647
+ const isAlive = opts.isAlive ?? pidExists;
5648
+ const platform3 = opts.platform ?? process.platform;
5649
+ const parentPid = getPpid();
5650
+ let armed = true;
5651
+ let poll = null;
5652
+ const stop = () => {
5653
+ armed = false;
5654
+ if (poll) clearInterval(poll);
5655
+ poll = null;
5656
+ };
5657
+ const fire = (reason) => {
5658
+ if (!armed) return;
5659
+ stop();
5660
+ opts.onParentDeath(reason);
5661
+ };
5662
+ const onPipeClosed = () => fire("parent-pipe-closed");
5663
+ stdin2.on("end", onPipeClosed);
5664
+ stdin2.on("close", onPipeClosed);
5665
+ stdin2.on("error", onPipeClosed);
5666
+ stdin2.resume();
5667
+ stdin2.unref?.();
5668
+ for (const output2 of outputs) {
5669
+ output2.on("error", (err) => {
5670
+ if (!isBrokenPipe(err)) throw err;
5671
+ fire("parent-output-closed");
5672
+ });
5673
+ }
5674
+ const check2 = () => {
5675
+ const ppid = getPpid();
5676
+ if (ppid !== parentPid || platform3 !== "win32" && ppid === 1) {
5677
+ fire("parent-reparented");
5678
+ return;
5679
+ }
5680
+ if (!isAlive(parentPid)) fire("parent-exited");
5681
+ };
5682
+ check2();
5683
+ if (armed) {
5684
+ poll = setInterval(check2, opts.pollIntervalMs ?? PARENT_POLL_INTERVAL_MS);
5685
+ poll.unref();
5686
+ }
5687
+ return { stop };
5688
+ }
5689
+
5186
5690
  // ../../domains/computer-transport/src/shared/rpc.ts
5187
5691
  var RpcError = class _RpcError extends Error {
5188
5692
  code;
@@ -5237,16 +5741,23 @@ var DuplexRpc = class {
5237
5741
  * Issue a JSON-RPC request. Resolves with the `result` payload, rejects with
5238
5742
  * `RpcError` on a JSON-RPC error reply, `RpcTimeoutError` on timeout, or
5239
5743
  * `RpcDisposedError` on disposal. `timeoutMs <= 0` disables the timeout.
5744
+ *
5745
+ * `actor`, when given, rides as the envelope's top-level `actor` member (who
5746
+ * asked — hub → computer requests only; see `requestActorSchema`).
5240
5747
  */
5241
- request(method, params, timeoutMs) {
5748
+ request(method, params, timeoutMs, actor) {
5242
5749
  if (this.disposed) {
5243
5750
  return Promise.reject(new RpcDisposedError(this.label));
5244
5751
  }
5245
5752
  const effectiveTimeout = timeoutMs ?? this.opts.defaultRequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
5246
5753
  const id = this.nextId++;
5247
- const envelope = JSON.stringify(
5248
- params === void 0 ? { jsonrpc: "2.0", id, method } : { jsonrpc: "2.0", id, method, params }
5249
- );
5754
+ const envelope = JSON.stringify({
5755
+ jsonrpc: "2.0",
5756
+ id,
5757
+ method,
5758
+ ...params === void 0 ? {} : { params },
5759
+ ...actor === void 0 ? {} : { actor }
5760
+ });
5250
5761
  return new Promise((resolve7, reject) => {
5251
5762
  const timer = effectiveTimeout > 0 ? setTimeout(() => {
5252
5763
  this.pending.delete(id);
@@ -5322,7 +5833,7 @@ var DuplexRpc = class {
5322
5833
  if (typeof msg.method === "string") {
5323
5834
  if (hasId) {
5324
5835
  if (this.opts.onPeerRequest) {
5325
- this.opts.onPeerRequest(msg.method, msg.params, rawId);
5836
+ this.opts.onPeerRequest(msg.method, msg.params, rawId, msg.actor);
5326
5837
  } else {
5327
5838
  this.respondError(
5328
5839
  rawId,
@@ -6183,10 +6694,10 @@ function mergeDefs(...defs) {
6183
6694
  function cloneDef(schema) {
6184
6695
  return mergeDefs(schema._zod.def);
6185
6696
  }
6186
- function getElementAtPath(obj, path8) {
6187
- if (!path8)
6697
+ function getElementAtPath(obj, path9) {
6698
+ if (!path9)
6188
6699
  return obj;
6189
- return path8.reduce((acc, key) => acc?.[key], obj);
6700
+ return path9.reduce((acc, key) => acc?.[key], obj);
6190
6701
  }
6191
6702
  function promiseAllObject(promisesObj) {
6192
6703
  const keys = Object.keys(promisesObj);
@@ -6526,11 +7037,11 @@ function explicitlyAborted(x, startIndex = 0) {
6526
7037
  }
6527
7038
  return false;
6528
7039
  }
6529
- function prefixIssues(path8, issues) {
7040
+ function prefixIssues(path9, issues) {
6530
7041
  return issues.map((iss) => {
6531
7042
  var _a3;
6532
7043
  (_a3 = iss).path ?? (_a3.path = []);
6533
- iss.path.unshift(path8);
7044
+ iss.path.unshift(path9);
6534
7045
  return iss;
6535
7046
  });
6536
7047
  }
@@ -6980,16 +7491,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
6980
7491
  }
6981
7492
  function formatError(error62, mapper = (issue2) => issue2.message) {
6982
7493
  const fieldErrors = { _errors: [] };
6983
- const processError = (error63, path8 = []) => {
7494
+ const processError = (error63, path9 = []) => {
6984
7495
  for (const issue2 of error63.issues) {
6985
7496
  if (issue2.code === "invalid_union" && issue2.errors.length) {
6986
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
7497
+ issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
6987
7498
  } else if (issue2.code === "invalid_key") {
6988
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
7499
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
6989
7500
  } else if (issue2.code === "invalid_element") {
6990
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
7501
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
6991
7502
  } else {
6992
- const fullpath = [...path8, ...issue2.path];
7503
+ const fullpath = [...path9, ...issue2.path];
6993
7504
  if (fullpath.length === 0) {
6994
7505
  fieldErrors._errors.push(mapper(issue2));
6995
7506
  } else {
@@ -7028,17 +7539,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
7028
7539
  }
7029
7540
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
7030
7541
  const result = { errors: [] };
7031
- const processError = (error63, path8 = []) => {
7542
+ const processError = (error63, path9 = []) => {
7032
7543
  var _a3;
7033
7544
  for (const issue2 of error63.issues) {
7034
7545
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7035
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
7546
+ issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
7036
7547
  } else if (issue2.code === "invalid_key") {
7037
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
7548
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7038
7549
  } else if (issue2.code === "invalid_element") {
7039
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
7550
+ processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7040
7551
  } else {
7041
- const fullpath = [...path8, ...issue2.path];
7552
+ const fullpath = [...path9, ...issue2.path];
7042
7553
  if (fullpath.length === 0) {
7043
7554
  result.errors.push(mapper(issue2));
7044
7555
  continue;
@@ -7077,8 +7588,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
7077
7588
  }
7078
7589
  function toDotPath(_path) {
7079
7590
  const segs = [];
7080
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7081
- for (const seg of path8) {
7591
+ const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7592
+ for (const seg of path9) {
7082
7593
  if (typeof seg === "number")
7083
7594
  segs.push(`[${seg}]`);
7084
7595
  else if (typeof seg === "symbol")
@@ -7364,8 +7875,8 @@ function emoji() {
7364
7875
  }
7365
7876
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
7366
7877
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
7367
- var mac = (delimiter2) => {
7368
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
7878
+ var mac = (delimiter3) => {
7879
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
7369
7880
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
7370
7881
  };
7371
7882
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -18269,8 +18780,8 @@ function registry() {
18269
18780
  var globalRegistry = globalThis.__zod_globalRegistry;
18270
18781
 
18271
18782
  // ../../../node_modules/zod/v4/core/compile.js
18272
- var INVALID = Symbol.for("zod.compile.invalid");
18273
- var FALLBACK_FLAG = Symbol.for("zod.compile.fallback");
18783
+ var INVALID = /* @__PURE__ */ Symbol.for("zod.compile.invalid");
18784
+ var FALLBACK_FLAG = /* @__PURE__ */ Symbol.for("zod.compile.fallback");
18274
18785
  var ZodCompileAsyncError = class extends Error {
18275
18786
  constructor(message = "z.compile does not support async refinements, transforms, or checks") {
18276
18787
  super(message);
@@ -22050,7 +22561,7 @@ var nonoptionalProcessor = (schema, ctx, _json, params) => {
22050
22561
  const seen = ctx.seen.get(schema);
22051
22562
  seen.ref = def.innerType;
22052
22563
  };
22053
- var UNREPRESENTABLE_DEFAULT = Symbol();
22564
+ var UNREPRESENTABLE_DEFAULT = /* @__PURE__ */ Symbol();
22054
22565
  function serializeDefaultValue(value, schema, ctx, json2, params) {
22055
22566
  let unrepresentable = false;
22056
22567
  const serialized = JSON.stringify(value, (_, val) => {
@@ -24180,13 +24691,13 @@ function resolveRef(ref, ctx) {
24180
24691
  if (!ref.startsWith("#")) {
24181
24692
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
24182
24693
  }
24183
- const path8 = ref.slice(1).split("/").filter(Boolean);
24184
- if (path8.length === 0) {
24694
+ const path9 = ref.slice(1).split("/").filter(Boolean);
24695
+ if (path9.length === 0) {
24185
24696
  return ctx.rootSchema;
24186
24697
  }
24187
24698
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
24188
- if (path8[0] === defsKey) {
24189
- const key = path8[1] === void 0 ? void 0 : decodeJSONPointerSegment(path8[1]);
24699
+ if (path9[0] === defsKey) {
24700
+ const key = path9[1] === void 0 ? void 0 : decodeJSONPointerSegment(path9[1]);
24190
24701
  if (!key || !ctx.defs[key]) {
24191
24702
  throw new Error(`Reference not found: ${ref}`);
24192
24703
  }
@@ -24814,7 +25325,7 @@ function fromJSONSchema(schema, params) {
24814
25325
  }
24815
25326
 
24816
25327
  // ../../../node_modules/zod/v4/core/visit.js
24817
- var RESOLVING = Symbol("z.visit/resolving");
25328
+ var RESOLVING = /* @__PURE__ */ Symbol("z.visit/resolving");
24818
25329
  function visit(schema, fnOrHandlers) {
24819
25330
  const fn = typeof fnOrHandlers === "function" ? fnOrHandlers : (node2, rewritten) => {
24820
25331
  const h = fnOrHandlers[node2._zod.def.type];
@@ -25037,7 +25548,7 @@ function date4(params) {
25037
25548
 
25038
25549
  // ../../domains/computer-transport/src/shared/frames.ts
25039
25550
  var COMPUTER_EXIT_NEEDS_RE_ENROLLMENT = 78;
25040
- var COMPUTER_PROTOCOL_VERSION = 2;
25551
+ var COMPUTER_PROTOCOL_VERSION = 3;
25041
25552
  var ComputerTransportErrorCodes = {
25042
25553
  /** Command is part of the contract but not implemented by this peer. */
25043
25554
  unimplemented: -32001,
@@ -25053,8 +25564,20 @@ var ComputerTransportErrorCodes = {
25053
25564
  * computer retries, and gets in once the incumbent's connection is gone.
25054
25565
  * Older computers treat any unlisted code as transient too.
25055
25566
  */
25056
- computerInUse: -32005
25567
+ computerInUse: -32005,
25568
+ /**
25569
+ * The computer refused the request by its own local policy (T-27): a hub-pushed
25570
+ * `allowedRoots` change, or a path / spawn cwd outside its `allowedRoots`. Not
25571
+ * transient — retrying the same request is refused again. The hub audits it as
25572
+ * `denied` and the message says what to do on the device.
25573
+ */
25574
+ refusedByComputer: -32006
25057
25575
  };
25576
+ var requestActorSchema = external_exports.object({
25577
+ userId: external_exports.string().min(1).max(200).nullable(),
25578
+ /** `trpc <path>` / `METHOD /path` the request entered the hub through. */
25579
+ route: external_exports.string().max(200).nullable()
25580
+ });
25058
25581
  var ComputerToHubMethods = {
25059
25582
  enroll: "enroll",
25060
25583
  hello: "hello",
@@ -25078,15 +25601,25 @@ var enrollParamsSchema = external_exports.object({
25078
25601
  });
25079
25602
  var enrollResultSchema = external_exports.object({
25080
25603
  computerId: external_exports.string().min(1),
25081
- apiKey: external_exports.string().min(1)
25604
+ apiKey: external_exports.string().min(1),
25605
+ /** The hub's protocol version (v3+). Absent = an older hub; the computer refuses it. */
25606
+ protocolVersion: external_exports.number().int().positive().optional()
25082
25607
  });
25083
25608
  var helloParamsSchema = external_exports.object({
25084
25609
  apiKey: external_exports.string().min(1),
25085
25610
  epoch: epochField,
25086
- hostId: hostIdField
25611
+ hostId: hostIdField,
25612
+ /**
25613
+ * v3+. Before v3 only `enroll` carried a version, so a computer enrolled long ago
25614
+ * reconnected on any protocol. Absent = a pre-v3 computer; a v3 hub refuses it
25615
+ * with `protocolMismatch`.
25616
+ */
25617
+ protocolVersion: external_exports.number().int().positive().optional()
25087
25618
  });
25088
25619
  var helloResultSchema = external_exports.object({
25089
- computerId: external_exports.string().min(1)
25620
+ computerId: external_exports.string().min(1),
25621
+ /** The hub's protocol version (v3+). Absent = an older hub; the computer refuses it. */
25622
+ protocolVersion: external_exports.number().int().positive().optional()
25090
25623
  });
25091
25624
  var heartbeatParamsSchema = external_exports.object({
25092
25625
  /** Sender wall-clock ms, for skew diagnostics. */
@@ -25161,6 +25694,8 @@ var HubToComputerMethods = {
25161
25694
  ptyList: "pty.list",
25162
25695
  /** Move a session still waiting for an agent-start slot to the front (tab revealed). */
25163
25696
  ptyPrioritize: "pty.prioritize",
25697
+ /** Which of claude's background jobs still have a live process under a session. */
25698
+ ptyBackgroundJobs: "pty.backgroundJobs",
25164
25699
  // warm pool (pre-warmed agents live ON the computer — see server/warm-*)
25165
25700
  ptyWarmSpawn: "pty.warmSpawn",
25166
25701
  ptyWarmAdopt: "pty.warmAdopt",
@@ -25281,6 +25816,21 @@ var ptyPrioritizeResultSchema = external_exports.object({
25281
25816
  ok: external_exports.literal(true),
25282
25817
  queued: external_exports.boolean()
25283
25818
  });
25819
+ var ptyBackgroundJobsParamsSchema = external_exports.object({
25820
+ sessionId: external_exports.string().min(1),
25821
+ jobs: external_exports.array(
25822
+ external_exports.object({
25823
+ id: external_exports.string().min(1),
25824
+ kind: external_exports.enum(["shell", "monitor"]),
25825
+ command: external_exports.string().nullable()
25826
+ })
25827
+ ).max(256)
25828
+ });
25829
+ var ptyBackgroundJobsResultSchema = external_exports.object({
25830
+ ok: external_exports.literal(true),
25831
+ supported: external_exports.boolean(),
25832
+ alive: external_exports.array(external_exports.string())
25833
+ });
25284
25834
  var ptyResizeParamsSchema = external_exports.object({
25285
25835
  sessionId: external_exports.string().min(1),
25286
25836
  cols: external_exports.number().int().positive(),
@@ -26007,7 +26557,7 @@ var HubDialer = class {
26007
26557
  write: (line) => {
26008
26558
  ws.send(line);
26009
26559
  },
26010
- onPeerRequest: (method, params, id) => this.handleHubRequest(conn, method, params, id),
26560
+ onPeerRequest: (method, params, id, actor) => this.handleHubRequest(conn, method, params, id, actor),
26011
26561
  onNotification: (method, params) => this.events.emit("notification", { method, params }),
26012
26562
  onParseError: (line, err) => {
26013
26563
  this.log("computer dialer received malformed frame", { error: String(err), line });
@@ -26048,6 +26598,22 @@ var HubDialer = class {
26048
26598
  });
26049
26599
  return enrollResultSchema.parse(raw);
26050
26600
  }
26601
+ /**
26602
+ * v3 (T-27): refuse a hub that does not echo our protocol version. Only a hub
26603
+ * older than v3 omits it, and such a hub sends no actor with its requests, so
26604
+ * nothing it asked for could be logged against a user. Fatal, like an enroll
26605
+ * mismatch: retrying the same hub cannot fix it; upgrading it does.
26606
+ */
26607
+ hubSpeaksOurProtocol(conn, hubVersion) {
26608
+ if (hubVersion === COMPUTER_PROTOCOL_VERSION) return true;
26609
+ this.fatal(
26610
+ new Error(
26611
+ `hub speaks computer protocol ${hubVersion === void 0 ? "older than v3" : `v${hubVersion}`}, this computer speaks v${COMPUTER_PROTOCOL_VERSION} \u2014 upgrade the hub and this computer to the same SlayZone release`
26612
+ ),
26613
+ conn
26614
+ );
26615
+ return false;
26616
+ }
26051
26617
  async authenticate(conn) {
26052
26618
  this.setState("authenticating");
26053
26619
  try {
@@ -26057,14 +26623,20 @@ var HubDialer = class {
26057
26623
  const raw = await conn.rpc.request(ComputerToHubMethods.hello, {
26058
26624
  apiKey: stored.apiKey,
26059
26625
  epoch: this.epoch,
26626
+ protocolVersion: COMPUTER_PROTOCOL_VERSION,
26060
26627
  // Reconnect is the ONLY moment the hub can learn that this computer
26061
26628
  // adopted a peer's host id while it was offline.
26062
26629
  ...this.opts.identity.hostId ? { hostId: this.opts.identity.hostId } : {}
26063
26630
  });
26064
26631
  const result2 = helloResultSchema.parse(raw);
26632
+ if (!this.hubSpeaksOurProtocol(conn, result2.protocolVersion)) return;
26065
26633
  this.onAuthenticated(conn, result2.computerId, "hello");
26066
26634
  return;
26067
26635
  } catch (err) {
26636
+ if (err instanceof RpcError && err.code === ComputerTransportErrorCodes.protocolMismatch) {
26637
+ this.fatal(new Error(`hub refused reconnect: ${err.message}`), conn);
26638
+ return;
26639
+ }
26068
26640
  if (!(err instanceof RpcError) || err.code !== ComputerTransportErrorCodes.unauthorized)
26069
26641
  throw err;
26070
26642
  if (!this.opts.joinToken) {
@@ -26137,6 +26709,7 @@ var HubDialer = class {
26137
26709
  throw retryErr;
26138
26710
  }
26139
26711
  }
26712
+ if (!this.hubSpeaksOurProtocol(conn, result.protocolVersion)) return;
26140
26713
  try {
26141
26714
  await this.opts.credentialStore.save({
26142
26715
  computerId: result.computerId,
@@ -26184,7 +26757,7 @@ var HubDialer = class {
26184
26757
  }, intervalMs);
26185
26758
  conn.heartbeatTimer.unref?.();
26186
26759
  }
26187
- handleHubRequest(conn, method, params, id) {
26760
+ handleHubRequest(conn, method, params, id, actor) {
26188
26761
  if (method === HubToComputerMethods.ping) {
26189
26762
  conn.rpc.respond(id, { ts: Date.now() });
26190
26763
  return;
@@ -26200,7 +26773,7 @@ var HubDialer = class {
26200
26773
  }
26201
26774
  void (async () => {
26202
26775
  try {
26203
- const result = await handler(method, params);
26776
+ const result = await handler(method, params, actor);
26204
26777
  conn.rpc.respond(id, result ?? null);
26205
26778
  } catch (err) {
26206
26779
  if (err instanceof RpcError) {
@@ -26311,7 +26884,19 @@ var computerConfigSchema = external_exports.object({
26311
26884
  /** Filesystem roots this computer may operate under (fs./git. commands). */
26312
26885
  allowedRoots: external_exports.array(external_exports.string().min(1)),
26313
26886
  /** Capability tags advertised at enrollment. */
26314
- capabilities: external_exports.array(external_exports.string().min(1))
26887
+ capabilities: external_exports.array(external_exports.string().min(1)),
26888
+ /**
26889
+ * May a hub change `allowedRoots` (`computer.setAllowedRoots`)? T-27: folder
26890
+ * limits are the device user's decision, so NO by default — set them on the
26891
+ * device (`slay computer roots`, or `computer.config.json`).
26892
+ *
26893
+ * Derived only, in {@link loadComputerConfig}: true for the desktop app's own
26894
+ * supervised computer when every hub it dials is on loopback — there the hub is
26895
+ * the local app (login off, same user, same machine), and it widens the jail for
26896
+ * projects the user adds. No file key and no env var sets it, so nothing a hub
26897
+ * can write turns it on.
26898
+ */
26899
+ hubMayEditRoots: external_exports.boolean().optional()
26315
26900
  });
26316
26901
  var ENV_VARS = {
26317
26902
  // Authority ONLY — `host[:port]`, no scheme, no path. The dial scheme
@@ -26363,13 +26948,36 @@ function realpathBoundary(target) {
26363
26948
  }
26364
26949
  }
26365
26950
  }
26366
- function assertPathAllowed(candidate, allowedRoots) {
26951
+ var JailViolationError = class extends Error {
26952
+ constructor(message) {
26953
+ super(message);
26954
+ this.name = "JailViolationError";
26955
+ }
26956
+ };
26957
+ var protectedPaths = [];
26958
+ function setProtectedPaths(paths) {
26959
+ protectedPaths = paths.map((p) => realpathBoundary(p));
26960
+ }
26961
+ function isInside(path9, dir) {
26962
+ return path9 === dir || path9.startsWith(dir + import_node_path3.sep);
26963
+ }
26964
+ function isProtectedPath(absolute) {
26965
+ return protectedPaths.some((guarded) => isInside(absolute, guarded));
26966
+ }
26967
+ function assertPathAllowed(candidate, allowedRoots, opts = {}) {
26367
26968
  if (allowedRoots.length === 0) {
26368
- throw new Error(
26969
+ throw new JailViolationError(
26369
26970
  `computer has no allowedRoots configured; refusing filesystem access to '${candidate}'`
26370
26971
  );
26371
26972
  }
26372
26973
  const resolved = realpathBoundary(candidate);
26974
+ for (const guarded of protectedPaths) {
26975
+ if (isInside(resolved, guarded) || opts.subtree && isInside(guarded, resolved)) {
26976
+ throw new JailViolationError(
26977
+ `path '${candidate}' holds this computer's own state (config, credentials or request log); refused`
26978
+ );
26979
+ }
26980
+ }
26373
26981
  for (const root of allowedRoots) {
26374
26982
  let realRoot;
26375
26983
  try {
@@ -26377,14 +26985,24 @@ function assertPathAllowed(candidate, allowedRoots) {
26377
26985
  } catch {
26378
26986
  continue;
26379
26987
  }
26380
- if (resolved === realRoot || resolved.startsWith(realRoot + import_node_path3.sep)) {
26988
+ if (isInside(resolved, realRoot)) {
26381
26989
  return resolved;
26382
26990
  }
26383
26991
  }
26384
- throw new Error(
26992
+ throw new JailViolationError(
26385
26993
  `path '${candidate}' is outside the computer's allowedRoots [${allowedRoots.join(", ")}]`
26386
26994
  );
26387
26995
  }
26996
+ function spawnCwd(cwd, allowedRoots) {
26997
+ if (cwd !== void 0) return assertPathAllowed(cwd, allowedRoots);
26998
+ for (const root of allowedRoots) {
26999
+ try {
27000
+ return import_node_fs2.realpathSync.native((0, import_node_path3.resolve)(root));
27001
+ } catch {
27002
+ }
27003
+ }
27004
+ throw new JailViolationError("computer has no existing allowedRoots; refusing to start a process");
27005
+ }
26388
27006
  function fromSharedConfig(shared) {
26389
27007
  const out = {};
26390
27008
  if (shared.hubUrl !== void 0) out.hubUrl = shared.hubUrl;
@@ -26419,6 +27037,9 @@ function loadComputerConfig(env = process.env, shared = env === process.env && e
26419
27037
  ...envJoinToken !== void 0 ? { joinToken: envJoinToken } : {}
26420
27038
  };
26421
27039
  const result = computerConfigSchema.safeParse(merged);
27040
+ if (result.success) {
27041
+ result.data.hubMayEditRoots = env.SLAYZONE_SUPERVISED === "1" && effectiveHubUrls(result.data).every((url2) => isLoopbackComputerUrl(url2));
27042
+ }
26422
27043
  if (!result.success) {
26423
27044
  const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
26424
27045
  throw new Error(
@@ -26461,71 +27082,587 @@ function effectiveHubUrls(config2) {
26461
27082
  return [...new Set(all)];
26462
27083
  }
26463
27084
 
26464
- // src/host-id.ts
26465
- var import_node_crypto2 = require("node:crypto");
26466
- var import_promises3 = require("node:fs/promises");
26467
- var import_node_os3 = require("node:os");
26468
- var import_node_path4 = require("node:path");
26469
- function slayzoneRootDir2() {
26470
- if (process.env.SLAYZONE_ROOT) return process.env.SLAYZONE_ROOT;
26471
- const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os3.homedir)();
26472
- return (0, import_node_path4.join)(home, ".slayzone");
26473
- }
26474
- function hostIdFilePath(baseDir) {
26475
- return (0, import_node_path4.join)(baseDir ?? slayzoneRootDir2(), "computer.host.json");
26476
- }
26477
- function legacyHostIdFilePath(baseDir) {
26478
- return (0, import_node_path4.join)(baseDir ?? slayzoneRootDir2(), "computer.host-id.json");
26479
- }
26480
- function sharedHostIdPath() {
26481
- return process.platform === "win32" ? null : "/tmp/slayzone-host-id";
26482
- }
26483
- var hostIdFileSchema = external_exports.object({ hostId: external_exports.string().min(1), updatedAt: external_exports.number() });
26484
- async function readDurable(filePath) {
26485
- try {
26486
- const parsed = hostIdFileSchema.safeParse(JSON.parse(await (0, import_promises3.readFile)(filePath, "utf-8")));
26487
- return parsed.success ? parsed.data.hostId : null;
26488
- } catch {
26489
- return null;
26490
- }
26491
- }
26492
- async function writeDurable(filePath, hostId) {
26493
- await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(filePath), { recursive: true, mode: 448 });
26494
- const tmpPath = `${filePath}.tmp-${process.pid}`;
26495
- await (0, import_promises3.writeFile)(tmpPath, `${JSON.stringify({ hostId, updatedAt: Date.now() }, null, 2)}
26496
- `, {
26497
- mode: 384
26498
- });
26499
- try {
26500
- await (0, import_promises3.rename)(tmpPath, filePath);
26501
- } catch (err) {
26502
- await (0, import_promises3.rm)(tmpPath, { force: true });
26503
- throw err;
26504
- }
26505
- }
26506
- async function readShared(sharedPath) {
26507
- try {
26508
- const raw = (await (0, import_promises3.readFile)(sharedPath, "utf-8")).trim();
26509
- return raw.length > 0 ? raw : null;
26510
- } catch {
26511
- return null;
26512
- }
26513
- }
26514
- async function writeShared(sharedPath, hostId) {
26515
- try {
26516
- const fh2 = await (0, import_promises3.open)(sharedPath, import_promises3.constants.O_WRONLY | import_promises3.constants.O_CREAT | import_promises3.constants.O_EXCL);
26517
- try {
26518
- await fh2.chmod(438);
26519
- await fh2.writeFile(`${hostId}
26520
- `);
26521
- } finally {
26522
- await fh2.close();
27085
+ // src/login-shell-path.ts
27086
+ var import_node_path5 = require("node:path");
27087
+
27088
+ // ../../shared/platform/src/index.ts
27089
+ init_dirs();
27090
+
27091
+ // ../../shared/platform/src/paths.ts
27092
+ init_dirs();
27093
+
27094
+ // ../../shared/platform/src/hub-switches.ts
27095
+ var HUB_SWITCH_ENV = {
27096
+ requireLogin: "SLAYZONE_HUB_REQUIRE_LOGIN",
27097
+ enablePassword: "SLAYZONE_HUB_ENABLE_PASSWORD",
27098
+ enableSso: "SLAYZONE_HUB_ENABLE_SSO",
27099
+ enableProcesses: "SLAYZONE_HUB_ENABLE_PROCESSES",
27100
+ enableIntegrations: "SLAYZONE_HUB_ENABLE_INTEGRATIONS"
27101
+ };
27102
+ var HUB_SSO_ENV = {
27103
+ tenantId: "SLAYZONE_HUB_SSO_TENANT_ID",
27104
+ clientId: "SLAYZONE_HUB_SSO_CLIENT_ID",
27105
+ /** PEM certificate registered on the Entra app (its SHA-256 thumbprint goes in each assertion). Env only. */
27106
+ certPath: "SLAYZONE_HUB_SSO_CERT_PATH",
27107
+ /** PEM private key of that certificate (RSA, unencrypted). Env only. */
27108
+ keyPath: "SLAYZONE_HUB_SSO_KEY_PATH",
27109
+ /** `0|1`, default `1`: refuse a sign-in whose ID token's `amr` does not contain `mfa`. */
27110
+ requireMfa: "SLAYZONE_HUB_SSO_REQUIRE_MFA"
27111
+ };
27112
+ var RETIRED_HUB_SSO_CLIENT_SECRET_ENV = "SLAYZONE_HUB_SSO_CLIENT_SECRET";
27113
+ var HUB_ADDRESS_ENV = {
27114
+ /** Where the hub LISTENS. Renamed from `SLAYZONE_HUB_ADDRESS`, which now only
27115
+ * means "the hub to connect to" on a computer / `slay`. */
27116
+ listen: "SLAYZONE_HUB_LISTEN_ADDRESS",
27117
+ /** How OTHER machines reach the hub (`host[:port]`, no scheme). */
27118
+ public: "SLAYZONE_HUB_PUBLIC_ADDRESS",
27119
+ /** Signing key of hub-auth. */
27120
+ authSecret: "SLAYZONE_HUB_AUTH_SECRET"
27121
+ };
27122
+ var LEGACY_HUB_LISTEN_ENV = "SLAYZONE_HUB_ADDRESS";
27123
+ var HUB_SESSION_RETENTION_DAYS_ENV = "SLAYZONE_HUB_SESSION_RETENTION_DAYS";
27124
+ var HUB_ALLOWED_WEB_ORIGINS_ENV = "SLAYZONE_HUB_ALLOWED_WEB_ORIGINS";
27125
+ var HUB_TRUSTED_PROXIES_ENV = "SLAYZONE_HUB_TRUSTED_PROXIES";
27126
+ var HUB_CONVEX_ADDRESS_ENV = "SLAYZONE_HUB_CONVEX_ADDRESS";
27127
+ var HUB_PROCESS_ENV = [
27128
+ ...Object.values(HUB_SWITCH_ENV),
27129
+ ...Object.values(HUB_SSO_ENV),
27130
+ RETIRED_HUB_SSO_CLIENT_SECRET_ENV,
27131
+ ...Object.values(HUB_ADDRESS_ENV),
27132
+ LEGACY_HUB_LISTEN_ENV,
27133
+ HUB_ALLOWED_WEB_ORIGINS_ENV,
27134
+ HUB_TRUSTED_PROXIES_ENV,
27135
+ HUB_SESSION_RETENTION_DAYS_ENV,
27136
+ HUB_CONVEX_ADDRESS_ENV
27137
+ ];
27138
+
27139
+ // ../../shared/platform/src/security-log.ts
27140
+ var import_node_async_hooks = require("node:async_hooks");
27141
+ var LINE_SEPARATOR = String.fromCharCode(8232);
27142
+ var PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
27143
+ var contextStore = new import_node_async_hooks.AsyncLocalStorage();
27144
+
27145
+ // ../../shared/platform/src/hub-switch-refusal.ts
27146
+ var MESSAGES = {
27147
+ HUB_PROCESSES_DISABLED: `This hub does not run processes, automation commands, terminals or agents itself (${HUB_SWITCH_ENV.enableProcesses}=0).`,
27148
+ HUB_INTEGRATIONS_DISABLED: `Integrations are switched off on this hub (${HUB_SWITCH_ENV.enableIntegrations}=0).`,
27149
+ NO_COMPUTER_CONNECTED: `Connect a computer: this hub does not read project files or run git, gh or other commands itself (${HUB_SWITCH_ENV.enableProcesses}=0).`,
27150
+ HUB_PASSWORD_DISABLED: `Password sign-in is switched off on this hub (${HUB_SWITCH_ENV.enablePassword}=0).`,
27151
+ HUB_CONVEX_NOT_CONFIGURED: `GitHub sign-in is off on this hub: no Convex deployment is configured (${HUB_CONVEX_ADDRESS_ENV}).`,
27152
+ HUB_CONVEX_MISMATCH: `This hub starts GitHub sign-in only against its own Convex deployment (${HUB_CONVEX_ADDRESS_ENV}); the request named another.`
27153
+ };
27154
+ var VARIABLES = {
27155
+ HUB_PROCESSES_DISABLED: HUB_SWITCH_ENV.enableProcesses,
27156
+ HUB_INTEGRATIONS_DISABLED: HUB_SWITCH_ENV.enableIntegrations,
27157
+ NO_COMPUTER_CONNECTED: HUB_SWITCH_ENV.enableProcesses,
27158
+ HUB_PASSWORD_DISABLED: HUB_SWITCH_ENV.enablePassword,
27159
+ HUB_CONVEX_NOT_CONFIGURED: HUB_CONVEX_ADDRESS_ENV,
27160
+ HUB_CONVEX_MISMATCH: HUB_CONVEX_ADDRESS_ENV
27161
+ };
27162
+
27163
+ // ../../shared/platform/src/env-manifest.ts
27164
+ var ENV_MANIFEST = {
27165
+ // --- secret: credentials/tokens, never in a terminal ---
27166
+ SLAYZONE_HUB_TOKEN: "secret",
27167
+ // HMAC signing key for ALL of hub-auth (better-auth's session/cookie signer AND
27168
+ // the computer enroll/api-key credentials) — hence `HUB_AUTH`, not the former
27169
+ // `HUB_COMPUTER_TRANSPORT_SECRET`, which named just one of its consumers. Rule 2
27170
+ // (name by what the value IS). `_SECRET` = a key you HOLD and sign with, vs
27171
+ // `_TOKEN` = a bearer you PRESENT (SLAYZONE_HUB_TOKEN above).
27172
+ SLAYZONE_HUB_AUTH_SECRET: "secret",
27173
+ // Hub-scoped by VALUE: the hub mints it, and it embeds the hub's url and a
27174
+ // secret verified against the hub's `join_tokens`
27175
+ // row. `mintJoinToken` binds it to NO computer (computer_id stays NULL until
27176
+ // redemption), so any computer can redeem any unused token — there is no computer in
27177
+ // the value to name. Rule 2 (name by what the value IS, not who reads it).
27178
+ SLAYZONE_HUB_JOIN_TOKEN: "secret",
27179
+ // Retired before it was ever read: Entra sign-in authenticates with a certificate
27180
+ // (SLAYZONE_HUB_SSO_CERT_PATH / _KEY_PATH below), and boot refuses a hub that still
27181
+ // sets this (B7). Still classified `secret` so a leftover value never reaches a
27182
+ // terminal or a log.
27183
+ SLAYZONE_HUB_SSO_CLIENT_SECRET: "secret",
27184
+ // Per-boot bearer the desktop's `/cap` listener requires on every connection
27185
+ // (desktop-bridge-server.ts `verifyClient`). Loopback is NOT trusted there —
27186
+ // the threat this closes is another loopback process finding the port — so
27187
+ // this is a real credential, `secret`-scoped like the two above it.
27188
+ SLAYZONE_DESKTOP_BRIDGE_TOKEN: "secret",
27189
+ // The pre-rename `SLAYZONE_RUNNER_JOIN_TOKEN` is deliberately UNLISTED: it is
27190
+ // retired, not aliased. It never shipped (the published computer betas read
27191
+ // `SLAYZONE_JOIN_TOKEN`), so no operator env carries it and there is nothing to
27192
+ // keep working. Unlisted ⇒ the fail-closed default strips it, same as every
27193
+ // other retired name in this rename sweep.
27194
+ // --- infra: SlayZone wiring a terminal must not reinterpret ---
27195
+ // The hub to CONNECT to, host[:port] (computer, `slay`); the scheme derives from
27196
+ // the host (loopback → http/ws, else https/wss). A hub no longer reads this name:
27197
+ // it LISTENS on SLAYZONE_HUB_LISTEN_ADDRESS, and a hub that still gets the old
27198
+ // name refuses to boot. Stripping both here keeps a terminal's dial address from
27199
+ // ever being mistaken for another process's listen address.
27200
+ SLAYZONE_HUB_ADDRESS: "infra",
27201
+ SLAYZONE_HUB_LISTEN_ADDRESS: "infra",
27202
+ // hub-only; where the hub listens
27203
+ SLAYZONE_HUB_PUBLIC_ADDRESS: "infra",
27204
+ // hub-only; how other machines reach it, written into join tokens
27205
+ // Hub-only; the front ends (IPs/CIDRs, comma-separated) whose X-Forwarded-For names
27206
+ // the client (client-address.ts). Not `_ADDRESS`: these are networks, not an authority.
27207
+ SLAYZONE_HUB_TRUSTED_PROXIES: "infra",
27208
+ // Per-hub switches (0|1) replacing SLAYZONE_MODE. Infra: they describe THIS hub
27209
+ // process, so a task terminal must not inherit them (a hub started from one would
27210
+ // otherwise pick up its parent's switches by accident).
27211
+ SLAYZONE_HUB_REQUIRE_LOGIN: "infra",
27212
+ SLAYZONE_HUB_ENABLE_PASSWORD: "infra",
27213
+ SLAYZONE_HUB_ENABLE_SSO: "infra",
27214
+ SLAYZONE_HUB_ENABLE_PROCESSES: "infra",
27215
+ SLAYZONE_HUB_ENABLE_INTEGRATIONS: "infra",
27216
+ SLAYZONE_HUB_SSO_TENANT_ID: "infra",
27217
+ SLAYZONE_HUB_SSO_CLIENT_ID: "infra",
27218
+ // Paths to the Entra client certificate and its private key, and the MFA switch.
27219
+ // Infra, not secret: a path is not the key (the file's own permissions protect it),
27220
+ // but a task terminal must still never inherit a hub's SSO wiring.
27221
+ SLAYZONE_HUB_SSO_CERT_PATH: "infra",
27222
+ SLAYZONE_HUB_SSO_KEY_PATH: "infra",
27223
+ SLAYZONE_HUB_SSO_REQUIRE_MFA: "infra",
27224
+ // Operator-facing hub name (`slay hub ls`/`stop` address a hub by it). Infra,
27225
+ // not global: it identifies THIS hub process, so inheriting it into a task
27226
+ // terminal — which may target a different hub entirely — would mislabel.
27227
+ SLAYZONE_HUB_NAME: "infra",
27228
+ // Days THIS hub keeps session content (T-23, hub session-retention.ts); 0 = off, the
27229
+ // default. Infra like the switches: a hub started from a task terminal must not
27230
+ // inherit its parent's period and start deleting.
27231
+ SLAYZONE_HUB_SESSION_RETENTION_DAYS: "infra",
27232
+ // The Convex deployment (host[:port]) the hub starts GitHub sign-in against — never
27233
+ // a client-named one (T-99). Hub process config, set by the desktop app for its hub.
27234
+ SLAYZONE_HUB_CONVEX_ADDRESS: "infra",
27235
+ SLAYZONE_DESKTOP_BRIDGE_ADDRESS: "infra",
27236
+ // sidecar→desktop capability bridge + REST proxy
27237
+ SLAYZONE_SUPERVISED: "infra",
27238
+ // "started by the desktop app" flag
27239
+ SLAYZONE_USER_DATA_DIR: "infra",
27240
+ // Playwright Electron userData redirect
27241
+ SLAYZONE_SIDECAR_HOT_RESTART: "infra",
27242
+ SLAYZONE_BOOT_LOG_PATH: "infra",
27243
+ SLAYZONE_DEBUG_BOOT: "infra",
27244
+ SLAYZONE_REGISTER_DEV_PROTOCOL: "infra",
27245
+ SLAYZONE_NONINTERACTIVE: "infra",
27246
+ // Historical opt-IN, now a NO-OP: e2e runs computer-ON by default, so the boot
27247
+ // gate no longer reads this. Kept manifested because specs still set it to
27248
+ // document "this spec wants a computer" (an unmanifested var would be stripped
27249
+ // by sanitizeSpawnEnv, fail-closed, and read as a silent typo).
27250
+ SLAYZONE_E2E_ALLOW_COMPUTER: "infra",
27251
+ // Opt-OUT, and the only lever that changes behavior now: set by the specs that
27252
+ // must boot computer-less (remote-mode, sidecar-crash, and the "hub accepts
27253
+ // computers with none spawned" contract test).
27254
+ SLAYZONE_E2E_NO_COMPUTER: "infra",
27255
+ SLAYZONE_E2E_INSTALL_HOOKS: "infra",
27256
+ // Redirects the MACHINE-WIDE ~/.slayzone (hooks/, bin/) — the files shared by
27257
+ // every SlayZone process on the box, which deliberately do NOT follow
27258
+ // SLAYZONE_ROOT. Sandbox redirect for tests, same role as the per-provider
27259
+ // overrides below. `infra`: a spawned terminal must never inherit it, or an
27260
+ // agent would resolve a different notify.sh than the one wired into its hooks.
27261
+ SLAYZONE_MACHINE_DIR: "infra",
27262
+ // per-provider config/hook/plugin path overrides (test/sandbox redirects)
27263
+ SLAYZONE_CLAUDE_SETTINGS_PATH: "infra",
27264
+ SLAYZONE_GEMINI_SETTINGS_PATH: "infra",
27265
+ SLAYZONE_CODEX_HOOKS_PATH: "infra",
27266
+ SLAYZONE_ANTIGRAVITY_HOOKS_PATH: "infra",
27267
+ SLAYZONE_OPENCODE_PLUGIN_PATH: "infra",
27268
+ // --- identity: per-task/agent; overlay re-adds the correct value per spawn ---
27269
+ SLAYZONE_TASK_ID: "identity",
27270
+ SLAYZONE_PROJECT_ID: "identity",
27271
+ // `SLAYZONE_SESSION_ID` (a warm agent's pre-task identity) is RETIRED and
27272
+ // deliberately unlisted: warm agents boot with SLAYZONE_TASK_ID now.
27273
+ SLAYZONE_AGENT_ID: "identity",
27274
+ SLAYZONE_AGENT_HOOK_URL: "identity",
27275
+ // Pairs with _HOOK_URL (where to post / who is posting). The pre-v4 name
27276
+ // `SLAYZONE_HOOK_CONTEXT` is RETIRED and deliberately unlisted: unlisted =
27277
+ // stripped (fail closed), and the old-name value an older release channel's app
27278
+ // supplies rides its OWN per-spawn overlay (applied after sanitizeSpawnEnv), so
27279
+ // notify.sh v4's fallback read still sees it. A manifest entry would only let a
27280
+ // stale inherited value survive.
27281
+ SLAYZONE_AGENT_HOOK_CONTEXT: "identity",
27282
+ // --- global: same app-wide, safe to inherit verbatim ---
27283
+ SLAYZONE_RELEASE_CHANNEL: "global",
27284
+ // attribution-only (hook envelope channel)
27285
+ // INSTALL IDENTITY — which SlayZone install this process tree belongs to, not
27286
+ // which task/session. A child MUST resolve the SAME install as its parent: the
27287
+ // `slay` CLI derives its DB from BOTH (getStorageDir() reads ROOT; the
27288
+ // filename is `slayzone{.dev}.sqlite` per DEV — apps/cli/src/db.ts getDbPath).
27289
+ // Strip either and a bare `slay` in a dev-app task terminal exits 1
27290
+ // ("Database not found … Re-run with --dev"), and under an isolated e2e ROOT a
27291
+ // child would escape the sandbox to the real ~/.slayzone. Neither is a
27292
+ // credential and neither can be reinterpreted as a different kind of target
27293
+ // (unlike a hub address), so both inherit verbatim.
27294
+ //
27295
+ // ROOT is NOT covered by the identity overlay: buildMcpEnv sets it only inside
27296
+ // setHookIdentity(), gated on a hook-capable agent mode — a plain `terminal`
27297
+ // PTY, a non-hook agent, or a pre-warmed pool spawn gets no overlay at all.
27298
+ SLAYZONE_ROOT: "global",
27299
+ SLAYZONE_DEV: "global"
27300
+ };
27301
+ var NON_PREFIXED_INFRA = /* @__PURE__ */ new Set([
27302
+ "ELECTRON_RUN_AS_NODE",
27303
+ "NODE_PATH",
27304
+ "PLAYWRIGHT"
27305
+ ]);
27306
+ function sanitizeSpawnEnv(base) {
27307
+ const out = {};
27308
+ for (const [key, value] of Object.entries(base)) {
27309
+ if (typeof value !== "string") continue;
27310
+ if (NON_PREFIXED_INFRA.has(key)) continue;
27311
+ if (key.startsWith("SLAYZONE_")) {
27312
+ if (ENV_MANIFEST[key] === "global") out[key] = value;
27313
+ continue;
27314
+ }
27315
+ out[key] = value;
27316
+ }
27317
+ return out;
27318
+ }
27319
+
27320
+ // ../../shared/platform/src/index.ts
27321
+ init_fs_utils();
27322
+
27323
+ // ../../shared/platform/src/migrations.ts
27324
+ init_dirs();
27325
+
27326
+ // ../../shared/platform/src/cli-install.ts
27327
+ var import_child_process = require("child_process");
27328
+ var import_util9 = require("util");
27329
+ var execFileAsync = (0, import_util9.promisify)(import_child_process.execFile);
27330
+ var WINDOWS_SHIM = [
27331
+ "@echo off",
27332
+ `for /f "tokens=1 delims=." %%v in ('node -e "process.stdout.write(process.versions.node)"') do set NODE_MAJOR=%%v`,
27333
+ "if %NODE_MAJOR% LEQ 22 (",
27334
+ ' node --experimental-sqlite --no-warnings "%~dp0slay.js" %*',
27335
+ ") else (",
27336
+ ' node --no-warnings "%~dp0slay.js" %*',
27337
+ ")",
27338
+ ""
27339
+ ].join("\r\n");
27340
+
27341
+ // ../../shared/platform/src/shell.ts
27342
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
27343
+ var import_os2 = require("os");
27344
+ var shellOverride = null;
27345
+ function shellExists(shellPath) {
27346
+ if ((0, import_os2.platform)() === "win32") return import_node_fs3.default.existsSync(shellPath);
27347
+ try {
27348
+ import_node_fs3.default.accessSync(shellPath, import_node_fs3.default.constants.X_OK);
27349
+ return true;
27350
+ } catch {
27351
+ return false;
27352
+ }
27353
+ }
27354
+ function defaultShellForPlatform() {
27355
+ if ((0, import_os2.platform)() === "win32") return process.env.COMSPEC || "cmd.exe";
27356
+ if ((0, import_os2.platform)() === "darwin") return "/bin/zsh";
27357
+ return "/bin/bash";
27358
+ }
27359
+ function resolveUserShell() {
27360
+ if (shellOverride && shellExists(shellOverride)) return shellOverride;
27361
+ const fromEnv = process.env.SHELL?.trim();
27362
+ if (fromEnv && shellExists(fromEnv)) return fromEnv;
27363
+ try {
27364
+ const fromUser = (0, import_os2.userInfo)().shell?.trim();
27365
+ if (fromUser && shellExists(fromUser)) return fromUser;
27366
+ } catch {
27367
+ }
27368
+ return defaultShellForPlatform();
27369
+ }
27370
+ function getShellStartupArgs(shellPath) {
27371
+ if ((0, import_os2.platform)() === "win32") return [];
27372
+ const shell = shellPath.toLowerCase();
27373
+ const name = shell.split("/").pop() ?? shell;
27374
+ if (name === "zsh" || name === "bash" || name === "fish") {
27375
+ return ["-i", "-l"];
27376
+ }
27377
+ return [];
27378
+ }
27379
+ function buildShellInvocation(command) {
27380
+ const shell = resolveUserShell();
27381
+ if ((0, import_os2.platform)() === "win32") return { file: shell, args: ["/c", command] };
27382
+ const isFish = shell.endsWith("/fish");
27383
+ return { file: shell, args: [...isFish ? ["-i", "-l"] : ["-l"], "-c", command] };
27384
+ }
27385
+
27386
+ // ../../shared/platform/src/process-tree.ts
27387
+ var import_node_child_process = require("node:child_process");
27388
+ var PROCESS_TREE_DETACHED = process.platform !== "win32";
27389
+ function killPidTree(pid, signal = "SIGTERM") {
27390
+ if (process.platform === "win32") {
27391
+ try {
27392
+ (0, import_node_child_process.execFileSync)("taskkill", ["/T", "/F", "/PID", String(pid)]);
27393
+ } catch {
27394
+ }
27395
+ } else {
27396
+ try {
27397
+ process.kill(-pid, signal);
27398
+ } catch {
27399
+ }
27400
+ }
27401
+ }
27402
+
27403
+ // ../../shared/platform/src/login-shell-path.ts
27404
+ var import_node_child_process2 = require("node:child_process");
27405
+ var import_node_os3 = require("node:os");
27406
+ var import_node_path4 = __toESM(require("node:path"), 1);
27407
+ var LOGIN_SHELL_PATH_MARKER = "__SLAYZONE_PATH__";
27408
+ var LOGIN_SHELL_PATH_PROBE_TIMEOUT_MS = 5e3;
27409
+ function loginShellPathProbes(shell) {
27410
+ if ((0, import_node_os3.platform)() === "win32") return [];
27411
+ const value = shell.endsWith("/fish") ? '(string join ":" $PATH)' : '"$PATH"';
27412
+ const cmd = `printf '\\n${LOGIN_SHELL_PATH_MARKER}%s\\n' ${value}`;
27413
+ return [
27414
+ // Interactive login first — sources .zshrc/.bashrc where pnpm/nvm add PATH
27415
+ { file: shell, args: [...getShellStartupArgs(shell), "-c", cmd] },
27416
+ // Fallback: login only — sources .zprofile/.bash_profile
27417
+ { file: shell, args: ["-l", "-c", cmd] }
27418
+ ];
27419
+ }
27420
+ function parseLoginShellPathOutput(stdout2) {
27421
+ for (const line of stdout2.split(/\r?\n/).reverse()) {
27422
+ if (!line.startsWith(LOGIN_SHELL_PATH_MARKER)) continue;
27423
+ return line.slice(LOGIN_SHELL_PATH_MARKER.length).trim() || null;
27424
+ }
27425
+ return null;
27426
+ }
27427
+ function mergePathLists(primary, secondary) {
27428
+ const entries = /* @__PURE__ */ new Set();
27429
+ for (const entry of [
27430
+ ...primary.split(import_node_path4.default.delimiter),
27431
+ ...(secondary ?? "").split(import_node_path4.default.delimiter)
27432
+ ]) {
27433
+ if (entry) entries.add(entry);
27434
+ }
27435
+ return [...entries].join(import_node_path4.default.delimiter);
27436
+ }
27437
+ function runProbe(probe, env) {
27438
+ return new Promise((resolve7) => {
27439
+ const child = (0, import_node_child_process2.execFile)(
27440
+ probe.file,
27441
+ probe.args,
27442
+ { encoding: "utf-8", timeout: LOGIN_SHELL_PATH_PROBE_TIMEOUT_MS, env },
27443
+ (err, stdout2) => resolve7(err ? null : parseLoginShellPathOutput(stdout2))
27444
+ );
27445
+ child.stdin?.end();
27446
+ });
27447
+ }
27448
+ async function probeLoginShellPath(opts = {}) {
27449
+ for (const probe of loginShellPathProbes(opts.shell ?? resolveUserShell())) {
27450
+ const probed = await runProbe(probe, opts.env ?? process.env);
27451
+ if (probed) return probed;
27452
+ }
27453
+ return null;
27454
+ }
27455
+
27456
+ // ../../shared/platform/src/slz-file-url.ts
27457
+ var SLZ_FILE_HOST = "app";
27458
+ var SLZ_FILE_PREFIX = `slz-file://${SLZ_FILE_HOST}`;
27459
+
27460
+ // ../../shared/platform/src/ipc-dedup.ts
27461
+ var UNCHANGED = Object.freeze({ __ipc_unchanged__: true });
27462
+
27463
+ // src/login-shell-path.ts
27464
+ async function adoptLoginShellPath(opts = {}) {
27465
+ const env = opts.env ?? process.env;
27466
+ const probed = await probeLoginShellPath({ env, ...opts.shell ? { shell: opts.shell } : {} });
27467
+ if (!probed) return null;
27468
+ const before = new Set((env.PATH ?? "").split(import_node_path5.delimiter));
27469
+ env.PATH = mergePathLists(probed, env.PATH);
27470
+ return { added: env.PATH.split(import_node_path5.delimiter).filter((entry) => !before.has(entry)) };
27471
+ }
27472
+
27473
+ // src/main.ts
27474
+ var import_node_fs10 = require("node:fs");
27475
+ var import_node_path13 = require("node:path");
27476
+
27477
+ // src/request-log.ts
27478
+ var import_node_fs4 = require("node:fs");
27479
+ var import_node_path6 = require("node:path");
27480
+ var REQUEST_LOG_DIR_NAME = "request-log";
27481
+ var REQUEST_LOG_FILE_NAME = "requests.log";
27482
+ var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
27483
+ var DEFAULT_KEEP = 9;
27484
+ var MAX_STRING = 300;
27485
+ function clip(value) {
27486
+ if (typeof value !== "string") return void 0;
27487
+ return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}\u2026` : value;
27488
+ }
27489
+ function programName(command) {
27490
+ if (typeof command !== "string") return void 0;
27491
+ const first = command.trim().split(/\s+/)[0] ?? "";
27492
+ const base = first.split(/[\\/]/).pop() ?? "";
27493
+ return /^[A-Za-z0-9._+-]{1,64}$/.test(base) ? base : void 0;
27494
+ }
27495
+ function ghSubcommand(args) {
27496
+ if (!Array.isArray(args)) return void 0;
27497
+ const words = [];
27498
+ for (const a of args.slice(0, 2)) {
27499
+ if (typeof a !== "string" || !/^[a-z][a-z-]{0,31}$/.test(a)) break;
27500
+ words.push(a);
27501
+ }
27502
+ return words.length > 0 ? words.join(" ") : void 0;
27503
+ }
27504
+ var TARGET_KEYS = [
27505
+ "sessionId",
27506
+ "warmId",
27507
+ "watchId",
27508
+ "projectId",
27509
+ "cwd",
27510
+ "path",
27511
+ "rootPath",
27512
+ "filePath",
27513
+ "dirPath",
27514
+ "targetPath",
27515
+ "oldPath",
27516
+ "newPath",
27517
+ "srcPath",
27518
+ "destPath",
27519
+ "absoluteSrc",
27520
+ "targetDir",
27521
+ "repoPath",
27522
+ "projectPath",
27523
+ "worktreePath",
27524
+ "branch",
27525
+ "signal"
27526
+ ];
27527
+ function summarizeTarget(method, params) {
27528
+ if (params === null || typeof params !== "object") return {};
27529
+ const p = params;
27530
+ const out = {};
27531
+ for (const key of TARGET_KEYS) {
27532
+ const v = clip(p[key]);
27533
+ if (v !== void 0) out[key] = v;
27534
+ }
27535
+ const program = programName(p.command);
27536
+ if (program) out.program = program;
27537
+ if (p.shell === true) out.shell = true;
27538
+ if (method === "gh.exec") {
27539
+ const sub = ghSubcommand(p.args);
27540
+ if (sub) out.subcommand = sub;
27541
+ }
27542
+ if (Array.isArray(p.roots)) {
27543
+ out.roots = p.roots.slice(0, 20).map(clip).filter((r) => r !== void 0);
27544
+ }
27545
+ return out;
27546
+ }
27547
+ function createRequestLog(opts) {
27548
+ const dir = (0, import_node_path6.join)(opts.stateDir, REQUEST_LOG_DIR_NAME);
27549
+ const filePath = (0, import_node_path6.join)(dir, REQUEST_LOG_FILE_NAME);
27550
+ const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
27551
+ const keep = Math.max(1, opts.keep ?? DEFAULT_KEEP);
27552
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
27553
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true, mode: 448 });
27554
+ (0, import_node_fs4.chmodSync)(dir, 448);
27555
+ (0, import_node_fs4.closeSync)((0, import_node_fs4.openSync)(filePath, "a", 384));
27556
+ (0, import_node_fs4.chmodSync)(filePath, 384);
27557
+ let size = (0, import_node_fs4.statSync)(filePath).size;
27558
+ const rotate = () => {
27559
+ for (let i = keep - 1; i >= 1; i--) {
27560
+ try {
27561
+ (0, import_node_fs4.renameSync)(`${filePath}.${i}`, `${filePath}.${i + 1}`);
27562
+ } catch {
27563
+ }
27564
+ }
27565
+ (0, import_node_fs4.renameSync)(filePath, `${filePath}.1`);
27566
+ size = 0;
27567
+ };
27568
+ const append = (line) => {
27569
+ const fd = (0, import_node_fs4.openSync)(filePath, "a", 384);
27570
+ try {
27571
+ (0, import_node_fs4.writeSync)(fd, line);
27572
+ } finally {
27573
+ (0, import_node_fs4.closeSync)(fd);
27574
+ }
27575
+ size += Buffer.byteLength(line);
27576
+ };
27577
+ return {
27578
+ filePath,
27579
+ record(entry) {
27580
+ try {
27581
+ const line = `${JSON.stringify({
27582
+ ts: now().toISOString(),
27583
+ hub: entry.hub,
27584
+ method: entry.method,
27585
+ actor: entry.actor,
27586
+ target: summarizeTarget(entry.method, entry.params),
27587
+ result: entry.result,
27588
+ ...entry.code !== void 0 ? { code: entry.code } : {},
27589
+ ...entry.reason !== void 0 ? { reason: clip(entry.reason) } : {}
27590
+ })}
27591
+ `;
27592
+ if (size > 0 && size + Buffer.byteLength(line) > maxBytes) rotate();
27593
+ append(line);
27594
+ } catch (err) {
27595
+ opts.onError?.(err);
27596
+ }
27597
+ }
27598
+ };
27599
+ }
27600
+
27601
+ // src/host-id.ts
27602
+ var import_node_crypto2 = require("node:crypto");
27603
+ var import_promises4 = require("node:fs/promises");
27604
+ var import_node_os4 = require("node:os");
27605
+ var import_node_path7 = require("node:path");
27606
+ function slayzoneRootDir2() {
27607
+ if (process.env.SLAYZONE_ROOT) return process.env.SLAYZONE_ROOT;
27608
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os4.homedir)();
27609
+ return (0, import_node_path7.join)(home, ".slayzone");
27610
+ }
27611
+ function hostIdFilePath(baseDir) {
27612
+ return (0, import_node_path7.join)(baseDir ?? slayzoneRootDir2(), "computer.host.json");
27613
+ }
27614
+ function legacyHostIdFilePath(baseDir) {
27615
+ return (0, import_node_path7.join)(baseDir ?? slayzoneRootDir2(), "computer.host-id.json");
27616
+ }
27617
+ function sharedHostIdPath() {
27618
+ return process.platform === "win32" ? null : "/tmp/slayzone-host-id";
27619
+ }
27620
+ var hostIdFileSchema = external_exports.object({ hostId: external_exports.string().min(1), updatedAt: external_exports.number() });
27621
+ async function readDurable(filePath) {
27622
+ try {
27623
+ const parsed = hostIdFileSchema.safeParse(JSON.parse(await (0, import_promises4.readFile)(filePath, "utf-8")));
27624
+ return parsed.success ? parsed.data.hostId : null;
27625
+ } catch {
27626
+ return null;
27627
+ }
27628
+ }
27629
+ async function writeDurable(filePath, hostId) {
27630
+ await (0, import_promises4.mkdir)((0, import_node_path7.dirname)(filePath), { recursive: true, mode: 448 });
27631
+ const tmpPath = `${filePath}.tmp-${process.pid}`;
27632
+ await (0, import_promises4.writeFile)(tmpPath, `${JSON.stringify({ hostId, updatedAt: Date.now() }, null, 2)}
27633
+ `, {
27634
+ mode: 384
27635
+ });
27636
+ try {
27637
+ await (0, import_promises4.rename)(tmpPath, filePath);
27638
+ } catch (err) {
27639
+ await (0, import_promises4.rm)(tmpPath, { force: true });
27640
+ throw err;
27641
+ }
27642
+ }
27643
+ async function readShared(sharedPath) {
27644
+ try {
27645
+ const raw = (await (0, import_promises4.readFile)(sharedPath, "utf-8")).trim();
27646
+ return raw.length > 0 ? raw : null;
27647
+ } catch {
27648
+ return null;
27649
+ }
27650
+ }
27651
+ async function writeShared(sharedPath, hostId) {
27652
+ try {
27653
+ const fh2 = await (0, import_promises4.open)(sharedPath, import_promises4.constants.O_WRONLY | import_promises4.constants.O_CREAT | import_promises4.constants.O_EXCL);
27654
+ try {
27655
+ await fh2.chmod(438);
27656
+ await fh2.writeFile(`${hostId}
27657
+ `);
27658
+ } finally {
27659
+ await fh2.close();
26523
27660
  }
26524
27661
  return;
26525
27662
  } catch (err) {
26526
27663
  if (err.code !== "EEXIST") throw err;
26527
27664
  }
26528
- const fh = await (0, import_promises3.open)(sharedPath, import_promises3.constants.O_WRONLY | import_promises3.constants.O_TRUNC);
27665
+ const fh = await (0, import_promises4.open)(sharedPath, import_promises4.constants.O_WRONLY | import_promises4.constants.O_TRUNC);
26529
27666
  try {
26530
27667
  await fh.writeFile(`${hostId}
26531
27668
  `);
@@ -26640,50 +27777,50 @@ function createAgentHookServer(ctx) {
26640
27777
  }
26641
27778
 
26642
27779
  // src/handlers/fs.ts
26643
- var import_node_fs4 = require("node:fs");
26644
- var import_promises5 = require("node:fs/promises");
26645
- var import_node_os4 = require("node:os");
26646
- var import_node_path6 = require("node:path");
27780
+ var import_node_fs6 = require("node:fs");
27781
+ var import_promises6 = require("node:fs/promises");
27782
+ var import_node_os5 = require("node:os");
27783
+ var import_node_path9 = require("node:path");
26647
27784
 
26648
27785
  // ../../domains/file-editor/src/server/file-ops.ts
26649
- var fs = __toESM(require("node:fs"), 1);
26650
- var path2 = __toESM(require("node:path"), 1);
26651
- var import_node_child_process = require("node:child_process");
27786
+ var fs3 = __toESM(require("node:fs"), 1);
27787
+ var path4 = __toESM(require("node:path"), 1);
27788
+ var import_node_child_process3 = require("node:child_process");
26652
27789
  var import_ignore = __toESM(require_ignore(), 1);
26653
27790
  var ALWAYS_IGNORED = /* @__PURE__ */ new Set([".git", ".DS_Store"]);
26654
27791
  var MAX_FILE_SIZE = 1 * 1024 * 1024;
26655
27792
  var FORCE_MAX_FILE_SIZE = 10 * 1024 * 1024;
26656
27793
  function assertWithinRoot(root, target) {
26657
- const resolved = path2.resolve(root, target);
26658
- if (!resolved.startsWith(path2.resolve(root) + path2.sep) && resolved !== path2.resolve(root)) {
27794
+ const resolved = path4.resolve(root, target);
27795
+ if (!resolved.startsWith(path4.resolve(root) + path4.sep) && resolved !== path4.resolve(root)) {
26659
27796
  throw new Error("Path traversal denied");
26660
27797
  }
26661
27798
  return resolved;
26662
27799
  }
26663
27800
  var ignoreCache = /* @__PURE__ */ new Map();
26664
27801
  function getIgnoreFilter(rootPath) {
26665
- const root = path2.resolve(rootPath);
26666
- const gitignorePath = path2.join(root, ".gitignore");
27802
+ const root = path4.resolve(rootPath);
27803
+ const gitignorePath = path4.join(root, ".gitignore");
26667
27804
  let mtime = 0;
26668
27805
  try {
26669
- mtime = fs.statSync(gitignorePath).mtimeMs;
27806
+ mtime = fs3.statSync(gitignorePath).mtimeMs;
26670
27807
  } catch {
26671
27808
  }
26672
27809
  const cached2 = ignoreCache.get(root);
26673
27810
  if (cached2 && cached2.mtime === mtime) return cached2.ig;
26674
27811
  const ig = (0, import_ignore.default)();
26675
27812
  try {
26676
- ig.add(fs.readFileSync(gitignorePath, "utf-8"));
27813
+ ig.add(fs3.readFileSync(gitignorePath, "utf-8"));
26677
27814
  } catch {
26678
27815
  }
26679
27816
  ignoreCache.set(root, { ig, mtime });
26680
27817
  return ig;
26681
27818
  }
26682
27819
  function invalidateIgnoreCache(rootPath) {
26683
- ignoreCache.delete(path2.resolve(rootPath));
27820
+ ignoreCache.delete(path4.resolve(rootPath));
26684
27821
  }
26685
27822
  function isIgnored(rootPath, relativePath, isDir) {
26686
- if (ALWAYS_IGNORED.has(path2.basename(relativePath))) return true;
27823
+ if (ALWAYS_IGNORED.has(path4.basename(relativePath))) return true;
26687
27824
  const ig = getIgnoreFilter(rootPath);
26688
27825
  return ig.ignores(isDir ? relativePath + "/" : relativePath);
26689
27826
  }
@@ -26691,10 +27828,10 @@ function escapeRegExp(s) {
26691
27828
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26692
27829
  }
26693
27830
  function readDir(rootPath, dirPath) {
26694
- const abs = dirPath ? assertWithinRoot(rootPath, dirPath) : path2.resolve(rootPath);
27831
+ const abs = dirPath ? assertWithinRoot(rootPath, dirPath) : path4.resolve(rootPath);
26695
27832
  let entries;
26696
27833
  try {
26697
- entries = fs.readdirSync(abs, { withFileTypes: true });
27834
+ entries = fs3.readdirSync(abs, { withFileTypes: true });
26698
27835
  } catch (error62) {
26699
27836
  if (error62?.code === "ENOENT") return [];
26700
27837
  throw error62;
@@ -26705,7 +27842,7 @@ function readDir(rootPath, dirPath) {
26705
27842
  let isDir = e.isDirectory();
26706
27843
  if (symlink) {
26707
27844
  try {
26708
- isDir = fs.statSync(path2.join(abs, e.name)).isDirectory();
27845
+ isDir = fs3.statSync(path4.join(abs, e.name)).isDirectory();
26709
27846
  } catch {
26710
27847
  return null;
26711
27848
  }
@@ -26725,23 +27862,23 @@ function readDir(rootPath, dirPath) {
26725
27862
  }
26726
27863
  function readFile3(rootPath, filePath, force) {
26727
27864
  const abs = assertWithinRoot(rootPath, filePath);
26728
- const stat2 = fs.statSync(abs);
27865
+ const stat2 = fs3.statSync(abs);
26729
27866
  if (stat2.size > FORCE_MAX_FILE_SIZE) {
26730
27867
  return { content: null, tooLarge: true, sizeBytes: stat2.size };
26731
27868
  }
26732
27869
  if (!force && stat2.size > MAX_FILE_SIZE) {
26733
27870
  return { content: null, tooLarge: true, sizeBytes: stat2.size };
26734
27871
  }
26735
- return { content: fs.readFileSync(abs, "utf-8") };
27872
+ return { content: fs3.readFileSync(abs, "utf-8") };
26736
27873
  }
26737
27874
  function listAllFiles(rootPath) {
26738
- const root = path2.resolve(rootPath);
27875
+ const root = path4.resolve(rootPath);
26739
27876
  const ig = getIgnoreFilter(rootPath);
26740
27877
  const results = [];
26741
27878
  function walk(dir, prefix) {
26742
27879
  let entries;
26743
27880
  try {
26744
- entries = fs.readdirSync(dir, { withFileTypes: true });
27881
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
26745
27882
  } catch {
26746
27883
  return;
26747
27884
  }
@@ -26750,7 +27887,7 @@ function listAllFiles(rootPath) {
26750
27887
  if (ALWAYS_IGNORED.has(e.name)) continue;
26751
27888
  if (ig.ignores(e.isDirectory() ? relPath + "/" : relPath)) continue;
26752
27889
  if (e.isDirectory()) {
26753
- walk(path2.join(dir, e.name), relPath);
27890
+ walk(path4.join(dir, e.name), relPath);
26754
27891
  } else {
26755
27892
  results.push(relPath);
26756
27893
  }
@@ -26761,65 +27898,65 @@ function listAllFiles(rootPath) {
26761
27898
  }
26762
27899
  function writeFile3(rootPath, filePath, content) {
26763
27900
  const abs = assertWithinRoot(rootPath, filePath);
26764
- fs.mkdirSync(path2.dirname(abs), { recursive: true });
26765
- fs.writeFileSync(abs, content, "utf-8");
27901
+ fs3.mkdirSync(path4.dirname(abs), { recursive: true });
27902
+ fs3.writeFileSync(abs, content, "utf-8");
26766
27903
  }
26767
27904
  function createFile(rootPath, filePath) {
26768
27905
  const abs = assertWithinRoot(rootPath, filePath);
26769
- if (fs.existsSync(abs)) throw new Error("File already exists");
26770
- fs.mkdirSync(path2.dirname(abs), { recursive: true });
26771
- fs.writeFileSync(abs, "", "utf-8");
27906
+ if (fs3.existsSync(abs)) throw new Error("File already exists");
27907
+ fs3.mkdirSync(path4.dirname(abs), { recursive: true });
27908
+ fs3.writeFileSync(abs, "", "utf-8");
26772
27909
  }
26773
27910
  function createDir(rootPath, dirPath) {
26774
27911
  const abs = assertWithinRoot(rootPath, dirPath);
26775
- fs.mkdirSync(abs, { recursive: true });
27912
+ fs3.mkdirSync(abs, { recursive: true });
26776
27913
  }
26777
27914
  function renamePath(rootPath, oldPath, newPath) {
26778
27915
  const absOld = assertWithinRoot(rootPath, oldPath);
26779
27916
  const absNew = assertWithinRoot(rootPath, newPath);
26780
- if (fs.existsSync(absNew)) throw new Error("Target already exists");
26781
- fs.renameSync(absOld, absNew);
27917
+ if (fs3.existsSync(absNew)) throw new Error("Target already exists");
27918
+ fs3.renameSync(absOld, absNew);
26782
27919
  }
26783
27920
  function deletePath(rootPath, targetPath) {
26784
27921
  const abs = assertWithinRoot(rootPath, targetPath);
26785
- fs.rmSync(abs, { recursive: true });
27922
+ fs3.rmSync(abs, { recursive: true });
26786
27923
  }
26787
27924
  function copyIn(rootPath, absoluteSrc, targetDir) {
26788
- const srcResolved = path2.resolve(absoluteSrc);
26789
- if (!fs.existsSync(srcResolved) || !fs.statSync(srcResolved).isFile()) {
27925
+ const srcResolved = path4.resolve(absoluteSrc);
27926
+ if (!fs3.existsSync(srcResolved) || !fs3.statSync(srcResolved).isFile()) {
26790
27927
  throw new Error("Source is not a file");
26791
27928
  }
26792
- const ext = path2.extname(srcResolved);
26793
- const stem = path2.basename(srcResolved, ext);
26794
- const baseName = path2.basename(srcResolved);
27929
+ const ext = path4.extname(srcResolved);
27930
+ const stem = path4.basename(srcResolved, ext);
27931
+ const baseName = path4.basename(srcResolved);
26795
27932
  const dirRel = (targetDir ?? "").trim();
26796
27933
  if (dirRel) assertWithinRoot(rootPath, dirRel);
26797
27934
  let relPath = dirRel ? `${dirRel}/${baseName}` : baseName;
26798
27935
  let dest = assertWithinRoot(rootPath, relPath);
26799
27936
  let i = 1;
26800
- while (fs.existsSync(dest)) {
27937
+ while (fs3.existsSync(dest)) {
26801
27938
  const candidate = `${stem} (${i})${ext}`;
26802
27939
  relPath = dirRel ? `${dirRel}/${candidate}` : candidate;
26803
27940
  dest = assertWithinRoot(rootPath, relPath);
26804
27941
  i++;
26805
27942
  }
26806
- fs.mkdirSync(path2.dirname(dest), { recursive: true });
26807
- fs.copyFileSync(srcResolved, dest);
27943
+ fs3.mkdirSync(path4.dirname(dest), { recursive: true });
27944
+ fs3.copyFileSync(srcResolved, dest);
26808
27945
  return relPath;
26809
27946
  }
26810
27947
  function copy(rootPath, srcPath, destPath) {
26811
27948
  const absSrc = assertWithinRoot(rootPath, srcPath);
26812
27949
  const absDest = assertWithinRoot(rootPath, destPath);
26813
- fs.cpSync(absSrc, absDest, { recursive: true });
27950
+ fs3.cpSync(absSrc, absDest, { recursive: true });
26814
27951
  }
26815
27952
  function gitStatus(rootPath) {
26816
- const root = path2.resolve(rootPath);
26817
- if (!fs.existsSync(path2.join(root, ".git"))) {
27953
+ const root = path4.resolve(rootPath);
27954
+ if (!fs3.existsSync(path4.join(root, ".git"))) {
26818
27955
  return Promise.resolve({ files: {}, isGitRepo: false });
26819
27956
  }
26820
27957
  return new Promise((resolve7) => {
26821
27958
  const chunks = [];
26822
- const proc = (0, import_node_child_process.spawn)("git", ["status", "--porcelain"], { cwd: root });
27959
+ const proc = (0, import_node_child_process3.spawn)("git", ["status", "--porcelain"], { cwd: root });
26823
27960
  proc.stdout.on("data", (chunk) => chunks.push(chunk));
26824
27961
  proc.on("error", () => resolve7({ files: {}, isGitRepo: false }));
26825
27962
  proc.on("close", (code) => {
@@ -26857,7 +27994,7 @@ function gitStatus(rootPath) {
26857
27994
  }
26858
27995
  function searchFiles(rootPath, query, options) {
26859
27996
  if (!query) return [];
26860
- const root = path2.resolve(rootPath);
27997
+ const root = path4.resolve(rootPath);
26861
27998
  const ig = getIgnoreFilter(rootPath);
26862
27999
  const matchCase = options?.matchCase ?? false;
26863
28000
  const useRegex = options?.regex ?? false;
@@ -26875,7 +28012,7 @@ function searchFiles(rootPath, query, options) {
26875
28012
  if (totalMatches >= maxResults) return;
26876
28013
  let entries;
26877
28014
  try {
26878
- entries = fs.readdirSync(dir, { withFileTypes: true });
28015
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
26879
28016
  } catch {
26880
28017
  return;
26881
28018
  }
@@ -26885,7 +28022,7 @@ function searchFiles(rootPath, query, options) {
26885
28022
  if (ALWAYS_IGNORED.has(e.name)) continue;
26886
28023
  if (ig.ignores(e.isDirectory() ? relPath + "/" : relPath)) continue;
26887
28024
  if (e.isDirectory()) {
26888
- walk(path2.join(dir, e.name), relPath);
28025
+ walk(path4.join(dir, e.name), relPath);
26889
28026
  } else {
26890
28027
  searchFile(root, relPath, pattern, results);
26891
28028
  }
@@ -26893,17 +28030,17 @@ function searchFiles(rootPath, query, options) {
26893
28030
  }
26894
28031
  function searchFile(rootDir, relPath, re, out) {
26895
28032
  if (totalMatches >= maxResults) return;
26896
- const abs = path2.join(rootDir, relPath);
28033
+ const abs = path4.join(rootDir, relPath);
26897
28034
  let stat2;
26898
28035
  try {
26899
- stat2 = fs.statSync(abs);
28036
+ stat2 = fs3.statSync(abs);
26900
28037
  } catch {
26901
28038
  return;
26902
28039
  }
26903
28040
  if (stat2.size > MAX_FILE_SIZE) return;
26904
28041
  let content;
26905
28042
  try {
26906
- content = fs.readFileSync(abs, "utf-8");
28043
+ content = fs3.readFileSync(abs, "utf-8");
26907
28044
  } catch {
26908
28045
  return;
26909
28046
  }
@@ -26928,11 +28065,11 @@ function searchFiles(rootPath, query, options) {
26928
28065
  }
26929
28066
 
26930
28067
  // ../../domains/file-editor/src/server/watcher.ts
26931
- var fs2 = __toESM(require("node:fs"), 1);
26932
- var path3 = __toESM(require("node:path"), 1);
28068
+ var fs4 = __toESM(require("node:fs"), 1);
28069
+ var path5 = __toESM(require("node:path"), 1);
26933
28070
  var watchers = /* @__PURE__ */ new Map();
26934
28071
  function subscribeFileWatcher(rootPath, listener) {
26935
- const root = path3.resolve(rootPath);
28072
+ const root = path5.resolve(rootPath);
26936
28073
  const existing = watchers.get(root);
26937
28074
  if (existing) {
26938
28075
  existing.listeners.add(listener);
@@ -26941,11 +28078,11 @@ function subscribeFileWatcher(rootPath, listener) {
26941
28078
  const debounceMap = /* @__PURE__ */ new Map();
26942
28079
  const listeners = /* @__PURE__ */ new Set([listener]);
26943
28080
  try {
26944
- const watcher = fs2.watch(root, { recursive: true }, (_eventType, filename) => {
28081
+ const watcher = fs4.watch(root, { recursive: true }, (_eventType, filename) => {
26945
28082
  if (!filename) return;
26946
28083
  const relPath = filename.replace(/\\/g, "/");
26947
28084
  if (ignoredSafely(root, relPath)) return;
26948
- if (path3.basename(relPath) === ".gitignore") {
28085
+ if (path5.basename(relPath) === ".gitignore") {
26949
28086
  invalidateIgnoreCache(root);
26950
28087
  }
26951
28088
  const prev = debounceMap.get(relPath);
@@ -26954,8 +28091,8 @@ function subscribeFileWatcher(rootPath, listener) {
26954
28091
  relPath,
26955
28092
  setTimeout(() => {
26956
28093
  debounceMap.delete(relPath);
26957
- const abs = path3.join(root, relPath);
26958
- const exists = fs2.existsSync(abs);
28094
+ const abs = path5.join(root, relPath);
28095
+ const exists = fs4.existsSync(abs);
26959
28096
  const event = {
26960
28097
  type: exists ? "changed" : "deleted",
26961
28098
  root,
@@ -26994,10 +28131,10 @@ function unsubscribe(root, listener) {
26994
28131
  }
26995
28132
 
26996
28133
  // ../../domains/file-editor/src/server/repo-discovery.ts
26997
- var import_node_child_process2 = require("node:child_process");
26998
- var import_node_fs3 = require("node:fs");
26999
- var import_promises4 = require("node:fs/promises");
27000
- var import_node_path5 = __toESM(require("node:path"), 1);
28134
+ var import_node_child_process4 = require("node:child_process");
28135
+ var import_node_fs5 = require("node:fs");
28136
+ var import_promises5 = require("node:fs/promises");
28137
+ var import_node_path8 = __toESM(require("node:path"), 1);
27001
28138
  var DEFAULT_REPO_SCAN_DEPTH = 3;
27002
28139
  var SKIP_DIRS = /* @__PURE__ */ new Set([
27003
28140
  "node_modules",
@@ -27022,7 +28159,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
27022
28159
  ]);
27023
28160
  function git(args, cwd) {
27024
28161
  return new Promise((resolve7, reject) => {
27025
- (0, import_node_child_process2.execFile)("git", args, { cwd, maxBuffer: 8 * 1024 * 1024 }, (err, stdout2) => {
28162
+ (0, import_node_child_process4.execFile)("git", args, { cwd, maxBuffer: 8 * 1024 * 1024 }, (err, stdout2) => {
27026
28163
  if (err) reject(err);
27027
28164
  else resolve7(stdout2);
27028
28165
  });
@@ -27042,17 +28179,17 @@ async function walkForGitRoots(root, maxDepth) {
27042
28179
  if (depth > maxDepth) return;
27043
28180
  let entries;
27044
28181
  try {
27045
- entries = await (0, import_promises4.readdir)(dir);
28182
+ entries = await (0, import_promises5.readdir)(dir);
27046
28183
  } catch {
27047
28184
  return;
27048
28185
  }
27049
28186
  await Promise.all(
27050
28187
  entries.map(async (entry) => {
27051
28188
  if (SKIP_DIRS.has(entry) || entry.startsWith(".")) return;
27052
- const full = import_node_path5.default.join(dir, entry);
28189
+ const full = import_node_path8.default.join(dir, entry);
27053
28190
  let s;
27054
28191
  try {
27055
- s = await (0, import_promises4.lstat)(full);
28192
+ s = await (0, import_promises5.lstat)(full);
27056
28193
  } catch {
27057
28194
  return;
27058
28195
  }
@@ -27070,7 +28207,7 @@ async function walkForGitRoots(root, maxDepth) {
27070
28207
  return found;
27071
28208
  }
27072
28209
  async function listSubmodules(repoPath) {
27073
- if (!(0, import_node_fs3.existsSync)(import_node_path5.default.join(repoPath, ".gitmodules"))) return [];
28210
+ if (!(0, import_node_fs5.existsSync)(import_node_path8.default.join(repoPath, ".gitmodules"))) return [];
27074
28211
  let out;
27075
28212
  try {
27076
28213
  out = await git(["submodule", "status", "--recursive"], repoPath);
@@ -27081,13 +28218,13 @@ async function listSubmodules(repoPath) {
27081
28218
  for (const line of out.split("\n")) {
27082
28219
  if (!line.trim()) continue;
27083
28220
  const m = line.match(/^[\s\-+U][0-9a-f]+\s+(\S+)/);
27084
- if (m) subs.push(import_node_path5.default.join(repoPath, m[1]));
28221
+ if (m) subs.push(import_node_path8.default.join(repoPath, m[1]));
27085
28222
  }
27086
28223
  return subs;
27087
28224
  }
27088
28225
  function nameFor(rootPath, repoPath) {
27089
- if (repoPath === rootPath) return import_node_path5.default.basename(rootPath);
27090
- return import_node_path5.default.relative(rootPath, repoPath) || import_node_path5.default.basename(repoPath);
28226
+ if (repoPath === rootPath) return import_node_path8.default.basename(rootPath);
28227
+ return import_node_path8.default.relative(rootPath, repoPath) || import_node_path8.default.basename(repoPath);
27091
28228
  }
27092
28229
  async function discoverRepos(rootPath, maxDepth = DEFAULT_REPO_SCAN_DEPTH) {
27093
28230
  const rootIsGit = await isGitRepoDir(rootPath);
@@ -27100,7 +28237,7 @@ async function discoverRepos(rootPath, maxDepth = DEFAULT_REPO_SCAN_DEPTH) {
27100
28237
  name: nameFor(rootPath, repoPath),
27101
28238
  kind: repoPath === rootPath ? "project-root" : "child-repo",
27102
28239
  parentPath: null,
27103
- hasGitmodules: (0, import_node_fs3.existsSync)(import_node_path5.default.join(repoPath, ".gitmodules"))
28240
+ hasGitmodules: (0, import_node_fs5.existsSync)(import_node_path8.default.join(repoPath, ".gitmodules"))
27104
28241
  });
27105
28242
  for (const subPath of await listSubmodules(repoPath)) {
27106
28243
  entries.push({
@@ -27108,7 +28245,7 @@ async function discoverRepos(rootPath, maxDepth = DEFAULT_REPO_SCAN_DEPTH) {
27108
28245
  name: nameFor(rootPath, subPath),
27109
28246
  kind: "submodule",
27110
28247
  parentPath: repoPath,
27111
- hasGitmodules: (0, import_node_fs3.existsSync)(import_node_path5.default.join(subPath, ".gitmodules"))
28248
+ hasGitmodules: (0, import_node_fs5.existsSync)(import_node_path8.default.join(subPath, ".gitmodules"))
27112
28249
  });
27113
28250
  }
27114
28251
  })
@@ -27121,92 +28258,28 @@ async function discoverRepos(rootPath, maxDepth = DEFAULT_REPO_SCAN_DEPTH) {
27121
28258
  if (a.parentPath !== null && b.parentPath === null) return 1;
27122
28259
  return a.path.localeCompare(b.path);
27123
28260
  });
27124
- return entries;
27125
- }
27126
-
27127
- // ../../shared/platform/src/hub-switches.ts
27128
- var HUB_SWITCH_ENV = {
27129
- requireLogin: "SLAYZONE_HUB_REQUIRE_LOGIN",
27130
- enablePassword: "SLAYZONE_HUB_ENABLE_PASSWORD",
27131
- enableSso: "SLAYZONE_HUB_ENABLE_SSO",
27132
- enableProcesses: "SLAYZONE_HUB_ENABLE_PROCESSES",
27133
- enableIntegrations: "SLAYZONE_HUB_ENABLE_INTEGRATIONS"
27134
- };
27135
- var HUB_SSO_ENV = {
27136
- tenantId: "SLAYZONE_HUB_SSO_TENANT_ID",
27137
- clientId: "SLAYZONE_HUB_SSO_CLIENT_ID",
27138
- /** PEM certificate registered on the Entra app (its SHA-256 thumbprint goes in each assertion). Env only. */
27139
- certPath: "SLAYZONE_HUB_SSO_CERT_PATH",
27140
- /** PEM private key of that certificate (RSA, unencrypted). Env only. */
27141
- keyPath: "SLAYZONE_HUB_SSO_KEY_PATH",
27142
- /** `0|1`, default `1`: refuse a sign-in whose ID token's `amr` does not contain `mfa`. */
27143
- requireMfa: "SLAYZONE_HUB_SSO_REQUIRE_MFA"
27144
- };
27145
- var RETIRED_HUB_SSO_CLIENT_SECRET_ENV = "SLAYZONE_HUB_SSO_CLIENT_SECRET";
27146
- var HUB_ADDRESS_ENV = {
27147
- /** Where the hub LISTENS. Renamed from `SLAYZONE_HUB_ADDRESS`, which now only
27148
- * means "the hub to connect to" on a computer / `slay`. */
27149
- listen: "SLAYZONE_HUB_LISTEN_ADDRESS",
27150
- /** How OTHER machines reach the hub (`host[:port]`, no scheme). */
27151
- public: "SLAYZONE_HUB_PUBLIC_ADDRESS",
27152
- /** Signing key of hub-auth. */
27153
- authSecret: "SLAYZONE_HUB_AUTH_SECRET"
27154
- };
27155
- var LEGACY_HUB_LISTEN_ENV = "SLAYZONE_HUB_ADDRESS";
27156
- var HUB_SESSION_RETENTION_DAYS_ENV = "SLAYZONE_HUB_SESSION_RETENTION_DAYS";
27157
- var HUB_ALLOWED_WEB_ORIGINS_ENV = "SLAYZONE_HUB_ALLOWED_WEB_ORIGINS";
27158
- var HUB_TRUSTED_PROXIES_ENV = "SLAYZONE_HUB_TRUSTED_PROXIES";
27159
- var HUB_CONVEX_ADDRESS_ENV = "SLAYZONE_HUB_CONVEX_ADDRESS";
27160
- var HUB_PROCESS_ENV = [
27161
- ...Object.values(HUB_SWITCH_ENV),
27162
- ...Object.values(HUB_SSO_ENV),
27163
- RETIRED_HUB_SSO_CLIENT_SECRET_ENV,
27164
- ...Object.values(HUB_ADDRESS_ENV),
27165
- LEGACY_HUB_LISTEN_ENV,
27166
- HUB_ALLOWED_WEB_ORIGINS_ENV,
27167
- HUB_TRUSTED_PROXIES_ENV,
27168
- HUB_SESSION_RETENTION_DAYS_ENV,
27169
- HUB_CONVEX_ADDRESS_ENV
27170
- ];
27171
-
27172
- // ../../shared/platform/src/security-log.ts
27173
- var import_node_async_hooks = require("node:async_hooks");
27174
- var LINE_SEPARATOR = String.fromCharCode(8232);
27175
- var PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
27176
- var contextStore = new import_node_async_hooks.AsyncLocalStorage();
27177
-
27178
- // ../../shared/platform/src/hub-switch-refusal.ts
27179
- var MESSAGES = {
27180
- HUB_PROCESSES_DISABLED: `This hub does not run processes, automation commands, terminals or agents itself (${HUB_SWITCH_ENV.enableProcesses}=0).`,
27181
- HUB_INTEGRATIONS_DISABLED: `Integrations are switched off on this hub (${HUB_SWITCH_ENV.enableIntegrations}=0).`,
27182
- NO_COMPUTER_CONNECTED: `Connect a computer: this hub does not read project files or run git, gh or other commands itself (${HUB_SWITCH_ENV.enableProcesses}=0).`,
27183
- HUB_PASSWORD_DISABLED: `Password sign-in is switched off on this hub (${HUB_SWITCH_ENV.enablePassword}=0).`,
27184
- HUB_CONVEX_NOT_CONFIGURED: `GitHub sign-in is off on this hub: no Convex deployment is configured (${HUB_CONVEX_ADDRESS_ENV}).`,
27185
- HUB_CONVEX_MISMATCH: `This hub starts GitHub sign-in only against its own Convex deployment (${HUB_CONVEX_ADDRESS_ENV}); the request named another.`
27186
- };
27187
- var VARIABLES = {
27188
- HUB_PROCESSES_DISABLED: HUB_SWITCH_ENV.enableProcesses,
27189
- HUB_INTEGRATIONS_DISABLED: HUB_SWITCH_ENV.enableIntegrations,
27190
- NO_COMPUTER_CONNECTED: HUB_SWITCH_ENV.enableProcesses,
27191
- HUB_PASSWORD_DISABLED: HUB_SWITCH_ENV.enablePassword,
27192
- HUB_CONVEX_NOT_CONFIGURED: HUB_CONVEX_ADDRESS_ENV,
27193
- HUB_CONVEX_MISMATCH: HUB_CONVEX_ADDRESS_ENV
27194
- };
28261
+ return entries;
28262
+ }
27195
28263
 
27196
28264
  // src/handlers/fs.ts
27197
28265
  var BROWSE_ALWAYS_HIDDEN = /* @__PURE__ */ new Set([".git", ".DS_Store", "node_modules"]);
27198
28266
  function createFsHandlers(ctx) {
27199
- const allow = (candidate) => assertPathAllowed(candidate, ctx.config.allowedRoots);
28267
+ const allow = (candidate, subtree = false) => assertPathAllowed(candidate, ctx.config.allowedRoots, { subtree });
28268
+ const allowIn = (rootPath, rel, subtree = false) => {
28269
+ const root = allow(rootPath);
28270
+ allow((0, import_node_path9.join)(root, rel), subtree);
28271
+ return root;
28272
+ };
27200
28273
  const watches = /* @__PURE__ */ new Map();
27201
28274
  function pathExists(rawParams) {
27202
- const { path: path8 } = fsPathExistsParamsSchema.parse(rawParams);
27203
- const resolved = allow(path8);
27204
- return { exists: (0, import_node_fs4.existsSync)(resolved) };
28275
+ const { path: path9 } = fsPathExistsParamsSchema.parse(rawParams);
28276
+ const resolved = allow(path9);
28277
+ return { exists: (0, import_node_fs6.existsSync)(resolved) };
27205
28278
  }
27206
28279
  async function removeDir(rawParams) {
27207
- const { path: path8 } = fsRemoveDirParamsSchema.parse(rawParams);
27208
- const resolved = allow(path8);
27209
- await (0, import_promises5.rm)(resolved, { recursive: true, force: true });
28280
+ const { path: path9 } = fsRemoveDirParamsSchema.parse(rawParams);
28281
+ const resolved = allow(path9, true);
28282
+ await (0, import_promises6.rm)(resolved, { recursive: true, force: true });
27210
28283
  ctx.log("fs removeDir", { path: resolved });
27211
28284
  return { ok: true };
27212
28285
  }
@@ -27215,32 +28288,32 @@ function createFsHandlers(ctx) {
27215
28288
  const resolvedRoots = [];
27216
28289
  for (const root of ctx.config.allowedRoots) {
27217
28290
  try {
27218
- resolvedRoots.push(import_node_fs4.realpathSync.native((0, import_node_path6.resolve)(root)));
28291
+ resolvedRoots.push(import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(root)));
27219
28292
  } catch {
27220
28293
  }
27221
28294
  }
27222
28295
  let home = null;
27223
28296
  try {
27224
- home = import_node_fs4.realpathSync.native((0, import_node_os4.homedir)());
28297
+ home = import_node_fs6.realpathSync.native((0, import_node_os5.homedir)());
27225
28298
  } catch {
27226
28299
  home = null;
27227
28300
  }
27228
- return { roots: resolvedRoots, home, platform: process.platform, sep: import_node_path6.sep };
28301
+ return { roots: resolvedRoots, home, platform: process.platform, sep: import_node_path9.sep };
27229
28302
  }
27230
28303
  async function listDir(rawParams) {
27231
28304
  const params = fsListDirParamsSchema.parse(rawParams);
27232
28305
  const abs = allow(params.path);
27233
- const dirents = await (0, import_promises5.readdir)(abs, { withFileTypes: true });
28306
+ const dirents = await (0, import_promises6.readdir)(abs, { withFileTypes: true });
27234
28307
  const entries = [];
27235
28308
  for (const e of dirents) {
27236
28309
  if (BROWSE_ALWAYS_HIDDEN.has(e.name)) continue;
27237
28310
  if (!params.includeHidden && e.name.startsWith(".")) continue;
27238
- const full = (0, import_node_path6.join)(abs, e.name);
28311
+ const full = (0, import_node_path9.join)(abs, e.name);
27239
28312
  const symlink = e.isSymbolicLink();
27240
28313
  let isDir = e.isDirectory();
27241
28314
  if (symlink) {
27242
28315
  try {
27243
- isDir = (await (0, import_promises5.stat)(full)).isDirectory();
28316
+ isDir = (await (0, import_promises6.stat)(full)).isDirectory();
27244
28317
  } catch {
27245
28318
  continue;
27246
28319
  }
@@ -27251,7 +28324,7 @@ function createFsHandlers(ctx) {
27251
28324
  path: full,
27252
28325
  type: isDir ? "directory" : "file",
27253
28326
  ...symlink && { isSymlink: true },
27254
- ...isDir && (0, import_node_fs4.existsSync)((0, import_node_path6.join)(full, ".git")) && { isGitRepo: true }
28327
+ ...isDir && (0, import_node_fs6.existsSync)((0, import_node_path9.join)(full, ".git")) && { isGitRepo: true }
27255
28328
  });
27256
28329
  }
27257
28330
  entries.sort((a, b) => {
@@ -27260,19 +28333,19 @@ function createFsHandlers(ctx) {
27260
28333
  });
27261
28334
  const isRoot = ctx.config.allowedRoots.some((r) => {
27262
28335
  try {
27263
- return import_node_fs4.realpathSync.native((0, import_node_path6.resolve)(r)) === abs;
28336
+ return import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(r)) === abs;
27264
28337
  } catch {
27265
28338
  return false;
27266
28339
  }
27267
28340
  });
27268
- return { path: abs, parent: isRoot ? null : (0, import_node_path6.dirname)(abs), entries };
28341
+ return { path: abs, parent: isRoot ? null : (0, import_node_path9.dirname)(abs), entries };
27269
28342
  }
27270
28343
  async function mkdirHandler(rawParams) {
27271
- const { path: path8 } = fsMkdirParamsSchema.parse(rawParams);
27272
- const abs = allow(path8);
27273
- await (0, import_promises5.mkdir)(abs, { recursive: true });
28344
+ const { path: path9 } = fsMkdirParamsSchema.parse(rawParams);
28345
+ const abs = allow(path9);
28346
+ await (0, import_promises6.mkdir)(abs, { recursive: true });
27274
28347
  ctx.log("fs mkdir", { path: abs });
27275
- return { path: import_node_fs4.realpathSync.native(abs) };
28348
+ return { path: import_node_fs6.realpathSync.native(abs) };
27276
28349
  }
27277
28350
  function readDirHandler(rawParams) {
27278
28351
  const { rootPath, dirPath } = fsReadDirParamsSchema.parse(rawParams);
@@ -27280,312 +28353,166 @@ function createFsHandlers(ctx) {
27280
28353
  }
27281
28354
  function readFileHandler(rawParams) {
27282
28355
  const { rootPath, filePath, force } = fsReadFileParamsSchema.parse(rawParams);
27283
- return readFile3(allow(rootPath), filePath, force);
28356
+ return readFile3(allowIn(rootPath, filePath), filePath, force);
27284
28357
  }
27285
28358
  function writeFileHandler(rawParams) {
27286
28359
  const { rootPath, filePath, content } = fsWriteFileParamsSchema.parse(rawParams);
27287
- writeFile3(allow(rootPath), filePath, content);
28360
+ writeFile3(allowIn(rootPath, filePath), filePath, content);
27288
28361
  return { ok: true };
27289
28362
  }
27290
28363
  function createFileHandler(rawParams) {
27291
28364
  const { rootPath, filePath } = fsCreateFileParamsSchema.parse(rawParams);
27292
- createFile(allow(rootPath), filePath);
28365
+ createFile(allowIn(rootPath, filePath), filePath);
27293
28366
  return { ok: true };
27294
28367
  }
27295
28368
  function createDirHandler(rawParams) {
27296
28369
  const { rootPath, dirPath } = fsCreateDirParamsSchema.parse(rawParams);
27297
- createDir(allow(rootPath), dirPath);
28370
+ createDir(allowIn(rootPath, dirPath), dirPath);
27298
28371
  return { ok: true };
27299
28372
  }
27300
28373
  function renameHandler(rawParams) {
27301
28374
  const { rootPath, oldPath, newPath } = fsRenameParamsSchema.parse(rawParams);
27302
- renamePath(allow(rootPath), oldPath, newPath);
28375
+ allowIn(rootPath, newPath, true);
28376
+ renamePath(allowIn(rootPath, oldPath, true), oldPath, newPath);
27303
28377
  return { ok: true };
27304
28378
  }
27305
28379
  function deleteHandler(rawParams) {
27306
28380
  const { rootPath, targetPath } = fsDeleteParamsSchema.parse(rawParams);
27307
- deletePath(allow(rootPath), targetPath);
28381
+ deletePath(allowIn(rootPath, targetPath, true), targetPath);
27308
28382
  return { ok: true };
27309
28383
  }
27310
28384
  function copyInHandler(rawParams) {
27311
28385
  const { rootPath, absoluteSrc, targetDir } = fsCopyInParamsSchema.parse(rawParams);
27312
28386
  const src = allow(absoluteSrc);
27313
- return { path: copyIn(allow(rootPath), src, targetDir) };
28387
+ return { path: copyIn(allowIn(rootPath, targetDir ?? ""), src, targetDir) };
27314
28388
  }
27315
28389
  function copyHandler(rawParams) {
27316
28390
  const { rootPath, srcPath, destPath } = fsCopyParamsSchema.parse(rawParams);
27317
- copy(allow(rootPath), srcPath, destPath);
28391
+ allowIn(rootPath, srcPath);
28392
+ copy(allowIn(rootPath, destPath, true), srcPath, destPath);
27318
28393
  return { ok: true };
27319
28394
  }
27320
28395
  async function gitStatusHandler(rawParams) {
27321
28396
  const { rootPath } = fsGitStatusParamsSchema.parse(rawParams);
27322
28397
  return gitStatus(allow(rootPath));
27323
28398
  }
27324
- function searchFilesHandler(rawParams) {
27325
- const { rootPath, query, options } = fsSearchFilesParamsSchema.parse(rawParams);
27326
- const maxResults = Math.min(options?.maxResults ?? FS_MAX_SEARCH_RESULTS, FS_MAX_SEARCH_RESULTS);
27327
- const results = searchFiles(allow(rootPath), query, {
27328
- ...options,
27329
- maxResults
27330
- });
27331
- const total = results.reduce((n, r) => n + r.matches.length, 0);
27332
- return { results, truncated: total >= maxResults };
27333
- }
27334
- function listAllFilesHandler(rawParams) {
27335
- const { rootPath } = fsListAllFilesParamsSchema.parse(rawParams);
27336
- const all = listAllFiles(allow(rootPath));
27337
- return {
27338
- files: all.slice(0, FS_MAX_LIST_ALL_FILES),
27339
- truncated: all.length > FS_MAX_LIST_ALL_FILES
27340
- };
27341
- }
27342
- async function discoverReposHandler(rawParams) {
27343
- const { rootPath, maxDepth } = fsDiscoverReposParamsSchema.parse(rawParams);
27344
- return { repos: await discoverRepos(allow(rootPath), maxDepth) };
27345
- }
27346
- function watchStart(rawParams) {
27347
- const { watchId, rootPath } = fsWatchStartParamsSchema.parse(rawParams);
27348
- watches.get(watchId)?.();
27349
- const abs = allow(rootPath);
27350
- const dispose = subscribeFileWatcher(abs, (e) => {
27351
- const params = { watchId, type: e.type, relPath: e.relPath };
27352
- ctx.dialer.notify(ComputerNotificationMethods.fsChange, params);
27353
- });
27354
- watches.set(watchId, dispose);
27355
- return { ok: true };
27356
- }
27357
- function watchStop(rawParams) {
27358
- const { watchId } = fsWatchStopParamsSchema.parse(rawParams);
27359
- watches.get(watchId)?.();
27360
- watches.delete(watchId);
27361
- return { ok: true };
27362
- }
27363
- return {
27364
- [HubToComputerMethods.fsPathExists]: pathExists,
27365
- [HubToComputerMethods.fsRemoveDir]: removeDir,
27366
- [HubToComputerMethods.fsListRoots]: listRoots,
27367
- [HubToComputerMethods.fsListDir]: listDir,
27368
- [HubToComputerMethods.fsMkdir]: mkdirHandler,
27369
- [HubToComputerMethods.fsReadDir]: readDirHandler,
27370
- [HubToComputerMethods.fsReadFile]: readFileHandler,
27371
- [HubToComputerMethods.fsWriteFile]: writeFileHandler,
27372
- [HubToComputerMethods.fsCreateFile]: createFileHandler,
27373
- [HubToComputerMethods.fsCreateDir]: createDirHandler,
27374
- [HubToComputerMethods.fsRename]: renameHandler,
27375
- [HubToComputerMethods.fsDelete]: deleteHandler,
27376
- [HubToComputerMethods.fsCopyIn]: copyInHandler,
27377
- [HubToComputerMethods.fsCopy]: copyHandler,
27378
- [HubToComputerMethods.fsGitStatus]: gitStatusHandler,
27379
- [HubToComputerMethods.fsSearchFiles]: searchFilesHandler,
27380
- [HubToComputerMethods.fsListAllFiles]: listAllFilesHandler,
27381
- [HubToComputerMethods.fsDiscoverRepos]: discoverReposHandler,
27382
- [HubToComputerMethods.fsWatchStart]: watchStart,
27383
- [HubToComputerMethods.fsWatchStop]: watchStop
27384
- };
27385
- }
27386
-
27387
- // src/handlers/gh.ts
27388
- var import_node_child_process3 = require("node:child_process");
27389
- var DEFAULT_TIMEOUT_MS = 3e4;
27390
- var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
27391
- function createGhHandlers(ctx) {
27392
- async function ghExec(rawParams) {
27393
- const params = ghExecParamsSchema.parse(rawParams);
27394
- const cwd = assertPathAllowed(params.cwd, ctx.config.allowedRoots);
27395
- return new Promise((resolve7) => {
27396
- const child = (0, import_node_child_process3.execFile)(
27397
- "gh",
27398
- params.args,
27399
- {
27400
- cwd,
27401
- timeout: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
27402
- maxBuffer: MAX_OUTPUT_BYTES,
27403
- // No env overlay: gh must see this machine's own `GH_*`/`GITHUB_TOKEN`
27404
- // and credential helpers. Injecting anything here would be SlayZone
27405
- // taking custody of credentials it deliberately does not hold.
27406
- env: process.env
27407
- },
27408
- (err, stdout2, stderr) => {
27409
- const status = err && typeof err.code === "number" ? err.code : err ? null : 0;
27410
- resolve7({ status, stdout: stdout2 ?? "", stderr: stderr ?? "" });
27411
- }
27412
- );
27413
- if (params.stdin !== void 0) {
27414
- child.stdin?.end(params.stdin);
27415
- }
27416
- });
27417
- }
27418
- return { [HubToComputerMethods.ghExec]: ghExec };
27419
- }
27420
-
27421
- // src/handlers/git.ts
27422
- var import_node_child_process5 = require("node:child_process");
27423
- var import_node_fs5 = require("node:fs");
27424
- var import_promises6 = require("node:fs/promises");
27425
- var import_node_path7 = require("node:path");
27426
-
27427
- // ../../shared/platform/src/env-manifest.ts
27428
- var ENV_MANIFEST = {
27429
- // --- secret: credentials/tokens, never in a terminal ---
27430
- SLAYZONE_HUB_TOKEN: "secret",
27431
- // HMAC signing key for ALL of hub-auth (better-auth's session/cookie signer AND
27432
- // the computer enroll/api-key credentials) — hence `HUB_AUTH`, not the former
27433
- // `HUB_COMPUTER_TRANSPORT_SECRET`, which named just one of its consumers. Rule 2
27434
- // (name by what the value IS). `_SECRET` = a key you HOLD and sign with, vs
27435
- // `_TOKEN` = a bearer you PRESENT (SLAYZONE_HUB_TOKEN above).
27436
- SLAYZONE_HUB_AUTH_SECRET: "secret",
27437
- // Hub-scoped by VALUE: the hub mints it, and it embeds the hub's url and a
27438
- // secret verified against the hub's `join_tokens`
27439
- // row. `mintJoinToken` binds it to NO computer (computer_id stays NULL until
27440
- // redemption), so any computer can redeem any unused token — there is no computer in
27441
- // the value to name. Rule 2 (name by what the value IS, not who reads it).
27442
- SLAYZONE_HUB_JOIN_TOKEN: "secret",
27443
- // Retired before it was ever read: Entra sign-in authenticates with a certificate
27444
- // (SLAYZONE_HUB_SSO_CERT_PATH / _KEY_PATH below), and boot refuses a hub that still
27445
- // sets this (B7). Still classified `secret` so a leftover value never reaches a
27446
- // terminal or a log.
27447
- SLAYZONE_HUB_SSO_CLIENT_SECRET: "secret",
27448
- // Per-boot bearer the desktop's `/cap` listener requires on every connection
27449
- // (desktop-bridge-server.ts `verifyClient`). Loopback is NOT trusted there —
27450
- // the threat this closes is another loopback process finding the port — so
27451
- // this is a real credential, `secret`-scoped like the two above it.
27452
- SLAYZONE_DESKTOP_BRIDGE_TOKEN: "secret",
27453
- // The pre-rename `SLAYZONE_RUNNER_JOIN_TOKEN` is deliberately UNLISTED: it is
27454
- // retired, not aliased. It never shipped (the published computer betas read
27455
- // `SLAYZONE_JOIN_TOKEN`), so no operator env carries it and there is nothing to
27456
- // keep working. Unlisted ⇒ the fail-closed default strips it, same as every
27457
- // other retired name in this rename sweep.
27458
- // --- infra: SlayZone wiring a terminal must not reinterpret ---
27459
- // The hub to CONNECT to, host[:port] (computer, `slay`); the scheme derives from
27460
- // the host (loopback → http/ws, else https/wss). A hub no longer reads this name:
27461
- // it LISTENS on SLAYZONE_HUB_LISTEN_ADDRESS, and a hub that still gets the old
27462
- // name refuses to boot. Stripping both here keeps a terminal's dial address from
27463
- // ever being mistaken for another process's listen address.
27464
- SLAYZONE_HUB_ADDRESS: "infra",
27465
- SLAYZONE_HUB_LISTEN_ADDRESS: "infra",
27466
- // hub-only; where the hub listens
27467
- SLAYZONE_HUB_PUBLIC_ADDRESS: "infra",
27468
- // hub-only; how other machines reach it, written into join tokens
27469
- // Hub-only; the front ends (IPs/CIDRs, comma-separated) whose X-Forwarded-For names
27470
- // the client (client-address.ts). Not `_ADDRESS`: these are networks, not an authority.
27471
- SLAYZONE_HUB_TRUSTED_PROXIES: "infra",
27472
- // Per-hub switches (0|1) replacing SLAYZONE_MODE. Infra: they describe THIS hub
27473
- // process, so a task terminal must not inherit them (a hub started from one would
27474
- // otherwise pick up its parent's switches by accident).
27475
- SLAYZONE_HUB_REQUIRE_LOGIN: "infra",
27476
- SLAYZONE_HUB_ENABLE_PASSWORD: "infra",
27477
- SLAYZONE_HUB_ENABLE_SSO: "infra",
27478
- SLAYZONE_HUB_ENABLE_PROCESSES: "infra",
27479
- SLAYZONE_HUB_ENABLE_INTEGRATIONS: "infra",
27480
- SLAYZONE_HUB_SSO_TENANT_ID: "infra",
27481
- SLAYZONE_HUB_SSO_CLIENT_ID: "infra",
27482
- // Paths to the Entra client certificate and its private key, and the MFA switch.
27483
- // Infra, not secret: a path is not the key (the file's own permissions protect it),
27484
- // but a task terminal must still never inherit a hub's SSO wiring.
27485
- SLAYZONE_HUB_SSO_CERT_PATH: "infra",
27486
- SLAYZONE_HUB_SSO_KEY_PATH: "infra",
27487
- SLAYZONE_HUB_SSO_REQUIRE_MFA: "infra",
27488
- // Operator-facing hub name (`slay hub ls`/`stop` address a hub by it). Infra,
27489
- // not global: it identifies THIS hub process, so inheriting it into a task
27490
- // terminal — which may target a different hub entirely — would mislabel.
27491
- SLAYZONE_HUB_NAME: "infra",
27492
- // Days THIS hub keeps session content (T-23, hub session-retention.ts); 0 = off, the
27493
- // default. Infra like the switches: a hub started from a task terminal must not
27494
- // inherit its parent's period and start deleting.
27495
- SLAYZONE_HUB_SESSION_RETENTION_DAYS: "infra",
27496
- // The Convex deployment (host[:port]) the hub starts GitHub sign-in against — never
27497
- // a client-named one (T-99). Hub process config, set by the desktop app for its hub.
27498
- SLAYZONE_HUB_CONVEX_ADDRESS: "infra",
27499
- SLAYZONE_DESKTOP_BRIDGE_ADDRESS: "infra",
27500
- // sidecar→desktop capability bridge + REST proxy
27501
- SLAYZONE_SUPERVISED: "infra",
27502
- // "started by the desktop app" flag
27503
- SLAYZONE_USER_DATA_DIR: "infra",
27504
- // Playwright Electron userData redirect
27505
- SLAYZONE_SIDECAR_HOT_RESTART: "infra",
27506
- SLAYZONE_BOOT_LOG_PATH: "infra",
27507
- SLAYZONE_DEBUG_BOOT: "infra",
27508
- SLAYZONE_REGISTER_DEV_PROTOCOL: "infra",
27509
- SLAYZONE_NONINTERACTIVE: "infra",
27510
- // Historical opt-IN, now a NO-OP: e2e runs computer-ON by default, so the boot
27511
- // gate no longer reads this. Kept manifested because specs still set it to
27512
- // document "this spec wants a computer" (an unmanifested var would be stripped
27513
- // by sanitizeSpawnEnv, fail-closed, and read as a silent typo).
27514
- SLAYZONE_E2E_ALLOW_COMPUTER: "infra",
27515
- // Opt-OUT, and the only lever that changes behavior now: set by the specs that
27516
- // must boot computer-less (remote-mode, sidecar-crash, and the "hub accepts
27517
- // computers with none spawned" contract test).
27518
- SLAYZONE_E2E_NO_COMPUTER: "infra",
27519
- SLAYZONE_E2E_INSTALL_HOOKS: "infra",
27520
- // Redirects the MACHINE-WIDE ~/.slayzone (hooks/, bin/) — the files shared by
27521
- // every SlayZone process on the box, which deliberately do NOT follow
27522
- // SLAYZONE_ROOT. Sandbox redirect for tests, same role as the per-provider
27523
- // overrides below. `infra`: a spawned terminal must never inherit it, or an
27524
- // agent would resolve a different notify.sh than the one wired into its hooks.
27525
- SLAYZONE_MACHINE_DIR: "infra",
27526
- // per-provider config/hook/plugin path overrides (test/sandbox redirects)
27527
- SLAYZONE_CLAUDE_SETTINGS_PATH: "infra",
27528
- SLAYZONE_GEMINI_SETTINGS_PATH: "infra",
27529
- SLAYZONE_CODEX_HOOKS_PATH: "infra",
27530
- SLAYZONE_ANTIGRAVITY_HOOKS_PATH: "infra",
27531
- SLAYZONE_OPENCODE_PLUGIN_PATH: "infra",
27532
- // --- identity: per-task/agent; overlay re-adds the correct value per spawn ---
27533
- SLAYZONE_TASK_ID: "identity",
27534
- SLAYZONE_PROJECT_ID: "identity",
27535
- // `SLAYZONE_SESSION_ID` (a warm agent's pre-task identity) is RETIRED and
27536
- // deliberately unlisted: warm agents boot with SLAYZONE_TASK_ID now.
27537
- SLAYZONE_AGENT_ID: "identity",
27538
- SLAYZONE_AGENT_HOOK_URL: "identity",
27539
- // Pairs with _HOOK_URL (where to post / who is posting). The pre-v4 name
27540
- // `SLAYZONE_HOOK_CONTEXT` is RETIRED and deliberately unlisted: unlisted =
27541
- // stripped (fail closed), and the old-name value an older release channel's app
27542
- // supplies rides its OWN per-spawn overlay (applied after sanitizeSpawnEnv), so
27543
- // notify.sh v4's fallback read still sees it. A manifest entry would only let a
27544
- // stale inherited value survive.
27545
- SLAYZONE_AGENT_HOOK_CONTEXT: "identity",
27546
- // --- global: same app-wide, safe to inherit verbatim ---
27547
- SLAYZONE_RELEASE_CHANNEL: "global",
27548
- // attribution-only (hook envelope channel)
27549
- // INSTALL IDENTITY — which SlayZone install this process tree belongs to, not
27550
- // which task/session. A child MUST resolve the SAME install as its parent: the
27551
- // `slay` CLI derives its DB from BOTH (getStorageDir() reads ROOT; the
27552
- // filename is `slayzone{.dev}.sqlite` per DEV — apps/cli/src/db.ts getDbPath).
27553
- // Strip either and a bare `slay` in a dev-app task terminal exits 1
27554
- // ("Database not found … Re-run with --dev"), and under an isolated e2e ROOT a
27555
- // child would escape the sandbox to the real ~/.slayzone. Neither is a
27556
- // credential and neither can be reinterpreted as a different kind of target
27557
- // (unlike a hub address), so both inherit verbatim.
27558
- //
27559
- // ROOT is NOT covered by the identity overlay: buildMcpEnv sets it only inside
27560
- // setHookIdentity(), gated on a hook-capable agent mode — a plain `terminal`
27561
- // PTY, a non-hook agent, or a pre-warmed pool spawn gets no overlay at all.
27562
- SLAYZONE_ROOT: "global",
27563
- SLAYZONE_DEV: "global"
27564
- };
27565
- var NON_PREFIXED_INFRA = /* @__PURE__ */ new Set([
27566
- "ELECTRON_RUN_AS_NODE",
27567
- "NODE_PATH",
27568
- "PLAYWRIGHT"
27569
- ]);
27570
- function sanitizeSpawnEnv(base) {
27571
- const out = {};
27572
- for (const [key, value] of Object.entries(base)) {
27573
- if (typeof value !== "string") continue;
27574
- if (NON_PREFIXED_INFRA.has(key)) continue;
27575
- if (key.startsWith("SLAYZONE_")) {
27576
- if (ENV_MANIFEST[key] === "global") out[key] = value;
27577
- continue;
28399
+ function searchFilesHandler(rawParams) {
28400
+ const { rootPath, query, options } = fsSearchFilesParamsSchema.parse(rawParams);
28401
+ const maxResults = Math.min(options?.maxResults ?? FS_MAX_SEARCH_RESULTS, FS_MAX_SEARCH_RESULTS);
28402
+ const root = allow(rootPath);
28403
+ const results = searchFiles(root, query, {
28404
+ ...options,
28405
+ maxResults
28406
+ }).filter((r) => !isProtectedPath((0, import_node_path9.join)(root, r.path)));
28407
+ const total = results.reduce((n, r) => n + r.matches.length, 0);
28408
+ return { results, truncated: total >= maxResults };
28409
+ }
28410
+ function listAllFilesHandler(rawParams) {
28411
+ const { rootPath } = fsListAllFilesParamsSchema.parse(rawParams);
28412
+ const all = listAllFiles(allow(rootPath));
28413
+ return {
28414
+ files: all.slice(0, FS_MAX_LIST_ALL_FILES),
28415
+ truncated: all.length > FS_MAX_LIST_ALL_FILES
28416
+ };
28417
+ }
28418
+ async function discoverReposHandler(rawParams) {
28419
+ const { rootPath, maxDepth } = fsDiscoverReposParamsSchema.parse(rawParams);
28420
+ return { repos: await discoverRepos(allow(rootPath), maxDepth) };
28421
+ }
28422
+ function watchStart(rawParams) {
28423
+ const { watchId, rootPath } = fsWatchStartParamsSchema.parse(rawParams);
28424
+ watches.get(watchId)?.();
28425
+ const abs = allow(rootPath);
28426
+ const dispose = subscribeFileWatcher(abs, (e) => {
28427
+ const params = { watchId, type: e.type, relPath: e.relPath };
28428
+ ctx.dialer.notify(ComputerNotificationMethods.fsChange, params);
28429
+ });
28430
+ watches.set(watchId, dispose);
28431
+ return { ok: true };
28432
+ }
28433
+ function watchStop(rawParams) {
28434
+ const { watchId } = fsWatchStopParamsSchema.parse(rawParams);
28435
+ watches.get(watchId)?.();
28436
+ watches.delete(watchId);
28437
+ return { ok: true };
28438
+ }
28439
+ function disposeAll() {
28440
+ for (const [watchId, dispose] of watches) {
28441
+ dispose();
28442
+ ctx.log("fs watch disposed on shutdown", { watchId });
27578
28443
  }
27579
- out[key] = value;
28444
+ watches.clear();
27580
28445
  }
27581
- return out;
28446
+ const handlers = {
28447
+ [HubToComputerMethods.fsPathExists]: pathExists,
28448
+ [HubToComputerMethods.fsRemoveDir]: removeDir,
28449
+ [HubToComputerMethods.fsListRoots]: listRoots,
28450
+ [HubToComputerMethods.fsListDir]: listDir,
28451
+ [HubToComputerMethods.fsMkdir]: mkdirHandler,
28452
+ [HubToComputerMethods.fsReadDir]: readDirHandler,
28453
+ [HubToComputerMethods.fsReadFile]: readFileHandler,
28454
+ [HubToComputerMethods.fsWriteFile]: writeFileHandler,
28455
+ [HubToComputerMethods.fsCreateFile]: createFileHandler,
28456
+ [HubToComputerMethods.fsCreateDir]: createDirHandler,
28457
+ [HubToComputerMethods.fsRename]: renameHandler,
28458
+ [HubToComputerMethods.fsDelete]: deleteHandler,
28459
+ [HubToComputerMethods.fsCopyIn]: copyInHandler,
28460
+ [HubToComputerMethods.fsCopy]: copyHandler,
28461
+ [HubToComputerMethods.fsGitStatus]: gitStatusHandler,
28462
+ [HubToComputerMethods.fsSearchFiles]: searchFilesHandler,
28463
+ [HubToComputerMethods.fsListAllFiles]: listAllFilesHandler,
28464
+ [HubToComputerMethods.fsDiscoverRepos]: discoverReposHandler,
28465
+ [HubToComputerMethods.fsWatchStart]: watchStart,
28466
+ [HubToComputerMethods.fsWatchStop]: watchStop
28467
+ };
28468
+ return { handlers, disposeAll };
28469
+ }
28470
+
28471
+ // src/handlers/gh.ts
28472
+ var import_node_child_process5 = require("node:child_process");
28473
+ var DEFAULT_TIMEOUT_MS = 3e4;
28474
+ var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
28475
+ function createGhHandlers(ctx) {
28476
+ async function ghExec(rawParams) {
28477
+ const params = ghExecParamsSchema.parse(rawParams);
28478
+ const cwd = assertPathAllowed(params.cwd, ctx.config.allowedRoots);
28479
+ return new Promise((resolve7) => {
28480
+ const child = (0, import_node_child_process5.execFile)(
28481
+ "gh",
28482
+ params.args,
28483
+ {
28484
+ cwd,
28485
+ timeout: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
28486
+ maxBuffer: MAX_OUTPUT_BYTES,
28487
+ // No env overlay: gh must see this machine's own `GH_*`/`GITHUB_TOKEN`
28488
+ // and credential helpers. Injecting anything here would be SlayZone
28489
+ // taking custody of credentials it deliberately does not hold.
28490
+ env: process.env
28491
+ },
28492
+ (err, stdout2, stderr) => {
28493
+ const status = err && typeof err.code === "number" ? err.code : err ? null : 0;
28494
+ resolve7({ status, stdout: stdout2 ?? "", stderr: stderr ?? "" });
28495
+ }
28496
+ );
28497
+ if (params.stdin !== void 0) {
28498
+ child.stdin?.end(params.stdin);
28499
+ }
28500
+ });
28501
+ }
28502
+ return { [HubToComputerMethods.ghExec]: ghExec };
27582
28503
  }
27583
28504
 
28505
+ // src/handlers/git.ts
28506
+ var import_node_child_process7 = require("node:child_process");
28507
+ var import_node_fs7 = require("node:fs");
28508
+ var import_promises7 = require("node:fs/promises");
28509
+ var import_node_path10 = require("node:path");
28510
+
27584
28511
  // src/handlers/exec.ts
27585
- var import_node_child_process4 = require("node:child_process");
28512
+ var import_node_child_process6 = require("node:child_process");
27586
28513
  function execCapture(command, args, opts = {}) {
27587
28514
  return new Promise((resolve7) => {
27588
- const child = (0, import_node_child_process4.spawn)(command, args, {
28515
+ const child = (0, import_node_child_process6.spawn)(command, args, {
27589
28516
  ...opts.cwd ? { cwd: opts.cwd } : {},
27590
28517
  ...opts.env ? { env: opts.env } : {},
27591
28518
  stdio: ["ignore", "pipe", "pipe"]
@@ -27604,7 +28531,7 @@ function execCapture(command, args, opts = {}) {
27604
28531
  });
27605
28532
  child.on("error", (err) => {
27606
28533
  if (timer) clearTimeout(timer);
27607
- resolve7({ stdout: "", stderr: err.message, status: 1 });
28534
+ resolve7({ stdout: "", stderr: err.message, status: 1, spawnError: err.message });
27608
28535
  });
27609
28536
  });
27610
28537
  }
@@ -27637,14 +28564,12 @@ var SETUP_SCRIPT_TIMEOUT_MS = 5 * 6e4;
27637
28564
  function createGitHandlers(ctx) {
27638
28565
  const allow = (candidate) => assertPathAllowed(candidate, ctx.config.allowedRoots);
27639
28566
  async function isGitRepo(rawParams) {
27640
- const { path: path8 } = isGitRepoParams.parse(rawParams);
27641
- const repoPath = allow(path8);
27642
- try {
27643
- await execGit(["rev-parse", "--git-dir"], repoPath);
27644
- return { isGitRepo: true };
27645
- } catch {
27646
- return { isGitRepo: false };
27647
- }
28567
+ const { path: path9 } = isGitRepoParams.parse(rawParams);
28568
+ const repoPath = allow(path9);
28569
+ if (!(0, import_node_fs7.existsSync)(repoPath)) return { isGitRepo: false };
28570
+ const result = await execCapture("git", ["rev-parse", "--git-dir"], { cwd: repoPath });
28571
+ if (result.spawnError) throw new Error(`Could not start git: ${result.spawnError}`);
28572
+ return { isGitRepo: result.status === 0 };
27648
28573
  }
27649
28574
  async function getCurrentBranch(rawParams) {
27650
28575
  const parsed = getCurrentBranchParams.parse(rawParams);
@@ -27674,7 +28599,7 @@ function createGitHandlers(ctx) {
27674
28599
  try {
27675
28600
  await execGit(["worktree", "remove", worktreePath, "--force"], repoPath);
27676
28601
  } catch (err) {
27677
- if (!(0, import_node_fs5.existsSync)(worktreePath)) {
28602
+ if (!(0, import_node_fs7.existsSync)(worktreePath)) {
27678
28603
  await execGit(["worktree", "prune"], repoPath);
27679
28604
  } else {
27680
28605
  throw err;
@@ -27702,13 +28627,13 @@ function createGitHandlers(ctx) {
27702
28627
  const params = runWorktreeSetupScriptParams.parse(rawParams);
27703
28628
  const worktreePath = allow(params.worktreePath);
27704
28629
  const repoPath = allow(params.repoPath);
27705
- const scriptPath = (0, import_node_path7.resolve)(worktreePath, SETUP_SCRIPT);
27706
- if (!(0, import_node_fs5.existsSync)(scriptPath)) return Promise.resolve({ ran: false });
28630
+ const scriptPath = (0, import_node_path10.resolve)(worktreePath, SETUP_SCRIPT);
28631
+ if (!(0, import_node_fs7.existsSync)(scriptPath)) return Promise.resolve({ ran: false });
27707
28632
  try {
27708
- (0, import_node_fs5.accessSync)(scriptPath, import_node_fs5.constants.X_OK);
28633
+ (0, import_node_fs7.accessSync)(scriptPath, import_node_fs7.constants.X_OK);
27709
28634
  } catch {
27710
28635
  try {
27711
- (0, import_node_fs5.chmodSync)(scriptPath, 493);
28636
+ (0, import_node_fs7.chmodSync)(scriptPath, 493);
27712
28637
  } catch {
27713
28638
  return Promise.resolve({ ran: false });
27714
28639
  }
@@ -27718,7 +28643,7 @@ function createGitHandlers(ctx) {
27718
28643
  env.REPO_PATH = repoPath;
27719
28644
  env.SOURCE_BRANCH = params.sourceBranch ?? "";
27720
28645
  return new Promise((resolvePromise) => {
27721
- const child = (0, import_node_child_process5.spawn)(scriptPath, [], {
28646
+ const child = (0, import_node_child_process7.spawn)(scriptPath, [], {
27722
28647
  cwd: worktreePath,
27723
28648
  stdio: ["ignore", "pipe", "pipe"],
27724
28649
  env
@@ -27759,20 +28684,20 @@ function createGitHandlers(ctx) {
27759
28684
  } else {
27760
28685
  relPaths = params.customPaths ?? [];
27761
28686
  }
27762
- const repoRoot = (0, import_node_path7.resolve)(repoPath);
27763
- const wtRoot = (0, import_node_path7.resolve)(worktreePath);
28687
+ const repoRoot = (0, import_node_path10.resolve)(repoPath);
28688
+ const wtRoot = (0, import_node_path10.resolve)(worktreePath);
27764
28689
  let copied = 0;
27765
28690
  for (const rel of relPaths) {
27766
- const src = (0, import_node_path7.resolve)(repoPath, rel);
27767
- const dest = (0, import_node_path7.resolve)(worktreePath, rel);
27768
- if (!isInside(src, repoRoot) || !isInside(dest, wtRoot)) {
28691
+ const src = (0, import_node_path10.resolve)(repoPath, rel);
28692
+ const dest = (0, import_node_path10.resolve)(worktreePath, rel);
28693
+ if (!isInside2(src, repoRoot) || !isInside2(dest, wtRoot)) {
27769
28694
  ctx.log("copyIgnoredFiles skipped traversal", { rel });
27770
28695
  continue;
27771
28696
  }
27772
- if (!(0, import_node_fs5.existsSync)(src)) continue;
28697
+ if (!(0, import_node_fs7.existsSync)(src)) continue;
27773
28698
  try {
27774
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(dest), { recursive: true });
27775
- await (0, import_promises6.cp)(src, dest, { recursive: true, verbatimSymlinks: true });
28699
+ await (0, import_promises7.mkdir)((0, import_node_path10.dirname)(dest), { recursive: true });
28700
+ await (0, import_promises7.cp)(src, dest, { recursive: true, verbatimSymlinks: true });
27776
28701
  copied += 1;
27777
28702
  } catch (err) {
27778
28703
  ctx.log("copyIgnoredFiles copy failed", { rel, error: String(err) });
@@ -27789,83 +28714,13 @@ function createGitHandlers(ctx) {
27789
28714
  [GitMethods.copyIgnoredFiles]: copyIgnoredFiles
27790
28715
  };
27791
28716
  }
27792
- function isInside(child, root) {
27793
- const c = (0, import_node_path7.resolve)(child);
27794
- return c === root || c.startsWith(root + import_node_path7.sep);
28717
+ function isInside2(child, root) {
28718
+ const c = (0, import_node_path10.resolve)(child);
28719
+ return c === root || c.startsWith(root + import_node_path10.sep);
27795
28720
  }
27796
28721
 
27797
28722
  // src/handlers/proc.ts
27798
- var import_node_child_process6 = require("node:child_process");
27799
-
27800
- // ../../shared/platform/src/index.ts
27801
- init_dirs();
27802
-
27803
- // ../../shared/platform/src/paths.ts
27804
- init_dirs();
27805
-
27806
- // ../../shared/platform/src/index.ts
27807
- init_fs_utils();
27808
-
27809
- // ../../shared/platform/src/migrations.ts
27810
- init_dirs();
27811
-
27812
- // ../../shared/platform/src/cli-install.ts
27813
- var import_child_process = require("child_process");
27814
- var import_util9 = require("util");
27815
- var execFileAsync = (0, import_util9.promisify)(import_child_process.execFile);
27816
- var WINDOWS_SHIM = [
27817
- "@echo off",
27818
- `for /f "tokens=1 delims=." %%v in ('node -e "process.stdout.write(process.versions.node)"') do set NODE_MAJOR=%%v`,
27819
- "if %NODE_MAJOR% LEQ 22 (",
27820
- ' node --experimental-sqlite --no-warnings "%~dp0slay.js" %*',
27821
- ") else (",
27822
- ' node --no-warnings "%~dp0slay.js" %*',
27823
- ")",
27824
- ""
27825
- ].join("\r\n");
27826
-
27827
- // ../../shared/platform/src/shell.ts
27828
- var import_node_fs6 = __toESM(require("node:fs"), 1);
27829
- var import_os2 = require("os");
27830
- var shellOverride = null;
27831
- function shellExists(shellPath) {
27832
- if ((0, import_os2.platform)() === "win32") return import_node_fs6.default.existsSync(shellPath);
27833
- try {
27834
- import_node_fs6.default.accessSync(shellPath, import_node_fs6.default.constants.X_OK);
27835
- return true;
27836
- } catch {
27837
- return false;
27838
- }
27839
- }
27840
- function defaultShellForPlatform() {
27841
- if ((0, import_os2.platform)() === "win32") return process.env.COMSPEC || "cmd.exe";
27842
- if ((0, import_os2.platform)() === "darwin") return "/bin/zsh";
27843
- return "/bin/bash";
27844
- }
27845
- function resolveUserShell() {
27846
- if (shellOverride && shellExists(shellOverride)) return shellOverride;
27847
- const fromEnv = process.env.SHELL?.trim();
27848
- if (fromEnv && shellExists(fromEnv)) return fromEnv;
27849
- try {
27850
- const fromUser = (0, import_os2.userInfo)().shell?.trim();
27851
- if (fromUser && shellExists(fromUser)) return fromUser;
27852
- } catch {
27853
- }
27854
- return defaultShellForPlatform();
27855
- }
27856
- function buildShellInvocation(command) {
27857
- const shell = resolveUserShell();
27858
- if ((0, import_os2.platform)() === "win32") return { file: shell, args: ["/c", command] };
27859
- const isFish = shell.endsWith("/fish");
27860
- return { file: shell, args: [...isFish ? ["-i", "-l"] : ["-l"], "-c", command] };
27861
- }
27862
-
27863
- // ../../shared/platform/src/slz-file-url.ts
27864
- var SLZ_FILE_HOST = "app";
27865
- var SLZ_FILE_PREFIX = `slz-file://${SLZ_FILE_HOST}`;
27866
-
27867
- // ../../shared/platform/src/ipc-dedup.ts
27868
- var UNCHANGED = Object.freeze({ __ipc_unchanged__: true });
28723
+ var import_node_child_process8 = require("node:child_process");
27869
28724
 
27870
28725
  // src/exit-tombstones.ts
27871
28726
  var TOMBSTONE_TTL_MS = 10 * 6e4;
@@ -27988,6 +28843,9 @@ function createProcHandlers(ctx, getProjectPaths) {
27988
28843
  }
27989
28844
  return merged;
27990
28845
  }
28846
+ function killTree(child, signal = "SIGTERM") {
28847
+ if (typeof child.pid === "number") killPidTree(child.pid, signal);
28848
+ }
27991
28849
  function procSpawn(rawParams) {
27992
28850
  const params = procSpawnParamsSchema.parse(rawParams);
27993
28851
  if (params.spawnId !== void 0) {
@@ -28000,21 +28858,21 @@ function createProcHandlers(ctx, getProjectPaths) {
28000
28858
  }
28001
28859
  }
28002
28860
  const resolved = params.cwd ?? (params.projectId ? getProjectPaths?.()?.get(params.projectId)?.path : void 0);
28003
- const cwd = resolved ? assertPathAllowed(resolved, ctx.config.allowedRoots) : void 0;
28861
+ const cwd = spawnCwd(resolved, ctx.config.allowedRoots);
28004
28862
  const existing = procs.get(params.sessionId);
28005
28863
  if (existing) {
28006
- try {
28007
- existing.child.kill();
28008
- } catch {
28009
- }
28864
+ killTree(existing.child);
28010
28865
  procs.delete(params.sessionId);
28011
28866
  }
28012
28867
  const invocation = params.shell ? buildShellInvocation(params.command) : { file: params.command, args: params.args ?? [] };
28013
- const child = (0, import_node_child_process6.spawn)(invocation.file, invocation.args, {
28868
+ const child = (0, import_node_child_process8.spawn)(invocation.file, invocation.args, {
28014
28869
  ...cwd ? { cwd } : {},
28015
28870
  env: buildEnv(params.env),
28016
28871
  // stdin PIPED (was 'ignore') so `proc.write` can reach the child.
28017
- stdio: ["pipe", "pipe", "pipe"]
28872
+ stdio: ["pipe", "pipe", "pipe"],
28873
+ // Its own process group, so a kill reaches everything it started (`pnpm dev`
28874
+ // → node → …), not just the shell — see `killTree`.
28875
+ detached: PROCESS_TREE_DETACHED
28018
28876
  });
28019
28877
  const buffer = new RingBuffer(MAX_BUFFER_SIZE);
28020
28878
  const session = {
@@ -28081,11 +28939,7 @@ function createProcHandlers(ctx, getProjectPaths) {
28081
28939
  ctx.log("proc kill on unknown session", { sessionId: params.sessionId });
28082
28940
  return { ok: true };
28083
28941
  }
28084
- try {
28085
- session.child.kill(params.signal ?? "SIGTERM");
28086
- } catch (err) {
28087
- ctx.log("proc kill failed", { sessionId: params.sessionId, error: String(err) });
28088
- }
28942
+ killTree(session.child, params.signal ?? "SIGTERM");
28089
28943
  return { ok: true };
28090
28944
  }
28091
28945
  function procWrite(rawParams) {
@@ -28123,10 +28977,7 @@ function createProcHandlers(ctx, getProjectPaths) {
28123
28977
  }
28124
28978
  function disposeAll() {
28125
28979
  for (const [sessionId, session] of procs) {
28126
- try {
28127
- session.child.kill();
28128
- } catch {
28129
- }
28980
+ killTree(session.child);
28130
28981
  ctx.log("proc disposed on shutdown", { sessionId });
28131
28982
  }
28132
28983
  procs.clear();
@@ -28142,7 +28993,7 @@ function createProcHandlers(ctx, getProjectPaths) {
28142
28993
  }
28143
28994
 
28144
28995
  // src/handlers/project.ts
28145
- var import_node_fs7 = require("node:fs");
28996
+ var import_node_fs8 = require("node:fs");
28146
28997
  function createProjectHandlers(ctx, getStore) {
28147
28998
  const allow = (candidate) => assertPathAllowed(candidate, ctx.config.allowedRoots);
28148
28999
  const store = () => {
@@ -28154,11 +29005,11 @@ function createProjectHandlers(ctx, getStore) {
28154
29005
  const { projectId } = projectResolvePathParamsSchema.parse(rawParams);
28155
29006
  const entry = store().get(projectId);
28156
29007
  if (!entry) return { path: null };
28157
- return { path: entry.path, exists: (0, import_node_fs7.existsSync)(entry.path) };
29008
+ return { path: entry.path, exists: (0, import_node_fs8.existsSync)(entry.path) };
28158
29009
  }
28159
29010
  async function setPath(rawParams) {
28160
- const { projectId, path: path8 } = projectSetPathParamsSchema.parse(rawParams);
28161
- const resolved = allow(path8);
29011
+ const { projectId, path: path9 } = projectSetPathParamsSchema.parse(rawParams);
29012
+ const resolved = allow(path9);
28162
29013
  const entry = await store().set(projectId, resolved);
28163
29014
  ctx.log("project path recorded", { projectId, path: entry.path });
28164
29015
  return { path: entry.path };
@@ -28175,7 +29026,7 @@ function createProjectHandlers(ctx, getStore) {
28175
29026
  projects: store().list().map((e) => ({
28176
29027
  projectId: e.projectId,
28177
29028
  path: e.path,
28178
- exists: (0, import_node_fs7.existsSync)(e.path)
29029
+ exists: (0, import_node_fs8.existsSync)(e.path)
28179
29030
  }))
28180
29031
  };
28181
29032
  }
@@ -28215,11 +29066,102 @@ function armStartupDeadline({
28215
29066
  return { markOutput: stop, cancel: stop };
28216
29067
  }
28217
29068
 
29069
+ // src/background-jobs.ts
29070
+ var import_node_child_process9 = require("node:child_process");
29071
+ var import_promises8 = require("node:fs/promises");
29072
+ var import_node_util = require("node:util");
29073
+ var execFileAsync2 = (0, import_node_util.promisify)(import_node_child_process9.execFile);
29074
+ var EXEC_TIMEOUT_MS = 5e3;
29075
+ function claudeEvalArg(cmd) {
29076
+ return `'${cmd.replace(/'/g, `'"'"'`)}'`;
29077
+ }
29078
+ function parsePsRows(out) {
29079
+ const rows = [];
29080
+ for (const line of out.split("\n")) {
29081
+ const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
29082
+ if (m) rows.push({ pid: Number(m[1]), ppid: Number(m[2]), args: m[3].trim() });
29083
+ }
29084
+ return rows;
29085
+ }
29086
+ function descendantsOf(rootPid, rows) {
29087
+ const byParent = /* @__PURE__ */ new Map();
29088
+ for (const r of rows) {
29089
+ const list = byParent.get(r.ppid);
29090
+ if (list) list.push(r);
29091
+ else byParent.set(r.ppid, [r]);
29092
+ }
29093
+ const out = [];
29094
+ const queue = [rootPid];
29095
+ const seen = new Set(queue);
29096
+ while (queue.length > 0) {
29097
+ for (const child of byParent.get(queue.shift()) ?? []) {
29098
+ if (seen.has(child.pid)) continue;
29099
+ seen.add(child.pid);
29100
+ out.push(child);
29101
+ queue.push(child.pid);
29102
+ }
29103
+ }
29104
+ return out;
29105
+ }
29106
+ function matchLiveJobs(jobs, descendants, stdoutByPid) {
29107
+ const outputs = new Set(stdoutByPid.values());
29108
+ const alive = [];
29109
+ for (const job of jobs) {
29110
+ const outputFile = `/tasks/${job.id}.output`;
29111
+ const byOutput = job.kind === "shell" && [...outputs].some((path9) => path9.endsWith(outputFile));
29112
+ const evalArg = job.command ? `eval ${claudeEvalArg(job.command)}` : null;
29113
+ const byCommand = evalArg !== null && descendants.some((d) => d.args.includes(evalArg));
29114
+ if (byOutput || byCommand) alive.push(job.id);
29115
+ }
29116
+ return alive;
29117
+ }
29118
+ async function readStdoutPaths(pids) {
29119
+ const out = /* @__PURE__ */ new Map();
29120
+ if (pids.length === 0) return out;
29121
+ if (process.platform === "linux") {
29122
+ await Promise.all(
29123
+ pids.map(
29124
+ (pid2) => (0, import_promises8.readlink)(`/proc/${pid2}/fd/1`).then(
29125
+ (path9) => void out.set(pid2, path9),
29126
+ () => void 0
29127
+ )
29128
+ )
29129
+ );
29130
+ return out;
29131
+ }
29132
+ const res = await execFileAsync2(
29133
+ "lsof",
29134
+ ["-b", "-n", "-P", "-w", "-a", "-p", pids.join(","), "-d", "1", "-F", "pn"],
29135
+ {
29136
+ timeout: EXEC_TIMEOUT_MS,
29137
+ maxBuffer: 4 * 1024 * 1024
29138
+ }
29139
+ ).catch((err) => ({ stdout: err.stdout ?? "" }));
29140
+ let pid = 0;
29141
+ for (const line of res.stdout.split("\n")) {
29142
+ if (line.startsWith("p")) pid = Number(line.slice(1));
29143
+ else if (line.startsWith("n") && pid) out.set(pid, line.slice(1));
29144
+ }
29145
+ return out;
29146
+ }
29147
+ async function probeBackgroundJobs(rootPid, jobs) {
29148
+ if (process.platform === "win32") return null;
29149
+ if (jobs.length === 0) return [];
29150
+ const { stdout: stdout2 } = await execFileAsync2("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], {
29151
+ timeout: EXEC_TIMEOUT_MS,
29152
+ maxBuffer: 16 * 1024 * 1024
29153
+ });
29154
+ const descendants = descendantsOf(rootPid, parsePsRows(stdout2));
29155
+ const needStdout = jobs.some((j) => j.kind === "shell");
29156
+ const stdoutByPid = needStdout ? await readStdoutPaths(descendants.map((d) => d.pid)) : /* @__PURE__ */ new Map();
29157
+ return matchLiveJobs(jobs, descendants, stdoutByPid);
29158
+ }
29159
+
28218
29160
  // src/spawn-admission.ts
28219
- var import_node_os5 = require("node:os");
29161
+ var import_node_os6 = require("node:os");
28220
29162
  var DEFAULT_START_CAP_MS = 5e3;
28221
29163
  var DEFAULT_WARM_QUIET_MS = 3e3;
28222
- function defaultAdmissionConcurrency(cores = (0, import_node_os5.availableParallelism)()) {
29164
+ function defaultAdmissionConcurrency(cores = (0, import_node_os6.availableParallelism)()) {
28223
29165
  return Math.max(2, Math.min(4, Math.floor(cores / 2)));
28224
29166
  }
28225
29167
  var LANE_RANK = { foreground: 0, background: 1, warm: 2 };
@@ -28342,7 +29284,7 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28342
29284
  const admission = options.admission ?? createSpawnAdmission();
28343
29285
  const queued = /* @__PURE__ */ new Map();
28344
29286
  function resolveCwd(opts) {
28345
- if (opts.cwd) return opts.cwd;
29287
+ if (opts.cwd) return spawnCwd(opts.cwd, ctx.config.allowedRoots);
28346
29288
  if (!opts.projectId) throw new Error("pty.spawn needs either cwd or projectId");
28347
29289
  const entry = getProjectPaths?.()?.get(opts.projectId);
28348
29290
  if (!entry) {
@@ -28350,7 +29292,7 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28350
29292
  `project ${opts.projectId} is not checked out on this computer \u2014 adopt a folder or enable remote git for it`
28351
29293
  );
28352
29294
  }
28353
- return entry.path;
29295
+ return spawnCwd(entry.path, ctx.config.allowedRoots);
28354
29296
  }
28355
29297
  function buildEnv(overrides) {
28356
29298
  const merged = overrides ? { ...sanitizeSpawnEnv(process.env), ...overrides } : sanitizeSpawnEnv(process.env);
@@ -28590,6 +29532,13 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28590
29532
  const item = queued.get(params.sessionId);
28591
29533
  return { ok: true, queued: item ? admission.prioritize(item.ticket) : false };
28592
29534
  }
29535
+ async function backgroundJobs(rawParams) {
29536
+ const params = ptyBackgroundJobsParamsSchema.parse(rawParams);
29537
+ const pid = sessions.get(params.sessionId)?.proc.pid;
29538
+ if (pid === void 0) return { ok: true, supported: true, alive: [] };
29539
+ const alive = await probeBackgroundJobs(pid, params.jobs);
29540
+ return alive === null ? { ok: true, supported: false, alive: [] } : { ok: true, supported: true, alive };
29541
+ }
28593
29542
  function dropQueued(reason) {
28594
29543
  for (const id of [...queued.keys()]) cancelQueued(id, reason);
28595
29544
  }
@@ -28602,6 +29551,15 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28602
29551
  `no warm session ${params.warmId} to adopt`
28603
29552
  );
28604
29553
  }
29554
+ const displaced = sessions.get(params.sessionId);
29555
+ if (displaced) {
29556
+ ctx.log("warm adopt displaced a live session", {
29557
+ warmId: params.warmId,
29558
+ sessionId: params.sessionId,
29559
+ displacedPid: displaced.proc.pid,
29560
+ displacedWarm: !!displaced.warm
29561
+ });
29562
+ }
28605
29563
  sessions.delete(params.warmId);
28606
29564
  session.id = params.sessionId;
28607
29565
  delete session.warm;
@@ -28759,6 +29717,7 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28759
29717
  [HubToComputerMethods.ptyGetBufferSince]: getBufferSince,
28760
29718
  [HubToComputerMethods.ptyList]: list,
28761
29719
  [HubToComputerMethods.ptyPrioritize]: prioritize,
29720
+ [HubToComputerMethods.ptyBackgroundJobs]: backgroundJobs,
28762
29721
  [HubToComputerMethods.ptyWarmSpawn]: warmSpawn,
28763
29722
  [HubToComputerMethods.ptyWarmAdopt]: warmAdopt,
28764
29723
  [HubToComputerMethods.ptyWarmKill]: warmKill,
@@ -28768,27 +29727,46 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
28768
29727
  }
28769
29728
 
28770
29729
  // src/handlers/computer-config.ts
28771
- var import_node_fs8 = require("node:fs");
28772
- var import_node_path8 = require("node:path");
29730
+ var import_node_fs9 = require("node:fs");
29731
+ var import_node_path11 = require("node:path");
29732
+ function canonicalRoots(roots) {
29733
+ const applied = [];
29734
+ const rejected = [];
29735
+ for (const root of roots) {
29736
+ if (!(0, import_node_path11.isAbsolute)(root)) {
29737
+ rejected.push({ path: root, reason: "not an absolute path" });
29738
+ continue;
29739
+ }
29740
+ let real;
29741
+ try {
29742
+ real = import_node_fs9.realpathSync.native((0, import_node_path11.resolve)(root));
29743
+ } catch {
29744
+ rejected.push({ path: root, reason: "does not exist on this computer" });
29745
+ continue;
29746
+ }
29747
+ if (!applied.includes(real)) applied.push(real);
29748
+ }
29749
+ return { roots: applied, rejected };
29750
+ }
29751
+ function reloadLocalRoots(config2, declared) {
29752
+ if (!declared || declared.length === 0) return null;
29753
+ const { roots } = canonicalRoots(declared);
29754
+ if (roots.length === 0) return null;
29755
+ const same = roots.length === config2.allowedRoots.length && roots.every((r, i) => r === config2.allowedRoots[i]);
29756
+ if (same) return null;
29757
+ config2.allowedRoots = roots;
29758
+ return roots;
29759
+ }
28773
29760
  function createComputerConfigHandlers(ctx) {
28774
29761
  function setAllowedRoots(rawParams) {
28775
29762
  const { roots } = computerSetAllowedRootsParamsSchema.parse(rawParams);
28776
- const applied = [];
28777
- const rejected = [];
28778
- for (const root of roots) {
28779
- if (!(0, import_node_path8.isAbsolute)(root)) {
28780
- rejected.push({ path: root, reason: "not an absolute path" });
28781
- continue;
28782
- }
28783
- let real;
28784
- try {
28785
- real = import_node_fs8.realpathSync.native((0, import_node_path8.resolve)(root));
28786
- } catch {
28787
- rejected.push({ path: root, reason: "does not exist on this computer" });
28788
- continue;
28789
- }
28790
- if (!applied.includes(real)) applied.push(real);
29763
+ if (!ctx.config.hubMayEditRoots) {
29764
+ throw new RpcError(
29765
+ ComputerTransportErrorCodes.refusedByComputer,
29766
+ `this computer's folder limits are set on the computer itself, not by a hub \u2014 on that machine run \`slay computer roots ${ctx.config.name} --add <dir>\` (or edit allowedRoots in its computer.config.json)`
29767
+ );
28791
29768
  }
29769
+ const { roots: applied, rejected } = canonicalRoots(roots);
28792
29770
  if (applied.length === 0) {
28793
29771
  throw new Error(
28794
29772
  rejected.length > 0 ? `no usable roots: ${rejected.map((r) => `${r.path} (${r.reason})`).join(", ")}` : "a computer needs at least one allowed root; revoke the computer to stop it entirely"
@@ -28805,13 +29783,13 @@ function createComputerConfigHandlers(ctx) {
28805
29783
  }
28806
29784
 
28807
29785
  // src/project-paths.ts
28808
- var import_promises8 = require("node:fs/promises");
28809
- var import_node_os6 = require("node:os");
28810
- var import_node_path9 = require("node:path");
29786
+ var import_promises9 = require("node:fs/promises");
29787
+ var import_node_os7 = require("node:os");
29788
+ var import_node_path12 = require("node:path");
28811
29789
  function slayzoneRootDir3() {
28812
29790
  if (process.env.SLAYZONE_ROOT) return process.env.SLAYZONE_ROOT;
28813
- const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os6.homedir)();
28814
- return (0, import_node_path9.join)(home, ".slayzone");
29791
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os7.homedir)();
29792
+ return (0, import_node_path12.join)(home, ".slayzone");
28815
29793
  }
28816
29794
  var projectPathEntrySchema = external_exports.object({
28817
29795
  /** Absolute, realpath-resolved at write time. */
@@ -28820,13 +29798,13 @@ var projectPathEntrySchema = external_exports.object({
28820
29798
  });
28821
29799
  var projectMapSchema = external_exports.record(external_exports.string(), projectPathEntrySchema);
28822
29800
  function projectsFilePath(baseDir) {
28823
- return (0, import_node_path9.join)(baseDir ?? slayzoneRootDir3(), "computer.projects.json");
29801
+ return (0, import_node_path12.join)(baseDir ?? slayzoneRootDir3(), "computer.projects.json");
28824
29802
  }
28825
29803
  var fileSchema = external_exports.record(external_exports.string(), projectMapSchema);
28826
29804
  async function readFileMap(filePath) {
28827
29805
  let raw;
28828
29806
  try {
28829
- raw = await (0, import_promises8.readFile)(filePath, "utf8");
29807
+ raw = await (0, import_promises9.readFile)(filePath, "utf8");
28830
29808
  } catch (err) {
28831
29809
  if (err.code === "ENOENT") return {};
28832
29810
  throw err;
@@ -28851,14 +29829,14 @@ async function readFileMap(filePath) {
28851
29829
  return fileSchema.parse(out);
28852
29830
  }
28853
29831
  async function writeFileMap(filePath, map2) {
28854
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(filePath), { recursive: true, mode: 448 });
29832
+ await (0, import_promises9.mkdir)((0, import_node_path12.dirname)(filePath), { recursive: true, mode: 448 });
28855
29833
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
28856
- await (0, import_promises8.writeFile)(tmpPath, `${JSON.stringify(map2, null, 2)}
29834
+ await (0, import_promises9.writeFile)(tmpPath, `${JSON.stringify(map2, null, 2)}
28857
29835
  `, { mode: 384 });
28858
29836
  try {
28859
- await (0, import_promises8.rename)(tmpPath, filePath);
29837
+ await (0, import_promises9.rename)(tmpPath, filePath);
28860
29838
  } catch (err) {
28861
- await (0, import_promises8.rm)(tmpPath, { force: true });
29839
+ await (0, import_promises9.rm)(tmpPath, { force: true });
28862
29840
  throw err;
28863
29841
  }
28864
29842
  }
@@ -28871,8 +29849,8 @@ async function createProjectPathStore(hubHost, options = {}) {
28871
29849
  get(projectId) {
28872
29850
  return cache[projectId] ?? null;
28873
29851
  },
28874
- async set(projectId, path8) {
28875
- const entry = { path: path8, updatedAt: Date.now() };
29852
+ async set(projectId, path9) {
29853
+ const entry = { path: path9, updatedAt: Date.now() };
28876
29854
  const all = await readFileMap(filePath);
28877
29855
  all[hubHost] = { ...all[hubHost] ?? {}, [projectId]: entry };
28878
29856
  await writeFileMap(filePath, all);
@@ -28910,6 +29888,7 @@ function createHubRequestHandler(deps) {
28910
29888
  deps.admission ? { admission: deps.admission } : {}
28911
29889
  );
28912
29890
  const proc = createProcHandlers(ctx, deps.getProjectPaths);
29891
+ const fs6 = createFsHandlers(ctx);
28913
29892
  const shutdownHandler = async (params) => {
28914
29893
  const parsed = computerShutdownParamsSchema.safeParse(params ?? {});
28915
29894
  const reason = parsed.success ? parsed.data.reason ?? "hub-requested" : "hub-requested";
@@ -28921,21 +29900,67 @@ function createHubRequestHandler(deps) {
28921
29900
  ...pty2.handlers,
28922
29901
  ...proc.handlers,
28923
29902
  ...createGitHandlers(ctx),
28924
- ...createFsHandlers(ctx),
29903
+ ...fs6.handlers,
28925
29904
  ...createComputerConfigHandlers(ctx),
28926
29905
  ...createGhHandlers(ctx),
28927
29906
  ...createProjectHandlers(ctx, deps.getProjectPaths ?? (() => null))
28928
29907
  };
28929
- const handle = async (method, params) => {
29908
+ const hub = deps.hubHost ?? "unknown";
29909
+ const ptyMethods = new Set(Object.keys(pty2.handlers));
29910
+ const handle = async (method, params, rawActor) => {
29911
+ const parsedActor = requestActorSchema.safeParse(rawActor);
29912
+ const actor = parsedActor.success ? parsedActor.data : null;
29913
+ const record2 = (result, code, reason) => deps.requestLog?.record({
29914
+ hub,
29915
+ method,
29916
+ actor,
29917
+ params,
29918
+ result,
29919
+ ...code !== void 0 ? { code } : {},
29920
+ ...reason !== void 0 ? { reason } : {}
29921
+ });
29922
+ if (!actor) {
29923
+ const err = new RpcError(
29924
+ ComputerTransportErrorCodes.protocolMismatch,
29925
+ "hub sent no actor with this request (a hub older than computer protocol v3) \u2014 upgrade the hub to this computer's release"
29926
+ );
29927
+ record2("refused", err.code, err.message);
29928
+ throw err;
29929
+ }
28930
29930
  const entry = table[method];
28931
29931
  if (!entry) {
28932
- throw new RpcError(ComputerTransportErrorCodes.unimplemented, `unimplemented: ${method}`);
29932
+ const err = new RpcError(
29933
+ ComputerTransportErrorCodes.unimplemented,
29934
+ `unimplemented: ${method}`
29935
+ );
29936
+ record2("error", err.code);
29937
+ throw err;
29938
+ }
29939
+ try {
29940
+ if (deps.pathReady && !ptyMethods.has(method)) await deps.pathReady;
29941
+ const result = await entry(params);
29942
+ record2("ok");
29943
+ return result;
29944
+ } catch (err) {
29945
+ if (err instanceof JailViolationError) {
29946
+ log("request refused by the path jail", { method, reason: err.message });
29947
+ const refused = new RpcError(ComputerTransportErrorCodes.refusedByComputer, err.message);
29948
+ record2("refused", refused.code, err.message);
29949
+ throw refused;
29950
+ }
29951
+ if (err instanceof RpcError && err.code === ComputerTransportErrorCodes.refusedByComputer) {
29952
+ log("request refused by local policy", { method, reason: err.message });
29953
+ record2("refused", err.code, err.message);
29954
+ throw err;
29955
+ }
29956
+ record2("error", err instanceof RpcError ? err.code : JSON_RPC_INTERNAL_ERROR);
29957
+ throw err;
28933
29958
  }
28934
- return entry(params);
28935
29959
  };
28936
29960
  const dispose = () => {
28937
29961
  pty2.disposeAll();
28938
29962
  proc.disposeAll();
29963
+ fs6.disposeAll();
28939
29964
  };
28940
29965
  const setAgentHookUrl = (url2) => {
28941
29966
  ctx.agentHookUrl = url2;
@@ -28964,6 +29989,36 @@ function startComputer(config2, deps = {}) {
28964
29989
  });
28965
29990
  });
28966
29991
  let handle;
29992
+ const stateDir = deps.stateDir ?? (0, import_node_path13.dirname)(getComputerConfigFilePath());
29993
+ const configFile = (0, import_node_path13.join)(stateDir, "computer.config.json");
29994
+ setProtectedPaths([
29995
+ configFile,
29996
+ // The legacy file computer.config.json falls back to when absent.
29997
+ (0, import_node_path13.join)(stateDir, "config.json"),
29998
+ credentialsFilePath(stateDir),
29999
+ (0, import_node_path13.join)(stateDir, REQUEST_LOG_DIR_NAME)
30000
+ ]);
30001
+ const requestLog = createRequestLog({
30002
+ stateDir,
30003
+ onError: (err) => log("request log write failed", { error: String(err) })
30004
+ });
30005
+ log("local request log", { file: requestLog.filePath });
30006
+ const watchLocalRoots = !config2.hubMayEditRoots;
30007
+ if (watchLocalRoots) {
30008
+ (0, import_node_fs10.watchFile)(configFile, { interval: 2e3, persistent: false }, () => {
30009
+ const declared = loadComputerConfigFile(configFile).allowedRoots;
30010
+ const applied = reloadLocalRoots(config2, declared);
30011
+ if (!applied) return;
30012
+ log("allowedRoots reloaded from local config", { applied });
30013
+ requestLog.record({
30014
+ hub: "local",
30015
+ method: "local.allowedRoots",
30016
+ actor: null,
30017
+ params: { roots: applied },
30018
+ result: "ok"
30019
+ });
30020
+ });
30021
+ }
28967
30022
  const admission = createSpawnAdmission();
28968
30023
  const connectHub = (hubUrl, isPrimary) => {
28969
30024
  const credentialStore = (isPrimary ? deps.credentialStore : void 0) ?? createFileCredentialStore(hubHostFromUrl(hubUrl));
@@ -28973,7 +30028,7 @@ function startComputer(config2, deps = {}) {
28973
30028
  identity,
28974
30029
  credentialStore,
28975
30030
  ...config2.joinToken && isPrimary ? { joinToken: config2.joinToken } : {},
28976
- onHubRequest: (method, params) => dispatch.handle(method, params),
30031
+ onHubRequest: (method, params, actor) => dispatch.handle(method, params, actor),
28977
30032
  log
28978
30033
  });
28979
30034
  let projectPaths = null;
@@ -28994,7 +30049,10 @@ function startComputer(config2, deps = {}) {
28994
30049
  config: config2,
28995
30050
  log,
28996
30051
  getProjectPaths: () => projectPaths,
28997
- admission
30052
+ admission,
30053
+ requestLog,
30054
+ hubHost: hubHostFromUrl(hubUrl),
30055
+ ...deps.pathReady ? { pathReady: deps.pathReady } : {}
28998
30056
  });
28999
30057
  dialer.events.on(
29000
30058
  "connected",
@@ -29044,6 +30102,7 @@ function startComputer(config2, deps = {}) {
29044
30102
  dialers: connections.map((c) => c.dialer),
29045
30103
  stop: async () => {
29046
30104
  hostIdStop?.();
30105
+ if (watchLocalRoots) (0, import_node_fs10.unwatchFile)(configFile);
29047
30106
  await Promise.all(connections.map((c) => c.dispose()));
29048
30107
  }
29049
30108
  };
@@ -29085,7 +30144,7 @@ async function maybeInteractiveSetup() {
29085
30144
  default: defaultRoot,
29086
30145
  transform: (raw) => {
29087
30146
  const roots = raw.split(",").map((r) => r.trim()).filter((r) => r.length > 0);
29088
- return roots.length > 0 ? { config: roots, env: roots.join(import_node_path10.delimiter) } : null;
30147
+ return roots.length > 0 ? { config: roots, env: roots.join(import_node_path14.delimiter) } : null;
29089
30148
  }
29090
30149
  },
29091
30150
  {
@@ -29097,7 +30156,7 @@ async function maybeInteractiveSetup() {
29097
30156
  configKey: "computerName",
29098
30157
  envKey: "SLAYZONE_COMPUTER_NAME",
29099
30158
  label: "Computer display name",
29100
- hint: `default: ${(0, import_node_os7.hostname)()}`
30159
+ hint: `default: ${(0, import_node_os8.hostname)()}`
29101
30160
  }
29102
30161
  ]
29103
30162
  });
@@ -29109,7 +30168,8 @@ async function maybeInteractiveSetup() {
29109
30168
  return out;
29110
30169
  }
29111
30170
  async function main() {
29112
- if (process.env.SLAYZONE_SUPERVISED !== "1" && !process.env.SLAYZONE_ROOT) {
30171
+ const supervised = process.env.SLAYZONE_SUPERVISED === "1";
30172
+ if (!supervised && !process.env.SLAYZONE_ROOT) {
29113
30173
  process.env.SLAYZONE_ROOT = process.cwd();
29114
30174
  }
29115
30175
  const prompted = await maybeInteractiveSetup();
@@ -29139,12 +30199,18 @@ async function main() {
29139
30199
  `);
29140
30200
  };
29141
30201
  for (const notice of ignoredCertPinNotices()) log(notice);
29142
- if (process.env.SLAYZONE_SUPERVISED !== "1") {
30202
+ if (!supervised) {
29143
30203
  const { installAgentHooks: installAgentHooks2 } = await Promise.resolve().then(() => (init_agent_hooks2(), agent_hooks_exports));
29144
30204
  await installAgentHooks2(log);
29145
30205
  }
30206
+ const pathReady = adoptLoginShellPath().then(
30207
+ (adopted) => log(
30208
+ adopted ? `PATH: adopted login-shell PATH (+${adopted.added.length} entries)` : "PATH: no login-shell PATH probed \u2014 keeping the inherited one"
30209
+ )
30210
+ );
29146
30211
  const handle = startComputer(config2, {
29147
30212
  log,
30213
+ pathReady,
29148
30214
  onShutdown: () => {
29149
30215
  process.exitCode = 0;
29150
30216
  }
@@ -29157,16 +30223,27 @@ async function main() {
29157
30223
  if (fatal) process.exitCode = 1;
29158
30224
  });
29159
30225
  let stopping = false;
29160
- const gracefulStop = (signal) => {
30226
+ const gracefulStop = (why) => {
29161
30227
  if (stopping) return;
29162
30228
  stopping = true;
29163
- log(`received ${signal}, stopping`);
30229
+ log(why);
30230
+ if (supervised) {
30231
+ setTimeout(() => {
30232
+ log(`still running ${SUPERVISED_STOP_DEADLINE_MS}ms after stop began \u2014 forcing exit`);
30233
+ process.exit(1);
30234
+ }, SUPERVISED_STOP_DEADLINE_MS).unref();
30235
+ }
29164
30236
  void handle.stop().then(() => {
29165
30237
  process.exitCode ??= 0;
29166
30238
  });
29167
30239
  };
29168
- process.on("SIGINT", () => gracefulStop("SIGINT"));
29169
- process.on("SIGTERM", () => gracefulStop("SIGTERM"));
30240
+ process.on("SIGINT", () => gracefulStop("received SIGINT, stopping"));
30241
+ process.on("SIGTERM", () => gracefulStop("received SIGTERM, stopping"));
30242
+ if (supervised) {
30243
+ watchParent({
30244
+ onParentDeath: (reason) => gracefulStop(`parent app is gone (${reason}), stopping`)
30245
+ });
30246
+ }
29170
30247
  }
29171
30248
  void main();
29172
30249
  //# sourceMappingURL=bin.cjs.map