@slayzone/computer 0.36.2 → 0.36.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -4415,26 +4415,26 @@ var require_ignore = __commonJS({
4415
4415
  const index = body.indexOf(SLASH);
4416
4416
  return index < 0 || index === body.length - 1;
4417
4417
  };
4418
- var basenameOf = (path9) => {
4419
- const end = path9.length - 1;
4420
- const index = path9.lastIndexOf(
4418
+ var basenameOf = (path11) => {
4419
+ const end = path11.length - 1;
4420
+ const index = path11.lastIndexOf(
4421
4421
  SLASH,
4422
- path9[end] === SLASH ? end - 1 : end
4422
+ path11[end] === SLASH ? end - 1 : end
4423
4423
  );
4424
- return index < 0 ? path9 : path9.slice(index + 1);
4424
+ return index < 0 ? path11 : path11.slice(index + 1);
4425
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);
4426
+ var parentOf = (path11) => {
4427
+ if (path11.charCodeAt(0) === SLASH_CODE || path11.indexOf(DOUBLE_SLASH) >= 0) {
4428
+ const slices = path11.split(SLASH).filter(Boolean);
4429
4429
  slices.pop();
4430
4430
  return slices.length ? slices.join(SLASH) + SLASH : EMPTY;
4431
4431
  }
4432
- const end = path9.length - 1;
4433
- const cut = path9.lastIndexOf(
4432
+ const end = path11.length - 1;
4433
+ const cut = path11.lastIndexOf(
4434
4434
  SLASH,
4435
- path9.charCodeAt(end) === SLASH_CODE ? end - 1 : end
4435
+ path11.charCodeAt(end) === SLASH_CODE ? end - 1 : end
4436
4436
  );
4437
- return cut < 0 ? EMPTY : path9.slice(0, cut + 1);
4437
+ return cut < 0 ? EMPTY : path11.slice(0, cut + 1);
4438
4438
  };
4439
4439
  var isString2 = (subject) => typeof subject === "string";
4440
4440
  var checkPattern = (pattern) => pattern && isString2(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
@@ -4543,20 +4543,20 @@ var require_ignore = __commonJS({
4543
4543
  // path matching.
4544
4544
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
4545
4545
  // @returns {TestResult} true if a file is ignored
4546
- test(path9, checkUnignored, mode) {
4546
+ test(path11, checkUnignored, mode) {
4547
4547
  let ignored = false;
4548
4548
  let unignored = false;
4549
4549
  let matchedRule;
4550
4550
  const rules = this._rules;
4551
4551
  const { length } = rules;
4552
4552
  const shortcut = this._basenameCount * 2 >= length;
4553
- const basename5 = shortcut ? basenameOf(path9) : path9;
4553
+ const basename5 = shortcut ? basenameOf(path11) : path11;
4554
4554
  for (let index = 0; index < length; index++) {
4555
4555
  const rule = rules[index];
4556
4556
  const { negative } = rule;
4557
4557
  const skip = unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored;
4558
4558
  if (!skip && rule[mode].test(
4559
- shortcut && rule._basenameOnly ? basename5 : path9
4559
+ shortcut && rule._basenameOnly ? basename5 : path11
4560
4560
  )) {
4561
4561
  ignored = !negative;
4562
4562
  unignored = negative;
@@ -4576,17 +4576,17 @@ var require_ignore = __commonJS({
4576
4576
  var throwError = (message, Ctor) => {
4577
4577
  throw new Ctor(message);
4578
4578
  };
4579
- var checkPath = (path9, originalPath, doThrow) => {
4580
- if (!isString2(path9)) {
4579
+ var checkPath = (path11, originalPath, doThrow) => {
4580
+ if (!isString2(path11)) {
4581
4581
  return doThrow(
4582
4582
  `path must be a string, but got \`${originalPath}\``,
4583
4583
  TypeError
4584
4584
  );
4585
4585
  }
4586
- if (!path9) {
4586
+ if (!path11) {
4587
4587
  return doThrow(`path must not be empty`, TypeError);
4588
4588
  }
4589
- if (checkPath.isNotRelative(path9)) {
4589
+ if (checkPath.isNotRelative(path11)) {
4590
4590
  const r = "`path.relative()`d";
4591
4591
  return doThrow(
4592
4592
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -4595,25 +4595,25 @@ var require_ignore = __commonJS({
4595
4595
  }
4596
4596
  return true;
4597
4597
  };
4598
- var isNotRelative = (path9) => {
4599
- const first = path9.charCodeAt(0);
4598
+ var isNotRelative = (path11) => {
4599
+ const first = path11.charCodeAt(0);
4600
4600
  if (first === SLASH_CODE) {
4601
4601
  return true;
4602
4602
  }
4603
4603
  if (first !== DOT_CODE) {
4604
4604
  return false;
4605
4605
  }
4606
- if (path9.length === 1) {
4606
+ if (path11.length === 1) {
4607
4607
  return true;
4608
4608
  }
4609
- const second = path9.charCodeAt(1);
4609
+ const second = path11.charCodeAt(1);
4610
4610
  if (second === SLASH_CODE) {
4611
4611
  return true;
4612
4612
  }
4613
4613
  if (second !== DOT_CODE) {
4614
4614
  return false;
4615
4615
  }
4616
- return path9.length === 2 || path9.charCodeAt(2) === SLASH_CODE;
4616
+ return path11.length === 2 || path11.charCodeAt(2) === SLASH_CODE;
4617
4617
  };
4618
4618
  checkPath.isNotRelative = isNotRelative;
4619
4619
  checkPath.convert = (p) => p;
@@ -4644,56 +4644,56 @@ var require_ignore = __commonJS({
4644
4644
  }
4645
4645
  // @returns {TestResult}
4646
4646
  _test(originalPath, cache, checkUnignored) {
4647
- const path9 = originalPath && checkPath.convert(originalPath);
4647
+ const path11 = originalPath && checkPath.convert(originalPath);
4648
4648
  checkPath(
4649
- path9,
4649
+ path11,
4650
4650
  originalPath,
4651
4651
  this._strictPathCheck ? throwError : RETURN_FALSE
4652
4652
  );
4653
- return this._t(path9, cache, checkUnignored);
4653
+ return this._t(path11, cache, checkUnignored);
4654
4654
  }
4655
- checkIgnore(path9) {
4656
- if (path9.charCodeAt(path9.length - 1) !== SLASH_CODE) {
4657
- return this.test(path9);
4655
+ checkIgnore(path11) {
4656
+ if (path11.charCodeAt(path11.length - 1) !== SLASH_CODE) {
4657
+ return this.test(path11);
4658
4658
  }
4659
- const parentPath = parentOf(path9);
4659
+ const parentPath = parentOf(path11);
4660
4660
  if (parentPath) {
4661
4661
  const parent = this._t(parentPath, this._testCache, true);
4662
4662
  if (parent.ignored) {
4663
4663
  return parent;
4664
4664
  }
4665
4665
  }
4666
- return this._rules.test(path9, false, MODE_CHECK_IGNORE);
4666
+ return this._rules.test(path11, false, MODE_CHECK_IGNORE);
4667
4667
  }
4668
- _t(path9, cache, checkUnignored) {
4669
- if (path9 in cache) {
4670
- return cache[path9];
4668
+ _t(path11, cache, checkUnignored) {
4669
+ if (path11 in cache) {
4670
+ return cache[path11];
4671
4671
  }
4672
- const parentPath = parentOf(path9);
4672
+ const parentPath = parentOf(path11);
4673
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);
4674
+ return cache[path11] = parent && parent.ignored ? parent : this._rules.test(path11, checkUnignored, MODE_IGNORE);
4675
4675
  }
4676
- ignores(path9) {
4677
- return this._test(path9, this._ignoreCache, false).ignored;
4676
+ ignores(path11) {
4677
+ return this._test(path11, this._ignoreCache, false).ignored;
4678
4678
  }
4679
4679
  createFilter() {
4680
- return (path9) => !this.ignores(path9);
4680
+ return (path11) => !this.ignores(path11);
4681
4681
  }
4682
4682
  filter(paths) {
4683
4683
  return makeArray(paths).filter(this.createFilter());
4684
4684
  }
4685
4685
  // @returns {TestResult}
4686
- test(path9) {
4687
- return this._test(path9, this._testCache, true);
4686
+ test(path11) {
4687
+ return this._test(path11, this._testCache, true);
4688
4688
  }
4689
4689
  };
4690
4690
  var factory = (options) => new Ignore(options);
4691
- var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
4691
+ var isPathValid = (path11) => checkPath(path11 && checkPath.convert(path11), path11, RETURN_FALSE);
4692
4692
  var setupWindows = () => {
4693
4693
  const makePosix = (str2) => /^\\\\\?\\/.test(str2) || /["<>|\u0000-\u001F]+/u.test(str2) ? str2 : str2.replace(/\\/g, "/");
4694
4694
  checkPath.convert = makePosix;
4695
4695
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
4696
- checkPath.isNotRelative = (path9) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
4696
+ checkPath.isNotRelative = (path11) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path11) || isNotRelative(path11);
4697
4697
  };
4698
4698
  if (
4699
4699
  // Detect `process` so that it can run in browsers.
@@ -5220,10 +5220,10 @@ var init_agent_hooks = __esm({
5220
5220
  }
5221
5221
  });
5222
5222
 
5223
- // raw-import:/tmp/slz-publish-wt.AbcjgE/packages/shared/hooks/src/notify.sh
5223
+ // raw-import:/tmp/slz-publish-wt.GgutIu/packages/shared/hooks/src/notify.sh
5224
5224
  var notify_default;
5225
5225
  var init_notify = __esm({
5226
- "raw-import:/tmp/slz-publish-wt.AbcjgE/packages/shared/hooks/src/notify.sh"() {
5226
+ "raw-import:/tmp/slz-publish-wt.GgutIu/packages/shared/hooks/src/notify.sh"() {
5227
5227
  notify_default = `#!/bin/sh
5228
5228
  # SlayZone agent lifecycle hook \u2014 BENIGN DUMB FORWARDER.
5229
5229
  #
@@ -5385,10 +5385,10 @@ exit 0
5385
5385
  }
5386
5386
  });
5387
5387
 
5388
- // raw-import:/tmp/slz-publish-wt.AbcjgE/packages/shared/hooks/src/opencode-plugin.js
5388
+ // raw-import:/tmp/slz-publish-wt.GgutIu/packages/shared/hooks/src/opencode-plugin.js
5389
5389
  var opencode_plugin_default;
5390
5390
  var init_opencode_plugin = __esm({
5391
- "raw-import:/tmp/slz-publish-wt.AbcjgE/packages/shared/hooks/src/opencode-plugin.js"() {
5391
+ "raw-import:/tmp/slz-publish-wt.GgutIu/packages/shared/hooks/src/opencode-plugin.js"() {
5392
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";
5393
5393
  }
5394
5394
  });
@@ -5431,8 +5431,8 @@ var init_agent_hooks2 = __esm({
5431
5431
  });
5432
5432
 
5433
5433
  // src/bin.ts
5434
- var import_node_os9 = require("node:os");
5435
- var import_node_path15 = require("node:path");
5434
+ var import_node_os10 = require("node:os");
5435
+ var import_node_path18 = require("node:path");
5436
5436
 
5437
5437
  // ../../shared/platform/src/config-prompt.ts
5438
5438
  var import_promises = require("node:readline/promises");
@@ -5467,14 +5467,14 @@ function coerceComputerConfig(raw) {
5467
5467
  }
5468
5468
  return cfg;
5469
5469
  }
5470
- function readJsonObject(path9) {
5470
+ function readJsonObject(path11) {
5471
5471
  let raw;
5472
5472
  try {
5473
- raw = (0, import_node_fs.readFileSync)(path9, "utf8");
5473
+ raw = (0, import_node_fs.readFileSync)(path11, "utf8");
5474
5474
  } catch (err) {
5475
5475
  if (err.code === "ENOENT") return null;
5476
5476
  process.stderr.write(
5477
- `[slayzone-config] cannot read ${path9}: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5477
+ `[slayzone-config] cannot read ${path11}: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5478
5478
  `
5479
5479
  );
5480
5480
  return null;
@@ -5484,61 +5484,61 @@ function readJsonObject(path9) {
5484
5484
  parsed = JSON.parse(raw);
5485
5485
  } catch (err) {
5486
5486
  process.stderr.write(
5487
- `[slayzone-config] ${path9} is not valid JSON: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5487
+ `[slayzone-config] ${path11} is not valid JSON: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
5488
5488
  `
5489
5489
  );
5490
5490
  return null;
5491
5491
  }
5492
5492
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5493
- process.stderr.write(`[slayzone-config] ${path9} is not a JSON object \u2014 using empty config
5493
+ process.stderr.write(`[slayzone-config] ${path11} is not a JSON object \u2014 using empty config
5494
5494
  `);
5495
5495
  return null;
5496
5496
  }
5497
5497
  return parsed;
5498
5498
  }
5499
- function writeJsonAtomic(path9, data) {
5500
- const dir = (0, import_node_path.dirname)(path9);
5499
+ function writeJsonAtomic(path11, data) {
5500
+ const dir = (0, import_node_path.dirname)(path11);
5501
5501
  (0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
5502
- const tmpPath = `${path9}.tmp-${process.pid}-${Date.now()}`;
5502
+ const tmpPath = `${path11}.tmp-${process.pid}-${Date.now()}`;
5503
5503
  (0, import_node_fs.writeFileSync)(tmpPath, `${JSON.stringify(data, null, 2)}
5504
5504
  `, { mode: 384 });
5505
5505
  try {
5506
- (0, import_node_fs.renameSync)(tmpPath, path9);
5506
+ (0, import_node_fs.renameSync)(tmpPath, path11);
5507
5507
  } catch (err) {
5508
5508
  (0, import_node_fs.rmSync)(tmpPath, { force: true });
5509
5509
  throw err;
5510
5510
  }
5511
5511
  }
5512
- function loadComputerConfigFile(path9 = getComputerConfigFilePath()) {
5513
- const raw = readJsonObject(path9);
5512
+ function loadComputerConfigFile(path11 = getComputerConfigFilePath()) {
5513
+ const raw = readJsonObject(path11);
5514
5514
  if (raw) return coerceComputerConfig(raw);
5515
- const legacy = readJsonObject(legacyConfigFilePathFor(path9));
5515
+ const legacy = readJsonObject(legacyConfigFilePathFor(path11));
5516
5516
  return legacy ? coerceComputerConfig(legacy) : {};
5517
5517
  }
5518
- function ignoredComputerConfigKeys(path9 = getComputerConfigFilePath()) {
5519
- const raw = readJsonObject(path9) ?? readJsonObject(legacyConfigFilePathFor(path9));
5518
+ function ignoredComputerConfigKeys(path11 = getComputerConfigFilePath()) {
5519
+ const raw = readJsonObject(path11) ?? readJsonObject(legacyConfigFilePathFor(path11));
5520
5520
  if (!raw) return [];
5521
5521
  return IGNORED_COMPUTER_CONFIG_KEYS.filter((key) => raw[key] !== void 0);
5522
5522
  }
5523
- function saveComputerConfigFile(cfg, path9 = getComputerConfigFilePath()) {
5524
- writeJsonAtomic(path9, cfg);
5523
+ function saveComputerConfigFile(cfg, path11 = getComputerConfigFilePath()) {
5524
+ writeJsonAtomic(path11, cfg);
5525
5525
  }
5526
- function updateComputerConfigFile(patch, path9 = getComputerConfigFilePath()) {
5527
- const current = readJsonObject(path9);
5526
+ function updateComputerConfigFile(patch, path11 = getComputerConfigFilePath()) {
5527
+ const current = readJsonObject(path11);
5528
5528
  const merged = current ? coerceComputerConfig(current) : {};
5529
5529
  for (const [k, v] of Object.entries(patch)) {
5530
5530
  if (v !== void 0) merged[k] = v;
5531
5531
  }
5532
- saveComputerConfigFile(merged, path9);
5532
+ saveComputerConfigFile(merged, path11);
5533
5533
  return merged;
5534
5534
  }
5535
- function updateJsonFile(patch, path9) {
5536
- const current = readJsonObject(path9) ?? {};
5535
+ function updateJsonFile(patch, path11) {
5536
+ const current = readJsonObject(path11) ?? {};
5537
5537
  const merged = { ...current };
5538
5538
  for (const [k, v] of Object.entries(patch)) {
5539
5539
  if (v !== void 0) merged[k] = v;
5540
5540
  }
5541
- writeJsonAtomic(path9, merged);
5541
+ writeJsonAtomic(path11, merged);
5542
5542
  }
5543
5543
 
5544
5544
  // ../../shared/platform/src/config-prompt.ts
@@ -5610,10 +5610,10 @@ function isLoopbackAuthority(addr) {
5610
5610
  return false;
5611
5611
  }
5612
5612
  }
5613
- function hubUrlFromAddr(addr, kind, path9 = "") {
5613
+ function hubUrlFromAddr(addr, kind, path11 = "") {
5614
5614
  const secure = !isLoopbackAuthority(addr);
5615
5615
  const scheme = kind === "ws" ? secure ? "wss" : "ws" : secure ? "https" : "http";
5616
- return `${scheme}://${addr}${path9}`;
5616
+ return `${scheme}://${addr}${path11}`;
5617
5617
  }
5618
5618
  function hubAddrFromUrl(url2) {
5619
5619
  let u;
@@ -6715,10 +6715,10 @@ function mergeDefs(...defs) {
6715
6715
  function cloneDef(schema) {
6716
6716
  return mergeDefs(schema._zod.def);
6717
6717
  }
6718
- function getElementAtPath(obj, path9) {
6719
- if (!path9)
6718
+ function getElementAtPath(obj, path11) {
6719
+ if (!path11)
6720
6720
  return obj;
6721
- return path9.reduce((acc, key) => acc?.[key], obj);
6721
+ return path11.reduce((acc, key) => acc?.[key], obj);
6722
6722
  }
6723
6723
  function promiseAllObject(promisesObj) {
6724
6724
  const keys = Object.keys(promisesObj);
@@ -7058,11 +7058,11 @@ function explicitlyAborted(x, startIndex = 0) {
7058
7058
  }
7059
7059
  return false;
7060
7060
  }
7061
- function prefixIssues(path9, issues) {
7061
+ function prefixIssues(path11, issues) {
7062
7062
  return issues.map((iss) => {
7063
7063
  var _a3;
7064
7064
  (_a3 = iss).path ?? (_a3.path = []);
7065
- iss.path.unshift(path9);
7065
+ iss.path.unshift(path11);
7066
7066
  return iss;
7067
7067
  });
7068
7068
  }
@@ -7512,16 +7512,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
7512
7512
  }
7513
7513
  function formatError(error62, mapper = (issue2) => issue2.message) {
7514
7514
  const fieldErrors = { _errors: [] };
7515
- const processError = (error63, path9 = []) => {
7515
+ const processError = (error63, path11 = []) => {
7516
7516
  for (const issue2 of error63.issues) {
7517
7517
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7518
- issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
7518
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
7519
7519
  } else if (issue2.code === "invalid_key") {
7520
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7520
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
7521
7521
  } else if (issue2.code === "invalid_element") {
7522
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7522
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
7523
7523
  } else {
7524
- const fullpath = [...path9, ...issue2.path];
7524
+ const fullpath = [...path11, ...issue2.path];
7525
7525
  if (fullpath.length === 0) {
7526
7526
  fieldErrors._errors.push(mapper(issue2));
7527
7527
  } else {
@@ -7560,17 +7560,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
7560
7560
  }
7561
7561
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
7562
7562
  const result = { errors: [] };
7563
- const processError = (error63, path9 = []) => {
7563
+ const processError = (error63, path11 = []) => {
7564
7564
  var _a3;
7565
7565
  for (const issue2 of error63.issues) {
7566
7566
  if (issue2.code === "invalid_union" && issue2.errors.length) {
7567
- issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
7567
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
7568
7568
  } else if (issue2.code === "invalid_key") {
7569
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7569
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
7570
7570
  } else if (issue2.code === "invalid_element") {
7571
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
7571
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
7572
7572
  } else {
7573
- const fullpath = [...path9, ...issue2.path];
7573
+ const fullpath = [...path11, ...issue2.path];
7574
7574
  if (fullpath.length === 0) {
7575
7575
  result.errors.push(mapper(issue2));
7576
7576
  continue;
@@ -7609,8 +7609,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
7609
7609
  }
7610
7610
  function toDotPath(_path) {
7611
7611
  const segs = [];
7612
- const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7613
- for (const seg of path9) {
7612
+ const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
7613
+ for (const seg of path11) {
7614
7614
  if (typeof seg === "number")
7615
7615
  segs.push(`[${seg}]`);
7616
7616
  else if (typeof seg === "symbol")
@@ -7896,8 +7896,8 @@ function emoji() {
7896
7896
  }
7897
7897
  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])$/;
7898
7898
  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}|:))$/;
7899
- var mac = (delimiter3) => {
7900
- const escapedDelim = escapeRegex(delimiter3 ?? ":");
7899
+ var mac = (delimiter4) => {
7900
+ const escapedDelim = escapeRegex(delimiter4 ?? ":");
7901
7901
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
7902
7902
  };
7903
7903
  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])$/;
@@ -22585,14 +22585,14 @@ var nonoptionalProcessor = (schema, ctx, _json, params) => {
22585
22585
  var UNREPRESENTABLE_DEFAULT = /* @__PURE__ */ Symbol();
22586
22586
  function serializeDefaultValue(value, schema, ctx, json2, params) {
22587
22587
  let unrepresentable = false;
22588
- const serialized = JSON.stringify(value, (_, val) => {
22588
+ const serialized2 = JSON.stringify(value, (_, val) => {
22589
22589
  if (typeof val !== "bigint")
22590
22590
  return val;
22591
22591
  unrepresentable = true;
22592
22592
  return null;
22593
22593
  });
22594
22594
  if (!unrepresentable)
22595
- return JSON.parse(serialized);
22595
+ return JSON.parse(serialized2);
22596
22596
  handleUnrepresentable(schema, ctx, json2, params, "BigInt defaults cannot be represented in JSON Schema");
22597
22597
  return UNREPRESENTABLE_DEFAULT;
22598
22598
  }
@@ -24712,13 +24712,13 @@ function resolveRef(ref, ctx) {
24712
24712
  if (!ref.startsWith("#")) {
24713
24713
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
24714
24714
  }
24715
- const path9 = ref.slice(1).split("/").filter(Boolean);
24716
- if (path9.length === 0) {
24715
+ const path11 = ref.slice(1).split("/").filter(Boolean);
24716
+ if (path11.length === 0) {
24717
24717
  return ctx.rootSchema;
24718
24718
  }
24719
24719
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
24720
- if (path9[0] === defsKey) {
24721
- const key = path9[1] === void 0 ? void 0 : decodeJSONPointerSegment(path9[1]);
24720
+ if (path11[0] === defsKey) {
24721
+ const key = path11[1] === void 0 ? void 0 : decodeJSONPointerSegment(path11[1]);
24722
24722
  if (!key || !ctx.defs[key]) {
24723
24723
  throw new Error(`Reference not found: ${ref}`);
24724
24724
  }
@@ -25592,8 +25592,20 @@ var ComputerTransportErrorCodes = {
25592
25592
  * transient — retrying the same request is refused again. The hub audits it as
25593
25593
  * `denied` and the message says what to do on the device.
25594
25594
  */
25595
- refusedByComputer: -32006
25595
+ refusedByComputer: -32006,
25596
+ /**
25597
+ * An agent for a project this computer holds no folder for. Not transient, and
25598
+ * not a policy refusal: the user picks the project's folder on this computer
25599
+ * (or the hub delivers it), then the same spawn works.
25600
+ */
25601
+ projectNotPlaced: -32007
25596
25602
  };
25603
+ function projectNotPlacedError(projectId) {
25604
+ return new RpcError(
25605
+ ComputerTransportErrorCodes.projectNotPlaced,
25606
+ `This computer has no folder for project ${projectId}. Pick the project's folder on this computer, then try again.`
25607
+ );
25608
+ }
25597
25609
  var requestActorSchema = external_exports.object({
25598
25610
  userId: external_exports.string().min(1).max(200).nullable(),
25599
25611
  /** `trpc <path>` / `METHOD /path` the request entered the hub through. */
@@ -25684,13 +25696,19 @@ var procDataParamsSchema = external_exports.object({
25684
25696
  /** Monotonic per-session sequence number for `stdout`. Absent on stderr. */
25685
25697
  seq: external_exports.number().int().nonnegative().optional(),
25686
25698
  data: external_exports.string(),
25687
- stream: external_exports.enum(["stdout", "stderr"]).optional()
25699
+ stream: external_exports.enum(["stdout", "stderr"]).optional(),
25700
+ /** The spawn attempt that produced this output; the hub drops a replaced attempt's. */
25701
+ spawnId: external_exports.string().min(1).optional()
25688
25702
  });
25689
25703
  var procExitParamsSchema = external_exports.object({
25690
25704
  sessionId: external_exports.string().min(1),
25691
25705
  exitCode: external_exports.number().int().nullable(),
25692
25706
  signal: external_exports.string().nullable().optional(),
25693
- reason: external_exports.string().min(1).optional()
25707
+ reason: external_exports.string().min(1).optional(),
25708
+ /** The spawn attempt that exited. */
25709
+ spawnId: external_exports.string().min(1).optional(),
25710
+ /** Why the process could not run (e.g. the OS refused to start it). */
25711
+ error: external_exports.string().optional()
25694
25712
  });
25695
25713
  var fsChangeParamsSchema = external_exports.object({
25696
25714
  watchId: external_exports.string().min(1),
@@ -25717,6 +25735,8 @@ var HubToComputerMethods = {
25717
25735
  ptyPrioritize: "pty.prioritize",
25718
25736
  /** Which of claude's background jobs still have a live process under a session. */
25719
25737
  ptyBackgroundJobs: "pty.backgroundJobs",
25738
+ /** Doctor's facts about this computer: node-pty install, the shell, agent binaries. */
25739
+ ptyDoctor: "pty.doctor",
25720
25740
  // warm pool (pre-warmed agents live ON the computer — see server/warm-*)
25721
25741
  ptyWarmSpawn: "pty.warmSpawn",
25722
25742
  ptyWarmAdopt: "pty.warmAdopt",
@@ -25775,7 +25795,13 @@ var HubToComputerMethods = {
25775
25795
  /** Edit the computer's own path-jail (Settings → Computers). */
25776
25796
  computerSetAllowedRoots: "computer.setAllowedRoots",
25777
25797
  /** Run `gh` on the computer (PR panel). One frame, not one per gh subcommand. */
25778
- ghExec: "gh.exec"
25798
+ ghExec: "gh.exec",
25799
+ /**
25800
+ * Read the active branch of a Claude Code session file on the computer (a
25801
+ * resumed or handed-over conversation's history for the chat timeline). Only
25802
+ * that file: the path is built from the cwd and a uuid, nothing else.
25803
+ */
25804
+ claudeSessionRead: "claudeSession.read"
25779
25805
  };
25780
25806
  var spawnIdField = external_exports.string().min(1).optional();
25781
25807
  var exitedSessionSchema = external_exports.object({
@@ -25852,6 +25878,19 @@ var ptyBackgroundJobsResultSchema = external_exports.object({
25852
25878
  supported: external_exports.boolean(),
25853
25879
  alive: external_exports.array(external_exports.string())
25854
25880
  });
25881
+ var ptyDoctorParamsSchema = external_exports.object({
25882
+ shell: external_exports.string().min(1).max(4096),
25883
+ /** Plain command names only (`claude`, `cursor-agent`), never paths or syntax. */
25884
+ binaries: external_exports.array(
25885
+ external_exports.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/)
25886
+ ).max(16)
25887
+ }).strict();
25888
+ var ptyDoctorResultSchema = external_exports.object({
25889
+ ok: external_exports.literal(true),
25890
+ problems: external_exports.array(external_exports.object({ check: external_exports.string(), detail: external_exports.string(), fix: external_exports.string() })),
25891
+ shellExists: external_exports.boolean(),
25892
+ binaries: external_exports.array(external_exports.object({ binary: external_exports.string(), path: external_exports.string().nullable() }))
25893
+ });
25855
25894
  var ptyResizeParamsSchema = external_exports.object({
25856
25895
  sessionId: external_exports.string().min(1),
25857
25896
  cols: external_exports.number().int().positive(),
@@ -26005,6 +26044,22 @@ var ghExecResultSchema = external_exports.object({
26005
26044
  stdout: external_exports.string(),
26006
26045
  stderr: external_exports.string()
26007
26046
  });
26047
+ var claudeSessionReadParamsSchema = external_exports.object({
26048
+ cwd: external_exports.string().min(1).optional(),
26049
+ projectId: external_exports.string().min(1).optional(),
26050
+ sessionId: external_exports.string().uuid(),
26051
+ /** Newest records to return at most. */
26052
+ maxRecords: external_exports.number().int().positive().max(5e3),
26053
+ /** Their total JSON size at most (large tool results are shortened first). */
26054
+ maxBytes: external_exports.number().int().positive().max(16 * 1024 * 1024)
26055
+ });
26056
+ var claudeSessionReadResultSchema = external_exports.object({
26057
+ /** False: no session file for that id in that cwd's project dir. */
26058
+ found: external_exports.boolean(),
26059
+ records: external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown())),
26060
+ /** Older records of the branch that were not sent (the caps). */
26061
+ omitted: external_exports.number().int().nonnegative()
26062
+ });
26008
26063
  var computerShutdownParamsSchema = external_exports.object({
26009
26064
  reason: external_exports.string().optional(),
26010
26065
  /** Grace period before the computer may hard-exit. */
@@ -26252,7 +26307,9 @@ var procSpawnParamsSchema = external_exports.object({
26252
26307
  */
26253
26308
  shell: external_exports.boolean().optional(),
26254
26309
  cwd: external_exports.string().min(1).optional(),
26255
- env: external_exports.record(external_exports.string(), external_exports.string()).optional()
26310
+ env: external_exports.record(external_exports.string(), external_exports.string()).optional(),
26311
+ /** Agent-start admission lane (chat agents), as for `pty.spawn`. Absent: start at once. */
26312
+ admission: ptySpawnAdmissionSchema.optional()
26256
26313
  });
26257
26314
  var procSpawnResultSchema = external_exports.object({
26258
26315
  /** `null` when the OS never assigned one (immediate spawn failure). The computer
@@ -26262,12 +26319,38 @@ var procSpawnResultSchema = external_exports.object({
26262
26319
  });
26263
26320
  var procKillParamsSchema = external_exports.object({
26264
26321
  sessionId: external_exports.string().min(1),
26265
- signal: external_exports.string().optional()
26322
+ signal: external_exports.string().optional(),
26323
+ /** Only kill this attempt: a kill meant for a replaced process must not hit its successor. */
26324
+ spawnId: spawnIdField
26266
26325
  });
26267
26326
  var procWriteParamsSchema = external_exports.object({
26268
26327
  sessionId: external_exports.string().min(1),
26269
- data: external_exports.string()
26328
+ data: external_exports.string(),
26329
+ /** Only write to this attempt; a replaced process is refused (`stale-spawn`). */
26330
+ spawnId: spawnIdField,
26331
+ /**
26332
+ * Idempotency key: a write the hub retries (its reply was lost) is written once.
26333
+ * The reply is sent only after the bytes reached the process's stdin, and a
26334
+ * write that cannot be delivered (unknown session, replaced attempt, broken pipe)
26335
+ * is refused rather than acked — so `{ok:true}` means delivered.
26336
+ */
26337
+ writeId: external_exports.string().min(1).max(200).optional()
26270
26338
  });
26339
+ var procWriteResultSchema = external_exports.object({
26340
+ ok: external_exports.literal(true),
26341
+ delivered: external_exports.literal(true).optional(),
26342
+ duplicate: external_exports.boolean().optional()
26343
+ });
26344
+ var ProcRefusalReasons = {
26345
+ /** `proc.spawn`: the command is not on the computer's PATH. `data.binary` names it. */
26346
+ binaryNotFound: "binary-not-found",
26347
+ /** `proc.write`: no process under this session id. */
26348
+ unknownSession: "unknown-session",
26349
+ /** `proc.write`: the session now runs a different spawn attempt. */
26350
+ staleSpawn: "stale-spawn",
26351
+ /** `proc.write`: the process's stdin is closed or broken. */
26352
+ stdinClosed: "stdin-closed"
26353
+ };
26271
26354
  var procGetBufferSinceParamsSchema = external_exports.object({
26272
26355
  sessionId: external_exports.string().min(1),
26273
26356
  seq: external_exports.number().int()
@@ -26917,7 +27000,17 @@ var computerConfigSchema = external_exports.object({
26917
27000
  * projects the user adds. No file key and no env var sets it, so nothing a hub
26918
27001
  * can write turns it on.
26919
27002
  */
26920
- hubMayEditRoots: external_exports.boolean().optional()
27003
+ hubMayEditRoots: external_exports.boolean().optional(),
27004
+ /**
27005
+ * A directory whose executables take precedence over PATH for every process this
27006
+ * computer starts — prepended in `buildSpawnEnv`, the one place both the pty and
27007
+ * the proc handler build a child's env. Exists so tests can swap an agent CLI for
27008
+ * a scripted fake (`@slayzone/test-utils/fake-claude`): a dir merely prepended to
27009
+ * the inherited PATH loses, because the computer adopts the login-shell PATH with
27010
+ * its entries FIRST at boot (login-shell-path.ts). Absolute: a relative dir would
27011
+ * resolve against each spawn's cwd. Env channel only (`ENV_VARS.binOverrideDir`).
27012
+ */
27013
+ binOverrideDir: external_exports.string().min(1).refine((dir) => (0, import_node_path3.isAbsolute)(dir), { message: "must be an absolute path" }).optional()
26921
27014
  });
26922
27015
  var ENV_VARS = {
26923
27016
  // Authority ONLY — `host[:port]`, no scheme, no path. The dial scheme
@@ -26936,7 +27029,9 @@ var ENV_VARS = {
26936
27029
  // named its CONSUMER, which is what CLAUDE.md rule 2 forbids. `_TOKEN` suffix +
26937
27030
  // `HUB_` family per rule 3, matching the domain term used everywhere else
26938
27031
  // (`szjt1.`, the `join_tokens` table, mintJoinToken, POST /api/computers/join-token).
26939
- joinToken: "SLAYZONE_HUB_JOIN_TOKEN"
27032
+ joinToken: "SLAYZONE_HUB_JOIN_TOKEN",
27033
+ // Read only by the computer (rule 2), a directory (rule 3). See `binOverrideDir`.
27034
+ binOverrideDir: "SLAYZONE_COMPUTER_BIN_OVERRIDE_DIR"
26940
27035
  // No alias for the pre-rename `SLAYZONE_RUNNER_JOIN_TOKEN`: it never shipped.
26941
27036
  // The published computer betas (0.36.0-beta.2/3) read `SLAYZONE_JOIN_TOKEN`, and
26942
27037
  // the name it was renamed FROM only ever existed on unreleased main, so there is
@@ -26979,8 +27074,8 @@ var protectedPaths = [];
26979
27074
  function setProtectedPaths(paths) {
26980
27075
  protectedPaths = paths.map((p) => realpathBoundary(p));
26981
27076
  }
26982
- function isInside(path9, dir) {
26983
- return path9 === dir || path9.startsWith(dir + import_node_path3.sep);
27077
+ function isInside(path11, dir) {
27078
+ return path11 === dir || path11.startsWith(dir + import_node_path3.sep);
26984
27079
  }
26985
27080
  function isProtectedPath(absolute) {
26986
27081
  return protectedPaths.some((guarded) => isInside(absolute, guarded));
@@ -27055,7 +27150,8 @@ function loadComputerConfig(env = process.env, shared = env === process.env && e
27055
27150
  // <ROOT>/computer.config.json — base under env (spread after). The single config file.
27056
27151
  ...fromShared,
27057
27152
  ...hubUrlFromEnv !== void 0 ? { hubUrl: hubUrlFromEnv } : {},
27058
- ...envJoinToken !== void 0 ? { joinToken: envJoinToken } : {}
27153
+ ...envJoinToken !== void 0 ? { joinToken: envJoinToken } : {},
27154
+ ...env[ENV_VARS.binOverrideDir] !== void 0 ? { binOverrideDir: env[ENV_VARS.binOverrideDir] } : {}
27059
27155
  };
27060
27156
  const result = computerConfigSchema.safeParse(merged);
27061
27157
  if (result.success) {
@@ -27452,6 +27548,11 @@ var ENV_MANIFEST = {
27452
27548
  // overrides below. `infra`: a spawned terminal must never inherit it, or an
27453
27549
  // agent would resolve a different notify.sh than the one wired into its hooks.
27454
27550
  SLAYZONE_MACHINE_DIR: "infra",
27551
+ // Computer-only: a directory whose executables take precedence over PATH for
27552
+ // every process the computer starts (spawn-env.ts). The e2e chat group puts its
27553
+ // fake `claude` there — a dir merely prepended to the inherited PATH loses to the
27554
+ // login-shell PATH the computer adopts at boot. Infra: an agent must not inherit it.
27555
+ SLAYZONE_COMPUTER_BIN_OVERRIDE_DIR: "infra",
27455
27556
  // per-provider config/hook/plugin path overrides (test/sandbox redirects)
27456
27557
  SLAYZONE_CLAUDE_SETTINGS_PATH: "infra",
27457
27558
  SLAYZONE_GEMINI_SETTINGS_PATH: "infra",
@@ -27499,16 +27600,16 @@ var NON_PREFIXED_INFRA = /* @__PURE__ */ new Set([
27499
27600
  function sanitizeSpawnEnv(base) {
27500
27601
  const out = {};
27501
27602
  for (const [key, value] of Object.entries(base)) {
27502
- if (typeof value !== "string") continue;
27503
- if (NON_PREFIXED_INFRA.has(key)) continue;
27504
- if (key.startsWith("SLAYZONE_")) {
27505
- if (ENV_MANIFEST[key] === "global") out[key] = value;
27506
- continue;
27507
- }
27508
- out[key] = value;
27603
+ if (keptInSpawnEnv(key, value)) out[key] = value;
27509
27604
  }
27510
27605
  return out;
27511
27606
  }
27607
+ function keptInSpawnEnv(key, value) {
27608
+ if (typeof value !== "string") return false;
27609
+ if (NON_PREFIXED_INFRA.has(key)) return false;
27610
+ if (key.startsWith("SLAYZONE_")) return ENV_MANIFEST[key] === "global";
27611
+ return true;
27612
+ }
27512
27613
 
27513
27614
  // ../../shared/platform/src/index.ts
27514
27615
  init_fs_utils();
@@ -27569,6 +27670,15 @@ function getShellStartupArgs(shellPath) {
27569
27670
  }
27570
27671
  return [];
27571
27672
  }
27673
+ function quoteForShell(arg) {
27674
+ if ((0, import_os2.platform)() === "win32") {
27675
+ if (arg.length === 0) return '""';
27676
+ if (!/[\s"&|<>^%!]/.test(arg)) return arg;
27677
+ return `"${arg.replace(/"/g, '""')}"`;
27678
+ }
27679
+ if (arg.length === 0) return "''";
27680
+ return `'${arg.replace(/'/g, `'"'"'`)}'`;
27681
+ }
27572
27682
  function buildShellInvocation(command) {
27573
27683
  const shell = resolveUserShell();
27574
27684
  if ((0, import_os2.platform)() === "win32") return { file: shell, args: ["/c", command] };
@@ -27645,6 +27755,16 @@ async function probeLoginShellPath(opts = {}) {
27645
27755
  }
27646
27756
  return null;
27647
27757
  }
27758
+ var FIND_ON_PATH_TIMEOUT_MS = 3e3;
27759
+ function findOnPath(name, env) {
27760
+ const [file2, args] = (0, import_node_os3.platform)() === "win32" ? ["where", [name]] : ["/bin/sh", ["-c", `command -v ${quoteForShell(name)}`]];
27761
+ return new Promise((resolve9) => {
27762
+ (0, import_node_child_process2.execFile)(file2, args, { timeout: FIND_ON_PATH_TIMEOUT_MS, env }, (err, stdout2) => {
27763
+ const found = err ? "" : String(stdout2).trim().split("\n")[0]?.trim();
27764
+ resolve9(found ? found : null);
27765
+ });
27766
+ });
27767
+ }
27648
27768
 
27649
27769
  // ../../shared/platform/src/slz-file-url.ts
27650
27770
  var SLZ_FILE_HOST = "app";
@@ -27664,8 +27784,8 @@ async function adoptLoginShellPath(opts = {}) {
27664
27784
  }
27665
27785
 
27666
27786
  // src/main.ts
27667
- var import_node_fs11 = require("node:fs");
27668
- var import_node_path14 = require("node:path");
27787
+ var import_node_fs14 = require("node:fs");
27788
+ var import_node_path17 = require("node:path");
27669
27789
 
27670
27790
  // src/request-log.ts
27671
27791
  var import_node_fs4 = require("node:fs");
@@ -28465,9 +28585,9 @@ var WORKSPACE_MAX_SEARCH_RESULTS = 2e3;
28465
28585
  function createLocalWorkspaceFs() {
28466
28586
  return refuseOnSwitchedOffHub(createUnguardedLocalWorkspaceFs());
28467
28587
  }
28468
- function refuseOnSwitchedOffHub(fs7) {
28588
+ function refuseOnSwitchedOffHub(fs8) {
28469
28589
  const guarded = {};
28470
- for (const [op, fn] of Object.entries(fs7)) {
28590
+ for (const [op, fn] of Object.entries(fs8)) {
28471
28591
  guarded[op] = (...args) => {
28472
28592
  assertHubLocalExecEnabled({ op: `fs.${op}` });
28473
28593
  return fn(...args);
@@ -28492,8 +28612,8 @@ function createUnguardedLocalWorkspaceFs() {
28492
28612
  sep: import_node_path9.sep
28493
28613
  };
28494
28614
  },
28495
- async listDir(path9, options) {
28496
- const abs = import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(path9));
28615
+ async listDir(path11, options) {
28616
+ const abs = import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(path11));
28497
28617
  const dirents = await (0, import_promises6.readdir)(abs, { withFileTypes: true });
28498
28618
  const entries = [];
28499
28619
  for (const e of dirents) {
@@ -28525,13 +28645,13 @@ function createUnguardedLocalWorkspaceFs() {
28525
28645
  const parent = (0, import_node_path9.dirname)(abs);
28526
28646
  return { path: abs, parent: parent === abs ? null : parent, entries };
28527
28647
  },
28528
- async mkdir(path9) {
28529
- const abs = (0, import_node_path9.resolve)(path9);
28648
+ async mkdir(path11) {
28649
+ const abs = (0, import_node_path9.resolve)(path11);
28530
28650
  await (0, import_promises6.mkdir)(abs, { recursive: true });
28531
28651
  return { path: import_node_fs6.realpathSync.native(abs) };
28532
28652
  },
28533
- pathExists(path9) {
28534
- return (0, import_node_fs6.existsSync)((0, import_node_path9.resolve)(path9));
28653
+ pathExists(path11) {
28654
+ return (0, import_node_fs6.existsSync)((0, import_node_path9.resolve)(path11));
28535
28655
  },
28536
28656
  // A hub has no path-jail — `allowedRoots` is a computer concept. Saying so is
28537
28657
  // better than accepting the edit and discarding it, which would leave the
@@ -28546,12 +28666,12 @@ function createUnguardedLocalWorkspaceFs() {
28546
28666
  // the fork sidecar and tests, neither of which should be minting a new
28547
28667
  // persistent state file for a mapping a real computer owns.
28548
28668
  resolveProjectPath(projectId) {
28549
- const path9 = localProjectPaths.get(projectId);
28550
- if (!path9) return { path: null };
28551
- return { path: path9, exists: (0, import_node_fs6.existsSync)(path9) };
28669
+ const path11 = localProjectPaths.get(projectId);
28670
+ if (!path11) return { path: null };
28671
+ return { path: path11, exists: (0, import_node_fs6.existsSync)(path11) };
28552
28672
  },
28553
- setProjectPath(projectId, path9) {
28554
- const abs = import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(path9));
28673
+ setProjectPath(projectId, path11) {
28674
+ const abs = import_node_fs6.realpathSync.native((0, import_node_path9.resolve)(path11));
28555
28675
  localProjectPaths.set(projectId, abs);
28556
28676
  return { path: abs };
28557
28677
  },
@@ -28591,12 +28711,12 @@ function createUnguardedLocalWorkspaceFs() {
28591
28711
  function createLocalWorkspaceFsAdapters(local = createLocalWorkspaceFs()) {
28592
28712
  return {
28593
28713
  listRoots: async () => local.listRoots(),
28594
- listDir: async (_r, path9, options) => local.listDir(path9, options),
28595
- mkdir: async (_r, path9) => local.mkdir(path9),
28596
- pathExists: async (_r, path9) => local.pathExists(path9),
28714
+ listDir: async (_r, path11, options) => local.listDir(path11, options),
28715
+ mkdir: async (_r, path11) => local.mkdir(path11),
28716
+ pathExists: async (_r, path11) => local.pathExists(path11),
28597
28717
  setAllowedRoots: async (_r, roots) => local.setAllowedRoots(roots),
28598
28718
  resolveProjectPath: async (_r, projectId) => local.resolveProjectPath(projectId),
28599
- setProjectPath: async (_r, projectId, path9) => local.setProjectPath(projectId, path9),
28719
+ setProjectPath: async (_r, projectId, path11) => local.setProjectPath(projectId, path11),
28600
28720
  forgetProjectPath: async (_r, projectId) => local.forgetProjectPath(projectId),
28601
28721
  readDir: async (_r, rootPath, dirPath) => local.readDir(rootPath, dirPath),
28602
28722
  readFile: async (_r, rootPath, filePath, force) => local.readFile(rootPath, filePath, force),
@@ -28686,28 +28806,28 @@ var noop = () => {
28686
28806
  var freezeIfAvailable = (obj) => {
28687
28807
  if (Object.freeze) Object.freeze(obj);
28688
28808
  };
28689
- function createInnerProxy(callback, path9, memo2) {
28809
+ function createInnerProxy(callback, path11, memo2) {
28690
28810
  var _memo$cacheKey;
28691
- const cacheKey = path9.join(".");
28811
+ const cacheKey = path11.join(".");
28692
28812
  (_memo$cacheKey = memo2[cacheKey]) !== null && _memo$cacheKey !== void 0 || (memo2[cacheKey] = new Proxy(noop, {
28693
28813
  get(_obj, key) {
28694
28814
  if (typeof key !== "string" || key === "then") return;
28695
- return createInnerProxy(callback, [...path9, key], memo2);
28815
+ return createInnerProxy(callback, [...path11, key], memo2);
28696
28816
  },
28697
28817
  apply(_1, _2, args) {
28698
- const lastOfPath = path9[path9.length - 1];
28699
- if (lastOfPath === "valueOf" || lastOfPath === "toString" || lastOfPath === "toJSON") return `tRPC.proxy(${path9.slice(0, -1).join(".")})`;
28818
+ const lastOfPath = path11[path11.length - 1];
28819
+ if (lastOfPath === "valueOf" || lastOfPath === "toString" || lastOfPath === "toJSON") return `tRPC.proxy(${path11.slice(0, -1).join(".")})`;
28700
28820
  let opts = {
28701
28821
  args,
28702
- path: path9
28822
+ path: path11
28703
28823
  };
28704
28824
  if (lastOfPath === "call") opts = {
28705
28825
  args: args.length >= 2 ? [args[1]] : [],
28706
- path: path9.slice(0, -1)
28826
+ path: path11.slice(0, -1)
28707
28827
  };
28708
28828
  else if (lastOfPath === "apply") opts = {
28709
28829
  args: args.length >= 2 ? args[1] : [],
28710
- path: path9.slice(0, -1)
28830
+ path: path11.slice(0, -1)
28711
28831
  };
28712
28832
  freezeIfAvailable(opts.args);
28713
28833
  freezeIfAvailable(opts.path);
@@ -28886,12 +29006,12 @@ function createRouterFactory(config2) {
28886
29006
  })
28887
29007
  };
28888
29008
  }
28889
- function step(from, path9 = []) {
29009
+ function step(from, path11 = []) {
28890
29010
  const aggregate = emptyObject();
28891
29011
  for (const [key, item] of Object.entries(from !== null && from !== void 0 ? from : {})) {
28892
29012
  if (isLazy(item)) {
28893
- lazy3[[...path9, key].join(".")] = createLazyLoader({
28894
- path: path9,
29013
+ lazy3[[...path11, key].join(".")] = createLazyLoader({
29014
+ path: path11,
28895
29015
  ref: item,
28896
29016
  key,
28897
29017
  aggregate
@@ -28899,14 +29019,14 @@ function createRouterFactory(config2) {
28899
29019
  continue;
28900
29020
  }
28901
29021
  if (isRouter(item)) {
28902
- aggregate[key] = step(item._def.record, [...path9, key]);
29022
+ aggregate[key] = step(item._def.record, [...path11, key]);
28903
29023
  continue;
28904
29024
  }
28905
29025
  if (!isProcedure(item)) {
28906
- aggregate[key] = step(item, [...path9, key]);
29026
+ aggregate[key] = step(item, [...path11, key]);
28907
29027
  continue;
28908
29028
  }
28909
- const newPath = [...path9, key].join(".");
29029
+ const newPath = [...path11, key].join(".");
28910
29030
  if (procedures[newPath]) throw new Error(`Duplicate key: ${newPath}`);
28911
29031
  procedures[newPath] = item;
28912
29032
  aggregate[key] = item;
@@ -28930,14 +29050,14 @@ function createRouterFactory(config2) {
28930
29050
  function isProcedure(procedureOrRouter) {
28931
29051
  return typeof procedureOrRouter === "function";
28932
29052
  }
28933
- async function getProcedureAtPath(router2, path9) {
29053
+ async function getProcedureAtPath(router2, path11) {
28934
29054
  const { _def } = router2;
28935
- let procedure = _def.procedures[path9];
29055
+ let procedure = _def.procedures[path11];
28936
29056
  while (!procedure) {
28937
- const key = Object.keys(_def.lazy).find((key2) => path9 === key2 || path9.startsWith(key2 + "."));
29057
+ const key = Object.keys(_def.lazy).find((key2) => path11 === key2 || path11.startsWith(key2 + "."));
28938
29058
  if (!key) return null;
28939
29059
  await _def.lazy[key].load();
28940
- procedure = _def.procedures[path9];
29060
+ procedure = _def.procedures[path11];
28941
29061
  }
28942
29062
  return procedure;
28943
29063
  }
@@ -28946,15 +29066,15 @@ function createCallerFactory() {
28946
29066
  const { _def } = router2;
28947
29067
  return function createCaller(ctxOrCallback, opts) {
28948
29068
  return createRecursiveProxy(async (innerOpts) => {
28949
- const { path: path9, args } = innerOpts;
28950
- const fullPath = path9.join(".");
28951
- if (path9.length === 1 && path9[0] === "_def") return _def;
29069
+ const { path: path11, args } = innerOpts;
29070
+ const fullPath = path11.join(".");
29071
+ if (path11.length === 1 && path11[0] === "_def") return _def;
28952
29072
  const procedure = await getProcedureAtPath(router2, fullPath);
28953
29073
  let ctx = void 0;
28954
29074
  try {
28955
29075
  if (!procedure) throw new TRPCError({
28956
29076
  code: "NOT_FOUND",
28957
- message: `No procedure found on path "${path9}"`
29077
+ message: `No procedure found on path "${path11}"`
28958
29078
  });
28959
29079
  ctx = isFunction(ctxOrCallback) ? await Promise.resolve(ctxOrCallback()) : ctxOrCallback;
28960
29080
  return await procedure({
@@ -29558,7 +29678,7 @@ var isURL = (payload) => payload instanceof URL;
29558
29678
 
29559
29679
  // ../../../node_modules/superjson/dist/pathstringifier.js
29560
29680
  var escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/\./g, "\\.");
29561
- var stringifyPath = (path9) => path9.map(String).map(escapeKey).join(".");
29681
+ var stringifyPath = (path11) => path11.map(String).map(escapeKey).join(".");
29562
29682
  var parsePath = (string4, legacyPaths) => {
29563
29683
  const result = [];
29564
29684
  let segment = "";
@@ -29805,26 +29925,26 @@ var getNthKey = (value, n) => {
29805
29925
  }
29806
29926
  return keys.next().value;
29807
29927
  };
29808
- function validatePath(path9) {
29809
- if (includes(path9, "__proto__")) {
29928
+ function validatePath(path11) {
29929
+ if (includes(path11, "__proto__")) {
29810
29930
  throw new Error("__proto__ is not allowed as a property");
29811
29931
  }
29812
- if (includes(path9, "prototype")) {
29932
+ if (includes(path11, "prototype")) {
29813
29933
  throw new Error("prototype is not allowed as a property");
29814
29934
  }
29815
- if (includes(path9, "constructor")) {
29935
+ if (includes(path11, "constructor")) {
29816
29936
  throw new Error("constructor is not allowed as a property");
29817
29937
  }
29818
29938
  }
29819
- var getDeep = (object2, path9) => {
29820
- validatePath(path9);
29821
- for (let i = 0; i < path9.length; i++) {
29822
- const key = path9[i];
29939
+ var getDeep = (object2, path11) => {
29940
+ validatePath(path11);
29941
+ for (let i = 0; i < path11.length; i++) {
29942
+ const key = path11[i];
29823
29943
  if (isSet(object2)) {
29824
29944
  object2 = getNthKey(object2, +key);
29825
29945
  } else if (isMap(object2)) {
29826
29946
  const row = +key;
29827
- const type = +path9[++i] === 0 ? "key" : "value";
29947
+ const type = +path11[++i] === 0 ? "key" : "value";
29828
29948
  const keyOfRow = getNthKey(object2, row);
29829
29949
  switch (type) {
29830
29950
  case "key":
@@ -29840,14 +29960,14 @@ var getDeep = (object2, path9) => {
29840
29960
  }
29841
29961
  return object2;
29842
29962
  };
29843
- var setDeep = (object2, path9, mapper) => {
29844
- validatePath(path9);
29845
- if (path9.length === 0) {
29963
+ var setDeep = (object2, path11, mapper) => {
29964
+ validatePath(path11);
29965
+ if (path11.length === 0) {
29846
29966
  return mapper(object2);
29847
29967
  }
29848
29968
  let parent = object2;
29849
- for (let i = 0; i < path9.length - 1; i++) {
29850
- const key = path9[i];
29969
+ for (let i = 0; i < path11.length - 1; i++) {
29970
+ const key = path11[i];
29851
29971
  if (isArray(parent)) {
29852
29972
  const index = +key;
29853
29973
  parent = parent[index];
@@ -29857,12 +29977,12 @@ var setDeep = (object2, path9, mapper) => {
29857
29977
  const row = +key;
29858
29978
  parent = getNthKey(parent, row);
29859
29979
  } else if (isMap(parent)) {
29860
- const isEnd = i === path9.length - 2;
29980
+ const isEnd = i === path11.length - 2;
29861
29981
  if (isEnd) {
29862
29982
  break;
29863
29983
  }
29864
29984
  const row = +key;
29865
- const type = +path9[++i] === 0 ? "key" : "value";
29985
+ const type = +path11[++i] === 0 ? "key" : "value";
29866
29986
  const keyOfRow = getNthKey(parent, row);
29867
29987
  switch (type) {
29868
29988
  case "key":
@@ -29874,7 +29994,7 @@ var setDeep = (object2, path9, mapper) => {
29874
29994
  }
29875
29995
  }
29876
29996
  }
29877
- const lastKey = path9[path9.length - 1];
29997
+ const lastKey = path11[path11.length - 1];
29878
29998
  if (isArray(parent)) {
29879
29999
  parent[+lastKey] = mapper(parent[+lastKey]);
29880
30000
  } else if (isPlainObject2(parent)) {
@@ -29889,7 +30009,7 @@ var setDeep = (object2, path9, mapper) => {
29889
30009
  }
29890
30010
  }
29891
30011
  if (isMap(parent)) {
29892
- const row = +path9[path9.length - 2];
30012
+ const row = +path11[path11.length - 2];
29893
30013
  const keyToRow = getNthKey(parent, row);
29894
30014
  const type = +lastKey === 0 ? "key" : "value";
29895
30015
  switch (type) {
@@ -29936,16 +30056,16 @@ function traverse(tree, walker2, version2, origin = []) {
29936
30056
  walker2(nodeValue, origin);
29937
30057
  }
29938
30058
  function applyValueAnnotations(plain, annotations, version2, superJson) {
29939
- traverse(annotations, (type, path9) => {
29940
- plain = setDeep(plain, path9, (v) => untransformValue(v, type, superJson));
30059
+ traverse(annotations, (type, path11) => {
30060
+ plain = setDeep(plain, path11, (v) => untransformValue(v, type, superJson));
29941
30061
  }, version2);
29942
30062
  return plain;
29943
30063
  }
29944
30064
  function applyReferentialEqualityAnnotations(plain, annotations, version2) {
29945
30065
  const legacyPaths = enableLegacyPaths(version2);
29946
- function apply(identicalPaths, path9) {
29947
- const object2 = getDeep(plain, parsePath(path9, legacyPaths));
29948
- identicalPaths.map((path10) => parsePath(path10, legacyPaths)).forEach((identicalObjectPath) => {
30066
+ function apply(identicalPaths, path11) {
30067
+ const object2 = getDeep(plain, parsePath(path11, legacyPaths));
30068
+ identicalPaths.map((path12) => parsePath(path12, legacyPaths)).forEach((identicalObjectPath) => {
29949
30069
  plain = setDeep(plain, identicalObjectPath, () => object2);
29950
30070
  });
29951
30071
  }
@@ -29963,12 +30083,12 @@ function applyReferentialEqualityAnnotations(plain, annotations, version2) {
29963
30083
  return plain;
29964
30084
  }
29965
30085
  var isDeep = (object2, superJson) => isPlainObject2(object2) || isArray(object2) || isMap(object2) || isSet(object2) || isError(object2) || isInstanceOfRegisteredClass(object2, superJson);
29966
- function addIdentity(object2, path9, identities) {
30086
+ function addIdentity(object2, path11, identities) {
29967
30087
  const existingSet = identities.get(object2);
29968
30088
  if (existingSet) {
29969
- existingSet.push(path9);
30089
+ existingSet.push(path11);
29970
30090
  } else {
29971
- identities.set(object2, [path9]);
30091
+ identities.set(object2, [path11]);
29972
30092
  }
29973
30093
  }
29974
30094
  function generateReferentialEqualityAnnotations(identitites, dedupe) {
@@ -29979,7 +30099,7 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
29979
30099
  return;
29980
30100
  }
29981
30101
  if (!dedupe) {
29982
- paths = paths.map((path9) => path9.map(String)).sort((a, b) => a.length - b.length);
30102
+ paths = paths.map((path11) => path11.map(String)).sort((a, b) => a.length - b.length);
29983
30103
  }
29984
30104
  const [representativePath, ...identicalPaths] = paths;
29985
30105
  if (representativePath.length === 0) {
@@ -29998,10 +30118,10 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
29998
30118
  return isEmptyObject(result) ? void 0 : result;
29999
30119
  }
30000
30120
  }
30001
- var walker = (object2, identities, superJson, dedupe, path9 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
30121
+ var walker = (object2, identities, superJson, dedupe, path11 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
30002
30122
  const primitive = isPrimitive(object2);
30003
30123
  if (!primitive) {
30004
- addIdentity(object2, path9, identities);
30124
+ addIdentity(object2, path11, identities);
30005
30125
  const seen = seenObjects.get(object2);
30006
30126
  if (seen) {
30007
30127
  return dedupe ? {
@@ -30035,7 +30155,7 @@ var walker = (object2, identities, superJson, dedupe, path9 = [], objectsInThisP
30035
30155
  if (index === "__proto__" || index === "constructor" || index === "prototype") {
30036
30156
  throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
30037
30157
  }
30038
- const recursiveResult = walker(value, identities, superJson, dedupe, [...path9, index], [...objectsInThisPath, object2], seenObjects);
30158
+ const recursiveResult = walker(value, identities, superJson, dedupe, [...path11, index], [...objectsInThisPath, object2], seenObjects);
30039
30159
  transformedValue[index] = recursiveResult.transformedValue;
30040
30160
  if (isArray(recursiveResult.annotations)) {
30041
30161
  innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
@@ -30281,12 +30401,12 @@ function auditAdminDenied(opts) {
30281
30401
  detail: { reason: "not_admin", code: ADMIN_ONLY, ...opts.detail }
30282
30402
  });
30283
30403
  }
30284
- function adminOnlyTrpcError(path9) {
30285
- auditAdminDenied({ targetKind: "procedure", targetId: path9 });
30404
+ function adminOnlyTrpcError(path11) {
30405
+ auditAdminDenied({ targetKind: "procedure", targetId: path11 });
30286
30406
  return new TRPCError({
30287
30407
  code: "FORBIDDEN",
30288
- message: `${path9} needs a hub admin account`,
30289
- cause: new AdminOnlyError(path9)
30408
+ message: `${path11} needs a hub admin account`,
30409
+ cause: new AdminOnlyError(path11)
30290
30410
  });
30291
30411
  }
30292
30412
 
@@ -30339,45 +30459,45 @@ var router = t.router;
30339
30459
  var middleware = t.middleware;
30340
30460
  var mergeRouters2 = t.mergeRouters;
30341
30461
  var securityContext = t.middleware(
30342
- ({ ctx, next, path: path9 }) => runWithSecurityContext(
30343
- { actor: ctx.principal?.userId ?? null, ip: peerAddress(ctx.req), route: `trpc ${path9}` },
30462
+ ({ ctx, next, path: path11 }) => runWithSecurityContext(
30463
+ { actor: ctx.principal?.userId ?? null, ip: peerAddress(ctx.req), route: `trpc ${path11}` },
30344
30464
  () => next()
30345
30465
  )
30346
30466
  );
30347
- function auditDenied(path9, detail) {
30467
+ function auditDenied(path11, detail) {
30348
30468
  securityEvent({
30349
30469
  event: "authz.denied",
30350
30470
  outcome: "denied",
30351
30471
  targetKind: "procedure",
30352
- targetId: path9,
30472
+ targetId: path11,
30353
30473
  detail
30354
30474
  });
30355
30475
  }
30356
- var authGate2 = t.middleware(({ ctx, next, path: path9 }) => {
30476
+ var authGate2 = t.middleware(({ ctx, next, path: path11 }) => {
30357
30477
  if (getAuthGate() && !ctx.principal) {
30358
- auditDenied(path9, { reason: "unauthenticated" });
30478
+ auditDenied(path11, { reason: "unauthenticated" });
30359
30479
  throw new TRPCError({ code: "UNAUTHORIZED", message: "hub requires authentication" });
30360
30480
  }
30361
30481
  return next();
30362
30482
  });
30363
- var scopeGate = t.middleware(({ ctx, next, path: path9 }) => {
30483
+ var scopeGate = t.middleware(({ ctx, next, path: path11 }) => {
30364
30484
  if (!getAuthGate()) return next();
30365
30485
  const scopes = ctx.principal?.scopes ?? [];
30366
- if (!procedurePolicy().scopeAllows(scopes, path9)) {
30367
- auditDenied(path9, { reason: "scope", scopes });
30368
- throw new TRPCError({ code: "FORBIDDEN", message: `insufficient scope for ${path9}` });
30486
+ if (!procedurePolicy().scopeAllows(scopes, path11)) {
30487
+ auditDenied(path11, { reason: "scope", scopes });
30488
+ throw new TRPCError({ code: "FORBIDDEN", message: `insufficient scope for ${path11}` });
30369
30489
  }
30370
30490
  return next();
30371
30491
  });
30372
- var adminGate = t.middleware(({ ctx, next, path: path9 }) => {
30492
+ var adminGate = t.middleware(({ ctx, next, path: path11 }) => {
30373
30493
  if (!getAuthGate()) return next();
30374
- if (!roleAllows(procedurePolicy().adminClassOf(path9), ctx.principal?.role ?? "user")) {
30375
- throw adminOnlyTrpcError(path9);
30494
+ if (!roleAllows(procedurePolicy().adminClassOf(path11), ctx.principal?.role ?? "user")) {
30495
+ throw adminOnlyTrpcError(path11);
30376
30496
  }
30377
30497
  return next();
30378
30498
  });
30379
- var ownerGate = t.middleware(async ({ ctx, next, path: path9, type, getRawInput }) => {
30380
- const filter = getAuthGate() ? await procedurePolicy().ownerCheck({ ctx, path: path9, type, getRawInput }) : null;
30499
+ var ownerGate = t.middleware(async ({ ctx, next, path: path11, type, getRawInput }) => {
30500
+ const filter = getAuthGate() ? await procedurePolicy().ownerCheck({ ctx, path: path11, type, getRawInput }) : null;
30381
30501
  const result = await next();
30382
30502
  if (!filter || !result.ok) return result;
30383
30503
  return { ...result, data: await filter(result.data) };
@@ -30405,8 +30525,8 @@ var unsafePathRefusal = t.middleware(async ({ next }) => {
30405
30525
  return result;
30406
30526
  });
30407
30527
  var publicProcedure = t.procedure.use(securityContext).use(authGate2).use(adminGate).use(scopeGate).use(ownerGate).use(switchRefusal).use(unsafePathRefusal);
30408
- var hubLocalProcedure = publicProcedure.use(({ next, path: path9 }) => {
30409
- assertHubLocalExecEnabled({ op: path9 });
30528
+ var hubLocalProcedure = publicProcedure.use(({ next, path: path11 }) => {
30529
+ assertHubLocalExecEnabled({ op: path11 });
30410
30530
  return next();
30411
30531
  });
30412
30532
  var openProcedure = t.procedure.use(securityContext);
@@ -30496,8 +30616,8 @@ var fileEditorRouter = router({
30496
30616
  * should only offer this when the workspace is co-resident — the caller decides.
30497
30617
  */
30498
30618
  showInFinder: publicProcedure.input(external_exports.object({ rootPath: external_exports.string(), targetPath: external_exports.string() })).mutation(async ({ input: input2 }) => {
30499
- const path9 = await import("node:path");
30500
- const abs = input2.targetPath ? path9.resolve(input2.rootPath, input2.targetPath) : path9.resolve(input2.rootPath);
30619
+ const path11 = await import("node:path");
30620
+ const abs = input2.targetPath ? path11.resolve(input2.rootPath, input2.targetPath) : path11.resolve(input2.rootPath);
30501
30621
  getFileEditorRouterDeps().shellShowItemInFolder(abs);
30502
30622
  }),
30503
30623
  /**
@@ -30549,9 +30669,9 @@ var workspaceRouter = router({
30549
30669
  * surfaced as such rather than rendered as an empty directory.
30550
30670
  */
30551
30671
  listRoots: publicProcedure.input(targetInput).query(async ({ input: input2 }) => {
30552
- const { fs: fs7, resolveComputer, isLocalComputer } = getWorkspaceDeps();
30672
+ const { fs: fs8, resolveComputer, isLocalComputer } = getWorkspaceDeps();
30553
30673
  const computerId = await resolveComputer(input2);
30554
- return { computerId, isLocal: isLocalComputer(computerId), ...await fs7.listRoots(computerId) };
30674
+ return { computerId, isLocal: isLocalComputer(computerId), ...await fs8.listRoots(computerId) };
30555
30675
  }),
30556
30676
  listDir: publicProcedure.input(
30557
30677
  targetInput.extend({
@@ -30561,18 +30681,18 @@ var workspaceRouter = router({
30561
30681
  includeHidden: external_exports.boolean().optional()
30562
30682
  })
30563
30683
  ).query(async ({ input: input2 }) => {
30564
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30684
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30565
30685
  const computerId = await resolveComputer(input2);
30566
- const res = await fs7.listDir(computerId, input2.path, {
30686
+ const res = await fs8.listDir(computerId, input2.path, {
30567
30687
  dirsOnly: input2.dirsOnly,
30568
30688
  includeHidden: input2.includeHidden
30569
30689
  });
30570
30690
  return { computerId, ...res };
30571
30691
  }),
30572
30692
  mkdir: publicProcedure.input(targetInput.extend({ path: external_exports.string().min(1) })).mutation(async ({ input: input2 }) => {
30573
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30693
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30574
30694
  const computerId = await resolveComputer(input2);
30575
- const res = await fs7.mkdir(computerId, input2.path);
30695
+ const res = await fs8.mkdir(computerId, input2.path);
30576
30696
  return { computerId, ...res };
30577
30697
  }),
30578
30698
  /**
@@ -30588,9 +30708,9 @@ var workspaceRouter = router({
30588
30708
  * from the enforced one.
30589
30709
  */
30590
30710
  setAllowedRoots: publicProcedure.input(targetInput.extend({ roots: external_exports.array(external_exports.string().min(1)) })).mutation(async ({ input: input2 }) => {
30591
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30711
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30592
30712
  const computerId = await resolveComputer(input2);
30593
- return { computerId, ...await fs7.setAllowedRoots(computerId, input2.roots) };
30713
+ return { computerId, ...await fs8.setAllowedRoots(computerId, input2.roots) };
30594
30714
  }),
30595
30715
  /**
30596
30716
  * Where does the target machine keep this project?
@@ -30601,13 +30721,13 @@ var workspaceRouter = router({
30601
30721
  * is different again: recorded, but the folder is gone.
30602
30722
  */
30603
30723
  resolveProjectPath: publicProcedure.input(targetInput.extend({ projectId: external_exports.string().min(1) })).query(async ({ input: input2 }) => {
30604
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30724
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30605
30725
  const computerId = await resolveComputer(input2);
30606
30726
  try {
30607
30727
  return {
30608
30728
  computerId,
30609
30729
  reachable: true,
30610
- ...await fs7.resolveProjectPath(computerId, input2.projectId)
30730
+ ...await fs8.resolveProjectPath(computerId, input2.projectId)
30611
30731
  };
30612
30732
  } catch (err) {
30613
30733
  return {
@@ -30626,18 +30746,18 @@ var workspaceRouter = router({
30626
30746
  path: external_exports.string().min(1)
30627
30747
  })
30628
30748
  ).mutation(async ({ input: input2 }) => {
30629
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30749
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30630
30750
  const computerId = await resolveComputer(input2);
30631
30751
  return {
30632
30752
  computerId,
30633
- ...await fs7.setProjectPath(computerId, input2.projectId, input2.path)
30753
+ ...await fs8.setProjectPath(computerId, input2.projectId, input2.path)
30634
30754
  };
30635
30755
  }),
30636
30756
  /** Drop the mapping on that machine. The checkout itself is left alone. */
30637
30757
  forgetProjectPath: publicProcedure.input(targetInput.extend({ projectId: external_exports.string().min(1) })).mutation(async ({ input: input2 }) => {
30638
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30758
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30639
30759
  const computerId = await resolveComputer(input2);
30640
- await fs7.forgetProjectPath(computerId, input2.projectId);
30760
+ await fs8.forgetProjectPath(computerId, input2.projectId);
30641
30761
  return { computerId, ok: true };
30642
30762
  }),
30643
30763
  /**
@@ -30649,10 +30769,10 @@ var workspaceRouter = router({
30649
30769
  * "No repository path configured" for a path that was fine.
30650
30770
  */
30651
30771
  pathExists: publicProcedure.input(targetInput.extend({ path: external_exports.string().min(1) })).query(async ({ input: input2 }) => {
30652
- const { fs: fs7, resolveComputer } = getWorkspaceDeps();
30772
+ const { fs: fs8, resolveComputer } = getWorkspaceDeps();
30653
30773
  const computerId = await resolveComputer(input2);
30654
30774
  try {
30655
- return { computerId, exists: await fs7.pathExists(computerId, input2.path), reachable: true };
30775
+ return { computerId, exists: await fs8.pathExists(computerId, input2.path), reachable: true };
30656
30776
  } catch (err) {
30657
30777
  return {
30658
30778
  computerId,
@@ -30676,13 +30796,13 @@ function createFsHandlers(ctx) {
30676
30796
  };
30677
30797
  const watches = /* @__PURE__ */ new Map();
30678
30798
  function pathExists(rawParams) {
30679
- const { path: path9 } = fsPathExistsParamsSchema.parse(rawParams);
30680
- const resolved = allow(path9);
30799
+ const { path: path11 } = fsPathExistsParamsSchema.parse(rawParams);
30800
+ const resolved = allow(path11);
30681
30801
  return { exists: (0, import_node_fs7.existsSync)(resolved) };
30682
30802
  }
30683
30803
  async function removeDir(rawParams) {
30684
- const { path: path9 } = fsRemoveDirParamsSchema.parse(rawParams);
30685
- const resolved = allow(path9, true);
30804
+ const { path: path11 } = fsRemoveDirParamsSchema.parse(rawParams);
30805
+ const resolved = allow(path11, true);
30686
30806
  await (0, import_promises7.rm)(resolved, { recursive: true, force: true });
30687
30807
  ctx.log("fs removeDir", { path: resolved });
30688
30808
  return { ok: true };
@@ -30745,8 +30865,8 @@ function createFsHandlers(ctx) {
30745
30865
  return { path: abs, parent: isRoot ? null : (0, import_node_path10.dirname)(abs), entries };
30746
30866
  }
30747
30867
  async function mkdirHandler(rawParams) {
30748
- const { path: path9 } = fsMkdirParamsSchema.parse(rawParams);
30749
- const abs = allow(path9);
30868
+ const { path: path11 } = fsMkdirParamsSchema.parse(rawParams);
30869
+ const abs = allow(path11);
30750
30870
  await (0, import_promises7.mkdir)(abs, { recursive: true });
30751
30871
  ctx.log("fs mkdir", { path: abs });
30752
30872
  return { path: import_node_fs7.realpathSync.native(abs) };
@@ -30906,11 +31026,132 @@ function createGhHandlers(ctx) {
30906
31026
  return { [HubToComputerMethods.ghExec]: ghExec };
30907
31027
  }
30908
31028
 
31029
+ // src/handlers/claude-session.ts
31030
+ var import_node_fs8 = require("node:fs");
31031
+ var import_node_os7 = require("node:os");
31032
+ var import_node_path11 = require("node:path");
31033
+ var import_node_readline = require("node:readline");
31034
+ var LARGE_RECORD_BYTES = 32 * 1024;
31035
+ var KEEP_TOOL_TEXT = 8 * 1024;
31036
+ function encodeProjectDir(cwd) {
31037
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
31038
+ }
31039
+ function claudeConfigDir() {
31040
+ return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".claude");
31041
+ }
31042
+ function sessionFile(cwd, sessionId) {
31043
+ const candidates = /* @__PURE__ */ new Set();
31044
+ try {
31045
+ candidates.add((0, import_node_fs8.realpathSync)(cwd));
31046
+ } catch {
31047
+ }
31048
+ candidates.add(cwd);
31049
+ for (const dir of candidates) {
31050
+ const file2 = (0, import_node_path11.join)(claudeConfigDir(), "projects", encodeProjectDir(dir), `${sessionId}.jsonl`);
31051
+ if ((0, import_node_fs8.existsSync)(file2)) return file2;
31052
+ }
31053
+ return null;
31054
+ }
31055
+ async function* records(file2) {
31056
+ const input2 = (0, import_node_fs8.createReadStream)(file2, { encoding: "utf8" });
31057
+ const rl = (0, import_node_readline.createInterface)({ input: input2, crlfDelay: Infinity });
31058
+ try {
31059
+ for await (const line of rl) {
31060
+ const trimmed = line.trim();
31061
+ if (!trimmed) continue;
31062
+ try {
31063
+ const rec = JSON.parse(trimmed);
31064
+ if (rec && typeof rec === "object" && !Array.isArray(rec)) {
31065
+ yield rec;
31066
+ }
31067
+ } catch {
31068
+ }
31069
+ }
31070
+ } finally {
31071
+ rl.close();
31072
+ input2.destroy();
31073
+ }
31074
+ }
31075
+ function isContent(rec) {
31076
+ if (rec.isSidechain === true) return false;
31077
+ if (rec.type === "user" || rec.type === "assistant") return true;
31078
+ return rec.type === "system" && rec.subtype === "compact_boundary";
31079
+ }
31080
+ function shortened(rec) {
31081
+ if (JSON.stringify(rec).length <= LARGE_RECORD_BYTES) return rec;
31082
+ const message = rec.message;
31083
+ if (!message || !Array.isArray(message.content)) return rec;
31084
+ const cut = (text) => text.length > KEEP_TOOL_TEXT ? `${text.slice(0, KEEP_TOOL_TEXT)}
31085
+ \u2026 (shortened)` : text;
31086
+ const content = message.content.map((block) => {
31087
+ const b = block;
31088
+ if (b?.type !== "tool_result") return block;
31089
+ if (typeof b.content === "string") return { ...b, content: cut(b.content) };
31090
+ if (Array.isArray(b.content)) {
31091
+ return {
31092
+ ...b,
31093
+ content: b.content.map((part) => {
31094
+ const p = part;
31095
+ return p?.type === "text" && typeof p.text === "string" ? { ...p, text: cut(p.text) } : part;
31096
+ })
31097
+ };
31098
+ }
31099
+ return block;
31100
+ });
31101
+ const { toolUseResult: _structured, ...rest } = rec;
31102
+ void _structured;
31103
+ return { ...rest, message: { ...message, content } };
31104
+ }
31105
+ function createClaudeSessionHandlers(ctx, getProjectPaths) {
31106
+ async function read(rawParams) {
31107
+ const params = claudeSessionReadParamsSchema.parse(rawParams);
31108
+ const resolved = params.cwd ?? (params.projectId ? getProjectPaths()?.get(params.projectId)?.path : void 0);
31109
+ if (!resolved) throw new Error("claudeSession.read: no cwd for this session");
31110
+ const cwd = assertPathAllowed(resolved, ctx.config.allowedRoots);
31111
+ const file2 = sessionFile(cwd, params.sessionId);
31112
+ if (!file2) return { found: false, records: [], omitted: 0 };
31113
+ const links = /* @__PURE__ */ new Map();
31114
+ let leaf = null;
31115
+ for await (const rec of records(file2)) {
31116
+ if (typeof rec.uuid !== "string") continue;
31117
+ const parent = typeof rec.parentUuid === "string" ? rec.parentUuid : typeof rec.logicalParentUuid === "string" ? rec.logicalParentUuid : null;
31118
+ const content = isContent(rec);
31119
+ links.set(rec.uuid, { parent, content });
31120
+ if (content && rec.type !== "system") leaf = rec.uuid;
31121
+ }
31122
+ const branch = [];
31123
+ const seen = /* @__PURE__ */ new Set();
31124
+ for (let at = leaf; at && !seen.has(at); at = links.get(at)?.parent ?? null) {
31125
+ seen.add(at);
31126
+ if (links.get(at)?.content) branch.push(at);
31127
+ }
31128
+ branch.reverse();
31129
+ const wanted = new Set(branch.slice(-params.maxRecords));
31130
+ const byUuid = /* @__PURE__ */ new Map();
31131
+ for await (const rec of records(file2)) {
31132
+ if (typeof rec.uuid === "string" && wanted.has(rec.uuid)) byUuid.set(rec.uuid, shortened(rec));
31133
+ }
31134
+ const picked = [];
31135
+ let bytes = 0;
31136
+ for (let i = branch.length - 1; i >= 0 && picked.length < params.maxRecords; i--) {
31137
+ const rec = byUuid.get(branch[i]);
31138
+ if (!rec) continue;
31139
+ const size = JSON.stringify(rec).length;
31140
+ if (bytes + size > params.maxBytes) break;
31141
+ bytes += size;
31142
+ picked.push(rec);
31143
+ }
31144
+ picked.reverse();
31145
+ return { found: true, records: picked, omitted: branch.length - picked.length };
31146
+ }
31147
+ return { [HubToComputerMethods.claudeSessionRead]: read };
31148
+ }
31149
+
30909
31150
  // src/handlers/git.ts
30910
31151
  var import_node_child_process7 = require("node:child_process");
30911
- var import_node_fs8 = require("node:fs");
31152
+ var import_node_fs9 = require("node:fs");
30912
31153
  var import_promises8 = require("node:fs/promises");
30913
- var import_node_path11 = require("node:path");
31154
+ var import_node_path12 = require("node:path");
30914
31155
 
30915
31156
  // src/handlers/exec.ts
30916
31157
  var import_node_child_process6 = require("node:child_process");
@@ -30968,9 +31209,9 @@ var SETUP_SCRIPT_TIMEOUT_MS = 5 * 6e4;
30968
31209
  function createGitHandlers(ctx) {
30969
31210
  const allow = (candidate) => assertPathAllowed(candidate, ctx.config.allowedRoots);
30970
31211
  async function isGitRepo(rawParams) {
30971
- const { path: path9 } = isGitRepoParams.parse(rawParams);
30972
- const repoPath = allow(path9);
30973
- if (!(0, import_node_fs8.existsSync)(repoPath)) return { isGitRepo: false };
31212
+ const { path: path11 } = isGitRepoParams.parse(rawParams);
31213
+ const repoPath = allow(path11);
31214
+ if (!(0, import_node_fs9.existsSync)(repoPath)) return { isGitRepo: false };
30974
31215
  const result = await execCapture("git", ["rev-parse", "--git-dir"], { cwd: repoPath });
30975
31216
  if (result.spawnError) throw new Error(`Could not start git: ${result.spawnError}`);
30976
31217
  return { isGitRepo: result.status === 0 };
@@ -31003,7 +31244,7 @@ function createGitHandlers(ctx) {
31003
31244
  try {
31004
31245
  await execGit(["worktree", "remove", worktreePath, "--force"], repoPath);
31005
31246
  } catch (err) {
31006
- if (!(0, import_node_fs8.existsSync)(worktreePath)) {
31247
+ if (!(0, import_node_fs9.existsSync)(worktreePath)) {
31007
31248
  await execGit(["worktree", "prune"], repoPath);
31008
31249
  } else {
31009
31250
  throw err;
@@ -31031,13 +31272,13 @@ function createGitHandlers(ctx) {
31031
31272
  const params = runWorktreeSetupScriptParams.parse(rawParams);
31032
31273
  const worktreePath = allow(params.worktreePath);
31033
31274
  const repoPath = allow(params.repoPath);
31034
- const scriptPath = (0, import_node_path11.resolve)(worktreePath, SETUP_SCRIPT);
31035
- if (!(0, import_node_fs8.existsSync)(scriptPath)) return Promise.resolve({ ran: false });
31275
+ const scriptPath = (0, import_node_path12.resolve)(worktreePath, SETUP_SCRIPT);
31276
+ if (!(0, import_node_fs9.existsSync)(scriptPath)) return Promise.resolve({ ran: false });
31036
31277
  try {
31037
- (0, import_node_fs8.accessSync)(scriptPath, import_node_fs8.constants.X_OK);
31278
+ (0, import_node_fs9.accessSync)(scriptPath, import_node_fs9.constants.X_OK);
31038
31279
  } catch {
31039
31280
  try {
31040
- (0, import_node_fs8.chmodSync)(scriptPath, 493);
31281
+ (0, import_node_fs9.chmodSync)(scriptPath, 493);
31041
31282
  } catch {
31042
31283
  return Promise.resolve({ ran: false });
31043
31284
  }
@@ -31088,19 +31329,19 @@ function createGitHandlers(ctx) {
31088
31329
  } else {
31089
31330
  relPaths = params.customPaths ?? [];
31090
31331
  }
31091
- const repoRoot = (0, import_node_path11.resolve)(repoPath);
31092
- const wtRoot = (0, import_node_path11.resolve)(worktreePath);
31332
+ const repoRoot = (0, import_node_path12.resolve)(repoPath);
31333
+ const wtRoot = (0, import_node_path12.resolve)(worktreePath);
31093
31334
  let copied = 0;
31094
31335
  for (const rel of relPaths) {
31095
- const src = (0, import_node_path11.resolve)(repoPath, rel);
31096
- const dest = (0, import_node_path11.resolve)(worktreePath, rel);
31336
+ const src = (0, import_node_path12.resolve)(repoPath, rel);
31337
+ const dest = (0, import_node_path12.resolve)(worktreePath, rel);
31097
31338
  if (!isInside2(src, repoRoot) || !isInside2(dest, wtRoot)) {
31098
31339
  ctx.log("copyIgnoredFiles skipped traversal", { rel });
31099
31340
  continue;
31100
31341
  }
31101
- if (!(0, import_node_fs8.existsSync)(src)) continue;
31342
+ if (!(0, import_node_fs9.existsSync)(src)) continue;
31102
31343
  try {
31103
- await (0, import_promises8.mkdir)((0, import_node_path11.dirname)(dest), { recursive: true });
31344
+ await (0, import_promises8.mkdir)((0, import_node_path12.dirname)(dest), { recursive: true });
31104
31345
  await (0, import_promises8.cp)(src, dest, { recursive: true, verbatimSymlinks: true });
31105
31346
  copied += 1;
31106
31347
  } catch (err) {
@@ -31119,12 +31360,13 @@ function createGitHandlers(ctx) {
31119
31360
  };
31120
31361
  }
31121
31362
  function isInside2(child, root) {
31122
- const c = (0, import_node_path11.resolve)(child);
31123
- return c === root || c.startsWith(root + import_node_path11.sep);
31363
+ const c = (0, import_node_path12.resolve)(child);
31364
+ return c === root || c.startsWith(root + import_node_path12.sep);
31124
31365
  }
31125
31366
 
31126
31367
  // src/handlers/proc.ts
31127
31368
  var import_node_child_process8 = require("node:child_process");
31369
+ var import_node_string_decoder = require("node:string_decoder");
31128
31370
 
31129
31371
  // src/exit-tombstones.ts
31130
31372
  var TOMBSTONE_TTL_MS = 10 * 6e4;
@@ -31223,6 +31465,153 @@ var RingBuffer = class {
31223
31465
  }
31224
31466
  };
31225
31467
 
31468
+ // src/spawn-admission.ts
31469
+ var import_node_os8 = require("node:os");
31470
+ var DEFAULT_START_CAP_MS = 5e3;
31471
+ var DEFAULT_WARM_QUIET_MS = 3e3;
31472
+ function defaultAdmissionConcurrency(cores = (0, import_node_os8.availableParallelism)()) {
31473
+ return Math.max(2, Math.min(4, Math.floor(cores / 2)));
31474
+ }
31475
+ var LANE_RANK = { foreground: 0, background: 1, warm: 2 };
31476
+ function createSpawnAdmission(opts = {}) {
31477
+ const concurrency = Math.max(1, opts.concurrency ?? defaultAdmissionConcurrency());
31478
+ const startCapMs = opts.startCapMs ?? DEFAULT_START_CAP_MS;
31479
+ const warmQuietMs = opts.warmQuietMs ?? DEFAULT_WARM_QUIET_MS;
31480
+ const now = opts.now ?? Date.now;
31481
+ const setTimer = opts.setTimer ?? ((fn, ms) => {
31482
+ const t2 = setTimeout(fn, ms);
31483
+ t2.unref?.();
31484
+ return t2;
31485
+ });
31486
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
31487
+ const queue = [];
31488
+ const active = /* @__PURE__ */ new Set();
31489
+ let arrivals = 0;
31490
+ let promotions = 0;
31491
+ let lastAgentActivity = Number.NEGATIVE_INFINITY;
31492
+ let warmRetry = null;
31493
+ let pumping = false;
31494
+ let pumpAgain = false;
31495
+ const activeAgents = () => [...active].filter((s) => !s.warm).length;
31496
+ const activeWarm = () => [...active].filter((s) => s.warm).length;
31497
+ function next() {
31498
+ if (active.size >= concurrency || queue.length === 0) return null;
31499
+ let best = null;
31500
+ for (const t2 of queue) {
31501
+ if (!best || LANE_RANK[t2.lane] < LANE_RANK[best.lane] || t2.lane === best.lane && t2.order < best.order) {
31502
+ best = t2;
31503
+ }
31504
+ }
31505
+ if (!best || best.lane !== "warm") return best;
31506
+ if (activeAgents() > 0 || activeWarm() > 0) return null;
31507
+ const quietLeft = lastAgentActivity + warmQuietMs - now();
31508
+ if (quietLeft > 0) {
31509
+ if (warmRetry === null) {
31510
+ warmRetry = setTimer(() => {
31511
+ warmRetry = null;
31512
+ pump();
31513
+ }, quietLeft);
31514
+ }
31515
+ return null;
31516
+ }
31517
+ return best;
31518
+ }
31519
+ function admit(ticket) {
31520
+ queue.splice(queue.indexOf(ticket), 1);
31521
+ const warm = ticket.lane === "warm";
31522
+ const held = { warm, released: false, cap: null };
31523
+ const release = () => {
31524
+ if (held.released) return;
31525
+ held.released = true;
31526
+ clearTimer(held.cap);
31527
+ active.delete(held);
31528
+ if (!warm) lastAgentActivity = now();
31529
+ pump();
31530
+ };
31531
+ held.cap = setTimer(release, startCapMs);
31532
+ active.add(held);
31533
+ try {
31534
+ ticket.start({ release });
31535
+ } catch {
31536
+ release();
31537
+ }
31538
+ }
31539
+ function pump() {
31540
+ if (pumping) {
31541
+ pumpAgain = true;
31542
+ return;
31543
+ }
31544
+ pumping = true;
31545
+ try {
31546
+ do {
31547
+ pumpAgain = false;
31548
+ for (let t2 = next(); t2; t2 = next()) admit(t2);
31549
+ } while (pumpAgain);
31550
+ } finally {
31551
+ pumping = false;
31552
+ }
31553
+ }
31554
+ return {
31555
+ enqueue(lane, start) {
31556
+ const ticket = { lane, order: ++arrivals, start };
31557
+ queue.push(ticket);
31558
+ if (lane !== "warm") lastAgentActivity = now();
31559
+ pump();
31560
+ return ticket;
31561
+ },
31562
+ cancel(ticket) {
31563
+ const i = queue.indexOf(ticket);
31564
+ if (i < 0) return false;
31565
+ queue.splice(i, 1);
31566
+ pump();
31567
+ return true;
31568
+ },
31569
+ prioritize(ticket) {
31570
+ const t2 = ticket;
31571
+ if (!queue.includes(t2)) return false;
31572
+ t2.lane = "foreground";
31573
+ t2.order = -++promotions;
31574
+ pump();
31575
+ return true;
31576
+ },
31577
+ isQueued(ticket) {
31578
+ return queue.includes(ticket);
31579
+ },
31580
+ stats() {
31581
+ return { queued: queue.length, active: active.size, concurrency };
31582
+ }
31583
+ };
31584
+ }
31585
+
31586
+ // src/handlers/resolve-command.ts
31587
+ var import_node_fs10 = require("node:fs");
31588
+ var import_node_path13 = require("node:path");
31589
+ function isExecutableFile(file2) {
31590
+ try {
31591
+ if (!(0, import_node_fs10.statSync)(file2).isFile()) return false;
31592
+ if (process.platform !== "win32") (0, import_node_fs10.accessSync)(file2, import_node_fs10.constants.X_OK);
31593
+ return true;
31594
+ } catch {
31595
+ return false;
31596
+ }
31597
+ }
31598
+ function resolveCommand(command, env, cwd) {
31599
+ const suffixes = process.platform === "win32" ? ["", ...(env.PATHEXT ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";")] : [""];
31600
+ const hasDir = command.includes("/") || process.platform === "win32" && command.includes("\\");
31601
+ if (hasDir) {
31602
+ const base = (0, import_node_path13.isAbsolute)(command) ? command : (0, import_node_path13.join)(cwd ?? process.cwd(), command);
31603
+ return suffixes.map((s) => base + s).find(isExecutableFile) ?? null;
31604
+ }
31605
+ for (const dir of (env.PATH ?? env.Path ?? "").split(import_node_path13.delimiter)) {
31606
+ if (!dir) continue;
31607
+ for (const suffix of suffixes) {
31608
+ const candidate = (0, import_node_path13.join)(dir, command + suffix);
31609
+ if (isExecutableFile(candidate)) return candidate;
31610
+ }
31611
+ }
31612
+ return null;
31613
+ }
31614
+
31226
31615
  // src/handlers/spawn-env.ts
31227
31616
  function buildSpawnEnv(ctx, overrides) {
31228
31617
  const merged = overrides ? { ...sanitizeSpawnEnv(process.env), ...overrides } : sanitizeSpawnEnv(process.env);
@@ -31231,6 +31620,9 @@ function buildSpawnEnv(ctx, overrides) {
31231
31620
  delete merged.SLAYZONE_HUB_TOKEN;
31232
31621
  }
31233
31622
  if (ctx.hubAddress) merged.SLAYZONE_HUB_ADDRESS = ctx.hubAddress;
31623
+ if (ctx.config.binOverrideDir) {
31624
+ merged.PATH = mergePathLists(ctx.config.binOverrideDir, merged.PATH);
31625
+ }
31234
31626
  return merged;
31235
31627
  }
31236
31628
 
@@ -31247,14 +31639,67 @@ var ProcNotifications = {
31247
31639
  procData: ComputerNotificationMethods.procData,
31248
31640
  procExit: ComputerNotificationMethods.procExit
31249
31641
  };
31250
- function createProcHandlers(ctx, getProjectPaths) {
31642
+ var WRITE_ID_MEMORY = 256;
31643
+ function refuse2(reason, message, data = {}) {
31644
+ return new RpcError(ComputerTransportErrorCodes.refusedByComputer, message, {
31645
+ reason,
31646
+ ...data
31647
+ });
31648
+ }
31649
+ function createProcHandlers(ctx, getProjectPaths, options = {}) {
31251
31650
  const procs = /* @__PURE__ */ new Map();
31252
31651
  const tombstones = new ExitTombstones();
31652
+ const admission = options.admission ?? createSpawnAdmission();
31653
+ const queued = /* @__PURE__ */ new Map();
31253
31654
  function killTree(child, signal = "SIGTERM") {
31254
31655
  if (typeof child.pid === "number") killPidTree(child.pid, signal);
31255
31656
  }
31657
+ function releaseSlot(session) {
31658
+ const slot = session.slot;
31659
+ session.slot = null;
31660
+ slot?.release();
31661
+ }
31256
31662
  function procSpawn(rawParams) {
31257
31663
  const params = procSpawnParamsSchema.parse(rawParams);
31664
+ if (!params.admission) return startProc(params, null);
31665
+ const prior = queued.get(params.sessionId);
31666
+ if (prior && params.spawnId !== void 0 && prior.spawnId === params.spawnId) {
31667
+ return prior.promise;
31668
+ }
31669
+ let resolve9;
31670
+ let reject;
31671
+ const promise2 = new Promise((res, rej) => {
31672
+ resolve9 = res;
31673
+ reject = rej;
31674
+ });
31675
+ const item = {
31676
+ ...params.spawnId !== void 0 ? { spawnId: params.spawnId } : {},
31677
+ ticket: void 0,
31678
+ promise: promise2,
31679
+ resolve: resolve9,
31680
+ reject
31681
+ };
31682
+ if (prior) {
31683
+ admission.cancel(prior.ticket);
31684
+ prior.resolve(promise2);
31685
+ }
31686
+ queued.set(params.sessionId, item);
31687
+ item.ticket = admission.enqueue(params.admission, (slot) => {
31688
+ if (queued.get(params.sessionId) !== item) {
31689
+ slot.release();
31690
+ return;
31691
+ }
31692
+ queued.delete(params.sessionId);
31693
+ try {
31694
+ item.resolve(startProc(params, slot));
31695
+ } catch (err) {
31696
+ slot.release();
31697
+ item.reject(err);
31698
+ }
31699
+ });
31700
+ return promise2;
31701
+ }
31702
+ function startProc(params, slot) {
31258
31703
  if (params.spawnId !== void 0) {
31259
31704
  const live = procs.get(params.sessionId);
31260
31705
  if (live && live.spawnId === params.spawnId) return { pid: live.child.pid ?? null };
@@ -31264,17 +31709,29 @@ function createProcHandlers(ctx, getProjectPaths) {
31264
31709
  );
31265
31710
  }
31266
31711
  }
31267
- const resolved = params.cwd ?? (params.projectId ? getProjectPaths?.()?.get(params.projectId)?.path : void 0);
31712
+ let resolved = params.cwd || void 0;
31713
+ if (!resolved && params.projectId) {
31714
+ resolved = getProjectPaths?.()?.get(params.projectId)?.path;
31715
+ if (!resolved) throw projectNotPlacedError(params.projectId);
31716
+ }
31268
31717
  const cwd = spawnCwd(resolved, ctx.config.allowedRoots);
31269
31718
  const existing = procs.get(params.sessionId);
31270
31719
  if (existing) {
31271
31720
  killTree(existing.child);
31272
31721
  procs.delete(params.sessionId);
31273
31722
  }
31723
+ const env = buildSpawnEnv(ctx, params.env);
31724
+ if (!params.shell && resolveCommand(params.command, env, cwd) === null) {
31725
+ throw refuse2(
31726
+ ProcRefusalReasons.binaryNotFound,
31727
+ `${params.command} was not found on this computer's PATH`,
31728
+ { binary: params.command }
31729
+ );
31730
+ }
31274
31731
  const invocation = params.shell ? buildShellInvocation(params.command) : { file: params.command, args: params.args ?? [] };
31275
31732
  const child = (0, import_node_child_process8.spawn)(invocation.file, invocation.args, {
31276
31733
  ...cwd ? { cwd } : {},
31277
- env: buildSpawnEnv(ctx, params.env),
31734
+ env,
31278
31735
  // stdin PIPED (was 'ignore') so `proc.write` can reach the child.
31279
31736
  stdio: ["pipe", "pipe", "pipe"],
31280
31737
  // Its own process group, so a kill reaches everything it started (`pnpm dev`
@@ -31285,14 +31742,24 @@ function createProcHandlers(ctx, getProjectPaths) {
31285
31742
  const session = {
31286
31743
  child,
31287
31744
  buffer,
31288
- ...params.spawnId !== void 0 ? { spawnId: params.spawnId } : {}
31745
+ ...params.spawnId !== void 0 ? { spawnId: params.spawnId } : {},
31746
+ stdinBroken: null,
31747
+ writeIds: [],
31748
+ slot
31289
31749
  };
31290
31750
  procs.set(params.sessionId, session);
31751
+ const tag = params.spawnId !== void 0 ? { spawnId: params.spawnId } : {};
31752
+ child.stdin?.on("error", (err) => {
31753
+ session.stdinBroken = err.message;
31754
+ ctx.log("proc stdin error", { sessionId: params.sessionId, error: err.message });
31755
+ });
31291
31756
  const settle2 = (payload) => {
31757
+ releaseSlot(session);
31292
31758
  if (procs.get(params.sessionId) !== session) return;
31293
31759
  procs.delete(params.sessionId);
31294
31760
  const delivered = ctx.dialer.notify(ProcNotifications.procExit, {
31295
31761
  sessionId: params.sessionId,
31762
+ ...tag,
31296
31763
  ...payload
31297
31764
  });
31298
31765
  tombstones.add(params.sessionId, {
@@ -31304,26 +31771,39 @@ function createProcHandlers(ctx, getProjectPaths) {
31304
31771
  ...delivered ? {} : { buffer: session.buffer }
31305
31772
  });
31306
31773
  };
31307
- child.stdout?.on("data", (d) => {
31308
- if (procs.get(params.sessionId) !== session) return;
31309
- const data = d.toString();
31774
+ const stdoutDecoder = new import_node_string_decoder.StringDecoder("utf8");
31775
+ const stderrDecoder = new import_node_string_decoder.StringDecoder("utf8");
31776
+ const emitStdout = (data) => {
31777
+ if (!data || procs.get(params.sessionId) !== session) return;
31310
31778
  const seq = buffer.append(data);
31311
31779
  ctx.dialer.notify(ProcNotifications.procData, {
31312
31780
  sessionId: params.sessionId,
31781
+ ...tag,
31313
31782
  seq,
31314
31783
  stream: "stdout",
31315
31784
  data
31316
31785
  });
31317
- });
31318
- child.stderr?.on("data", (d) => {
31319
- if (procs.get(params.sessionId) !== session) return;
31786
+ };
31787
+ const emitStderr = (data) => {
31788
+ if (!data || procs.get(params.sessionId) !== session) return;
31320
31789
  ctx.dialer.notify(ProcNotifications.procData, {
31321
31790
  sessionId: params.sessionId,
31791
+ ...tag,
31322
31792
  stream: "stderr",
31323
- data: d.toString()
31793
+ data
31324
31794
  });
31795
+ };
31796
+ child.stdout?.on("data", (d) => {
31797
+ releaseSlot(session);
31798
+ emitStdout(stdoutDecoder.write(d));
31799
+ });
31800
+ child.stderr?.on("data", (d) => {
31801
+ releaseSlot(session);
31802
+ emitStderr(stderrDecoder.write(d));
31325
31803
  });
31326
31804
  child.on("close", (code, signal) => {
31805
+ emitStdout(stdoutDecoder.end());
31806
+ emitStderr(stderrDecoder.end());
31327
31807
  settle2({
31328
31808
  exitCode: typeof code === "number" ? code : null,
31329
31809
  signal: signal != null ? String(signal) : null
@@ -31346,6 +31826,10 @@ function createProcHandlers(ctx, getProjectPaths) {
31346
31826
  ctx.log("proc kill on unknown session", { sessionId: params.sessionId });
31347
31827
  return { ok: true };
31348
31828
  }
31829
+ if (params.spawnId !== void 0 && session.spawnId !== params.spawnId) {
31830
+ ctx.log("proc kill for a replaced attempt", { sessionId: params.sessionId });
31831
+ return { ok: true };
31832
+ }
31349
31833
  killTree(session.child, params.signal ?? "SIGTERM");
31350
31834
  return { ok: true };
31351
31835
  }
@@ -31354,14 +31838,55 @@ function createProcHandlers(ctx, getProjectPaths) {
31354
31838
  const session = procs.get(params.sessionId);
31355
31839
  if (!session) {
31356
31840
  ctx.log("proc write on unknown session", { sessionId: params.sessionId });
31357
- return { ok: true };
31841
+ return Promise.reject(
31842
+ refuse2(ProcRefusalReasons.unknownSession, `proc.write: no process ${params.sessionId}`)
31843
+ );
31358
31844
  }
31359
- try {
31360
- session.child.stdin?.write(params.data);
31361
- } catch (err) {
31362
- ctx.log("proc write failed", { sessionId: params.sessionId, error: String(err) });
31845
+ if (params.spawnId !== void 0 && session.spawnId !== params.spawnId) {
31846
+ return Promise.reject(
31847
+ refuse2(
31848
+ ProcRefusalReasons.staleSpawn,
31849
+ `proc.write: ${params.sessionId} now runs a different process`
31850
+ )
31851
+ );
31363
31852
  }
31364
- return { ok: true };
31853
+ if (params.writeId !== void 0 && session.writeIds.includes(params.writeId)) {
31854
+ return Promise.resolve({ ok: true, delivered: true, duplicate: true });
31855
+ }
31856
+ const stdin2 = session.child.stdin;
31857
+ if (!stdin2 || session.stdinBroken !== null || stdin2.destroyed || stdin2.writableEnded) {
31858
+ return Promise.reject(
31859
+ refuse2(
31860
+ ProcRefusalReasons.stdinClosed,
31861
+ `proc.write: ${params.sessionId} stdin is closed${session.stdinBroken ? ` (${session.stdinBroken})` : ""}`
31862
+ )
31863
+ );
31864
+ }
31865
+ if (params.writeId !== void 0) {
31866
+ session.writeIds.push(params.writeId);
31867
+ if (session.writeIds.length > WRITE_ID_MEMORY) session.writeIds.shift();
31868
+ }
31869
+ return new Promise((resolve9, reject) => {
31870
+ const fail = (err) => {
31871
+ if (params.writeId !== void 0) {
31872
+ session.writeIds = session.writeIds.filter((id) => id !== params.writeId);
31873
+ }
31874
+ const message = err instanceof Error ? err.message : String(err);
31875
+ session.stdinBroken ??= message;
31876
+ ctx.log("proc write failed", { sessionId: params.sessionId, error: message });
31877
+ reject(
31878
+ refuse2(ProcRefusalReasons.stdinClosed, `proc.write: ${params.sessionId}: ${message}`)
31879
+ );
31880
+ };
31881
+ try {
31882
+ stdin2.write(
31883
+ params.data,
31884
+ (err) => err ? fail(err) : resolve9({ ok: true, delivered: true })
31885
+ );
31886
+ } catch (err) {
31887
+ fail(err);
31888
+ }
31889
+ });
31365
31890
  }
31366
31891
  function procGetBufferSince(rawParams) {
31367
31892
  const params = procGetBufferSinceParamsSchema.parse(rawParams);
@@ -31383,7 +31908,13 @@ function createProcHandlers(ctx, getProjectPaths) {
31383
31908
  return { sessions, exited: tombstones.list() };
31384
31909
  }
31385
31910
  function disposeAll() {
31911
+ for (const item of queued.values()) {
31912
+ admission.cancel(item.ticket);
31913
+ item.reject(new Error("computer shutting down"));
31914
+ }
31915
+ queued.clear();
31386
31916
  for (const [sessionId, session] of procs) {
31917
+ releaseSlot(session);
31387
31918
  killTree(session.child);
31388
31919
  ctx.log("proc disposed on shutdown", { sessionId });
31389
31920
  }
@@ -31400,7 +31931,7 @@ function createProcHandlers(ctx, getProjectPaths) {
31400
31931
  }
31401
31932
 
31402
31933
  // src/handlers/project.ts
31403
- var import_node_fs9 = require("node:fs");
31934
+ var import_node_fs11 = require("node:fs");
31404
31935
  function createProjectHandlers(ctx, getStore) {
31405
31936
  const allow = (candidate) => assertPathAllowed(candidate, ctx.config.allowedRoots);
31406
31937
  const store = () => {
@@ -31412,11 +31943,11 @@ function createProjectHandlers(ctx, getStore) {
31412
31943
  const { projectId } = projectResolvePathParamsSchema.parse(rawParams);
31413
31944
  const entry = store().get(projectId);
31414
31945
  if (!entry) return { path: null };
31415
- return { path: entry.path, exists: (0, import_node_fs9.existsSync)(entry.path) };
31946
+ return { path: entry.path, exists: (0, import_node_fs11.existsSync)(entry.path) };
31416
31947
  }
31417
31948
  async function setPath(rawParams) {
31418
- const { projectId, path: path9 } = projectSetPathParamsSchema.parse(rawParams);
31419
- const resolved = allow(path9);
31949
+ const { projectId, path: path11 } = projectSetPathParamsSchema.parse(rawParams);
31950
+ const resolved = allow(path11);
31420
31951
  const entry = await store().set(projectId, resolved);
31421
31952
  ctx.log("project path recorded", { projectId, path: entry.path });
31422
31953
  return { path: entry.path };
@@ -31433,7 +31964,7 @@ function createProjectHandlers(ctx, getStore) {
31433
31964
  projects: store().list().map((e) => ({
31434
31965
  projectId: e.projectId,
31435
31966
  path: e.path,
31436
- exists: (0, import_node_fs9.existsSync)(e.path)
31967
+ exists: (0, import_node_fs11.existsSync)(e.path)
31437
31968
  }))
31438
31969
  };
31439
31970
  }
@@ -31446,8 +31977,77 @@ function createProjectHandlers(ctx, getStore) {
31446
31977
  }
31447
31978
 
31448
31979
  // src/handlers/pty.ts
31980
+ var import_node_module = require("node:module");
31981
+ var path8 = __toESM(require("node:path"), 1);
31449
31982
  var pty = __toESM(require("node-pty"), 1);
31450
31983
 
31984
+ // ../../shared/platform/src/node-pty-install.ts
31985
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
31986
+ var import_node_path14 = __toESM(require("node:path"), 1);
31987
+ function pendingBuildsFrom(modulesYaml) {
31988
+ try {
31989
+ const parsed = JSON.parse(modulesYaml);
31990
+ const pending = parsed?.pendingBuilds;
31991
+ return Array.isArray(pending) && pending.every((p) => typeof p === "string") ? pending : null;
31992
+ } catch {
31993
+ return null;
31994
+ }
31995
+ }
31996
+ function spawnHelperCandidates(nodePtyDir2, platform3 = process.platform, arch = process.arch) {
31997
+ if (platform3 === "win32") return [];
31998
+ return ["build/Release", "build/Debug", `prebuilds/${platform3}-${arch}`].map(
31999
+ (dir) => import_node_path14.default.join(nodePtyDir2, dir, "spawn-helper")
32000
+ );
32001
+ }
32002
+ function unpackedAsarPath(file2) {
32003
+ return file2.replace(/app\.asar(?!\.unpacked)/, "app.asar.unpacked").replace(/node_modules\.asar(?!\.unpacked)/, "node_modules.asar.unpacked");
32004
+ }
32005
+ var shellQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
32006
+ function installProblems(state) {
32007
+ const problems = [];
32008
+ if (state.repoRoot !== null && state.pendingBuilds && state.pendingBuilds.length > 0) {
32009
+ problems.push({
32010
+ check: "Install scripts ran",
32011
+ detail: `pnpm skipped the install scripts of: ${state.pendingBuilds.join(", ")}.`,
32012
+ fix: `cd ${shellQuote(state.repoRoot)} && pnpm rebuild --pending`
32013
+ });
32014
+ }
32015
+ const broken = state.helpers.filter((h) => !h.executable).map((h) => h.path);
32016
+ if (broken.length > 0) {
32017
+ problems.push({
32018
+ check: "spawn-helper executable",
32019
+ detail: `${broken.join(", ")} ${broken.length === 1 ? "is" : "are"} not executable, so every terminal fails to start ("posix_spawnp failed").`,
32020
+ fix: `chmod +x ${broken.map(shellQuote).join(" ")}`
32021
+ });
32022
+ }
32023
+ return problems;
32024
+ }
32025
+ function readNodePtyInstall(nodePtyDir2) {
32026
+ const nodeModules = import_node_path14.default.dirname(nodePtyDir2);
32027
+ let pendingBuilds = null;
32028
+ let repoRoot = null;
32029
+ try {
32030
+ pendingBuilds = pendingBuildsFrom(
32031
+ import_node_fs12.default.readFileSync(import_node_path14.default.join(nodeModules, ".modules.yaml"), "utf8")
32032
+ );
32033
+ repoRoot = import_node_path14.default.dirname(nodeModules);
32034
+ } catch {
32035
+ }
32036
+ const helpers = spawnHelperCandidates(nodePtyDir2).map(unpackedAsarPath).filter((p) => import_node_fs12.default.existsSync(p)).map((p) => ({ path: p, executable: isExecutable(p) }));
32037
+ return { repoRoot, pendingBuilds, helpers };
32038
+ }
32039
+ function nodePtyInstallProblems(nodePtyDir2) {
32040
+ return installProblems(readNodePtyInstall(nodePtyDir2));
32041
+ }
32042
+ function isExecutable(file2) {
32043
+ try {
32044
+ import_node_fs12.default.accessSync(file2, import_node_fs12.default.constants.X_OK);
32045
+ return true;
32046
+ } catch {
32047
+ return false;
32048
+ }
32049
+ }
32050
+
31451
32051
  // ../../shared/platform/src/startup-deadline.ts
31452
32052
  function armStartupDeadline({
31453
32053
  timeoutMs,
@@ -31515,7 +32115,7 @@ function matchLiveJobs(jobs, descendants, stdoutByPid) {
31515
32115
  const alive = [];
31516
32116
  for (const job of jobs) {
31517
32117
  const outputFile = `/tasks/${job.id}.output`;
31518
- const byOutput = job.kind === "shell" && [...outputs].some((path9) => path9.endsWith(outputFile));
32118
+ const byOutput = job.kind === "shell" && [...outputs].some((path11) => path11.endsWith(outputFile));
31519
32119
  const evalArg = job.command ? `eval ${claudeEvalArg(job.command)}` : null;
31520
32120
  const byCommand = evalArg !== null && descendants.some((d) => d.args.includes(evalArg));
31521
32121
  if (byOutput || byCommand) alive.push(job.id);
@@ -31529,7 +32129,7 @@ async function readStdoutPaths(pids) {
31529
32129
  await Promise.all(
31530
32130
  pids.map(
31531
32131
  (pid2) => (0, import_promises9.readlink)(`/proc/${pid2}/fd/1`).then(
31532
- (path9) => void out.set(pid2, path9),
32132
+ (path11) => void out.set(pid2, path11),
31533
32133
  () => void 0
31534
32134
  )
31535
32135
  )
@@ -31564,141 +32164,26 @@ async function probeBackgroundJobs(rootPid, jobs) {
31564
32164
  return matchLiveJobs(jobs, descendants, stdoutByPid);
31565
32165
  }
31566
32166
 
31567
- // src/spawn-admission.ts
31568
- var import_node_os7 = require("node:os");
31569
- var DEFAULT_START_CAP_MS = 5e3;
31570
- var DEFAULT_WARM_QUIET_MS = 3e3;
31571
- function defaultAdmissionConcurrency(cores = (0, import_node_os7.availableParallelism)()) {
31572
- return Math.max(2, Math.min(4, Math.floor(cores / 2)));
31573
- }
31574
- var LANE_RANK = { foreground: 0, background: 1, warm: 2 };
31575
- function createSpawnAdmission(opts = {}) {
31576
- const concurrency = Math.max(1, opts.concurrency ?? defaultAdmissionConcurrency());
31577
- const startCapMs = opts.startCapMs ?? DEFAULT_START_CAP_MS;
31578
- const warmQuietMs = opts.warmQuietMs ?? DEFAULT_WARM_QUIET_MS;
31579
- const now = opts.now ?? Date.now;
31580
- const setTimer = opts.setTimer ?? ((fn, ms) => {
31581
- const t2 = setTimeout(fn, ms);
31582
- t2.unref?.();
31583
- return t2;
31584
- });
31585
- const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
31586
- const queue = [];
31587
- const active = /* @__PURE__ */ new Set();
31588
- let arrivals = 0;
31589
- let promotions = 0;
31590
- let lastAgentActivity = Number.NEGATIVE_INFINITY;
31591
- let warmRetry = null;
31592
- let pumping = false;
31593
- let pumpAgain = false;
31594
- const activeAgents = () => [...active].filter((s) => !s.warm).length;
31595
- const activeWarm = () => [...active].filter((s) => s.warm).length;
31596
- function next() {
31597
- if (active.size >= concurrency || queue.length === 0) return null;
31598
- let best = null;
31599
- for (const t2 of queue) {
31600
- if (!best || LANE_RANK[t2.lane] < LANE_RANK[best.lane] || t2.lane === best.lane && t2.order < best.order) {
31601
- best = t2;
31602
- }
31603
- }
31604
- if (!best || best.lane !== "warm") return best;
31605
- if (activeAgents() > 0 || activeWarm() > 0) return null;
31606
- const quietLeft = lastAgentActivity + warmQuietMs - now();
31607
- if (quietLeft > 0) {
31608
- if (warmRetry === null) {
31609
- warmRetry = setTimer(() => {
31610
- warmRetry = null;
31611
- pump();
31612
- }, quietLeft);
31613
- }
31614
- return null;
31615
- }
31616
- return best;
31617
- }
31618
- function admit(ticket) {
31619
- queue.splice(queue.indexOf(ticket), 1);
31620
- const warm = ticket.lane === "warm";
31621
- const held = { warm, released: false, cap: null };
31622
- const release = () => {
31623
- if (held.released) return;
31624
- held.released = true;
31625
- clearTimer(held.cap);
31626
- active.delete(held);
31627
- if (!warm) lastAgentActivity = now();
31628
- pump();
31629
- };
31630
- held.cap = setTimer(release, startCapMs);
31631
- active.add(held);
31632
- try {
31633
- ticket.start({ release });
31634
- } catch {
31635
- release();
31636
- }
31637
- }
31638
- function pump() {
31639
- if (pumping) {
31640
- pumpAgain = true;
31641
- return;
31642
- }
31643
- pumping = true;
31644
- try {
31645
- do {
31646
- pumpAgain = false;
31647
- for (let t2 = next(); t2; t2 = next()) admit(t2);
31648
- } while (pumpAgain);
31649
- } finally {
31650
- pumping = false;
31651
- }
31652
- }
31653
- return {
31654
- enqueue(lane, start) {
31655
- const ticket = { lane, order: ++arrivals, start };
31656
- queue.push(ticket);
31657
- if (lane !== "warm") lastAgentActivity = now();
31658
- pump();
31659
- return ticket;
31660
- },
31661
- cancel(ticket) {
31662
- const i = queue.indexOf(ticket);
31663
- if (i < 0) return false;
31664
- queue.splice(i, 1);
31665
- pump();
31666
- return true;
31667
- },
31668
- prioritize(ticket) {
31669
- const t2 = ticket;
31670
- if (!queue.includes(t2)) return false;
31671
- t2.lane = "foreground";
31672
- t2.order = -++promotions;
31673
- pump();
31674
- return true;
31675
- },
31676
- isQueued(ticket) {
31677
- return queue.includes(ticket);
31678
- },
31679
- stats() {
31680
- return { queued: queue.length, active: active.size, concurrency };
31681
- }
31682
- };
31683
- }
31684
-
31685
32167
  // src/handlers/pty.ts
31686
32168
  var MAX_BUFFER_SIZE2 = 750 * 1024;
31687
32169
  var STARTUP_KILL_REAP_MS = 2e3;
32170
+ function nodePtyDir() {
32171
+ const resolve9 = typeof require === "function" ? require.resolve : (0, import_node_module.createRequire)(process.argv[1] ?? path8.join(process.cwd(), "index.js")).resolve;
32172
+ return path8.dirname(resolve9("node-pty/package.json"));
32173
+ }
32174
+ var installProblemsOnDisk = () => nodePtyInstallProblems(nodePtyDir());
31688
32175
  function createPtyHandlers(ctx, getProjectPaths, options = {}) {
31689
32176
  const sessions = /* @__PURE__ */ new Map();
31690
32177
  const tombstones = new ExitTombstones();
31691
32178
  const admission = options.admission ?? createSpawnAdmission();
32179
+ const spawnPty = options.spawnPty ?? pty.spawn;
32180
+ const installProblems2 = options.nodePtyInstallProblems ?? installProblemsOnDisk;
31692
32181
  const queued = /* @__PURE__ */ new Map();
31693
32182
  function resolveCwd(opts) {
31694
32183
  if (opts.cwd) return spawnCwd(opts.cwd, ctx.config.allowedRoots);
31695
32184
  if (!opts.projectId) throw new Error("pty.spawn needs either cwd or projectId");
31696
32185
  const entry = getProjectPaths?.()?.get(opts.projectId);
31697
- if (!entry) {
31698
- throw new Error(
31699
- `project ${opts.projectId} is not checked out on this computer \u2014 adopt a folder or enable remote git for it`
31700
- );
31701
- }
32186
+ if (!entry) throw projectNotPlacedError(opts.projectId);
31702
32187
  return spawnCwd(entry.path, ctx.config.allowedRoots);
31703
32188
  }
31704
32189
  function spawnSession(id, opts) {
@@ -31713,13 +32198,18 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
31713
32198
  }
31714
32199
  sessions.delete(id);
31715
32200
  }
31716
- const proc = pty.spawn(opts.command, opts.args ?? [], {
31717
- name: "xterm-color",
31718
- cwd,
31719
- env: buildSpawnEnv(ctx, opts.env),
31720
- cols: opts.cols ?? 80,
31721
- rows: opts.rows ?? 24
31722
- });
32201
+ let proc;
32202
+ try {
32203
+ proc = spawnPty(opts.command, opts.args ?? [], {
32204
+ name: "xterm-color",
32205
+ cwd,
32206
+ env: buildSpawnEnv(ctx, opts.env),
32207
+ cols: opts.cols ?? 80,
32208
+ rows: opts.rows ?? 24
32209
+ });
32210
+ } catch (err) {
32211
+ throw explainSpawnFailure(err);
32212
+ }
31723
32213
  const buffer = new RingBuffer(MAX_BUFFER_SIZE2);
31724
32214
  const session = {
31725
32215
  proc,
@@ -31756,6 +32246,18 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
31756
32246
  });
31757
32247
  return session;
31758
32248
  }
32249
+ function explainSpawnFailure(err) {
32250
+ const original = err instanceof Error ? err : new Error(String(err));
32251
+ let problems;
32252
+ try {
32253
+ problems = installProblems2();
32254
+ } catch {
32255
+ return original;
32256
+ }
32257
+ if (problems.length === 0) return original;
32258
+ const why = problems.map((p) => `${p.detail} Fix: ${p.fix}`).join(" ");
32259
+ return new Error(`${original.message} ${why}`, { cause: original });
32260
+ }
31759
32261
  function reap(session, exitCode, signal) {
31760
32262
  if (sessions.get(session.id) !== session) return;
31761
32263
  sessions.delete(session.id);
@@ -31938,6 +32440,20 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
31938
32440
  const alive = await probeBackgroundJobs(pid, params.jobs);
31939
32441
  return alive === null ? { ok: true, supported: false, alive: [] } : { ok: true, supported: true, alive };
31940
32442
  }
32443
+ async function doctor(rawParams) {
32444
+ const params = ptyDoctorParamsSchema.parse(rawParams);
32445
+ await options.pathReady;
32446
+ const env = buildSpawnEnv(ctx);
32447
+ const binaries = await Promise.all(
32448
+ params.binaries.map(async (binary) => ({ binary, path: await findOnPath(binary, env) }))
32449
+ );
32450
+ return {
32451
+ ok: true,
32452
+ problems: installProblems2(),
32453
+ shellExists: shellExists(params.shell),
32454
+ binaries
32455
+ };
32456
+ }
31941
32457
  function dropQueued(reason) {
31942
32458
  for (const id of [...queued.keys()]) cancelQueued(id, reason);
31943
32459
  }
@@ -32117,6 +32633,7 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
32117
32633
  [HubToComputerMethods.ptyList]: list,
32118
32634
  [HubToComputerMethods.ptyPrioritize]: prioritize,
32119
32635
  [HubToComputerMethods.ptyBackgroundJobs]: backgroundJobs,
32636
+ [HubToComputerMethods.ptyDoctor]: doctor,
32120
32637
  [HubToComputerMethods.ptyWarmSpawn]: warmSpawn,
32121
32638
  [HubToComputerMethods.ptyWarmAdopt]: warmAdopt,
32122
32639
  [HubToComputerMethods.ptyWarmKill]: warmKill,
@@ -32126,19 +32643,19 @@ function createPtyHandlers(ctx, getProjectPaths, options = {}) {
32126
32643
  }
32127
32644
 
32128
32645
  // src/handlers/computer-config.ts
32129
- var import_node_fs10 = require("node:fs");
32130
- var import_node_path12 = require("node:path");
32646
+ var import_node_fs13 = require("node:fs");
32647
+ var import_node_path15 = require("node:path");
32131
32648
  function canonicalRoots(roots) {
32132
32649
  const applied = [];
32133
32650
  const rejected = [];
32134
32651
  for (const root of roots) {
32135
- if (!(0, import_node_path12.isAbsolute)(root)) {
32652
+ if (!(0, import_node_path15.isAbsolute)(root)) {
32136
32653
  rejected.push({ path: root, reason: "not an absolute path" });
32137
32654
  continue;
32138
32655
  }
32139
32656
  let real;
32140
32657
  try {
32141
- real = import_node_fs10.realpathSync.native((0, import_node_path12.resolve)(root));
32658
+ real = import_node_fs13.realpathSync.native((0, import_node_path15.resolve)(root));
32142
32659
  } catch {
32143
32660
  rejected.push({ path: root, reason: "does not exist on this computer" });
32144
32661
  continue;
@@ -32183,12 +32700,13 @@ function createComputerConfigHandlers(ctx) {
32183
32700
 
32184
32701
  // src/project-paths.ts
32185
32702
  var import_promises10 = require("node:fs/promises");
32186
- var import_node_os8 = require("node:os");
32187
- var import_node_path13 = require("node:path");
32703
+ var import_node_crypto3 = require("node:crypto");
32704
+ var import_node_os9 = require("node:os");
32705
+ var import_node_path16 = require("node:path");
32188
32706
  function slayzoneRootDir3() {
32189
32707
  if (process.env.SLAYZONE_ROOT) return process.env.SLAYZONE_ROOT;
32190
- const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os8.homedir)();
32191
- return (0, import_node_path13.join)(home, ".slayzone");
32708
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? (0, import_node_os9.homedir)();
32709
+ return (0, import_node_path16.join)(home, ".slayzone");
32192
32710
  }
32193
32711
  var projectPathEntrySchema = external_exports.object({
32194
32712
  /** Absolute, realpath-resolved at write time. */
@@ -32197,7 +32715,7 @@ var projectPathEntrySchema = external_exports.object({
32197
32715
  });
32198
32716
  var projectMapSchema = external_exports.record(external_exports.string(), projectPathEntrySchema);
32199
32717
  function projectsFilePath(baseDir) {
32200
- return (0, import_node_path13.join)(baseDir ?? slayzoneRootDir3(), "computer.projects.json");
32718
+ return (0, import_node_path16.join)(baseDir ?? slayzoneRootDir3(), "computer.projects.json");
32201
32719
  }
32202
32720
  var fileSchema = external_exports.record(external_exports.string(), projectMapSchema);
32203
32721
  async function readFileMap(filePath) {
@@ -32228,8 +32746,8 @@ async function readFileMap(filePath) {
32228
32746
  return fileSchema.parse(out);
32229
32747
  }
32230
32748
  async function writeFileMap(filePath, map2) {
32231
- await (0, import_promises10.mkdir)((0, import_node_path13.dirname)(filePath), { recursive: true, mode: 448 });
32232
- const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
32749
+ await (0, import_promises10.mkdir)((0, import_node_path16.dirname)(filePath), { recursive: true, mode: 448 });
32750
+ const tmpPath = `${filePath}.tmp-${process.pid}-${(0, import_node_crypto3.randomUUID)()}`;
32233
32751
  await (0, import_promises10.writeFile)(tmpPath, `${JSON.stringify(map2, null, 2)}
32234
32752
  `, { mode: 384 });
32235
32753
  try {
@@ -32239,6 +32757,16 @@ async function writeFileMap(filePath, map2) {
32239
32757
  throw err;
32240
32758
  }
32241
32759
  }
32760
+ var writeQueues = /* @__PURE__ */ new Map();
32761
+ function serialized(filePath, write) {
32762
+ const previous = writeQueues.get(filePath) ?? Promise.resolve();
32763
+ const next = previous.then(write, write);
32764
+ writeQueues.set(
32765
+ filePath,
32766
+ next.catch(() => void 0)
32767
+ );
32768
+ return next;
32769
+ }
32242
32770
  async function createProjectPathStore(hubHost, options = {}) {
32243
32771
  if (!hubHost.trim()) throw new Error(`invalid hub host for project map: '${hubHost}'`);
32244
32772
  const filePath = projectsFilePath(options.baseDir);
@@ -32248,21 +32776,25 @@ async function createProjectPathStore(hubHost, options = {}) {
32248
32776
  get(projectId) {
32249
32777
  return cache[projectId] ?? null;
32250
32778
  },
32251
- async set(projectId, path9) {
32252
- const entry = { path: path9, updatedAt: Date.now() };
32253
- const all = await readFileMap(filePath);
32254
- all[hubHost] = { ...all[hubHost] ?? {}, [projectId]: entry };
32255
- await writeFileMap(filePath, all);
32256
- cache = all[hubHost];
32257
- return entry;
32779
+ set(projectId, path11) {
32780
+ return serialized(filePath, async () => {
32781
+ const entry = { path: path11, updatedAt: Date.now() };
32782
+ const all = await readFileMap(filePath);
32783
+ all[hubHost] = { ...all[hubHost] ?? {}, [projectId]: entry };
32784
+ await writeFileMap(filePath, all);
32785
+ cache = all[hubHost];
32786
+ return entry;
32787
+ });
32258
32788
  },
32259
- async forget(projectId) {
32260
- const all = await readFileMap(filePath);
32261
- const forHub = { ...all[hubHost] ?? {} };
32262
- delete forHub[projectId];
32263
- all[hubHost] = forHub;
32264
- await writeFileMap(filePath, all);
32265
- cache = forHub;
32789
+ forget(projectId) {
32790
+ return serialized(filePath, async () => {
32791
+ const all = await readFileMap(filePath);
32792
+ const forHub = { ...all[hubHost] ?? {} };
32793
+ delete forHub[projectId];
32794
+ all[hubHost] = forHub;
32795
+ await writeFileMap(filePath, all);
32796
+ cache = forHub;
32797
+ });
32266
32798
  },
32267
32799
  list() {
32268
32800
  return Object.entries(cache).map(([projectId, entry]) => ({ projectId, ...entry }));
@@ -32282,13 +32814,16 @@ function createHubRequestHandler(deps2) {
32282
32814
  ...deps2.agentHookUrl ? { agentHookUrl: deps2.agentHookUrl } : {},
32283
32815
  ...deps2.hubAddress ? { hubAddress: deps2.hubAddress } : {}
32284
32816
  };
32285
- const pty2 = createPtyHandlers(
32817
+ const pty2 = createPtyHandlers(ctx, deps2.getProjectPaths, {
32818
+ ...deps2.admission ? { admission: deps2.admission } : {},
32819
+ ...deps2.pathReady ? { pathReady: deps2.pathReady } : {}
32820
+ });
32821
+ const proc = createProcHandlers(
32286
32822
  ctx,
32287
32823
  deps2.getProjectPaths,
32288
32824
  deps2.admission ? { admission: deps2.admission } : {}
32289
32825
  );
32290
- const proc = createProcHandlers(ctx, deps2.getProjectPaths);
32291
- const fs7 = createFsHandlers(ctx);
32826
+ const fs8 = createFsHandlers(ctx);
32292
32827
  const shutdownHandler = async (params) => {
32293
32828
  const parsed = computerShutdownParamsSchema.safeParse(params ?? {});
32294
32829
  const reason = parsed.success ? parsed.data.reason ?? "hub-requested" : "hub-requested";
@@ -32300,9 +32835,10 @@ function createHubRequestHandler(deps2) {
32300
32835
  ...pty2.handlers,
32301
32836
  ...proc.handlers,
32302
32837
  ...createGitHandlers(ctx),
32303
- ...fs7.handlers,
32838
+ ...fs8.handlers,
32304
32839
  ...createComputerConfigHandlers(ctx),
32305
32840
  ...createGhHandlers(ctx),
32841
+ ...createClaudeSessionHandlers(ctx, deps2.getProjectPaths ?? (() => null)),
32306
32842
  ...createProjectHandlers(ctx, deps2.getProjectPaths ?? (() => null))
32307
32843
  };
32308
32844
  const hub = deps2.hubHost ?? "unknown";
@@ -32360,7 +32896,7 @@ function createHubRequestHandler(deps2) {
32360
32896
  const dispose = () => {
32361
32897
  pty2.disposeAll();
32362
32898
  proc.disposeAll();
32363
- fs7.disposeAll();
32899
+ fs8.disposeAll();
32364
32900
  };
32365
32901
  const setAgentHookUrl = (url2) => {
32366
32902
  ctx.agentHookUrl = url2;
@@ -32389,14 +32925,14 @@ function startComputer(config2, deps2 = {}) {
32389
32925
  });
32390
32926
  });
32391
32927
  let handle;
32392
- const stateDir = deps2.stateDir ?? (0, import_node_path14.dirname)(getComputerConfigFilePath());
32393
- const configFile = (0, import_node_path14.join)(stateDir, "computer.config.json");
32928
+ const stateDir = deps2.stateDir ?? (0, import_node_path17.dirname)(getComputerConfigFilePath());
32929
+ const configFile = (0, import_node_path17.join)(stateDir, "computer.config.json");
32394
32930
  setProtectedPaths([
32395
32931
  configFile,
32396
32932
  // The legacy file computer.config.json falls back to when absent.
32397
- (0, import_node_path14.join)(stateDir, "config.json"),
32933
+ (0, import_node_path17.join)(stateDir, "config.json"),
32398
32934
  credentialsFilePath(stateDir),
32399
- (0, import_node_path14.join)(stateDir, REQUEST_LOG_DIR_NAME)
32935
+ (0, import_node_path17.join)(stateDir, REQUEST_LOG_DIR_NAME)
32400
32936
  ]);
32401
32937
  const requestLog = createRequestLog({
32402
32938
  stateDir,
@@ -32405,7 +32941,7 @@ function startComputer(config2, deps2 = {}) {
32405
32941
  log("local request log", { file: requestLog.filePath });
32406
32942
  const watchLocalRoots = !config2.hubMayEditRoots;
32407
32943
  if (watchLocalRoots) {
32408
- (0, import_node_fs11.watchFile)(configFile, { interval: 2e3, persistent: false }, () => {
32944
+ (0, import_node_fs14.watchFile)(configFile, { interval: 2e3, persistent: false }, () => {
32409
32945
  const declared = loadComputerConfigFile(configFile).allowedRoots;
32410
32946
  const applied = reloadLocalRoots(config2, declared);
32411
32947
  if (!applied) return;
@@ -32505,7 +33041,7 @@ function startComputer(config2, deps2 = {}) {
32505
33041
  dialers: connections.map((c) => c.dialer),
32506
33042
  stop: async () => {
32507
33043
  hostIdStop?.();
32508
- if (watchLocalRoots) (0, import_node_fs11.unwatchFile)(configFile);
33044
+ if (watchLocalRoots) (0, import_node_fs14.unwatchFile)(configFile);
32509
33045
  await Promise.all(connections.map((c) => c.dispose()));
32510
33046
  }
32511
33047
  };
@@ -32547,7 +33083,7 @@ async function maybeInteractiveSetup() {
32547
33083
  default: defaultRoot,
32548
33084
  transform: (raw) => {
32549
33085
  const roots = raw.split(",").map((r) => r.trim()).filter((r) => r.length > 0);
32550
- return roots.length > 0 ? { config: roots, env: roots.join(import_node_path15.delimiter) } : null;
33086
+ return roots.length > 0 ? { config: roots, env: roots.join(import_node_path18.delimiter) } : null;
32551
33087
  }
32552
33088
  },
32553
33089
  {
@@ -32559,7 +33095,7 @@ async function maybeInteractiveSetup() {
32559
33095
  configKey: "computerName",
32560
33096
  envKey: "SLAYZONE_COMPUTER_NAME",
32561
33097
  label: "Computer display name",
32562
- hint: `default: ${(0, import_node_os9.hostname)()}`
33098
+ hint: `default: ${(0, import_node_os10.hostname)()}`
32563
33099
  }
32564
33100
  ]
32565
33101
  });