@cortexkit/aft-opencode 0.30.0 → 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.
Files changed (38) hide show
  1. package/dist/bg-notifications.d.ts +2 -0
  2. package/dist/bg-notifications.d.ts.map +1 -1
  3. package/dist/hooks/auto-update-checker/cache.d.ts +5 -16
  4. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  5. package/dist/hooks/auto-update-checker/index.d.ts +1 -1
  6. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1005 -632
  9. package/dist/notifications.d.ts +8 -5
  10. package/dist/notifications.d.ts.map +1 -1
  11. package/dist/shared/last-assistant-model.d.ts +5 -6
  12. package/dist/shared/last-assistant-model.d.ts.map +1 -1
  13. package/dist/shared/live-server-client.d.ts +24 -22
  14. package/dist/shared/live-server-client.d.ts.map +1 -1
  15. package/dist/shared/rpc-client.d.ts +10 -3
  16. package/dist/shared/rpc-client.d.ts.map +1 -1
  17. package/dist/shared/storage-paths.d.ts +2 -0
  18. package/dist/shared/storage-paths.d.ts.map +1 -0
  19. package/dist/tools/_shared.d.ts +39 -0
  20. package/dist/tools/_shared.d.ts.map +1 -1
  21. package/dist/tools/ast.d.ts.map +1 -1
  22. package/dist/tools/bash.d.ts.map +1 -1
  23. package/dist/tools/bash_watch.d.ts +4 -0
  24. package/dist/tools/bash_watch.d.ts.map +1 -1
  25. package/dist/tools/hoisted.d.ts.map +1 -1
  26. package/dist/tools/lsp.d.ts.map +1 -1
  27. package/dist/tools/navigation.d.ts.map +1 -1
  28. package/dist/tools/reading.d.ts.map +1 -1
  29. package/dist/tools/search.d.ts.map +1 -1
  30. package/dist/tools/semantic.d.ts.map +1 -1
  31. package/dist/tui.js +222 -123
  32. package/package.json +8 -8
  33. package/src/shared/last-assistant-model.ts +9 -27
  34. package/src/shared/live-server-client.ts +80 -48
  35. package/src/shared/rpc-client.ts +132 -28
  36. package/src/shared/storage-paths.ts +22 -0
  37. package/src/tui/index.tsx +39 -17
  38. package/src/tui/sidebar.tsx +94 -27
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
  }
@@ -51714,15 +51876,25 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
51714
51876
  return null;
51715
51877
  }
51716
51878
  }
