@cortexkit/aft-opencode 0.30.1 → 0.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12399,7 +12399,7 @@ var require_snapshot_utils = __commonJS((exports, module) => {
12399
12399
  // ../../node_modules/.bun/undici@7.25.0/node_modules/undici/lib/mock/snapshot-recorder.js
12400
12400
  var require_snapshot_recorder = __commonJS((exports, module) => {
12401
12401
  var { writeFile, readFile, mkdir } = __require("node:fs/promises");
12402
- var { dirname: dirname3, resolve: resolve2 } = __require("node:path");
12402
+ var { dirname: dirname4, resolve: resolve2 } = __require("node:path");
12403
12403
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
12404
12404
  var { InvalidArgumentError, UndiciError } = require_errors();
12405
12405
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -12594,7 +12594,7 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
12594
12594
  throw new InvalidArgumentError("Snapshot path is required");
12595
12595
  }
12596
12596
  const resolvedPath = resolve2(path);
12597
- await mkdir(dirname3(resolvedPath), { recursive: true });
12597
+ await mkdir(dirname4(resolvedPath), { recursive: true });
12598
12598
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
12599
12599
  hash,
12600
12600
  snapshot
@@ -29810,10 +29810,10 @@ var require_stringify = __commonJS((exports, module) => {
29810
29810
  replacer = null;
29811
29811
  indent = EMPTY;
29812
29812
  };
29813
- var join8 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
29813
+ var join9 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
29814
29814
  var join_content = (inside, value, gap) => {
29815
29815
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
29816
- return join8(comment, inside, gap);
29816
+ return join9(comment, inside, gap);
29817
29817
  };
29818
29818
  var stringify_string = (holder, key, value) => {
29819
29819
  const raw = get_raw_string_literal(holder, key);
@@ -29835,13 +29835,13 @@ var require_stringify = __commonJS((exports, module) => {
29835
29835
  if (i !== 0) {
29836
29836
  inside += COMMA;
29837
29837
  }
29838
- const before = join8(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
29838
+ const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
29839
29839
  inside += before || LF + deeper_gap;
29840
29840
  inside += stringify(i, value, deeper_gap) || STR_NULL;
29841
29841
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
29842
29842
  after_comma = process_comments(value, AFTER(i), deeper_gap);
29843
29843
  }
29844
- inside += join8(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
29844
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
29845
29845
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
29846
29846
  };
29847
29847
  var object_stringify = (value, gap) => {
@@ -29862,13 +29862,13 @@ var require_stringify = __commonJS((exports, module) => {
29862
29862
  inside += COMMA;
29863
29863
  }
29864
29864
  first = false;
29865
- const before = join8(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
29865
+ const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
29866
29866
  inside += before || LF + deeper_gap;
29867
29867
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
29868
29868
  after_comma = process_comments(value, AFTER(key), deeper_gap);
29869
29869
  };
29870
29870
  keys.forEach(iteratee);
29871
- inside += join8(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
29871
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
29872
29872
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
29873
29873
  };
29874
29874
  function stringify(key, holder, gap) {
@@ -33691,9 +33691,9 @@ var require_xterm_headless = __commonJS((exports) => {
33691
33691
  });
33692
33692
 
33693
33693
  // src/index.ts
33694
- import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "node:fs";
33694
+ import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "node:fs";
33695
33695
  import { createRequire as createRequire3 } from "node:module";
33696
- import { join as join23 } from "node:path";
33696
+ import { dirname as dirname14 } from "node:path";
33697
33697
 
33698
33698
  // ../aft-bridge/dist/active-logger.js
33699
33699
  var ACTIVE_LOGGER_SYMBOL = Symbol.for("aft-bridge-active-logger");
@@ -33982,6 +33982,11 @@ class BinaryBridge {
33982
33982
  if (requestSessionId && options?.configureWarningClient !== undefined) {
33983
33983
  this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
33984
33984
  }
33985
+ const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
33986
+ const implicitTransportOptions = {
33987
+ ...options?.transportTimeoutMs !== undefined || options?.timeoutMs !== undefined ? { transportTimeoutMs: effectiveTimeoutMs } : {},
33988
+ markConfiguredOnSuccess: false
33989
+ };
33985
33990
  if (!this.configured) {
33986
33991
  if (command !== "configure" && command !== "version") {
33987
33992
  if (!this._configurePromise) {
@@ -33992,12 +33997,12 @@ class BinaryBridge {
33992
33997
  project_root: this.cwd,
33993
33998
  ...this.configOverrides,
33994
33999
  ...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
33995
- });
34000
+ }, implicitTransportOptions);
33996
34001
  if (configResult.success === false) {
33997
34002
  throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
33998
34003
  }
33999
34004
  await this.deliverConfigureWarnings(configResult, params, options);
34000
- await this.checkVersion();
34005
+ await this.checkVersion(implicitTransportOptions);
34001
34006
  if (!this.isAlive()) {
34002
34007
  throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
34003
34008
  }
@@ -34027,9 +34032,8 @@ class BinaryBridge {
34027
34032
  }
34028
34033
  const line = `${JSON.stringify(request)}
34029
34034
  `;
34030
- const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
34031
34035
  const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
34032
- return new Promise((resolve, reject) => {
34036
+ const response = await new Promise((resolve, reject) => {
34033
34037
  const timer = setTimeout(() => {
34034
34038
  this.pending.delete(id);
34035
34039
  const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
@@ -34062,6 +34066,10 @@ class BinaryBridge {
34062
34066
  }
34063
34067
  });
34064
34068
  });
34069
+ if (command === "configure" && response.success === true && options?.markConfiguredOnSuccess !== false) {
34070
+ this.configured = true;
34071
+ }
34072
+ return response;
34065
34073
  } catch (err) {
34066
34074
  if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
34067
34075
  this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
@@ -34152,11 +34160,11 @@ class BinaryBridge {
34152
34160
  });
34153
34161
  }
34154
34162
  }
34155
- async checkVersion() {
34163
+ async checkVersion(options) {
34156
34164
  if (!this.minVersion)
34157
34165
  return;
34158
34166
  try {
34159
- const resp = await this.send("version");
34167
+ const resp = await this.send("version", {}, options);
34160
34168
  if (resp.success === false) {
34161
34169
  throw new Error(`Binary version check failed: ${String(resp.code ?? "unknown")} — likely too old`);
34162
34170
  }
@@ -34231,10 +34239,14 @@ class BinaryBridge {
34231
34239
  ...process.env,
34232
34240
  ...envPath ? { PATH: envPath } : {}
34233
34241
  };
34242
+ this.logVia(`bridge.spawnProcess: useFastembedBackend=${useFastembedBackend}, ` + `parentORT=${process.env.ORT_DYLIB_PATH ?? "(unset)"}, ` + `ortLibraryPath=${ortLibraryPath ?? "(none)"}`);
34234
34243
  if (useFastembedBackend) {
34235
34244
  env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join(this.configOverrides.storage_dir, "semantic", "models") : join(homedir() || "", ".cache", "fastembed"));
34236
- if (ortLibraryPath) {
34245
+ if (process.env.ORT_DYLIB_PATH) {
34246
+ this.logVia(`ORT_DYLIB_PATH inherited from parent env: ${process.env.ORT_DYLIB_PATH}`);
34247
+ } else if (ortLibraryPath) {
34237
34248
  env.ORT_DYLIB_PATH = ortLibraryPath;
34249
+ this.logVia(`ORT_DYLIB_PATH set from managed ONNX Runtime: ${ortLibraryPath}`);
34238
34250
  }
34239
34251
  }
34240
34252
  const child = spawn(this.binaryPath, [], {
@@ -34480,8 +34492,9 @@ class BinaryBridge {
34480
34492
  }
34481
34493
  }
34482
34494
  // ../aft-bridge/dist/downloader.js
34483
- import { createHash } from "node:crypto";
34484
- import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, unlinkSync } from "node:fs";
34495
+ import { spawnSync } from "node:child_process";
34496
+ import { createHash, randomUUID } from "node:crypto";
34497
+ import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
34485
34498
  import { homedir as homedir2 } from "node:os";
34486
34499
  import { join as join2 } from "node:path";
34487
34500
  import { Readable } from "node:stream";
@@ -34509,6 +34522,34 @@ var LATEST_TAG_TIMEOUT_MS = 30000;
34509
34522
  var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
34510
34523
  var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
34511
34524
  var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
34525
+ function readBinaryVersion(binaryPath) {
34526
+ try {
34527
+ const result = spawnSync(binaryPath, ["--version"], {
34528
+ encoding: "utf-8",
34529
+ stdio: ["pipe", "pipe", "pipe"],
34530
+ timeout: 5000
34531
+ });
34532
+ const stdoutVersion = result.stdout?.trim();
34533
+ const stderrVersion = result.stderr?.trim();
34534
+ const rawVersion = stdoutVersion || stderrVersion;
34535
+ if (!rawVersion)
34536
+ return null;
34537
+ return rawVersion.replace(/^aft\s+/, "");
34538
+ } catch {
34539
+ return null;
34540
+ }
34541
+ }
34542
+ function expectedVersionFromTag(tag) {
34543
+ return tag.startsWith("v") ? tag.slice(1) : tag;
34544
+ }
34545
+ function isExpectedCachedBinary(binaryPath, tag) {
34546
+ const expected = expectedVersionFromTag(tag);
34547
+ const actual = readBinaryVersion(binaryPath);
34548
+ if (actual === expected)
34549
+ return true;
34550
+ warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; refreshing cache entry`);
34551
+ return false;
34552
+ }
34512
34553
  function getCacheDir() {
34513
34554
  if (process.platform === "win32") {
34514
34555
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
@@ -34544,7 +34585,7 @@ async function downloadBinary(version) {
34544
34585
  const versionedCacheDir = join2(getCacheDir(), tag);
34545
34586
  const binaryName = getBinaryName();
34546
34587
  const binaryPath = join2(versionedCacheDir, binaryName);
34547
- if (existsSync(binaryPath)) {
34588
+ if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
34548
34589
  return binaryPath;
34549
34590
  }
34550
34591
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
@@ -34562,7 +34603,7 @@ async function downloadBinary(version) {
34562
34603
  mkdirSync(versionedCacheDir, { recursive: true });
34563
34604
  }
34564
34605
  releaseLock = await acquireDownloadLock(lockPath);
34565
- if (existsSync(binaryPath)) {
34606
+ if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
34566
34607
  return binaryPath;
34567
34608
  }
34568
34609
  binaryController = new AbortController;
@@ -34651,7 +34692,7 @@ async function ensureBinary(version) {
34651
34692
  if (version) {
34652
34693
  const tag = version.startsWith("v") ? version : `v${version}`;
34653
34694
  const versionCached = getCachedBinaryPath(tag);
34654
- if (versionCached) {
34695
+ if (versionCached && isExpectedCachedBinary(versionCached, tag)) {
34655
34696
  log(`Found cached binary for ${tag}: ${versionCached}`);
34656
34697
  return versionCached;
34657
34698
  }
@@ -34665,13 +34706,17 @@ async function acquireDownloadLock(lockPath) {
34665
34706
  const startedAt = Date.now();
34666
34707
  while (true) {
34667
34708
  try {
34709
+ const owner = `${process.pid}:${Date.now()}:${randomUUID()}`;
34668
34710
  const fd = openSync(lockPath, "wx");
34711
+ writeSync(fd, owner);
34669
34712
  return () => {
34670
34713
  try {
34671
34714
  closeSync(fd);
34672
34715
  } catch {}
34673
34716
  try {
34674
- rmSync(lockPath, { force: true });
34717
+ if (readFileSync(lockPath, "utf-8") === owner) {
34718
+ rmSync(lockPath, { force: true });
34719
+ }
34675
34720
  } catch {}
34676
34721
  };
34677
34722
  } catch (err) {
@@ -34727,34 +34772,35 @@ async function fetchLatestTag() {
34727
34772
  }
34728
34773
  // ../aft-bridge/dist/migration.js
34729
34774
  import { spawnSync as spawnSync2 } from "node:child_process";
34730
- import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
34775
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
34731
34776
  import { homedir as homedir4, tmpdir } from "node:os";
34732
- import { dirname, join as join4 } from "node:path";
34777
+ import { dirname as dirname2, join as join5 } from "node:path";
34778
+
34779
+ // ../aft-bridge/dist/paths.js
34780
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "node:fs";
34781
+ import { dirname, join as join3 } from "node:path";
34782
+ function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
34783
+ return join3(storageRoot, harness, ...segments);
34784
+ }
34785
+ function repairRootScopedStorageFile(storageRoot, harness, fileName) {
34786
+ const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
34787
+ const rootPath = join3(storageRoot, fileName);
34788
+ if (existsSync2(harnessPath) || !existsSync2(rootPath))
34789
+ return harnessPath;
34790
+ try {
34791
+ mkdirSync2(dirname(harnessPath), { recursive: true });
34792
+ renameSync2(rootPath, harnessPath);
34793
+ } catch {}
34794
+ return harnessPath;
34795
+ }
34733
34796
 
34734
34797
  // ../aft-bridge/dist/resolver.js
34735
- import { execSync, spawnSync } from "node:child_process";
34736
- import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "node:fs";
34798
+ import { execSync } from "node:child_process";
34799
+ import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3 } from "node:fs";
34737
34800
  import { createRequire as createRequire2 } from "node:module";
34738
34801
  import { homedir as homedir3 } from "node:os";
34739
- import { join as join3 } from "node:path";
34802
+ import { join as join4 } from "node:path";
34740
34803
  var ensureBinaryForResolver = ensureBinary;
34741
- function readBinaryVersion(binaryPath) {
34742
- try {
34743
- const result = spawnSync(binaryPath, ["--version"], {
34744
- encoding: "utf-8",
34745
- stdio: ["pipe", "pipe", "pipe"],
34746
- timeout: 5000
34747
- });
34748
- const stdoutVersion = result.stdout?.trim();
34749
- const stderrVersion = result.stderr?.trim();
34750
- const rawVersion = stdoutVersion || stderrVersion;
34751
- if (!rawVersion)
34752
- return null;
34753
- return rawVersion.replace(/^aft\s+/, "");
34754
- } catch {
34755
- return null;
34756
- }
34757
- }
34758
34804
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
34759
34805
  try {
34760
34806
  const version = knownVersion ?? readBinaryVersion(npmBinaryPath);
@@ -34762,22 +34808,22 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
34762
34808
  return null;
34763
34809
  const tag = version.startsWith("v") ? version : `v${version}`;
34764
34810
  const cacheDir = getCacheDir();
34765
- const versionedDir = join3(cacheDir, tag);
34811
+ const versionedDir = join4(cacheDir, tag);
34766
34812
  const ext = process.platform === "win32" ? ".exe" : "";
34767
- const cachedPath = join3(versionedDir, `aft${ext}`);
34768
- if (existsSync2(cachedPath)) {
34813
+ const cachedPath = join4(versionedDir, `aft${ext}`);
34814
+ if (existsSync3(cachedPath)) {
34769
34815
  const cachedVersion = readBinaryVersion(cachedPath);
34770
34816
  if (cachedVersion === version)
34771
34817
  return cachedPath;
34772
34818
  warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
34773
34819
  }
34774
- mkdirSync2(versionedDir, { recursive: true });
34820
+ mkdirSync3(versionedDir, { recursive: true });
34775
34821
  const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
34776
34822
  copyFileSync(npmBinaryPath, tmpPath);
34777
34823
  if (process.platform !== "win32") {
34778
34824
  chmodSync2(tmpPath, 493);
34779
34825
  }
34780
- renameSync2(tmpPath, cachedPath);
34826
+ renameSync3(tmpPath, cachedPath);
34781
34827
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
34782
34828
  return cachedPath;
34783
34829
  } catch (err) {
@@ -34793,17 +34839,17 @@ function homeDirFromEnv(env) {
34793
34839
  }
34794
34840
  function cacheDirFromEnv(env) {
34795
34841
  if (process.platform === "win32") {
34796
- const base2 = env.LOCALAPPDATA || env.APPDATA || join3(homeDirFromEnv(env), "AppData", "Local");
34797
- return join3(base2, "aft", "bin");
34842
+ const base2 = env.LOCALAPPDATA || env.APPDATA || join4(homeDirFromEnv(env), "AppData", "Local");
34843
+ return join4(base2, "aft", "bin");
34798
34844
  }
34799
- const base = env.XDG_CACHE_HOME || join3(homeDirFromEnv(env), ".cache");
34800
- return join3(base, "aft", "bin");
34845
+ const base = env.XDG_CACHE_HOME || join4(homeDirFromEnv(env), ".cache");
34846
+ return join4(base, "aft", "bin");
34801
34847
  }
34802
34848
  function cachedBinaryPathFromEnv(version, env, ext) {
34803
- const binaryPath = join3(cacheDirFromEnv(env), version, `aft${ext}`);
34804
- return existsSync2(binaryPath) ? binaryPath : null;
34849
+ const binaryPath = join4(cacheDirFromEnv(env), version, `aft${ext}`);
34850
+ return existsSync3(binaryPath) ? binaryPath : null;
34805
34851
  }
34806
- function isExpectedCachedBinary(binaryPath, expectedVersion) {
34852
+ function isExpectedCachedBinary2(binaryPath, expectedVersion) {
34807
34853
  const expected = normalizeBareVersion(expectedVersion);
34808
34854
  const actual = readBinaryVersion(binaryPath);
34809
34855
  if (actual === expected)
@@ -34811,6 +34857,21 @@ function isExpectedCachedBinary(binaryPath, expectedVersion) {
34811
34857
  warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
34812
34858
  return false;
34813
34859
  }
34860
+ function probeBinaryCandidate(binaryPath, source, expectedVersion) {
34861
+ const actual = readBinaryVersion(binaryPath);
34862
+ if (actual === null) {
34863
+ warn(`${source} binary at ${binaryPath} did not report a version; skipping`);
34864
+ return null;
34865
+ }
34866
+ if (expectedVersion && actual !== normalizeBareVersion(expectedVersion)) {
34867
+ warn(`${source} binary at ${binaryPath} reports ${actual}, expected ${normalizeBareVersion(expectedVersion)}; skipping`);
34868
+ return null;
34869
+ }
34870
+ return binaryPath;
34871
+ }
34872
+ function parsePathLookupOutput(output) {
34873
+ return output.split(/\r?\n/).map((candidate) => candidate.trim()).filter(Boolean);
34874
+ }
34814
34875
  function platformKey(platform = process.platform, arch = process.arch) {
34815
34876
  const archMap = PLATFORM_ARCH_MAP[platform];
34816
34877
  if (!archMap) {
@@ -34836,7 +34897,7 @@ function findBinarySync(expectedVersion) {
34836
34897
  if (pluginVersion) {
34837
34898
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
34838
34899
  const versionCached = cachedBinaryPathFromEnv(tag, env, ext);
34839
- if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
34900
+ if (versionCached && isExpectedCachedBinary2(versionCached, pluginVersion))
34840
34901
  return versionCached;
34841
34902
  }
34842
34903
  try {
@@ -34844,7 +34905,7 @@ function findBinarySync(expectedVersion) {
34844
34905
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
34845
34906
  const req = createRequire2(import.meta.url);
34846
34907
  const resolved = req.resolve(packageBin);
34847
- if (existsSync2(resolved)) {
34908
+ if (existsSync3(resolved)) {
34848
34909
  const npmVersion = readBinaryVersion(resolved);
34849
34910
  if (npmVersion === null) {
34850
34911
  warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
@@ -34863,12 +34924,18 @@ function findBinarySync(expectedVersion) {
34863
34924
  env,
34864
34925
  stdio: ["pipe", "pipe", "pipe"]
34865
34926
  }).trim();
34866
- if (result)
34867
- return result;
34927
+ for (const candidate of parsePathLookupOutput(result)) {
34928
+ const usable = probeBinaryCandidate(candidate, "PATH", expectedVersion);
34929
+ if (usable)
34930
+ return usable;
34931
+ }
34868
34932
  } catch {}
34869
- const cargoPath = join3(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
34870
- if (existsSync2(cargoPath))
34871
- return cargoPath;
34933
+ const cargoPath = join4(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
34934
+ if (existsSync3(cargoPath)) {
34935
+ const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
34936
+ if (usable)
34937
+ return usable;
34938
+ }
34872
34939
  return null;
34873
34940
  }
34874
34941
  async function findBinary(expectedVersion) {
@@ -34909,9 +34976,9 @@ function dataHome() {
34909
34976
  if (process.env.XDG_DATA_HOME)
34910
34977
  return process.env.XDG_DATA_HOME;
34911
34978
  if (process.platform === "win32") {
34912
- return process.env.LOCALAPPDATA || process.env.APPDATA || join4(homeDir(), "AppData", "Local");
34979
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join5(homeDir(), "AppData", "Local");
34913
34980
  }
34914
- return join4(homeDir(), ".local", "share");
34981
+ return join5(homeDir(), ".local", "share");
34915
34982
  }
34916
34983
  function homeDir() {
34917
34984
  if (process.platform === "win32")
@@ -34920,11 +34987,11 @@ function homeDir() {
34920
34987
  }
34921
34988
  function resolveLegacyStorageRoot(harness) {
34922
34989
  if (harness === "pi")
34923
- return join4(homeDir(), ".pi", "agent", "aft");
34924
- return join4(dataHome(), "opencode", "storage", "plugin", "aft");
34990
+ return join5(homeDir(), ".pi", "agent", "aft");
34991
+ return join5(dataHome(), "opencode", "storage", "plugin", "aft");
34925
34992
  }
34926
34993
  function resolveCortexKitStorageRoot() {
34927
- return join4(dataHome(), "cortexkit", "aft");
34994
+ return join5(dataHome(), "cortexkit", "aft");
34928
34995
  }
34929
34996
  function tail(value) {
34930
34997
  if (!value)
@@ -34938,26 +35005,26 @@ function spawnErrorLabel(error2) {
34938
35005
  return [code, error2.message].filter(Boolean).join(": ");
34939
35006
  }
34940
35007
  function migrationLogPath(newRoot, harness, logger) {
34941
- const desired = join4(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
35008
+ const desired = join5(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
34942
35009
  try {
34943
- mkdirSync3(dirname(desired), { recursive: true });
35010
+ mkdirSync4(dirname2(desired), { recursive: true });
34944
35011
  return desired;
34945
35012
  } catch (err) {
34946
- const fallback = join4(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
34947
- logger?.warn?.(`Failed to create AFT migration log directory ${dirname(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
35013
+ const fallback = join5(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
35014
+ logger?.warn?.(`Failed to create AFT migration log directory ${dirname2(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
34948
35015
  return fallback;
34949
35016
  }
34950
35017
  }
34951
35018
  async function ensureStorageMigrated(opts) {
34952
35019
  const legacyRoot = resolveLegacyStorageRoot(opts.harness);
34953
35020
  const newRoot = resolveCortexKitStorageRoot();
34954
- const targetMarker = join4(newRoot, opts.harness, TARGET_MARKER);
35021
+ const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
34955
35022
  const info = opts.logger?.info ?? opts.logger?.log;
34956
- if (existsSync3(targetMarker)) {
35023
+ if (existsSync4(targetMarker)) {
34957
35024
  info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
34958
35025
  return;
34959
35026
  }
34960
- if (!existsSync3(legacyRoot)) {
35027
+ if (!existsSync4(legacyRoot)) {
34961
35028
  info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
34962
35029
  return;
34963
35030
  }
@@ -35007,8 +35074,8 @@ async function ensureStorageMigrated(opts) {
35007
35074
  // ../aft-bridge/dist/onnx-runtime.js
35008
35075
  import { execFileSync } from "node:child_process";
35009
35076
  import { createHash as createHash2 } from "node:crypto";
35010
- import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync4, openSync as openSync2, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
35011
- import { dirname as dirname2, join as join5, relative, resolve } from "node:path";
35077
+ import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync2, readdirSync, readFileSync as readFileSync2, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
35078
+ import { basename, dirname as dirname3, join as join6, relative, resolve } from "node:path";
35012
35079
  import { Readable as Readable2 } from "node:stream";
35013
35080
  import { pipeline as pipeline2 } from "node:stream/promises";
35014
35081
  var ORT_VERSION = "1.24.4";
@@ -35074,9 +35141,9 @@ function getManualInstallHint() {
35074
35141
  }
35075
35142
  async function ensureOnnxRuntime(storageDir) {
35076
35143
  const info = getPlatformInfo();
35077
- const ortDir = join5(storageDir, "onnxruntime", ORT_VERSION);
35078
- const libPath = join5(ortDir, info?.libName ?? "libonnxruntime.dylib");
35079
- if (existsSync4(libPath)) {
35144
+ const ortDir = join6(storageDir, "onnxruntime", ORT_VERSION);
35145
+ const libPath = join6(ortDir, info?.libName ?? "libonnxruntime.dylib");
35146
+ if (existsSync5(libPath)) {
35080
35147
  const meta = readOnnxInstalledMeta(ortDir);
35081
35148
  if (meta?.sha256) {
35082
35149
  try {
@@ -35105,9 +35172,9 @@ async function ensureOnnxRuntime(storageDir) {
35105
35172
  warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
35106
35173
  return null;
35107
35174
  }
35108
- const onnxBaseDir = join5(storageDir, "onnxruntime");
35109
- mkdirSync4(onnxBaseDir, { recursive: true });
35110
- const lockPath = join5(onnxBaseDir, ONNX_LOCK_FILE);
35175
+ const onnxBaseDir = join6(storageDir, "onnxruntime");
35176
+ mkdirSync5(onnxBaseDir, { recursive: true });
35177
+ const lockPath = join6(onnxBaseDir, ONNX_LOCK_FILE);
35111
35178
  cleanupAbandonedStagingDirs(onnxBaseDir);
35112
35179
  if (!acquireLock(lockPath)) {
35113
35180
  warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
@@ -35126,7 +35193,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
35126
35193
  for (const entry of entries) {
35127
35194
  if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
35128
35195
  continue;
35129
- const stagingDir = join5(onnxBaseDir, entry);
35196
+ const stagingDir = join6(onnxBaseDir, entry);
35130
35197
  const parts = entry.split(".");
35131
35198
  const pidStr = parts[parts.length - 2];
35132
35199
  const pid = pidStr ? Number.parseInt(pidStr, 10) : NaN;
@@ -35158,7 +35225,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
35158
35225
  }
35159
35226
  function cleanupIncompleteTargetIfUnowned(ortDir) {
35160
35227
  try {
35161
- if (existsSync4(ortDir) && !existsSync4(join5(ortDir, ONNX_INSTALLED_META_FILE))) {
35228
+ if (existsSync5(ortDir) && !existsSync5(join6(ortDir, ONNX_INSTALLED_META_FILE))) {
35162
35229
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
35163
35230
  rmSync2(ortDir, { recursive: true, force: true });
35164
35231
  }
@@ -35168,6 +35235,14 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
35168
35235
  }
35169
35236
  var REQUIRED_ORT_MAJOR = 1;
35170
35237
  var REQUIRED_ORT_MIN_MINOR = 20;
35238
+ var INVALID_ORT_VERSION = "<invalid>";
35239
+ function parseOnnxVersionFromPath(value) {
35240
+ const name = basename(value);
35241
+ const semverish = name.match(/\.(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.dylib)?$/);
35242
+ if (semverish)
35243
+ return semverish[1].split(/[-+]/, 1)[0];
35244
+ return /\d+\.\d+\.\d+/.test(name) ? INVALID_ORT_VERSION : null;
35245
+ }
35171
35246
  function detectOnnxVersion(libDir, libName) {
35172
35247
  try {
35173
35248
  const entries = readdirSync(libDir);
@@ -35175,23 +35250,23 @@ function detectOnnxVersion(libDir, libName) {
35175
35250
  for (const entry of entries) {
35176
35251
  if (!entry.startsWith(barePrefix))
35177
35252
  continue;
35178
- const match = entry.match(/\.(\d+\.\d+\.\d+)(?:\.dylib)?$/);
35179
- if (match)
35180
- return match[1];
35253
+ const version = parseOnnxVersionFromPath(entry);
35254
+ if (version)
35255
+ return version;
35181
35256
  }
35182
- const base = join5(libDir, libName);
35183
- if (existsSync4(base)) {
35257
+ const base = join6(libDir, libName);
35258
+ if (existsSync5(base)) {
35184
35259
  try {
35185
35260
  const real = realpathSync(base);
35186
- const m = real.match(/\.(\d+\.\d+\.\d+)(?:\.dylib)?$/);
35187
- if (m)
35188
- return m[1];
35261
+ const version = parseOnnxVersionFromPath(real);
35262
+ if (version)
35263
+ return version;
35189
35264
  } catch {}
35190
35265
  try {
35191
35266
  const target = readlinkSync(base);
35192
- const m = target.match(/\.(\d+\.\d+\.\d+)(?:\.dylib)?$/);
35193
- if (m)
35194
- return m[1];
35267
+ const version = parseOnnxVersionFromPath(target);
35268
+ if (version)
35269
+ return version;
35195
35270
  } catch {}
35196
35271
  }
35197
35272
  } catch {}
@@ -35216,7 +35291,7 @@ function findSystemOnnxRuntime(libName) {
35216
35291
  searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
35217
35292
  }
35218
35293
  for (const dir of searchPaths) {
35219
- if (!existsSync4(join5(dir, libName)))
35294
+ if (!existsSync5(join6(dir, libName)))
35220
35295
  continue;
35221
35296
  const version = detectOnnxVersion(dir, libName);
35222
35297
  if (version && !isOnnxVersionCompatible(version)) {
@@ -35243,7 +35318,7 @@ async function downloadFileWithCap(url, destPath) {
35243
35318
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
35244
35319
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
35245
35320
  }
35246
- mkdirSync4(dirname2(destPath), { recursive: true });
35321
+ mkdirSync5(dirname3(destPath), { recursive: true });
35247
35322
  let bytesWritten = 0;
35248
35323
  const guard = new TransformStream({
35249
35324
  transform(chunk, transformController) {
@@ -35273,11 +35348,11 @@ function validateExtractedTree(stagingRoot) {
35273
35348
  const walk = (dir) => {
35274
35349
  const entries = readdirSync(dir);
35275
35350
  for (const entry of entries) {
35276
- const fullPath = join5(dir, entry);
35351
+ const fullPath = join6(dir, entry);
35277
35352
  const lst = lstatSync(fullPath);
35278
35353
  if (lst.isSymbolicLink()) {
35279
35354
  const linkTarget = readlinkSync(fullPath);
35280
- const resolvedTarget = resolve(dirname2(fullPath), linkTarget);
35355
+ const resolvedTarget = resolve(dirname3(fullPath), linkTarget);
35281
35356
  const rel2 = relative(realRoot, resolvedTarget);
35282
35357
  if (rel2.startsWith("..") || process.platform !== "win32" && rel2.startsWith("/")) {
35283
35358
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
@@ -35307,8 +35382,8 @@ async function downloadOnnxRuntime(info, targetDir) {
35307
35382
  log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
35308
35383
  const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
35309
35384
  try {
35310
- mkdirSync4(tmpDir, { recursive: true });
35311
- const archivePath = join5(tmpDir, `onnxruntime.${info.archiveType}`);
35385
+ mkdirSync5(tmpDir, { recursive: true });
35386
+ const archivePath = join6(tmpDir, `onnxruntime.${info.archiveType}`);
35312
35387
  await downloadFileWithCap(url, archivePath);
35313
35388
  const archiveSha256 = sha256File(archivePath);
35314
35389
  log(`ONNX Runtime archive sha256=${archiveSha256}`);
@@ -35324,16 +35399,16 @@ async function downloadOnnxRuntime(info, targetDir) {
35324
35399
  unlinkSync2(archivePath);
35325
35400
  } catch {}
35326
35401
  validateExtractedTree(tmpDir);
35327
- const extractedDir = join5(tmpDir, info.assetName, "lib");
35328
- if (!existsSync4(extractedDir)) {
35402
+ const extractedDir = join6(tmpDir, info.assetName, "lib");
35403
+ if (!existsSync5(extractedDir)) {
35329
35404
  throw new Error(`Expected directory not found: ${extractedDir}`);
35330
35405
  }
35331
- mkdirSync4(targetDir, { recursive: true });
35406
+ mkdirSync5(targetDir, { recursive: true });
35332
35407
  const libFiles = readdirSync(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
35333
35408
  const realFiles = [];
35334
35409
  const symlinks = [];
35335
35410
  for (const libFile of libFiles) {
35336
- const src = join5(extractedDir, libFile);
35411
+ const src = join6(extractedDir, libFile);
35337
35412
  try {
35338
35413
  const stat = lstatSync(src);
35339
35414
  log(`ORT extract: ${libFile} — isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
@@ -35348,7 +35423,7 @@ async function downloadOnnxRuntime(info, targetDir) {
35348
35423
  }
35349
35424
  }
35350
35425
  copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
35351
- const libPath = join5(targetDir, info.libName);
35426
+ const libPath = join6(targetDir, info.libName);
35352
35427
  let libHash = null;
35353
35428
  try {
35354
35429
  libHash = sha256File(libPath);
@@ -35373,8 +35448,8 @@ async function downloadOnnxRuntime(info, targetDir) {
35373
35448
  function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync2) {
35374
35449
  const requiredLibs = new Set([info.libName]);
35375
35450
  for (const libFile of realFiles) {
35376
- const src = join5(extractedDir, libFile);
35377
- const dst = join5(targetDir, libFile);
35451
+ const src = join6(extractedDir, libFile);
35452
+ const dst = join6(targetDir, libFile);
35378
35453
  try {
35379
35454
  copyFile(src, dst);
35380
35455
  if (process.platform !== "win32") {
@@ -35389,7 +35464,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
35389
35464
  }
35390
35465
  }
35391
35466
  for (const link of symlinks) {
35392
- const dst = join5(targetDir, link.name);
35467
+ const dst = join6(targetDir, link.name);
35393
35468
  try {
35394
35469
  unlinkSync2(dst);
35395
35470
  } catch {}
@@ -35403,8 +35478,8 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
35403
35478
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
35404
35479
  }
35405
35480
  }
35406
- const requiredPath = join5(targetDir, info.libName);
35407
- if (!existsSync4(requiredPath)) {
35481
+ const requiredPath = join6(targetDir, info.libName);
35482
+ if (!existsSync5(requiredPath)) {
35408
35483
  rmSync2(targetDir, { recursive: true, force: true });
35409
35484
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
35410
35485
  }
@@ -35430,17 +35505,17 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
35430
35505
  ...sha256 ? { sha256 } : {},
35431
35506
  archiveSha256
35432
35507
  };
35433
- writeFileSync(join5(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
35508
+ writeFileSync(join6(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
35434
35509
  } catch (err) {
35435
35510
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
35436
35511
  }
35437
35512
  }
35438
35513
  function readOnnxInstalledMeta(installDir) {
35439
- const path = join5(installDir, ONNX_INSTALLED_META_FILE);
35514
+ const path = join6(installDir, ONNX_INSTALLED_META_FILE);
35440
35515
  try {
35441
35516
  if (!statSync2(path).isFile())
35442
35517
  return null;
35443
- const raw = readFileSync(path, "utf8");
35518
+ const raw = readFileSync2(path, "utf8");
35444
35519
  const parsed = JSON.parse(raw);
35445
35520
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
35446
35521
  return null;
@@ -35456,7 +35531,7 @@ function readOnnxInstalledMeta(installDir) {
35456
35531
  }
35457
35532
  function sha256File(path) {
35458
35533
  const hash = createHash2("sha256");
35459
- hash.update(readFileSync(path));
35534
+ hash.update(readFileSync2(path));
35460
35535
  return hash.digest("hex");
35461
35536
  }
35462
35537
  function acquireLock(lockPath) {
@@ -35484,7 +35559,7 @@ ${new Date().toISOString()}
35484
35559
  let owningPid = null;
35485
35560
  let lockMtimeMs = 0;
35486
35561
  try {
35487
- const raw = readFileSync(lockPath, "utf8");
35562
+ const raw = readFileSync2(lockPath, "utf8");
35488
35563
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
35489
35564
  const parsed = Number.parseInt(firstLine, 10);
35490
35565
  if (Number.isFinite(parsed) && parsed > 0)
@@ -35510,7 +35585,7 @@ function releaseLock(lockPath) {
35510
35585
  try {
35511
35586
  let owningPid = null;
35512
35587
  try {
35513
- const raw = readFileSync(lockPath, "utf8");
35588
+ const raw = readFileSync2(lockPath, "utf8");
35514
35589
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
35515
35590
  const parsed = Number.parseInt(firstLine, 10);
35516
35591
  if (Number.isFinite(parsed) && parsed > 0)
@@ -35556,6 +35631,7 @@ var DEFAULT_MAX_POOL_SIZE = 8;
35556
35631
  var CLEANUP_INTERVAL_MS = 60 * 1000;
35557
35632
  class BridgePool {
35558
35633
  bridges = new Map;
35634
+ staleBridges = new Set;
35559
35635
  binaryPath;
35560
35636
  maxPoolSize;
35561
35637
  idleTimeoutMs;
@@ -35578,6 +35654,7 @@ class BridgePool {
35578
35654
  onConfigureWarnings: options.onConfigureWarnings,
35579
35655
  onBashCompletion: options.onBashCompletion,
35580
35656
  onBashLongRunning: options.onBashLongRunning,
35657
+ onBashPatternMatch: options.onBashPatternMatch,
35581
35658
  errorPrefix: options.errorPrefix,
35582
35659
  logger: options.logger
35583
35660
  };
@@ -35629,6 +35706,12 @@ class BridgePool {
35629
35706
  this.bridges.delete(dir);
35630
35707
  }
35631
35708
  }
35709
+ for (const bridge of this.staleBridges) {
35710
+ if (bridge.hasPendingRequests())
35711
+ continue;
35712
+ bridge.shutdown().catch((err) => this.error("stale cleanup shutdown failed:", err));
35713
+ this.staleBridges.delete(bridge);
35714
+ }
35632
35715
  }
35633
35716
  evictLRU() {
35634
35717
  let oldestDir = null;
@@ -35652,14 +35735,21 @@ class BridgePool {
35652
35735
  clearInterval(this.cleanupTimer);
35653
35736
  this.cleanupTimer = null;
35654
35737
  }
35655
- const shutdowns = Array.from(this.bridges.values()).map((e) => e.bridge.shutdown());
35738
+ const shutdowns = [
35739
+ ...Array.from(this.bridges.values(), (e) => e.bridge.shutdown()),
35740
+ ...Array.from(this.staleBridges.values(), (bridge) => bridge.shutdown())
35741
+ ];
35656
35742
  this.bridges.clear();
35743
+ this.staleBridges.clear();
35657
35744
  await Promise.allSettled(shutdowns);
35658
35745
  }
35659
35746
  async replaceBinary(newPath) {
35660
35747
  this.binaryPath = newPath;
35748
+ for (const entry of this.bridges.values()) {
35749
+ this.staleBridges.add(entry.bridge);
35750
+ }
35661
35751
  this.bridges.clear();
35662
- this.log(`Binary path updated to ${newPath}. All bridges cleared — next calls will use the new binary.`);
35752
+ this.log(`Binary path updated to ${newPath}. Active bridges marked stale — next calls will use the new binary.`);
35663
35753
  return newPath;
35664
35754
  }
35665
35755
  log(message, meta) {
@@ -35709,11 +35799,11 @@ function normalizeKey(projectRoot) {
35709
35799
  }
35710
35800
  }
35711
35801
  // ../aft-bridge/dist/url-fetch.js
35712
- import { createHash as createHash3 } from "node:crypto";
35802
+ import { createHash as createHash3, randomBytes } from "node:crypto";
35713
35803
  import { lookup } from "node:dns/promises";
35714
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
35804
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
35715
35805
  import { isIP } from "node:net";
35716
- import { join as join6 } from "node:path";
35806
+ import { join as join7 } from "node:path";
35717
35807
 
35718
35808
  // ../../node_modules/.bun/undici@7.25.0/node_modules/undici/index.js
35719
35809
  var __filename = "/home/runner/work/aft/aft/node_modules/.bun/undici@7.25.0/node_modules/undici/index.js";
@@ -35863,16 +35953,16 @@ var FETCH_TIMEOUT_MS = 30000;
35863
35953
  var BODY_CHUNK_TIMEOUT_MS = 15000;
35864
35954
  var MAX_REDIRECTS = 5;
35865
35955
  function cacheDir(storageDir) {
35866
- return join6(storageDir, "url_cache");
35956
+ return join7(storageDir, "url_cache");
35867
35957
  }
35868
35958
  function hashUrl(url) {
35869
35959
  return createHash3("sha256").update(url).digest("hex").slice(0, 16);
35870
35960
  }
35871
35961
  function metaPath(storageDir, hash) {
35872
- return join6(cacheDir(storageDir), `${hash}.meta.json`);
35962
+ return join7(cacheDir(storageDir), `${hash}.meta.json`);
35873
35963
  }
35874
35964
  function contentPath(storageDir, hash, extension) {
35875
- return join6(cacheDir(storageDir), `${hash}${extension}`);
35965
+ return join7(cacheDir(storageDir), `${hash}${extension}`);
35876
35966
  }
35877
35967
  function _isPrivateIpv4(address) {
35878
35968
  const parts = address.split(".").map((part) => Number.parseInt(part, 10));
@@ -35997,13 +36087,21 @@ function resolveRedirectUrl(currentUrl, location) {
35997
36087
  }
35998
36088
  return new URL(location, currentUrl);
35999
36089
  }
36090
+ var isBunRuntime = typeof globalThis.Bun !== "undefined";
36000
36091
  async function fetchWithRedirects(startUrl, allowPrivate, deps = {}) {
36001
36092
  let currentUrl = startUrl;
36002
- const fetchImpl2 = deps.fetchImpl ?? $fetch;
36093
+ const fetchImpl2 = deps.fetchImpl ?? (isBunRuntime ? globalThis.fetch : $fetch);
36003
36094
  const dnsLookup = deps.lookup ?? lookup;
36004
36095
  for (let redirectCount = 0;redirectCount <= MAX_REDIRECTS; redirectCount++) {
36005
36096
  const validatedIp = await assertPublicUrl(currentUrl, allowPrivate, dnsLookup);
36006
- const dispatcher = validatedIp ? (deps.dispatcherFactory ?? createPinnedDispatcher)(validatedIp) : undefined;
36097
+ let dispatcher;
36098
+ if (validatedIp) {
36099
+ if (deps.dispatcherFactory) {
36100
+ dispatcher = deps.dispatcherFactory(validatedIp);
36101
+ } else if (!isBunRuntime) {
36102
+ dispatcher = createPinnedDispatcher(validatedIp);
36103
+ }
36104
+ }
36007
36105
  const controller = new AbortController;
36008
36106
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
36009
36107
  let response;
@@ -36014,7 +36112,7 @@ async function fetchWithRedirects(startUrl, allowPrivate, deps = {}) {
36014
36112
  dispatcher,
36015
36113
  headers: {
36016
36114
  "user-agent": "aft-opencode-plugin",
36017
- accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, text/plain;q=0.5"
36115
+ accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, application/json;q=0.8, text/plain;q=0.5"
36018
36116
  }
36019
36117
  });
36020
36118
  } catch (err) {
@@ -36055,6 +36153,9 @@ function resolveExtension(contentType) {
36055
36153
  if (lower === "text/plain") {
36056
36154
  return ".md";
36057
36155
  }
36156
+ if (lower === "application/json" || lower === "application/ld+json" || lower.endsWith("+json")) {
36157
+ return ".json";
36158
+ }
36058
36159
  return null;
36059
36160
  }
36060
36161
  async function fetchUrlToTempFile(url, storageDir, options = {}) {
@@ -36070,15 +36171,15 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
36070
36171
  const allowPrivate = options.allowPrivate === true;
36071
36172
  await assertPublicUrl(parsed, allowPrivate, options.lookup);
36072
36173
  const dir = cacheDir(storageDir);
36073
- mkdirSync5(dir, { recursive: true });
36174
+ mkdirSync6(dir, { recursive: true });
36074
36175
  const hash = hashUrl(url);
36075
36176
  const metaFile = metaPath(storageDir, hash);
36076
- if (existsSync5(metaFile)) {
36177
+ if (existsSync6(metaFile)) {
36077
36178
  try {
36078
- const meta2 = JSON.parse(readFileSync2(metaFile, "utf8"));
36179
+ const meta2 = JSON.parse(readFileSync3(metaFile, "utf8"));
36079
36180
  const age = Date.now() - meta2.fetchedAt;
36080
36181
  const cached = contentPath(storageDir, hash, meta2.extension);
36081
- if (age < CACHE_TTL_MS && existsSync5(cached)) {
36182
+ if (age < CACHE_TTL_MS && existsSync6(cached)) {
36082
36183
  log(`URL cache hit: ${url} (${Math.round(age / 1000)}s old)`);
36083
36184
  return cached;
36084
36185
  }
@@ -36144,39 +36245,40 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
36144
36245
  }
36145
36246
  const body = Buffer.concat(chunks);
36146
36247
  const contentFile = contentPath(storageDir, hash, extension);
36147
- const tmpContent = `${contentFile}.tmp-${process.pid}`;
36248
+ const nonce = randomBytes(8).toString("hex");
36249
+ const tmpContent = `${contentFile}.tmp-${process.pid}-${nonce}`;
36148
36250
  writeFileSync2(tmpContent, body);
36149
- const { renameSync: renameSync3 } = await import("node:fs");
36150
- renameSync3(tmpContent, contentFile);
36251
+ const { renameSync: renameSync4 } = await import("node:fs");
36252
+ renameSync4(tmpContent, contentFile);
36151
36253
  const meta = {
36152
36254
  url,
36153
36255
  contentType,
36154
36256
  extension,
36155
36257
  fetchedAt: Date.now()
36156
36258
  };
36157
- const tmpMeta = `${metaFile}.tmp-${process.pid}`;
36259
+ const tmpMeta = `${metaFile}.tmp-${process.pid}-${nonce}`;
36158
36260
  writeFileSync2(tmpMeta, JSON.stringify(meta));
36159
- renameSync3(tmpMeta, metaFile);
36261
+ renameSync4(tmpMeta, metaFile);
36160
36262
  log(`URL cached (${total} bytes): ${url}`);
36161
36263
  return contentFile;
36162
36264
  }
36163
36265
  function cleanupUrlCache(storageDir) {
36164
36266
  const dir = cacheDir(storageDir);
36165
- if (!existsSync5(dir))
36267
+ if (!existsSync6(dir))
36166
36268
  return;
36167
36269
  let removed = 0;
36168
36270
  try {
36169
36271
  for (const entry of readdirSync2(dir)) {
36170
36272
  if (!entry.endsWith(".meta.json"))
36171
36273
  continue;
36172
- const metaFile = join6(dir, entry);
36274
+ const metaFile = join7(dir, entry);
36173
36275
  try {
36174
- const meta = JSON.parse(readFileSync2(metaFile, "utf8"));
36276
+ const meta = JSON.parse(readFileSync3(metaFile, "utf8"));
36175
36277
  const age = Date.now() - meta.fetchedAt;
36176
36278
  if (age > CACHE_TTL_MS) {
36177
36279
  const hash = entry.slice(0, -".meta.json".length);
36178
36280
  const content = contentPath(storageDir, hash, meta.extension);
36179
- if (existsSync5(content))
36281
+ if (existsSync6(content))
36180
36282
  unlinkSync3(content);
36181
36283
  unlinkSync3(metaFile);
36182
36284
  removed++;
@@ -36247,7 +36349,7 @@ function formatZoomText(targetLabel, response) {
36247
36349
  `);
36248
36350
  }
36249
36351
  // src/bg-notifications.ts
36250
- import { createHash as createHash4, randomUUID } from "node:crypto";
36352
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
36251
36353
 
36252
36354
  // src/logger.ts
36253
36355
  import * as fs from "node:fs";
@@ -36346,11 +36448,6 @@ function extractMessages(response) {
36346
36448
  return response.data;
36347
36449
  return [];
36348
36450
  }
36349
- function getRole(message) {
36350
- if (!isRecord(message) || !isRecord(message.info))
36351
- return;
36352
- return typeof message.info.role === "string" ? message.info.role : undefined;
36353
- }
36354
36451
  function extractFromMessage(message) {
36355
36452
  if (!isRecord(message) || !isRecord(message.info))
36356
36453
  return null;
@@ -36401,16 +36498,6 @@ async function resolvePromptContext(client, sessionId) {
36401
36498
  if (messages.length === 0)
36402
36499
  return null;
36403
36500
  let result = {};
36404
- for (let i = messages.length - 1;i >= 0; i -= 1) {
36405
- if (getRole(messages[i]) !== "assistant")
36406
- continue;
36407
- const ctx = extractFromMessage(messages[i]);
36408
- if (!ctx)
36409
- continue;
36410
- result = mergeContexts(result, ctx);
36411
- if (isComplete(result))
36412
- return result;
36413
- }
36414
36501
  for (let i = messages.length - 1;i >= 0; i -= 1) {
36415
36502
  const ctx = extractFromMessage(messages[i]);
36416
36503
  if (!ctx)
@@ -36430,6 +36517,13 @@ var clientCache = new Map;
36430
36517
  function cacheKey(serverUrl, directory) {
36431
36518
  return `${serverUrl}|${directory}`;
36432
36519
  }
36520
+ function normalizeServerUrl(serverUrl) {
36521
+ try {
36522
+ return new URL(serverUrl).toString();
36523
+ } catch {
36524
+ return serverUrl;
36525
+ }
36526
+ }
36433
36527
  function serverAuthHeaders() {
36434
36528
  const password = process.env.OPENCODE_SERVER_PASSWORD;
36435
36529
  if (!password)
@@ -36453,30 +36547,46 @@ function getLiveServerClient(serverUrl, directory) {
36453
36547
  clientCache.set(key, client);
36454
36548
  return client;
36455
36549
  }
36550
+ var liveServerWakeAvailableByServerUrl = new Map;
36551
+ var legacyLiveServerWakeAvailable = false;
36456
36552
  async function probeServerReachable(serverUrl, timeoutMs = 1500) {
36457
36553
  if (!serverUrl)
36458
36554
  return false;
36555
+ const normalizedServerUrl = normalizeServerUrl(serverUrl);
36459
36556
  const controller = new AbortController;
36460
36557
  const timer = setTimeout(() => controller.abort(), timeoutMs);
36558
+ let reachable = false;
36461
36559
  try {
36462
36560
  const probeUrl = new URL("/session", serverUrl).toString();
36463
36561
  const res = await globalThis.fetch(probeUrl, {
36464
36562
  method: "GET",
36563
+ headers: serverAuthHeaders(),
36465
36564
  signal: controller.signal
36466
36565
  });
36467
- return res.status >= 200 && res.status < 500;
36566
+ reachable = res.ok || res.status === 401 || res.status === 403;
36468
36567
  } catch {
36469
- return false;
36568
+ reachable = false;
36470
36569
  } finally {
36471
36570
  clearTimeout(timer);
36571
+ liveServerWakeAvailableByServerUrl.set(normalizedServerUrl, reachable);
36472
36572
  }
36573
+ return reachable;
36473
36574
  }
36474
- var liveServerWakeAvailable = false;
36475
- function setLiveServerWakeAvailable(available) {
36476
- liveServerWakeAvailable = available;
36575
+ function setLiveServerWakeAvailable(serverUrlOrAvailable, available) {
36576
+ if (typeof serverUrlOrAvailable === "boolean") {
36577
+ legacyLiveServerWakeAvailable = serverUrlOrAvailable;
36578
+ return;
36579
+ }
36580
+ if (!serverUrlOrAvailable) {
36581
+ legacyLiveServerWakeAvailable = available ?? false;
36582
+ return;
36583
+ }
36584
+ liveServerWakeAvailableByServerUrl.set(normalizeServerUrl(serverUrlOrAvailable), available ?? false);
36477
36585
  }
36478
- function useLiveServerWake() {
36479
- return liveServerWakeAvailable;
36586
+ function useLiveServerWake(serverUrl) {
36587
+ if (!serverUrl)
36588
+ return legacyLiveServerWakeAvailable;
36589
+ return liveServerWakeAvailableByServerUrl.get(normalizeServerUrl(serverUrl)) ?? false;
36480
36590
  }
36481
36591
 
36482
36592
  // src/bg-notifications.ts
@@ -36571,7 +36681,7 @@ function markExplicitControl(sessionID, taskId, trackOutstanding = true) {
36571
36681
  if (idx >= 0) {
36572
36682
  const completion = state.pendingCompletions[idx];
36573
36683
  state.pendingCompletions.splice(idx, 1);
36574
- queuePendingPatternMatch(state, completionToExitPattern(completion));
36684
+ queuePendingPatternMatch(state, completionToExitPattern(completion, true));
36575
36685
  state.wakeDeferredTaskIds.delete(taskId);
36576
36686
  }
36577
36687
  }
@@ -36599,7 +36709,7 @@ function routeExplicitControlCompletions(state) {
36599
36709
  state.outstandingTaskIds.delete(completion.task_id);
36600
36710
  state.explicitControlTasks.delete(completion.task_id);
36601
36711
  state.wakeDeferredTaskIds.delete(completion.task_id);
36602
- queuePendingPatternMatch(state, completionToExitPattern(completion));
36712
+ queuePendingPatternMatch(state, completionToExitPattern(completion, true));
36603
36713
  } else {
36604
36714
  remaining.push(completion);
36605
36715
  }
@@ -36626,7 +36736,7 @@ function ingestBgCompletions(sessionID, completions) {
36626
36736
  if (state.explicitControlTasks.has(completion.task_id)) {
36627
36737
  state.outstandingTaskIds.delete(completion.task_id);
36628
36738
  state.explicitControlTasks.delete(completion.task_id);
36629
- queuePendingPatternMatch(state, completionToExitPattern(completion));
36739
+ queuePendingPatternMatch(state, completionToExitPattern(completion, true));
36630
36740
  continue;
36631
36741
  }
36632
36742
  if (!state.outstandingTaskIds.has(completion.task_id)) {
@@ -36666,6 +36776,8 @@ async function appendInTurnBgCompletions(drainContext, output) {
36666
36776
  if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0)
36667
36777
  return;
36668
36778
  const deliveredCompletions = [...state.pendingCompletions];
36779
+ const deliveredPatternMatches = [...state.pendingPatternMatches];
36780
+ const completionAcks = completionAcksForDelivery(deliveredCompletions, deliveredPatternMatches);
36669
36781
  const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning, state.pendingPatternMatches);
36670
36782
  output.output = appendReminder(output.output ?? "", reminder);
36671
36783
  sessionLog(drainContext.sessionID, `${LOG_PREFIX} in-turn append`, {
@@ -36683,7 +36795,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
36683
36795
  state.pendingPatternMatches = [];
36684
36796
  state.wakeRetryAttempts = 0;
36685
36797
  state.wakeHardStopped = false;
36686
- await ackCompletions(drainContext, deliveredCompletions);
36798
+ await ackCompletions(drainContext, completionAcks);
36687
36799
  if (state.debounceTimer) {
36688
36800
  clearTimeout(state.debounceTimer);
36689
36801
  state.debounceTimer = null;
@@ -36705,86 +36817,112 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
36705
36817
  if (!hasWakeEligiblePending(state, includeDeferredCompletions))
36706
36818
  return;
36707
36819
  scheduleWake(state, async (reminder, deliveredCompletions) => {
36708
- let client;
36709
- let clientPath;
36710
- if (useLiveServerWake() && drainContext.serverUrl) {
36711
- client = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
36712
- clientPath = "live-server";
36713
- } else {
36820
+ const taskIDs = deliveredCompletions.map((completion) => completion.task_id);
36821
+ const getInProcessClient = () => {
36714
36822
  if (!drainContext.client) {
36715
36823
  sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake client unavailable`, {
36716
36824
  event: "bash_completion_wake_client_unavailable",
36717
- task_ids: deliveredCompletions.map((c) => c.task_id),
36825
+ task_ids: taskIDs,
36718
36826
  directory: drainContext.directory,
36719
36827
  attempt: state.wakeRetryAttempts + 1
36720
36828
  });
36721
36829
  throw new Error("no wake transport available: live-server unreachable and input.client absent");
36722
36830
  }
36723
- client = drainContext.client;
36724
- clientPath = "in-process-fallback";
36725
- }
36726
- if (typeof client.session?.promptAsync !== "function") {
36727
- throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
36728
- }
36729
- const promptContext = await resolvePromptContext(client, drainContext.sessionID);
36730
- const body = {
36731
- noReply: false,
36732
- parts: [{ type: "text", text: reminder }]
36831
+ return drainContext.client;
36733
36832
  };
36734
- if (promptContext?.agent)
36735
- body.agent = promptContext.agent;
36736
- if (promptContext?.model) {
36737
- body.model = {
36738
- providerID: promptContext.model.providerID,
36739
- modelID: promptContext.model.modelID
36833
+ const sendPrompt = async (client, clientPath) => {
36834
+ if (typeof client.session?.promptAsync !== "function") {
36835
+ throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
36836
+ }
36837
+ const promptContext = await resolvePromptContext(client, drainContext.sessionID);
36838
+ const body = {
36839
+ noReply: false,
36840
+ parts: [{ type: "text", text: reminder }]
36740
36841
  };
36741
- }
36742
- if (promptContext?.variant)
36743
- body.variant = promptContext.variant;
36744
- const deliveryID = `aftdel_${randomUUID()}`;
36745
- const taskIDs = deliveredCompletions.map((c) => c.task_id);
36746
- const wakeMeta = {
36747
- delivery_id: deliveryID,
36748
- attempt: state.wakeRetryAttempts + 1,
36749
- task_ids: taskIDs,
36750
- directory: drainContext.directory,
36751
- reminder_sha256: hashReminder(reminder),
36752
- reminder_chars: reminder.length,
36753
- wake_client_path: clientPath,
36754
- prompt_context: promptContext ? {
36755
- agent: promptContext.agent,
36756
- model: promptContext.model ? {
36842
+ if (promptContext?.agent)
36843
+ body.agent = promptContext.agent;
36844
+ if (promptContext?.model) {
36845
+ body.model = {
36757
36846
  providerID: promptContext.model.providerID,
36758
36847
  modelID: promptContext.model.modelID
36759
- } : null,
36760
- variant: promptContext.variant ?? null
36761
- } : null
36762
- };
36763
- sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync start`, {
36764
- event: "bash_completion_wake_prompt_async_start",
36765
- ...wakeMeta
36766
- });
36767
- try {
36768
- await client.session.promptAsync({
36769
- path: { id: drainContext.sessionID },
36770
- body
36848
+ };
36849
+ }
36850
+ if (promptContext?.variant)
36851
+ body.variant = promptContext.variant;
36852
+ const deliveryID2 = `aftdel_${randomUUID2()}`;
36853
+ const wakeMeta = {
36854
+ delivery_id: deliveryID2,
36855
+ attempt: state.wakeRetryAttempts + 1,
36856
+ task_ids: taskIDs,
36857
+ directory: drainContext.directory,
36858
+ reminder_sha256: hashReminder(reminder),
36859
+ reminder_chars: reminder.length,
36860
+ wake_client_path: clientPath,
36861
+ prompt_context: promptContext ? {
36862
+ agent: promptContext.agent,
36863
+ model: promptContext.model ? {
36864
+ providerID: promptContext.model.providerID,
36865
+ modelID: promptContext.model.modelID
36866
+ } : null,
36867
+ variant: promptContext.variant ?? null
36868
+ } : null
36869
+ };
36870
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync start`, {
36871
+ event: "bash_completion_wake_prompt_async_start",
36872
+ ...wakeMeta
36771
36873
  });
36772
- } catch (err) {
36773
- sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
36774
- event: "bash_completion_wake_prompt_async_error",
36775
- delivery_id: deliveryID,
36874
+ try {
36875
+ await client.session.promptAsync({
36876
+ path: { id: drainContext.sessionID },
36877
+ body
36878
+ });
36879
+ } catch (err) {
36880
+ sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
36881
+ event: "bash_completion_wake_prompt_async_error",
36882
+ delivery_id: deliveryID2,
36883
+ attempt: state.wakeRetryAttempts + 1,
36884
+ task_ids: taskIDs,
36885
+ wake_client_path: clientPath,
36886
+ error: err instanceof Error ? err.message : String(err)
36887
+ });
36888
+ throw err;
36889
+ }
36890
+ sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync ok`, {
36891
+ event: "bash_completion_wake_prompt_async_ok",
36892
+ delivery_id: deliveryID2,
36776
36893
  attempt: state.wakeRetryAttempts + 1,
36777
36894
  task_ids: taskIDs,
36778
- error: err instanceof Error ? err.message : String(err)
36895
+ wake_client_path: clientPath
36779
36896
  });
36780
- throw err;
36897
+ return deliveryID2;
36898
+ };
36899
+ if (useLiveServerWake(drainContext.serverUrl) && drainContext.serverUrl) {
36900
+ try {
36901
+ const liveClient = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
36902
+ const deliveryID2 = await sendPrompt(liveClient, "live-server");
36903
+ await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
36904
+ return;
36905
+ } catch (err) {
36906
+ setLiveServerWakeAvailable(drainContext.serverUrl, false);
36907
+ sessionWarn(drainContext.sessionID, `${LOG_PREFIX} live-server wake failed; falling back`, {
36908
+ event: "bash_completion_wake_live_server_fallback",
36909
+ task_ids: taskIDs,
36910
+ directory: drainContext.directory,
36911
+ server_url: drainContext.serverUrl,
36912
+ attempt: state.wakeRetryAttempts + 1,
36913
+ error: err instanceof Error ? err.message : String(err)
36914
+ });
36915
+ const fallbackClient2 = getInProcessClient();
36916
+ const deliveryID2 = await sendPrompt(fallbackClient2, "in-process-fallback");
36917
+ state.retryDelayMs = null;
36918
+ state.wakeRetryAttempts = 0;
36919
+ state.wakeHardStopped = false;
36920
+ await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
36921
+ return;
36922
+ }
36781
36923
  }
36782
- sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync ok`, {
36783
- event: "bash_completion_wake_prompt_async_ok",
36784
- delivery_id: deliveryID,
36785
- attempt: state.wakeRetryAttempts + 1,
36786
- task_ids: taskIDs
36787
- });
36924
+ const fallbackClient = getInProcessClient();
36925
+ const deliveryID = await sendPrompt(fallbackClient, "in-process-fallback");
36788
36926
  await ackCompletions(drainContext, deliveredCompletions, deliveryID);
36789
36927
  }, (err, hardStopped) => {
36790
36928
  sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -36965,7 +37103,8 @@ function scheduleWake(state, sendWake, onSendFailure, sessionID, includeDeferred
36965
37103
  state.wakeDeferredTaskIds.delete(taskId);
36966
37104
  state.pendingLongRunning = [];
36967
37105
  state.pendingPatternMatches = [];
36968
- sendWake(reminder, pending).then(() => {
37106
+ const completionAcks = completionAcksForDelivery(pending, pendingPatternMatches);
37107
+ sendWake(reminder, completionAcks).then(() => {
36969
37108
  state.retryDelayMs = null;
36970
37109
  state.wakeRetryAttempts = 0;
36971
37110
  state.wakeHardStopped = false;
@@ -37030,7 +37169,7 @@ function ingestDrainedBgCompletions(sessionID, completions) {
37030
37169
  state.outstandingTaskIds.delete(completion.task_id);
37031
37170
  if (state.explicitControlTasks.has(completion.task_id)) {
37032
37171
  state.explicitControlTasks.delete(completion.task_id);
37033
- queuePendingPatternMatch(state, completionToExitPattern(completion));
37172
+ queuePendingPatternMatch(state, completionToExitPattern(completion, true));
37034
37173
  continue;
37035
37174
  }
37036
37175
  if (state.consumedTaskIds.has(completion.task_id))
@@ -37066,10 +37205,10 @@ function bufferUnknownCompletion(state, completion) {
37066
37205
  function pruneUnknownCompletions(state, now) {
37067
37206
  state.unknownCompletions = state.unknownCompletions.filter((entry) => now - entry.receivedAt <= UNKNOWN_COMPLETION_TTL_MS);
37068
37207
  }
37069
- function completionToExitPattern(completion) {
37208
+ function completionToExitPattern(completion, ackCompletionOnDelivery = false) {
37070
37209
  const status = formatStatus(completion);
37071
37210
  const preview = formatOutputPreview(completion).replace(/^ {4}/gm, "").slice(-300);
37072
- return {
37211
+ const entry = {
37073
37212
  task_id: completion.task_id,
37074
37213
  session_id: "",
37075
37214
  watch_id: "exit",
@@ -37080,6 +37219,20 @@ ${preview}` : `task ${completion.task_id} exited (${status})`,
37080
37219
  once: true,
37081
37220
  reason: "task_exit"
37082
37221
  };
37222
+ if (ackCompletionOnDelivery)
37223
+ entry.ackCompletionOnDelivery = true;
37224
+ return entry;
37225
+ }
37226
+ function completionAcksForDelivery(completions, patternMatches) {
37227
+ const acks = [...completions];
37228
+ const ackedTaskIds = new Set(acks.map((completion) => completion.task_id));
37229
+ for (const match of patternMatches) {
37230
+ if (!match.ackCompletionOnDelivery || ackedTaskIds.has(match.task_id))
37231
+ continue;
37232
+ acks.push({ task_id: match.task_id, status: "unknown", exit_code: null, command: "" });
37233
+ ackedTaskIds.add(match.task_id);
37234
+ }
37235
+ return acks;
37083
37236
  }
37084
37237
  function isBgCompletion(value) {
37085
37238
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -37147,9 +37300,9 @@ function formatDuration(completion) {
37147
37300
 
37148
37301
  // src/config.ts
37149
37302
  var import_comment_json = __toESM(require_src2(), 1);
37150
- import { existsSync as existsSync6, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
37303
+ import { existsSync as existsSync7, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
37151
37304
  import { homedir as homedir5 } from "node:os";
37152
- import { join as join8 } from "node:path";
37305
+ import { join as join9 } from "node:path";
37153
37306
 
37154
37307
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
37155
37308
  var exports_external = {};
@@ -51032,13 +51185,13 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
51032
51185
  return movedKeys;
51033
51186
  }
51034
51187
  function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
51035
- if (!existsSync6(configPath)) {
51188
+ if (!existsSync7(configPath)) {
51036
51189
  return { migrated: false, oldKeys: [] };
51037
51190
  }
51038
51191
  let tmpPath = null;
51039
51192
  let oldKeys = [];
51040
51193
  try {
51041
- const content = readFileSync3(configPath, "utf-8");
51194
+ const content = readFileSync4(configPath, "utf-8");
51042
51195
  const rawConfig = import_comment_json.parse(content);
51043
51196
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
51044
51197
  return { migrated: false, oldKeys: [] };
@@ -51056,7 +51209,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
51056
51209
  ${serialized}` : serialized;
51057
51210
  tmpPath = `${configPath}.tmp.${process.pid}`;
51058
51211
  writeFileSync3(tmpPath, nextContent, "utf-8");
51059
- renameSync3(tmpPath, configPath);
51212
+ renameSync4(tmpPath, configPath);
51060
51213
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
51061
51214
  return { migrated: true, oldKeys };
51062
51215
  } catch (err) {
@@ -51076,10 +51229,10 @@ ${serialized}` : serialized;
51076
51229
  function detectConfigFile(basePath) {
51077
51230
  const jsoncPath = `${basePath}.jsonc`;
51078
51231
  const jsonPath = `${basePath}.json`;
51079
- if (existsSync6(jsoncPath)) {
51232
+ if (existsSync7(jsoncPath)) {
51080
51233
  return { format: "jsonc", path: jsoncPath };
51081
51234
  }
51082
- if (existsSync6(jsonPath)) {
51235
+ if (existsSync7(jsonPath)) {
51083
51236
  return { format: "json", path: jsonPath };
51084
51237
  }
51085
51238
  return { format: "none", path: jsonPath };
@@ -51112,10 +51265,10 @@ function parseConfigPartially(rawConfig) {
51112
51265
  }
51113
51266
  function loadConfigFromPath(configPath) {
51114
51267
  try {
51115
- if (!existsSync6(configPath)) {
51268
+ if (!existsSync7(configPath)) {
51116
51269
  return null;
51117
51270
  }
51118
- const content = readFileSync3(configPath, "utf-8");
51271
+ const content = readFileSync4(configPath, "utf-8");
51119
51272
  const rawConfig = import_comment_json.parse(content);
51120
51273
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
51121
51274
  const result = AftConfigSchema.safeParse(rawConfig);
@@ -51276,17 +51429,17 @@ function getOpenCodeConfigDir() {
51276
51429
  if (envDir) {
51277
51430
  return envDir;
51278
51431
  }
51279
- const xdgConfig = process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config");
51280
- return join8(xdgConfig, "opencode");
51432
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config");
51433
+ return join9(xdgConfig, "opencode");
51281
51434
  }
51282
51435
  function loadAftConfig(projectDirectory) {
51283
51436
  const configDir = getOpenCodeConfigDir();
51284
- const userBasePath = join8(configDir, "aft");
51437
+ const userBasePath = join9(configDir, "aft");
51285
51438
  migrateAftConfigFile(`${userBasePath}.jsonc`);
51286
51439
  migrateAftConfigFile(`${userBasePath}.json`);
51287
51440
  const userDetected = detectConfigFile(userBasePath);
51288
51441
  const userConfigPath = userDetected.format !== "none" ? userDetected.path : `${userBasePath}.json`;
51289
- const projectBasePath = join8(projectDirectory, ".opencode", "aft");
51442
+ const projectBasePath = join9(projectDirectory, ".opencode", "aft");
51290
51443
  migrateAftConfigFile(`${projectBasePath}.jsonc`);
51291
51444
  migrateAftConfigFile(`${projectBasePath}.json`);
51292
51445
  const projectDetected = detectConfigFile(projectBasePath);
@@ -51311,44 +51464,53 @@ function loadAftConfig(projectDirectory) {
51311
51464
  }
51312
51465
 
51313
51466
  // src/hooks/auto-update-checker/index.ts
51314
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "node:fs";
51315
- import { dirname as dirname5, join as join12 } from "node:path";
51467
+ import {
51468
+ closeSync as closeSync3,
51469
+ existsSync as existsSync10,
51470
+ mkdirSync as mkdirSync7,
51471
+ openSync as openSync3,
51472
+ readFileSync as readFileSync7,
51473
+ renameSync as renameSync5,
51474
+ rmSync as rmSync4,
51475
+ writeFileSync as writeFileSync6
51476
+ } from "node:fs";
51477
+ import { dirname as dirname6 } from "node:path";
51316
51478
 
51317
51479
  // src/hooks/auto-update-checker/cache.ts
51318
51480
  var import_comment_json3 = __toESM(require_src2(), 1);
51319
51481
  import { spawn as spawn2 } from "node:child_process";
51320
- import { cpSync, existsSync as existsSync8, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
51482
+ import { cpSync, existsSync as existsSync9, mkdtempSync, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
51321
51483
  import { tmpdir as tmpdir3 } from "node:os";
51322
- import { basename, dirname as dirname4, join as join11 } from "node:path";
51484
+ import { basename as basename2, dirname as dirname5, join as join12 } from "node:path";
51323
51485
 
51324
51486
  // src/hooks/auto-update-checker/checker.ts
51325
51487
  var import_comment_json2 = __toESM(require_src2(), 1);
51326
- import { existsSync as existsSync7, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
51488
+ import { existsSync as existsSync8, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
51327
51489
  import { homedir as homedir7 } from "node:os";
51328
- import { dirname as dirname3, isAbsolute, join as join10, resolve as resolve2 } from "node:path";
51490
+ import { dirname as dirname4, isAbsolute, join as join11, resolve as resolve2 } from "node:path";
51329
51491
  import { fileURLToPath } from "node:url";
51330
51492
 
51331
51493
  // src/hooks/auto-update-checker/constants.ts
51332
51494
  import { homedir as homedir6, platform } from "node:os";
51333
- import { join as join9 } from "node:path";
51495
+ import { join as join10 } from "node:path";
51334
51496
  var PACKAGE_NAME = "@cortexkit/aft-opencode";
51335
51497
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
51336
51498
  var NPM_FETCH_TIMEOUT = 1e4;
51337
51499
  function getOpenCodeCacheRoot() {
51338
51500
  if (platform() === "win32") {
51339
- return join9(process.env.LOCALAPPDATA ?? homedir6(), "opencode");
51501
+ return join10(process.env.LOCALAPPDATA ?? homedir6(), "opencode");
51340
51502
  }
51341
- return join9(homedir6(), ".cache", "opencode");
51503
+ return join10(homedir6(), ".cache", "opencode");
51342
51504
  }
51343
51505
  function getOpenCodeConfigRoot() {
51344
51506
  if (platform() === "win32") {
51345
- return join9(process.env.APPDATA ?? join9(homedir6(), "AppData", "Roaming"), "opencode");
51507
+ return join10(process.env.APPDATA ?? join10(homedir6(), "AppData", "Roaming"), "opencode");
51346
51508
  }
51347
- return join9(process.env.XDG_CONFIG_HOME ?? join9(homedir6(), ".config"), "opencode");
51509
+ return join10(process.env.XDG_CONFIG_HOME ?? join10(homedir6(), ".config"), "opencode");
51348
51510
  }
51349
- var CACHE_DIR = join9(getOpenCodeCacheRoot(), "packages");
51350
- var USER_OPENCODE_CONFIG = join9(getOpenCodeConfigRoot(), "opencode.json");
51351
- var USER_OPENCODE_CONFIG_JSONC = join9(getOpenCodeConfigRoot(), "opencode.jsonc");
51511
+ var CACHE_DIR = join10(getOpenCodeCacheRoot(), "packages");
51512
+ var USER_OPENCODE_CONFIG = join10(getOpenCodeConfigRoot(), "opencode.json");
51513
+ var USER_OPENCODE_CONFIG_JSONC = join10(getOpenCodeConfigRoot(), "opencode.jsonc");
51352
51514
 
51353
51515
  // src/hooks/auto-update-checker/types.ts
51354
51516
  var NpmPackageEnvelopeSchema = exports_external.object({
@@ -51406,8 +51568,8 @@ function extractChannel(version2) {
51406
51568
  }
51407
51569
  function getConfigPaths(directory) {
51408
51570
  return [
51409
- join10(directory, ".opencode", "opencode.json"),
51410
- join10(directory, ".opencode", "opencode.jsonc"),
51571
+ join11(directory, ".opencode", "opencode.json"),
51572
+ join11(directory, ".opencode", "opencode.jsonc"),
51411
51573
  USER_OPENCODE_CONFIG,
51412
51574
  USER_OPENCODE_CONFIG_JSONC
51413
51575
  ];
@@ -51422,14 +51584,14 @@ function resolvePathPluginSpec(spec, configPath) {
51422
51584
  }
51423
51585
  if (isAbsolute(spec) || /^[A-Za-z]:[\\/]/.test(spec))
51424
51586
  return spec;
51425
- return resolve2(dirname3(configPath), spec);
51587
+ return resolve2(dirname4(configPath), spec);
51426
51588
  }
51427
51589
  function getLocalDevPath(directory) {
51428
51590
  for (const configPath of getConfigPaths(directory)) {
51429
51591
  try {
51430
- if (!existsSync7(configPath))
51592
+ if (!existsSync8(configPath))
51431
51593
  continue;
51432
- const rawConfig = parseJsonConfig(readFileSync4(configPath, "utf-8"));
51594
+ const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
51433
51595
  const plugins = getPluginEntries(rawConfig);
51434
51596
  for (const entry of plugins) {
51435
51597
  if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
@@ -51439,7 +51601,7 @@ function getLocalDevPath(directory) {
51439
51601
  const pkgPath = findPackageJsonUp(localPath);
51440
51602
  if (!pkgPath)
51441
51603
  continue;
51442
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
51604
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
51443
51605
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
51444
51606
  return localPath;
51445
51607
  }
@@ -51451,17 +51613,17 @@ function getLocalDevPath(directory) {
51451
51613
  function findPackageJsonUp(startPath) {
51452
51614
  try {
51453
51615
  const stat = statSync3(startPath);
51454
- let dir = stat.isDirectory() ? startPath : dirname3(startPath);
51616
+ let dir = stat.isDirectory() ? startPath : dirname4(startPath);
51455
51617
  for (let i = 0;i < 10; i++) {
51456
- const pkgPath = join10(dir, "package.json");
51457
- if (existsSync7(pkgPath)) {
51618
+ const pkgPath = join11(dir, "package.json");
51619
+ if (existsSync8(pkgPath)) {
51458
51620
  try {
51459
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
51621
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
51460
51622
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
51461
51623
  return pkgPath;
51462
51624
  } catch {}
51463
51625
  }
51464
- const parent = dirname3(dir);
51626
+ const parent = dirname4(dir);
51465
51627
  if (parent === dir)
51466
51628
  break;
51467
51629
  dir = parent;
@@ -51477,7 +51639,7 @@ function getLocalDevVersion(directory) {
51477
51639
  const pkgPath = findPackageJsonUp(localPath);
51478
51640
  if (!pkgPath)
51479
51641
  return null;
51480
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
51642
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
51481
51643
  return pkg.success ? pkg.data.version ?? null : null;
51482
51644
  } catch {
51483
51645
  return null;
@@ -51485,7 +51647,7 @@ function getLocalDevVersion(directory) {
51485
51647
  }
51486
51648
  function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
51487
51649
  try {
51488
- return findPackageJsonUp(dirname3(fileURLToPath(currentModuleUrl)));
51650
+ return findPackageJsonUp(dirname4(fileURLToPath(currentModuleUrl)));
51489
51651
  } catch (err) {
51490
51652
  warn2(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
51491
51653
  return null;
@@ -51494,9 +51656,9 @@ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
51494
51656
  function findPluginEntry(directory) {
51495
51657
  for (const configPath of getConfigPaths(directory)) {
51496
51658
  try {
51497
- if (!existsSync7(configPath))
51659
+ if (!existsSync8(configPath))
51498
51660
  continue;
51499
- const rawConfig = parseJsonConfig(readFileSync4(configPath, "utf-8"));
51661
+ const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
51500
51662
  const plugins = getPluginEntries(rawConfig);
51501
51663
  for (const entry of plugins) {
51502
51664
  if (entry === PACKAGE_NAME) {
@@ -51514,7 +51676,7 @@ function findPluginEntry(directory) {
51514
51676
  }
51515
51677
  var cachedPackageVersion = null;
51516
51678
  function getSpecCachePackageJsonPath(spec) {
51517
- return join10(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
51679
+ return join11(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
51518
51680
  }
51519
51681
  function getCachedVersion(spec) {
51520
51682
  if (!spec && cachedPackageVersion)
@@ -51523,13 +51685,13 @@ function getCachedVersion(spec) {
51523
51685
  getCurrentRuntimePackageJsonPath(),
51524
51686
  spec ? getSpecCachePackageJsonPath(spec) : null,
51525
51687
  getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
51526
- join10(homedir7(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
51688
+ join11(homedir7(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
51527
51689
  ].filter(isString);
51528
51690
  for (const packageJsonPath of candidates) {
51529
51691
  try {
51530
- if (!existsSync7(packageJsonPath))
51692
+ if (!existsSync8(packageJsonPath))
51531
51693
  continue;
51532
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
51694
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(packageJsonPath, "utf-8")));
51533
51695
  if (pkg.success && pkg.data.version) {
51534
51696
  if (!spec)
51535
51697
  cachedPackageVersion = pkg.data.version;
@@ -51571,17 +51733,17 @@ async function getLatestVersion(channel = "latest", options = {}) {
51571
51733
  // src/hooks/auto-update-checker/cache.ts
51572
51734
  var pendingSnapshots = new Map;
51573
51735
  function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
51574
- const packageDir = join11(installDir, "node_modules", packageName);
51575
- const lockfilePath = join11(installDir, "package-lock.json");
51576
- const tempDir = mkdtempSync(join11(tmpdir3(), "aft-auto-update-"));
51577
- const stagedPackageDir = existsSync8(packageDir) ? join11(tempDir, "package") : null;
51736
+ const packageDir = join12(installDir, "node_modules", packageName);
51737
+ const lockfilePath = join12(installDir, "package-lock.json");
51738
+ const tempDir = mkdtempSync(join12(tmpdir3(), "aft-auto-update-"));
51739
+ const stagedPackageDir = existsSync9(packageDir) ? join12(tempDir, "package") : null;
51578
51740
  if (stagedPackageDir)
51579
51741
  cpSync(packageDir, stagedPackageDir, { recursive: true });
51580
51742
  return {
51581
51743
  packageJsonPath,
51582
- packageJson: existsSync8(packageJsonPath) ? readFileSync5(packageJsonPath, "utf-8") : null,
51744
+ packageJson: existsSync9(packageJsonPath) ? readFileSync6(packageJsonPath, "utf-8") : null,
51583
51745
  lockfilePath,
51584
- lockfile: existsSync8(lockfilePath) ? readFileSync5(lockfilePath, "utf-8") : null,
51746
+ lockfile: existsSync9(lockfilePath) ? readFileSync6(lockfilePath, "utf-8") : null,
51585
51747
  packageDir,
51586
51748
  stagedPackageDir,
51587
51749
  tempDir
@@ -51608,18 +51770,18 @@ function restoreAutoUpdateSnapshot(snapshot) {
51608
51770
  function stripPackageNameFromPath(pathValue, packageName) {
51609
51771
  let current = pathValue;
51610
51772
  for (const segment of [...packageName.split("/")].reverse()) {
51611
- if (basename(current) !== segment)
51773
+ if (basename2(current) !== segment)
51612
51774
  return null;
51613
- current = dirname4(current);
51775
+ current = dirname5(current);
51614
51776
  }
51615
51777
  return current;
51616
51778
  }
51617
51779
  function removeFromPackageLock(installDir, packageName) {
51618
- const lockPath = join11(installDir, "package-lock.json");
51619
- if (!existsSync8(lockPath))
51780
+ const lockPath = join12(installDir, "package-lock.json");
51781
+ if (!existsSync9(lockPath))
51620
51782
  return false;
51621
51783
  try {
51622
- const lock = import_comment_json3.parse(readFileSync5(lockPath, "utf-8"));
51784
+ const lock = import_comment_json3.parse(readFileSync6(lockPath, "utf-8"));
51623
51785
  let modified = false;
51624
51786
  if (lock.packages) {
51625
51787
  const key = `node_modules/${packageName}`;
@@ -51642,10 +51804,10 @@ function removeFromPackageLock(installDir, packageName) {
51642
51804
  }
51643
51805
  }
51644
51806
  function ensureDependencyVersion(packageJsonPath, packageName, version2) {
51645
- if (!existsSync8(packageJsonPath))
51807
+ if (!existsSync9(packageJsonPath))
51646
51808
  return false;
51647
51809
  try {
51648
- const raw = import_comment_json3.parse(readFileSync5(packageJsonPath, "utf-8"));
51810
+ const raw = import_comment_json3.parse(readFileSync6(packageJsonPath, "utf-8"));
51649
51811
  const pkgJson = PackageJsonSchema.safeParse(raw);
51650
51812
  if (!pkgJson.success)
51651
51813
  return false;
@@ -51664,8 +51826,8 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
51664
51826
  }
51665
51827
  }
51666
51828
  function removeInstalledPackage(installDir, packageName) {
51667
- const packageDir = join11(installDir, "node_modules", packageName);
51668
- if (!existsSync8(packageDir))
51829
+ const packageDir = join12(installDir, "node_modules", packageName);
51830
+ if (!existsSync9(packageDir))
51669
51831
  return false;
51670
51832
  rmSync3(packageDir, { recursive: true, force: true });
51671
51833
  log2(`[auto-update-checker] Package removed: ${packageDir}`);
@@ -51673,19 +51835,19 @@ function removeInstalledPackage(installDir, packageName) {
51673
51835
  }
51674
51836
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
51675
51837
  if (runtimePackageJsonPath) {
51676
- const packageDir = dirname4(runtimePackageJsonPath);
51838
+ const packageDir = dirname5(runtimePackageJsonPath);
51677
51839
  const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
51678
- if (nodeModulesDir && basename(nodeModulesDir) === "node_modules") {
51679
- const installDir = dirname4(nodeModulesDir);
51680
- const packageJsonPath = join11(installDir, "package.json");
51681
- if (existsSync8(packageJsonPath))
51840
+ if (nodeModulesDir && basename2(nodeModulesDir) === "node_modules") {
51841
+ const installDir = dirname5(nodeModulesDir);
51842
+ const packageJsonPath = join12(installDir, "package.json");
51843
+ if (existsSync9(packageJsonPath))
51682
51844
  return { installDir, packageJsonPath };
51683
51845
  }
51684
51846
  return null;
51685
51847
  }
51686
- const legacyPackageJsonPath = join11(dirname4(CACHE_DIR), "package.json");
51687
- if (existsSync8(legacyPackageJsonPath)) {
51688
- return { installDir: dirname4(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
51848
+ const legacyPackageJsonPath = join12(dirname5(CACHE_DIR), "package.json");
51849
+ if (existsSync9(legacyPackageJsonPath)) {
51850
+ return { installDir: dirname5(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
51689
51851
  }
51690
51852
  return null;
51691
51853
  }
@@ -51835,36 +51997,76 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
51835
51997
  async function maybeRunCheck(ctx, options) {
51836
51998
  if (options.signal.aborted)
51837
51999
  return;
51838
- if (!claimCheckSlot(options.storageDir, options.checkIntervalMs)) {
52000
+ const checkSlot = claimCheckSlot(options.storageDir, options.checkIntervalMs);
52001
+ if (!checkSlot) {
51839
52002
  log2("[auto-update-checker] Skipping check (another instance ran one recently)");
51840
52003
  return;
51841
52004
  }
51842
- await runStartupCheck(ctx, options);
52005
+ try {
52006
+ await runStartupCheck(ctx, options);
52007
+ } finally {
52008
+ checkSlot.release();
52009
+ }
51843
52010
  }
51844
52011
  function claimCheckSlot(storageDir, intervalMs) {
51845
52012
  if (!storageDir)
51846
- return true;
52013
+ return { release: () => {} };
52014
+ const file2 = repairRootScopedStorageFile(storageDir, "opencode", TIMESTAMP_FILENAME);
51847
52015
  try {
51848
- const file2 = join12(storageDir, TIMESTAMP_FILENAME);
51849
- if (existsSync9(file2)) {
51850
- try {
51851
- const raw = JSON.parse(readFileSync6(file2, "utf-8"));
51852
- const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
51853
- if (Number.isFinite(last) && Date.now() - last < intervalMs) {
51854
- return false;
51855
- }
51856
- } catch {}
52016
+ if (hasRecentCheckTimestamp(file2, intervalMs))
52017
+ return null;
52018
+ mkdirSync7(dirname6(file2), { recursive: true });
52019
+ const lockPath = `${file2}.lock`;
52020
+ let lockFd;
52021
+ try {
52022
+ lockFd = openSync3(lockPath, "wx");
52023
+ writeFileSync6(lockFd, JSON.stringify({ pid: process.pid, startedMs: Date.now() }));
52024
+ } catch (err) {
52025
+ if (err.code !== "EEXIST") {
52026
+ warn2(`[auto-update-checker] Could not acquire update lock: ${String(err)}`);
52027
+ }
52028
+ return null;
52029
+ }
52030
+ const lock = {
52031
+ release: () => {
52032
+ try {
52033
+ closeSync3(lockFd);
52034
+ } catch {}
52035
+ rmSync4(lockPath, { force: true });
52036
+ }
52037
+ };
52038
+ try {
52039
+ if (hasRecentCheckTimestamp(file2, intervalMs)) {
52040
+ lock.release();
52041
+ return null;
52042
+ }
52043
+ writeCheckTimestamp(file2);
52044
+ return lock;
52045
+ } catch (err) {
52046
+ lock.release();
52047
+ throw err;
51857
52048
  }
51858
- mkdirSync6(dirname5(file2), { recursive: true });
51859
- const tmp = `${file2}.tmp.${process.pid}`;
51860
- writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
51861
- renameSync4(tmp, file2);
51862
- return true;
51863
52049
  } catch (err) {
51864
52050
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
51865
- return true;
52051
+ return null;
51866
52052
  }
51867
52053
  }
52054
+ function hasRecentCheckTimestamp(file2, intervalMs) {
52055
+ if (!existsSync10(file2))
52056
+ return false;
52057
+ try {
52058
+ const raw = JSON.parse(readFileSync7(file2, "utf-8"));
52059
+ const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
52060
+ return Number.isFinite(last) && Date.now() - last < intervalMs;
52061
+ } catch {
52062
+ return false;
52063
+ }
52064
+ }
52065
+ function writeCheckTimestamp(file2) {
52066
+ const tmp = `${file2}.tmp.${process.pid}`;
52067
+ writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
52068
+ renameSync5(tmp, file2);
52069
+ }
51868
52070
  async function runStartupCheck(ctx, options) {
51869
52071
  if (options.signal.aborted)
51870
52072
  return;
@@ -51944,21 +52146,24 @@ ${installResult.stderrTail}` : "";
51944
52146
  warn2(`[auto-update-checker] npm install failed; update not installed${failureDetail}${stderrDetail}`);
51945
52147
  }
51946
52148
  function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
51947
- ctx.client.tui.showToast({ body: { title, message, variant, duration: duration3 } }).catch(() => {});
52149
+ const tui = ctx.client.tui;
52150
+ if (typeof tui?.showToast !== "function")
52151
+ return;
52152
+ tui.showToast({ body: { title, message, variant, duration: duration3 } }).catch(() => {});
51948
52153
  }
51949
52154
 
51950
52155
  // src/lsp-auto-install.ts
51951
52156
  import { spawn as spawn3 } from "node:child_process";
51952
52157
  import { createHash as createHash5 } from "node:crypto";
51953
- import { createReadStream, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "node:fs";
52158
+ import { createReadStream, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
51954
52159
  import { join as join15 } from "node:path";
51955
52160
 
51956
52161
  // src/lsp-cache.ts
51957
52162
  import {
51958
- closeSync as closeSync3,
51959
- mkdirSync as mkdirSync7,
51960
- openSync as openSync3,
51961
- readFileSync as readFileSync7,
52163
+ closeSync as closeSync4,
52164
+ mkdirSync as mkdirSync8,
52165
+ openSync as openSync4,
52166
+ readFileSync as readFileSync8,
51962
52167
  statSync as statSync4,
51963
52168
  unlinkSync as unlinkSync5,
51964
52169
  writeFileSync as writeFileSync7
@@ -52006,7 +52211,7 @@ function lspBinaryCandidates(binary) {
52006
52211
  var INSTALLED_META_FILE = ".aft-installed";
52007
52212
  function writeInstalledMetaIn(installDir, version2, sha256) {
52008
52213
  try {
52009
- mkdirSync7(installDir, { recursive: true });
52214
+ mkdirSync8(installDir, { recursive: true });
52010
52215
  const meta3 = {
52011
52216
  version: version2,
52012
52217
  installedAt: new Date().toISOString(),
@@ -52022,7 +52227,7 @@ function readInstalledMetaIn(installDir) {
52022
52227
  try {
52023
52228
  if (!statSync4(path2).isFile())
52024
52229
  return null;
52025
- const raw = readFileSync7(path2, "utf8");
52230
+ const raw = readFileSync8(path2, "utf8");
52026
52231
  const parsed = JSON.parse(raw);
52027
52232
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
52028
52233
  return null;
@@ -52046,17 +52251,17 @@ function lockPath(npmPackage) {
52046
52251
  }
52047
52252
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
52048
52253
  function acquireInstallLock(lockKey) {
52049
- mkdirSync7(lspPackageDir(lockKey), { recursive: true });
52254
+ mkdirSync8(lspPackageDir(lockKey), { recursive: true });
52050
52255
  const lock = lockPath(lockKey);
52051
52256
  const tryClaim = () => {
52052
52257
  try {
52053
- const fd = openSync3(lock, "wx");
52258
+ const fd = openSync4(lock, "wx");
52054
52259
  try {
52055
52260
  writeFileSync7(fd, `${process.pid}
52056
52261
  ${new Date().toISOString()}
52057
52262
  `);
52058
52263
  } finally {
52059
- closeSync3(fd);
52264
+ closeSync4(fd);
52060
52265
  }
52061
52266
  return true;
52062
52267
  } catch (err) {
@@ -52072,7 +52277,7 @@ ${new Date().toISOString()}
52072
52277
  let owningPid = null;
52073
52278
  let lockMtimeMs = 0;
52074
52279
  try {
52075
- const raw = readFileSync7(lock, "utf8");
52280
+ const raw = readFileSync8(lock, "utf8");
52076
52281
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
52077
52282
  const parsed = Number.parseInt(firstLine, 10);
52078
52283
  if (Number.isFinite(parsed) && parsed > 0)
@@ -52110,7 +52315,7 @@ function releaseInstallLock(lockKey) {
52110
52315
  try {
52111
52316
  let owningPid = null;
52112
52317
  try {
52113
- const raw = readFileSync7(lock, "utf8");
52318
+ const raw = readFileSync8(lock, "utf8");
52114
52319
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
52115
52320
  const parsed = Number.parseInt(firstLine, 10);
52116
52321
  if (Number.isFinite(parsed) && parsed > 0)
@@ -52151,7 +52356,7 @@ var VERSION_CHECK_FILE = ".aft-version-check";
52151
52356
  function readVersionCheck(npmPackage) {
52152
52357
  const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
52153
52358
  try {
52154
- const raw = readFileSync7(file2, "utf8");
52359
+ const raw = readFileSync8(file2, "utf8");
52155
52360
  const parsed = JSON.parse(raw);
52156
52361
  if (typeof parsed.last_checked === "string") {
52157
52362
  return {
@@ -52165,7 +52370,7 @@ function readVersionCheck(npmPackage) {
52165
52370
  }
52166
52371
  }
52167
52372
  function writeVersionCheck(npmPackage, latest) {
52168
- mkdirSync7(lspPackageDir(npmPackage), { recursive: true });
52373
+ mkdirSync8(lspPackageDir(npmPackage), { recursive: true });
52169
52374
  const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
52170
52375
  const record2 = {
52171
52376
  last_checked: new Date().toISOString(),
@@ -52328,7 +52533,7 @@ var NPM_LSP_TABLE = [
52328
52533
  ];
52329
52534
 
52330
52535
  // src/lsp-project-relevance.ts
52331
- import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
52536
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
52332
52537
  import { join as join14 } from "node:path";
52333
52538
  var MAX_WALK_DIRS = 200;
52334
52539
  var MAX_WALK_DEPTH = 4;
@@ -52346,7 +52551,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
52346
52551
  if (!rootMarkers)
52347
52552
  return false;
52348
52553
  for (const marker of rootMarkers) {
52349
- if (existsSync10(join14(projectRoot, marker)))
52554
+ if (existsSync11(join14(projectRoot, marker)))
52350
52555
  return true;
52351
52556
  }
52352
52557
  return false;
@@ -52370,7 +52575,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
52370
52575
  }
52371
52576
  function readPackageJson(projectRoot) {
52372
52577
  try {
52373
- const raw = readFileSync8(join14(projectRoot, "package.json"), "utf8");
52578
+ const raw = readFileSync9(join14(projectRoot, "package.json"), "utf8");
52374
52579
  const parsed = JSON.parse(raw);
52375
52580
  if (typeof parsed !== "object" || parsed === null)
52376
52581
  return null;
@@ -52697,16 +52902,16 @@ function installedBinaryPath(spec) {
52697
52902
  return null;
52698
52903
  }
52699
52904
  function sha256OfFileSync(path2) {
52700
- return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
52905
+ return createHash5("sha256").update(readFileSync10(path2)).digest("hex");
52701
52906
  }
52702
52907
  function quarantineCachedNpmInstall(spec, reason) {
52703
52908
  const packageDir = lspPackageDir(spec.npm);
52704
52909
  const dest = join15(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
52705
52910
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
52706
52911
  try {
52707
- mkdirSync8(join15(dest, ".."), { recursive: true });
52708
- rmSync4(dest, { recursive: true, force: true });
52709
- renameSync5(packageDir, dest);
52912
+ mkdirSync9(join15(dest, ".."), { recursive: true });
52913
+ rmSync5(dest, { recursive: true, force: true });
52914
+ renameSync6(packageDir, dest);
52710
52915
  } catch (err) {
52711
52916
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
52712
52917
  }
@@ -52782,25 +52987,25 @@ function runAutoInstall(projectRoot, config2, fetchImpl2 = fetch) {
52782
52987
 
52783
52988
  // src/lsp-github-install.ts
52784
52989
  import { execFileSync as execFileSync2 } from "node:child_process";
52785
- import { createHash as createHash6, randomBytes } from "node:crypto";
52990
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
52786
52991
  import {
52787
52992
  copyFileSync as copyFileSync3,
52788
52993
  createReadStream as createReadStream2,
52789
52994
  createWriteStream as createWriteStream3,
52790
- existsSync as existsSync11,
52995
+ existsSync as existsSync12,
52791
52996
  lstatSync as lstatSync2,
52792
- mkdirSync as mkdirSync9,
52997
+ mkdirSync as mkdirSync10,
52793
52998
  readdirSync as readdirSync4,
52794
- readFileSync as readFileSync10,
52999
+ readFileSync as readFileSync11,
52795
53000
  readlinkSync as readlinkSync2,
52796
53001
  realpathSync as realpathSync3,
52797
- renameSync as renameSync6,
52798
- rmSync as rmSync5,
53002
+ renameSync as renameSync7,
53003
+ rmSync as rmSync6,
52799
53004
  statSync as statSync6,
52800
53005
  unlinkSync as unlinkSync6,
52801
53006
  writeFileSync as writeFileSync8
52802
53007
  } from "node:fs";
52803
- import { dirname as dirname6, join as join16, relative as relative2, resolve as resolve3 } from "node:path";
53008
+ import { dirname as dirname7, join as join16, relative as relative2, resolve as resolve3 } from "node:path";
52804
53009
  import { Readable as Readable3 } from "node:stream";
52805
53010
  import { pipeline as pipeline3 } from "node:stream/promises";
52806
53011
 
@@ -52925,7 +53130,7 @@ function readGithubInstalledMetaIn(installDir) {
52925
53130
  const path2 = join16(installDir, INSTALLED_META_FILE2);
52926
53131
  if (!statSync6(path2).isFile())
52927
53132
  return null;
52928
- const parsed = JSON.parse(readFileSync10(path2, "utf8"));
53133
+ const parsed = JSON.parse(readFileSync11(path2, "utf8"));
52929
53134
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
52930
53135
  return null;
52931
53136
  return {
@@ -52941,7 +53146,7 @@ function readGithubInstalledMetaIn(installDir) {
52941
53146
  }
52942
53147
  function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
52943
53148
  try {
52944
- mkdirSync9(installDir, { recursive: true });
53149
+ mkdirSync10(installDir, { recursive: true });
52945
53150
  const meta3 = {
52946
53151
  version: version2,
52947
53152
  installedAt: new Date().toISOString(),
@@ -52966,7 +53171,7 @@ function sha256OfFile(path2) {
52966
53171
  });
52967
53172
  }
52968
53173
  function sha256OfFileSync2(path2) {
52969
- return createHash6("sha256").update(readFileSync10(path2)).digest("hex");
53174
+ return createHash6("sha256").update(readFileSync11(path2)).digest("hex");
52970
53175
  }
52971
53176
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl2, signal) {
52972
53177
  const candidates = [];
@@ -53142,7 +53347,7 @@ async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
53142
53347
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
53143
53348
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
53144
53349
  }
53145
- mkdirSync9(dirname6(destPath), { recursive: true });
53350
+ mkdirSync10(dirname7(destPath), { recursive: true });
53146
53351
  let bytesWritten = 0;
53147
53352
  const guard = new TransformStream({
53148
53353
  transform(chunk, controller) {
@@ -53263,23 +53468,23 @@ function precheckArchiveContents(archivePath, archiveType) {
53263
53468
  }
53264
53469
  }
53265
53470
  function extractArchiveSafely(archivePath, destDir, archiveType) {
53266
- const suffix = randomBytes(8).toString("hex");
53471
+ const suffix = randomBytes2(8).toString("hex");
53267
53472
  const stagingDir = `${destDir}.staging-${suffix}`;
53268
53473
  try {
53269
- rmSync5(stagingDir, { recursive: true, force: true });
53474
+ rmSync6(stagingDir, { recursive: true, force: true });
53270
53475
  } catch {}
53271
- mkdirSync9(stagingDir, { recursive: true });
53476
+ mkdirSync10(stagingDir, { recursive: true });
53272
53477
  try {
53273
53478
  precheckArchiveContents(archivePath, archiveType);
53274
53479
  runPlatformExtractor(archivePath, stagingDir, archiveType);
53275
53480
  validateExtraction(stagingDir);
53276
53481
  try {
53277
- rmSync5(destDir, { recursive: true, force: true });
53482
+ rmSync6(destDir, { recursive: true, force: true });
53278
53483
  } catch {}
53279
- renameSync6(stagingDir, destDir);
53484
+ renameSync7(stagingDir, destDir);
53280
53485
  } catch (err) {
53281
53486
  try {
53282
- rmSync5(stagingDir, { recursive: true, force: true });
53487
+ rmSync6(stagingDir, { recursive: true, force: true });
53283
53488
  } catch {}
53284
53489
  throw err;
53285
53490
  }
@@ -53289,9 +53494,9 @@ function quarantineCachedGithubInstall(spec, reason) {
53289
53494
  const dest = join16(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
53290
53495
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
53291
53496
  try {
53292
- mkdirSync9(dirname6(dest), { recursive: true });
53293
- rmSync5(dest, { recursive: true, force: true });
53294
- renameSync6(packageDir, dest);
53497
+ mkdirSync10(dirname7(dest), { recursive: true });
53498
+ rmSync6(dest, { recursive: true, force: true });
53499
+ renameSync7(packageDir, dest);
53295
53500
  } catch (err) {
53296
53501
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
53297
53502
  }
@@ -53418,12 +53623,12 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl2
53418
53623
  } catch {}
53419
53624
  }
53420
53625
  const innerBinaryPath = join16(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
53421
- if (!existsSync11(innerBinaryPath)) {
53626
+ if (!existsSync12(innerBinaryPath)) {
53422
53627
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
53423
53628
  return null;
53424
53629
  }
53425
53630
  const targetBinary = ghBinaryPath(spec, platform2);
53426
- mkdirSync9(dirname6(targetBinary), { recursive: true });
53631
+ mkdirSync10(dirname7(targetBinary), { recursive: true });
53427
53632
  try {
53428
53633
  copyFileSync3(innerBinaryPath, targetBinary);
53429
53634
  if (platform2 !== "win32") {
@@ -53502,7 +53707,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl2 = fetch) {
53502
53707
  if (!host) {
53503
53708
  for (const spec of GITHUB_LSP_TABLE) {
53504
53709
  try {
53505
- if (existsSync11(ghBinDir(spec))) {
53710
+ if (existsSync12(ghBinDir(spec))) {
53506
53711
  cachedBinDirs.push(ghBinDir(spec));
53507
53712
  }
53508
53713
  } catch {}
@@ -53665,9 +53870,9 @@ function normalizeToolMap(tools) {
53665
53870
  }
53666
53871
 
53667
53872
  // src/notifications.ts
53668
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "node:fs";
53873
+ import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
53669
53874
  import { homedir as homedir9, platform as platform2 } from "node:os";
53670
- import { join as join17 } from "node:path";
53875
+ import { dirname as dirname8, join as join17 } from "node:path";
53671
53876
  function isTuiMode() {
53672
53877
  return process.env.OPENCODE_CLIENT === "cli";
53673
53878
  }
@@ -53702,13 +53907,13 @@ function getDesktopStatePath() {
53702
53907
  }
53703
53908
  return null;
53704
53909
  }
53705
- function readDesktopState(directory) {
53910
+ function readDesktopState() {
53706
53911
  const statePath = getDesktopStatePath();
53707
- if (!statePath || !existsSync12(statePath)) {
53708
- return { sessionId: null, serverUrl: null };
53912
+ if (!statePath || !existsSync13(statePath)) {
53913
+ return { serverUrl: null };
53709
53914
  }
53710
53915
  try {
53711
- const raw = readFileSync11(statePath, "utf-8");
53916
+ const raw = readFileSync12(statePath, "utf-8");
53712
53917
  const state = JSON.parse(raw);
53713
53918
  let serverUrl = null;
53714
53919
  const serverStr = state.server;
@@ -53720,19 +53925,43 @@ function readDesktopState(directory) {
53720
53925
  }
53721
53926
  } catch {}
53722
53927
  }
53723
- let sessionId = null;
53724
- const layoutPage = state["layout.page"];
53725
- if (typeof layoutPage === "string") {
53726
- const parsed = JSON.parse(layoutPage);
53727
- const lastProjectSession = parsed.lastProjectSession;
53728
- if (lastProjectSession) {
53729
- const entry = lastProjectSession[directory];
53730
- sessionId = entry?.id ?? null;
53731
- }
53732
- }
53733
- return { sessionId, serverUrl };
53928
+ return { serverUrl };
53734
53929
  } catch {
53735
- return { sessionId: null, serverUrl: null };
53930
+ return { serverUrl: null };
53931
+ }
53932
+ }
53933
+ var MAX_PENDING_DESKTOP_NOTIFICATIONS = 20;
53934
+ var pendingDesktopNotifications = new Map;
53935
+ function getExplicitSessionId(opts) {
53936
+ const sessionId = opts.sessionId?.trim();
53937
+ return sessionId ? sessionId : null;
53938
+ }
53939
+ function enqueuePendingDesktopNotification(directory, notification) {
53940
+ if (!directory)
53941
+ return;
53942
+ const pending = pendingDesktopNotifications.get(directory) ?? [];
53943
+ if (pending.some((item) => item.key === notification.key))
53944
+ return;
53945
+ pending.push(notification);
53946
+ if (pending.length > MAX_PENDING_DESKTOP_NOTIFICATIONS) {
53947
+ pending.splice(0, pending.length - MAX_PENDING_DESKTOP_NOTIFICATIONS);
53948
+ }
53949
+ pendingDesktopNotifications.set(directory, pending);
53950
+ }
53951
+ async function flushPendingDesktopNotifications(opts, sessionId) {
53952
+ const pending = pendingDesktopNotifications.get(opts.directory);
53953
+ if (!pending?.length)
53954
+ return;
53955
+ pendingDesktopNotifications.delete(opts.directory);
53956
+ for (const notification of pending) {
53957
+ if (notification.shouldSkip?.())
53958
+ continue;
53959
+ const delivered = await sendIgnoredMessage(opts.client, sessionId, notification.text);
53960
+ if (delivered) {
53961
+ notification.onDelivered?.();
53962
+ } else {
53963
+ enqueuePendingDesktopNotification(opts.directory, notification);
53964
+ }
53736
53965
  }
53737
53966
  }
53738
53967
  function getServerAuth() {
@@ -53797,52 +54026,72 @@ async function sendWarning(opts, message) {
53797
54026
  const toastSent = await showTuiToast(opts.client, "AFT Warning", message, "warning", 1e4);
53798
54027
  if (toastSent)
53799
54028
  return;
53800
- const { sessionId } = readDesktopState(opts.directory);
53801
- if (!sessionId)
53802
- return;
53803
54029
  const text = `${WARNING_MARKER} ${message}`;
54030
+ const sessionId = getExplicitSessionId(opts);
54031
+ if (!sessionId) {
54032
+ enqueuePendingDesktopNotification(opts.directory, { key: `warning:${message}`, text });
54033
+ return;
54034
+ }
54035
+ await flushPendingDesktopNotifications(opts, sessionId);
53804
54036
  sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
53805
54037
  await sendIgnoredMessage(opts.client, sessionId, text);
53806
54038
  }
53807
54039
  async function sendFeatureAnnouncement(opts, version2, features, footer, storageDir) {
53808
- if (storageDir) {
53809
- const versionFile = join17(storageDir, "last_announced_version");
53810
- try {
53811
- if (existsSync12(versionFile)) {
53812
- const lastVersion = readFileSync11(versionFile, "utf-8").trim();
53813
- if (lastVersion === version2)
53814
- return;
53815
- }
53816
- } catch {}
53817
- }
54040
+ if (hasAnnouncedVersion(storageDir, version2))
54041
+ return;
53818
54042
  const hasFooter = typeof footer === "string" && footer.trim().length > 0;
53819
54043
  const featureText = hasFooter ? [features.map((f) => `• ${f}`).join(`
53820
54044
  `), "", footer].join(`
53821
54045
  `) : features.map((f) => `• ${f}`).join(`
53822
54046
  `);
53823
54047
  const toastSent = await showTuiToast(opts.client, `AFT v${version2}`, featureText, "info", 12000);
53824
- if (!toastSent) {
53825
- const { sessionId } = readDesktopState(opts.directory);
53826
- if (!sessionId)
53827
- return;
53828
- const sections = [
53829
- `${FEATURE_MARKER} v${version2}:`,
53830
- ...features.map((f) => ` • ${f}`)
53831
- ];
53832
- if (hasFooter)
53833
- sections.push("", footer);
53834
- const text = sections.join(`
54048
+ if (toastSent) {
54049
+ persistAnnouncedVersion(storageDir, version2);
54050
+ return;
54051
+ }
54052
+ const sections = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` • ${f}`)];
54053
+ if (hasFooter)
54054
+ sections.push("", footer);
54055
+ const text = sections.join(`
53835
54056
  `);
53836
- sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
53837
- await sendIgnoredMessage(opts.client, sessionId, text);
54057
+ const pending = {
54058
+ key: `feature:${version2}`,
54059
+ text,
54060
+ shouldSkip: () => hasAnnouncedVersion(storageDir, version2),
54061
+ onDelivered: () => persistAnnouncedVersion(storageDir, version2)
54062
+ };
54063
+ const sessionId = getExplicitSessionId(opts);
54064
+ if (!sessionId) {
54065
+ enqueuePendingDesktopNotification(opts.directory, pending);
54066
+ return;
53838
54067
  }
53839
- if (storageDir) {
53840
- try {
53841
- mkdirSync10(storageDir, { recursive: true });
53842
- writeFileSync9(join17(storageDir, "last_announced_version"), version2);
53843
- } catch {}
54068
+ await flushPendingDesktopNotifications(opts, sessionId);
54069
+ if (hasAnnouncedVersion(storageDir, version2))
54070
+ return;
54071
+ sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
54072
+ if (await sendIgnoredMessage(opts.client, sessionId, text)) {
54073
+ persistAnnouncedVersion(storageDir, version2);
53844
54074
  }
53845
54075
  }
54076
+ function hasAnnouncedVersion(storageDir, version2) {
54077
+ if (!storageDir)
54078
+ return false;
54079
+ const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
54080
+ try {
54081
+ return existsSync13(versionFile) && readFileSync12(versionFile, "utf-8").trim() === version2;
54082
+ } catch {
54083
+ return false;
54084
+ }
54085
+ }
54086
+ function persistAnnouncedVersion(storageDir, version2) {
54087
+ if (!storageDir)
54088
+ return;
54089
+ try {
54090
+ const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
54091
+ mkdirSync11(dirname8(versionFile), { recursive: true });
54092
+ writeFileSync9(versionFile, version2);
54093
+ } catch {}
54094
+ }
53846
54095
  async function readWarnedTools(bridge) {
53847
54096
  try {
53848
54097
  const resp = await bridge.send("db_get_state", { key: "warned_tools" });
@@ -53909,6 +54158,9 @@ function formatConfigureWarning(warning) {
53909
54158
  ${warning.hint}`;
53910
54159
  }
53911
54160
  async function deliverConfigureWarnings(opts, warnings) {
54161
+ if (opts.projectRoot) {
54162
+ await flushPendingDesktopNotifications({ client: opts.client, directory: opts.projectRoot }, opts.sessionId);
54163
+ }
53912
54164
  if (warnings.length === 0)
53913
54165
  return;
53914
54166
  for (const warning of warnings) {
@@ -53924,10 +54176,10 @@ async function deliverConfigureWarnings(opts, warnings) {
53924
54176
  async function cleanupWarnings(opts) {
53925
54177
  if (isTuiMode())
53926
54178
  return;
53927
- const { sessionId, serverUrl: desktopServerUrl } = readDesktopState(opts.directory);
54179
+ const sessionId = getExplicitSessionId(opts);
53928
54180
  if (!sessionId)
53929
54181
  return;
53930
- const effectiveServerUrl = opts.serverUrl || desktopServerUrl;
54182
+ const effectiveServerUrl = opts.serverUrl || readDesktopState().serverUrl;
53931
54183
  if (!effectiveServerUrl)
53932
54184
  return;
53933
54185
  const messages = await getSessionMessages(opts.client, sessionId);
@@ -54061,10 +54313,10 @@ function writeTerminal(terminal, data) {
54061
54313
  }
54062
54314
 
54063
54315
  // src/shared/rpc-server.ts
54064
- import { randomBytes as randomBytes2 } from "node:crypto";
54065
- import { mkdirSync as mkdirSync11, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
54316
+ import { randomBytes as randomBytes3 } from "node:crypto";
54317
+ import { mkdirSync as mkdirSync12, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
54066
54318
  import { createServer } from "node:http";
54067
- import { dirname as dirname7, join as join19 } from "node:path";
54319
+ import { dirname as dirname9, join as join19 } from "node:path";
54068
54320
 
54069
54321
  // src/shared/rpc-utils.ts
54070
54322
  import { createHash as createHash7 } from "node:crypto";
@@ -54089,7 +54341,7 @@ class AftRpcServer {
54089
54341
  instanceId;
54090
54342
  constructor(storageDir, directory) {
54091
54343
  this.portsDir = rpcPortFileDir(storageDir, directory);
54092
- this.instanceId = randomBytes2(8).toString("hex");
54344
+ this.instanceId = randomBytes3(8).toString("hex");
54093
54345
  this.portFilePath = join19(this.portsDir, `${this.instanceId}.json`);
54094
54346
  }
54095
54347
  handle(method, handler) {
@@ -54109,17 +54361,17 @@ class AftRpcServer {
54109
54361
  return;
54110
54362
  }
54111
54363
  this.port = addr.port;
54112
- this.token = randomBytes2(32).toString("hex");
54364
+ this.token = randomBytes3(32).toString("hex");
54113
54365
  this.server = server;
54114
54366
  try {
54115
- const dir = dirname7(this.portFilePath);
54116
- mkdirSync11(dir, { recursive: true, mode: 448 });
54367
+ const dir = dirname9(this.portFilePath);
54368
+ mkdirSync12(dir, { recursive: true, mode: 448 });
54117
54369
  const tmpPath = `${this.portFilePath}.tmp`;
54118
54370
  writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
54119
54371
  encoding: "utf-8",
54120
54372
  mode: 384
54121
54373
  });
54122
- renameSync7(tmpPath, this.portFilePath);
54374
+ renameSync8(tmpPath, this.portFilePath);
54123
54375
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
54124
54376
  } catch (err) {
54125
54377
  warn2(`Failed to write RPC port file: ${err}`);
@@ -54428,8 +54680,8 @@ function formatStatusMarkdown(status) {
54428
54680
 
54429
54681
  // src/shared/tui-config.ts
54430
54682
  var import_comment_json4 = __toESM(require_src2(), 1);
54431
- import { existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "node:fs";
54432
- import { dirname as dirname8, join as join21 } from "node:path";
54683
+ import { existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
54684
+ import { dirname as dirname10, join as join21 } from "node:path";
54433
54685
 
54434
54686
  // src/shared/opencode-config-dir.ts
54435
54687
  import { homedir as homedir10 } from "node:os";
@@ -54465,9 +54717,9 @@ function resolveTuiConfigPath() {
54465
54717
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
54466
54718
  const jsoncPath = join21(configDir, "tui.jsonc");
54467
54719
  const jsonPath = join21(configDir, "tui.json");
54468
- if (existsSync13(jsoncPath))
54720
+ if (existsSync14(jsoncPath))
54469
54721
  return jsoncPath;
54470
- if (existsSync13(jsonPath))
54722
+ if (existsSync14(jsonPath))
54471
54723
  return jsonPath;
54472
54724
  return jsonPath;
54473
54725
  }
@@ -54475,8 +54727,8 @@ function ensureTuiPluginEntry() {
54475
54727
  try {
54476
54728
  const configPath = resolveTuiConfigPath();
54477
54729
  let config2 = {};
54478
- if (existsSync13(configPath)) {
54479
- config2 = import_comment_json4.parse(readFileSync12(configPath, "utf-8")) ?? {};
54730
+ if (existsSync14(configPath)) {
54731
+ config2 = import_comment_json4.parse(readFileSync13(configPath, "utf-8")) ?? {};
54480
54732
  }
54481
54733
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
54482
54734
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -54484,7 +54736,7 @@ function ensureTuiPluginEntry() {
54484
54736
  }
54485
54737
  plugins.push(PLUGIN_ENTRY);
54486
54738
  config2.plugin = plugins;
54487
- mkdirSync12(dirname8(configPath), { recursive: true });
54739
+ mkdirSync13(dirname10(configPath), { recursive: true });
54488
54740
  writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
54489
54741
  `);
54490
54742
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
@@ -55538,6 +55790,10 @@ function createBashTool(ctx) {
55538
55790
  return rendered2;
55539
55791
  }
55540
55792
  if (Date.now() - startedAt >= waitTimeoutMs) {
55793
+ if (subagentForcedForeground) {
55794
+ await sleep(FOREGROUND_POLL_INTERVAL_MS);
55795
+ continue;
55796
+ }
55541
55797
  const promoted = await callBridge(ctx, context, "bash_promote", { task_id: taskId });
55542
55798
  if (promoted.success === false) {
55543
55799
  throw new Error(promoted.message ?? "bash_promote failed");
@@ -55770,12 +56026,13 @@ var BASH_WAIT_POLL_INTERVAL_MS = 100;
55770
56026
  var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
55771
56027
  var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
55772
56028
  var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
56029
+ var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
55773
56030
  function createBashWatchTool(ctx) {
55774
56031
  return {
55775
56032
  description: "Block on a background bash task until a pattern matches, it exits, or timeout elapses; or register an async pattern notification with background:true.",
55776
56033
  args: {
55777
56034
  taskId: z5.string().describe("Background task ID returned by bash({ background: true })."),
55778
- pattern: z5.union([z5.string(), z5.object({ regex: z5.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true."),
56035
+ pattern: z5.union([z5.string(), z5.object({ regex: z5.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true. Sync substring watches keep only the overlap tail needed for boundary matches; sync regex watches use a 64 KB rolling output window."),
55779
56036
  background: z5.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
55780
56037
  timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 300000."),
55781
56038
  once: z5.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
@@ -55867,7 +56124,7 @@ async function bashStatusSnapshot2(ctx, runtime, taskId, outputMode, options) {
55867
56124
  async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effectiveWaitMs) {
55868
56125
  const startedAt = Date.now();
55869
56126
  const deadline = startedAt + effectiveWaitMs;
55870
- let spillCursor = 0;
56127
+ let spillCursor = { output: 0, stderr: 0, combined: 0 };
55871
56128
  let scanText = "";
55872
56129
  let scanBaseOffset = 0;
55873
56130
  const bridgeOptions = {
@@ -55875,14 +56132,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55875
56132
  transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2
55876
56133
  };
55877
56134
  markTaskWaiting(runtime.sessionID, taskId);
56135
+ let sawTerminal = false;
55878
56136
  try {
55879
56137
  while (true) {
55880
56138
  const data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, bridgeOptions);
55881
- if (isTerminalStatus2(data.status)) {
55882
- consumeBgCompletion(runtime.sessionID, taskId);
55883
- await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
55884
- return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
55885
- }
56139
+ const terminal = isTerminalStatus2(data.status);
55886
56140
  if (waitFor) {
55887
56141
  const scan = await readNewTaskOutput(runtime, taskId, data, spillCursor);
55888
56142
  if (scan) {
@@ -55892,7 +56146,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55892
56146
  scanText += scan.text;
55893
56147
  const match = findWaitMatch(scanText, waitFor);
55894
56148
  if (match) {
55895
- unmarkTaskWaiting(runtime.sessionID, taskId);
56149
+ if (terminal) {
56150
+ sawTerminal = true;
56151
+ consumeBgCompletion(runtime.sessionID, taskId);
56152
+ await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
56153
+ }
55896
56154
  return withWaited(data, {
55897
56155
  reason: "matched",
55898
56156
  elapsed_ms: Date.now() - startedAt,
@@ -55900,31 +56158,61 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55900
56158
  match_offset: scanBaseOffset + Buffer.byteLength(scanText.slice(0, match.index), "utf8")
55901
56159
  });
55902
56160
  }
56161
+ const trimmed = trimWaitScanBuffer(scanText, scanBaseOffset, waitFor);
56162
+ scanText = trimmed.text;
56163
+ scanBaseOffset = trimmed.baseOffset;
55903
56164
  }
55904
56165
  }
56166
+ if (terminal) {
56167
+ sawTerminal = true;
56168
+ consumeBgCompletion(runtime.sessionID, taskId);
56169
+ await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
56170
+ return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
56171
+ }
55905
56172
  if (Date.now() >= deadline) {
55906
- unmarkTaskWaiting(runtime.sessionID, taskId);
55907
56173
  return withWaited(data, { reason: "timeout", elapsed_ms: Date.now() - startedAt });
55908
56174
  }
55909
56175
  await sleep2(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
55910
56176
  }
55911
- } catch (err) {
55912
- unmarkTaskWaiting(runtime.sessionID, taskId);
55913
- throw err;
56177
+ } finally {
56178
+ if (!sawTerminal)
56179
+ unmarkTaskWaiting(runtime.sessionID, taskId);
56180
+ await disposePtyTerminal(watchPtyCacheKey(runtime, taskId));
55914
56181
  }
55915
56182
  }
55916
56183
  async function readNewTaskOutput(runtime, taskId, data, cursor) {
55917
56184
  const outputPath = data.output_path;
55918
- if (!outputPath)
55919
- return;
55920
56185
  if (data.mode === "pty") {
55921
- const state = await getOrCreatePtyTerminal(ptyCacheKey2(runtime, taskId), outputPath);
56186
+ if (!outputPath)
56187
+ return;
56188
+ const state = await getOrCreatePtyTerminal(watchPtyCacheKey(runtime, taskId), outputPath);
55922
56189
  const baseOffset = state.offset;
55923
- const bytes2 = await readPtyBytes(state);
55924
- return { text: bytes2.toString("utf8"), baseOffset, nextCursor: state.offset };
56190
+ const bytes = await readPtyBytes(state);
56191
+ if (bytes.length === 0)
56192
+ return;
56193
+ return {
56194
+ text: bytes.toString("utf8"),
56195
+ baseOffset,
56196
+ nextCursor: { output: state.offset, stderr: 0, combined: state.offset }
56197
+ };
55925
56198
  }
55926
- const bytes = await readFileBytesFrom(outputPath, cursor);
55927
- return { text: bytes.toString("utf8"), baseOffset: cursor, nextCursor: cursor + bytes.length };
56199
+ const stderrPath = data.stderr_path;
56200
+ if (!outputPath && !stderrPath)
56201
+ return;
56202
+ const stdoutBytes = outputPath ? await readFileBytesFrom(outputPath, cursor.output) : Buffer.alloc(0);
56203
+ const stderrBytes = stderrPath ? await readFileBytesFrom(stderrPath, cursor.stderr) : Buffer.alloc(0);
56204
+ const bytesRead = stdoutBytes.length + stderrBytes.length;
56205
+ if (bytesRead === 0)
56206
+ return;
56207
+ return {
56208
+ text: Buffer.concat([stdoutBytes, stderrBytes]).toString("utf8"),
56209
+ baseOffset: cursor.combined,
56210
+ nextCursor: {
56211
+ output: cursor.output + stdoutBytes.length,
56212
+ stderr: cursor.stderr + stderrBytes.length,
56213
+ combined: cursor.combined + bytesRead
56214
+ }
56215
+ };
55928
56216
  }
55929
56217
  async function readFileBytesFrom(outputPath, cursor) {
55930
56218
  const handle = await fs5.open(outputPath, "r");
@@ -55965,6 +56253,34 @@ function findWaitMatch(text, pattern) {
55965
56253
  const match = pattern.value.exec(text);
55966
56254
  return match ? { text: match[0], index: match.index } : undefined;
55967
56255
  }
56256
+ function trimWaitScanBuffer(text, baseOffset, pattern) {
56257
+ const keepFrom = pattern.kind === "substring" ? substringKeepStart(text, pattern.value) : regexKeepStart(text, REGEX_WAIT_SCAN_WINDOW_BYTES);
56258
+ if (keepFrom <= 0)
56259
+ return { text, baseOffset };
56260
+ return {
56261
+ text: text.slice(keepFrom),
56262
+ baseOffset: baseOffset + Buffer.byteLength(text.slice(0, keepFrom), "utf8")
56263
+ };
56264
+ }
56265
+ function substringKeepStart(text, pattern) {
56266
+ const keepChars = Math.max(0, pattern.length - 1);
56267
+ return text.length > keepChars ? text.length - keepChars : 0;
56268
+ }
56269
+ function regexKeepStart(text, maxBytes) {
56270
+ if (Buffer.byteLength(text, "utf8") <= maxBytes)
56271
+ return 0;
56272
+ let low = 0;
56273
+ let high = text.length;
56274
+ while (low < high) {
56275
+ const mid = Math.floor((low + high) / 2);
56276
+ if (Buffer.byteLength(text.slice(mid), "utf8") > maxBytes) {
56277
+ low = mid + 1;
56278
+ } else {
56279
+ high = mid;
56280
+ }
56281
+ }
56282
+ return low;
56283
+ }
55968
56284
  function withWaited(data, waited) {
55969
56285
  return { ...data, waited };
55970
56286
  }
@@ -55974,6 +56290,9 @@ function isTerminalStatus2(status) {
55974
56290
  function ptyCacheKey2(runtime, taskId) {
55975
56291
  return `${projectRootFor(runtime)}::${runtime.sessionID ?? "__default__"}::${taskId}`;
55976
56292
  }
56293
+ function watchPtyCacheKey(runtime, taskId) {
56294
+ return `${ptyCacheKey2(runtime, taskId)}::watch`;
56295
+ }
55977
56296
  function sleep2(ms) {
55978
56297
  return new Promise((resolve7) => setTimeout(resolve7, ms));
55979
56298
  }
@@ -57816,6 +58135,39 @@ function normalizeGlob(pattern) {
57816
58135
  }
57817
58136
  return pattern;
57818
58137
  }
58138
+ function absoluteSearchPath(context, target) {
58139
+ return path6.isAbsolute(target) ? target : path6.resolve(context.directory, target);
58140
+ }
58141
+ function searchPathExists(context, target) {
58142
+ return fs7.existsSync(absoluteSearchPath(context, target));
58143
+ }
58144
+ function splitSearchPathArg(context, raw) {
58145
+ if (searchPathExists(context, raw) || !/\s/.test(raw)) {
58146
+ return [raw];
58147
+ }
58148
+ const fragments = raw.trim().split(/\s+/).filter(Boolean);
58149
+ if (fragments.length < 2 || !fragments.every((fragment) => searchPathExists(context, fragment))) {
58150
+ return [raw];
58151
+ }
58152
+ return fragments;
58153
+ }
58154
+ function searchPathKind(context, target, defaultKind) {
58155
+ try {
58156
+ const stat = fs7.lstatSync(absoluteSearchPath(context, target));
58157
+ if (defaultKind === "file") {
58158
+ return stat.isDirectory() ? "directory" : "file";
58159
+ }
58160
+ return stat.isFile() ? "file" : "directory";
58161
+ } catch {
58162
+ return defaultKind;
58163
+ }
58164
+ }
58165
+ function searchPathTargets(context, raw, defaultKind) {
58166
+ return splitSearchPathArg(context, raw).map((target) => ({
58167
+ target,
58168
+ kind: searchPathKind(context, target, defaultKind)
58169
+ }));
58170
+ }
57819
58171
  function splitIncludeArg(raw) {
57820
58172
  const out = [];
57821
58173
  let depth = 0;
@@ -57865,17 +58217,13 @@ function searchTools(ctx) {
57865
58217
  if (grepDenied)
57866
58218
  return permissionDeniedResponse(grepDenied);
57867
58219
  if (pathArg) {
57868
- let kind = "file";
57869
- try {
57870
- const abs = path6.isAbsolute(pathArg) ? pathArg : path6.resolve(context.directory, pathArg);
57871
- if (fs7.lstatSync(abs).isDirectory())
57872
- kind = "directory";
57873
- } catch {}
57874
- const externalDenied = await assertExternalDirectoryPermission(context, pathArg, {
57875
- kind
57876
- });
57877
- if (externalDenied)
57878
- return permissionDeniedResponse(externalDenied);
58220
+ for (const target of searchPathTargets(context, pathArg, "file")) {
58221
+ const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
58222
+ kind: target.kind
58223
+ });
58224
+ if (externalDenied)
58225
+ return permissionDeniedResponse(externalDenied);
58226
+ }
57879
58227
  }
57880
58228
  const response = await callBridge(ctx, context, "grep", {
57881
58229
  pattern,
@@ -57907,23 +58255,22 @@ function searchTools(ctx) {
57907
58255
  globPath = globPattern.slice(0, lastSlash);
57908
58256
  globPattern = `**/${globPattern.slice(lastSlash + 1)}`;
57909
58257
  }
58258
+ } else if (metaIdx === -1) {
58259
+ globPath = path6.dirname(globPattern);
58260
+ globPattern = path6.basename(globPattern);
57910
58261
  }
57911
58262
  }
57912
58263
  const globDenied = await askGlobPermission(context, globPattern, { path: globPath });
57913
58264
  if (globDenied)
57914
58265
  return permissionDeniedResponse(globDenied);
57915
58266
  if (globPath) {
57916
- let kind = "directory";
57917
- try {
57918
- const abs = path6.isAbsolute(globPath) ? globPath : path6.resolve(context.directory, globPath);
57919
- if (fs7.lstatSync(abs).isFile())
57920
- kind = "file";
57921
- } catch {}
57922
- const externalDenied = await assertExternalDirectoryPermission(context, globPath, {
57923
- kind
57924
- });
57925
- if (externalDenied)
57926
- return permissionDeniedResponse(externalDenied);
58267
+ for (const target of searchPathTargets(context, globPath, "directory")) {
58268
+ const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
58269
+ kind: target.kind
58270
+ });
58271
+ if (externalDenied)
58272
+ return permissionDeniedResponse(externalDenied);
58273
+ }
57927
58274
  }
57928
58275
  const response = await callBridge(ctx, context, "glob", {
57929
58276
  pattern: globPattern,
@@ -58507,10 +58854,10 @@ ${lines}
58507
58854
  return { show: false };
58508
58855
  }
58509
58856
  if (storageDir) {
58510
- const versionFile = join23(storageDir, "last_announced_version");
58511
58857
  try {
58512
- if (existsSync15(versionFile)) {
58513
- const lastVersion = readFileSync13(versionFile, "utf-8").trim();
58858
+ const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
58859
+ if (existsSync17(versionFile)) {
58860
+ const lastVersion = readFileSync14(versionFile, "utf-8").trim();
58514
58861
  if (lastVersion === ANNOUNCEMENT_VERSION)
58515
58862
  return { show: false };
58516
58863
  }
@@ -58526,8 +58873,9 @@ ${lines}
58526
58873
  rpcServer.handle("mark-announced", async () => {
58527
58874
  if (storageDir && ANNOUNCEMENT_VERSION) {
58528
58875
  try {
58529
- mkdirSync13(storageDir, { recursive: true });
58530
- writeFileSync12(join23(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
58876
+ const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
58877
+ mkdirSync14(dirname14(versionFile), { recursive: true });
58878
+ writeFileSync12(versionFile, ANNOUNCEMENT_VERSION);
58531
58879
  } catch {}
58532
58880
  }
58533
58881
  return { success: true };