perchai-cli 2.4.11 → 2.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/perch.mjs +1885 -456
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -5198,8 +5198,8 @@ var require_xlsx = __commonJS({
5198
5198
  }
5199
5199
  return L.length - R.length;
5200
5200
  }
5201
- function dirname(p) {
5202
- if (p.charAt(p.length - 1) == "/") return p.slice(0, -1).indexOf("/") === -1 ? p : dirname(p.slice(0, -1));
5201
+ function dirname2(p) {
5202
+ if (p.charAt(p.length - 1) == "/") return p.slice(0, -1).indexOf("/") === -1 ? p : dirname2(p.slice(0, -1));
5203
5203
  var c = p.lastIndexOf("/");
5204
5204
  return c === -1 ? p : p.slice(0, c + 1);
5205
5205
  }
@@ -5620,7 +5620,7 @@ var require_xlsx = __commonJS({
5620
5620
  data.push([cfb.FullPaths[i2], cfb.FileIndex[i2]]);
5621
5621
  }
5622
5622
  for (i2 = 0; i2 < data.length; ++i2) {
5623
- var dad = dirname(data[i2][0]);
5623
+ var dad = dirname2(data[i2][0]);
5624
5624
  s = fullPaths[dad];
5625
5625
  if (!s) {
5626
5626
  data.push([dad, {
@@ -5656,13 +5656,13 @@ var require_xlsx = __commonJS({
5656
5656
  elt.size = 0;
5657
5657
  elt.type = 5;
5658
5658
  } else if (nm.slice(-1) == "/") {
5659
- for (j = i2 + 1; j < data.length; ++j) if (dirname(cfb.FullPaths[j]) == nm) break;
5659
+ for (j = i2 + 1; j < data.length; ++j) if (dirname2(cfb.FullPaths[j]) == nm) break;
5660
5660
  elt.C = j >= data.length ? -1 : j;
5661
- for (j = i2 + 1; j < data.length; ++j) if (dirname(cfb.FullPaths[j]) == dirname(nm)) break;
5661
+ for (j = i2 + 1; j < data.length; ++j) if (dirname2(cfb.FullPaths[j]) == dirname2(nm)) break;
5662
5662
  elt.R = j >= data.length ? -1 : j;
5663
5663
  elt.type = 1;
5664
5664
  } else {
5665
- if (dirname(cfb.FullPaths[i2 + 1] || "") == dirname(nm)) elt.R = i2 + 1;
5665
+ if (dirname2(cfb.FullPaths[i2 + 1] || "") == dirname2(nm)) elt.R = i2 + 1;
5666
5666
  elt.type = 2;
5667
5667
  }
5668
5668
  }
@@ -41950,11 +41950,11 @@ var require_html2canvas = __commonJS({
41950
41950
  };
41951
41951
  function __awaiter3(thisArg, _arguments, P, generator) {
41952
41952
  function adopt(value) {
41953
- return value instanceof P ? value : new P(function(resolve4) {
41954
- resolve4(value);
41953
+ return value instanceof P ? value : new P(function(resolve5) {
41954
+ resolve5(value);
41955
41955
  });
41956
41956
  }
41957
- return new (P || (P = Promise))(function(resolve4, reject2) {
41957
+ return new (P || (P = Promise))(function(resolve5, reject2) {
41958
41958
  function fulfilled(value) {
41959
41959
  try {
41960
41960
  step(generator.next(value));
@@ -41970,7 +41970,7 @@ var require_html2canvas = __commonJS({
41970
41970
  }
41971
41971
  }
41972
41972
  function step(result2) {
41973
- result2.done ? resolve4(result2.value) : adopt(result2.value).then(fulfilled, rejected);
41973
+ result2.done ? resolve5(result2.value) : adopt(result2.value).then(fulfilled, rejected);
41974
41974
  }
41975
41975
  step((generator = generator.apply(thisArg, _arguments || [])).next());
41976
41976
  });
@@ -43962,16 +43962,16 @@ var require_html2canvas = __commonJS({
43962
43962
  [width, 0],
43963
43963
  [width, height]
43964
43964
  ];
43965
- return corners.reduce(function(stat, corner) {
43965
+ return corners.reduce(function(stat2, corner) {
43966
43966
  var cx = corner[0], cy = corner[1];
43967
43967
  var d = distance(x - cx, y - cy);
43968
- if (closest ? d < stat.optimumDistance : d > stat.optimumDistance) {
43968
+ if (closest ? d < stat2.optimumDistance : d > stat2.optimumDistance) {
43969
43969
  return {
43970
43970
  optimumCorner: corner,
43971
43971
  optimumDistance: d
43972
43972
  };
43973
43973
  }
43974
- return stat;
43974
+ return stat2;
43975
43975
  }, {
43976
43976
  optimumDistance: closest ? Infinity : -Infinity,
43977
43977
  optimumCorner: null
@@ -46099,10 +46099,10 @@ var require_html2canvas = __commonJS({
46099
46099
  return svg;
46100
46100
  };
46101
46101
  var loadSerializedSVG$1 = function(svg) {
46102
- return new Promise(function(resolve4, reject2) {
46102
+ return new Promise(function(resolve5, reject2) {
46103
46103
  var img = new Image();
46104
46104
  img.onload = function() {
46105
- return resolve4(img);
46105
+ return resolve5(img);
46106
46106
  };
46107
46107
  img.onerror = reject2;
46108
46108
  img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(new XMLSerializer().serializeToString(svg));
@@ -47464,24 +47464,24 @@ var require_html2canvas = __commonJS({
47464
47464
  return cloneIframeContainer;
47465
47465
  };
47466
47466
  var imageReady = function(img) {
47467
- return new Promise(function(resolve4) {
47467
+ return new Promise(function(resolve5) {
47468
47468
  if (img.complete) {
47469
- resolve4();
47469
+ resolve5();
47470
47470
  return;
47471
47471
  }
47472
47472
  if (!img.src) {
47473
- resolve4();
47473
+ resolve5();
47474
47474
  return;
47475
47475
  }
47476
- img.onload = resolve4;
47477
- img.onerror = resolve4;
47476
+ img.onload = resolve5;
47477
+ img.onerror = resolve5;
47478
47478
  });
47479
47479
  };
47480
47480
  var imagesReady = function(document2) {
47481
47481
  return Promise.all([].slice.call(document2.images, 0).map(imageReady));
47482
47482
  };
47483
47483
  var iframeLoader = function(iframe) {
47484
- return new Promise(function(resolve4, reject2) {
47484
+ return new Promise(function(resolve5, reject2) {
47485
47485
  var cloneWindow = iframe.contentWindow;
47486
47486
  if (!cloneWindow) {
47487
47487
  return reject2("No window assigned for iframe");
@@ -47492,7 +47492,7 @@ var require_html2canvas = __commonJS({
47492
47492
  var interval = setInterval(function() {
47493
47493
  if (documentClone.body.childNodes.length > 0 && documentClone.readyState === "complete") {
47494
47494
  clearInterval(interval);
47495
- resolve4(iframe);
47495
+ resolve5(iframe);
47496
47496
  }
47497
47497
  }, 50);
47498
47498
  };
@@ -47631,10 +47631,10 @@ var require_html2canvas = __commonJS({
47631
47631
  _a.label = 2;
47632
47632
  case 2:
47633
47633
  this.context.logger.debug("Added image " + key.substring(0, 256));
47634
- return [4, new Promise(function(resolve4, reject2) {
47634
+ return [4, new Promise(function(resolve5, reject2) {
47635
47635
  var img = new Image();
47636
47636
  img.onload = function() {
47637
- return resolve4(img);
47637
+ return resolve5(img);
47638
47638
  };
47639
47639
  img.onerror = reject2;
47640
47640
  if (isInlineBase64Image(src) || useCORS) {
@@ -47643,7 +47643,7 @@ var require_html2canvas = __commonJS({
47643
47643
  img.src = src;
47644
47644
  if (img.complete === true) {
47645
47645
  setTimeout(function() {
47646
- return resolve4(img);
47646
+ return resolve5(img);
47647
47647
  }, 500);
47648
47648
  }
47649
47649
  if (_this._options.imageTimeout > 0) {
@@ -47671,17 +47671,17 @@ var require_html2canvas = __commonJS({
47671
47671
  throw new Error("No proxy defined");
47672
47672
  }
47673
47673
  var key = src.substring(0, 256);
47674
- return new Promise(function(resolve4, reject2) {
47674
+ return new Promise(function(resolve5, reject2) {
47675
47675
  var responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? "blob" : "text";
47676
47676
  var xhr = new XMLHttpRequest();
47677
47677
  xhr.onload = function() {
47678
47678
  if (xhr.status === 200) {
47679
47679
  if (responseType === "text") {
47680
- resolve4(xhr.response);
47680
+ resolve5(xhr.response);
47681
47681
  } else {
47682
47682
  var reader_1 = new FileReader();
47683
47683
  reader_1.addEventListener("load", function() {
47684
- return resolve4(reader_1.result);
47684
+ return resolve5(reader_1.result);
47685
47685
  }, false);
47686
47686
  reader_1.addEventListener("error", function(e2) {
47687
47687
  return reject2(e2);
@@ -49496,10 +49496,10 @@ var require_html2canvas = __commonJS({
49496
49496
  })(Renderer)
49497
49497
  );
49498
49498
  var loadSerializedSVG = function(svg) {
49499
- return new Promise(function(resolve4, reject2) {
49499
+ return new Promise(function(resolve5, reject2) {
49500
49500
  var img = new Image();
49501
49501
  img.onload = function() {
49502
- resolve4(img);
49502
+ resolve5(img);
49503
49503
  };
49504
49504
  img.onerror = reject2;
49505
49505
  img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(new XMLSerializer().serializeToString(svg));
@@ -51584,7 +51584,7 @@ var require_make_built_in = __commonJS({
51584
51584
  var defineProperty = Object.defineProperty;
51585
51585
  var stringSlice = uncurryThis("".slice);
51586
51586
  var replace = uncurryThis("".replace);
51587
- var join = uncurryThis([].join);
51587
+ var join2 = uncurryThis([].join);
51588
51588
  var CONFIGURABLE_LENGTH = DESCRIPTORS && !fails(function() {
51589
51589
  return defineProperty(function() {
51590
51590
  }, "length", { value: 8 }).length !== 8;
@@ -51611,7 +51611,7 @@ var require_make_built_in = __commonJS({
51611
51611
  }
51612
51612
  var state = enforceInternalState(value);
51613
51613
  if (!hasOwn(state, "source")) {
51614
- state.source = join(TEMPLATE, typeof name == "string" ? name : "");
51614
+ state.source = join2(TEMPLATE, typeof name == "string" ? name : "");
51615
51615
  }
51616
51616
  return value;
51617
51617
  };
@@ -52685,8 +52685,8 @@ var require_promise_constructor_detection = __commonJS({
52685
52685
  if (!GLOBAL_CORE_JS_PROMISE && V8_VERSION === 66) return true;
52686
52686
  if (IS_PURE && !(NativePromisePrototype["catch"] && NativePromisePrototype["finally"])) return true;
52687
52687
  if (!V8_VERSION || V8_VERSION < 51 || !/native code/.test(PROMISE_CONSTRUCTOR_SOURCE)) {
52688
- var promise = new NativePromiseConstructor(function(resolve4) {
52689
- resolve4(1);
52688
+ var promise = new NativePromiseConstructor(function(resolve5) {
52689
+ resolve5(1);
52690
52690
  });
52691
52691
  var FakePromise = function(exec3) {
52692
52692
  exec3(function() {
@@ -52716,13 +52716,13 @@ var require_new_promise_capability = __commonJS({
52716
52716
  var aCallable = require_a_callable();
52717
52717
  var $TypeError = TypeError;
52718
52718
  var PromiseCapability = function(C) {
52719
- var resolve4, reject2;
52719
+ var resolve5, reject2;
52720
52720
  this.promise = new C(function($$resolve, $$reject) {
52721
- if (resolve4 !== void 0 || reject2 !== void 0) throw new $TypeError("Bad Promise constructor");
52722
- resolve4 = $$resolve;
52721
+ if (resolve5 !== void 0 || reject2 !== void 0) throw new $TypeError("Bad Promise constructor");
52722
+ resolve5 = $$resolve;
52723
52723
  reject2 = $$reject;
52724
52724
  });
52725
- this.resolve = aCallable(resolve4);
52725
+ this.resolve = aCallable(resolve5);
52726
52726
  this.reject = aCallable(reject2);
52727
52727
  };
52728
52728
  module2.exports.f = function(C) {
@@ -52793,7 +52793,7 @@ var require_es_promise_constructor = __commonJS({
52793
52793
  var value = state.value;
52794
52794
  var ok = state.state === FULFILLED;
52795
52795
  var handler = ok ? reaction.ok : reaction.fail;
52796
- var resolve4 = reaction.resolve;
52796
+ var resolve5 = reaction.resolve;
52797
52797
  var reject2 = reaction.reject;
52798
52798
  var domain = reaction.domain;
52799
52799
  var result2, then, exited;
@@ -52815,8 +52815,8 @@ var require_es_promise_constructor = __commonJS({
52815
52815
  if (result2 === reaction.promise) {
52816
52816
  reject2(new TypeError2("Promise-chain cycle"));
52817
52817
  } else if (then = isThenable(result2)) {
52818
- call(then, result2, resolve4, reject2);
52819
- } else resolve4(result2);
52818
+ call(then, result2, resolve5, reject2);
52819
+ } else resolve5(result2);
52820
52820
  } else reject2(value);
52821
52821
  } catch (error) {
52822
52822
  if (domain && !exited) domain.exit();
@@ -52972,8 +52972,8 @@ var require_es_promise_constructor = __commonJS({
52972
52972
  if (!NATIVE_PROMISE_SUBCLASSING) {
52973
52973
  defineBuiltIn(NativePromisePrototype, "then", function then(onFulfilled, onRejected) {
52974
52974
  var that = this;
52975
- return new PromiseConstructor(function(resolve4, reject2) {
52976
- call(nativeThen, that, resolve4, reject2);
52975
+ return new PromiseConstructor(function(resolve5, reject2) {
52976
+ call(nativeThen, that, resolve5, reject2);
52977
52977
  }).then(onFulfilled, onRejected);
52978
52978
  }, { unsafe: true });
52979
52979
  }
@@ -53233,7 +53233,7 @@ var require_es_promise_all = __commonJS({
53233
53233
  all: function all(iterable) {
53234
53234
  var C = this;
53235
53235
  var capability = newPromiseCapabilityModule.f(C);
53236
- var resolve4 = capability.resolve;
53236
+ var resolve5 = capability.resolve;
53237
53237
  var reject2 = capability.reject;
53238
53238
  var result2 = perform(function() {
53239
53239
  var $promiseResolve = aCallable(C.resolve);
@@ -53248,10 +53248,10 @@ var require_es_promise_all = __commonJS({
53248
53248
  if (alreadyCalled) return;
53249
53249
  alreadyCalled = true;
53250
53250
  values2[index] = value;
53251
- --remaining || resolve4(values2);
53251
+ --remaining || resolve5(values2);
53252
53252
  }, reject2);
53253
53253
  });
53254
- --remaining || resolve4(values2);
53254
+ --remaining || resolve5(values2);
53255
53255
  });
53256
53256
  if (result2.error) reject2(result2.value);
53257
53257
  return capability.promise;
@@ -53345,8 +53345,8 @@ var require_promise_resolve = __commonJS({
53345
53345
  anObject(C);
53346
53346
  if (isObject4(x) && x.constructor === C) return x;
53347
53347
  var promiseCapability = newPromiseCapability.f(C);
53348
- var resolve4 = promiseCapability.resolve;
53349
- resolve4(x);
53348
+ var resolve5 = promiseCapability.resolve;
53349
+ resolve5(x);
53350
53350
  return promiseCapability.promise;
53351
53351
  };
53352
53352
  }
@@ -53365,7 +53365,7 @@ var require_es_promise_resolve = __commonJS({
53365
53365
  var PromiseConstructorWrapper = getBuiltIn("Promise");
53366
53366
  var CHECK_WRAPPER = IS_PURE && !FORCED_PROMISE_CONSTRUCTOR;
53367
53367
  $2({ target: "Promise", stat: true, forced: IS_PURE || FORCED_PROMISE_CONSTRUCTOR }, {
53368
- resolve: function resolve4(x) {
53368
+ resolve: function resolve5(x) {
53369
53369
  return promiseResolve(CHECK_WRAPPER && this === PromiseConstructorWrapper ? NativePromiseConstructor : this, x);
53370
53370
  }
53371
53371
  });
@@ -54854,7 +54854,7 @@ var require_es_array_join = __commonJS({
54854
54854
  var ES3_STRINGS = IndexedObject !== Object;
54855
54855
  var FORCED = ES3_STRINGS || !arrayMethodIsStrict("join", ",");
54856
54856
  $2({ target: "Array", proto: true, forced: FORCED }, {
54857
- join: function join(separator) {
54857
+ join: function join2(separator) {
54858
54858
  return nativeJoin(toIndexedObject(this), separator === void 0 ? "," : separator);
54859
54859
  }
54860
54860
  });
@@ -58983,8 +58983,8 @@ var require_lib2 = __commonJS({
58983
58983
  var FRAMERATE = this.FRAMERATE, mouse = this.mouse;
58984
58984
  var frameDuration = 1e3 / FRAMERATE;
58985
58985
  this.frameDuration = frameDuration;
58986
- this.readyPromise = new Promise(function(resolve4) {
58987
- _this.resolveReady = resolve4;
58986
+ this.readyPromise = new Promise(function(resolve5) {
58987
+ _this.resolveReady = resolve5;
58988
58988
  });
58989
58989
  if (this.isReady()) {
58990
58990
  this.render(element, ignoreDimensions, ignoreClear, scaleWidth, scaleHeight, offsetX, offsetY);
@@ -65022,9 +65022,9 @@ var require_lib2 = __commonJS({
65022
65022
  if (anonymousCrossOrigin) {
65023
65023
  image2.crossOrigin = "Anonymous";
65024
65024
  }
65025
- return _context.abrupt("return", new Promise(function(resolve4, reject2) {
65025
+ return _context.abrupt("return", new Promise(function(resolve5, reject2) {
65026
65026
  image2.onload = function() {
65027
- resolve4(image2);
65027
+ resolve5(image2);
65028
65028
  };
65029
65029
  image2.onerror = function(_event, _source, _lineno, _colno, error) {
65030
65030
  reject2(error);
@@ -73320,7 +73320,7 @@ async function walk(rootPath, currentPath, files) {
73320
73320
  continue;
73321
73321
  }
73322
73322
  if (!entry.isFile()) continue;
73323
- const stat = await fs3.promises.stat(fullPath);
73323
+ const stat2 = await fs3.promises.stat(fullPath);
73324
73324
  const extension2 = path3.extname(entry.name).toLowerCase();
73325
73325
  const role = sensitivity.sensitive ? "secret_sensitive" : classifyFileRole(entry.name, relativePath);
73326
73326
  const ignored = sensitivity.sensitive;
@@ -73329,11 +73329,11 @@ async function walk(rootPath, currentPath, files) {
73329
73329
  relativePath,
73330
73330
  fileName: entry.name,
73331
73331
  extension: extension2,
73332
- sizeBytes: stat.size,
73332
+ sizeBytes: stat2.size,
73333
73333
  role,
73334
73334
  ignored,
73335
73335
  ignoreReason: sensitivity.reason,
73336
- textSnippet: ignored ? void 0 : await safeTextSnippet(fullPath, extension2, stat.size)
73336
+ textSnippet: ignored ? void 0 : await safeTextSnippet(fullPath, extension2, stat2.size)
73337
73337
  });
73338
73338
  }
73339
73339
  }
@@ -75408,8 +75408,8 @@ async function listDirs(root2) {
75408
75408
  }
75409
75409
  async function fileExists(filePath) {
75410
75410
  try {
75411
- const stat = await fs7.promises.stat(filePath);
75412
- return stat.isFile();
75411
+ const stat2 = await fs7.promises.stat(filePath);
75412
+ return stat2.isFile();
75413
75413
  } catch {
75414
75414
  return false;
75415
75415
  }
@@ -75566,7 +75566,6 @@ var init_payroll = __esm({
75566
75566
  // lib/perchBusinessTools/index.ts
75567
75567
  var init_perchBusinessTools = __esm({
75568
75568
  "lib/perchBusinessTools/index.ts"() {
75569
- "use strict";
75570
75569
  init_generateAPAuditPacket();
75571
75570
  init_inventoryFolder();
75572
75571
  init_loadBusinessTables();
@@ -80628,13 +80627,13 @@ function buildDesktopContextSection(input) {
80628
80627
  label: "CLI workspace",
80629
80628
  content: [
80630
80629
  "## CLI local workspace",
80631
- "Perch is running from a terminal. Local filesystem, shell, sandbox-code, and AP evidence tools are available through the CLI local bridge.",
80630
+ "Perch is running from a terminal. Local filesystem, shell, sandbox-code, and AP evidence tools are available through the CLI local runtime.",
80632
80631
  input.activeRootPath?.trim() ? `Workspace root: ${input.activeRootPath}. Treat relative paths as relative to this root.` : "Workspace root: current terminal directory.",
80633
- "Desktop-only services are not connected here: embedded browser, Google/Gmail/Calendar delivery, Desktop RAG indexing, MCP Desktop servers, and project memory writes are unavailable unless the Desktop app is running.",
80632
+ "GUI-only services are not connected here: embedded browser, Google/Gmail/Calendar delivery, Desktop RAG indexing, MCP Desktop servers, and project memory writes are unavailable unless the desktop app is running.",
80634
80633
  localReadTools.length > 0 ? `Read tools: ${localReadTools.join(", ")}.` : "Read tools: none exposed for this turn.",
80635
80634
  localWriteTools.length > 0 ? `Write/command tools: ${localWriteTools.join(", ")}. These are governed by the selected permission mode and command policy.` : "Write/command tools: none exposed for this turn."
80636
80635
  ].join("\n"),
80637
- reason: "Terminal local bridge state and CLI tool availability for this turn.",
80636
+ reason: "Terminal local runtime state and CLI tool availability for this turn.",
80638
80637
  sourcePath: input.activeRootPath,
80639
80638
  metadata: {
80640
80639
  desktopConnected: false,
@@ -80649,7 +80648,7 @@ function buildDesktopContextSection(input) {
80649
80648
  lane: "desktop",
80650
80649
  label: "Desktop workspace",
80651
80650
  content: "",
80652
- reason: "Desktop bridge is unavailable in browser mode.",
80651
+ reason: "Local workspace runtime is unavailable in browser mode.",
80653
80652
  sourcePath: input.activeRootPath,
80654
80653
  skipped: true,
80655
80654
  metadata: { desktopConnected: false }
@@ -80736,7 +80735,7 @@ function buildDesktopContextSection(input) {
80736
80735
  label: "Desktop workspace",
80737
80736
  content: [
80738
80737
  "## Desktop environment",
80739
- "Desktop bridge is connected. Local filesystem tools are available to the operator runtime.",
80738
+ "Desktop local runtime is connected. Local filesystem tools are available to the operator runtime.",
80740
80739
  input.activeRootPath?.trim() ? `Optional folder scope: ${input.activeRootPath} (you are NOT limited to it \u2014 absolute paths anywhere in the user's home work directly).` : "No folder scope is selected, and none is needed. When the user gives a file path, USE IT DIRECTLY \u2014 call readLocalFile / glob / grep / visionInspect with the absolute path (e.g. /Users/you/Desktop/shot.png). Full-access policy already allows any path in the user's home. NEVER tell the user to select or approve a folder, and NEVER refuse a file because no folder is selected.",
80741
80740
  `Visible local sources: ${totalVisibleFiles}`,
80742
80741
  input.localSourcesMeta?.refreshedAt ? `Local source snapshot refreshed: ${input.localSourcesMeta.refreshedAt}` : null,
@@ -80754,7 +80753,7 @@ function buildDesktopContextSection(input) {
80754
80753
  availableReadTools.length > 0 ? `Read tools: ${availableReadTools.join(", ")}.` : "Read tools: none exposed for this turn.",
80755
80754
  availableWriteTools.length > 0 ? `Write/command tools: ${availableWriteTools.join(", ")}. These are governed by the selected permission mode and command policy.` : "Write/command tools: none exposed for this turn."
80756
80755
  ].filter(Boolean).join("\n"),
80757
- reason: "Desktop bridge state, optional folder scope, and tool availability for this turn.",
80756
+ reason: "Local runtime state, optional folder scope, and tool availability for this turn.",
80758
80757
  sourcePath: input.activeRootPath,
80759
80758
  metadata: {
80760
80759
  desktopConnected: true,
@@ -81720,7 +81719,7 @@ function buildThreadLedgerItemsFromTurn(input) {
81720
81719
  kind: "context_boundary",
81721
81720
  ts: event.ts,
81722
81721
  runId: input.runId,
81723
- summary: `Context compacted from ${event.tokensBefore} to ${event.tokensAfter} tokens.`,
81722
+ summary: `Context compacted from ${event.tokensBefore} to ${event.tokensAfter} tokens` + (event.targetTokens ? ` (target ${event.targetTokens}).` : "."),
81724
81723
  resultPreview: truncate3(event.summary, MAX_TEXT)
81725
81724
  });
81726
81725
  } else if (event.type === "live_card_end" && event.receipt) {
@@ -82300,6 +82299,8 @@ function sanitizePendingActionPayload(value) {
82300
82299
  }
82301
82300
  function parseContextCompaction(value) {
82302
82301
  if (!isRecord4(value)) return null;
82302
+ const compactBoundary = parseCompactBoundary(value.compactBoundary);
82303
+ const compactionReport = parseCompactionReport(value.compactionReport);
82303
82304
  return {
82304
82305
  compactConversation: value.compactConversation === true,
82305
82306
  dropNotFoundRows: value.dropNotFoundRows === true,
@@ -82310,9 +82311,38 @@ function parseContextCompaction(value) {
82310
82311
  autoCompactedAtIso: typeof value.autoCompactedAtIso === "string" ? value.autoCompactedAtIso : void 0,
82311
82312
  autoCompactedMessageCount: asFiniteNonNegativeInt(value.autoCompactedMessageCount) ?? void 0,
82312
82313
  compactedSummary: typeof value.compactedSummary === "string" ? value.compactedSummary : void 0,
82313
- effectiveContextLimitTokens: asFiniteNonNegativeInt(value.effectiveContextLimitTokens) ?? void 0
82314
+ effectiveContextLimitTokens: asFiniteNonNegativeInt(value.effectiveContextLimitTokens) ?? void 0,
82315
+ ...compactBoundary ? { compactBoundary } : {},
82316
+ ...compactionReport ? { compactionReport } : {}
82314
82317
  };
82315
82318
  }
82319
+ function parseCompactBoundary(value) {
82320
+ if (!isRecord4(value)) return void 0;
82321
+ const kind = value.kind === "manual" ? "manual" : value.kind === "auto" ? "auto" : null;
82322
+ const compactedAtIso = typeof value.compactedAtIso === "string" ? value.compactedAtIso : null;
82323
+ if (!kind || !compactedAtIso) return void 0;
82324
+ return {
82325
+ kind,
82326
+ compactedAtIso,
82327
+ compactedThroughMessageId: typeof value.compactedThroughMessageId === "string" ? value.compactedThroughMessageId : null,
82328
+ compactedThroughMessageCreatedAt: typeof value.compactedThroughMessageCreatedAt === "string" ? value.compactedThroughMessageCreatedAt : null,
82329
+ compactedThroughWireIndex: asFiniteNonNegativeInt(value.compactedThroughWireIndex)
82330
+ };
82331
+ }
82332
+ function parseCompactionReport(value) {
82333
+ if (!isRecord4(value)) return void 0;
82334
+ const parsed = {
82335
+ tokensBefore: asFiniteNonNegativeInt(value.tokensBefore) ?? void 0,
82336
+ tokensAfter: asFiniteNonNegativeInt(value.tokensAfter) ?? void 0,
82337
+ targetTokens: asFiniteNonNegativeInt(value.targetTokens) ?? void 0,
82338
+ summaryTokens: asFiniteNonNegativeInt(value.summaryTokens) ?? void 0,
82339
+ recentTailTokens: asFiniteNonNegativeInt(value.recentTailTokens) ?? void 0,
82340
+ recentTailMessages: asFiniteNonNegativeInt(value.recentTailMessages) ?? void 0,
82341
+ summarizedMessages: asFiniteNonNegativeInt(value.summarizedMessages) ?? void 0,
82342
+ restoredToolsOrFiles: asFiniteNonNegativeInt(value.restoredToolsOrFiles) ?? void 0
82343
+ };
82344
+ return Object.values(parsed).some((entry) => typeof entry === "number") ? parsed : void 0;
82345
+ }
82316
82346
  function parseWorkerRuns(value) {
82317
82347
  if (!Array.isArray(value)) return void 0;
82318
82348
  const parsed = [];
@@ -82425,6 +82455,7 @@ function parseContextMetrics(value) {
82425
82455
  const consecutiveAutoCompactFailures = asFiniteNonNegativeInt(value.consecutiveAutoCompactFailures);
82426
82456
  const lastApiUsage = parseModelUsage(value.lastApiUsage);
82427
82457
  const turnApiUsage = parseModelUsage(value.turnApiUsage);
82458
+ const compactionReport = parseCompactionReport(value.compactionReport);
82428
82459
  return {
82429
82460
  threadContextTokens,
82430
82461
  latestSendTokens,
@@ -82448,6 +82479,7 @@ function parseContextMetrics(value) {
82448
82479
  ...consecutiveAutoCompactFailures !== null ? { consecutiveAutoCompactFailures } : {},
82449
82480
  ...typeof value.autoCompactedAtIso === "string" ? { autoCompactedAtIso: value.autoCompactedAtIso } : {},
82450
82481
  ...asFiniteNonNegativeInt(value.autoCompactedMessageCount) !== null ? { autoCompactedMessageCount: asFiniteNonNegativeInt(value.autoCompactedMessageCount) } : {},
82482
+ ...compactionReport ? { compactionReport } : {},
82451
82483
  resetAtIso: typeof value.resetAtIso === "string" ? value.resetAtIso : null,
82452
82484
  updatedAtIso: typeof value.updatedAtIso === "string" ? value.updatedAtIso : (/* @__PURE__ */ new Date()).toISOString()
82453
82485
  };
@@ -82895,7 +82927,6 @@ function truncateHistoryLine(value, max2) {
82895
82927
  }
82896
82928
  var init_operatorTruth = __esm({
82897
82929
  "features/perchTerminal/runtime/operatorTruth.ts"() {
82898
- "use strict";
82899
82930
  }
82900
82931
  });
82901
82932
 
@@ -82906,7 +82937,17 @@ function buildMessageHistory(recentMessages, currentInput, options) {
82906
82937
  const hasCompactedSummary = compactedSummary.length > 0;
82907
82938
  const shouldUseCompactedHistory = options?.compactConversation === true && hasCompactedSummary;
82908
82939
  const keepCount = options?.keepCount ?? (shouldUseCompactedHistory ? 12 : recentMessages.length);
82909
- const sourceMessages = shouldUseCompactedHistory ? recentMessages.slice(-keepCount) : recentMessages;
82940
+ const sourceMessages = shouldUseCompactedHistory ? selectRecentHistoryTail(
82941
+ filterMessagesAfterCompactBoundary(recentMessages, {
82942
+ compactedThroughMessageId: options?.compactedThroughMessageId,
82943
+ compactedThroughMessageCreatedAt: options?.compactedThroughMessageCreatedAt
82944
+ }),
82945
+ {
82946
+ tokenTarget: options?.recentTailTokenTarget,
82947
+ fallbackKeepCount: keepCount,
82948
+ minMessages: Math.min(DEFAULT_COMPACTED_MIN_RECENT_MESSAGES, keepCount)
82949
+ }
82950
+ ) : recentMessages;
82910
82951
  if (hasCompactedSummary) {
82911
82952
  history.push({
82912
82953
  role: "user",
@@ -82955,6 +82996,47 @@ ${compactedSummary}`
82955
82996
  history.push({ role: "user", content: currentInput });
82956
82997
  return history;
82957
82998
  }
82999
+ function filterMessagesAfterCompactBoundary(messages, boundary) {
83000
+ const boundaryId = boundary.compactedThroughMessageId?.trim();
83001
+ if (boundaryId) {
83002
+ const idx = messages.findIndex((message) => message.id === boundaryId);
83003
+ if (idx >= 0) return messages.slice(idx + 1);
83004
+ }
83005
+ const boundaryAt = boundary.compactedThroughMessageCreatedAt?.trim();
83006
+ if (boundaryAt) {
83007
+ const boundaryMs = Date.parse(boundaryAt);
83008
+ if (Number.isFinite(boundaryMs)) {
83009
+ return messages.filter((message) => {
83010
+ const createdMs = Date.parse(message.createdAt);
83011
+ return !Number.isFinite(createdMs) || createdMs > boundaryMs;
83012
+ });
83013
+ }
83014
+ }
83015
+ return messages;
83016
+ }
83017
+ function selectRecentHistoryTail(messages, input) {
83018
+ const tokenTarget = typeof input.tokenTarget === "number" && Number.isFinite(input.tokenTarget) && input.tokenTarget > 0 ? Math.floor(input.tokenTarget) : null;
83019
+ if (!tokenTarget) {
83020
+ return messages.slice(-Math.max(1, input.fallbackKeepCount));
83021
+ }
83022
+ const minMessages = Math.max(1, input.minMessages ?? DEFAULT_COMPACTED_MIN_RECENT_MESSAGES);
83023
+ let startIndex = messages.length;
83024
+ let tokens = 0;
83025
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
83026
+ const msgTokens = estimateHistoryMessageTokens(messages[i]);
83027
+ const selectedCount = messages.length - startIndex;
83028
+ const mustKeep = selectedCount === 0;
83029
+ if (!mustKeep && tokens + msgTokens > tokenTarget) break;
83030
+ tokens += msgTokens;
83031
+ startIndex = i;
83032
+ if (selectedCount + 1 >= minMessages && tokens >= tokenTarget) break;
83033
+ }
83034
+ return messages.slice(startIndex);
83035
+ }
83036
+ function estimateHistoryMessageTokens(message) {
83037
+ const text = message.kind === "text" ? messageContentForModelHistory(message) : `[Plan: ${message.plan.title}] ${message.plan.goal}`;
83038
+ return Math.max(1, Math.ceil(`${message.kind}:${text}`.length / 4));
83039
+ }
82958
83040
  function messageContentForModelHistory(msg) {
82959
83041
  if (msg.role !== "assistant" || !msg.operatorState?.events?.length) {
82960
83042
  return msg.text;
@@ -82966,11 +83048,14 @@ function messageContentForModelHistory(msg) {
82966
83048
  turnSummary: msg.turnSummary
82967
83049
  });
82968
83050
  }
83051
+ var DEFAULT_COMPACTED_RECENT_TAIL_TOKENS, DEFAULT_COMPACTED_MIN_RECENT_MESSAGES;
82969
83052
  var init_messageHistory = __esm({
82970
83053
  "features/perchTerminal/runtime/messageContext/messageHistory.ts"() {
82971
83054
  "use strict";
82972
83055
  init_operatorTruth();
82973
83056
  init_wireTranscript();
83057
+ DEFAULT_COMPACTED_RECENT_TAIL_TOKENS = 24e3;
83058
+ DEFAULT_COMPACTED_MIN_RECENT_MESSAGES = 4;
82974
83059
  }
82975
83060
  });
82976
83061
 
@@ -91612,6 +91697,9 @@ function assembleContext(input) {
91612
91697
  {
91613
91698
  compactConversation: contextCompaction?.compactConversation === true,
91614
91699
  compactedSummary: contextCompaction?.compactedSummary ?? null,
91700
+ compactedThroughMessageId: contextCompaction?.compactBoundary?.compactedThroughMessageId ?? null,
91701
+ compactedThroughMessageCreatedAt: contextCompaction?.compactBoundary?.compactedThroughMessageCreatedAt ?? null,
91702
+ recentTailTokenTarget: contextCompaction?.compactionReport?.recentTailTokens ?? DEFAULT_COMPACTED_RECENT_TAIL_TOKENS,
91615
91703
  wireReplayMessages: threadSession?.threadWireMessages ?? null,
91616
91704
  wireReplayContextLimitTokens: input.contextLimitTokens ?? threadSession?.contextMetrics?.effectiveLimitTokens ?? null,
91617
91705
  // Coordinator sessions reference planIds and worker completions from earlier
@@ -91740,7 +91828,7 @@ function assembleContext(input) {
91740
91828
  );
91741
91829
  if (!input.desktopConnected && input.cliLocalTools !== true) {
91742
91830
  warnings.push(
91743
- "Desktop bridge is not connected. Local workspace tools are unavailable in browser mode."
91831
+ "Local workspace runtime is not connected. Local workspace tools are unavailable in browser mode."
91744
91832
  );
91745
91833
  }
91746
91834
  return {
@@ -93571,11 +93659,11 @@ function __metadata(metadataKey, metadataValue) {
93571
93659
  }
93572
93660
  function __awaiter(thisArg, _arguments, P, generator) {
93573
93661
  function adopt(value) {
93574
- return value instanceof P ? value : new P(function(resolve4) {
93575
- resolve4(value);
93662
+ return value instanceof P ? value : new P(function(resolve5) {
93663
+ resolve5(value);
93576
93664
  });
93577
93665
  }
93578
- return new (P || (P = Promise))(function(resolve4, reject2) {
93666
+ return new (P || (P = Promise))(function(resolve5, reject2) {
93579
93667
  function fulfilled(value) {
93580
93668
  try {
93581
93669
  step(generator.next(value));
@@ -93591,7 +93679,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
93591
93679
  }
93592
93680
  }
93593
93681
  function step(result2) {
93594
- result2.done ? resolve4(result2.value) : adopt(result2.value).then(fulfilled, rejected);
93682
+ result2.done ? resolve5(result2.value) : adopt(result2.value).then(fulfilled, rejected);
93595
93683
  }
93596
93684
  step((generator = generator.apply(thisArg, _arguments || [])).next());
93597
93685
  });
@@ -93782,14 +93870,14 @@ function __asyncValues(o) {
93782
93870
  }, i);
93783
93871
  function verb(n) {
93784
93872
  i[n] = o[n] && function(v) {
93785
- return new Promise(function(resolve4, reject2) {
93786
- v = o[n](v), settle(resolve4, reject2, v.done, v.value);
93873
+ return new Promise(function(resolve5, reject2) {
93874
+ v = o[n](v), settle(resolve5, reject2, v.done, v.value);
93787
93875
  });
93788
93876
  };
93789
93877
  }
93790
- function settle(resolve4, reject2, d, v) {
93878
+ function settle(resolve5, reject2, d, v) {
93791
93879
  Promise.resolve(v).then(function(v2) {
93792
- resolve4({ value: v2, done: d });
93880
+ resolve5({ value: v2, done: d });
93793
93881
  }, reject2);
93794
93882
  }
93795
93883
  }
@@ -94404,18 +94492,18 @@ var require_dist = __commonJS({
94404
94492
  }
94405
94493
  };
94406
94494
  function sleep2(ms, signal) {
94407
- return new Promise((resolve4) => {
94495
+ return new Promise((resolve5) => {
94408
94496
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
94409
- resolve4();
94497
+ resolve5();
94410
94498
  return;
94411
94499
  }
94412
94500
  const id = setTimeout(() => {
94413
94501
  signal === null || signal === void 0 || signal.removeEventListener("abort", onAbort);
94414
- resolve4();
94502
+ resolve5();
94415
94503
  }, ms);
94416
94504
  function onAbort() {
94417
94505
  clearTimeout(id);
94418
- resolve4();
94506
+ resolve5();
94419
94507
  }
94420
94508
  signal === null || signal === void 0 || signal.addEventListener("abort", onAbort);
94421
94509
  });
@@ -102425,15 +102513,15 @@ var require_RealtimeChannel = __commonJS({
102425
102513
  }
102426
102514
  }
102427
102515
  } else {
102428
- return new Promise((resolve4) => {
102516
+ return new Promise((resolve5) => {
102429
102517
  var _a2, _b2, _c;
102430
102518
  const push2 = this.channelAdapter.push(args.type, args, opts.timeout || this.timeout);
102431
102519
  if (args.type === "broadcast" && !((_c = (_b2 = (_a2 = this.params) === null || _a2 === void 0 ? void 0 : _a2.config) === null || _b2 === void 0 ? void 0 : _b2.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
102432
- resolve4("ok");
102520
+ resolve5("ok");
102433
102521
  }
102434
- push2.receive("ok", () => resolve4("ok"));
102435
- push2.receive("error", () => resolve4("error"));
102436
- push2.receive("timeout", () => resolve4("timed out"));
102522
+ push2.receive("ok", () => resolve5("ok"));
102523
+ push2.receive("error", () => resolve5("error"));
102524
+ push2.receive("timeout", () => resolve5("timed out"));
102437
102525
  });
102438
102526
  }
102439
102527
  }
@@ -102458,8 +102546,8 @@ var require_RealtimeChannel = __commonJS({
102458
102546
  * @category Realtime
102459
102547
  */
102460
102548
  async unsubscribe(timeout = this.timeout) {
102461
- return new Promise((resolve4) => {
102462
- this.channelAdapter.unsubscribe(timeout).receive("ok", () => resolve4("ok")).receive("timeout", () => resolve4("timed out")).receive("error", () => resolve4("error"));
102549
+ return new Promise((resolve5) => {
102550
+ this.channelAdapter.unsubscribe(timeout).receive("ok", () => resolve5("ok")).receive("timeout", () => resolve5("timed out")).receive("error", () => resolve5("error"));
102463
102551
  });
102464
102552
  }
102465
102553
  /**
@@ -102540,8 +102628,8 @@ var require_RealtimeChannel = __commonJS({
102540
102628
  }
102541
102629
  /** @internal */
102542
102630
  _notThisChannelEvent(event, ref) {
102543
- const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
102544
- const events = [close, error, leave, join];
102631
+ const { close, error, leave, join: join2 } = constants_1.CHANNEL_EVENTS;
102632
+ const events = [close, error, leave, join2];
102545
102633
  return ref && events.includes(event) && ref !== this.joinPush.ref;
102546
102634
  }
102547
102635
  /** @internal */
@@ -102663,11 +102751,11 @@ var require_socketAdapter = __commonJS({
102663
102751
  this.socket.connect();
102664
102752
  }
102665
102753
  disconnect(callback, code, reason, timeout = 1e4) {
102666
- return new Promise((resolve4) => {
102667
- setTimeout(() => resolve4("timeout"), timeout);
102754
+ return new Promise((resolve5) => {
102755
+ setTimeout(() => resolve5("timeout"), timeout);
102668
102756
  this.socket.disconnect(() => {
102669
102757
  callback();
102670
- resolve4("ok");
102758
+ resolve5("ok");
102671
102759
  }, code, reason);
102672
102760
  });
102673
102761
  }
@@ -104213,7 +104301,7 @@ var require_dist3 = __commonJS({
104213
104301
  return _objectSpread24(_objectSpread24({}, params), parameters);
104214
104302
  };
104215
104303
  async function _handleRequest2(fetcher, method, url, options, parameters, body, namespace) {
104216
- return new Promise((resolve4, reject2) => {
104304
+ return new Promise((resolve5, reject2) => {
104217
104305
  fetcher(url, _getRequestParams2(method, options, parameters, body)).then((result2) => {
104218
104306
  if (!result2.ok) throw result2;
104219
104307
  if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result2;
@@ -104223,7 +104311,7 @@ var require_dist3 = __commonJS({
104223
104311
  if (!contentType || !contentType.includes("application/json")) return {};
104224
104312
  }
104225
104313
  return result2.json();
104226
- }).then((data) => resolve4(data)).catch((error) => handleError2(error, reject2, options, namespace));
104314
+ }).then((data) => resolve5(data)).catch((error) => handleError2(error, reject2, options, namespace));
104227
104315
  });
104228
104316
  }
104229
104317
  function createFetchApi2(namespace = "storage") {
@@ -114757,11 +114845,11 @@ var require_dist4 = __commonJS({
114757
114845
  };
114758
114846
  function __awaiter3(thisArg, _arguments, P, generator) {
114759
114847
  function adopt(value) {
114760
- return value instanceof P ? value : new P(function(resolve4) {
114761
- resolve4(value);
114848
+ return value instanceof P ? value : new P(function(resolve5) {
114849
+ resolve5(value);
114762
114850
  });
114763
114851
  }
114764
- return new (P || (P = Promise))(function(resolve4, reject2) {
114852
+ return new (P || (P = Promise))(function(resolve5, reject2) {
114765
114853
  function fulfilled(value) {
114766
114854
  try {
114767
114855
  step(generator.next(value));
@@ -114777,7 +114865,7 @@ var require_dist4 = __commonJS({
114777
114865
  }
114778
114866
  }
114779
114867
  function step(result2) {
114780
- result2.done ? resolve4(result2.value) : adopt(result2.value).then(fulfilled, rejected);
114868
+ result2.done ? resolve5(result2.value) : adopt(result2.value).then(fulfilled, rejected);
114781
114869
  }
114782
114870
  step((generator = generator.apply(thisArg, _arguments || [])).next());
114783
114871
  });
@@ -118758,18 +118846,18 @@ var init_streamNormalizer = __esm({
118758
118846
 
118759
118847
  // node_modules/@supabase/postgrest-js/dist/index.mjs
118760
118848
  function sleep(ms, signal) {
118761
- return new Promise((resolve4) => {
118849
+ return new Promise((resolve5) => {
118762
118850
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
118763
- resolve4();
118851
+ resolve5();
118764
118852
  return;
118765
118853
  }
118766
118854
  const id = setTimeout(() => {
118767
118855
  signal === null || signal === void 0 || signal.removeEventListener("abort", onAbort);
118768
- resolve4();
118856
+ resolve5();
118769
118857
  }, ms);
118770
118858
  function onAbort() {
118771
118859
  clearTimeout(id);
118772
- resolve4();
118860
+ resolve5();
118773
118861
  }
118774
118862
  signal === null || signal === void 0 || signal.addEventListener("abort", onAbort);
118775
118863
  });
@@ -124304,7 +124392,7 @@ function normalizeHeaders(headers) {
124304
124392
  return result2;
124305
124393
  }
124306
124394
  async function _handleRequest(fetcher, method, url, options, parameters, body, namespace) {
124307
- return new Promise((resolve4, reject2) => {
124395
+ return new Promise((resolve5, reject2) => {
124308
124396
  fetcher(url, _getRequestParams(method, options, parameters, body)).then((result2) => {
124309
124397
  if (!result2.ok) throw result2;
124310
124398
  if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result2;
@@ -124314,7 +124402,7 @@ async function _handleRequest(fetcher, method, url, options, parameters, body, n
124314
124402
  if (!contentType || !contentType.includes("application/json")) return {};
124315
124403
  }
124316
124404
  return result2.json();
124317
- }).then((data) => resolve4(data)).catch((error) => handleError(error, reject2, options, namespace));
124405
+ }).then((data) => resolve5(data)).catch((error) => handleError(error, reject2, options, namespace));
124318
124406
  });
124319
124407
  }
124320
124408
  function createFetchApi(namespace = "storage") {
@@ -126932,11 +127020,11 @@ __export(dist_exports, {
126932
127020
  });
126933
127021
  function __awaiter2(thisArg, _arguments, P, generator) {
126934
127022
  function adopt(value) {
126935
- return value instanceof P ? value : new P(function(resolve4) {
126936
- resolve4(value);
127023
+ return value instanceof P ? value : new P(function(resolve5) {
127024
+ resolve5(value);
126937
127025
  });
126938
127026
  }
126939
- return new (P || (P = Promise))(function(resolve4, reject2) {
127027
+ return new (P || (P = Promise))(function(resolve5, reject2) {
126940
127028
  function fulfilled(value) {
126941
127029
  try {
126942
127030
  step(generator.next(value));
@@ -126952,7 +127040,7 @@ function __awaiter2(thisArg, _arguments, P, generator) {
126952
127040
  }
126953
127041
  }
126954
127042
  function step(result2) {
126955
- result2.done ? resolve4(result2.value) : adopt(result2.value).then(fulfilled, rejected);
127043
+ result2.done ? resolve5(result2.value) : adopt(result2.value).then(fulfilled, rejected);
126956
127044
  }
126957
127045
  step((generator = generator.apply(thisArg, _arguments || [])).next());
126958
127046
  });
@@ -128257,12 +128345,12 @@ async function callAutoRaced(opts, parallelism) {
128257
128345
  }
128258
128346
  async function firstSuccessful(promises) {
128259
128347
  const pending = new Set(promises);
128260
- return new Promise((resolve4, reject2) => {
128348
+ return new Promise((resolve5, reject2) => {
128261
128349
  promises.forEach((promise) => {
128262
128350
  promise.then((outcome) => {
128263
128351
  pending.delete(promise);
128264
128352
  if ("result" in outcome) {
128265
- resolve4(outcome);
128353
+ resolve5(outcome);
128266
128354
  } else if (pending.size === 0) {
128267
128355
  reject2(new Error(`All ${promises.length} racers failed`));
128268
128356
  }
@@ -130156,7 +130244,7 @@ function createProviderAbortSignal(parent, timeoutMs = AUTO_ROUTER_PROVIDER_TIME
130156
130244
  };
130157
130245
  }
130158
130246
  function readStreamChunkWithIdleTimeout(reader, idleMs, abortSignal) {
130159
- return new Promise((resolve4, reject2) => {
130247
+ return new Promise((resolve5, reject2) => {
130160
130248
  const timer = setTimeout(
130161
130249
  () => reject2(new Error(`stream idle timeout after ${idleMs}ms`)),
130162
130250
  idleMs
@@ -130177,7 +130265,7 @@ function readStreamChunkWithIdleTimeout(reader, idleMs, abortSignal) {
130177
130265
  (result2) => {
130178
130266
  clearTimeout(timer);
130179
130267
  if (abortSignal) abortSignal.removeEventListener("abort", onAbort);
130180
- resolve4(result2);
130268
+ resolve5(result2);
130181
130269
  },
130182
130270
  (err) => {
130183
130271
  clearTimeout(timer);
@@ -133482,7 +133570,7 @@ function validateToolCallPolicy(input) {
133482
133570
  return {
133483
133571
  ok: false,
133484
133572
  code: "tool_desktop_required",
133485
- message: "Local and browser automation tools require Perch AI Desktop. Filesystem access is validated per path by the current permission mode.",
133573
+ message: "This tool requires a local runtime. Filesystem access is validated per path by the current permission mode.",
133486
133574
  riskLevel: classified.riskLevel,
133487
133575
  desktopRequired: true
133488
133576
  };
@@ -133824,8 +133912,8 @@ function validateArgs(name, args) {
133824
133912
  return `${name}.path must be a safe path.`;
133825
133913
  return null;
133826
133914
  case TOOL_NAMES.writeLocalFile:
133827
- if (!isSafeRelativePath(args.path))
133828
- return "writeLocalFile.path must be a workspace-relative path such as intelligence_center_mockup.html, not an absolute /Users/... path.";
133915
+ if (!isSafeFilePath(args.path))
133916
+ return "writeLocalFile.path must be a safe local path.";
133829
133917
  if (typeof args.content !== "string")
133830
133918
  return "writeLocalFile.content must be a string.";
133831
133919
  if (args.overwrite !== void 0 && typeof args.overwrite !== "boolean")
@@ -133833,20 +133921,20 @@ function validateArgs(name, args) {
133833
133921
  return null;
133834
133922
  case TOOL_NAMES.moveLocalFile:
133835
133923
  case TOOL_NAMES.copyLocalFile:
133836
- if (!isSafeRelativePath(args.from))
133837
- return `${name}.from must be a safe relative path.`;
133838
- if (!isSafeRelativePath(args.to))
133839
- return `${name}.to must be a safe relative path.`;
133924
+ if (!isSafeFilePath(args.from))
133925
+ return `${name}.from must be a safe local path.`;
133926
+ if (!isSafeFilePath(args.to))
133927
+ return `${name}.to must be a safe local path.`;
133840
133928
  return null;
133841
133929
  case TOOL_NAMES.createDirectory:
133842
133930
  case TOOL_NAMES.deleteLocalFile:
133843
133931
  case TOOL_NAMES.printFile:
133844
- if (!isSafeRelativePath(args.path))
133845
- return `${name}.path must be a safe relative path.`;
133932
+ if (!isSafeFilePath(args.path))
133933
+ return `${name}.path must be a safe local path.`;
133846
133934
  return null;
133847
133935
  case TOOL_NAMES.editLocalFile:
133848
- if (!isSafeRelativePath(args.path))
133849
- return "editLocalFile.path must be a safe relative path.";
133936
+ if (!isSafeFilePath(args.path))
133937
+ return "editLocalFile.path must be a safe local path.";
133850
133938
  if (typeof args.oldText !== "string" || !args.oldText)
133851
133939
  return "editLocalFile.oldText must be a non-empty string.";
133852
133940
  if (typeof args.newText !== "string")
@@ -133871,8 +133959,8 @@ function validateArgs(name, args) {
133871
133959
  return "listLocalSources.maxResults must be a positive number.";
133872
133960
  return null;
133873
133961
  case TOOL_NAMES.readLocalSourceFile:
133874
- if (!isSafeLocalSourceId(args.localSourceId) && !isSafeRelativePath(args.localSourceId))
133875
- return "readLocalSourceFile.localSourceId must be a valid local source id or safe relative path.";
133962
+ if (!isSafeLocalSourceId(args.localSourceId) && !isSafeFilePath(args.localSourceId))
133963
+ return "readLocalSourceFile.localSourceId must be a valid local source id or safe local path.";
133876
133964
  return null;
133877
133965
  case TOOL_NAMES.generateAPAuditPacket:
133878
133966
  if (typeof args.folderPath !== "string" || !args.folderPath.trim())
@@ -134140,7 +134228,7 @@ function isSafeFilePath(value) {
134140
134228
  if (typeof value !== "string") return false;
134141
134229
  const trimmed = value.trim();
134142
134230
  if (!trimmed) return false;
134143
- if (/^file:/i.test(trimmed) || trimmed.includes("::") || trimmed === "~" || trimmed.startsWith("~/"))
134231
+ if (/^file:/i.test(trimmed) || trimmed.includes("::"))
134144
134232
  return false;
134145
134233
  return !trimmed.split(/[\\/]+/).includes("..");
134146
134234
  }
@@ -134423,13 +134511,13 @@ function getDesktopToolDefinitions() {
134423
134511
  type: "function",
134424
134512
  function: {
134425
134513
  name: TOOL_NAMES.writeLocalFile,
134426
- description: "Create or overwrite a local UTF-8 text file. Permission mode controls whether it runs immediately or asks for approval.",
134514
+ description: "Create or overwrite a local UTF-8 text file. Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Permission mode controls whether it runs immediately or asks for approval.",
134427
134515
  parameters: {
134428
134516
  type: "object",
134429
134517
  properties: {
134430
134518
  path: {
134431
134519
  type: "string",
134432
- description: "Workspace-relative path to write, such as intelligence_center_mockup.html or reports/summary.md. Do not pass an absolute /Users/... path."
134520
+ description: "Path to write. May be absolute, ~/..., or relative to the current local working directory."
134433
134521
  },
134434
134522
  content: {
134435
134523
  type: "string",
@@ -134449,17 +134537,17 @@ function getDesktopToolDefinitions() {
134449
134537
  type: "function",
134450
134538
  function: {
134451
134539
  name: TOOL_NAMES.moveLocalFile,
134452
- description: "Move or rename a file within the approved workspace. Write-tier risk.",
134540
+ description: "Move or rename a local file. Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Write-tier risk.",
134453
134541
  parameters: {
134454
134542
  type: "object",
134455
134543
  properties: {
134456
134544
  from: {
134457
134545
  type: "string",
134458
- description: "Workspace-relative source path to move."
134546
+ description: "Source path to move."
134459
134547
  },
134460
134548
  to: {
134461
134549
  type: "string",
134462
- description: "Workspace-relative destination path."
134550
+ description: "Destination path."
134463
134551
  }
134464
134552
  },
134465
134553
  required: ["from", "to"],
@@ -134471,17 +134559,17 @@ function getDesktopToolDefinitions() {
134471
134559
  type: "function",
134472
134560
  function: {
134473
134561
  name: TOOL_NAMES.copyLocalFile,
134474
- description: "Copy a file within the approved workspace. Write-tier risk.",
134562
+ description: "Copy a local file. Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Write-tier risk.",
134475
134563
  parameters: {
134476
134564
  type: "object",
134477
134565
  properties: {
134478
134566
  from: {
134479
134567
  type: "string",
134480
- description: "Workspace-relative source path to copy."
134568
+ description: "Source path to copy."
134481
134569
  },
134482
134570
  to: {
134483
134571
  type: "string",
134484
- description: "Workspace-relative destination path."
134572
+ description: "Destination path."
134485
134573
  }
134486
134574
  },
134487
134575
  required: ["from", "to"],
@@ -134493,13 +134581,13 @@ function getDesktopToolDefinitions() {
134493
134581
  type: "function",
134494
134582
  function: {
134495
134583
  name: TOOL_NAMES.createDirectory,
134496
- description: "Create a workspace-local directory recursively (mkdir -p). Write-tier risk.",
134584
+ description: "Create a local directory recursively (mkdir -p). Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Write-tier risk.",
134497
134585
  parameters: {
134498
134586
  type: "object",
134499
134587
  properties: {
134500
134588
  path: {
134501
134589
  type: "string",
134502
- description: "Workspace-relative directory path to create."
134590
+ description: "Directory path to create."
134503
134591
  }
134504
134592
  },
134505
134593
  required: ["path"],
@@ -134511,13 +134599,13 @@ function getDesktopToolDefinitions() {
134511
134599
  type: "function",
134512
134600
  function: {
134513
134601
  name: TOOL_NAMES.deleteLocalFile,
134514
- description: "Delete a single workspace-local file. Lower permission modes ask for approval; Take the Wheel runs it without asking.",
134602
+ description: "Delete a single local file. Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Lower permission modes ask for approval; Take the Wheel runs it without asking.",
134515
134603
  parameters: {
134516
134604
  type: "object",
134517
134605
  properties: {
134518
134606
  path: {
134519
134607
  type: "string",
134520
- description: "Workspace-relative file path to delete."
134608
+ description: "File path to delete."
134521
134609
  }
134522
134610
  },
134523
134611
  required: ["path"],
@@ -134529,13 +134617,13 @@ function getDesktopToolDefinitions() {
134529
134617
  type: "function",
134530
134618
  function: {
134531
134619
  name: TOOL_NAMES.printFile,
134532
- description: "Send a workspace-local file to the default printer using the system print command. Write-tier risk.",
134620
+ description: "Send a local file to the default printer using the system print command. Write-tier risk.",
134533
134621
  parameters: {
134534
134622
  type: "object",
134535
134623
  properties: {
134536
134624
  path: {
134537
134625
  type: "string",
134538
- description: "Workspace-relative file path to print."
134626
+ description: "File path to print."
134539
134627
  }
134540
134628
  },
134541
134629
  required: ["path"],
@@ -134547,13 +134635,13 @@ function getDesktopToolDefinitions() {
134547
134635
  type: "function",
134548
134636
  function: {
134549
134637
  name: TOOL_NAMES.editLocalFile,
134550
- description: "Exact find/replace edit in a workspace-local UTF-8 text file. Lower permission modes ask for approval; Take the Wheel runs it without asking.",
134638
+ description: "Exact find/replace edit in a local UTF-8 text file. Accepts absolute paths, ~/ paths, or paths relative to the current local working directory. Lower permission modes ask for approval; Take the Wheel runs it without asking.",
134551
134639
  parameters: {
134552
134640
  type: "object",
134553
134641
  properties: {
134554
134642
  path: {
134555
134643
  type: "string",
134556
- description: "Workspace-relative path to edit."
134644
+ description: "Path to edit."
134557
134645
  },
134558
134646
  oldText: { type: "string", description: "Exact text to replace." },
134559
134647
  newText: { type: "string", description: "Replacement text." },
@@ -135943,7 +136031,7 @@ function getNativeToolDefinitions() {
135943
136031
  type: "function",
135944
136032
  function: {
135945
136033
  name: TOOL_NAMES.configInspect,
135946
- description: "Inspect the Perch Terminal runtime configuration: execution host, Desktop bridge status, optional folder scope, permission mode, and capabilities.",
136034
+ description: "Inspect the Perch Terminal runtime configuration: execution host, local runtime status, optional folder scope, permission mode, and capabilities.",
135947
136035
  parameters: {
135948
136036
  type: "object",
135949
136037
  properties: {},
@@ -136846,7 +136934,7 @@ function notifyApprovedRootsChanged() {
136846
136934
  }
136847
136935
  async function pickFolder(request) {
136848
136936
  const bridge = getDesktopBridge();
136849
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
136937
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136850
136938
  const result2 = await bridge.pickFolder({
136851
136939
  permissionMode: currentPermissionMode(),
136852
136940
  defaultPath: request?.defaultPath ?? null,
@@ -136859,7 +136947,7 @@ async function pickFolder(request) {
136859
136947
  }
136860
136948
  async function approveFolderAccess(path14, options) {
136861
136949
  const bridge = getDesktopBridge();
136862
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
136950
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136863
136951
  const result2 = await bridge.approveFolderAccess({
136864
136952
  path: path14,
136865
136953
  permissionMode: currentPermissionMode(),
@@ -136873,7 +136961,7 @@ async function checkFsAccess(request) {
136873
136961
  if (!bridge) {
136874
136962
  return {
136875
136963
  decision: "block",
136876
- reason: "Desktop bridge not available",
136964
+ reason: "Local runtime not available",
136877
136965
  absolutePath: request.absolutePath,
136878
136966
  approvalPath: request.absolutePath
136879
136967
  };
@@ -136892,7 +136980,7 @@ async function checkFullDiskAccess() {
136892
136980
  }
136893
136981
  async function resolveDefaultSavePath(request) {
136894
136982
  const bridge = getDesktopBridge();
136895
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
136983
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136896
136984
  return bridge.resolveDefaultSavePath(request);
136897
136985
  }
136898
136986
  async function getApprovedRoots() {
@@ -136902,17 +136990,17 @@ async function getApprovedRoots() {
136902
136990
  }
136903
136991
  async function removeRoot(rootId) {
136904
136992
  const bridge = getDesktopBridge();
136905
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
136993
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136906
136994
  return bridge.removeRoot(rootId);
136907
136995
  }
136908
136996
  async function listFiles(rootId, relativePath, depth) {
136909
136997
  const bridge = getDesktopBridge();
136910
- if (!bridge) return { ok: false, rootId, basePath: "", entries: [], truncated: false, error: "Desktop bridge not available" };
136998
+ if (!bridge) return { ok: false, rootId, basePath: "", entries: [], truncated: false, error: "Local runtime not available" };
136911
136999
  return bridge.listFiles(withPermissionMode({ rootId, relativePath, depth }));
136912
137000
  }
136913
137001
  async function readFile2(rootId, relativePath) {
136914
137002
  const bridge = getDesktopBridge();
136915
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137003
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136916
137004
  return bridge.readFile(withPermissionMode({ rootId, relativePath }));
136917
137005
  }
136918
137006
  function normalizeLegacyListResult(result2, request) {
@@ -136948,7 +137036,7 @@ async function listLocalSourcesDetailed(request) {
136948
137036
  query: typeof request === "object" && request ? request.query ?? null : null,
136949
137037
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
136950
137038
  maxSources: 0,
136951
- warning: "Desktop bridge not available"
137039
+ warning: "Local runtime not available"
136952
137040
  };
136953
137041
  }
136954
137042
  const raw = await bridge.listLocalSources(request);
@@ -136956,7 +137044,7 @@ async function listLocalSourcesDetailed(request) {
136956
137044
  }
136957
137045
  async function readLocalSourceFile(localSourceId) {
136958
137046
  const bridge = getDesktopBridge();
136959
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137047
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136960
137048
  return bridge.readLocalFile(localSourceId, { permissionMode: currentPermissionMode() });
136961
137049
  }
136962
137050
  async function getProjectRules(rootId) {
@@ -136966,37 +137054,37 @@ async function getProjectRules(rootId) {
136966
137054
  }
136967
137055
  async function readProjectMemory(rootId) {
136968
137056
  const bridge = getDesktopBridge();
136969
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137057
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136970
137058
  return bridge.readProjectMemory({ rootId });
136971
137059
  }
136972
137060
  async function writeProjectMemory(rootId, meta) {
136973
137061
  const bridge = getDesktopBridge();
136974
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137062
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136975
137063
  return bridge.writeProjectMemory({ rootId, meta });
136976
137064
  }
136977
137065
  async function writeMemoryFile(rootId, request) {
136978
137066
  const bridge = getDesktopBridge();
136979
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137067
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136980
137068
  return bridge.writeMemoryFile({ rootId, ...request });
136981
137069
  }
136982
137070
  async function writeRule(rootId, request) {
136983
137071
  const bridge = getDesktopBridge();
136984
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137072
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136985
137073
  return bridge.writeRule({ rootId, ...request });
136986
137074
  }
136987
137075
  async function writePerchMd(rootId, request) {
136988
137076
  const bridge = getDesktopBridge();
136989
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137077
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136990
137078
  return bridge.writePerchMd({ rootId, ...request });
136991
137079
  }
136992
137080
  async function readGlobalPerchMd() {
136993
137081
  const bridge = getDesktopBridge();
136994
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137082
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
136995
137083
  return bridge.readGlobalPerchMd();
136996
137084
  }
136997
137085
  async function writeGlobalPerchMd(content) {
136998
137086
  const bridge = getDesktopBridge();
136999
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137087
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137000
137088
  return bridge.writeGlobalPerchMd({ content });
137001
137089
  }
137002
137090
  async function getMcpStatus() {
@@ -137011,12 +137099,12 @@ async function listMcpTools() {
137011
137099
  }
137012
137100
  async function callMcpTool(request) {
137013
137101
  const bridge = getDesktopBridge();
137014
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137102
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137015
137103
  return bridge.callMcpTool(request);
137016
137104
  }
137017
137105
  async function callEmbeddedBrowserTool(request) {
137018
137106
  const bridge = getDesktopBridge();
137019
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137107
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137020
137108
  if (typeof bridge.callEmbeddedBrowserTool !== "function") {
137021
137109
  return { ok: false, error: "Embedded browser bridge not available in this desktop build" };
137022
137110
  }
@@ -137024,7 +137112,7 @@ async function callEmbeddedBrowserTool(request) {
137024
137112
  }
137025
137113
  async function setEmbeddedBrowserVisible(open) {
137026
137114
  const bridge = getDesktopBridge();
137027
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137115
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137028
137116
  if (typeof bridge.setEmbeddedBrowserVisible !== "function") {
137029
137117
  return { ok: false, error: "Embedded browser bridge not available in this desktop build" };
137030
137118
  }
@@ -137047,7 +137135,7 @@ function onEmbeddedBrowserState(handler) {
137047
137135
  }
137048
137136
  async function reconnectMcp() {
137049
137137
  const bridge = getDesktopBridge();
137050
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137138
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137051
137139
  return bridge.reconnectMcp();
137052
137140
  }
137053
137141
  async function getOrStartBashTerminal(request) {
@@ -137092,37 +137180,37 @@ async function runLocalBash(request) {
137092
137180
  }
137093
137181
  async function readWorkspaceFile(request) {
137094
137182
  const bridge = getDesktopBridge();
137095
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137183
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137096
137184
  return bridge.readWorkspaceFile(withPermissionMode(request, request));
137097
137185
  }
137098
137186
  async function writeWorkspaceFile(request) {
137099
137187
  const bridge = getDesktopBridge();
137100
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137188
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137101
137189
  return bridge.writeWorkspaceFile(withPermissionMode(request, request));
137102
137190
  }
137103
137191
  async function moveLocalFile(request) {
137104
137192
  const bridge = getDesktopBridge();
137105
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137193
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137106
137194
  return bridge.moveLocalFile(withPermissionMode(request, request));
137107
137195
  }
137108
137196
  async function copyLocalFile(request) {
137109
137197
  const bridge = getDesktopBridge();
137110
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137198
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137111
137199
  return bridge.copyLocalFile(withPermissionMode(request, request));
137112
137200
  }
137113
137201
  async function createDirectory(request) {
137114
137202
  const bridge = getDesktopBridge();
137115
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137203
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137116
137204
  return bridge.createDirectory(withPermissionMode(request, request));
137117
137205
  }
137118
137206
  async function deleteLocalFile(request) {
137119
137207
  const bridge = getDesktopBridge();
137120
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137208
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137121
137209
  return bridge.deleteLocalFile(withPermissionMode(request, request));
137122
137210
  }
137123
137211
  async function printFile(request) {
137124
137212
  const bridge = getDesktopBridge();
137125
- if (!bridge) return { ok: false, error: "Desktop bridge not available" };
137213
+ if (!bridge) return { ok: false, error: "Local runtime not available" };
137126
137214
  return bridge.printFile(withPermissionMode(request, request));
137127
137215
  }
137128
137216
  async function listWorkspaceFilesGlob(request) {
@@ -137181,7 +137269,7 @@ async function runLocalAPAuditPacket(request) {
137181
137269
  if (!bridge) {
137182
137270
  return {
137183
137271
  ok: false,
137184
- error: "Desktop bridge not available",
137272
+ error: "Local runtime not available",
137185
137273
  errorCode: "desktop_bridge_unavailable",
137186
137274
  executionHost: "electron_desktop",
137187
137275
  durationMs: 0
@@ -137194,7 +137282,7 @@ async function runLocalPrepareAPEvidence(request) {
137194
137282
  if (!bridge?.runLocalPrepareAPEvidence) {
137195
137283
  return {
137196
137284
  ok: false,
137197
- error: "Desktop bridge AP evidence tool not available",
137285
+ error: "AP evidence tool not available in local runtime",
137198
137286
  errorCode: bridge ? "missing_dependency" : "desktop_bridge_unavailable",
137199
137287
  executionHost: "electron_desktop",
137200
137288
  durationMs: 0
@@ -137207,7 +137295,7 @@ async function runLocalQueryAPCases(request) {
137207
137295
  if (!bridge?.runLocalQueryAPCases) {
137208
137296
  return {
137209
137297
  ok: false,
137210
- error: "Desktop bridge AP case query tool not available",
137298
+ error: "AP case query tool not available in local runtime",
137211
137299
  errorCode: bridge ? "missing_dependency" : "desktop_bridge_unavailable",
137212
137300
  executionHost: "electron_desktop",
137213
137301
  durationMs: 0
@@ -137220,7 +137308,7 @@ async function runLocalRenderAPControlGraph(request) {
137220
137308
  if (!bridge?.runLocalRenderAPControlGraph) {
137221
137309
  return {
137222
137310
  ok: false,
137223
- error: "Desktop bridge AP control graph tool not available",
137311
+ error: "AP control graph tool not available in local runtime",
137224
137312
  errorCode: bridge ? "missing_dependency" : "desktop_bridge_unavailable",
137225
137313
  executionHost: "electron_desktop",
137226
137314
  durationMs: 0
@@ -137233,7 +137321,7 @@ async function runLocalPayrollRunArtifact(request) {
137233
137321
  if (!bridge) {
137234
137322
  return {
137235
137323
  ok: false,
137236
- error: "Desktop bridge not available",
137324
+ error: "Local runtime not available",
137237
137325
  errorCode: "desktop_bridge_unavailable",
137238
137326
  executionHost: "electron_desktop",
137239
137327
  durationMs: 0
@@ -137248,7 +137336,7 @@ function desktopGLReconcileAvailable() {
137248
137336
  async function runLocalGLReconcileArtifact(folderPath) {
137249
137337
  const bridge = getDesktopBridge();
137250
137338
  if (!bridge?.runLocalGLReconcileArtifact) {
137251
- return { ok: false, error: "Desktop bridge unavailable", errorCode: "desktop_bridge_unavailable", executionHost: "electron_desktop", durationMs: 0 };
137339
+ return { ok: false, error: "Local runtime unavailable", errorCode: "desktop_bridge_unavailable", executionHost: "electron_desktop", durationMs: 0 };
137252
137340
  }
137253
137341
  return bridge.runLocalGLReconcileArtifact({ folderPath });
137254
137342
  }
@@ -137259,7 +137347,7 @@ function desktopStatementAuditAvailable() {
137259
137347
  async function runLocalStatementAuditArtifact(folderPath) {
137260
137348
  const bridge = getDesktopBridge();
137261
137349
  if (!bridge?.runLocalStatementAuditArtifact) {
137262
- return { ok: false, error: "Desktop bridge unavailable", errorCode: "desktop_bridge_unavailable", executionHost: "electron_desktop", durationMs: 0 };
137350
+ return { ok: false, error: "Local runtime unavailable", errorCode: "desktop_bridge_unavailable", executionHost: "electron_desktop", durationMs: 0 };
137263
137351
  }
137264
137352
  return bridge.runLocalStatementAuditArtifact({ folderPath });
137265
137353
  }
@@ -137284,7 +137372,7 @@ async function runSandboxCodeJob(request) {
137284
137372
  kind: "blocked",
137285
137373
  error: bridge ? "capability_unavailable" : "not_desktop",
137286
137374
  executionHost: "electron-main",
137287
- message: bridge ? "This Perch AI Desktop bridge version does not expose runSandboxCodeJob." : "Perch AI Desktop bridge is unavailable."
137375
+ message: bridge ? "This Perch build does not expose runSandboxCodeJob." : "Local runtime is unavailable."
137288
137376
  };
137289
137377
  }
137290
137378
  return bridge.runSandboxCodeJob(request);
@@ -138738,8 +138826,8 @@ var init_capabilityBus = __esm({
138738
138826
  }
138739
138827
  let resolvePromise = () => {
138740
138828
  };
138741
- const promise = new Promise((resolve4) => {
138742
- resolvePromise = resolve4;
138829
+ const promise = new Promise((resolve5) => {
138830
+ resolvePromise = resolve5;
138743
138831
  });
138744
138832
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
138745
138833
  const request = {
@@ -138796,7 +138884,7 @@ var init_capabilityBus = __esm({
138796
138884
  return true;
138797
138885
  }
138798
138886
  getPending(sessionId) {
138799
- return Array.from(this.pending.values()).filter((entry) => !sessionId || entry.sessionId === sessionId).map(({ resolve: resolve4, promise, ...request }) => request);
138887
+ return Array.from(this.pending.values()).filter((entry) => !sessionId || entry.sessionId === sessionId).map(({ resolve: resolve5, promise, ...request }) => request);
138800
138888
  }
138801
138889
  dispatchBrowserEvent(type, payload) {
138802
138890
  if (typeof window === "undefined") return;
@@ -139236,7 +139324,7 @@ function createAbortableBrowserDriver(driver, signal, timeoutMs = DEFAULT_BROWSE
139236
139324
  };
139237
139325
  }
139238
139326
  function withAbortAndTimeout(promise, signal, timeoutMs, label) {
139239
- return new Promise((resolve4, reject2) => {
139327
+ return new Promise((resolve5, reject2) => {
139240
139328
  if (signal?.aborted) {
139241
139329
  reject2(abortError(`${label} cancelled.`));
139242
139330
  return;
@@ -139262,7 +139350,7 @@ function withAbortAndTimeout(promise, signal, timeoutMs, label) {
139262
139350
  );
139263
139351
  }
139264
139352
  promise.then(
139265
- (value) => finish(() => resolve4(value)),
139353
+ (value) => finish(() => resolve5(value)),
139266
139354
  (error) => finish(() => reject2(error))
139267
139355
  );
139268
139356
  });
@@ -140021,7 +140109,7 @@ async function maybeWaitForCapabilityAndRetry(failureKind, input, runtime, snaps
140021
140109
  function waitForAbortable(promise, signal) {
140022
140110
  if (!signal) return promise;
140023
140111
  if (signal.aborted) return Promise.reject(new DOMException("Browser action cancelled.", "AbortError"));
140024
- return new Promise((resolve4, reject2) => {
140112
+ return new Promise((resolve5, reject2) => {
140025
140113
  const onAbort = () => {
140026
140114
  signal.removeEventListener("abort", onAbort);
140027
140115
  reject2(new DOMException("Browser action cancelled.", "AbortError"));
@@ -140030,7 +140118,7 @@ function waitForAbortable(promise, signal) {
140030
140118
  promise.then(
140031
140119
  (value) => {
140032
140120
  signal.removeEventListener("abort", onAbort);
140033
- resolve4(value);
140121
+ resolve5(value);
140034
140122
  },
140035
140123
  (error) => {
140036
140124
  signal.removeEventListener("abort", onAbort);
@@ -140726,8 +140814,8 @@ function pasteShortcut() {
140726
140814
  }
140727
140815
  function browserShortcut(key) {
140728
140816
  const override = (typeof process !== "undefined" ? process.env?.PERCH_PLAYWRIGHT_OS : "")?.toLowerCase();
140729
- const platform2 = (typeof process !== "undefined" ? process.platform : "") ?? "";
140730
- const isMac = override === "mac" || override !== "windows" && override !== "linux" && platform2 === "darwin";
140817
+ const platform3 = (typeof process !== "undefined" ? process.platform : "") ?? "";
140818
+ const isMac = override === "mac" || override !== "windows" && override !== "linux" && platform3 === "darwin";
140731
140819
  return isMac ? `Meta+${key}` : `Control+${key}`;
140732
140820
  }
140733
140821
  function failureMessage(kind) {
@@ -140766,12 +140854,12 @@ function isAbortError(err) {
140766
140854
  return err instanceof DOMException && err.name === "AbortError";
140767
140855
  }
140768
140856
  function delay(ms, signal) {
140769
- return new Promise((resolve4, reject2) => {
140857
+ return new Promise((resolve5, reject2) => {
140770
140858
  if (signal?.aborted) {
140771
140859
  reject2(new DOMException("Browser action cancelled.", "AbortError"));
140772
140860
  return;
140773
140861
  }
140774
- const timeout = globalThis.setTimeout(resolve4, ms);
140862
+ const timeout = globalThis.setTimeout(resolve5, ms);
140775
140863
  signal?.addEventListener(
140776
140864
  "abort",
140777
140865
  () => {
@@ -141197,12 +141285,12 @@ function toStringArray2(value) {
141197
141285
  return result2.length > 0 ? result2 : void 0;
141198
141286
  }
141199
141287
  function pause(ms, signal) {
141200
- return new Promise((resolve4, reject2) => {
141288
+ return new Promise((resolve5, reject2) => {
141201
141289
  if (signal?.aborted) {
141202
141290
  reject2(new DOMException("Browser research cancelled.", "AbortError"));
141203
141291
  return;
141204
141292
  }
141205
- const timer = setTimeout(resolve4, ms);
141293
+ const timer = setTimeout(resolve5, ms);
141206
141294
  signal?.addEventListener(
141207
141295
  "abort",
141208
141296
  () => {
@@ -141589,7 +141677,7 @@ async function fetchData(url, type = "text") {
141589
141677
  }
141590
141678
  return response.text();
141591
141679
  }
141592
- return new Promise((resolve4, reject2) => {
141680
+ return new Promise((resolve5, reject2) => {
141593
141681
  const request = new XMLHttpRequest();
141594
141682
  request.open("GET", url, true);
141595
141683
  request.responseType = type;
@@ -141602,10 +141690,10 @@ async function fetchData(url, type = "text") {
141602
141690
  case "arraybuffer":
141603
141691
  case "blob":
141604
141692
  case "json":
141605
- resolve4(request.response);
141693
+ resolve5(request.response);
141606
141694
  return;
141607
141695
  }
141608
- resolve4(request.responseText);
141696
+ resolve5(request.responseText);
141609
141697
  return;
141610
141698
  }
141611
141699
  reject2(new Error(request.statusText));
@@ -144612,7 +144700,7 @@ var init_pdf = __esm({
144612
144700
  var defineProperty = Object.defineProperty;
144613
144701
  var stringSlice = uncurryThis("".slice);
144614
144702
  var replace = uncurryThis("".replace);
144615
- var join = uncurryThis([].join);
144703
+ var join2 = uncurryThis([].join);
144616
144704
  var CONFIGURABLE_LENGTH = DESCRIPTORS && !fails(function() {
144617
144705
  return defineProperty(function() {
144618
144706
  }, "length", { value: 8 }).length !== 8;
@@ -144639,7 +144727,7 @@ var init_pdf = __esm({
144639
144727
  }
144640
144728
  var state = enforceInternalState(value);
144641
144729
  if (!hasOwn(state, "source")) {
144642
- state.source = join(TEMPLATE, typeof name == "string" ? name : "");
144730
+ state.source = join2(TEMPLATE, typeof name == "string" ? name : "");
144643
144731
  }
144644
144732
  return value;
144645
144733
  };
@@ -144667,13 +144755,13 @@ var init_pdf = __esm({
144667
144755
  var aCallable = __webpack_require__2(9306);
144668
144756
  var $TypeError = TypeError;
144669
144757
  var PromiseCapability = function(C) {
144670
- var resolve4, reject2;
144758
+ var resolve5, reject2;
144671
144759
  this.promise = new C(function($$resolve, $$reject) {
144672
- if (resolve4 !== void 0 || reject2 !== void 0) throw new $TypeError("Bad Promise constructor");
144673
- resolve4 = $$resolve;
144760
+ if (resolve5 !== void 0 || reject2 !== void 0) throw new $TypeError("Bad Promise constructor");
144761
+ resolve5 = $$resolve;
144674
144762
  reject2 = $$reject;
144675
144763
  });
144676
- this.resolve = aCallable(resolve4);
144764
+ this.resolve = aCallable(resolve5);
144677
144765
  this.reject = aCallable(reject2);
144678
144766
  };
144679
144767
  module2.exports.f = function(C) {
@@ -147791,11 +147879,11 @@ var init_pdf = __esm({
147791
147879
  const mustRemoveAspectRatioPromise = _ImageManager._isSVGFittingCanvas;
147792
147880
  const fileReader = new FileReader();
147793
147881
  const imageElement = new Image();
147794
- const imagePromise = new Promise((resolve4, reject2) => {
147882
+ const imagePromise = new Promise((resolve5, reject2) => {
147795
147883
  imageElement.onload = () => {
147796
147884
  data.bitmap = imageElement;
147797
147885
  data.isSvg = true;
147798
- resolve4();
147886
+ resolve5();
147799
147887
  };
147800
147888
  fileReader.onload = async () => {
147801
147889
  const url = data.svgUrl = fileReader.result;
@@ -151644,8 +151732,8 @@ var init_pdf = __esm({
151644
151732
  if (this.isSyncFontLoadingSupported) {
151645
151733
  return;
151646
151734
  }
151647
- await new Promise((resolve4) => {
151648
- const request = this._queueLoadingCallback(resolve4);
151735
+ await new Promise((resolve5) => {
151736
+ const request = this._queueLoadingCallback(resolve5);
151649
151737
  this._prepareFontLoadEvent(font, request);
151650
151738
  });
151651
151739
  }
@@ -155985,8 +156073,8 @@ var init_pdf = __esm({
155985
156073
  this._readCapability = Promise.withResolvers();
155986
156074
  this._headersCapability = Promise.withResolvers();
155987
156075
  const fs14 = process.getBuiltinModule("fs");
155988
- fs14.promises.lstat(this._url).then((stat) => {
155989
- this._contentLength = stat.size;
156076
+ fs14.promises.lstat(this._url).then((stat2) => {
156077
+ this._contentLength = stat2.size;
155990
156078
  this._setReadableStream(fs14.createReadStream(this._url));
155991
156079
  this._headersCapability.resolve();
155992
156080
  }, (error) => {
@@ -157002,14 +157090,14 @@ var init_pdf = __esm({
157002
157090
  return this.getXfa().then((xfa) => XfaText.textContent(xfa));
157003
157091
  }
157004
157092
  const readableStream = this.streamTextContent(params);
157005
- return new Promise(function(resolve4, reject2) {
157093
+ return new Promise(function(resolve5, reject2) {
157006
157094
  function pump() {
157007
157095
  reader.read().then(function({
157008
157096
  value,
157009
157097
  done
157010
157098
  }) {
157011
157099
  if (done) {
157012
- resolve4(textContent);
157100
+ resolve5(textContent);
157013
157101
  return;
157014
157102
  }
157015
157103
  textContent.lang ??= value.lang;
@@ -165106,7 +165194,7 @@ var init_pdf = __esm({
165106
165194
  input.type = "file";
165107
165195
  input.accept = _StampEditor.supportedTypesStr;
165108
165196
  const signal = this._uiManager._signal;
165109
- this.#bitmapPromise = new Promise((resolve4) => {
165197
+ this.#bitmapPromise = new Promise((resolve5) => {
165110
165198
  input.addEventListener("change", async () => {
165111
165199
  if (!input.files || input.files.length === 0) {
165112
165200
  this.remove();
@@ -165121,13 +165209,13 @@ var init_pdf = __esm({
165121
165209
  });
165122
165210
  this.#getBitmapFetched(data);
165123
165211
  }
165124
- resolve4();
165212
+ resolve5();
165125
165213
  }, {
165126
165214
  signal
165127
165215
  });
165128
165216
  input.addEventListener("cancel", () => {
165129
165217
  this.remove();
165130
- resolve4();
165218
+ resolve5();
165131
165219
  }, {
165132
165220
  signal
165133
165221
  });
@@ -170434,13 +170522,13 @@ var require_thenables = __commonJS({
170434
170522
  promise._captureStackTrace();
170435
170523
  if (context) context._popContext();
170436
170524
  var synchronous = true;
170437
- var result2 = util.tryCatch(then).call(x, resolve4, reject2);
170525
+ var result2 = util.tryCatch(then).call(x, resolve5, reject2);
170438
170526
  synchronous = false;
170439
170527
  if (promise && result2 === errorObj2) {
170440
170528
  promise._rejectCallback(result2.e, true, true);
170441
170529
  promise = null;
170442
170530
  }
170443
- function resolve4(value) {
170531
+ function resolve5(value) {
170444
170532
  if (!promise) return;
170445
170533
  promise._resolveCallback(value);
170446
170534
  promise = null;
@@ -170975,9 +171063,9 @@ var require_debuggability = __commonJS({
170975
171063
  return false;
170976
171064
  }
170977
171065
  Promise2.prototype._fireEvent = defaultFireEvent;
170978
- Promise2.prototype._execute = function(executor, resolve4, reject2) {
171066
+ Promise2.prototype._execute = function(executor, resolve5, reject2) {
170979
171067
  try {
170980
- executor(resolve4, reject2);
171068
+ executor(resolve5, reject2);
170981
171069
  } catch (e) {
170982
171070
  return e;
170983
171071
  }
@@ -171000,10 +171088,10 @@ var require_debuggability = __commonJS({
171000
171088
  ;
171001
171089
  ;
171002
171090
  };
171003
- function cancellationExecute(executor, resolve4, reject2) {
171091
+ function cancellationExecute(executor, resolve5, reject2) {
171004
171092
  var promise = this;
171005
171093
  try {
171006
- executor(resolve4, reject2, function(onCancel) {
171094
+ executor(resolve5, reject2, function(onCancel) {
171007
171095
  if (typeof onCancel !== "function") {
171008
171096
  throw new TypeError("onCancel must be a function, got: " + util.toString(onCancel));
171009
171097
  }
@@ -173026,15 +173114,15 @@ var require_generators = __commonJS({
173026
173114
  var stack = new Error().stack;
173027
173115
  return function() {
173028
173116
  var generator = generatorFunction.apply(this, arguments);
173029
- var spawn2 = new PromiseSpawn$(
173117
+ var spawn3 = new PromiseSpawn$(
173030
173118
  void 0,
173031
173119
  void 0,
173032
173120
  yieldHandler,
173033
173121
  stack
173034
173122
  );
173035
- var ret2 = spawn2.promise();
173036
- spawn2._generator = generator;
173037
- spawn2._promiseFulfilled(void 0);
173123
+ var ret2 = spawn3.promise();
173124
+ spawn3._generator = generator;
173125
+ spawn3._promiseFulfilled(void 0);
173038
173126
  return ret2;
173039
173127
  };
173040
173128
  };
@@ -173049,9 +173137,9 @@ var require_generators = __commonJS({
173049
173137
  if (typeof generatorFunction !== "function") {
173050
173138
  return apiRejection("generatorFunction must be a function\n\n See http://goo.gl/MqrFmX\n");
173051
173139
  }
173052
- var spawn2 = new PromiseSpawn(generatorFunction, this);
173053
- var ret2 = spawn2.promise();
173054
- spawn2._run(Promise2.spawn);
173140
+ var spawn3 = new PromiseSpawn(generatorFunction, this);
173141
+ var ret2 = spawn3.promise();
173142
+ spawn3._run(Promise2.spawn);
173055
173143
  return ret2;
173056
173144
  };
173057
173145
  };
@@ -174649,14 +174737,14 @@ var require_promises = __commonJS({
174649
174737
  });
174650
174738
  };
174651
174739
  function defer() {
174652
- var resolve4;
174740
+ var resolve5;
174653
174741
  var reject2;
174654
174742
  var promise = new bluebird.Promise(function(resolveArg, rejectArg) {
174655
- resolve4 = resolveArg;
174743
+ resolve5 = resolveArg;
174656
174744
  reject2 = rejectArg;
174657
174745
  });
174658
174746
  return {
174659
- resolve: resolve4,
174747
+ resolve: resolve5,
174660
174748
  reject: reject2,
174661
174749
  promise
174662
174750
  };
@@ -175345,7 +175433,7 @@ var require_BufferList = __commonJS({
175345
175433
  this.head = this.tail = null;
175346
175434
  this.length = 0;
175347
175435
  };
175348
- BufferList.prototype.join = function join(s) {
175436
+ BufferList.prototype.join = function join2(s) {
175349
175437
  if (this.length === 0) return "";
175350
175438
  var p = this.head;
175351
175439
  var ret2 = "" + p.data;
@@ -177320,8 +177408,8 @@ var require_lib4 = __commonJS({
177320
177408
  return this;
177321
177409
  }
177322
177410
  var p = this.constructor;
177323
- return this.then(resolve5, reject3);
177324
- function resolve5(value) {
177411
+ return this.then(resolve6, reject3);
177412
+ function resolve6(value) {
177325
177413
  function yes() {
177326
177414
  return value;
177327
177415
  }
@@ -177474,8 +177562,8 @@ var require_lib4 = __commonJS({
177474
177562
  }
177475
177563
  return out;
177476
177564
  }
177477
- Promise2.resolve = resolve4;
177478
- function resolve4(value) {
177565
+ Promise2.resolve = resolve5;
177566
+ function resolve5(value) {
177479
177567
  if (value instanceof this) {
177480
177568
  return value;
177481
177569
  }
@@ -178006,10 +178094,10 @@ var require_utils2 = __commonJS({
178006
178094
  var promise = external.Promise.resolve(inputData).then(function(data) {
178007
178095
  var isBlob = support.blob && (data instanceof Blob || ["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(data)) !== -1);
178008
178096
  if (isBlob && typeof FileReader !== "undefined") {
178009
- return new external.Promise(function(resolve4, reject2) {
178097
+ return new external.Promise(function(resolve5, reject2) {
178010
178098
  var reader = new FileReader();
178011
178099
  reader.onload = function(e) {
178012
- resolve4(e.target.result);
178100
+ resolve5(e.target.result);
178013
178101
  };
178014
178102
  reader.onerror = function(e) {
178015
178103
  reject2(e.target.error);
@@ -178564,7 +178652,7 @@ var require_StreamHelper = __commonJS({
178564
178652
  }
178565
178653
  }
178566
178654
  function accumulate(helper, updateCallback) {
178567
- return new external.Promise(function(resolve4, reject2) {
178655
+ return new external.Promise(function(resolve5, reject2) {
178568
178656
  var dataArray = [];
178569
178657
  var chunkType = helper._internalType, resultType = helper._outputType, mimeType = helper._mimeType;
178570
178658
  helper.on("data", function(data, meta) {
@@ -178578,7 +178666,7 @@ var require_StreamHelper = __commonJS({
178578
178666
  }).on("end", function() {
178579
178667
  try {
178580
178668
  var result2 = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType);
178581
- resolve4(result2);
178669
+ resolve5(result2);
178582
178670
  } catch (e) {
178583
178671
  reject2(e);
178584
178672
  }
@@ -183370,7 +183458,7 @@ var require_ZipFileWorker = __commonJS({
183370
183458
  var generateDosExternalFileAttr = function(dosPermissions) {
183371
183459
  return (dosPermissions || 0) & 63;
183372
183460
  };
183373
- var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform2, encodeFileName) {
183461
+ var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform3, encodeFileName) {
183374
183462
  var file = streamInfo["file"], compression = streamInfo["compression"], useCustomEncoding = encodeFileName !== utf8.utf8encode, encodedFileName = utils3.transformTo("string", encodeFileName(file.name)), utfEncodedFileName = utils3.transformTo("string", utf8.utf8encode(file.name)), comment = file.comment, encodedComment = utils3.transformTo("string", encodeFileName(comment)), utfEncodedComment = utils3.transformTo("string", utf8.utf8encode(comment)), useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, useUTF8ForComment = utfEncodedComment.length !== comment.length, dosTime, dosDate, extraFields = "", unicodePathExtraField = "", unicodeCommentExtraField = "", dir = file.dir, date = file.date;
183375
183463
  var dataInfo = {
183376
183464
  crc32: 0,
@@ -183394,7 +183482,7 @@ var require_ZipFileWorker = __commonJS({
183394
183482
  if (dir) {
183395
183483
  extFileAttr |= 16;
183396
183484
  }
183397
- if (platform2 === "UNIX") {
183485
+ if (platform3 === "UNIX") {
183398
183486
  versionMadeBy = 798;
183399
183487
  extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
183400
183488
  } else {
@@ -183479,11 +183567,11 @@ var require_ZipFileWorker = __commonJS({
183479
183567
  decToHex(streamInfo["uncompressedSize"], 4);
183480
183568
  return descriptor;
183481
183569
  };
183482
- function ZipFileWorker(streamFiles, comment, platform2, encodeFileName) {
183570
+ function ZipFileWorker(streamFiles, comment, platform3, encodeFileName) {
183483
183571
  GenericWorker.call(this, "ZipFileWorker");
183484
183572
  this.bytesWritten = 0;
183485
183573
  this.zipComment = comment;
183486
- this.zipPlatform = platform2;
183574
+ this.zipPlatform = platform3;
183487
183575
  this.encodeFileName = encodeFileName;
183488
183576
  this.streamFiles = streamFiles;
183489
183577
  this.accumulate = false;
@@ -184691,7 +184779,7 @@ var require_load = __commonJS({
184691
184779
  var Crc32Probe = require_Crc32Probe();
184692
184780
  var nodejsUtils = require_nodejsUtils();
184693
184781
  function checkEntryCRC32(zipEntry) {
184694
- return new external.Promise(function(resolve4, reject2) {
184782
+ return new external.Promise(function(resolve5, reject2) {
184695
184783
  var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
184696
184784
  worker.on("error", function(e) {
184697
184785
  reject2(e);
@@ -184699,7 +184787,7 @@ var require_load = __commonJS({
184699
184787
  if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) {
184700
184788
  reject2(new Error("Corrupted zip : CRC32 mismatch"));
184701
184789
  } else {
184702
- resolve4();
184790
+ resolve5();
184703
184791
  }
184704
184792
  }).resume();
184705
184793
  });
@@ -194523,7 +194611,7 @@ var require_files = __commonJS({
194523
194611
  var fs14 = __require("fs");
194524
194612
  var url = __require("url");
194525
194613
  var os6 = __require("os");
194526
- var dirname = __require("path").dirname;
194614
+ var dirname2 = __require("path").dirname;
194527
194615
  var resolvePath2 = __require("path").resolve;
194528
194616
  var isAbsolutePath2 = require_path_is_absolute();
194529
194617
  var promises = require_promises();
@@ -194538,10 +194626,10 @@ var require_files = __commonJS({
194538
194626
  }
194539
194627
  };
194540
194628
  }
194541
- var base = options.relativeToFile ? dirname(options.relativeToFile) : null;
194629
+ var base = options.relativeToFile ? dirname2(options.relativeToFile) : null;
194542
194630
  function read(uri, encoding) {
194543
194631
  return resolveUri(uri).then(function(path14) {
194544
- return readFile3(path14, encoding).caught(function(error) {
194632
+ return readFile4(path14, encoding).caught(function(error) {
194545
194633
  var message = "could not open external image: '" + uri + "' (document directory: '" + base + "')\n" + error.message;
194546
194634
  return promises.reject(new Error(message));
194547
194635
  });
@@ -194561,15 +194649,15 @@ var require_files = __commonJS({
194561
194649
  read
194562
194650
  };
194563
194651
  }
194564
- var readFile3 = promises.promisify(fs14.readFile.bind(fs14));
194565
- function uriToPath(uriString, platform2) {
194566
- if (!platform2) {
194567
- platform2 = os6.platform();
194652
+ var readFile4 = promises.promisify(fs14.readFile.bind(fs14));
194653
+ function uriToPath(uriString, platform3) {
194654
+ if (!platform3) {
194655
+ platform3 = os6.platform();
194568
194656
  }
194569
194657
  var uri = url.parse(uriString);
194570
194658
  if (isLocalFileUri(uri) || isRelativeUri(uri)) {
194571
194659
  var path14 = decodeURIComponent(uri.path);
194572
- if (platform2 === "win32" && /^\/[a-z]:/i.test(path14)) {
194660
+ if (platform3 === "win32" && /^\/[a-z]:/i.test(path14)) {
194573
194661
  return path14.slice(1);
194574
194662
  } else {
194575
194663
  return path14;
@@ -197278,10 +197366,10 @@ var require_unzip = __commonJS({
197278
197366
  var promises = require_promises();
197279
197367
  var zipfile = require_zipfile();
197280
197368
  exports2.openZip = openZip;
197281
- var readFile3 = promises.promisify(fs14.readFile);
197369
+ var readFile4 = promises.promisify(fs14.readFile);
197282
197370
  function openZip(options) {
197283
197371
  if (options.path) {
197284
- return readFile3(options.path).then(zipfile.openArrayBuffer);
197372
+ return readFile4(options.path).then(zipfile.openArrayBuffer);
197285
197373
  } else if (options.buffer) {
197286
197374
  return promises.resolve(zipfile.openArrayBuffer(options.buffer));
197287
197375
  } else if (options.file) {
@@ -198080,8 +198168,8 @@ async function resolvePlaybookInputs(args) {
198080
198168
  return {
198081
198169
  ok: false,
198082
198170
  status: "blocked",
198083
- errorCode: "desktop_bridge_unavailable",
198084
- message: "Managed playbooks require the Perch AI Desktop bridge for local source discovery and file reads.",
198171
+ errorCode: "local_runtime_unavailable",
198172
+ message: "Managed playbooks require the local runtime for source discovery and file reads.",
198085
198173
  folderPath: rawFolderPath,
198086
198174
  warnings: []
198087
198175
  };
@@ -198417,9 +198505,9 @@ async function resolveScopedLocalSourceId(localSourceIdOrRelativePath, scopedWor
198417
198505
  if (localSourceIdOrRelativePath.includes("::")) return localSourceIdOrRelativePath;
198418
198506
  const scope = await resolveLocalSourceScope(scopedWorkspaceRoot);
198419
198507
  if (!scope) return localSourceIdOrRelativePath;
198420
- const relative = normalizeRelativePath(localSourceIdOrRelativePath);
198508
+ const relative2 = normalizeRelativePath(localSourceIdOrRelativePath);
198421
198509
  const prefix = normalizeRelativePath(scope.relativePrefix);
198422
- const scopedRelative = [prefix, relative].filter(Boolean).join("/");
198510
+ const scopedRelative = [prefix, relative2].filter(Boolean).join("/");
198423
198511
  return `${scope.rootId}::${scopedRelative}`;
198424
198512
  }
198425
198513
  async function resolveApprovedLocalSourceIdForAbsolutePath(pathValue) {
@@ -199442,7 +199530,6 @@ function containsBrowserDeliveryTask(tasks) {
199442
199530
  var BROWSER_DELIVERY_ROLE_IDS;
199443
199531
  var init_browserDeliveryLock = __esm({
199444
199532
  "features/perchTerminal/agentPlatform/browserDeliveryLock.ts"() {
199445
- "use strict";
199446
199533
  BROWSER_DELIVERY_ROLE_IDS = /* @__PURE__ */ new Set([
199447
199534
  "doc_writer",
199448
199535
  "email_sender",
@@ -199813,6 +199900,30 @@ function abortRuntimeRun(runId, reason = "Run cancelled.") {
199813
199900
  cleanupRun(record);
199814
199901
  return true;
199815
199902
  }
199903
+ function submitRuntimeSteer(input) {
199904
+ const runId = input.runId?.trim();
199905
+ if (!runId) return { ok: false, error: "No active turn to steer." };
199906
+ if (input.expectedRunId && input.expectedRunId !== runId) {
199907
+ return { ok: false, error: `Expected active turn ${input.expectedRunId}, found ${runId}.` };
199908
+ }
199909
+ const record = getRuntimeRun(runId);
199910
+ if (!record || record.status !== "running" || record.controller.signal.aborted) {
199911
+ return { ok: false, error: "No active turn to steer." };
199912
+ }
199913
+ if (input.threadId && record.threadId && input.threadId !== record.threadId) {
199914
+ return { ok: false, error: "Active turn belongs to a different thread." };
199915
+ }
199916
+ const text = input.text.trim();
199917
+ if (!text) return { ok: false, error: "Steer input cannot be empty." };
199918
+ record.steerQueue.push({
199919
+ text,
199920
+ mode: input.mode ?? "append",
199921
+ clientMessageId: input.clientMessageId ?? null,
199922
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString()
199923
+ });
199924
+ record.updatedAt = nowMs();
199925
+ return { ok: true, turnId: runId };
199926
+ }
199816
199927
  function consumeRuntimeSteers(runId) {
199817
199928
  const record = getRuntimeRun(runId);
199818
199929
  if (!record || record.status !== "running" || record.controller.signal.aborted) return [];
@@ -201827,8 +201938,8 @@ function buildWorkerSourceManifest(input) {
201827
201938
  const exactNames = extractExplicitFileNames(input.objective);
201828
201939
  const maxEntries = Math.max(1, Math.min(200, input.maxEntries ?? 80));
201829
201940
  const candidates = sources.filter((source) => !isExcludedPath(source.relativePath)).filter((source) => {
201830
- const relative = normalize2(source.relativePath);
201831
- if (selectedPrefixes.length > 0 && selectedPrefixes.some((prefix) => relative.startsWith(`${prefix}/`) || relative === prefix)) {
201941
+ const relative2 = normalize2(source.relativePath);
201942
+ if (selectedPrefixes.length > 0 && selectedPrefixes.some((prefix) => relative2.startsWith(`${prefix}/`) || relative2 === prefix)) {
201832
201943
  return true;
201833
201944
  }
201834
201945
  if (exactNames.size > 0 && exactNames.has(source.fileName.toLowerCase())) {
@@ -201893,17 +202004,17 @@ function extractExplicitFileNames(objective) {
201893
202004
  return names;
201894
202005
  }
201895
202006
  function sourceSortScore(source, objective) {
201896
- const relative = normalize2(source.relativePath);
202007
+ const relative2 = normalize2(source.relativePath);
201897
202008
  const file = source.fileName.toLowerCase();
201898
202009
  let score = 0;
201899
202010
  if (objective.includes(file)) score += 100;
201900
- if (relative.includes("perch-output") || relative.includes("perch-artifacts")) score -= 1e3;
202011
+ if (relative2.includes("perch-output") || relative2.includes("perch-artifacts")) score -= 1e3;
201901
202012
  if (/_\d{3}\.(?:txt|csv|md)$/i.test(file)) score -= 15;
201902
202013
  if (file.endsWith(".csv")) score += 8;
201903
202014
  if (file.endsWith(".txt") || file.endsWith(".md")) score += 5;
201904
202015
  if (file.endsWith(".pdf")) score -= 3;
201905
202016
  for (const token of objective.split(/[^a-z0-9]+/i).filter((t) => t.length >= 4)) {
201906
- if (relative.includes(token.toLowerCase())) score += 2;
202017
+ if (relative2.includes(token.toLowerCase())) score += 2;
201907
202018
  }
201908
202019
  return score;
201909
202020
  }
@@ -202331,13 +202442,13 @@ function clamp2(value, min2, max2) {
202331
202442
  return Math.max(min2, Math.min(max2, value));
202332
202443
  }
202333
202444
  function loadImage(dataUrl) {
202334
- return new Promise((resolve4, reject2) => {
202445
+ return new Promise((resolve5, reject2) => {
202335
202446
  if (typeof Image === "undefined") {
202336
202447
  reject2(new Error("Image API unavailable"));
202337
202448
  return;
202338
202449
  }
202339
202450
  const image2 = new Image();
202340
- image2.onload = () => resolve4(image2);
202451
+ image2.onload = () => resolve5(image2);
202341
202452
  image2.onerror = () => reject2(new Error("Failed to decode screenshot"));
202342
202453
  image2.src = dataUrl;
202343
202454
  });
@@ -202465,9 +202576,9 @@ function fitInside(width, height, maxWidth, maxHeight) {
202465
202576
  };
202466
202577
  }
202467
202578
  async function loadImageElement(dataUrl) {
202468
- return new Promise((resolve4, reject2) => {
202579
+ return new Promise((resolve5, reject2) => {
202469
202580
  const img = new Image();
202470
- img.onload = () => resolve4(img);
202581
+ img.onload = () => resolve5(img);
202471
202582
  img.onerror = () => reject2(new Error("Failed to decode screenshot data URL"));
202472
202583
  img.src = dataUrl;
202473
202584
  });
@@ -203689,14 +203800,14 @@ function adapterDriver(ctx) {
203689
203800
  );
203690
203801
  }
203691
203802
  function adapterDelay(ms, signal) {
203692
- return new Promise((resolve4, reject2) => {
203803
+ return new Promise((resolve5, reject2) => {
203693
203804
  if (signal?.aborted) {
203694
203805
  reject2(new DOMException("Adapter wait cancelled.", "AbortError"));
203695
203806
  return;
203696
203807
  }
203697
203808
  const timeout = setTimeout(() => {
203698
203809
  signal?.removeEventListener("abort", onAbort);
203699
- resolve4();
203810
+ resolve5();
203700
203811
  }, ms);
203701
203812
  const onAbort = () => {
203702
203813
  clearTimeout(timeout);
@@ -205289,8 +205400,8 @@ var init_GoogleDocsAdapter = __esm({
205289
205400
 
205290
205401
  // features/perchTerminal/runtime/playwright/adapters/GoogleSheetsAdapter.ts
205291
205402
  function shortcut2(key) {
205292
- const platform2 = typeof navigator === "undefined" ? "" : navigator.platform;
205293
- return /mac/i.test(platform2) ? `Meta+${key}` : `Control+${key}`;
205403
+ const platform3 = typeof navigator === "undefined" ? "" : navigator.platform;
205404
+ return /mac/i.test(platform3) ? `Meta+${key}` : `Control+${key}`;
205294
205405
  }
205295
205406
  var GoogleSheetsAdapter;
205296
205407
  var init_GoogleSheetsAdapter = __esm({
@@ -205861,14 +205972,14 @@ async function runAPAuditPacketAdapter(args) {
205861
205972
  const folderPath = (args.folderPath ?? "").trim();
205862
205973
  if (!isDesktopAvailable()) {
205863
205974
  return blockedEnvelope({
205864
- reason: "AP audit packet requires the Perch AI Desktop bridge for local filesystem access. The browser cannot read the user's filesystem.",
205865
- errorCode: "desktop_bridge_unavailable",
205975
+ reason: "AP audit packet requires the local runtime for filesystem access. The browser cannot read the user's filesystem.",
205976
+ errorCode: "local_runtime_unavailable",
205866
205977
  folderPath: folderPath || null
205867
205978
  });
205868
205979
  }
205869
205980
  if (!desktopAPAuditAvailable()) {
205870
205981
  return blockedEnvelope({
205871
- reason: "This Perch AI Desktop bridge version does not expose runLocalAPAuditPacket. Update Perch AI Desktop to a build that includes the AP audit capability.",
205982
+ reason: "This Perch build does not expose runLocalAPAuditPacket. Update Perch CLI or rebuild the app package.",
205872
205983
  errorCode: "capability_unavailable",
205873
205984
  folderPath: folderPath || null
205874
205985
  });
@@ -206091,14 +206202,14 @@ function preflight(toolName) {
206091
206202
  if (!isDesktopAvailable()) {
206092
206203
  return blocked({
206093
206204
  toolName,
206094
- reason: "AP evidence tools require the Perch AI Desktop bridge for local filesystem access.",
206095
- errorCode: "desktop_bridge_unavailable"
206205
+ reason: "AP evidence tools require the local runtime for filesystem access.",
206206
+ errorCode: "local_runtime_unavailable"
206096
206207
  });
206097
206208
  }
206098
206209
  if (!desktopAPEvidenceAvailable()) {
206099
206210
  return blocked({
206100
206211
  toolName,
206101
- reason: "This Perch AI Desktop bridge version does not expose AP evidence tools. Update Desktop or run npm run desktop:compile.",
206212
+ reason: "This Perch build does not expose AP evidence tools. Update Perch CLI or rebuild the app package.",
206102
206213
  errorCode: "capability_unavailable"
206103
206214
  });
206104
206215
  }
@@ -206849,13 +206960,13 @@ function blocked2(reason, errorCode) {
206849
206960
  async function runSandboxAnalysisAdapter(args, opts) {
206850
206961
  if (!isDesktopAvailable()) {
206851
206962
  return blocked2(
206852
- "runSandboxAnalysis requires the Perch AI Desktop bridge. The browser cannot execute sandbox jobs against local sources.",
206853
- "desktop_bridge_unavailable"
206963
+ "runSandboxAnalysis requires the local runtime. The browser cannot execute sandbox jobs against local sources.",
206964
+ "local_runtime_unavailable"
206854
206965
  );
206855
206966
  }
206856
206967
  if (!desktopSandboxAnalysisAvailable()) {
206857
206968
  return blocked2(
206858
- "This Perch AI Desktop bridge version does not expose runSandboxAnalysisJob. Update Perch AI Desktop to a build that includes the sandbox analysis capability.",
206969
+ "This Perch build does not expose runSandboxAnalysisJob. Update Perch CLI or rebuild the app package.",
206859
206970
  "capability_unavailable"
206860
206971
  );
206861
206972
  }
@@ -210307,6 +210418,7 @@ function buildContextSnapshotFromMetrics(threadId, metrics) {
210307
210418
  consecutiveAutoCompactFailures: metrics.consecutiveAutoCompactFailures ?? 0,
210308
210419
  autoCompactedAtIso: metrics.autoCompactedAtIso ?? null,
210309
210420
  autoCompactedMessageCount: metrics.autoCompactedMessageCount,
210421
+ compactionReport: metrics.compactionReport,
210310
210422
  contextResetAtIso: metrics.resetAtIso ?? null,
210311
210423
  builtAtIso: builtAt,
210312
210424
  createdAt: builtAt
@@ -210357,7 +210469,7 @@ function validateSuitePlan(plan, opts) {
210357
210469
  };
210358
210470
  }
210359
210471
  if (opts?.desktopConnected === false) {
210360
- return { valid: false, reason: "Desktop bridge required for local workflow tools." };
210472
+ return { valid: false, reason: "Local runtime required for workflow tools." };
210361
210473
  }
210362
210474
  return { valid: true };
210363
210475
  }
@@ -212685,6 +212797,29 @@ var init_localSources = __esm({
212685
212797
  classification: { native: false },
212686
212798
  handler: async (args, ctx) => {
212687
212799
  const workspaceRoot = effectiveWorkspaceRoot(ctx);
212800
+ const explicitPath = typeof args.path === "string" && args.path.trim() ? args.path.trim() : "";
212801
+ if (explicitPath) {
212802
+ const query2 = typeof args.query === "string" ? args.query.trim().toLowerCase() : "";
212803
+ const maxResults2 = sanitizeListLocalSourcesMaxResults(args.maxResults);
212804
+ const globbed = await listWorkspaceFilesGlob({
212805
+ workspaceRoot: explicitPath,
212806
+ pattern: "*",
212807
+ maxResults: MAX_LIST_LOCAL_SOURCES_MAX_RESULTS,
212808
+ fsAccessOverride: "allow_once"
212809
+ });
212810
+ const allMatches = globbed?.matches ?? [];
212811
+ const filtered = (query2 ? allMatches.filter((m) => m.toLowerCase().includes(query2)) : allMatches).slice(0, maxResults2);
212812
+ return {
212813
+ ok: true,
212814
+ scopedFolderPath: explicitPath,
212815
+ viaGlob: true,
212816
+ query: query2 || null,
212817
+ sources: filtered,
212818
+ matches: filtered,
212819
+ totalFound: globbed?.totalFound ?? allMatches.length,
212820
+ truncated: allMatches.length > maxResults2
212821
+ };
212822
+ }
212688
212823
  const scope = await resolveLocalSourceScope(workspaceRoot);
212689
212824
  if (!scope) {
212690
212825
  const globRoot = typeof args.path === "string" && args.path.trim() || workspaceRoot?.trim() || "";
@@ -212746,7 +212881,7 @@ var init_localSources = __esm({
212746
212881
  const requestedLocalSourceId = String(
212747
212882
  args.localSourceId ?? args.path ?? args.filePath ?? ""
212748
212883
  );
212749
- const looksLikePath = isAbsolute3(requestedLocalSourceId.trim()) || !requestedLocalSourceId.includes("::") && requestedLocalSourceId.includes("/");
212884
+ const looksLikePath = isAbsolute3(requestedLocalSourceId.trim()) || requestedLocalSourceId.trim().startsWith("~/") || !requestedLocalSourceId.includes("::") && requestedLocalSourceId.includes("/");
212750
212885
  if (looksLikePath) {
212751
212886
  const r = await readWorkspaceFile({
212752
212887
  workspaceRoot,
@@ -213026,7 +213161,7 @@ var init_config2 = __esm({
213026
213161
  ok: true,
213027
213162
  configured: false,
213028
213163
  servers: [],
213029
- note: "Desktop bridge required for MCP."
213164
+ note: "MCP servers are not connected in this runtime."
213030
213165
  };
213031
213166
  }
213032
213167
  const { getMcpStatus: getMcpStatus2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
@@ -213861,7 +213996,7 @@ async function executeToolCall(call, opts) {
213861
213996
  startMs,
213862
213997
  startedAt,
213863
213998
  code: "tool_desktop_required",
213864
- message: "Desktop bridge not available. Local tools require Perch AI Desktop.",
213999
+ message: "Local runtime not available. Local tools require Perch Desktop or CLI local mode.",
213865
214000
  riskLevel: policy.riskLevel,
213866
214001
  desktopRequired: true
213867
214002
  });
@@ -214051,7 +214186,7 @@ async function resolveFsAccessForTool(input) {
214051
214186
  function waitForCapabilityResolution(promise, signal) {
214052
214187
  if (!signal) return promise;
214053
214188
  if (signal.aborted) return Promise.reject(new DOMException("Capability wait cancelled.", "AbortError"));
214054
- return new Promise((resolve4, reject2) => {
214189
+ return new Promise((resolve5, reject2) => {
214055
214190
  const onAbort = () => {
214056
214191
  signal.removeEventListener("abort", onAbort);
214057
214192
  reject2(new DOMException("Capability wait cancelled.", "AbortError"));
@@ -214060,7 +214195,7 @@ function waitForCapabilityResolution(promise, signal) {
214060
214195
  promise.then(
214061
214196
  (value) => {
214062
214197
  signal.removeEventListener("abort", onAbort);
214063
- resolve4(value);
214198
+ resolve5(value);
214064
214199
  },
214065
214200
  (error) => {
214066
214201
  signal.removeEventListener("abort", onAbort);
@@ -214799,14 +214934,103 @@ function pruneBrowserArtifactsInMessages(messages) {
214799
214934
  }
214800
214935
  return pruned;
214801
214936
  }
214802
- function splitMessagesForCompaction(wireMessages, keepCount) {
214803
- if (wireMessages.length <= keepCount) {
214804
- return { toSummarize: [], recent: wireMessages };
214937
+ function resolvePostCompactTargetTokens(contextLimitTokens) {
214938
+ const limit = Number.isFinite(contextLimitTokens) && contextLimitTokens > 0 ? Math.floor(contextLimitTokens) : 128e3;
214939
+ const windowScaled = Math.floor(limit * 0.25);
214940
+ return Math.max(
214941
+ POST_COMPACT_MIN_TARGET_TOKENS,
214942
+ Math.min(
214943
+ POST_COMPACT_MAX_TARGET_TOKENS,
214944
+ POST_COMPACT_TARGET_TOKENS,
214945
+ windowScaled
214946
+ )
214947
+ );
214948
+ }
214949
+ function estimateSingleMessageTokens(message, modelContext) {
214950
+ const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "");
214951
+ let tokens = tokenizeText(`${message.role}: ${content}`, modelContext).tokenCount;
214952
+ if (message.tool_calls?.length) {
214953
+ tokens += tokenizeText(JSON.stringify(message.tool_calls), modelContext).tokenCount;
214805
214954
  }
214806
- const splitAt = wireMessages.length - keepCount;
214955
+ return Math.max(1, tokens);
214956
+ }
214957
+ function adjustTailStartForToolPairs(messages, startIndex) {
214958
+ if (startIndex <= 0) return startIndex;
214959
+ const missingToolCallIds = /* @__PURE__ */ new Set();
214960
+ for (let i = startIndex; i < messages.length; i += 1) {
214961
+ const msg = messages[i];
214962
+ if (msg?.role === "tool" && msg.tool_call_id) {
214963
+ missingToolCallIds.add(msg.tool_call_id);
214964
+ }
214965
+ if (msg?.role === "assistant" && msg.tool_calls?.length) {
214966
+ for (const call of msg.tool_calls) missingToolCallIds.delete(call.id);
214967
+ }
214968
+ }
214969
+ if (missingToolCallIds.size === 0) return startIndex;
214970
+ let nextStart = startIndex;
214971
+ for (let i = startIndex - 1; i >= 0 && missingToolCallIds.size > 0; i -= 1) {
214972
+ const msg = messages[i];
214973
+ if (msg.role !== "assistant" || !msg.tool_calls?.length) continue;
214974
+ const matches = msg.tool_calls.some((call) => missingToolCallIds.has(call.id));
214975
+ if (!matches) continue;
214976
+ nextStart = i;
214977
+ for (const call of msg.tool_calls) missingToolCallIds.delete(call.id);
214978
+ }
214979
+ return nextStart;
214980
+ }
214981
+ function selectRecentWireMessagesByTokenBudget(wireMessages, input) {
214982
+ if (wireMessages.length === 0) {
214983
+ return { recent: [], recentTailTokens: 0, startIndex: 0 };
214984
+ }
214985
+ const minMessages = Math.max(1, input.minMessages ?? RECENT_MESSAGES_KEEP_COUNT);
214986
+ const tokenBudget = Math.max(1, Math.floor(input.tokenBudget));
214987
+ let startIndex = wireMessages.length;
214988
+ let tokens = 0;
214989
+ for (let i = wireMessages.length - 1; i >= 0; i -= 1) {
214990
+ const messageTokens = estimateSingleMessageTokens(wireMessages[i], input.modelContext);
214991
+ const selectedCount = wireMessages.length - startIndex;
214992
+ const mustKeep = selectedCount === 0;
214993
+ if (!mustKeep && tokens + messageTokens > tokenBudget) break;
214994
+ tokens += messageTokens;
214995
+ startIndex = i;
214996
+ if (selectedCount + 1 >= minMessages && tokens >= tokenBudget) break;
214997
+ }
214998
+ const adjustedStart = adjustTailStartForToolPairs(wireMessages, startIndex);
214999
+ if (adjustedStart !== startIndex) {
215000
+ startIndex = adjustedStart;
215001
+ tokens = wireMessages.slice(startIndex).reduce(
215002
+ (sum, message) => sum + estimateSingleMessageTokens(message, input.modelContext),
215003
+ 0
215004
+ );
215005
+ }
215006
+ return {
215007
+ recent: wireMessages.slice(startIndex),
215008
+ recentTailTokens: tokens,
215009
+ startIndex
215010
+ };
215011
+ }
215012
+ function resolveRecentTailBudgetTokens(input) {
215013
+ const nonMessageTokens = input.breakdownAfterPrune.system + input.breakdownAfterPrune.tools + input.breakdownAfterPrune.overhead;
215014
+ const available = input.postCompactTargetTokens - nonMessageTokens - COMPACT_SUMMARY_RESERVE_TOKENS;
215015
+ return Math.max(
215016
+ POST_COMPACT_RECENT_TAIL_MIN_TOKENS,
215017
+ Math.min(POST_COMPACT_RECENT_TAIL_MAX_TOKENS, available)
215018
+ );
215019
+ }
215020
+ function splitMessagesForCompaction(wireMessages, input) {
215021
+ const { recent, recentTailTokens, startIndex } = selectRecentWireMessagesByTokenBudget(
215022
+ wireMessages,
215023
+ {
215024
+ modelContext: input.modelContext,
215025
+ tokenBudget: input.recentTailTokenBudget,
215026
+ minMessages: input.minMessages
215027
+ }
215028
+ );
214807
215029
  return {
214808
- toSummarize: wireMessages.slice(0, splitAt),
214809
- recent: wireMessages.slice(splitAt)
215030
+ toSummarize: wireMessages.slice(0, startIndex),
215031
+ recent,
215032
+ recentTailTokens,
215033
+ compactedThroughWireIndex: startIndex > 0 ? startIndex - 1 : null
214810
215034
  };
214811
215035
  }
214812
215036
  function formatMessagesForSummary(messages) {
@@ -214901,6 +215125,11 @@ async function prepareLoopMessagesForSend(input) {
214901
215125
  });
214902
215126
  const contextLimitTokens = resolveEffectiveContextLimit(input.option);
214903
215127
  const compactThreshold = getAutoCompactThreshold(input.option);
215128
+ const postCompactTargetTokens = resolvePostCompactTargetTokens(contextLimitTokens);
215129
+ const recentTailTokenBudget = resolveRecentTailBudgetTokens({
215130
+ postCompactTargetTokens,
215131
+ breakdownAfterPrune
215132
+ });
214904
215133
  let breakdown = breakdownAfterPrune;
214905
215134
  const calibrationRatio = typeof input.priorApiContextTokens === "number" && input.priorApiContextTokens > 0 && typeof input.priorEstimateTokens === "number" && input.priorEstimateTokens > 0 ? Math.min(4, Math.max(1, input.priorApiContextTokens / input.priorEstimateTokens)) : 1;
214906
215135
  const anchoredTokens = (estimateTotal) => Math.ceil(estimateTotal * calibrationRatio);
@@ -214929,10 +215158,16 @@ async function prepareLoopMessagesForSend(input) {
214929
215158
  }
214930
215159
  }
214931
215160
  if (autoCompactEnabled && anchoredTokens(breakdown.total) >= compactThreshold) {
214932
- const { toSummarize, recent } = splitMessagesForCompaction(
214933
- working,
214934
- RECENT_MESSAGES_KEEP_COUNT
214935
- );
215161
+ const {
215162
+ toSummarize,
215163
+ recent,
215164
+ recentTailTokens,
215165
+ compactedThroughWireIndex
215166
+ } = splitMessagesForCompaction(working, {
215167
+ modelContext,
215168
+ recentTailTokenBudget,
215169
+ minMessages: RECENT_MESSAGES_KEEP_COUNT
215170
+ });
214936
215171
  if (toSummarize.length > 0) {
214937
215172
  const summary = await summarizeMessagesForCompaction({
214938
215173
  priorSummary: input.compactedSummary,
@@ -214946,6 +215181,7 @@ async function prepareLoopMessagesForSend(input) {
214946
215181
  signal: input.signal
214947
215182
  });
214948
215183
  if (summary) {
215184
+ const compactedAtIso = (/* @__PURE__ */ new Date()).toISOString();
214949
215185
  const compactedMessages = buildCompactedWireMessages(summary, recent);
214950
215186
  working.length = 0;
214951
215187
  working.push(...compactedMessages);
@@ -214956,20 +215192,45 @@ async function prepareLoopMessagesForSend(input) {
214956
215192
  toolDefinitionsJson,
214957
215193
  modelContext
214958
215194
  });
215195
+ const tokensAfter = anchoredTokens(breakdown.total);
215196
+ const summaryTokens = tokenizeText(summary, modelContext).tokenCount;
215197
+ const compactionReport = {
215198
+ tokensBefore: tokensBeforeCompact,
215199
+ tokensAfter,
215200
+ targetTokens: postCompactTargetTokens,
215201
+ summaryTokens,
215202
+ recentTailTokens,
215203
+ recentTailMessages: recent.length,
215204
+ summarizedMessages: toSummarize.length,
215205
+ restoredToolsOrFiles: 0
215206
+ };
214959
215207
  compactionState = {
214960
215208
  compactedSummary: summary,
214961
215209
  tokensBefore: tokensBeforeCompact,
214962
- tokensAfter: anchoredTokens(breakdown.total),
215210
+ tokensAfter,
214963
215211
  autoCompactEnabled: true,
214964
215212
  effectiveContextLimitTokens: contextLimitTokens,
214965
- compactedAtIso: (/* @__PURE__ */ new Date()).toISOString()
215213
+ compactedAtIso,
215214
+ compactBoundary: {
215215
+ kind: "auto",
215216
+ compactedAtIso,
215217
+ compactedThroughWireIndex
215218
+ },
215219
+ compactionReport
214966
215220
  };
214967
215221
  input.onEvent?.({
214968
215222
  type: "context_compacted",
214969
215223
  tokensBefore: tokensBeforeCompact,
214970
- tokensAfter: anchoredTokens(breakdown.total),
215224
+ tokensAfter,
214971
215225
  summary,
214972
- ts: (/* @__PURE__ */ new Date()).toISOString()
215226
+ compactionKind: "auto",
215227
+ targetTokens: postCompactTargetTokens,
215228
+ summaryTokens,
215229
+ recentTailTokens,
215230
+ recentTailMessages: recent.length,
215231
+ summarizedMessages: toSummarize.length,
215232
+ restoredToolsOrFiles: 0,
215233
+ ts: compactedAtIso
214973
215234
  });
214974
215235
  }
214975
215236
  }
@@ -215005,7 +215266,7 @@ async function prepareLoopMessagesForSend(input) {
215005
215266
  microcompactedToolResults
215006
215267
  };
215007
215268
  }
215008
- var OPERATOR_SCREENSHOT_MARKER2, KEPT_SNAPSHOT_MAX_CHARS, KEPT_SNAPSHOT_TRUNCATED_MARKER, BROWSER_TOOL_NAMES, RECENT_MESSAGES_KEEP_COUNT, RECENT_TOOL_RESULTS_KEEP_COUNT, MICROCOMPACT_CLEARED_MARKER, COMPACT_SUMMARY_RESERVE_TOKENS, COMPACT_SYSTEM_PROMPT;
215269
+ var OPERATOR_SCREENSHOT_MARKER2, KEPT_SNAPSHOT_MAX_CHARS, KEPT_SNAPSHOT_TRUNCATED_MARKER, BROWSER_TOOL_NAMES, RECENT_MESSAGES_KEEP_COUNT, RECENT_TOOL_RESULTS_KEEP_COUNT, POST_COMPACT_TARGET_TOKENS, POST_COMPACT_MIN_TARGET_TOKENS, POST_COMPACT_MAX_TARGET_TOKENS, POST_COMPACT_RECENT_TAIL_MIN_TOKENS, POST_COMPACT_RECENT_TAIL_MAX_TOKENS, MICROCOMPACT_CLEARED_MARKER, COMPACT_SUMMARY_RESERVE_TOKENS, COMPACT_SYSTEM_PROMPT;
215009
215270
  var init_contextLoopCompaction = __esm({
215010
215271
  "features/perchTerminal/runtime/services/contextLoopCompaction.ts"() {
215011
215272
  "use strict";
@@ -215035,6 +215296,11 @@ var init_contextLoopCompaction = __esm({
215035
215296
  ]);
215036
215297
  RECENT_MESSAGES_KEEP_COUNT = 12;
215037
215298
  RECENT_TOOL_RESULTS_KEEP_COUNT = 6;
215299
+ POST_COMPACT_TARGET_TOKENS = 32e3;
215300
+ POST_COMPACT_MIN_TARGET_TOKENS = 2e4;
215301
+ POST_COMPACT_MAX_TARGET_TOKENS = 4e4;
215302
+ POST_COMPACT_RECENT_TAIL_MIN_TOKENS = 4e3;
215303
+ POST_COMPACT_RECENT_TAIL_MAX_TOKENS = 24e3;
215038
215304
  MICROCOMPACT_CLEARED_MARKER = "[Old tool result content cleared to free context]";
215039
215305
  COMPACT_SUMMARY_RESERVE_TOKENS = 4096;
215040
215306
  COMPACT_SYSTEM_PROMPT = `You summarize conversation history for a delivery assistant using Google Docs, Gmail, Calendar, and browser tools.
@@ -217109,15 +217375,15 @@ async function dumpModelPayloadIfRequested(input) {
217109
217375
  const dumpDir = process.env.PERCH_MODEL_PAYLOAD_DUMP_DIR;
217110
217376
  if (process.env.PERCH_DUMP_MODEL_PAYLOAD !== "1" && !dumpDir) return;
217111
217377
  try {
217112
- const [{ mkdir, writeFile: writeFile2 }, { join }] = await Promise.all([
217378
+ const [{ mkdir: mkdir2, writeFile: writeFile3 }, { join: join2 }] = await Promise.all([
217113
217379
  import("node:fs/promises"),
217114
217380
  import("node:path")
217115
217381
  ]);
217116
- const dir = dumpDir || join(process.cwd(), ".perch", "debug", "model-payloads");
217117
- await mkdir(dir, { recursive: true });
217118
- const safeRunId = (input.runId || "local").replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80);
217119
- await writeFile2(
217120
- join(dir, `${safeRunId}-iter-${input.iteration}.json`),
217382
+ const dir = dumpDir || join2(process.cwd(), ".perch", "debug", "model-payloads");
217383
+ await mkdir2(dir, { recursive: true });
217384
+ const safeRunId2 = (input.runId || "local").replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80);
217385
+ await writeFile3(
217386
+ join2(dir, `${safeRunId2}-iter-${input.iteration}.json`),
217121
217387
  JSON.stringify({
217122
217388
  dumpedAt: (/* @__PURE__ */ new Date()).toISOString(),
217123
217389
  runId: input.runId,
@@ -217605,13 +217871,18 @@ async function runLiveAgentsLoop(input) {
217605
217871
  effectiveContextLimitTokens: state.effectiveContextLimitTokens,
217606
217872
  compactedAtIso: state.compactedAtIso,
217607
217873
  autoCompactedAtIso: state.compactedAtIso,
217874
+ autoCompactedMessageCount: state.compactionReport.summarizedMessages,
217875
+ compactBoundary: state.compactBoundary,
217876
+ compactionReport: state.compactionReport,
217608
217877
  consecutiveAutoCompactFailures: 0
217609
217878
  },
217610
217879
  contextMetrics: latest?.contextMetrics ? {
217611
217880
  ...latest.contextMetrics,
217612
217881
  effectiveLimitTokens: state.effectiveContextLimitTokens,
217613
217882
  autoCompactEnabled: state.autoCompactEnabled,
217614
- autoCompactedAtIso: state.compactedAtIso
217883
+ autoCompactedAtIso: state.compactedAtIso,
217884
+ autoCompactedMessageCount: state.compactionReport.summarizedMessages,
217885
+ compactionReport: state.compactionReport
217615
217886
  } : latest?.contextMetrics,
217616
217887
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
217617
217888
  },
@@ -219818,7 +220089,13 @@ function emitProgressForRuntimeEvent(event, emit, state = {
219818
220089
  case "context_compacted":
219819
220090
  emitInstantItem(emit, state, turnId, "context", itemId(turnId, "context-compacted", ts), "Context compacted", event.summary, ts, {
219820
220091
  tokensBefore: event.tokensBefore,
219821
- tokensAfter: event.tokensAfter
220092
+ tokensAfter: event.tokensAfter,
220093
+ targetTokens: event.targetTokens,
220094
+ summaryTokens: event.summaryTokens,
220095
+ recentTailTokens: event.recentTailTokens,
220096
+ recentTailMessages: event.recentTailMessages,
220097
+ summarizedMessages: event.summarizedMessages,
220098
+ restoredToolsOrFiles: event.restoredToolsOrFiles
219822
220099
  });
219823
220100
  break;
219824
220101
  case "plan_card_generated": {
@@ -221003,6 +221280,7 @@ function resolveThreadContextAccounting(input) {
221003
221280
  consecutiveAutoCompactFailures: priorCompaction?.consecutiveAutoCompactFailures ?? priorMetrics?.consecutiveAutoCompactFailures ?? 0,
221004
221281
  autoCompactedAtIso: priorCompaction?.autoCompactedAtIso ?? priorMetrics?.autoCompactedAtIso ?? null,
221005
221282
  autoCompactedMessageCount: priorCompaction?.autoCompactedMessageCount ?? priorMetrics?.autoCompactedMessageCount,
221283
+ compactionReport: priorCompaction?.compactionReport ?? priorMetrics?.compactionReport,
221006
221284
  tokenCountMethod: fill.tokenCountMethod,
221007
221285
  accountingMethod: "thread_fill_v4: headline = retained window fill high-water until compaction; provider usage is last-call detail; workers off-thread only.",
221008
221286
  resetAtIso: compactedAtIso,
@@ -221126,6 +221404,7 @@ function buildClientContextSnapshot(input) {
221126
221404
  consecutiveAutoCompactFailures: accounting.consecutiveAutoCompactFailures ?? 0,
221127
221405
  autoCompactedAtIso: accounting.autoCompactedAtIso ?? null,
221128
221406
  autoCompactedMessageCount: accounting.autoCompactedMessageCount,
221407
+ compactionReport: accounting.compactionReport,
221129
221408
  builtAtIso: input.builtAtIso,
221130
221409
  createdAt: input.builtAtIso,
221131
221410
  ...wireDisplay ? {
@@ -222671,8 +222950,849 @@ var init_runOperatorTurn = __esm({
222671
222950
  }
222672
222951
  });
222673
222952
 
222674
- // features/perchTerminal/runtime/cliHost/nodeLocalBridge.ts
222953
+ // electron/localSandboxHost.ts
222954
+ import { copyFile, mkdir, mkdtemp, readdir, readFile as readFile3, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
222955
+ import { createHash as createHash2, randomUUID } from "node:crypto";
222956
+ import { basename, dirname, extname, join, relative, resolve as resolve4 } from "node:path";
222957
+ import { homedir, platform, tmpdir } from "node:os";
222675
222958
  import { spawn } from "node:child_process";
222959
+ function buildSandboxRuntimeInfo(networkPolicy) {
222960
+ return {
222961
+ networkPolicy,
222962
+ packageInstalls: networkPolicy === "disabled" ? "disabled" : "enabled",
222963
+ pythonCommand: "python3",
222964
+ pipCommand: "python3 -m pip",
222965
+ inputManifestPath: "input_manifest.json",
222966
+ helperModule: "perch_helpers",
222967
+ helperFunctions: [
222968
+ "input_files",
222969
+ "load_tabular",
222970
+ "extract_pdf_text",
222971
+ "extract_invoice_fields",
222972
+ "extract_pdf_tables",
222973
+ "find_duplicates",
222974
+ "write_report",
222975
+ "sandbox_runtime_info"
222976
+ ],
222977
+ notes: [
222978
+ "Read copied sources from paths listed in input_manifest.json.",
222979
+ networkPolicy === "disabled" ? "Package installs and network egress are disabled for this run." : "Package installs are allowed for this run; use python3 -m pip install <package> when needed.",
222980
+ "perch_helpers.extract_pdf_text has a stdlib raw-stream fallback for simple invoice PDFs, so PDF parsing should not require pdfplumber/pypdf for text fields."
222981
+ ]
222982
+ };
222983
+ }
222984
+ async function writeSandboxManifestFiles(input) {
222985
+ const payload = {
222986
+ ...input.inputManifest,
222987
+ runtime: input.runtimeInfo
222988
+ };
222989
+ const text = JSON.stringify(payload, null, 2);
222990
+ await writeFile2(join(input.workspaceRoot, "input_manifest.json"), text, "utf-8");
222991
+ await writeFile2(join(input.workspaceRoot, "sandbox_runtime.json"), JSON.stringify(input.runtimeInfo, null, 2), "utf-8");
222992
+ }
222993
+ async function runDesktopSandboxCodeJob(spec, deps) {
222994
+ const runId = safeRunId(spec.runId) ?? makeRunId2("sandbox-code");
222995
+ const startedAt = Date.now();
222996
+ const now13 = () => (deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
222997
+ const emitLive = (type, payload) => {
222998
+ deps.onEvent?.({ runId, type, payload, ts: now13() });
222999
+ };
223000
+ const commandText = typeof spec.command === "string" ? spec.command.trim() : "";
223001
+ const code = typeof spec.code === "string" ? spec.code : "";
223002
+ const language = spec.language === "node" ? "node" : "python";
223003
+ const executionMode = commandText ? "command" : "script";
223004
+ const executableText = executionMode === "command" ? commandText : code;
223005
+ const codeSha256 = createHash2("sha256").update(executableText).digest("hex");
223006
+ const networkPolicy = spec.networkPolicy ?? "disabled";
223007
+ if (!executableText.trim()) {
223008
+ return { ok: false, kind: "blocked", message: "run_sandbox_code requires either command or code.", executionHost: "electron-main", runId, codeSha256 };
223009
+ }
223010
+ emitLive("sandbox_started", {
223011
+ executionHost: "electron-main",
223012
+ runnerKind: "dev-local",
223013
+ networkPolicy,
223014
+ label: spec.label ?? null,
223015
+ codeSha256,
223016
+ language: executionMode === "command" ? "shell" : language
223017
+ });
223018
+ const validationReasons = executionMode === "script" ? validateScript(code, language) : [];
223019
+ if (validationReasons.length > 0) {
223020
+ emitLive("sandbox_failed", {
223021
+ stopReason: "validation_rejected",
223022
+ message: "The model-written sandbox code was rejected by validation.",
223023
+ validationReasons,
223024
+ codeSha256
223025
+ });
223026
+ return {
223027
+ ok: false,
223028
+ kind: "failed",
223029
+ message: "The model-written sandbox code was rejected by validation.",
223030
+ runnerKind: "dev-local",
223031
+ executionHost: "electron-main",
223032
+ runId,
223033
+ error: validationReasons.join("; "),
223034
+ validationReasons,
223035
+ codeSha256
223036
+ };
223037
+ }
223038
+ let resolved = [];
223039
+ if (Array.isArray(spec.sources) && spec.sources.length > 0) {
223040
+ try {
223041
+ emitLive("inputs_preparing", { sourceCount: spec.sources.length });
223042
+ resolved = await resolveApprovedInputs(spec.sources, deps);
223043
+ } catch (err) {
223044
+ const message = err instanceof Error ? err.message : String(err);
223045
+ emitLive("sandbox_failed", { message, stopReason: "input_resolution_failed" });
223046
+ return { ok: false, kind: "blocked", message, executionHost: "electron-main", runId, error: message, codeSha256 };
223047
+ }
223048
+ }
223049
+ const workspaceKey = safeWorkspaceKey(spec.workspaceKey) ?? runId;
223050
+ const workspaceRoot = await ensureCodeWorkspace(workspaceKey);
223051
+ const scriptName = language === "python" ? `run-${runId}.py` : `run-${runId}.js`;
223052
+ const command = executionMode === "command" ? commandText : language === "python" ? buildPythonCommand(scriptName) : `"$PERCH_ELECTRON_EXEC" "${scriptName}"`;
223053
+ emitLive("script_planned", {
223054
+ source: "model",
223055
+ fallbackReason: null,
223056
+ script: redactUserVisibleText(executableText),
223057
+ publicPlan: spec.label ?? (executionMode === "command" ? "Sandbox shell command." : "Model-written sandbox code."),
223058
+ plannerContent: null,
223059
+ provider: null,
223060
+ codeSha256,
223061
+ language: executionMode === "command" ? "shell" : language
223062
+ });
223063
+ emitLive("command_started", { command: redactUserVisibleText(command), language: executionMode === "command" ? "shell" : language, codeSha256 });
223064
+ const runResult = await runCodeSandbox({
223065
+ runId,
223066
+ command,
223067
+ workspaceRoot,
223068
+ inputs: resolved,
223069
+ timeoutMs: Math.min(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS2, DEFAULT_TIMEOUT_MS2),
223070
+ onEvent: emitLive,
223071
+ networkPolicy,
223072
+ scriptFile: executionMode === "script" ? { name: scriptName, content: code } : null
223073
+ });
223074
+ const outputBundle = buildOutputBundle(runResult);
223075
+ const hasReport = runResult.structuredOutput?.reportJson != null;
223076
+ const completedWithoutReport = runResult.status === "completed" && !hasReport;
223077
+ const eventKind = runResult.status === "completed" ? "completed" : "failed";
223078
+ const stopReason = hasReport ? "completed" : completedWithoutReport ? "completed_unstructured" : runResult.status === "timed_out" ? "run_timed_out" : "run_failed";
223079
+ emitLive(eventKind === "completed" ? "sandbox_completed" : "sandbox_failed", {
223080
+ stopReason,
223081
+ runnerKind: "dev-local",
223082
+ executionHost: "electron-main",
223083
+ codeSha256,
223084
+ outputSummary: {
223085
+ files: outputBundle?.files.length ?? 0,
223086
+ tables: outputBundle?.tables.length ?? 0,
223087
+ charts: outputBundle?.charts.length ?? 0,
223088
+ logs: outputBundle?.logs.length ?? 0,
223089
+ reportJsonPresent: hasReport
223090
+ }
223091
+ });
223092
+ if (runResult.status !== "completed") {
223093
+ return {
223094
+ ok: false,
223095
+ kind: "failed",
223096
+ message: "Model-written sandbox code failed.",
223097
+ runnerKind: "dev-local",
223098
+ executionHost: "electron-main",
223099
+ runId,
223100
+ error: runResult.stderr || stopReason,
223101
+ codeSha256,
223102
+ status: runResult.status,
223103
+ exitCode: runResult.exitCode,
223104
+ durationMs: Date.now() - startedAt,
223105
+ stdout: runResult.stdout,
223106
+ stderr: runResult.stderr,
223107
+ stdoutTruncated: runResult.stdoutTruncated,
223108
+ stderrTruncated: runResult.stderrTruncated,
223109
+ producedFiles: runResult.producedFiles,
223110
+ inputManifest: runResult.inputManifest,
223111
+ runtimeInfo: runResult.runtimeInfo,
223112
+ structuredOutput: runResult.structuredOutput,
223113
+ workspacePath: runResult.workspacePath,
223114
+ language,
223115
+ reportJsonPresent: hasReport,
223116
+ warnings: runResult.warnings
223117
+ };
223118
+ }
223119
+ const kind = "completed";
223120
+ return {
223121
+ ok: true,
223122
+ kind,
223123
+ message: hasReport ? "Sandbox execution completed and emitted output/report.json." : "Sandbox execution completed; stdout/stderr and produced files were captured.",
223124
+ runnerKind: "dev-local",
223125
+ executionHost: "electron-main",
223126
+ runId,
223127
+ status: runResult.status,
223128
+ exitCode: runResult.exitCode,
223129
+ durationMs: Date.now() - startedAt,
223130
+ stdout: runResult.stdout,
223131
+ stderr: runResult.stderr,
223132
+ stdoutTruncated: runResult.stdoutTruncated,
223133
+ stderrTruncated: runResult.stderrTruncated,
223134
+ producedFiles: runResult.producedFiles,
223135
+ inputManifest: runResult.inputManifest,
223136
+ runtimeInfo: runResult.runtimeInfo,
223137
+ structuredOutput: runResult.structuredOutput,
223138
+ workspacePath: runResult.workspacePath,
223139
+ codeSha256,
223140
+ language: executionMode === "command" ? "shell" : language,
223141
+ reportJsonPresent: hasReport,
223142
+ warnings: runResult.warnings
223143
+ };
223144
+ }
223145
+ async function resolveApprovedInputs(sources, deps) {
223146
+ const out = [];
223147
+ const seen = /* @__PURE__ */ new Set();
223148
+ for (const source of sources.slice(0, MAX_SOURCES)) {
223149
+ if (!source.localSourceId || seen.has(source.localSourceId)) continue;
223150
+ seen.add(source.localSourceId);
223151
+ const resolved = resolveSandboxInputRef(source.localSourceId, deps);
223152
+ if (!resolved) continue;
223153
+ const { absPath, relPath } = resolved;
223154
+ const fileName = basename(absPath);
223155
+ if (deps.shouldIgnore(fileName)) continue;
223156
+ const s = await stat(absPath);
223157
+ if (!s.isFile()) continue;
223158
+ if (s.size > MAX_SOURCE_BYTES) continue;
223159
+ out.push({
223160
+ localSourceId: source.localSourceId,
223161
+ fileName: safeRelativeName(source.fileName || fileName),
223162
+ fileType: source.fileType || guessFileTypeFromExt(extname(fileName).toLowerCase()),
223163
+ sizeBytes: s.size,
223164
+ description: source.description,
223165
+ relativePath: safeRelativeName(relPath),
223166
+ sourcePath: absPath
223167
+ });
223168
+ }
223169
+ return out;
223170
+ }
223171
+ function resolveSandboxInputRef(inputRef, deps) {
223172
+ const sep = inputRef.indexOf("::");
223173
+ if (sep >= 0) {
223174
+ const rootId = inputRef.slice(0, sep);
223175
+ const relPath2 = inputRef.slice(sep + 2);
223176
+ const root3 = deps.getApprovedRoot(rootId);
223177
+ if (!root3) return null;
223178
+ if (!deps.isPathSafe(root3.path, relPath2)) return null;
223179
+ const absPath2 = resolve4(root3.path, relPath2);
223180
+ if (!deps.isInsideApprovedRoot(absPath2)) return null;
223181
+ return { absPath: absPath2, relPath: relPath2 };
223182
+ }
223183
+ const absPath = resolve4(inputRef);
223184
+ const root2 = deps.isInsideApprovedRoot(absPath);
223185
+ if (!root2) return null;
223186
+ const relPath = relative(root2.path, absPath);
223187
+ if (!deps.isPathSafe(root2.path, relPath)) return null;
223188
+ return { absPath, relPath };
223189
+ }
223190
+ function validateScript(script, language = "node") {
223191
+ if (language === "python") return validatePythonScript(script);
223192
+ const reasons = [];
223193
+ const blocked3 = [
223194
+ "child_process",
223195
+ "exec(",
223196
+ "spawn(",
223197
+ "process.env",
223198
+ "require('http')",
223199
+ 'require("http")',
223200
+ "require('https')",
223201
+ 'require("https")',
223202
+ "fetch(",
223203
+ "eval(",
223204
+ "Function("
223205
+ ];
223206
+ for (const token of blocked3) {
223207
+ if (script.includes(token)) reasons.push(`blocked token: ${token}`);
223208
+ }
223209
+ return reasons;
223210
+ }
223211
+ function validatePythonScript(script) {
223212
+ const reasons = [];
223213
+ const blocked3 = [
223214
+ "subprocess",
223215
+ "os.system(",
223216
+ "os.popen(",
223217
+ "os.exec",
223218
+ "shutil.rmtree",
223219
+ "__import__(",
223220
+ "importlib",
223221
+ "ctypes",
223222
+ "requests.get",
223223
+ "requests.post",
223224
+ "urllib.request",
223225
+ "http.client",
223226
+ "socket.",
223227
+ "open('/etc",
223228
+ "open('/proc",
223229
+ "open('/sys"
223230
+ ];
223231
+ for (const token of blocked3) {
223232
+ if (script.includes(token)) reasons.push(`blocked token: ${token}`);
223233
+ }
223234
+ return reasons;
223235
+ }
223236
+ function buildPythonCommand(scriptPath) {
223237
+ return `python3 "${scriptPath}"`;
223238
+ }
223239
+ async function ensureCodeWorkspace(workspaceKey) {
223240
+ const existing = codeWorkspaces.get(workspaceKey);
223241
+ if (existing) {
223242
+ try {
223243
+ const s = await stat(existing);
223244
+ if (s.isDirectory()) return existing;
223245
+ } catch {
223246
+ codeWorkspaces.delete(workspaceKey);
223247
+ }
223248
+ }
223249
+ const workspaceRoot = await mkdtemp(join(tmpdir(), `perch-code-${workspaceKey.slice(0, 16)}-`));
223250
+ await mkdir(join(workspaceRoot, "input"), { recursive: true });
223251
+ await mkdir(join(workspaceRoot, "output"), { recursive: true });
223252
+ await writeFile2(join(workspaceRoot, "perch_helpers.py"), PYTHON_HELPERS_SOURCE, "utf-8");
223253
+ codeWorkspaces.set(workspaceKey, workspaceRoot);
223254
+ return workspaceRoot;
223255
+ }
223256
+ async function runCodeSandbox(opts) {
223257
+ const warnings = [];
223258
+ const copiedInputFiles = [];
223259
+ const skippedInputFiles = [];
223260
+ const inputManifestFiles = [];
223261
+ const skippedManifestEntries = [];
223262
+ const runtimeInfo = buildSandboxRuntimeInfo(opts.networkPolicy);
223263
+ const inputDir = join(opts.workspaceRoot, "input");
223264
+ const outputDir = join(opts.workspaceRoot, "output");
223265
+ await rm(inputDir, { recursive: true, force: true });
223266
+ await rm(outputDir, { recursive: true, force: true });
223267
+ await mkdir(inputDir, { recursive: true });
223268
+ await mkdir(outputDir, { recursive: true });
223269
+ if (opts.scriptFile) {
223270
+ await writeFile2(join(opts.workspaceRoot, opts.scriptFile.name), opts.scriptFile.content, "utf-8");
223271
+ }
223272
+ await writeFile2(join(opts.workspaceRoot, "perch_helpers.py"), PYTHON_HELPERS_SOURCE, "utf-8");
223273
+ for (const input of opts.inputs) {
223274
+ const safeRel = safeRelativeName(input.relativePath || input.fileName);
223275
+ if (!safeRel) {
223276
+ skippedInputFiles.push(input.fileName);
223277
+ skippedManifestEntries.push({
223278
+ sourcePath: input.sourcePath,
223279
+ localSourceId: input.localSourceId,
223280
+ fileName: input.fileName,
223281
+ sizeBytes: input.sizeBytes,
223282
+ fileType: input.fileType,
223283
+ reason: "invalid_sandbox_relative_path"
223284
+ });
223285
+ continue;
223286
+ }
223287
+ try {
223288
+ const dest = join(inputDir, safeRel);
223289
+ await mkdir(dirname(dest), { recursive: true });
223290
+ await copyFile(input.sourcePath, dest);
223291
+ copiedInputFiles.push(safeRel);
223292
+ inputManifestFiles.push({
223293
+ sandboxPath: `input/${safeRel}`,
223294
+ sourcePath: input.sourcePath,
223295
+ localSourceId: input.localSourceId,
223296
+ fileName: input.fileName || basename(safeRel),
223297
+ sizeBytes: input.sizeBytes,
223298
+ fileType: input.fileType || null
223299
+ });
223300
+ opts.onEvent?.("input_copied", {
223301
+ relativePath: safeRel,
223302
+ fileName: input.fileName,
223303
+ fileType: input.fileType,
223304
+ sizeBytes: input.sizeBytes
223305
+ });
223306
+ } catch (err) {
223307
+ const msg = err instanceof Error ? err.message : String(err);
223308
+ skippedInputFiles.push(safeRel);
223309
+ skippedManifestEntries.push({
223310
+ sandboxPath: `input/${safeRel}`,
223311
+ sourcePath: input.sourcePath,
223312
+ localSourceId: input.localSourceId,
223313
+ fileName: input.fileName || basename(safeRel),
223314
+ sizeBytes: input.sizeBytes,
223315
+ fileType: input.fileType,
223316
+ reason: msg
223317
+ });
223318
+ warnings.push(`Failed to copy input ${safeRel}: ${msg}`);
223319
+ }
223320
+ }
223321
+ opts.onEvent?.("inputs_ready", {
223322
+ requestedInputFileCount: opts.inputs.length,
223323
+ copiedInputFiles,
223324
+ skippedInputFiles,
223325
+ workspacePath: redactUserVisibleText(opts.workspaceRoot),
223326
+ inputManifestPath: "input_manifest.json",
223327
+ runtimeInfo
223328
+ });
223329
+ const inputManifest = {
223330
+ root: "input",
223331
+ files: inputManifestFiles,
223332
+ skipped: skippedManifestEntries
223333
+ };
223334
+ await writeSandboxManifestFiles({
223335
+ workspaceRoot: opts.workspaceRoot,
223336
+ inputManifest,
223337
+ runtimeInfo
223338
+ });
223339
+ const execResult = await executeCommand(
223340
+ opts.command,
223341
+ opts.workspaceRoot,
223342
+ opts.timeoutMs,
223343
+ opts.networkPolicy,
223344
+ opts.onEvent
223345
+ );
223346
+ const producedFiles = await collectOutputFiles(outputDir, warnings, opts.onEvent);
223347
+ opts.onEvent?.("outputs_collected", { producedFiles });
223348
+ const structuredOutput = await readStructuredOutput(outputDir, execResult.stdout);
223349
+ if (structuredOutput?.reportJson) opts.onEvent?.("report_detected", { source: "report.json" });
223350
+ return {
223351
+ runId: opts.runId,
223352
+ status: execResult.timedOut ? "timed_out" : execResult.exitCode === 0 ? "completed" : "failed",
223353
+ exitCode: execResult.exitCode,
223354
+ stdout: execResult.stdout,
223355
+ stderr: execResult.stderr,
223356
+ stdoutTruncated: execResult.stdoutTruncated,
223357
+ stderrTruncated: execResult.stderrTruncated,
223358
+ producedFiles,
223359
+ inputManifest,
223360
+ runtimeInfo,
223361
+ structuredOutput,
223362
+ warnings,
223363
+ durationMs: 0,
223364
+ workspacePath: opts.workspaceRoot,
223365
+ cleanedUp: false,
223366
+ executionEvidence: {
223367
+ requestedInputFileCount: opts.inputs.length,
223368
+ copiedInputFiles,
223369
+ skippedInputFiles,
223370
+ command: opts.command
223371
+ }
223372
+ };
223373
+ }
223374
+ async function executeCommand(command, cwd2, timeoutMs, networkPolicy, onEvent) {
223375
+ const launch = await prepareSandboxLaunch(command, cwd2, networkPolicy);
223376
+ if ("error" in launch) {
223377
+ return {
223378
+ stdout: "",
223379
+ stderr: launch.error,
223380
+ exitCode: 126,
223381
+ timedOut: false,
223382
+ stdoutTruncated: false,
223383
+ stderrTruncated: false
223384
+ };
223385
+ }
223386
+ return new Promise((resolvePromise) => {
223387
+ let stdout = "";
223388
+ let stderr = "";
223389
+ let stdoutBytes = 0;
223390
+ let stderrBytes = 0;
223391
+ let stdoutTruncated = false;
223392
+ let stderrTruncated = false;
223393
+ let timedOut = false;
223394
+ const childEnv = {
223395
+ NODE_ENV: "sandbox",
223396
+ PERCH_SANDBOX_NETWORK: networkPolicy,
223397
+ LANG: "en_US.UTF-8",
223398
+ HOME: cwd2,
223399
+ TMPDIR: join(cwd2, "tmp"),
223400
+ PYTHONDONTWRITEBYTECODE: "1",
223401
+ PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
223402
+ ELECTRON_RUN_AS_NODE: "1",
223403
+ PERCH_ELECTRON_EXEC: process.execPath,
223404
+ ...launch.env
223405
+ };
223406
+ const child = spawn(launch.command, launch.args, {
223407
+ cwd: cwd2,
223408
+ env: childEnv,
223409
+ stdio: ["ignore", "pipe", "pipe"]
223410
+ });
223411
+ const timer = setTimeout(() => {
223412
+ timedOut = true;
223413
+ child.kill("SIGKILL");
223414
+ }, timeoutMs);
223415
+ child.stdout?.on("data", (data) => {
223416
+ stdoutBytes += data.length;
223417
+ const chunk2 = data.toString("utf8");
223418
+ onEvent?.("stdout", { chunk: redactUserVisibleText(chunk2) });
223419
+ if (stdoutBytes <= MAX_STDOUT_BYTES) stdout += chunk2;
223420
+ else stdoutTruncated = true;
223421
+ });
223422
+ child.stderr?.on("data", (data) => {
223423
+ stderrBytes += data.length;
223424
+ const chunk2 = data.toString("utf8");
223425
+ onEvent?.("stderr", { chunk: redactUserVisibleText(chunk2) });
223426
+ if (stderrBytes <= MAX_STDERR_BYTES) stderr += chunk2;
223427
+ else stderrTruncated = true;
223428
+ });
223429
+ child.on("close", (code) => {
223430
+ clearTimeout(timer);
223431
+ resolvePromise({ stdout, stderr, exitCode: code, timedOut, stdoutTruncated, stderrTruncated });
223432
+ });
223433
+ child.on("error", (err) => {
223434
+ clearTimeout(timer);
223435
+ resolvePromise({ stdout, stderr: err.message, exitCode: 1, timedOut: false, stdoutTruncated, stderrTruncated });
223436
+ });
223437
+ });
223438
+ }
223439
+ async function prepareSandboxLaunch(command, cwd2, networkPolicy) {
223440
+ await mkdir(join(cwd2, "tmp"), { recursive: true });
223441
+ if (networkPolicy !== "disabled") {
223442
+ return { command: "bash", args: ["-c", command] };
223443
+ }
223444
+ if (platform() !== "darwin") {
223445
+ return {
223446
+ error: "Sandbox execution requires macOS sandbox-exec for disabled-network local code runs in this build."
223447
+ };
223448
+ }
223449
+ const sandboxExec = "/usr/bin/sandbox-exec";
223450
+ try {
223451
+ const s = await stat(sandboxExec);
223452
+ if (!s.isFile()) throw new Error("not a file");
223453
+ } catch {
223454
+ return {
223455
+ error: "macOS sandbox-exec is unavailable; refusing to run model-written code without OS-level isolation."
223456
+ };
223457
+ }
223458
+ const profilePath = join(cwd2, ".perch-sandbox.sb");
223459
+ await writeFile2(profilePath, buildSeatbeltProfile(cwd2), "utf-8");
223460
+ return {
223461
+ command: sandboxExec,
223462
+ args: ["-f", profilePath, "/bin/bash", "-c", command],
223463
+ env: { PERCH_SANDBOX_PROFILE: profilePath }
223464
+ };
223465
+ }
223466
+ function buildSeatbeltProfile(workspaceRoot) {
223467
+ const home = homedir();
223468
+ const deniedUserDataRoots = Array.from(/* @__PURE__ */ new Set([
223469
+ home,
223470
+ "/Users/Shared",
223471
+ "/Volumes"
223472
+ ])).map((path14) => `(subpath ${seatbeltString(path14)})`).join("\n ");
223473
+ return `
223474
+ (version 1)
223475
+ (allow default)
223476
+ (deny network*)
223477
+ (deny file-read*
223478
+ ${deniedUserDataRoots})
223479
+ (deny file-write*
223480
+ ${deniedUserDataRoots})
223481
+ (allow file-read*
223482
+ (subpath ${seatbeltString(workspaceRoot)})
223483
+ (subpath ${seatbeltString(dirname(process.execPath))}))
223484
+ (allow file-write*
223485
+ (subpath ${seatbeltString(workspaceRoot)}))
223486
+ `.trim();
223487
+ }
223488
+ function seatbeltString(value) {
223489
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
223490
+ }
223491
+ async function collectOutputFiles(outputDir, warnings, onEvent) {
223492
+ const files = [];
223493
+ let totalBytes = 0;
223494
+ async function walk2(dir, base) {
223495
+ let entries;
223496
+ try {
223497
+ entries = await readdir(dir, { withFileTypes: true });
223498
+ } catch {
223499
+ return;
223500
+ }
223501
+ for (const entry of entries) {
223502
+ if (files.length >= MAX_OUTPUT_FILES) return;
223503
+ const rel = base ? `${base}/${entry.name}` : entry.name;
223504
+ const full = join(dir, entry.name);
223505
+ if (entry.isDirectory()) {
223506
+ await walk2(full, rel);
223507
+ continue;
223508
+ }
223509
+ const s = await stat(full);
223510
+ totalBytes += s.size;
223511
+ if (totalBytes > MAX_OUTPUT_BYTES) {
223512
+ warnings.push("Produced file byte limit exceeded");
223513
+ return;
223514
+ }
223515
+ const file = { relativePath: rel, sizeBytes: s.size, mimeType: guessMimeTypeFromName(entry.name) };
223516
+ files.push(file);
223517
+ onEvent?.("output_detected", file);
223518
+ }
223519
+ }
223520
+ await walk2(outputDir, "");
223521
+ return files;
223522
+ }
223523
+ async function readStructuredOutput(outputDir, stdout) {
223524
+ let parsedStdout = null;
223525
+ const line = stdout.trim().split(/\r?\n/).reverse().find((candidate) => candidate.trim().startsWith("{"));
223526
+ if (line) {
223527
+ try {
223528
+ parsedStdout = JSON.parse(line);
223529
+ } catch {
223530
+ parsedStdout = null;
223531
+ }
223532
+ }
223533
+ let reportJson = parsedStdout?.reportJson;
223534
+ if (!reportJson) {
223535
+ try {
223536
+ reportJson = JSON.parse(await readFile3(join(outputDir, "report.json"), "utf8"));
223537
+ } catch {
223538
+ reportJson = void 0;
223539
+ }
223540
+ }
223541
+ const tables = Array.isArray(parsedStdout?.tables) ? parsedStdout.tables : [];
223542
+ const charts = Array.isArray(parsedStdout?.charts) ? parsedStdout.charts : [];
223543
+ const logs = Array.isArray(parsedStdout?.logs) ? parsedStdout.logs.filter((x) => typeof x === "string") : [];
223544
+ if (!reportJson && tables.length === 0 && charts.length === 0 && logs.length === 0) return null;
223545
+ return { reportJson, tables, charts, logs };
223546
+ }
223547
+ function buildOutputBundle(run) {
223548
+ const structured = run.structuredOutput;
223549
+ if (!structured && run.producedFiles.length === 0) return null;
223550
+ return {
223551
+ status: run.status,
223552
+ files: run.producedFiles.map((file) => ({
223553
+ fileName: safeRelativeName(file.relativePath),
223554
+ sizeBytes: file.sizeBytes,
223555
+ mimeType: file.mimeType
223556
+ })),
223557
+ tables: structured?.tables ?? [],
223558
+ charts: structured?.charts ?? [],
223559
+ logs: structured?.logs ?? [],
223560
+ reportJson: structured?.reportJson ?? null
223561
+ };
223562
+ }
223563
+ function makeRunId2(prefix) {
223564
+ try {
223565
+ return `${prefix}-${randomUUID().slice(0, 12)}`;
223566
+ } catch {
223567
+ return `${prefix}-${Date.now().toString(36)}`;
223568
+ }
223569
+ }
223570
+ function safeRunId(value) {
223571
+ if (!value) return null;
223572
+ return /^[a-zA-Z0-9_-]{1,80}$/.test(value) ? value : null;
223573
+ }
223574
+ function safeWorkspaceKey(value) {
223575
+ if (!value) return null;
223576
+ const cleaned = value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
223577
+ return cleaned || null;
223578
+ }
223579
+ function safeRelativeName(value) {
223580
+ return value.split(/[\\/]+/).filter((part) => part && part !== "." && part !== "..").join("/") || "unnamed";
223581
+ }
223582
+ function redactUserVisibleText(text) {
223583
+ if (!text) return "";
223584
+ return text.replace(/\/Users\/[^/\s"'`]+/g, "[home]").replace(/\/home\/[^/\s"'`]+/g, "[home]").replace(/\/private\/var\/folders\/[^\s"'`]+/g, "[temp]").replace(/\/var\/folders\/[^\s"'`]+/g, "[temp]").replace(/\/tmp\/perch-sandbox-[^\s"'`]+/g, "[sandbox]").replace(/\/var\/tmp\/perch-sandbox-[^\s"'`]+/g, "[sandbox]");
223585
+ }
223586
+ function guessFileTypeFromExt(ext) {
223587
+ if ([".xlsx", ".xls"].includes(ext)) return "spreadsheet";
223588
+ if (ext === ".csv" || ext === ".tsv") return "csv";
223589
+ if (ext === ".pdf") return "pdf";
223590
+ if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(ext)) return "image";
223591
+ if ([".doc", ".docx", ".txt", ".md", ".rtf"].includes(ext)) return "document";
223592
+ return "unknown";
223593
+ }
223594
+ function guessMimeTypeFromName(fileName) {
223595
+ const ext = extname(fileName).toLowerCase();
223596
+ const map2 = {
223597
+ ".json": "application/json",
223598
+ ".csv": "text/csv",
223599
+ ".tsv": "text/tab-separated-values",
223600
+ ".txt": "text/plain",
223601
+ ".md": "text/markdown",
223602
+ ".html": "text/html"
223603
+ };
223604
+ return map2[ext] ?? "application/octet-stream";
223605
+ }
223606
+ var MAX_SOURCES, MAX_SOURCE_BYTES, MAX_STDOUT_BYTES, MAX_STDERR_BYTES, MAX_OUTPUT_FILES, MAX_OUTPUT_BYTES, DEFAULT_TIMEOUT_MS2, codeWorkspaces, PYTHON_HELPERS_SOURCE;
223607
+ var init_localSandboxHost = __esm({
223608
+ "electron/localSandboxHost.ts"() {
223609
+ MAX_SOURCES = 20;
223610
+ MAX_SOURCE_BYTES = 20 * 1024 * 1024;
223611
+ MAX_STDOUT_BYTES = 512 * 1024;
223612
+ MAX_STDERR_BYTES = 128 * 1024;
223613
+ MAX_OUTPUT_FILES = 50;
223614
+ MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
223615
+ DEFAULT_TIMEOUT_MS2 = 3e4;
223616
+ codeWorkspaces = /* @__PURE__ */ new Map();
223617
+ PYTHON_HELPERS_SOURCE = `
223618
+ import csv
223619
+ import json
223620
+ import re
223621
+ import sys
223622
+ import zlib
223623
+ from pathlib import Path
223624
+ from collections import Counter
223625
+
223626
+ def sandbox_runtime_info(path="sandbox_runtime.json"):
223627
+ p = Path(path)
223628
+ if p.exists():
223629
+ return json.loads(p.read_text(encoding="utf-8"))
223630
+ return {
223631
+ "networkPolicy": "unknown",
223632
+ "packageInstalls": "unknown",
223633
+ "pythonCommand": sys.executable,
223634
+ "helperModule": "perch_helpers",
223635
+ }
223636
+
223637
+ def input_manifest(path="input_manifest.json"):
223638
+ p = Path(path)
223639
+ if not p.exists():
223640
+ return {"root": "input", "files": [], "skipped": []}
223641
+ return json.loads(p.read_text(encoding="utf-8"))
223642
+
223643
+ def input_files(root="input"):
223644
+ base = Path(root)
223645
+ if not base.exists():
223646
+ return []
223647
+ return [str(p) for p in base.rglob("*") if p.is_file()]
223648
+
223649
+ def load_tabular(path):
223650
+ p = Path(path)
223651
+ suffix = p.suffix.lower()
223652
+ if suffix in {".csv", ".tsv"}:
223653
+ try:
223654
+ import pandas as pd
223655
+ return pd.read_csv(p, sep="\\t" if suffix == ".tsv" else ",")
223656
+ except Exception:
223657
+ with p.open(newline="", encoding="utf-8-sig") as fh:
223658
+ return list(csv.DictReader(fh, delimiter="\\t" if suffix == ".tsv" else ","))
223659
+ if suffix in {".xlsx", ".xls"}:
223660
+ try:
223661
+ import pandas as pd
223662
+ return pd.read_excel(p)
223663
+ except Exception as exc:
223664
+ raise RuntimeError("Excel loading requires pandas/openpyxl in the local Python environment") from exc
223665
+ raise ValueError(f"Unsupported tabular file type: {p.suffix}")
223666
+
223667
+ def extract_pdf_tables(path):
223668
+ try:
223669
+ import pdfplumber
223670
+ except Exception as exc:
223671
+ raise RuntimeError("PDF table extraction requires pdfplumber in the local Python environment") from exc
223672
+ rows = []
223673
+ with pdfplumber.open(path) as pdf:
223674
+ for page_index, page in enumerate(pdf.pages, start=1):
223675
+ for table in page.extract_tables() or []:
223676
+ rows.append({"page": page_index, "table": table})
223677
+ return rows
223678
+
223679
+ def _pdf_literal_unescape(value):
223680
+ out = []
223681
+ i = 0
223682
+ while i < len(value):
223683
+ ch = value[i]
223684
+ if ch != "\\\\":
223685
+ out.append(ch)
223686
+ i += 1
223687
+ continue
223688
+ i += 1
223689
+ if i >= len(value):
223690
+ break
223691
+ esc = value[i]
223692
+ i += 1
223693
+ mapping = {"n": "\\n", "r": "\\r", "t": "\\t", "b": "\\b", "f": "\\f", "\\\\": "\\\\", "(": "(", ")": ")"}
223694
+ if esc in mapping:
223695
+ out.append(mapping[esc])
223696
+ elif esc in "01234567":
223697
+ octal = esc
223698
+ for _ in range(2):
223699
+ if i < len(value) and value[i] in "01234567":
223700
+ octal += value[i]
223701
+ i += 1
223702
+ else:
223703
+ break
223704
+ try:
223705
+ out.append(chr(int(octal, 8)))
223706
+ except Exception:
223707
+ pass
223708
+ else:
223709
+ out.append(esc)
223710
+ return "".join(out)
223711
+
223712
+ def _extract_pdf_text_raw(path):
223713
+ data = Path(path).read_bytes()
223714
+ chunks = []
223715
+ for match in re.finditer(rb"stream\\r?\\n(.*?)\\r?\\nendstream", data, re.S):
223716
+ raw = match.group(1)
223717
+ candidates = [raw]
223718
+ try:
223719
+ candidates.insert(0, zlib.decompress(raw))
223720
+ except Exception:
223721
+ pass
223722
+ for candidate in candidates:
223723
+ try:
223724
+ decoded = candidate.decode("latin-1", errors="ignore")
223725
+ except Exception:
223726
+ continue
223727
+ literals = [_pdf_literal_unescape(x) for x in re.findall(r"\\((.*?)\\)", decoded, re.S)]
223728
+ if literals:
223729
+ chunks.extend(literals)
223730
+ break
223731
+ return " ".join(part.strip() for part in chunks if part and part.strip())
223732
+
223733
+ def extract_pdf_text(path):
223734
+ p = Path(path)
223735
+ if not p.exists():
223736
+ raise FileNotFoundError(str(p))
223737
+ errors = []
223738
+ for module_name in ("pypdf", "PyPDF2"):
223739
+ try:
223740
+ module = __import__(module_name)
223741
+ reader_cls = getattr(module, "PdfReader")
223742
+ reader = reader_cls(str(p))
223743
+ text = "\\n".join((page.extract_text() or "") for page in reader.pages)
223744
+ if text.strip():
223745
+ return text
223746
+ except Exception as exc:
223747
+ errors.append(f"{module_name}: {exc}")
223748
+ try:
223749
+ from pdfminer.high_level import extract_text
223750
+ text = extract_text(str(p))
223751
+ if text.strip():
223752
+ return text
223753
+ except Exception as exc:
223754
+ errors.append(f"pdfminer: {exc}")
223755
+ raw = _extract_pdf_text_raw(p)
223756
+ if raw.strip():
223757
+ return raw
223758
+ raise RuntimeError("No extractable PDF text found; tried pypdf, PyPDF2, pdfminer, and raw PDF streams. " + "; ".join(errors))
223759
+
223760
+ def extract_invoice_fields(path):
223761
+ text = extract_pdf_text(path)
223762
+ def find(pattern):
223763
+ m = re.search(pattern, text, re.I)
223764
+ return m.group(1).strip() if m else None
223765
+ amount = find(r"Amount\\s+Due:\\s*(?:USD\\s*)?([0-9,]+(?:\\.\\d+)?)")
223766
+ return {
223767
+ "file": str(path),
223768
+ "text": text,
223769
+ "vendor_name": text.split(" EIN")[0].strip() if " EIN" in text else None,
223770
+ "ein": find(r"EIN:\\s*([0-9-]+)"),
223771
+ "invoice_number": find(r"Invoice\\s+Number:\\s*(INV-[0-9]+)"),
223772
+ "invoice_date": find(r"Date:\\s*([0-9]{4}-[0-9]{2}-[0-9]{2})"),
223773
+ "po_number": find(r"Purchase\\s+Order:\\s*(PO-[0-9]+)"),
223774
+ "amount_due": float(amount.replace(",", "")) if amount else None,
223775
+ }
223776
+
223777
+ def find_duplicates(rows, keys):
223778
+ if hasattr(rows, "to_dict"):
223779
+ records = rows.to_dict("records")
223780
+ else:
223781
+ records = list(rows)
223782
+ counts = Counter(tuple(str(row.get(k, "")).strip().lower() for k in keys) for row in records)
223783
+ return [row for row in records if counts[tuple(str(row.get(k, "")).strip().lower() for k in keys)] > 1]
223784
+
223785
+ def write_report(report, path="output/report.json"):
223786
+ out = Path(path)
223787
+ out.parent.mkdir(parents=True, exist_ok=True)
223788
+ out.write_text(json.dumps(report, indent=2, default=str), encoding="utf-8")
223789
+ return str(out)
223790
+ `.trim();
223791
+ }
223792
+ });
223793
+
223794
+ // features/perchTerminal/runtime/cliHost/nodeLocalBridge.ts
223795
+ import { spawn as spawn2 } from "node:child_process";
222676
223796
  import fs9 from "node:fs";
222677
223797
  import fsp from "node:fs/promises";
222678
223798
  import os from "node:os";
@@ -222692,6 +223812,12 @@ function installCliNodeLocalBridge(input) {
222692
223812
  function createCliNodeLocalBridge(input) {
222693
223813
  const workspaceRoot = path10.resolve(input.workspaceRoot);
222694
223814
  const now13 = () => (/* @__PURE__ */ new Date()).toISOString();
223815
+ const sandboxHandlers = /* @__PURE__ */ new Map();
223816
+ const emitSandboxEvent = (event) => {
223817
+ const handlers = sandboxHandlers.get(event.runId);
223818
+ if (!handlers) return;
223819
+ for (const handler of handlers) handler(event);
223820
+ };
222695
223821
  const terminalSession = () => ({
222696
223822
  sessionId: "cli-terminal",
222697
223823
  title: "Perch CLI",
@@ -222726,17 +223852,9 @@ function createCliNodeLocalBridge(input) {
222726
223852
  }),
222727
223853
  checkFsAccess: async (request) => {
222728
223854
  const absolutePath = path10.resolve(expandHome4(request.absolutePath));
222729
- if (request.fsAccessOverride === "allow_once" || isInside(workspaceRoot, absolutePath)) {
222730
- return {
222731
- decision: "allow",
222732
- reason: "Allowed by Perch CLI local workspace.",
222733
- absolutePath,
222734
- approvalPath: absolutePath
222735
- };
222736
- }
222737
223855
  return {
222738
- decision: "block",
222739
- reason: `Path is outside the Perch CLI workspace: ${workspaceRoot}`,
223856
+ decision: "allow",
223857
+ reason: "Allowed by Perch CLI local filesystem access.",
222740
223858
  absolutePath,
222741
223859
  approvalPath: absolutePath
222742
223860
  };
@@ -222762,7 +223880,7 @@ function createCliNodeLocalBridge(input) {
222762
223880
  executionHost: "electron_desktop"
222763
223881
  }),
222764
223882
  runLocalBash: async (request) => runShellCommand({
222765
- workspaceRoot,
223883
+ workspaceRoot: resolveRequestRoot(workspaceRoot, request.workspaceRoot),
222766
223884
  command: request.command,
222767
223885
  cwd: request.cwd,
222768
223886
  timeoutMs: request.timeoutMs
@@ -222798,45 +223916,45 @@ function createCliNodeLocalBridge(input) {
222798
223916
  offBashTerminalEvent: () => {
222799
223917
  },
222800
223918
  readWorkspaceFile: async (request) => {
222801
- const target = resolveReadPath(workspaceRoot, request.relativePath);
222802
- if (!isInside(workspaceRoot, target)) return { ok: false, error: `Path is outside the CLI workspace: ${target}` };
222803
- const stat = await safeStat(target);
222804
- if (!stat?.isFile()) return { ok: false, error: `File does not exist: ${target}` };
222805
- if (stat.size > MAX_READ_BYTES && request.encoding !== "base64") {
222806
- return { ok: false, error: `File is too large for direct text read (${stat.size} bytes). Use grep or a narrower file.` };
223919
+ const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
223920
+ const target = resolveReadPath(baseRoot, request.relativePath);
223921
+ const stat2 = await safeStat(target);
223922
+ if (!stat2?.isFile()) return { ok: false, error: `File does not exist: ${target}` };
223923
+ if (stat2.size > MAX_READ_BYTES && request.encoding !== "base64") {
223924
+ return { ok: false, error: `File is too large for direct text read (${stat2.size} bytes). Use grep or a narrower file.` };
222807
223925
  }
222808
223926
  const buffer = await fsp.readFile(target);
222809
223927
  return {
222810
223928
  ok: true,
222811
- relativePath: path10.relative(workspaceRoot, target) || path10.basename(target),
223929
+ relativePath: displayPath(baseRoot, target, request.relativePath),
222812
223930
  content: request.encoding === "base64" ? buffer.toString("base64") : buffer.toString("utf8"),
222813
- sizeBytes: stat.size,
223931
+ sizeBytes: stat2.size,
222814
223932
  ...request.encoding === "base64" ? { encoding: "base64" } : {}
222815
223933
  };
222816
223934
  },
222817
223935
  writeWorkspaceFile: async (request) => {
222818
- const target = resolveWritePath(workspaceRoot, request.relativePath);
222819
- if (!isInside(workspaceRoot, target)) return { ok: false, error: `Refusing to write outside CLI workspace: ${target}` };
223936
+ const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
223937
+ const target = resolveWritePath(baseRoot, request.relativePath);
222820
223938
  await fsp.mkdir(path10.dirname(target), { recursive: true });
222821
223939
  const flag = request.overwrite === true ? "w" : "wx";
223940
+ const payload = request.encoding === "base64" && typeof request.contentBase64 === "string" ? Buffer.from(request.contentBase64, "base64") : request.content ?? "";
222822
223941
  try {
222823
- await fsp.writeFile(target, request.content ?? "", { encoding: "utf8", flag });
223942
+ await fsp.writeFile(target, payload, typeof payload === "string" ? { encoding: "utf8", flag } : { flag });
222824
223943
  } catch (error) {
222825
223944
  if (error.code === "EEXIST") {
222826
- return { ok: false, error: `File already exists: ${path10.relative(workspaceRoot, target)}. Pass overwrite=true to replace it.` };
223945
+ return { ok: false, error: `File already exists: ${displayPath(baseRoot, target, request.relativePath)}. Pass overwrite=true to replace it.` };
222827
223946
  }
222828
223947
  throw error;
222829
223948
  }
222830
223949
  return {
222831
223950
  ok: true,
222832
- relativePath: path10.relative(workspaceRoot, target),
222833
- bytesWritten: Buffer.byteLength(request.content ?? "", "utf8")
223951
+ relativePath: displayPath(baseRoot, target, request.relativePath),
223952
+ bytesWritten: Buffer.byteLength(payload)
222834
223953
  };
222835
223954
  },
222836
223955
  moveLocalFile: async (request) => {
222837
223956
  const src = resolveReadPath(workspaceRoot, request.src);
222838
223957
  const dest = resolveWritePath(workspaceRoot, request.dest);
222839
- if (!isInside(workspaceRoot, src) || !isInside(workspaceRoot, dest)) return { ok: false, error: "Move paths must stay inside the CLI workspace." };
222840
223958
  await fsp.mkdir(path10.dirname(dest), { recursive: true });
222841
223959
  await fsp.rename(src, dest);
222842
223960
  return { ok: true, fromPath: src, toPath: dest };
@@ -222844,37 +223962,32 @@ function createCliNodeLocalBridge(input) {
222844
223962
  copyLocalFile: async (request) => {
222845
223963
  const src = resolveReadPath(workspaceRoot, request.src);
222846
223964
  const dest = resolveWritePath(workspaceRoot, request.dest);
222847
- if (!isInside(workspaceRoot, src) || !isInside(workspaceRoot, dest)) return { ok: false, error: "Copy paths must stay inside the CLI workspace." };
222848
223965
  await fsp.mkdir(path10.dirname(dest), { recursive: true });
222849
223966
  await fsp.copyFile(src, dest);
222850
223967
  return { ok: true, fromPath: src, toPath: dest };
222851
223968
  },
222852
223969
  createDirectory: async (request) => {
222853
223970
  const target = resolveWritePath(workspaceRoot, request.path);
222854
- if (!isInside(workspaceRoot, target)) return { ok: false, error: `Refusing to create directory outside CLI workspace: ${target}` };
222855
223971
  await fsp.mkdir(target, { recursive: true });
222856
223972
  return { ok: true, path: target };
222857
223973
  },
222858
223974
  deleteLocalFile: async (request) => {
222859
223975
  const target = resolveWritePath(workspaceRoot, request.path);
222860
- if (!isInside(workspaceRoot, target)) return { ok: false, error: `Refusing to delete outside CLI workspace: ${target}` };
222861
- const stat = await safeStat(target);
222862
- if (!stat) return { ok: false, error: `File does not exist: ${target}` };
222863
- if (!stat.isFile() && !stat.isSymbolicLink()) return { ok: false, error: "deleteLocalFile only deletes files, not directories." };
223976
+ const stat2 = await safeStat(target);
223977
+ if (!stat2) return { ok: false, error: `File does not exist: ${target}` };
223978
+ if (!stat2.isFile() && !stat2.isSymbolicLink()) return { ok: false, error: "deleteLocalFile only deletes files, not directories." };
222864
223979
  await fsp.rm(target);
222865
223980
  return { ok: true, path: target };
222866
223981
  },
222867
223982
  printFile: async () => ({ ok: false, error: "Printing is not supported from Perch CLI local mode." }),
222868
223983
  listWorkspaceFilesGlob: async (request) => {
222869
- const root2 = request.path ? resolveReadPath(workspaceRoot, request.path) : workspaceRoot;
222870
- if (!isInside(workspaceRoot, root2)) {
222871
- return { ok: false, matches: [], totalFound: 0, truncated: false, resolvedRoot: workspaceRoot, executionHost: "electron_desktop", errorCode: "path_outside_workspace", errorMessage: `Path is outside CLI workspace: ${root2}` };
222872
- }
223984
+ const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
223985
+ const root2 = request.path ? resolveReadPath(baseRoot, request.path) : baseRoot;
222873
223986
  const maxResults = sanitizeMaxResults(request.maxResults);
222874
223987
  const pattern = request.pattern?.trim() || "**/*";
222875
223988
  const regex2 = globToRegex(pattern);
222876
223989
  const files = await collectFiles(root2, { maxVisits: Math.max(maxResults * 20, 1e3) });
222877
- const matches = files.map((file) => path10.relative(root2, file).replace(/\\/g, "/")).filter((relative) => regex2.test(relative)).slice(0, maxResults);
223990
+ const matches = files.map((file) => path10.relative(root2, file).replace(/\\/g, "/")).filter((relative2) => regex2.test(relative2)).slice(0, maxResults);
222878
223991
  return {
222879
223992
  ok: true,
222880
223993
  matches,
@@ -222884,23 +223997,21 @@ function createCliNodeLocalBridge(input) {
222884
223997
  dirsVisited: 0,
222885
223998
  pattern,
222886
223999
  searchPath: root2,
222887
- resolvedRoot: workspaceRoot,
224000
+ resolvedRoot: root2,
222888
224001
  executionHost: "electron_desktop"
222889
224002
  };
222890
224003
  },
222891
224004
  searchWorkspaceFilesGrep: async (request) => {
222892
- const root2 = request.path ? resolveReadPath(workspaceRoot, request.path) : workspaceRoot;
222893
- if (!isInside(workspaceRoot, root2)) {
222894
- return { ok: false, matches: [], totalMatches: 0, truncated: false, resolvedRoot: workspaceRoot, executionHost: "electron_desktop", errorCode: "path_outside_workspace", errorMessage: `Path is outside CLI workspace: ${root2}` };
222895
- }
224005
+ const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
224006
+ const root2 = request.path ? resolveReadPath(baseRoot, request.path) : baseRoot;
222896
224007
  const maxResults = sanitizeMaxResults(request.maxResults);
222897
224008
  const includeRegex = request.include ? globToRegex(request.include) : null;
222898
224009
  const queryRegex = compileQueryRegex(request.query, request.caseSensitive !== false);
222899
224010
  const files = await collectFiles(root2, { maxVisits: 5e3 });
222900
224011
  const matches = [];
222901
224012
  for (const file of files) {
222902
- const relative = path10.relative(root2, file).replace(/\\/g, "/");
222903
- if (includeRegex && !includeRegex.test(relative)) continue;
224013
+ const relative2 = path10.relative(root2, file).replace(/\\/g, "/");
224014
+ if (includeRegex && !includeRegex.test(relative2)) continue;
222904
224015
  const text = await readTextFileIfReasonable(file);
222905
224016
  if (text === null) continue;
222906
224017
  const lines = text.split(/\r?\n/);
@@ -222910,7 +224021,7 @@ function createCliNodeLocalBridge(input) {
222910
224021
  queryRegex.lastIndex = 0;
222911
224022
  if (!match) continue;
222912
224023
  matches.push({
222913
- file: relative,
224024
+ file: relative2,
222914
224025
  line: index + 1,
222915
224026
  column: match.index + 1,
222916
224027
  match: match[0],
@@ -222928,35 +224039,34 @@ function createCliNodeLocalBridge(input) {
222928
224039
  filesSearched: files.length,
222929
224040
  query: request.query,
222930
224041
  searchPath: root2,
222931
- resolvedRoot: workspaceRoot,
224042
+ resolvedRoot: root2,
222932
224043
  executionHost: "electron_desktop"
222933
224044
  };
222934
224045
  },
222935
224046
  statWorkspacePath: async (request) => {
222936
- const target = resolveReadPath(workspaceRoot, request.relativePath);
222937
- if (!isInside(workspaceRoot, target)) {
222938
- return { ok: false, relativePath: request.relativePath, exists: false, executionHost: "electron_desktop", errorCode: "path_outside_workspace", errorMessage: `Path is outside CLI workspace: ${target}` };
222939
- }
222940
- const stat = await safeStat(target);
224047
+ const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
224048
+ const target = resolveReadPath(baseRoot, request.relativePath);
224049
+ const stat2 = await safeStat(target);
222941
224050
  return {
222942
224051
  ok: true,
222943
- relativePath: path10.relative(workspaceRoot, target) || ".",
222944
- exists: Boolean(stat),
222945
- isFile: stat?.isFile() ?? false,
222946
- isDirectory: stat?.isDirectory() ?? false,
222947
- sizeBytes: stat?.size,
222948
- modifiedAt: stat?.mtime.toISOString(),
224052
+ relativePath: path10.isAbsolute(request.relativePath) || request.relativePath.startsWith("~") ? target : path10.relative(baseRoot, target) || ".",
224053
+ exists: Boolean(stat2),
224054
+ isFile: stat2?.isFile() ?? false,
224055
+ isDirectory: stat2?.isDirectory() ?? false,
224056
+ sizeBytes: stat2?.size,
224057
+ modifiedAt: stat2?.mtime.toISOString(),
222949
224058
  executionHost: "electron_desktop"
222950
224059
  };
222951
224060
  },
222952
224061
  listLocalSources: async (request) => {
222953
224062
  const query = typeof request === "object" && request ? request.query?.toLowerCase().trim() ?? "" : "";
222954
224063
  const maxResults = typeof request === "object" && request ? sanitizeMaxResults(request.maxResults) : DEFAULT_MAX_RESULTS;
222955
- const files = await collectFiles(workspaceRoot, { maxVisits: Math.max(maxResults * 20, 1e3) });
222956
- const entries = files.map((file) => localSourceEntry(workspaceRoot, file)).filter((entry) => !query || entry.relativePath.toLowerCase().includes(query) || entry.fileName.toLowerCase().includes(query)).slice(0, maxResults);
224064
+ const root2 = typeof request === "object" && request?.path ? resolveReadPath(workspaceRoot, request.path) : workspaceRoot;
224065
+ const files = await collectFiles(root2, { maxVisits: Math.max(maxResults * 20, 1e3) });
224066
+ const entries = files.map((file) => localSourceEntry(root2, file, root2 !== workspaceRoot)).filter((entry) => !query || entry.relativePath.toLowerCase().includes(query) || entry.fileName.toLowerCase().includes(query)).slice(0, maxResults);
222957
224067
  return {
222958
224068
  ok: true,
222959
- rootId: CLI_ROOT_ID,
224069
+ rootId: root2 === workspaceRoot ? CLI_ROOT_ID : "cli-absolute-root",
222960
224070
  entries,
222961
224071
  totalFound: files.length,
222962
224072
  truncated: files.length > maxResults,
@@ -222967,59 +224077,306 @@ function createCliNodeLocalBridge(input) {
222967
224077
  },
222968
224078
  readLocalFile: async (localSourceId) => {
222969
224079
  const target = resolveLocalSourceId(workspaceRoot, localSourceId);
222970
- if (!isInside(workspaceRoot, target)) return { ok: false, error: `Path is outside CLI workspace: ${target}` };
222971
- const stat = await safeStat(target);
222972
- if (!stat?.isFile()) return { ok: false, error: `File does not exist: ${target}` };
224080
+ const stat2 = await safeStat(target);
224081
+ if (!stat2?.isFile()) return { ok: false, error: `File does not exist: ${target}` };
222973
224082
  const buffer = await fsp.readFile(target);
222974
224083
  return {
222975
224084
  ok: true,
222976
224085
  data: buffer.toString("base64"),
222977
224086
  fileName: path10.relative(workspaceRoot, target) || path10.basename(target),
222978
224087
  mimeType: inferMimeType(target),
222979
- sizeBytes: stat.size,
224088
+ sizeBytes: stat2.size,
222980
224089
  encoding: "base64"
222981
224090
  };
222982
224091
  },
222983
224092
  getProjectRules: async () => [],
222984
- readProjectMemory: async () => ({ ok: false, error: "Project memory is desktop-only in CLI local mode." }),
222985
- writeProjectMemory: async () => ({ ok: false, error: "Project memory is desktop-only in CLI local mode." }),
222986
- writeMemoryFile: async () => ({ ok: false, error: "Project memory is desktop-only in CLI local mode." }),
222987
- writeRule: async () => ({ ok: false, error: "Project rules are desktop-only in CLI local mode." }),
222988
- writePerchMd: async () => ({ ok: false, error: "PERCH.md writes are desktop-only in CLI local mode." }),
222989
- readGlobalPerchMd: async () => ({ ok: false, error: "Global PERCH.md is desktop-only in CLI local mode." }),
222990
- writeGlobalPerchMd: async () => ({ ok: false, error: "Global PERCH.md is desktop-only in CLI local mode." }),
224093
+ readProjectMemory: async () => ({ ok: false, error: "Project memory is not available in this CLI package yet." }),
224094
+ writeProjectMemory: async () => ({ ok: false, error: "Project memory writes are not available in this CLI package yet." }),
224095
+ writeMemoryFile: async () => ({ ok: false, error: "Project memory files are not available in this CLI package yet." }),
224096
+ writeRule: async () => ({ ok: false, error: "Project rule writes are not available in this CLI package yet." }),
224097
+ writePerchMd: async () => ({ ok: false, error: "PERCH.md writes are not available in this CLI package yet." }),
224098
+ readGlobalPerchMd: async () => ({ ok: false, error: "Global PERCH.md is not available in this CLI package yet." }),
224099
+ writeGlobalPerchMd: async () => ({ ok: false, error: "Global PERCH.md writes are not available in this CLI package yet." }),
222991
224100
  getMcpStatus: async () => [],
222992
224101
  listMcpTools: async () => [],
222993
224102
  callMcpTool: async () => ({ ok: false, error: "MCP tools are not available in CLI local mode." }),
222994
224103
  reconnectMcp: async () => ({ ok: false, error: "MCP tools are not available in CLI local mode." }),
222995
- prepareSandboxInputs: async () => ({ ok: false, error: "Sandbox input staging is not available in CLI local mode." }),
222996
- onSandboxEvent: () => () => {
224104
+ runLocalAPAuditPacket: async (request) => runCliAPAuditPacket(workspaceRoot, request),
224105
+ runLocalPrepareAPEvidence: async (request) => runCliPrepareAPEvidence(workspaceRoot, request),
224106
+ runLocalQueryAPCases: async (request) => runCliQueryAPCases(workspaceRoot, request),
224107
+ runLocalRenderAPControlGraph: async (request) => runCliRenderAPControlGraph(workspaceRoot, request),
224108
+ prepareSandboxInputs: async (request) => prepareCliSandboxInputs(workspaceRoot, request),
224109
+ runSandboxCodeJob: async (request) => runCliSandboxCodeJob(workspaceRoot, request, emitSandboxEvent),
224110
+ onSandboxEvent: (runId, handler) => {
224111
+ const handlers = sandboxHandlers.get(runId) ?? /* @__PURE__ */ new Set();
224112
+ handlers.add(handler);
224113
+ sandboxHandlers.set(runId, handlers);
224114
+ return () => {
224115
+ handlers.delete(handler);
224116
+ if (handlers.size === 0) sandboxHandlers.delete(runId);
224117
+ };
222997
224118
  },
222998
224119
  offSandboxEvent: () => {
222999
224120
  }
223000
224121
  };
223001
224122
  }
223002
- async function runShellCommand(input) {
223003
- const startedAt = Date.now();
223004
- const cwd2 = input.cwd ? resolveReadPath(input.workspaceRoot, input.cwd) : input.workspaceRoot;
223005
- if (!isInside(input.workspaceRoot, cwd2)) {
224123
+ async function runCliAPAuditPacket(workspaceRoot, request) {
224124
+ const startMs = Date.now();
224125
+ try {
224126
+ const result2 = await generateAPCorePacket({
224127
+ folderPath: resolveReadPath(workspaceRoot, request.folderPath),
224128
+ outputRoot: request.outputRoot ? resolveWritePath(workspaceRoot, request.outputRoot) : void 0,
224129
+ timestamp: request.timestamp,
224130
+ writingStudioNarrativeHtml: request.writingStudioNarrativeHtml,
224131
+ writingStudioNarrativeText: request.writingStudioNarrativeText,
224132
+ writingStudioNarrativeDiagnostic: request.writingStudioNarrativeDiagnostic,
224133
+ requireWritingStudioNarrative: request.requireWritingStudioNarrative
224134
+ });
223006
224135
  return {
223007
- ok: false,
223008
- stdout: "",
223009
- stderr: "",
223010
- exitCode: null,
223011
- durationMs: Date.now() - startedAt,
223012
- timedOut: false,
223013
- command: input.command,
223014
- cwd: cwd2,
224136
+ ok: true,
224137
+ data: {
224138
+ packet: result2.payload.packet,
224139
+ outputDir: result2.run.outputDir,
224140
+ outputFiles: result2.payload.packet.outputFiles,
224141
+ summary: result2.summary
224142
+ },
223015
224143
  executionHost: "electron_desktop",
223016
- errorCode: "cwd_outside_workspace",
223017
- errorMessage: `Command cwd is outside CLI workspace: ${cwd2}`
224144
+ durationMs: Date.now() - startMs
223018
224145
  };
224146
+ } catch (error) {
224147
+ return cliAPAuditError(startMs, error);
223019
224148
  }
224149
+ }
224150
+ async function runCliPrepareAPEvidence(workspaceRoot, request) {
224151
+ const startMs = Date.now();
224152
+ try {
224153
+ const result2 = await prepareAPCoreEvidence({
224154
+ folderPath: resolveReadPath(workspaceRoot, request.folderPath),
224155
+ artifactRoot: request.artifactRoot ? resolveWritePath(workspaceRoot, request.artifactRoot) : void 0,
224156
+ timestamp: request.timestamp,
224157
+ mode: request.mode
224158
+ });
224159
+ const artifact = result2.payload.artifact;
224160
+ return {
224161
+ ok: true,
224162
+ data: {
224163
+ artifactId: artifact.artifactId,
224164
+ outputDir: artifact.outputDir,
224165
+ outputFiles: artifact.outputFiles,
224166
+ coverage: artifact.coverage,
224167
+ metrics: artifact.metrics,
224168
+ caseSummary: summarizeCliCases(artifact.cases ?? []),
224169
+ topCases: (artifact.cases ?? []).slice(0, 12),
224170
+ duplicateCases: result2.payload.duplicateCases,
224171
+ controlGraph: result2.payload.controlGraph,
224172
+ relativeOutputDir: typeof artifact.outputDir === "string" ? path10.basename(artifact.outputDir) : null,
224173
+ approvedRootMatch: true
224174
+ },
224175
+ executionHost: "electron_desktop",
224176
+ durationMs: Date.now() - startMs
224177
+ };
224178
+ } catch (error) {
224179
+ return cliAPError(startMs, error);
224180
+ }
224181
+ }
224182
+ async function runCliQueryAPCases(workspaceRoot, request) {
224183
+ const startMs = Date.now();
224184
+ try {
224185
+ const result2 = await queryAPCases(
224186
+ normalizeCliArtifactRequest(workspaceRoot, request)
224187
+ );
224188
+ return {
224189
+ ok: result2.ok,
224190
+ data: result2,
224191
+ error: result2.ok ? void 0 : result2.error,
224192
+ errorCode: result2.ok ? void 0 : result2.errorCode,
224193
+ executionHost: "electron_desktop",
224194
+ durationMs: Date.now() - startMs
224195
+ };
224196
+ } catch (error) {
224197
+ return cliAPError(startMs, error);
224198
+ }
224199
+ }
224200
+ async function runCliRenderAPControlGraph(workspaceRoot, request) {
224201
+ const startMs = Date.now();
224202
+ try {
224203
+ const result2 = await renderAPControlGraph(normalizeCliArtifactRequest(workspaceRoot, request));
224204
+ return {
224205
+ ok: result2.ok,
224206
+ data: result2,
224207
+ error: result2.ok ? void 0 : result2.error,
224208
+ errorCode: result2.ok ? void 0 : result2.errorCode,
224209
+ executionHost: "electron_desktop",
224210
+ durationMs: Date.now() - startMs
224211
+ };
224212
+ } catch (error) {
224213
+ return cliAPError(startMs, error);
224214
+ }
224215
+ }
224216
+ function normalizeCliArtifactRequest(workspaceRoot, request) {
224217
+ return {
224218
+ ...request,
224219
+ folderPath: request.folderPath ? resolveReadPath(workspaceRoot, request.folderPath) : void 0,
224220
+ artifactRoot: request.artifactRoot ? resolveWritePath(workspaceRoot, request.artifactRoot) : void 0
224221
+ };
224222
+ }
224223
+ function cliAPError(startMs, error) {
224224
+ const message = error instanceof Error ? error.message : String(error);
224225
+ return {
224226
+ ok: false,
224227
+ error: message,
224228
+ errorCode: classifyCliAPError(message),
224229
+ executionHost: "electron_desktop",
224230
+ durationMs: Date.now() - startMs
224231
+ };
224232
+ }
224233
+ function cliAPAuditError(startMs, error) {
224234
+ const message = error instanceof Error ? error.message : String(error);
224235
+ return {
224236
+ ok: false,
224237
+ error: message,
224238
+ errorCode: classifyCliAPError(message),
224239
+ executionHost: "electron_desktop",
224240
+ durationMs: Date.now() - startMs
224241
+ };
224242
+ }
224243
+ function classifyCliAPError(message) {
224244
+ if (/folderPath is required|invalid/i.test(message)) return "invalid_input";
224245
+ if (/not a directory/i.test(message)) return "invalid_folder";
224246
+ if (/not found|ENOENT/i.test(message)) return "folder_not_found";
224247
+ return "execution_failed";
224248
+ }
224249
+ function summarizeCliCases(cases) {
224250
+ const counts = {};
224251
+ for (const item of cases) {
224252
+ const severity = item.severity ?? "unknown";
224253
+ counts[severity] = (counts[severity] ?? 0) + 1;
224254
+ }
224255
+ return counts;
224256
+ }
224257
+ async function prepareCliSandboxInputs(workspaceRoot, request) {
224258
+ const jobId = request.jobId?.trim() || `cli-sandbox-${Date.now()}`;
224259
+ const maxFiles = Math.max(1, Math.min(request.maxFiles ?? 20, 100));
224260
+ const maxBytesPerFile = Math.max(1, Math.min(request.maxBytesPerFile ?? 20 * 1024 * 1024, 50 * 1024 * 1024));
224261
+ const tempDir = await fsp.mkdtemp(path10.join(os.tmpdir(), `${jobId}-`));
224262
+ const entries = [];
224263
+ const skipped = [];
224264
+ for (const localSourceId of request.localSourceIds.slice(0, maxFiles)) {
224265
+ const sourcePath = resolveLocalSourceId(workspaceRoot, localSourceId);
224266
+ const stat2 = await safeStat(sourcePath);
224267
+ if (!stat2?.isFile()) {
224268
+ skipped.push({ localSourceId, reason: `File does not exist: ${sourcePath}` });
224269
+ continue;
224270
+ }
224271
+ if (stat2.size > maxBytesPerFile) {
224272
+ skipped.push({ localSourceId, reason: `File is too large: ${stat2.size} bytes.` });
224273
+ continue;
224274
+ }
224275
+ const relativePath = safeSandboxRelativePath(path10.isAbsolute(localSourceId) || localSourceId.startsWith("~") ? path10.basename(sourcePath) : path10.relative(workspaceRoot, sourcePath));
224276
+ const tempPath = path10.join(tempDir, relativePath);
224277
+ await fsp.mkdir(path10.dirname(tempPath), { recursive: true });
224278
+ await fsp.copyFile(sourcePath, tempPath);
224279
+ entries.push({
224280
+ localSourceId,
224281
+ fileName: path10.basename(sourcePath),
224282
+ relativePath,
224283
+ tempPath,
224284
+ sizeBytes: stat2.size,
224285
+ mimeType: inferMimeType(sourcePath),
224286
+ fileType: guessCliFileType(sourcePath)
224287
+ });
224288
+ }
224289
+ return { ok: true, jobId, tempDir, entries, skipped };
224290
+ }
224291
+ async function runCliSandboxCodeJob(workspaceRoot, request, onEvent) {
224292
+ const hasCommand = typeof request?.command === "string" && request.command.trim().length > 0;
224293
+ const hasCode = typeof request?.code === "string" && request.code.trim().length > 0;
224294
+ if (!request || !hasCommand && !hasCode) {
224295
+ return {
224296
+ ok: false,
224297
+ kind: "blocked",
224298
+ message: "run_sandbox_code requires either command or code.",
224299
+ error: "invalid_input",
224300
+ executionHost: "electron-main"
224301
+ };
224302
+ }
224303
+ const hostResult = await runDesktopSandboxCodeJob(
224304
+ {
224305
+ runId: request.runId,
224306
+ workspaceKey: request.workspaceKey ?? request.runId ?? null,
224307
+ command: request.command,
224308
+ language: request.language,
224309
+ code: request.code,
224310
+ label: request.label ?? null,
224311
+ sources: Array.isArray(request.sources) ? request.sources : [],
224312
+ timeoutMs: request.timeoutMs,
224313
+ networkPolicy: request.networkPolicy ?? "disabled"
224314
+ },
224315
+ {
224316
+ getApprovedRoot: (rootId) => rootId === CLI_ROOT_ID ? { id: CLI_ROOT_ID, path: workspaceRoot, approvedAt: (/* @__PURE__ */ new Date()).toISOString() } : null,
224317
+ isPathSafe: (_rootPath, targetPath) => !targetPath.split(/[\\/]+/).includes(".."),
224318
+ isInsideApprovedRoot: (filePath) => {
224319
+ const absolute = path10.resolve(expandHome4(filePath));
224320
+ if (isInside(workspaceRoot, absolute)) {
224321
+ return { id: CLI_ROOT_ID, path: workspaceRoot, approvedAt: (/* @__PURE__ */ new Date()).toISOString() };
224322
+ }
224323
+ return { id: "cli-absolute-root", path: path10.dirname(absolute), approvedAt: (/* @__PURE__ */ new Date()).toISOString() };
224324
+ },
224325
+ shouldIgnore: (fileName) => IGNORED_DIRS.has(fileName),
224326
+ guessMimeType: inferMimeType,
224327
+ onEvent
224328
+ }
224329
+ );
224330
+ const data = sandboxHostData(hostResult);
224331
+ return {
224332
+ ok: hostResult.ok,
224333
+ kind: hostResult.kind,
224334
+ message: hostResult.message,
224335
+ error: hostResult.ok ? void 0 : "execution_failed",
224336
+ executionHost: "electron-main",
224337
+ runId: hostResult.runId,
224338
+ data
224339
+ };
224340
+ }
224341
+ function sandboxHostData(result2) {
224342
+ return {
224343
+ status: result2.status,
224344
+ exitCode: result2.exitCode,
224345
+ durationMs: result2.durationMs,
224346
+ stdout: result2.stdout,
224347
+ stderr: result2.stderr,
224348
+ stdoutTruncated: result2.stdoutTruncated,
224349
+ stderrTruncated: result2.stderrTruncated,
224350
+ producedFiles: result2.producedFiles,
224351
+ inputManifest: result2.inputManifest,
224352
+ runtimeInfo: result2.runtimeInfo,
224353
+ structuredOutput: result2.structuredOutput,
224354
+ workspacePath: result2.workspacePath,
224355
+ language: result2.language,
224356
+ reportJsonPresent: result2.reportJsonPresent,
224357
+ codeSha256: result2.codeSha256,
224358
+ warnings: result2.warnings
224359
+ };
224360
+ }
224361
+ function safeSandboxRelativePath(value) {
224362
+ const clean = value.replace(/\\/g, "/").split("/").filter((part) => part && part !== "." && part !== "..").join("/");
224363
+ return clean || "input";
224364
+ }
224365
+ function guessCliFileType(filePath) {
224366
+ const ext = path10.extname(filePath).toLowerCase();
224367
+ if ([".xlsx", ".xls"].includes(ext)) return "spreadsheet";
224368
+ if (ext === ".csv" || ext === ".tsv") return "csv";
224369
+ if (ext === ".pdf") return "pdf";
224370
+ if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(ext)) return "image";
224371
+ if ([".doc", ".docx", ".txt", ".md", ".rtf"].includes(ext)) return "document";
224372
+ return "unknown";
224373
+ }
224374
+ async function runShellCommand(input) {
224375
+ const startedAt = Date.now();
224376
+ const cwd2 = input.cwd ? resolveReadPath(input.workspaceRoot, input.cwd) : input.workspaceRoot;
223020
224377
  const timeoutMs = Math.max(1e3, Math.min(input.timeoutMs ?? 3e4, 12e4));
223021
- return new Promise((resolve4) => {
223022
- const child = spawn(process.env.SHELL || "/bin/zsh", ["-lc", input.command], {
224378
+ return new Promise((resolve5) => {
224379
+ const child = spawn2(process.env.SHELL || "/bin/zsh", ["-lc", input.command], {
223023
224380
  cwd: cwd2,
223024
224381
  env: process.env,
223025
224382
  stdio: ["ignore", "pipe", "pipe"]
@@ -223039,7 +224396,7 @@ async function runShellCommand(input) {
223039
224396
  });
223040
224397
  child.on("error", (error) => {
223041
224398
  clearTimeout(timer);
223042
- resolve4({
224399
+ resolve5({
223043
224400
  ok: false,
223044
224401
  stdout,
223045
224402
  stderr,
@@ -223056,7 +224413,7 @@ async function runShellCommand(input) {
223056
224413
  child.on("close", (code, signal) => {
223057
224414
  clearTimeout(timer);
223058
224415
  const capped = capOutput(stdout, stderr);
223059
- resolve4({
224416
+ resolve5({
223060
224417
  ok: code === 0 && !timedOut,
223061
224418
  stdout: capped.stdout,
223062
224419
  stderr: capped.stderr,
@@ -223077,8 +224434,18 @@ function resolveReadPath(root2, inputPath) {
223077
224434
  const expanded = expandHome4(inputPath || ".");
223078
224435
  return path10.resolve(path10.isAbsolute(expanded) ? expanded : path10.join(root2, expanded));
223079
224436
  }
224437
+ function resolveRequestRoot(defaultRoot, requestRoot) {
224438
+ const trimmed = requestRoot?.trim();
224439
+ return trimmed ? resolveReadPath(defaultRoot, trimmed) : defaultRoot;
224440
+ }
223080
224441
  function resolveWritePath(root2, inputPath) {
223081
- return path10.resolve(root2, inputPath || ".");
224442
+ const expanded = expandHome4(inputPath || ".");
224443
+ return path10.resolve(path10.isAbsolute(expanded) ? expanded : path10.join(root2, expanded));
224444
+ }
224445
+ function displayPath(root2, target, requestedPath) {
224446
+ const expanded = expandHome4(requestedPath || ".");
224447
+ if (path10.isAbsolute(expanded) || requestedPath.startsWith("~")) return target;
224448
+ return path10.relative(root2, target) || path10.basename(target);
223082
224449
  }
223083
224450
  function resolveLocalSourceId(root2, localSourceId) {
223084
224451
  const raw = localSourceId.includes("::") ? localSourceId.split("::").slice(1).join("::") : localSourceId;
@@ -223090,8 +224457,8 @@ function expandHome4(inputPath) {
223090
224457
  return inputPath;
223091
224458
  }
223092
224459
  function isInside(root2, candidate) {
223093
- const relative = path10.relative(path10.resolve(root2), path10.resolve(candidate));
223094
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
224460
+ const relative2 = path10.relative(path10.resolve(root2), path10.resolve(candidate));
224461
+ return relative2 === "" || !relative2.startsWith("..") && !path10.isAbsolute(relative2);
223095
224462
  }
223096
224463
  async function safeStat(target) {
223097
224464
  try {
@@ -223154,8 +224521,8 @@ function escapeRegex2(value) {
223154
224521
  return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
223155
224522
  }
223156
224523
  async function readTextFileIfReasonable(file) {
223157
- const stat = await safeStat(file);
223158
- if (!stat?.isFile() || stat.size > 1e6) return null;
224524
+ const stat2 = await safeStat(file);
224525
+ if (!stat2?.isFile() || stat2.size > 1e6) return null;
223159
224526
  const buffer = await fsp.readFile(file);
223160
224527
  if (buffer.includes(0)) return null;
223161
224528
  return buffer.toString("utf8");
@@ -223163,18 +224530,18 @@ async function readTextFileIfReasonable(file) {
223163
224530
  function sanitizeMaxResults(value) {
223164
224531
  return Math.max(1, Math.min(typeof value === "number" ? Math.floor(value) : DEFAULT_MAX_RESULTS, 1e3));
223165
224532
  }
223166
- function localSourceEntry(root2, file) {
223167
- const stat = fs9.statSync(file);
224533
+ function localSourceEntry(root2, file, absoluteId = false) {
224534
+ const stat2 = fs9.statSync(file);
223168
224535
  const relativePath = path10.relative(root2, file);
223169
224536
  const extension2 = path10.extname(file).toLowerCase();
223170
224537
  return {
223171
- localSourceId: `${CLI_ROOT_ID}::${relativePath}`,
223172
- rootId: CLI_ROOT_ID,
224538
+ localSourceId: absoluteId ? file : `${CLI_ROOT_ID}::${relativePath}`,
224539
+ rootId: absoluteId ? "cli-absolute-root" : CLI_ROOT_ID,
223173
224540
  relativePath,
223174
224541
  fileName: path10.basename(file),
223175
224542
  extension: extension2,
223176
- sizeBytes: stat.size,
223177
- modifiedAt: stat.mtime.toISOString(),
224543
+ sizeBytes: stat2.size,
224544
+ modifiedAt: stat2.mtime.toISOString(),
223178
224545
  mimeType: inferMimeType(file),
223179
224546
  isDirectory: false
223180
224547
  };
@@ -223214,6 +224581,9 @@ var CLI_ROOT_ID, DEFAULT_MAX_RESULTS, MAX_READ_BYTES, IGNORED_DIRS;
223214
224581
  var init_nodeLocalBridge = __esm({
223215
224582
  "features/perchTerminal/runtime/cliHost/nodeLocalBridge.ts"() {
223216
224583
  "use strict";
224584
+ init_perchCore();
224585
+ init_perchBusinessTools();
224586
+ init_localSandboxHost();
223217
224587
  CLI_ROOT_ID = "cli-root";
223218
224588
  DEFAULT_MAX_RESULTS = 200;
223219
224589
  MAX_READ_BYTES = 2e6;
@@ -223282,6 +224652,7 @@ function buildCliTurnInput(input, resolved) {
223282
224652
  const userId = input.userId ?? null;
223283
224653
  return {
223284
224654
  trimmedInput: resolved.prompt,
224655
+ clientRunId: input.clientRunId ?? null,
223285
224656
  chatMode: input.chatMode ?? "agents",
223286
224657
  threadId: resolved.threadId,
223287
224658
  personaId: input.personaId ?? DEFAULT_PERSONA_ID,
@@ -223867,8 +225238,8 @@ function createMemoryAuthStorage() {
223867
225238
  }
223868
225239
  async function createOAuthCallbackServer(input) {
223869
225240
  let resolveResult = null;
223870
- const resultPromise = new Promise((resolve4) => {
223871
- resolveResult = resolve4;
225241
+ const resultPromise = new Promise((resolve5) => {
225242
+ resolveResult = resolve5;
223872
225243
  });
223873
225244
  const server = http.createServer((request, response) => {
223874
225245
  const requestUrl = new URL(request.url ?? "/", `http://${input.host}`);
@@ -223895,11 +225266,11 @@ async function createOAuthCallbackServer(input) {
223895
225266
  resolveResult?.({ ok: true, code });
223896
225267
  resolveResult = null;
223897
225268
  });
223898
- await new Promise((resolve4, reject2) => {
225269
+ await new Promise((resolve5, reject2) => {
223899
225270
  server.once("error", reject2);
223900
225271
  server.listen(0, input.host, () => {
223901
225272
  server.off("error", reject2);
223902
- resolve4();
225273
+ resolve5();
223903
225274
  });
223904
225275
  });
223905
225276
  const address = server.address();
@@ -223917,7 +225288,7 @@ async function createOAuthCallbackServer(input) {
223917
225288
  waitForCode: async () => resultPromise,
223918
225289
  close: async () => {
223919
225290
  clearTimeout(timeout);
223920
- await new Promise((resolve4) => server.close(() => resolve4()));
225291
+ await new Promise((resolve5) => server.close(() => resolve5()));
223921
225292
  }
223922
225293
  };
223923
225294
  }
@@ -224871,21 +226242,21 @@ var require_react_development = __commonJS({
224871
226242
  );
224872
226243
  actScopeDepth = prevActScopeDepth;
224873
226244
  }
224874
- function recursivelyFlushAsyncActWork(returnValue, resolve4, reject2) {
226245
+ function recursivelyFlushAsyncActWork(returnValue, resolve5, reject2) {
224875
226246
  var queue2 = ReactSharedInternals.actQueue;
224876
226247
  if (null !== queue2)
224877
226248
  if (0 !== queue2.length)
224878
226249
  try {
224879
226250
  flushActQueue(queue2);
224880
226251
  enqueueTask(function() {
224881
- return recursivelyFlushAsyncActWork(returnValue, resolve4, reject2);
226252
+ return recursivelyFlushAsyncActWork(returnValue, resolve5, reject2);
224882
226253
  });
224883
226254
  return;
224884
226255
  } catch (error) {
224885
226256
  ReactSharedInternals.thrownErrors.push(error);
224886
226257
  }
224887
226258
  else ReactSharedInternals.actQueue = null;
224888
- 0 < ReactSharedInternals.thrownErrors.length ? (queue2 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject2(queue2)) : resolve4(returnValue);
226259
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue2 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject2(queue2)) : resolve5(returnValue);
224889
226260
  }
224890
226261
  function flushActQueue(queue2) {
224891
226262
  if (!isFlushing) {
@@ -225072,7 +226443,7 @@ var require_react_development = __commonJS({
225072
226443
  ));
225073
226444
  });
225074
226445
  return {
225075
- then: function(resolve4, reject2) {
226446
+ then: function(resolve5, reject2) {
225076
226447
  didAwaitActCall = true;
225077
226448
  thenable.then(
225078
226449
  function(returnValue) {
@@ -225082,7 +226453,7 @@ var require_react_development = __commonJS({
225082
226453
  flushActQueue(queue2), enqueueTask(function() {
225083
226454
  return recursivelyFlushAsyncActWork(
225084
226455
  returnValue,
225085
- resolve4,
226456
+ resolve5,
225086
226457
  reject2
225087
226458
  );
225088
226459
  });
@@ -225096,7 +226467,7 @@ var require_react_development = __commonJS({
225096
226467
  ReactSharedInternals.thrownErrors.length = 0;
225097
226468
  reject2(_thrownError);
225098
226469
  }
225099
- } else resolve4(returnValue);
226470
+ } else resolve5(returnValue);
225100
226471
  },
225101
226472
  function(error) {
225102
226473
  popActScope(prevActQueue, prevActScopeDepth);
@@ -225118,15 +226489,15 @@ var require_react_development = __commonJS({
225118
226489
  if (0 < ReactSharedInternals.thrownErrors.length)
225119
226490
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
225120
226491
  return {
225121
- then: function(resolve4, reject2) {
226492
+ then: function(resolve5, reject2) {
225122
226493
  didAwaitActCall = true;
225123
226494
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue2, enqueueTask(function() {
225124
226495
  return recursivelyFlushAsyncActWork(
225125
226496
  returnValue$jscomp$0,
225126
- resolve4,
226497
+ resolve5,
225127
226498
  reject2
225128
226499
  );
225129
- })) : resolve4(returnValue$jscomp$0);
226500
+ })) : resolve5(returnValue$jscomp$0);
225130
226501
  }
225131
226502
  };
225132
226503
  };
@@ -225553,7 +226924,7 @@ var init_compat = __esm({
225553
226924
  });
225554
226925
 
225555
226926
  // node_modules/environment/index.js
225556
- var isBrowser, isNode2, isBun, isDeno, isElectron, isJsDom, isWebWorker, isDedicatedWorker, isSharedWorker, isServiceWorker, platform, isMacOs, isWindows, isLinux, isIos, isAndroid;
226927
+ var isBrowser, isNode2, isBun, isDeno, isElectron, isJsDom, isWebWorker, isDedicatedWorker, isSharedWorker, isServiceWorker, platform2, isMacOs, isWindows, isLinux, isIos, isAndroid;
225557
226928
  var init_environment = __esm({
225558
226929
  "node_modules/environment/index.js"() {
225559
226930
  isBrowser = globalThis.window?.document !== void 0;
@@ -225566,12 +226937,12 @@ var init_environment = __esm({
225566
226937
  isDedicatedWorker = typeof DedicatedWorkerGlobalScope !== "undefined" && globalThis instanceof DedicatedWorkerGlobalScope;
225567
226938
  isSharedWorker = typeof SharedWorkerGlobalScope !== "undefined" && globalThis instanceof SharedWorkerGlobalScope;
225568
226939
  isServiceWorker = typeof ServiceWorkerGlobalScope !== "undefined" && globalThis instanceof ServiceWorkerGlobalScope;
225569
- platform = globalThis.navigator?.userAgentData?.platform;
225570
- isMacOs = platform === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
225571
- isWindows = platform === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
225572
- isLinux = platform === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
225573
- isIos = platform === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
225574
- isAndroid = platform === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
226940
+ platform2 = globalThis.navigator?.userAgentData?.platform;
226941
+ isMacOs = platform2 === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
226942
+ isWindows = platform2 === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
226943
+ isLinux = platform2 === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
226944
+ isIos = platform2 === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
226945
+ isAndroid = platform2 === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
225575
226946
  }
225576
226947
  });
225577
226948
 
@@ -229888,8 +231259,8 @@ var require_react_reconciler_production = __commonJS({
229888
231259
  currentEntangledActionThenable = {
229889
231260
  status: "pending",
229890
231261
  value: void 0,
229891
- then: function(resolve4) {
229892
- entangledListeners.push(resolve4);
231262
+ then: function(resolve5) {
231263
+ entangledListeners.push(resolve5);
229893
231264
  }
229894
231265
  };
229895
231266
  }
@@ -229912,8 +231283,8 @@ var require_react_reconciler_production = __commonJS({
229912
231283
  status: "pending",
229913
231284
  value: null,
229914
231285
  reason: null,
229915
- then: function(resolve4) {
229916
- listeners.push(resolve4);
231286
+ then: function(resolve5) {
231287
+ listeners.push(resolve5);
229917
231288
  }
229918
231289
  };
229919
231290
  thenable.then(
@@ -239512,8 +240883,8 @@ var require_react_reconciler_development = __commonJS({
239512
240883
  currentEntangledActionThenable = {
239513
240884
  status: "pending",
239514
240885
  value: void 0,
239515
- then: function(resolve4) {
239516
- entangledListeners.push(resolve4);
240886
+ then: function(resolve5) {
240887
+ entangledListeners.push(resolve5);
239517
240888
  }
239518
240889
  };
239519
240890
  }
@@ -239536,8 +240907,8 @@ var require_react_reconciler_development = __commonJS({
239536
240907
  status: "pending",
239537
240908
  value: null,
239538
240909
  reason: null,
239539
- then: function(resolve4) {
239540
- listeners.push(resolve4);
240910
+ then: function(resolve5) {
240911
+ listeners.push(resolve5);
239541
240912
  }
239542
240913
  };
239543
240914
  thenable.then(
@@ -254995,7 +256366,7 @@ var require_websocket = __commonJS({
254995
256366
  var http2 = __require("http");
254996
256367
  var net = __require("net");
254997
256368
  var tls = __require("tls");
254998
- var { randomBytes, createHash: createHash2 } = __require("crypto");
256369
+ var { randomBytes, createHash: createHash3 } = __require("crypto");
254999
256370
  var { Duplex, Readable } = __require("stream");
255000
256371
  var { URL: URL2 } = __require("url");
255001
256372
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -255663,7 +257034,7 @@ var require_websocket = __commonJS({
255663
257034
  abortHandshake(websocket, socket, "Invalid Upgrade header");
255664
257035
  return;
255665
257036
  }
255666
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
257037
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
255667
257038
  if (res.headers["sec-websocket-accept"] !== digest) {
255668
257039
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
255669
257040
  return;
@@ -256032,7 +257403,7 @@ var require_websocket_server = __commonJS({
256032
257403
  var EventEmitter3 = __require("events");
256033
257404
  var http2 = __require("http");
256034
257405
  var { Duplex } = __require("stream");
256035
- var { createHash: createHash2 } = __require("crypto");
257406
+ var { createHash: createHash3 } = __require("crypto");
256036
257407
  var extension2 = require_extension();
256037
257408
  var PerMessageDeflate2 = require_permessage_deflate();
256038
257409
  var subprotocol2 = require_subprotocol();
@@ -256339,7 +257710,7 @@ var require_websocket_server = __commonJS({
256339
257710
  );
256340
257711
  }
256341
257712
  if (this._state > RUNNING) return abortHandshake(socket, 503);
256342
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
257713
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
256343
257714
  const headers = [
256344
257715
  "HTTP/1.1 101 Switching Protocols",
256345
257716
  "Upgrade: websocket",
@@ -277257,8 +278628,8 @@ var init_ink = __esm({
277257
278628
  }
277258
278629
  }
277259
278630
  async waitUntilExit() {
277260
- this.exitPromise ||= new Promise((resolve4, reject2) => {
277261
- this.resolveExitPromise = resolve4;
278631
+ this.exitPromise ||= new Promise((resolve5, reject2) => {
278632
+ this.resolveExitPromise = resolve5;
277262
278633
  this.rejectExitPromise = reject2;
277263
278634
  });
277264
278635
  if (!this.beforeExitHandler) {
@@ -278731,6 +280102,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
278731
280102
  const [pulse, setPulse] = React11.useState(0);
278732
280103
  const [, refresh] = React11.useState(0);
278733
280104
  const liveTextRef = React11.useRef("");
280105
+ const activeRunRef = React11.useRef(null);
278734
280106
  React11.useEffect(() => {
278735
280107
  if (!working) return void 0;
278736
280108
  const timer = setInterval(() => setPulse((value) => value + 1), 120);
@@ -278799,6 +280171,15 @@ async function runInkInteractivePerchCli(writer, deps, options) {
278799
280171
  setLiveText("");
278800
280172
  liveTextRef.current = "";
278801
280173
  const toolNamesById = /* @__PURE__ */ new Map();
280174
+ const clientRunId = createCliRunId();
280175
+ const externalController = new AbortController();
280176
+ const runtimeRun = registerRuntimeRun({
280177
+ runId: clientRunId,
280178
+ kind: "turn",
280179
+ threadId: state.threadId,
280180
+ parentSignal: externalController.signal
280181
+ });
280182
+ activeRunRef.current = { runId: clientRunId, threadId: state.threadId };
278802
280183
  try {
278803
280184
  if (!isCliModelConnectionReady(connection)) {
278804
280185
  addItem({
@@ -278824,8 +280205,10 @@ async function runInkInteractivePerchCli(writer, deps, options) {
278824
280205
  workspaceId: hostedContext.workspaceId,
278825
280206
  permanentMemories: hostedContext.permanentMemories,
278826
280207
  founderModelSelection: connection.founderModelSelection,
280208
+ clientRunId,
278827
280209
  desktopConnected: state.desktopConnected,
278828
280210
  cliLocalTools: state.cliLocalTools,
280211
+ signal: runtimeRun.controller.signal,
278829
280212
  onEvent: (event) => {
278830
280213
  switch (event.type) {
278831
280214
  case "content_delta":
@@ -278919,6 +280302,11 @@ async function runInkInteractivePerchCli(writer, deps, options) {
278919
280302
  } catch (error) {
278920
280303
  addItem({ label: "stop", text: humanizeCliError(errorMessage(error)), tone: "danger" });
278921
280304
  } finally {
280305
+ finishRuntimeRun(clientRunId, runtimeRun.controller.signal.aborted ? "cancelled" : "completed");
280306
+ externalController.abort();
280307
+ if (activeRunRef.current?.runId === clientRunId) {
280308
+ activeRunRef.current = null;
280309
+ }
278922
280310
  setWorking(false);
278923
280311
  setWorkingText("ready");
278924
280312
  setLiveText("");
@@ -278926,7 +280314,44 @@ async function runInkInteractivePerchCli(writer, deps, options) {
278926
280314
  }
278927
280315
  }, [addItem, app, reconnect, runTurn, updateToolItem, working]);
278928
280316
  Ink2.useInput((input, key) => {
278929
- if (working) return;
280317
+ if (working) {
280318
+ if (key.ctrl && input === "c") {
280319
+ const active = activeRunRef.current;
280320
+ if (active && abortRuntimeRun(active.runId, "Stopped from CLI.")) {
280321
+ addItem({ label: "stop", text: "stop requested", tone: "danger" });
280322
+ setWorkingText("stopping");
280323
+ }
280324
+ return;
280325
+ }
280326
+ if (key.return) {
280327
+ const steerText = draft.trim();
280328
+ const active = activeRunRef.current;
280329
+ if (steerText && active) {
280330
+ const result2 = submitRuntimeSteer({
280331
+ runId: active.runId,
280332
+ expectedRunId: active.runId,
280333
+ threadId: active.threadId,
280334
+ text: steerText,
280335
+ mode: "append",
280336
+ clientMessageId: `cli-steer-${Date.now()}`
280337
+ });
280338
+ if (result2.ok) {
280339
+ addItem({ label: "you", text: steerText, tone: "touch" });
280340
+ addItem({ label: "system", text: "steer queued", tone: "muted" });
280341
+ setDraft("");
280342
+ } else {
280343
+ addItem({ label: "need", text: result2.error, tone: "danger" });
280344
+ }
280345
+ }
280346
+ return;
280347
+ }
280348
+ if (key.backspace || key.delete) {
280349
+ setDraft((value) => value.slice(0, -1));
280350
+ return;
280351
+ }
280352
+ if (input && !key.escape) setDraft((value) => value + input);
280353
+ return;
280354
+ }
278930
280355
  if (key.return) {
278931
280356
  void submitPrompt(draft);
278932
280357
  return;
@@ -279073,7 +280498,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
279073
280498
  )
279074
280499
  );
279075
280500
  }),
279076
- { exitOnCtrlC: true }
280501
+ { exitOnCtrlC: false }
279077
280502
  );
279078
280503
  await instance.waitUntilExit();
279079
280504
  connection.restore();
@@ -279601,6 +281026,9 @@ function trimRecentMessages(messages) {
279601
281026
  messages.splice(0, messages.length - 30);
279602
281027
  }
279603
281028
  }
281029
+ function createCliRunId() {
281030
+ return `cli-turn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
281031
+ }
279604
281032
  async function runAuthCommand(parsed, writer) {
279605
281033
  if (parsed.action === "logout") {
279606
281034
  await clearStoredCliAuthSession();
@@ -279641,7 +281069,7 @@ async function runAuthCommand(parsed, writer) {
279641
281069
  `);
279642
281070
  return 0;
279643
281071
  }
279644
- await new Promise((resolve4) => setTimeout(resolve4, 1500));
281072
+ await new Promise((resolve5) => setTimeout(resolve5, 1500));
279645
281073
  }
279646
281074
  writer.stderr("No CLI session arrived. Try `perch login` again after confirming the browser sign-in completed.\n");
279647
281075
  return 2;
@@ -279774,14 +281202,14 @@ function writeAPScenarioResult(result2, json, writer) {
279774
281202
  }
279775
281203
  function resolveFolderPath(input) {
279776
281204
  const resolved = resolvePath(input);
279777
- const stat = fs13.existsSync(resolved) ? fs13.statSync(resolved) : null;
279778
- if (!stat?.isDirectory()) throw new Error(`Folder does not exist: ${resolved}`);
281205
+ const stat2 = fs13.existsSync(resolved) ? fs13.statSync(resolved) : null;
281206
+ if (!stat2?.isDirectory()) throw new Error(`Folder does not exist: ${resolved}`);
279779
281207
  return resolved;
279780
281208
  }
279781
281209
  function resolveExistingDirectory(input) {
279782
281210
  const resolved = resolvePath(input);
279783
- const stat = fs13.existsSync(resolved) ? fs13.statSync(resolved) : null;
279784
- if (!stat?.isDirectory()) throw new Error(`Directory does not exist: ${resolved}`);
281211
+ const stat2 = fs13.existsSync(resolved) ? fs13.statSync(resolved) : null;
281212
+ if (!stat2?.isDirectory()) throw new Error(`Directory does not exist: ${resolved}`);
279785
281213
  return resolved;
279786
281214
  }
279787
281215
  function resolvePath(input) {
@@ -279871,6 +281299,7 @@ var init_perch_cli = __esm({
279871
281299
  init_cliStandaloneOAuth();
279872
281300
  init_contextMeterDisplay();
279873
281301
  init_threadSession();
281302
+ init_runRegistry();
279874
281303
  execFileAsync3 = promisify3(execFile3);
279875
281304
  DEFAULT_CLI_LOGIN_APP_URL = "https://app.perchai.app";
279876
281305
  CLI_PACKAGE_VERSION = readCliPackageVersion();