51879
+ var STDERR_TAIL_BYTES = 16 * 1024;
51717
51880
  async function runNpmInstallSafe(installDir, options = {}) {
51718
51881
  let timeout = null;
51882
+ let stderrTail = "";
51719
51883
  try {
51720
51884
  if (options.signal?.aborted)
51721
- return false;
51722
- const proc = spawn2("npm", ["install", "--no-audit", "--no-fund", "--no-progress"], {
51885
+ return { ok: false, reason: "aborted" };
51886
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
51887
+ const proc = spawn2(npmBin, ["install", "--no-audit", "--no-fund", "--no-progress", "--ignore-scripts"], {
51723
51888
  cwd: installDir,
51724
- stdio: "ignore"
51889
+ stdio: ["ignore", "pipe", "pipe"]
51725
51890
  });
51891
+ proc.stderr?.on("data", (chunk) => {
51892
+ stderrTail += chunk.toString("utf8");
51893
+ if (stderrTail.length > STDERR_TAIL_BYTES) {
51894
+ stderrTail = stderrTail.slice(-STDERR_TAIL_BYTES);
51895
+ }
51896
+ });
51897
+ proc.stdout?.on("data", () => {});
51726
51898
  const abortProcess = () => {
51727
51899
  try {
51728
51900
  proc.kill();
@@ -51730,8 +51902,8 @@ async function runNpmInstallSafe(installDir, options = {}) {
51730
51902
  };
51731
51903
  options.signal?.addEventListener("abort", abortProcess, { once: true });
51732
51904
  const exitPromise = new Promise((resolveExit) => {
51733
- proc.on("error", () => resolveExit(false));
51734
- proc.on("exit", (code) => resolveExit(code === 0));
51905
+ proc.on("error", (err) => resolveExit({ ok: false, reason: `spawn error: ${String(err)}` }));
51906
+ proc.on("exit", (code) => resolveExit(code === 0 ? { ok: true } : { ok: false, reason: `npm install exited with code ${code ?? "signal/unknown"}` }));
51735
51907
  });
51736
51908
  const timeoutPromise = new Promise((resolveTimeout) => {
51737
51909
  timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
@@ -51745,29 +51917,41 @@ async function runNpmInstallSafe(installDir, options = {}) {
51745
51917
  pendingSnapshots.delete(installDir);
51746
51918
  restoreAutoUpdateSnapshot(snapshot2);
51747
51919
  }
51748
- return false;
51920
+ const reason = options.signal?.aborted ? "aborted" : "timeout";
51921
+ warnNpmInstallFailure(reason, stderrTail);
51922
+ return { ok: false, reason, stderrTail: stderrTail || undefined };
51749
51923
  }
51750
51924
  const snapshot = pendingSnapshots.get(installDir);
51751
51925
  pendingSnapshots.delete(installDir);
51752
- if (!result && snapshot) {
51926
+ if (!result.ok && snapshot) {
51753
51927
  restoreAutoUpdateSnapshot(snapshot);
51754
51928
  } else if (snapshot) {
51755
51929
  rmSync3(snapshot.tempDir, { recursive: true, force: true });
51756
51930
  }
51757
- return result;
51931
+ if (!result.ok) {
51932
+ warnNpmInstallFailure(result.reason ?? "npm install failed", stderrTail);
51933
+ }
51934
+ return { ...result, stderrTail: stderrTail || undefined };
51758
51935
  } catch (err) {
51759
51936
  const snapshot = pendingSnapshots.get(installDir);
51760
51937
  if (snapshot) {
51761
51938
  pendingSnapshots.delete(installDir);
51762
51939
  restoreAutoUpdateSnapshot(snapshot);
51763
51940
  }
51764
- warn2(`[auto-update-checker] npm install error: ${String(err)}`);
51765
- return false;
51941
+ const reason = `exception: ${String(err)}`;
51942
+ warnNpmInstallFailure(reason, stderrTail);
51943
+ return { ok: false, reason, stderrTail: stderrTail || undefined };
51766
51944
  } finally {
51767
51945
  if (timeout)
51768
51946
  clearTimeout(timeout);
51769
51947
  }
51770
51948
  }
51949
+ function warnNpmInstallFailure(reason, stderrTail) {
51950
+ const tail2 = stderrTail ? `
51951
+ stderr tail:
51952
+ ${stderrTail}` : "";
51953
+ warn2(`[auto-update-checker] npm install failed (${reason})${tail2}`);
51954
+ }
51771
51955
 
51772
51956
  // src/hooks/auto-update-checker/index.ts
51773
51957
  var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
@@ -51813,36 +51997,76 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
51813
51997
  async function maybeRunCheck(ctx, options) {
51814
51998
  if (options.signal.aborted)
51815
51999
  return;
51816
- if (!claimCheckSlot(options.storageDir, options.checkIntervalMs)) {
52000
+ const checkSlot = claimCheckSlot(options.storageDir, options.checkIntervalMs);
52001
+ if (!checkSlot) {
51817
52002
  log2("[auto-update-checker] Skipping check (another instance ran one recently)");
51818
52003
  return;
51819
52004
  }
51820
- await runStartupCheck(ctx, options);
52005
+ try {
52006
+ await runStartupCheck(ctx, options);
52007
+ } finally {
52008
+ checkSlot.release();
52009
+ }
51821
52010
  }
51822
52011
  function claimCheckSlot(storageDir, intervalMs) {
51823
52012
  if (!storageDir)
51824
- return true;
52013
+ return { release: () => {} };
52014
+ const file2 = repairRootScopedStorageFile(storageDir, "opencode", TIMESTAMP_FILENAME);
51825
52015
  try {
51826
- const file2 = join12(storageDir, TIMESTAMP_FILENAME);
51827
- if (existsSync9(file2)) {
51828
- try {
51829
- const raw = JSON.parse(readFileSync6(file2, "utf-8"));
51830
- const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
51831
- if (Number.isFinite(last) && Date.now() - last < intervalMs) {
51832
- return false;
51833
- }
51834
- } 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;
51835
52048
  }
51836
- mkdirSync6(dirname5(file2), { recursive: true });
51837
- const tmp = `${file2}.tmp.${process.pid}`;
51838
- writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
51839
- renameSync4(tmp, file2);
51840
- return true;
51841
52049
  } catch (err) {
51842
52050
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
51843
- return true;
52051
+ return null;
51844
52052
  }
51845
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
+ }
51846
52070
  async function runStartupCheck(ctx, options) {
51847
52071
  if (options.signal.aborted)
51848
52072
  return;
@@ -51907,32 +52131,39 @@ async function runBackgroundUpdateCheck(ctx, options) {
51907
52131
  warn2("[auto-update-checker] Failed to prepare install root for auto-update");
51908
52132
  return;
51909
52133
  }
51910
- const installSuccess = await runNpmInstallSafe(installDir, { signal: options.signal });
51911
- if (installSuccess) {
52134
+ const installResult = await runNpmInstallSafe(installDir, { signal: options.signal });
52135
+ if (installResult.ok) {
51912
52136
  showToast(ctx, "AFT Updated!", `v${currentVersion} → v${latestVersion}
51913
52137
  Restart OpenCode to apply.`, "success", 8000);
51914
52138
  log2(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
51915
52139
  return;
51916
52140
  }
51917
52141
  showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
51918
- warn2("[auto-update-checker] npm install failed; update not installed");
52142
+ const failureDetail = installResult.reason ? `: ${installResult.reason}` : "";
52143
+ const stderrDetail = installResult.stderrTail ? `
52144
+ stderr tail:
52145
+ ${installResult.stderrTail}` : "";
52146
+ warn2(`[auto-update-checker] npm install failed; update not installed${failureDetail}${stderrDetail}`);
51919
52147
  }
51920
52148
  function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
51921
- 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(() => {});
51922
52153
  }
51923
52154
 
51924
52155
  // src/lsp-auto-install.ts
51925
52156
  import { spawn as spawn3 } from "node:child_process";
51926
52157
  import { createHash as createHash5 } from "node:crypto";
51927
- 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";
51928
52159
  import { join as join15 } from "node:path";
51929
52160
 
51930
52161
  // src/lsp-cache.ts
51931
52162
  import {
51932
- closeSync as closeSync3,
51933
- mkdirSync as mkdirSync7,
51934
- openSync as openSync3,
51935
- readFileSync as readFileSync7,
52163
+ closeSync as closeSync4,
52164
+ mkdirSync as mkdirSync8,
52165
+ openSync as openSync4,
52166
+ readFileSync as readFileSync8,
51936
52167
  statSync as statSync4,
51937
52168
  unlinkSync as unlinkSync5,
51938
52169
  writeFileSync as writeFileSync7
@@ -51980,7 +52211,7 @@ function lspBinaryCandidates(binary) {
51980
52211
  var INSTALLED_META_FILE = ".aft-installed";
51981
52212
  function writeInstalledMetaIn(installDir, version2, sha256) {
51982
52213
  try {
51983
- mkdirSync7(installDir, { recursive: true });
52214
+ mkdirSync8(installDir, { recursive: true });
51984
52215
  const meta3 = {
51985
52216
  version: version2,
51986
52217
  installedAt: new Date().toISOString(),
@@ -51996,7 +52227,7 @@ function readInstalledMetaIn(installDir) {
51996
52227
  try {
51997
52228
  if (!statSync4(path2).isFile())
51998
52229
  return null;
51999
- const raw = readFileSync7(path2, "utf8");
52230
+ const raw = readFileSync8(path2, "utf8");
52000
52231
  const parsed = JSON.parse(raw);
52001
52232
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
52002
52233
  return null;
@@ -52020,17 +52251,17 @@ function lockPath(npmPackage) {
52020
52251
  }
52021
52252
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
52022
52253
  function acquireInstallLock(lockKey) {
52023
- mkdirSync7(lspPackageDir(lockKey), { recursive: true });
52254
+ mkdirSync8(lspPackageDir(lockKey), { recursive: true });
52024
52255
  const lock = lockPath(lockKey);
52025
52256
  const tryClaim = () => {
52026
52257
  try {
52027
- const fd = openSync3(lock, "wx");
52258
+ const fd = openSync4(lock, "wx");
52028
52259
  try {
52029
52260
  writeFileSync7(fd, `${process.pid}
52030
52261
  ${new Date().toISOString()}
52031
52262
  `);
52032
52263
  } finally {
52033
- closeSync3(fd);
52264
+ closeSync4(fd);
52034
52265
  }
52035
52266
  return true;
52036
52267
  } catch (err) {
@@ -52046,7 +52277,7 @@ ${new Date().toISOString()}
52046
52277
  let owningPid = null;
52047
52278
  let lockMtimeMs = 0;
52048
52279
  try {
52049
- const raw = readFileSync7(lock, "utf8");
52280
+ const raw = readFileSync8(lock, "utf8");
52050
52281
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
52051
52282
  const parsed = Number.parseInt(firstLine, 10);
52052
52283
  if (Number.isFinite(parsed) && parsed > 0)
@@ -52084,7 +52315,7 @@ function releaseInstallLock(lockKey) {
52084
52315
  try {
52085
52316
  let owningPid = null;
52086
52317
  try {
52087
- const raw = readFileSync7(lock, "utf8");
52318
+ const raw = readFileSync8(lock, "utf8");
52088
52319
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
52089
52320
  const parsed = Number.parseInt(firstLine, 10);
52090
52321
  if (Number.isFinite(parsed) && parsed > 0)
@@ -52125,7 +52356,7 @@ var VERSION_CHECK_FILE = ".aft-version-check";
52125
52356
  function readVersionCheck(npmPackage) {
52126
52357
  const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
52127
52358
  try {
52128
- const raw = readFileSync7(file2, "utf8");
52359
+ const raw = readFileSync8(file2, "utf8");
52129
52360
  const parsed = JSON.parse(raw);
52130
52361
  if (typeof parsed.last_checked === "string") {
52131
52362
  return {
@@ -52139,7 +52370,7 @@ function readVersionCheck(npmPackage) {
52139
52370
  }
52140
52371
  }
52141
52372
  function writeVersionCheck(npmPackage, latest) {
52142
- mkdirSync7(lspPackageDir(npmPackage), { recursive: true });
52373
+ mkdirSync8(lspPackageDir(npmPackage), { recursive: true });
52143
52374
  const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
52144
52375
  const record2 = {
52145
52376
  last_checked: new Date().toISOString(),
@@ -52302,7 +52533,7 @@ var NPM_LSP_TABLE = [
52302
52533
  ];
52303
52534
 
52304
52535
  // src/lsp-project-relevance.ts
52305
- 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";
52306
52537
  import { join as join14 } from "node:path";
52307
52538
  var MAX_WALK_DIRS = 200;
52308
52539
  var MAX_WALK_DEPTH = 4;
@@ -52320,7 +52551,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
52320
52551
  if (!rootMarkers)
52321
52552
  return false;
52322
52553
  for (const marker of rootMarkers) {
52323
- if (existsSync10(join14(projectRoot, marker)))
52554
+ if (existsSync11(join14(projectRoot, marker)))
52324
52555
  return true;
52325
52556
  }
52326
52557
  return false;
@@ -52344,7 +52575,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
52344
52575
  }
52345
52576
  function readPackageJson(projectRoot) {
52346
52577
  try {
52347
- const raw = readFileSync8(join14(projectRoot, "package.json"), "utf8");
52578
+ const raw = readFileSync9(join14(projectRoot, "package.json"), "utf8");
52348
52579
  const parsed = JSON.parse(raw);
52349
52580
  if (typeof parsed !== "object" || parsed === null)
52350
52581
  return null;
@@ -52671,16 +52902,16 @@ function installedBinaryPath(spec) {
52671
52902
  return null;
52672
52903
  }
52673
52904
  function sha256OfFileSync(path2) {
52674
- return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
52905
+ return createHash5("sha256").update(readFileSync10(path2)).digest("hex");
52675
52906
  }
52676
52907
  function quarantineCachedNpmInstall(spec, reason) {
52677
52908
  const packageDir = lspPackageDir(spec.npm);
52678
52909
  const dest = join15(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
52679
52910
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
52680
52911
  try {
52681
- mkdirSync8(join15(dest, ".."), { recursive: true });
52682
- rmSync4(dest, { recursive: true, force: true });
52683
- renameSync5(packageDir, dest);
52912
+ mkdirSync9(join15(dest, ".."), { recursive: true });
52913
+ rmSync5(dest, { recursive: true, force: true });
52914
+ renameSync6(packageDir, dest);
52684
52915
  } catch (err) {
52685
52916
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
52686
52917
  }
@@ -52756,25 +52987,25 @@ function runAutoInstall(projectRoot, config2, fetchImpl2 = fetch) {
52756
52987
 
52757
52988
  // src/lsp-github-install.ts
52758
52989
  import { execFileSync as execFileSync2 } from "node:child_process";
52759
- import { createHash as createHash6, randomBytes } from "node:crypto";
52990
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
52760
52991
  import {
52761
52992
  copyFileSync as copyFileSync3,
52762
52993
  createReadStream as createReadStream2,
52763
52994
  createWriteStream as createWriteStream3,
52764
- existsSync as existsSync11,
52995
+ existsSync as existsSync12,
52765
52996
  lstatSync as lstatSync2,
52766
- mkdirSync as mkdirSync9,
52997
+ mkdirSync as mkdirSync10,
52767
52998
  readdirSync as readdirSync4,
52768
- readFileSync as readFileSync10,
52999
+ readFileSync as readFileSync11,
52769
53000
  readlinkSync as readlinkSync2,
52770
53001
  realpathSync as realpathSync3,
52771
- renameSync as renameSync6,
52772
- rmSync as rmSync5,
53002
+ renameSync as renameSync7,
53003
+ rmSync as rmSync6,
52773
53004
  statSync as statSync6,
52774
53005
  unlinkSync as unlinkSync6,
52775
53006
  writeFileSync as writeFileSync8
52776
53007
  } from "node:fs";
52777
- 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";
52778
53009
  import { Readable as Readable3 } from "node:stream";
52779
53010
  import { pipeline as pipeline3 } from "node:stream/promises";
52780
53011
 
@@ -52899,7 +53130,7 @@ function readGithubInstalledMetaIn(installDir) {
52899
53130
  const path2 = join16(installDir, INSTALLED_META_FILE2);
52900
53131
  if (!statSync6(path2).isFile())
52901
53132
  return null;
52902
- const parsed = JSON.parse(readFileSync10(path2, "utf8"));
53133
+ const parsed = JSON.parse(readFileSync11(path2, "utf8"));
52903
53134
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
52904
53135
  return null;
52905
53136
  return {
@@ -52915,7 +53146,7 @@ function readGithubInstalledMetaIn(installDir) {
52915
53146
  }
52916
53147
  function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
52917
53148
  try {
52918
- mkdirSync9(installDir, { recursive: true });
53149
+ mkdirSync10(installDir, { recursive: true });
52919
53150
  const meta3 = {
52920
53151
  version: version2,
52921
53152
  installedAt: new Date().toISOString(),
@@ -52940,7 +53171,7 @@ function sha256OfFile(path2) {
52940
53171
  });
52941
53172
  }
52942
53173
  function sha256OfFileSync2(path2) {
52943
- return createHash6("sha256").update(readFileSync10(path2)).digest("hex");
53174
+ return createHash6("sha256").update(readFileSync11(path2)).digest("hex");
52944
53175
  }
52945
53176
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl2, signal) {
52946
53177
  const candidates = [];
@@ -53116,7 +53347,7 @@ async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
53116
53347
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
53117
53348
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
53118
53349
  }
53119
- mkdirSync9(dirname6(destPath), { recursive: true });
53350
+ mkdirSync10(dirname7(destPath), { recursive: true });
53120
53351
  let bytesWritten = 0;
53121
53352
  const guard = new TransformStream({
53122
53353
  transform(chunk, controller) {
@@ -53237,23 +53468,23 @@ function precheckArchiveContents(archivePath, archiveType) {
53237
53468
  }
53238
53469
  }
53239
53470
  function extractArchiveSafely(archivePath, destDir, archiveType) {
53240
- const suffix = randomBytes(8).toString("hex");
53471
+ const suffix = randomBytes2(8).toString("hex");
53241
53472
  const stagingDir = `${destDir}.staging-${suffix}`;
53242
53473
  try {
53243
- rmSync5(stagingDir, { recursive: true, force: true });
53474
+ rmSync6(stagingDir, { recursive: true, force: true });
53244
53475
  } catch {}
53245
- mkdirSync9(stagingDir, { recursive: true });
53476
+ mkdirSync10(stagingDir, { recursive: true });
53246
53477
  try {
53247
53478
  precheckArchiveContents(archivePath, archiveType);
53248
53479
  runPlatformExtractor(archivePath, stagingDir, archiveType);
53249
53480
  validateExtraction(stagingDir);
53250
53481
  try {
53251
- rmSync5(destDir, { recursive: true, force: true });
53482
+ rmSync6(destDir, { recursive: true, force: true });
53252
53483
  } catch {}
53253
- renameSync6(stagingDir, destDir);
53484
+ renameSync7(stagingDir, destDir);
53254
53485
  } catch (err) {
53255
53486
  try {
53256
- rmSync5(stagingDir, { recursive: true, force: true });
53487
+ rmSync6(stagingDir, { recursive: true, force: true });
53257
53488
  } catch {}
53258
53489
  throw err;
53259
53490
  }
@@ -53263,9 +53494,9 @@ function quarantineCachedGithubInstall(spec, reason) {
53263
53494
  const dest = join16(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
53264
53495
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
53265
53496
  try {
53266
- mkdirSync9(dirname6(dest), { recursive: true });
53267
- rmSync5(dest, { recursive: true, force: true });
53268
- renameSync6(packageDir, dest);
53497
+ mkdirSync10(dirname7(dest), { recursive: true });
53498
+ rmSync6(dest, { recursive: true, force: true });
53499
+ renameSync7(packageDir, dest);
53269
53500
  } catch (err) {
53270
53501
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
53271
53502
  }
@@ -53392,12 +53623,12 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl2
53392
53623
  } catch {}
53393
53624
  }
53394
53625
  const innerBinaryPath = join16(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
53395
- if (!existsSync11(innerBinaryPath)) {
53626
+ if (!existsSync12(innerBinaryPath)) {
53396
53627
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
53397
53628
  return null;
53398
53629
  }
53399
53630
  const targetBinary = ghBinaryPath(spec, platform2);
53400
- mkdirSync9(dirname6(targetBinary), { recursive: true });
53631
+ mkdirSync10(dirname7(targetBinary), { recursive: true });
53401
53632
  try {
53402
53633
  copyFileSync3(innerBinaryPath, targetBinary);
53403
53634
  if (platform2 !== "win32") {
@@ -53476,7 +53707,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl2 = fetch) {
53476
53707
  if (!host) {
53477
53708
  for (const spec of GITHUB_LSP_TABLE) {
53478
53709
  try {
53479
- if (existsSync11(ghBinDir(spec))) {
53710
+ if (existsSync12(ghBinDir(spec))) {
53480
53711
  cachedBinDirs.push(ghBinDir(spec));
53481
53712
  }
53482
53713
  } catch {}
@@ -53639,9 +53870,9 @@ function normalizeToolMap(tools) {
53639
53870
  }
53640
53871
 
53641
53872
  // src/notifications.ts
53642
- 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";
53643
53874
  import { homedir as homedir9, platform as platform2 } from "node:os";
53644
- import { join as join17 } from "node:path";
53875
+ import { dirname as dirname8, join as join17 } from "node:path";
53645
53876
  function isTuiMode() {
53646
53877
  return process.env.OPENCODE_CLIENT === "cli";
53647
53878
  }
@@ -53676,13 +53907,13 @@ function getDesktopStatePath() {
53676
53907
  }
53677
53908
  return null;
53678
53909
  }
53679
- function readDesktopState(directory) {
53910
+ function readDesktopState() {
53680
53911
  const statePath = getDesktopStatePath();
53681
- if (!statePath || !existsSync12(statePath)) {
53682
- return { sessionId: null, serverUrl: null };
53912
+ if (!statePath || !existsSync13(statePath)) {
53913
+ return { serverUrl: null };
53683
53914
  }
53684
53915
  try {
53685
- const raw = readFileSync11(statePath, "utf-8");
53916
+ const raw = readFileSync12(statePath, "utf-8");
53686
53917
  const state = JSON.parse(raw);
53687
53918
  let serverUrl = null;
53688
53919
  const serverStr = state.server;
@@ -53694,19 +53925,43 @@ function readDesktopState(directory) {
53694
53925
  }
53695
53926
  } catch {}
53696
53927
  }
53697
- let sessionId = null;
53698
- const layoutPage = state["layout.page"];
53699
- if (typeof layoutPage === "string") {
53700
- const parsed = JSON.parse(layoutPage);
53701
- const lastProjectSession = parsed.lastProjectSession;
53702
- if (lastProjectSession) {
53703
- const entry = lastProjectSession[directory];
53704
- sessionId = entry?.id ?? null;
53705
- }
53706
- }
53707
- return { sessionId, serverUrl };
53928
+ return { serverUrl };
53708
53929
  } catch {
53709
- 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
+ }
53710
53965
  }
53711
53966
  }
53712
53967
  function getServerAuth() {
@@ -53771,52 +54026,72 @@ async function sendWarning(opts, message) {
53771
54026
  const toastSent = await showTuiToast(opts.client, "AFT Warning", message, "warning", 1e4);
53772
54027
  if (toastSent)
53773
54028
  return;
53774
- const { sessionId } = readDesktopState(opts.directory);
53775
- if (!sessionId)
53776
- return;
53777
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);
53778
54036
  sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
53779
54037
  await sendIgnoredMessage(opts.client, sessionId, text);
53780
54038
  }
53781
54039
  async function sendFeatureAnnouncement(opts, version2, features, footer, storageDir) {
53782
- if (storageDir) {
53783
- const versionFile = join17(storageDir, "last_announced_version");
53784
- try {
53785
- if (existsSync12(versionFile)) {
53786
- const lastVersion = readFileSync11(versionFile, "utf-8").trim();
53787
- if (lastVersion === version2)
53788
- return;
53789
- }
53790
- } catch {}
53791
- }
54040
+ if (hasAnnouncedVersion(storageDir, version2))
54041
+ return;
53792
54042
  const hasFooter = typeof footer === "string" && footer.trim().length > 0;
53793
54043
  const featureText = hasFooter ? [features.map((f) => `• ${f}`).join(`
53794
54044
  `), "", footer].join(`
53795
54045
  `) : features.map((f) => `• ${f}`).join(`
53796
54046
  `);
53797
54047
  const toastSent = await showTuiToast(opts.client, `AFT v${version2}`, featureText, "info", 12000);
53798
- if (!toastSent) {
53799
- const { sessionId } = readDesktopState(opts.directory);
53800
- if (!sessionId)
53801
- return;
53802
- const sections = [
53803
- `${FEATURE_MARKER} v${version2}:`,
53804
- ...features.map((f) => ` • ${f}`)
53805
- ];
53806
- if (hasFooter)
53807
- sections.push("", footer);
53808
- 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(`
53809
54056
  `);
53810
- sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
53811
- 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;
53812
54067
  }
53813
- if (storageDir) {
53814
- try {
53815
- mkdirSync10(storageDir, { recursive: true });
53816
- writeFileSync9(join17(storageDir, "last_announced_version"), version2);
53817
- } 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);
53818
54074
  }
53819
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
+ }
53820
54095
  async function readWarnedTools(bridge) {
53821
54096
  try {
53822
54097
  const resp = await bridge.send("db_get_state", { key: "warned_tools" });
@@ -53883,6 +54158,9 @@ function formatConfigureWarning(warning) {
53883
54158
  ${warning.hint}`;
53884
54159
  }
53885
54160
  async function deliverConfigureWarnings(opts, warnings) {
54161
+ if (opts.projectRoot) {
54162
+ await flushPendingDesktopNotifications({ client: opts.client, directory: opts.projectRoot }, opts.sessionId);
54163
+ }
53886
54164
  if (warnings.length === 0)
53887
54165
  return;
53888
54166
  for (const warning of warnings) {
@@ -53898,10 +54176,10 @@ async function deliverConfigureWarnings(opts, warnings) {
53898
54176
  async function cleanupWarnings(opts) {
53899
54177
  if (isTuiMode())
53900
54178
  return;
53901
- const { sessionId, serverUrl: desktopServerUrl } = readDesktopState(opts.directory);
54179
+ const sessionId = getExplicitSessionId(opts);
53902
54180
  if (!sessionId)
53903
54181
  return;
53904
- const effectiveServerUrl = opts.serverUrl || desktopServerUrl;
54182
+ const effectiveServerUrl = opts.serverUrl || readDesktopState().serverUrl;
53905
54183
  if (!effectiveServerUrl)
53906
54184
  return;
53907
54185
  const messages = await getSessionMessages(opts.client, sessionId);
@@ -54035,10 +54313,10 @@ function writeTerminal(terminal, data) {
54035
54313
  }
54036
54314
 
54037
54315
  // src/shared/rpc-server.ts
54038
- import { randomBytes as randomBytes2 } from "node:crypto";
54039
- 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";
54040
54318
  import { createServer } from "node:http";
54041
- import { dirname as dirname7, join as join19 } from "node:path";
54319
+ import { dirname as dirname9, join as join19 } from "node:path";
54042
54320
 
54043
54321
  // src/shared/rpc-utils.ts
54044
54322
  import { createHash as createHash7 } from "node:crypto";
@@ -54063,7 +54341,7 @@ class AftRpcServer {
54063
54341
  instanceId;
54064
54342
  constructor(storageDir, directory) {
54065
54343
  this.portsDir = rpcPortFileDir(storageDir, directory);
54066
- this.instanceId = randomBytes2(8).toString("hex");
54344
+ this.instanceId = randomBytes3(8).toString("hex");
54067
54345
  this.portFilePath = join19(this.portsDir, `${this.instanceId}.json`);
54068
54346
  }
54069
54347
  handle(method, handler) {
@@ -54083,17 +54361,17 @@ class AftRpcServer {
54083
54361
  return;
54084
54362
  }
54085
54363
  this.port = addr.port;
54086
- this.token = randomBytes2(32).toString("hex");
54364
+ this.token = randomBytes3(32).toString("hex");
54087
54365
  this.server = server;
54088
54366
  try {
54089
- const dir = dirname7(this.portFilePath);
54090
- mkdirSync11(dir, { recursive: true, mode: 448 });
54367
+ const dir = dirname9(this.portFilePath);
54368
+ mkdirSync12(dir, { recursive: true, mode: 448 });
54091
54369
  const tmpPath = `${this.portFilePath}.tmp`;
54092
54370
  writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
54093
54371
  encoding: "utf-8",
54094
54372
  mode: 384
54095
54373
  });
54096
- renameSync7(tmpPath, this.portFilePath);
54374
+ renameSync8(tmpPath, this.portFilePath);
54097
54375
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
54098
54376
  } catch (err) {
54099
54377
  warn2(`Failed to write RPC port file: ${err}`);
@@ -54402,8 +54680,8 @@ function formatStatusMarkdown(status) {
54402
54680
 
54403
54681
  // src/shared/tui-config.ts
54404
54682
  var import_comment_json4 = __toESM(require_src2(), 1);
54405
- import { existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "node:fs";
54406
- 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";
54407
54685
 
54408
54686
  // src/shared/opencode-config-dir.ts
54409
54687
  import { homedir as homedir10 } from "node:os";
@@ -54439,9 +54717,9 @@ function resolveTuiConfigPath() {
54439
54717
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
54440
54718
  const jsoncPath = join21(configDir, "tui.jsonc");
54441
54719
  const jsonPath = join21(configDir, "tui.json");
54442
- if (existsSync13(jsoncPath))
54720
+ if (existsSync14(jsoncPath))
54443
54721
  return jsoncPath;
54444
- if (existsSync13(jsonPath))
54722
+ if (existsSync14(jsonPath))
54445
54723
  return jsonPath;
54446
54724
  return jsonPath;
54447
54725
  }
@@ -54449,8 +54727,8 @@ function ensureTuiPluginEntry() {
54449
54727
  try {
54450
54728
  const configPath = resolveTuiConfigPath();
54451
54729
  let config2 = {};
54452
- if (existsSync13(configPath)) {
54453
- config2 = import_comment_json4.parse(readFileSync12(configPath, "utf-8")) ?? {};
54730
+ if (existsSync14(configPath)) {
54731
+ config2 = import_comment_json4.parse(readFileSync13(configPath, "utf-8")) ?? {};
54454
54732
  }
54455
54733
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
54456
54734
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -54458,7 +54736,7 @@ function ensureTuiPluginEntry() {
54458
54736
  }
54459
54737
  plugins.push(PLUGIN_ENTRY);
54460
54738
  config2.plugin = plugins;
54461
- mkdirSync12(dirname8(configPath), { recursive: true });
54739
+ mkdirSync13(dirname10(configPath), { recursive: true });
54462
54740
  writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
54463
54741
  `);
54464
54742
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
@@ -54522,11 +54800,14 @@ function registerShutdownCleanup(fn) {
54522
54800
  }
54523
54801
 
54524
54802
  // src/tools/ast.ts
54525
- import { tool as tool2 } from "@opencode-ai/plugin";
54803
+ import { tool as tool3 } from "@opencode-ai/plugin";
54526
54804
 
54527
54805
  // src/tools/_shared.ts
54528
54806
  import * as fs3 from "node:fs";
54529
54807
  import * as path2 from "node:path";
54808
+ import { tool as tool2 } from "@opencode-ai/plugin";
54809
+ var z2 = tool2.schema;
54810
+ var optionalInt = (min, max) => z2.number().int().min(min).max(max).optional();
54530
54811
  var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
54531
54812
  callers: 60000,
54532
54813
  trace_to: 60000,
@@ -54739,7 +55020,7 @@ function permissionDeniedResponse(message) {
54739
55020
  }
54740
55021
 
54741
55022
  // src/tools/ast.ts
54742
- var z2 = tool2.schema;
55023
+ var z3 = tool3.schema;
54743
55024
  function showOutputToUser(context, output) {
54744
55025
  const ctx = context;
54745
55026
  ctx.metadata?.({ metadata: { output } });
@@ -54772,11 +55053,11 @@ function astTools(ctx) {
54772
55053
 
54773
55054
  ` + "Returns: Text summary — 'Found N match(es) across M file(s)' followed by file:line blocks with matched text and captured meta-variables.",
54774
55055
  args: {
54775
- pattern: z2.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
54776
- lang: z2.enum(SUPPORTED_LANGS).describe("Target language"),
54777
- paths: z2.array(z2.string()).optional().describe("Paths to search (default: ['.'])"),
54778
- globs: z2.array(z2.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
54779
- contextLines: z2.number().optional().describe("Number of context lines to show around each match")
55056
+ pattern: z3.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
55057
+ lang: z3.enum(SUPPORTED_LANGS).describe("Target language"),
55058
+ paths: z3.array(z3.string()).optional().describe("Paths to search (default: ['.'])"),
55059
+ globs: z3.array(z3.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
55060
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Number of context lines to show around each match")
54780
55061
  },
54781
55062
  execute: async (args, context) => {
54782
55063
  const externalDenied = await checkAstPathsPermission(context, args.paths);
@@ -54866,12 +55147,12 @@ ${hint}`;
54866
55147
 
54867
55148
  ` + "Returns: Text summary — 'Replaced N match(es) across M file(s)' (or '[DRY RUN] Would replace...') followed by per-file output. In dry-run mode, each file is shown with its unified diff so you can verify the rewrite before applying (e.g. catch literal $$$ from anonymous-variadic typos). Diff preview is capped at 8KB total; remaining files are summarized.",
54868
55149
  args: {
54869
- pattern: z2.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
54870
- rewrite: z2.string().describe("Replacement pattern (can use $VAR from pattern)"),
54871
- lang: z2.enum(SUPPORTED_LANGS).describe("Target language"),
54872
- paths: z2.array(z2.string()).optional().describe("Paths to search (default: ['.'])"),
54873
- globs: z2.array(z2.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
54874
- dryRun: z2.boolean().optional().describe("Preview changes without applying (default: false)")
55150
+ pattern: z3.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
55151
+ rewrite: z3.string().describe("Replacement pattern (can use $VAR from pattern)"),
55152
+ lang: z3.enum(SUPPORTED_LANGS).describe("Target language"),
55153
+ paths: z3.array(z3.string()).optional().describe("Paths to search (default: ['.'])"),
55154
+ globs: z3.array(z3.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
55155
+ dryRun: z3.boolean().optional().describe("Preview changes without applying (default: false)")
54875
55156
  },
54876
55157
  execute: async (args, context) => {
54877
55158
  const isDryRun = args.dryRun === true;
@@ -55036,7 +55317,7 @@ function conflictTools(ctx) {
55036
55317
  // src/tools/hoisted.ts
55037
55318
  import * as fs6 from "node:fs";
55038
55319
  import * as path4 from "node:path";
55039
- import { tool as tool6 } from "@opencode-ai/plugin";
55320
+ import { tool as tool7 } from "@opencode-ai/plugin";
55040
55321
 
55041
55322
  // src/patch-parser.ts
55042
55323
  function stripHeredoc(input) {
@@ -55330,7 +55611,7 @@ ${chunk.old_lines.join(`
55330
55611
  }
55331
55612
 
55332
55613
  // src/tools/bash.ts
55333
- import { tool as tool3 } from "@opencode-ai/plugin";
55614
+ import { tool as tool4 } from "@opencode-ai/plugin";
55334
55615
 
55335
55616
  // src/shared/subagent-detect.ts
55336
55617
  var CACHE_MAX_ENTRIES2 = 200;
@@ -55383,7 +55664,7 @@ function setCache2(sessionId, isSubagent) {
55383
55664
  }
55384
55665
 
55385
55666
  // src/tools/bash.ts
55386
- var z3 = tool3.schema;
55667
+ var z4 = tool4.schema;
55387
55668
  var METADATA_PREVIEW_LIMIT = 30 * 1024;
55388
55669
  var FOREGROUND_WAIT_WINDOW_MS = 5000;
55389
55670
  var FOREGROUND_POLL_INTERVAL_MS = 100;
@@ -55416,15 +55697,15 @@ function createBashTool(ctx) {
55416
55697
  return {
55417
55698
  description: BASH_DESCRIPTION,
55418
55699
  args: {
55419
- command: z3.string().describe("Shell command to execute through AFT's unified bash schema. Supports normal shell syntax, pipes, redirection, and command rewriting to dedicated AFT tools when available."),
55420
- timeout: z3.number().int().positive().optional().describe("Hard kill cap in milliseconds (positive integer). When omitted, the task can run up to 30 minutes. Foreground bash returns inline if the command finishes within ~5s; otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
55421
- workdir: z3.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
55422
- description: z3.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
55423
- background: z3.boolean().optional().describe("When true, spawn the command in the background and return a task_id for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
55424
- compressed: z3.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
55425
- pty: z3.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Requires background: true and is unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
55426
- ptyRows: z3.number().int().min(1).max(60).optional().describe("PTY terminal height in rows. Applies only when pty: true; defaults to 24. Minimum 1, maximum 60."),
55427
- ptyCols: z3.number().int().min(1).max(140).optional().describe("PTY terminal width in columns. Applies only when pty: true; defaults to 80. Minimum 1, maximum 140.")
55700
+ command: z4.string().describe("Shell command to execute through AFT's unified bash schema. Supports normal shell syntax, pipes, redirection, and command rewriting to dedicated AFT tools when available."),
55701
+ timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Hard kill cap in milliseconds (positive integer). When omitted, the task can run up to 30 minutes. Foreground bash returns inline if the command finishes within ~5s; otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
55702
+ workdir: z4.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
55703
+ description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
55704
+ background: z4.boolean().optional().describe("When true, spawn the command in the background and return a task_id for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
55705
+ compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
55706
+ pty: z4.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically. Unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
55707
+ ptyRows: optionalInt(1, 60).describe("PTY terminal height in rows ignored when pty is false. Defaults to 24 when pty: true. Minimum 1, maximum 60."),
55708
+ ptyCols: optionalInt(1, 140).describe("PTY terminal width in columns ignored when pty is false. Defaults to 80 when pty: true. Minimum 1, maximum 140.")
55428
55709
  },
55429
55710
  execute: async (args, context) => {
55430
55711
  const bashCfg = resolveBashConfig(ctx.config);
@@ -55434,14 +55715,8 @@ function createBashTool(ctx) {
55434
55715
  const command = args.command;
55435
55716
  const cwd = args.workdir ?? context.directory;
55436
55717
  const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
55437
- const requestedBackground = args.background === true;
55438
55718
  const requestedPty = args.pty === true;
55439
- if (!requestedPty && (args.ptyRows !== undefined || args.ptyCols !== undefined)) {
55440
- throw new Error("invalid_request: ptyRows/ptyCols require pty: true");
55441
- }
55442
- if (requestedPty && !requestedBackground) {
55443
- throw new Error("PTY mode requires background: true");
55444
- }
55719
+ const requestedBackground = args.background === true || requestedPty;
55445
55720
  if (requestedPty && isSubagent) {
55446
55721
  throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
55447
55722
  }
@@ -55515,6 +55790,10 @@ function createBashTool(ctx) {
55515
55790
  return rendered2;
55516
55791
  }
55517
55792
  if (Date.now() - startedAt >= waitTimeoutMs) {
55793
+ if (subagentForcedForeground) {
55794
+ await sleep(FOREGROUND_POLL_INTERVAL_MS);
55795
+ continue;
55796
+ }
55518
55797
  const promoted = await callBridge(ctx, context, "bash_promote", { task_id: taskId });
55519
55798
  if (promoted.success === false) {
55520
55799
  throw new Error(promoted.message ?? "bash_promote failed");
@@ -55578,8 +55857,8 @@ function createBashStatusTool(ctx) {
55578
55857
  return {
55579
55858
  description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Use bash_watch to block on or register for pattern matches and exit events.",
55580
55859
  args: {
55581
- taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047."),
55582
- outputMode: z3.enum(["screen", "raw", "both"]).optional().describe("PTY output rendering mode. Defaults to screen for PTY tasks and preserves existing behavior for piped tasks when omitted.")
55860
+ taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047."),
55861
+ outputMode: z4.enum(["screen", "raw", "both"]).optional().describe("PTY output rendering mode. Defaults to screen for PTY tasks and preserves existing behavior for piped tasks when omitted.")
55583
55862
  },
55584
55863
  execute: async (args, context) => {
55585
55864
  const taskId = args.taskId;
@@ -55593,7 +55872,7 @@ function createBashKillTool(ctx) {
55593
55872
  return {
55594
55873
  description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
55595
55874
  args: {
55596
- taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
55875
+ taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
55597
55876
  },
55598
55877
  execute: async (args, context) => {
55599
55878
  const data = await callBridge(ctx, context, "bash_kill", {
@@ -55741,21 +56020,22 @@ function shortenCommand(command) {
55741
56020
 
55742
56021
  // src/tools/bash_watch.ts
55743
56022
  import * as fs5 from "node:fs/promises";
55744
- import { tool as tool4 } from "@opencode-ai/plugin";
55745
- var z4 = tool4.schema;
56023
+ import { tool as tool5 } from "@opencode-ai/plugin";
56024
+ var z5 = tool5.schema;
55746
56025
  var BASH_WAIT_POLL_INTERVAL_MS = 100;
55747
56026
  var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
55748
56027
  var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
55749
56028
  var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
56029
+ var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
55750
56030
  function createBashWatchTool(ctx) {
55751
56031
  return {
55752
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.",
55753
56033
  args: {
55754
- taskId: z4.string().describe("Background task ID returned by bash({ background: true })."),
55755
- pattern: z4.union([z4.string(), z4.object({ regex: z4.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true."),
55756
- background: z4.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
55757
- timeoutMs: z4.number().int().positive().optional().describe("Sync-only timeout in milliseconds. Default 30000, max 300000."),
55758
- once: z4.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
56034
+ taskId: z5.string().describe("Background task ID returned by bash({ 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."),
56036
+ background: z5.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
56037
+ timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 300000."),
56038
+ once: z5.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
55759
56039
  },
55760
56040
  execute: async (args, context) => {
55761
56041
  const taskId = args.taskId;
@@ -55844,7 +56124,7 @@ async function bashStatusSnapshot2(ctx, runtime, taskId, outputMode, options) {
55844
56124
  async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effectiveWaitMs) {
55845
56125
  const startedAt = Date.now();
55846
56126
  const deadline = startedAt + effectiveWaitMs;
55847
- let spillCursor = 0;
56127
+ let spillCursor = { output: 0, stderr: 0, combined: 0 };
55848
56128
  let scanText = "";
55849
56129
  let scanBaseOffset = 0;
55850
56130
  const bridgeOptions = {
@@ -55852,14 +56132,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55852
56132
  transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2
55853
56133
  };
55854
56134
  markTaskWaiting(runtime.sessionID, taskId);
56135
+ let sawTerminal = false;
55855
56136
  try {
55856
56137
  while (true) {
55857
56138
  const data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, bridgeOptions);
55858
- if (isTerminalStatus2(data.status)) {
55859
- consumeBgCompletion(runtime.sessionID, taskId);
55860
- await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
55861
- return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
55862
- }
56139
+ const terminal = isTerminalStatus2(data.status);
55863
56140
  if (waitFor) {
55864
56141
  const scan = await readNewTaskOutput(runtime, taskId, data, spillCursor);
55865
56142
  if (scan) {
@@ -55869,7 +56146,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55869
56146
  scanText += scan.text;
55870
56147
  const match = findWaitMatch(scanText, waitFor);
55871
56148
  if (match) {
55872
- 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
+ }
55873
56154
  return withWaited(data, {
55874
56155
  reason: "matched",
55875
56156
  elapsed_ms: Date.now() - startedAt,
@@ -55877,31 +56158,61 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
55877
56158
  match_offset: scanBaseOffset + Buffer.byteLength(scanText.slice(0, match.index), "utf8")
55878
56159
  });
55879
56160
  }
56161
+ const trimmed = trimWaitScanBuffer(scanText, scanBaseOffset, waitFor);
56162
+ scanText = trimmed.text;
56163
+ scanBaseOffset = trimmed.baseOffset;
55880
56164
  }
55881
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
+ }
55882
56172
  if (Date.now() >= deadline) {
55883
- unmarkTaskWaiting(runtime.sessionID, taskId);
55884
56173
  return withWaited(data, { reason: "timeout", elapsed_ms: Date.now() - startedAt });
55885
56174
  }
55886
56175
  await sleep2(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
55887
56176
  }
55888
- } catch (err) {
55889
- unmarkTaskWaiting(runtime.sessionID, taskId);
55890
- throw err;
56177
+ } finally {
56178
+ if (!sawTerminal)
56179
+ unmarkTaskWaiting(runtime.sessionID, taskId);
56180
+ await disposePtyTerminal(watchPtyCacheKey(runtime, taskId));
55891
56181
  }
55892
56182
  }
55893
56183
  async function readNewTaskOutput(runtime, taskId, data, cursor) {
55894
56184
  const outputPath = data.output_path;
55895
- if (!outputPath)
55896
- return;
55897
56185
  if (data.mode === "pty") {
55898
- const state = await getOrCreatePtyTerminal(ptyCacheKey2(runtime, taskId), outputPath);
56186
+ if (!outputPath)
56187
+ return;
56188
+ const state = await getOrCreatePtyTerminal(watchPtyCacheKey(runtime, taskId), outputPath);
55899
56189
  const baseOffset = state.offset;
55900
- const bytes2 = await readPtyBytes(state);
55901
- 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
+ };
55902
56198
  }
55903
- const bytes = await readFileBytesFrom(outputPath, cursor);
55904
- 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
+ };
55905
56216
  }
55906
56217
  async function readFileBytesFrom(outputPath, cursor) {
55907
56218
  const handle = await fs5.open(outputPath, "r");
@@ -55942,6 +56253,34 @@ function findWaitMatch(text, pattern) {
55942
56253
  const match = pattern.value.exec(text);
55943
56254
  return match ? { text: match[0], index: match.index } : undefined;
55944
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
+ }
55945
56284
  function withWaited(data, waited) {
55946
56285
  return { ...data, waited };
55947
56286
  }
@@ -55951,24 +56290,27 @@ function isTerminalStatus2(status) {
55951
56290
  function ptyCacheKey2(runtime, taskId) {
55952
56291
  return `${projectRootFor(runtime)}::${runtime.sessionID ?? "__default__"}::${taskId}`;
55953
56292
  }
56293
+ function watchPtyCacheKey(runtime, taskId) {
56294
+ return `${ptyCacheKey2(runtime, taskId)}::watch`;
56295
+ }
55954
56296
  function sleep2(ms) {
55955
56297
  return new Promise((resolve7) => setTimeout(resolve7, ms));
55956
56298
  }
55957
56299
 
55958
56300
  // src/tools/bash_write.ts
55959
- import { tool as tool5 } from "@opencode-ai/plugin";
55960
- var z5 = tool5.schema;
56301
+ import { tool as tool6 } from "@opencode-ai/plugin";
56302
+ var z6 = tool6.schema;
55961
56303
  function createBashWriteTool(ctx) {
55962
56304
  return {
55963
56305
  description: 'Write input bytes to a running PTY bash task. PTY-only; check bash_status reports mode: "pty" first. ' + 'Input is either a string (verbatim bytes) or an array mixing strings and { key: "esc" | "enter" | "up" | "ctrl-c" | ... } objects ' + 'for atomic text+key sequences such as [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ]. ' + "Named keys cover enter/return/tab/space/backspace/esc/escape, arrows, home/end/page-up/page-down/delete/insert, f1..f12, and ctrl-a..ctrl-z. " + "Maximum 1 MiB per call (post-expansion).",
55964
56306
  args: {
55965
- taskId: z5.string().describe("Background PTY task ID returned by bash({ pty: true, background: true })."),
55966
- input: z5.union([
55967
- z5.string(),
55968
- z5.array(z5.union([
55969
- z5.string(),
55970
- z5.object({
55971
- key: z5.string().describe("Named control key, e.g. 'esc', 'enter', 'up', 'ctrl-c'. Case-insensitive.")
56307
+ taskId: z6.string().describe("Background PTY task ID returned by bash({ pty: true, background: true })."),
56308
+ input: z6.union([
56309
+ z6.string(),
56310
+ z6.array(z6.union([
56311
+ z6.string(),
56312
+ z6.object({
56313
+ key: z6.string().describe("Named control key, e.g. 'esc', 'enter', 'up', 'ctrl-c'. Case-insensitive.")
55972
56314
  })
55973
56315
  ]))
55974
56316
  ]).describe("Either a string of verbatim bytes (e.g. 'print(1)\\n') OR an array mixing strings " + "and { key: '<name>' } objects for atomic text+key sequences. " + "Example: [ 'iHello', { key: 'esc' }, ':wq', { key: 'enter' } ].")
@@ -56195,7 +56537,7 @@ function inferBeforeStart(ops, from, beforeLen) {
56195
56537
  }
56196
56538
  return beforeLen;
56197
56539
  }
56198
- var z6 = tool6.schema;
56540
+ var z7 = tool7.schema;
56199
56541
  var READ_DESCRIPTION = `Read file contents or list directory entries.
56200
56542
 
56201
56543
  Use either startLine/endLine OR offset/limit to read a section of a file.
@@ -56219,11 +56561,11 @@ function createReadTool(ctx) {
56219
56561
  return {
56220
56562
  description: READ_DESCRIPTION,
56221
56563
  args: {
56222
- filePath: z6.string().describe("Path to file or directory (absolute or relative to project root)"),
56223
- startLine: z6.number().optional().describe("1-based line to start reading from"),
56224
- endLine: z6.number().optional().describe("1-based line to stop reading at (inclusive)"),
56225
- limit: z6.number().optional().describe("Max lines to return (default: 2000)"),
56226
- offset: z6.number().optional().describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
56564
+ filePath: z7.string().describe("Path to file or directory (absolute or relative to project root)"),
56565
+ startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
56566
+ endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to stop reading at (inclusive)"),
56567
+ limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines to return (default: 2000)"),
56568
+ offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
56227
56569
  },
56228
56570
  execute: async (args, context) => {
56229
56571
  const file2 = args.filePath;
@@ -56355,8 +56697,8 @@ function createWriteTool(ctx, editToolName = "edit") {
56355
56697
  return {
56356
56698
  description: getWriteDescription(editToolName),
56357
56699
  args: {
56358
- filePath: z6.string().describe("Path to the file to write (absolute or relative to project root)"),
56359
- content: z6.string().describe("The full content to write to the file")
56700
+ filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
56701
+ content: z7.string().describe("The full content to write to the file")
56360
56702
  },
56361
56703
  execute: async (args, context) => {
56362
56704
  const file2 = args.filePath;
@@ -56501,16 +56843,16 @@ function createEditTool(ctx, writeToolName = "write") {
56501
56843
  return {
56502
56844
  description: getEditDescription(writeToolName),
56503
56845
  args: {
56504
- filePath: z6.string().optional().describe("Path to the file to edit (absolute or relative to project root). Required for all modes except 'operations' multi-file transactions"),
56505
- oldString: z6.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
56506
- newString: z6.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
56507
- replaceAll: z6.boolean().optional().describe("Replace all occurrences"),
56508
- occurrence: z6.number().optional().describe("0-indexed occurrence to replace when multiple matches exist"),
56509
- symbol: z6.string().optional().describe("Named symbol to replace (function, class, type)"),
56510
- content: z6.string().optional().describe("New content for symbol replace or file write"),
56511
- appendContent: z6.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
56512
- edits: z6.array(z6.record(z6.string(), z6.unknown())).optional().describe("Batch edits — array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
56513
- operations: z6.array(z6.record(z6.string(), z6.unknown())).optional().describe("Transaction — array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)")
56846
+ filePath: z7.string().optional().describe("Path to the file to edit (absolute or relative to project root). Required for all modes except 'operations' multi-file transactions"),
56847
+ oldString: z7.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
56848
+ newString: z7.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
56849
+ replaceAll: z7.boolean().optional().describe("Replace all occurrences"),
56850
+ occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER).describe("0-indexed occurrence to replace when multiple matches exist"),
56851
+ symbol: z7.string().optional().describe("Named symbol to replace (function, class, type)"),
56852
+ content: z7.string().optional().describe("New content for symbol replace or file write"),
56853
+ appendContent: z7.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
56854
+ edits: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Batch edits — array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
56855
+ operations: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Transaction — array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)")
56514
56856
  },
56515
56857
  execute: async (args, context) => {
56516
56858
  const argsRecord = args;
@@ -56726,7 +57068,7 @@ function createApplyPatchTool(ctx) {
56726
57068
  return {
56727
57069
  description: APPLY_PATCH_DESCRIPTION,
56728
57070
  args: {
56729
- patchText: z6.string().describe("The full patch text including Begin/End markers")
57071
+ patchText: z7.string().describe("The full patch text including Begin/End markers")
56730
57072
  },
56731
57073
  execute: async (args, context) => {
56732
57074
  const patchText = args.patchText;
@@ -57002,8 +57344,8 @@ function createDeleteTool(ctx) {
57002
57344
  return {
57003
57345
  description: DELETE_DESCRIPTION,
57004
57346
  args: {
57005
- files: z6.array(z6.string()).min(1).describe("Paths to delete (one or more). May include directories when recursive=true."),
57006
- recursive: z6.boolean().optional().describe("Required to delete a directory and its contents. Defaults to false; passing a directory without this returns an error.")
57347
+ files: z7.array(z7.string()).min(1).describe("Paths to delete (one or more). May include directories when recursive=true."),
57348
+ recursive: z7.boolean().optional().describe("Required to delete a directory and its contents. Defaults to false; passing a directory without this returns an error.")
57007
57349
  },
57008
57350
  execute: async (args, context) => {
57009
57351
  const inputs = args.files;
@@ -57056,8 +57398,8 @@ function createMoveTool(ctx) {
57056
57398
  return {
57057
57399
  description: MOVE_DESCRIPTION,
57058
57400
  args: {
57059
- filePath: z6.string().describe("Source file path to move"),
57060
- destination: z6.string().describe("Destination file path")
57401
+ filePath: z7.string().describe("Source file path to move"),
57402
+ destination: z7.string().describe("Destination file path")
57061
57403
  },
57062
57404
  execute: async (args, context) => {
57063
57405
  const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
@@ -57167,8 +57509,8 @@ function aftPrefixedTools(ctx) {
57167
57509
  }
57168
57510
 
57169
57511
  // src/tools/imports.ts
57170
- import { tool as tool7 } from "@opencode-ai/plugin";
57171
- var z7 = tool7.schema;
57512
+ import { tool as tool8 } from "@opencode-ai/plugin";
57513
+ var z8 = tool8.schema;
57172
57514
  function importTools(ctx) {
57173
57515
  return {
57174
57516
  aft_import: {
@@ -57184,14 +57526,14 @@ function importTools(ctx) {
57184
57526
  ` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
57185
57527
  ` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
57186
57528
  args: {
57187
- op: z7.enum(["add", "remove", "organize"]).describe("Import operation"),
57188
- filePath: z7.string().describe("Path to the file (absolute or relative to project root)"),
57189
- module: z7.string().optional().describe("Module path (required for add, remove — e.g. 'react', './utils', 'std::fmt')"),
57190
- names: z7.array(z7.string()).optional().describe("Named imports to add (e.g. ['useState', 'useEffect'])"),
57191
- defaultImport: z7.string().optional().describe("Default import name (e.g. 'React')"),
57192
- typeOnly: z7.boolean().optional().describe("Type-only import (TS only, default: false)"),
57193
- removeName: z7.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
57194
- validate: z7.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
57529
+ op: z8.enum(["add", "remove", "organize"]).describe("Import operation"),
57530
+ filePath: z8.string().describe("Path to the file (absolute or relative to project root)"),
57531
+ module: z8.string().optional().describe("Module path (required for add, remove — e.g. 'react', './utils', 'std::fmt')"),
57532
+ names: z8.array(z8.string()).optional().describe("Named imports to add (e.g. ['useState', 'useEffect'])"),
57533
+ defaultImport: z8.string().optional().describe("Default import name (e.g. 'React')"),
57534
+ typeOnly: z8.boolean().optional().describe("Type-only import (TS only, default: false)"),
57535
+ removeName: z8.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
57536
+ validate: z8.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
57195
57537
  },
57196
57538
  execute: async (args, context) => {
57197
57539
  const op = args.op;
@@ -57236,8 +57578,8 @@ function importTools(ctx) {
57236
57578
  }
57237
57579
 
57238
57580
  // src/tools/lsp.ts
57239
- import { tool as tool8 } from "@opencode-ai/plugin";
57240
- var z8 = tool8.schema;
57581
+ import { tool as tool9 } from "@opencode-ai/plugin";
57582
+ var z9 = tool9.schema;
57241
57583
  function lspTools(ctx) {
57242
57584
  const diagnosticsTool = {
57243
57585
  description: "On-demand LSP file/scope check. Spawns the relevant language server (if " + "registered for the file's extension), opens the document, prefers LSP 3.17 " + "pull diagnostics when supported, and falls back to push + waitMs otherwise. " + "NOT a project-wide type checker — for full coverage run `tsc --noEmit`, " + "`cargo check`, `pyright`, etc.\n" + `
@@ -57259,10 +57601,10 @@ function lspTools(ctx) {
57259
57601
  ` + "- `total: 0, complete: true, lsp_servers_used: [{status: 'pull_ok'}]` → file is genuinely clean.\n" + "- `total: 0, lsp_servers_used: []` → **nothing was checked** (no server registered for this extension). Tell the user, don't claim 'no errors'.\n" + "- `lsp_servers_used: [{status: 'binary_not_installed: <name>'}]` → server matched the extension but its binary isn't on PATH. Tell the user to install it.\n" + "- `lsp_servers_used: [{status: 'no_root_marker (...)'}]` → server is registered but couldn't find a workspace root marker walking up from this file. The user's project layout doesn't match what the server expects.\n" + "- `complete: false` (directory mode) → some files in the directory weren't checked; see `unchecked_files`.\n" + `
57260
57602
  ` + "**When this tool gives an unhelpful answer**, run `npx @cortexkit/aft doctor lsp <filePath>` from a terminal to get a full per-server breakdown (registered servers, binary resolution, root-marker resolution, spawn outcome).",
57261
57603
  args: {
57262
- filePath: z8.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
57263
- directory: z8.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
57264
- severity: z8.enum(["error", "warning", "information", "hint", "all"]).optional().describe("Filter by severity (default: 'all')."),
57265
- waitMs: z8.number().optional().describe("Wait up to N ms (max 10000, default 0) for push diagnostics to arrive. Only matters for servers that don't support LSP 3.17 pull (bash-language-server, yaml-language-server). Use after an edit to let the server re-analyze.")
57604
+ filePath: z9.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
57605
+ directory: z9.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
57606
+ severity: z9.enum(["error", "warning", "information", "hint", "all"]).optional().describe("Filter by severity (default: 'all')."),
57607
+ waitMs: optionalInt(1, 1e4).describe("Wait up to N ms (max 10000, default 0) for push diagnostics to arrive. Only matters for servers that don't support LSP 3.17 pull (bash-language-server, yaml-language-server). Use after an edit to let the server re-analyze.")
57266
57608
  },
57267
57609
  execute: async (args, context) => {
57268
57610
  const filePath = args.filePath || undefined;
@@ -57293,8 +57635,8 @@ function lspTools(ctx) {
57293
57635
  }
57294
57636
 
57295
57637
  // src/tools/navigation.ts
57296
- import { tool as tool9 } from "@opencode-ai/plugin";
57297
- var z9 = tool9.schema;
57638
+ import { tool as tool10 } from "@opencode-ai/plugin";
57639
+ var z10 = tool10.schema;
57298
57640
  function navigationTools(ctx) {
57299
57641
  return {
57300
57642
  aft_navigate: {
@@ -57311,11 +57653,11 @@ function navigationTools(ctx) {
57311
57653
 
57312
57654
  `,
57313
57655
  args: {
57314
- op: z9.enum(["call_tree", "callers", "trace_to", "impact", "trace_data"]).describe("Navigation operation"),
57315
- filePath: z9.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
57316
- symbol: z9.string().describe("Name of the symbol to analyze"),
57317
- depth: z9.number().optional().describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, impact=5, trace_data=5)"),
57318
- expression: z9.string().optional().describe("Expression to track through data flow (required for trace_data op)")
57656
+ op: z10.enum(["call_tree", "callers", "trace_to", "impact", "trace_data"]).describe("Navigation operation"),
57657
+ filePath: z10.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
57658
+ symbol: z10.string().describe("Name of the symbol to analyze"),
57659
+ depth: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, impact=5, trace_data=5)"),
57660
+ expression: z10.string().optional().describe("Expression to track through data flow (required for trace_data op)")
57319
57661
  },
57320
57662
  execute: async (args, context) => {
57321
57663
  const params = {
@@ -57341,8 +57683,8 @@ function navigationTools(ctx) {
57341
57683
 
57342
57684
  // src/tools/reading.ts
57343
57685
  import { resolve as resolve8 } from "node:path";
57344
- import { tool as tool10 } from "@opencode-ai/plugin";
57345
- var z10 = tool10.schema;
57686
+ import { tool as tool11 } from "@opencode-ai/plugin";
57687
+ var z11 = tool11.schema;
57346
57688
  function readingTools(ctx) {
57347
57689
  return {
57348
57690
  aft_outline: {
@@ -57353,7 +57695,7 @@ function readingTools(ctx) {
57353
57695
  ` + ` • URL (http:// or https://) → fetch and outline a remote HTML/Markdown document
57354
57696
  ` + " • array of paths → outline multiple files in one call",
57355
57697
  args: {
57356
- target: z10.union([z10.string(), z10.array(z10.string())]).describe("What to outline: a file path, directory path, URL, or array of file paths. The mode is auto-detected: URLs by `http://`/`https://` prefix, directories by stat, arrays as multi-file.")
57698
+ target: z11.union([z11.string(), z11.array(z11.string())]).describe("What to outline: a file path, directory path, URL, or array of file paths. The mode is auto-detected: URLs by `http://`/`https://` prefix, directories by stat, arrays as multi-file.")
57357
57699
  },
57358
57700
  execute: async (args, context) => {
57359
57701
  const target = args.target;
@@ -57408,11 +57750,11 @@ function readingTools(ctx) {
57408
57750
 
57409
57751
  Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lookup or 'symbols' for multiple in one call.`,
57410
57752
  args: {
57411
- filePath: z10.string().optional().describe("Path to file (absolute or relative to project root)"),
57412
- url: z10.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
57413
- symbol: z10.string().optional().describe("Symbol name for code, or heading text for Markdown/HTML"),
57414
- symbols: z10.array(z10.string()).optional().describe("Array of symbol names or heading texts for a single batched call"),
57415
- contextLines: z10.number().optional().describe("Lines of context before/after the symbol (default: 3)")
57753
+ filePath: z11.string().optional().describe("Path to file (absolute or relative to project root)"),
57754
+ url: z11.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
57755
+ symbol: z11.string().optional().describe("Symbol name for code, or heading text for Markdown/HTML"),
57756
+ symbols: z11.array(z11.string()).optional().describe("Array of symbol names or heading texts for a single batched call"),
57757
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)")
57416
57758
  },
57417
57759
  execute: async (args, context) => {
57418
57760
  const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
@@ -57494,7 +57836,7 @@ ${lines}`;
57494
57836
  }
57495
57837
 
57496
57838
  // src/tools/refactoring.ts
57497
- import { tool as tool11 } from "@opencode-ai/plugin";
57839
+ import { tool as tool12 } from "@opencode-ai/plugin";
57498
57840
 
57499
57841
  // src/lsp.ts
57500
57842
  var LSP_SYMBOL_KIND_MAP = {
@@ -57553,7 +57895,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
57553
57895
  }
57554
57896
 
57555
57897
  // src/tools/refactoring.ts
57556
- var z11 = tool11.schema;
57898
+ var z12 = tool12.schema;
57557
57899
  function refactoringTools(ctx) {
57558
57900
  return {
57559
57901
  aft_refactor: {
@@ -57571,15 +57913,15 @@ function refactoringTools(ctx) {
57571
57913
 
57572
57914
  ` + "Returns: move { ok, files_modified, consumers_updated, checkpoint_name, results }. extract returns { file, name, parameters, return_type, syntax_valid, formatted, ... }. inline returns { file, symbol, call_context, substitutions, conflicts, syntax_valid, formatted, ... }.",
57573
57915
  args: {
57574
- op: z11.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
57575
- filePath: z11.string().describe("Path to the source file (absolute or relative to project root)"),
57576
- symbol: z11.string().optional().describe("Symbol name — required for 'move' and 'inline' ops"),
57577
- destination: z11.string().optional().describe("Target file path — required for 'move' op"),
57578
- scope: z11.string().optional().describe("Disambiguation scope for 'move' op — when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
57579
- name: z11.string().optional().describe("New function name — required for 'extract' op"),
57580
- startLine: z11.number().optional().describe("1-based start line — required for 'extract' op"),
57581
- endLine: z11.number().optional().describe("1-based end line (inclusive) — required for 'extract' op"),
57582
- callSiteLine: z11.number().optional().describe("1-based call site line — required for 'inline' op")
57916
+ op: z12.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
57917
+ filePath: z12.string().describe("Path to the source file (absolute or relative to project root)"),
57918
+ symbol: z12.string().optional().describe("Symbol name — required for 'move' and 'inline' ops"),
57919
+ destination: z12.string().optional().describe("Target file path — required for 'move' op"),
57920
+ scope: z12.string().optional().describe("Disambiguation scope for 'move' op — when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
57921
+ name: z12.string().optional().describe("New function name — required for 'extract' op"),
57922
+ startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based start line — required for 'extract' op"),
57923
+ endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based end line (inclusive) — required for 'extract' op"),
57924
+ callSiteLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based call site line — required for 'inline' op")
57583
57925
  },
57584
57926
  execute: async (args, context) => {
57585
57927
  const op = args.op;
@@ -57660,8 +58002,8 @@ function refactoringTools(ctx) {
57660
58002
 
57661
58003
  // src/tools/safety.ts
57662
58004
  import * as path5 from "node:path";
57663
- import { tool as tool12 } from "@opencode-ai/plugin";
57664
- var z12 = tool12.schema;
58005
+ import { tool as tool13 } from "@opencode-ai/plugin";
58006
+ var z13 = tool13.schema;
57665
58007
  function safetyTools(ctx) {
57666
58008
  return {
57667
58009
  aft_safety: {
@@ -57682,10 +58024,10 @@ function safetyTools(ctx) {
57682
58024
 
57683
58025
  ` + "Returns: undo { path, backup_id }. history { file, entries }. checkpoint { ok, name }. restore { ok, name }. list { checkpoints }.",
57684
58026
  args: {
57685
- op: z12.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
57686
- filePath: z12.string().optional().describe("File path (required for history, optional for undo). Absolute or relative to project root"),
57687
- name: z12.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
57688
- files: z12.array(z12.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
58027
+ op: z13.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
58028
+ filePath: z13.string().optional().describe("File path (required for history, optional for undo). Absolute or relative to project root"),
58029
+ name: z13.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
58030
+ files: z13.array(z13.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
57689
58031
  },
57690
58032
  execute: async (args, context) => {
57691
58033
  const op = args.op;
@@ -57793,6 +58135,39 @@ function normalizeGlob(pattern) {
57793
58135
  }
57794
58136
  return pattern;
57795
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
+ }
57796
58171
  function splitIncludeArg(raw) {
57797
58172
  const out = [];
57798
58173
  let depth = 0;
@@ -57842,17 +58217,13 @@ function searchTools(ctx) {
57842
58217
  if (grepDenied)
57843
58218
  return permissionDeniedResponse(grepDenied);
57844
58219
  if (pathArg) {
57845
- let kind = "file";
57846
- try {
57847
- const abs = path6.isAbsolute(pathArg) ? pathArg : path6.resolve(context.directory, pathArg);
57848
- if (fs7.lstatSync(abs).isDirectory())
57849
- kind = "directory";
57850
- } catch {}
57851
- const externalDenied = await assertExternalDirectoryPermission(context, pathArg, {
57852
- kind
57853
- });
57854
- if (externalDenied)
57855
- 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
+ }
57856
58227
  }
57857
58228
  const response = await callBridge(ctx, context, "grep", {
57858
58229
  pattern,
@@ -57884,23 +58255,22 @@ function searchTools(ctx) {
57884
58255
  globPath = globPattern.slice(0, lastSlash);
57885
58256
  globPattern = `**/${globPattern.slice(lastSlash + 1)}`;
57886
58257
  }
58258
+ } else if (metaIdx === -1) {
58259
+ globPath = path6.dirname(globPattern);
58260
+ globPattern = path6.basename(globPattern);
57887
58261
  }
57888
58262
  }
57889
58263
  const globDenied = await askGlobPermission(context, globPattern, { path: globPath });
57890
58264
  if (globDenied)
57891
58265
  return permissionDeniedResponse(globDenied);
57892
58266
  if (globPath) {
57893
- let kind = "directory";
57894
- try {
57895
- const abs = path6.isAbsolute(globPath) ? globPath : path6.resolve(context.directory, globPath);
57896
- if (fs7.lstatSync(abs).isFile())
57897
- kind = "file";
57898
- } catch {}
57899
- const externalDenied = await assertExternalDirectoryPermission(context, globPath, {
57900
- kind
57901
- });
57902
- if (externalDenied)
57903
- 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
+ }
57904
58274
  }
57905
58275
  const response = await callBridge(ctx, context, "glob", {
57906
58276
  pattern: globPattern,
@@ -57927,6 +58297,8 @@ function searchTools(ctx) {
57927
58297
  }
57928
58298
 
57929
58299
  // src/tools/semantic.ts
58300
+ import { tool as tool14 } from "@opencode-ai/plugin";
58301
+ var z14 = tool14.schema;
57930
58302
  function arg2(schema) {
57931
58303
  return schema;
57932
58304
  }
@@ -57951,8 +58323,8 @@ function semanticTools(ctx) {
57951
58323
  ].join(`
57952
58324
  `),
57953
58325
  args: {
57954
- query: arg2(exports_external.string().describe("Concept or capability to find, phrased as a programmer would describe the code. Examples: 'fuzzy match with whitespace tolerance', 'undo backup before edit', 'retry failed network request'.")),
57955
- topK: arg2(exports_external.number().optional().describe("Number of results (default: 10, max: 100)"))
58326
+ query: arg2(z14.string().describe("Concept or capability to find, phrased as a programmer would describe the code. Examples: 'fuzzy match with whitespace tolerance', 'undo backup before edit', 'retry failed network request'.")),
58327
+ topK: arg2(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)"))
57956
58328
  },
57957
58329
  execute: async (args, context) => {
57958
58330
  const response = await callBridge(ctx, context, "semantic_search", {
@@ -57977,8 +58349,8 @@ function semanticTools(ctx) {
57977
58349
  }
57978
58350
 
57979
58351
  // src/tools/structure.ts
57980
- import { tool as tool13 } from "@opencode-ai/plugin";
57981
- var z13 = tool13.schema;
58352
+ import { tool as tool15 } from "@opencode-ai/plugin";
58353
+ var z15 = tool15.schema;
57982
58354
  function structureTools(ctx) {
57983
58355
  return {
57984
58356
  aft_transform: {
@@ -58002,19 +58374,19 @@ function structureTools(ctx) {
58002
58374
  ` + `- add_decorator: { file, target, decorator, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
58003
58375
  ` + "- add_struct_tags: { file, target, field, tag_string, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
58004
58376
  args: {
58005
- op: z13.enum(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"]).describe("Transformation operation"),
58006
- filePath: z13.string().describe("Path to the source file (absolute or relative to project root)"),
58007
- container: z13.string().optional().describe("Container name for add_member — the class, struct, or impl block to insert into. Appears as 'scope' in the response."),
58008
- code: z13.string().optional().describe("Member code to insert (add_member)"),
58009
- position: z13.string().optional().describe("For add_member: 'first', 'last' (default), 'before:name', 'after:name'. For add_decorator: 'first' (default) or 'last' only."),
58010
- target: z13.string().optional().describe("Target symbol name (add_derive: struct/enum, wrap_try_catch: function, add_decorator: function/class, add_struct_tags: struct)"),
58011
- derives: z13.array(z13.string()).optional().describe("Derive macro names (add_derive — e.g. ['Clone', 'Debug'])"),
58012
- catchBody: z13.string().optional().describe("Catch block body (wrap_try_catch — default: 'throw error;')"),
58013
- decorator: z13.string().optional().describe("Decorator text without @ (add_decorator — e.g. 'staticmethod')"),
58014
- field: z13.string().optional().describe("Struct field name (add_struct_tags)"),
58015
- tag: z13.string().optional().describe("Tag key (add_struct_tags — e.g. 'json')"),
58016
- value: z13.string().optional().describe("Tag value (add_struct_tags — e.g. 'user_name,omitempty')"),
58017
- validate: z13.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
58377
+ op: z15.enum(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"]).describe("Transformation operation"),
58378
+ filePath: z15.string().describe("Path to the source file (absolute or relative to project root)"),
58379
+ container: z15.string().optional().describe("Container name for add_member — the class, struct, or impl block to insert into. Appears as 'scope' in the response."),
58380
+ code: z15.string().optional().describe("Member code to insert (add_member)"),
58381
+ position: z15.string().optional().describe("For add_member: 'first', 'last' (default), 'before:name', 'after:name'. For add_decorator: 'first' (default) or 'last' only."),
58382
+ target: z15.string().optional().describe("Target symbol name (add_derive: struct/enum, wrap_try_catch: function, add_decorator: function/class, add_struct_tags: struct)"),
58383
+ derives: z15.array(z15.string()).optional().describe("Derive macro names (add_derive — e.g. ['Clone', 'Debug'])"),
58384
+ catchBody: z15.string().optional().describe("Catch block body (wrap_try_catch — default: 'throw error;')"),
58385
+ decorator: z15.string().optional().describe("Decorator text without @ (add_decorator — e.g. 'staticmethod')"),
58386
+ field: z15.string().optional().describe("Struct field name (add_struct_tags)"),
58387
+ tag: z15.string().optional().describe("Tag key (add_struct_tags — e.g. 'json')"),
58388
+ value: z15.string().optional().describe("Tag value (add_struct_tags — e.g. 'user_name,omitempty')"),
58389
+ validate: z15.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
58018
58390
  },
58019
58391
  execute: async (args, context) => {
58020
58392
  const op = args.op;
@@ -58482,10 +58854,10 @@ ${lines}
58482
58854
  return { show: false };
58483
58855
  }
58484
58856
  if (storageDir) {
58485
- const versionFile = join23(storageDir, "last_announced_version");
58486
58857
  try {
58487
- if (existsSync15(versionFile)) {
58488
- 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();
58489
58861
  if (lastVersion === ANNOUNCEMENT_VERSION)
58490
58862
  return { show: false };
58491
58863
  }
@@ -58501,8 +58873,9 @@ ${lines}
58501
58873
  rpcServer.handle("mark-announced", async () => {
58502
58874
  if (storageDir && ANNOUNCEMENT_VERSION) {
58503
58875
  try {
58504
- mkdirSync13(storageDir, { recursive: true });
58505
- 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);
58506
58879
  } catch {}
58507
58880
  }
58508
58881
  return { success: true };