@cortexkit/aft-opencode 0.39.4 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11594,11 +11594,6 @@ function error(message, meta) {
11594
11594
  }
11595
11595
  }
11596
11596
  // ../aft-bridge/dist/bash-format.js
11597
- function appendPipeStripNote(output, note) {
11598
- return note ? `${output}
11599
-
11600
- ${note}` : output;
11601
- }
11602
11597
  function formatSeconds(ms) {
11603
11598
  return `${Number((ms / 1000).toFixed(1))}s`;
11604
11599
  }
@@ -11676,12 +11671,16 @@ function maybeAppendGrepSearchHint(output, command, aftSearchRegistered, project
11676
11671
  ${hint}`;
11677
11672
  }
11678
11673
  function shouldSuppressGrepSearchHint(command, projectRoot) {
11679
- const root = projectRoot?.trim();
11680
- if (!root)
11681
- return false;
11682
11674
  const statements = splitTopLevelStatements(command);
11683
11675
  if (statements === null)
11684
11676
  return false;
11677
+ if (allSearchPathOperandsAreDynamic(statements))
11678
+ return true;
11679
+ const root = projectRoot?.trim();
11680
+ if (!root)
11681
+ return false;
11682
+ const resolvedRoot = path.resolve(root);
11683
+ let effectiveCwd = resolvedRoot;
11685
11684
  let sawCodeSearchStatement = false;
11686
11685
  for (const statement of statements) {
11687
11686
  const firstStage = firstPipelineStage(statement);
@@ -11690,19 +11689,84 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
11690
11689
  const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11691
11690
  if (firstToken === null)
11692
11691
  continue;
11693
- if (firstToken.token !== "grep" && firstToken.token !== "rg")
11692
+ const head = firstToken.token;
11693
+ if (head === "cd") {
11694
+ effectiveCwd = nextCwdAfterCd(effectiveCwd, firstStage, firstToken.end);
11695
+ continue;
11696
+ }
11697
+ if (head !== "grep" && head !== "rg")
11694
11698
  continue;
11695
11699
  sawCodeSearchStatement = true;
11700
+ if (effectiveCwd === null)
11701
+ continue;
11702
+ if (!isDirInsideProject(resolvedRoot, effectiveCwd))
11703
+ continue;
11696
11704
  const operands = collectPathOperands(firstStage, firstToken.end);
11697
11705
  if (operands.length === 0)
11698
11706
  return false;
11699
11707
  for (const operand of operands) {
11700
- if (isPathInsideProject(root, operand))
11708
+ if (isDynamicPathOperand(operand))
11709
+ continue;
11710
+ if (isPathInsideProject(resolvedRoot, effectiveCwd, operand))
11701
11711
  return false;
11702
11712
  }
11703
11713
  }
11704
11714
  return sawCodeSearchStatement;
11705
11715
  }
11716
+ function allSearchPathOperandsAreDynamic(statements) {
11717
+ let sawDynamicOnlySearch = false;
11718
+ for (const statement of statements) {
11719
+ const firstStage = firstPipelineStage(statement);
11720
+ if (firstStage === null)
11721
+ continue;
11722
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11723
+ if (firstToken === null)
11724
+ continue;
11725
+ const head = firstToken.token;
11726
+ if (head !== "grep" && head !== "rg")
11727
+ continue;
11728
+ const operands = collectPathOperands(firstStage, firstToken.end);
11729
+ if (operands.length === 0)
11730
+ return false;
11731
+ if (operands.some((operand) => !isDynamicPathOperand(operand)))
11732
+ return false;
11733
+ sawDynamicOnlySearch = true;
11734
+ }
11735
+ return sawDynamicOnlySearch;
11736
+ }
11737
+ function nextCwdAfterCd(currentCwd, firstStage, afterCd) {
11738
+ let index = skipSpaces(firstStage, afterCd);
11739
+ let target = null;
11740
+ while (index < firstStage.length) {
11741
+ const tokenResult = readShellToken(firstStage, index);
11742
+ if (tokenResult === null)
11743
+ break;
11744
+ const { token, end } = tokenResult;
11745
+ if (end <= index)
11746
+ break;
11747
+ index = skipSpaces(firstStage, end);
11748
+ if (token === "-")
11749
+ return null;
11750
+ if (token.length > 1 && token.startsWith("-"))
11751
+ continue;
11752
+ target = token;
11753
+ break;
11754
+ }
11755
+ if (target === null)
11756
+ return path.resolve(os.homedir());
11757
+ if (target.includes("$") || target.includes("`"))
11758
+ return null;
11759
+ const expanded = expandTilde(target);
11760
+ if (path.isAbsolute(expanded))
11761
+ return path.resolve(expanded);
11762
+ if (currentCwd === null)
11763
+ return null;
11764
+ return path.resolve(currentCwd, expanded);
11765
+ }
11766
+ function isDirInsideProject(resolvedRoot, dir) {
11767
+ const rel = path.relative(resolvedRoot, path.resolve(dir));
11768
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
11769
+ }
11706
11770
  function collectPathOperands(firstStage, startAfterCommand) {
11707
11771
  const operands = [];
11708
11772
  let index = skipSpaces(firstStage, startAfterCommand);
@@ -11724,6 +11788,9 @@ function collectPathOperands(firstStage, startAfterCommand) {
11724
11788
  function looksLikePathOperand(token) {
11725
11789
  return token.includes("/") || token.startsWith("~") || token.startsWith("./") || token.startsWith("../");
11726
11790
  }
11791
+ function isDynamicPathOperand(token) {
11792
+ return token.startsWith("~") || token.includes("$") || token.includes("`");
11793
+ }
11727
11794
  function expandTilde(target) {
11728
11795
  if (!target.startsWith("~"))
11729
11796
  return target;
@@ -11736,10 +11803,9 @@ function resolvePathOperand(projectRoot, operand) {
11736
11803
  const expanded = expandTilde(operand);
11737
11804
  return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(projectRoot, expanded);
11738
11805
  }
11739
- function isPathInsideProject(projectRoot, operand) {
11740
- const root = path.resolve(projectRoot);
11741
- const resolved = resolvePathOperand(root, operand);
11742
- const rel = path.relative(root, resolved);
11806
+ function isPathInsideProject(resolvedRoot, baseCwd, operand) {
11807
+ const resolved = resolvePathOperand(baseCwd, operand);
11808
+ const rel = path.relative(resolvedRoot, resolved);
11743
11809
  return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
11744
11810
  }
11745
11811
  function splitTopLevelStatements(command) {
@@ -11932,6 +11998,7 @@ var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
11932
11998
  trace_to_symbol: 60000,
11933
11999
  trace_data: 60000,
11934
12000
  impact: 60000,
12001
+ inspect: 60000,
11935
12002
  grep: 60000,
11936
12003
  glob: 60000,
11937
12004
  semantic_search: 60000
@@ -11995,7 +12062,6 @@ ${formatStatusBar(counts)}`;
11995
12062
  // ../aft-bridge/dist/bridge.js
11996
12063
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
11997
12064
  var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
11998
- var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
11999
12065
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
12000
12066
  var STDOUT_BUFFER_COMPACT_THRESHOLD = 64 * 1024;
12001
12067
  var TERMINAL_BASH_STATUSES = new Set([
@@ -12068,28 +12134,19 @@ function compareSemver(a, b) {
12068
12134
  }
12069
12135
  return 0;
12070
12136
  }
12071
- function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
12072
- const semantic = configOverrides.semantic;
12073
- if (!semantic || typeof semantic !== "object" || Array.isArray(semantic)) {
12074
- return configOverrides;
12075
- }
12076
- const timeoutMs = semantic.timeout_ms;
12077
- if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
12078
- return configOverrides;
12079
- }
12080
- const semanticTransportBudgetMs = Math.max(bridgeTimeoutMs, LONG_RUNNING_COMMAND_TIMEOUT_MS.semantic_search ?? 0);
12081
- const maxSemanticTimeoutMs = semanticTransportBudgetMs > SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS ? semanticTransportBudgetMs - SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS : Math.max(1, semanticTransportBudgetMs - 1);
12082
- if (timeoutMs <= maxSemanticTimeoutMs) {
12083
- return configOverrides;
12084
- }
12085
- warn(`semantic.timeout_ms=${timeoutMs} exceeds the semantic transport budget; clamping to ${maxSemanticTimeoutMs}ms (budget: ${semanticTransportBudgetMs}ms)`);
12086
- return {
12087
- ...configOverrides,
12088
- semantic: {
12089
- ...semantic,
12090
- timeout_ms: maxSemanticTimeoutMs
12137
+ function coerceConfigureDroppedKeys(value) {
12138
+ if (!Array.isArray(value))
12139
+ return [];
12140
+ const dropped = [];
12141
+ for (const item of value) {
12142
+ if (!item || typeof item !== "object" || Array.isArray(item))
12143
+ continue;
12144
+ const record = item;
12145
+ if (typeof record.key === "string" && typeof record.tier === "string" && typeof record.reason === "string") {
12146
+ dropped.push({ key: record.key, tier: record.tier, reason: record.reason });
12091
12147
  }
12092
- };
12148
+ }
12149
+ return dropped;
12093
12150
  }
12094
12151
 
12095
12152
  class BridgeReplacedDuringVersionCheck extends Error {
@@ -12159,7 +12216,7 @@ class BinaryBridge {
12159
12216
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
12160
12217
  this.hangThreshold = options?.hangThreshold ?? BRIDGE_HANG_TIMEOUT_THRESHOLD;
12161
12218
  this.maxRestarts = options?.maxRestarts ?? 3;
12162
- this.configOverrides = clampSemanticTimeout(configOverrides ?? {}, this.timeoutMs);
12219
+ this.configOverrides = configOverrides ?? {};
12163
12220
  this.minVersion = options?.minVersion;
12164
12221
  this.onVersionMismatch = options?.onVersionMismatch;
12165
12222
  this.onConfigureWarnings = options?.onConfigureWarnings;
@@ -12396,18 +12453,24 @@ class BinaryBridge {
12396
12453
  }
12397
12454
  }
12398
12455
  async deliverConfigureWarnings(configResult, params, options) {
12399
- if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
12456
+ if (!this.onConfigureWarnings)
12400
12457
  return;
12401
- if (configResult.warnings.length === 0)
12458
+ const warnings = Array.isArray(configResult.warnings) ? configResult.warnings : [];
12459
+ const configDroppedKeys = coerceConfigureDroppedKeys(configResult.config_dropped_keys);
12460
+ if (warnings.length === 0 && configDroppedKeys.length === 0)
12402
12461
  return;
12403
12462
  const sessionId = typeof params.session_id === "string" ? params.session_id : undefined;
12463
+ const context = {
12464
+ projectRoot: this.cwd,
12465
+ sessionId,
12466
+ client: options?.configureWarningClient ?? (sessionId ? this.configureWarningClients.get(sessionId) : undefined),
12467
+ warnings
12468
+ };
12469
+ if (configDroppedKeys.length > 0) {
12470
+ context.configDroppedKeys = configDroppedKeys;
12471
+ }
12404
12472
  try {
12405
- await this.onConfigureWarnings({
12406
- projectRoot: this.cwd,
12407
- sessionId,
12408
- client: options?.configureWarningClient ?? (sessionId ? this.configureWarningClients.get(sessionId) : undefined),
12409
- warnings: configResult.warnings
12410
- });
12473
+ await this.onConfigureWarnings(context);
12411
12474
  } catch (err) {
12412
12475
  this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
12413
12476
  } finally {
@@ -13114,6 +13177,62 @@ function coerceStringArray(value) {
13114
13177
  }
13115
13178
  return [];
13116
13179
  }
13180
+ function coerceTargetParam(value) {
13181
+ if (Array.isArray(value)) {
13182
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
13183
+ }
13184
+ if (typeof value === "string") {
13185
+ const trimmed = value.trim();
13186
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
13187
+ try {
13188
+ const parsed = JSON.parse(trimmed);
13189
+ if (Array.isArray(parsed)) {
13190
+ return parsed.filter((entry) => typeof entry === "string" && entry.length > 0);
13191
+ }
13192
+ } catch {}
13193
+ }
13194
+ return value;
13195
+ }
13196
+ return value;
13197
+ }
13198
+ function coerceBoolean(value, defaultValue = false) {
13199
+ if (defaultValue) {
13200
+ if (value === undefined)
13201
+ return true;
13202
+ if (typeof value === "boolean")
13203
+ return value;
13204
+ if (typeof value === "number")
13205
+ return value !== 0;
13206
+ if (typeof value === "string") {
13207
+ const normalized = value.trim().toLowerCase();
13208
+ return normalized !== "false" && normalized !== "0";
13209
+ }
13210
+ return true;
13211
+ }
13212
+ if (typeof value === "boolean")
13213
+ return value;
13214
+ if (typeof value === "number")
13215
+ return value === 1;
13216
+ if (typeof value === "string") {
13217
+ const normalized = value.trim().toLowerCase();
13218
+ return normalized === "true" || normalized === "1";
13219
+ }
13220
+ return false;
13221
+ }
13222
+ function coerceOptionalInt(v, paramName, min, max) {
13223
+ if (v === undefined || v === null || v === "")
13224
+ return;
13225
+ if (typeof v === "number" && (!Number.isFinite(v) || v === 0 && min > 0))
13226
+ return;
13227
+ const n = typeof v === "string" ? Number(v) : v;
13228
+ if (typeof n !== "number" || !Number.isInteger(n)) {
13229
+ throw new Error(`${paramName} must be an integer between ${min} and ${max}`);
13230
+ }
13231
+ if (n < min || n > max) {
13232
+ throw new Error(`${paramName} must be between ${min} and ${max}`);
13233
+ }
13234
+ return n;
13235
+ }
13117
13236
  function isEmptyParam(value) {
13118
13237
  if (value === undefined || value === null)
13119
13238
  return true;
@@ -13125,10 +13244,43 @@ function isEmptyParam(value) {
13125
13244
  return Object.keys(value).length === 0;
13126
13245
  return false;
13127
13246
  }
13247
+ // ../aft-bridge/dist/config-tiers.js
13248
+ import { existsSync, readFileSync } from "node:fs";
13249
+ import { resolve as resolve2 } from "node:path";
13250
+ function readConfigTiers(opts) {
13251
+ const tiers = [];
13252
+ try {
13253
+ if (existsSync(opts.userConfigPath)) {
13254
+ const doc = readFileSync(opts.userConfigPath, "utf-8");
13255
+ tiers.push({
13256
+ tier: "user",
13257
+ source: resolve2(opts.userConfigPath),
13258
+ doc
13259
+ });
13260
+ }
13261
+ } catch {}
13262
+ try {
13263
+ if (existsSync(opts.projectConfigPath)) {
13264
+ const doc = readFileSync(opts.projectConfigPath, "utf-8");
13265
+ tiers.push({
13266
+ tier: "project",
13267
+ source: resolve2(opts.projectConfigPath),
13268
+ doc
13269
+ });
13270
+ }
13271
+ } catch {}
13272
+ return tiers;
13273
+ }
13274
+ function formatDroppedKeyWarnings(dropped) {
13275
+ if (!dropped || !Array.isArray(dropped)) {
13276
+ return [];
13277
+ }
13278
+ return dropped.map((item) => `Ignoring ${item.key} from ${item.tier} config (${item.reason})`);
13279
+ }
13128
13280
  // ../aft-bridge/dist/downloader.js
13129
13281
  import { spawnSync } from "node:child_process";
13130
13282
  import { createHash, randomUUID } from "node:crypto";
13131
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
13283
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync2, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
13132
13284
  import { homedir as homedir4 } from "node:os";
13133
13285
  import { join as join3 } from "node:path";
13134
13286
  import { Readable } from "node:stream";
@@ -13200,7 +13352,7 @@ function getCachedBinaryPath(version) {
13200
13352
  if (!version)
13201
13353
  return null;
13202
13354
  const binaryPath = join3(getCacheDir(), version, getBinaryName());
13203
- return existsSync(binaryPath) ? binaryPath : null;
13355
+ return existsSync2(binaryPath) ? binaryPath : null;
13204
13356
  }
13205
13357
  async function downloadBinary(version) {
13206
13358
  const archMap = PLATFORM_ARCH_MAP[process.platform] ?? {};
@@ -13219,7 +13371,7 @@ async function downloadBinary(version) {
13219
13371
  const versionedCacheDir = join3(getCacheDir(), tag);
13220
13372
  const binaryName = getBinaryName();
13221
13373
  const binaryPath = join3(versionedCacheDir, binaryName);
13222
- if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13374
+ if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13223
13375
  return binaryPath;
13224
13376
  }
13225
13377
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
@@ -13233,11 +13385,11 @@ async function downloadBinary(version) {
13233
13385
  let checksumTimeout = null;
13234
13386
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
13235
13387
  try {
13236
- if (!existsSync(versionedCacheDir)) {
13388
+ if (!existsSync2(versionedCacheDir)) {
13237
13389
  mkdirSync(versionedCacheDir, { recursive: true });
13238
13390
  }
13239
13391
  releaseLock = await acquireDownloadLock(lockPath);
13240
- if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13392
+ if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13241
13393
  return binaryPath;
13242
13394
  }
13243
13395
  binaryController = new AbortController;
@@ -13302,7 +13454,7 @@ async function downloadBinary(version) {
13302
13454
  renameSync(tmpPath, binaryPath);
13303
13455
  }
13304
13456
  try {
13305
- if (existsSync(tmpPath))
13457
+ if (existsSync2(tmpPath))
13306
13458
  unlinkSync(tmpPath);
13307
13459
  } catch {
13308
13460
  warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
@@ -13312,7 +13464,7 @@ async function downloadBinary(version) {
13312
13464
  } catch (err) {
13313
13465
  const msg = err instanceof Error ? err.message : String(err);
13314
13466
  error(`Failed to download AFT binary: ${msg}`);
13315
- if (existsSync(tmpPath)) {
13467
+ if (existsSync2(tmpPath)) {
13316
13468
  try {
13317
13469
  unlinkSync(tmpPath);
13318
13470
  } catch {}
@@ -13356,7 +13508,7 @@ async function acquireDownloadLock(lockPath) {
13356
13508
  closeSync(fd);
13357
13509
  } catch {}
13358
13510
  try {
13359
- if (readFileSync(lockPath, "utf-8") === owner) {
13511
+ if (readFileSync2(lockPath, "utf-8") === owner) {
13360
13512
  rmSync(lockPath, { force: true });
13361
13513
  }
13362
13514
  } catch {}
@@ -13377,7 +13529,7 @@ async function acquireDownloadLock(lockPath) {
13377
13529
  if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
13378
13530
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
13379
13531
  }
13380
- await new Promise((resolve2) => setTimeout(resolve2, 100));
13532
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
13381
13533
  }
13382
13534
  }
13383
13535
  }
@@ -13474,20 +13626,71 @@ function stripJsoncSymbols(value) {
13474
13626
  }
13475
13627
  // ../aft-bridge/dist/migration.js
13476
13628
  import { spawnSync as spawnSync2 } from "node:child_process";
13477
- import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
13478
- import { homedir as homedir6, tmpdir } from "node:os";
13479
- import { dirname as dirname2, join as join6 } from "node:path";
13629
+ import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync4, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
13630
+ import { homedir as homedir7, tmpdir } from "node:os";
13631
+ import { basename, dirname as dirname2, join as join6, resolve as resolve4 } from "node:path";
13480
13632
 
13481
13633
  // ../aft-bridge/dist/paths.js
13482
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync } from "node:fs";
13483
- import { dirname, join as join4 } from "node:path";
13634
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync } from "node:fs";
13635
+ import { homedir as homedir5 } from "node:os";
13636
+ import { dirname, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
13637
+ function homeDir() {
13638
+ if (process.platform === "win32")
13639
+ return process.env.USERPROFILE || process.env.HOME || homedir5();
13640
+ return process.env.HOME || homedir5();
13641
+ }
13642
+ function configHome() {
13643
+ const xdg = process.env.XDG_CONFIG_HOME;
13644
+ if (xdg && isAbsolute2(xdg))
13645
+ return xdg;
13646
+ return join4(homeDir(), ".config");
13647
+ }
13648
+ function legacyOpenCodeConfigDir() {
13649
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13650
+ if (envDir)
13651
+ return resolve3(envDir);
13652
+ return join4(configHome(), "opencode");
13653
+ }
13654
+ function legacyPiAgentDir() {
13655
+ return join4(homeDir(), ".pi", "agent");
13656
+ }
13657
+ function legacySources(basePath, label, harness) {
13658
+ return [
13659
+ { path: `${basePath}.jsonc`, label: `${label} aft.jsonc`, harness },
13660
+ { path: `${basePath}.json`, label: `${label} aft.json`, harness }
13661
+ ];
13662
+ }
13663
+ function resolveCortexKitUserConfigPath() {
13664
+ return join4(configHome(), "cortexkit", "aft.jsonc");
13665
+ }
13666
+ function resolveCortexKitProjectConfigPath(projectDirectory) {
13667
+ return join4(projectDirectory, ".cortexkit", "aft.jsonc");
13668
+ }
13669
+ function resolveCortexKitConfigPaths(projectDirectory) {
13670
+ return {
13671
+ userConfigPath: resolveCortexKitUserConfigPath(),
13672
+ projectConfigPath: resolveCortexKitProjectConfigPath(projectDirectory)
13673
+ };
13674
+ }
13675
+ function resolveLegacyAftConfigSources(projectDirectory) {
13676
+ return {
13677
+ user: [
13678
+ ...legacySources(join4(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13679
+ ...legacySources(join4(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13680
+ ],
13681
+ project: [
13682
+ ...legacySources(join4(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13683
+ ...legacySources(join4(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13684
+ ]
13685
+ };
13686
+ }
13484
13687
  function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
13485
13688
  return join4(storageRoot, harness, ...segments);
13486
13689
  }
13487
13690
  function repairRootScopedStorageFile(storageRoot, harness, fileName) {
13488
13691
  const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
13489
13692
  const rootPath = join4(storageRoot, fileName);
13490
- if (existsSync2(harnessPath) || !existsSync2(rootPath))
13693
+ if (existsSync3(harnessPath) || !existsSync3(rootPath))
13491
13694
  return harnessPath;
13492
13695
  try {
13493
13696
  mkdirSync2(dirname(harnessPath), { recursive: true });
@@ -13501,8 +13704,8 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
13501
13704
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
13502
13705
  let lastVersion = "";
13503
13706
  try {
13504
- if (existsSync2(versionFile)) {
13505
- lastVersion = readFileSync2(versionFile, "utf-8").trim();
13707
+ if (existsSync3(versionFile)) {
13708
+ lastVersion = readFileSync3(versionFile, "utf-8").trim();
13506
13709
  }
13507
13710
  } catch {
13508
13711
  return false;
@@ -13530,9 +13733,9 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
13530
13733
 
13531
13734
  // ../aft-bridge/dist/resolver.js
13532
13735
  import { execSync } from "node:child_process";
13533
- import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13736
+ import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13534
13737
  import { createRequire as createRequire2 } from "node:module";
13535
- import { homedir as homedir5 } from "node:os";
13738
+ import { homedir as homedir6 } from "node:os";
13536
13739
  import { join as join5 } from "node:path";
13537
13740
  var ensureBinaryForResolver = ensureBinary;
13538
13741
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
@@ -13545,7 +13748,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
13545
13748
  const versionedDir = join5(cacheDir, tag);
13546
13749
  const ext = process.platform === "win32" ? ".exe" : "";
13547
13750
  const cachedPath = join5(versionedDir, `aft${ext}`);
13548
- if (existsSync3(cachedPath)) {
13751
+ if (existsSync4(cachedPath)) {
13549
13752
  const cachedVersion = readBinaryVersion(cachedPath);
13550
13753
  if (cachedVersion === version)
13551
13754
  return cachedPath;
@@ -13557,7 +13760,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
13557
13760
  if (process.platform !== "win32") {
13558
13761
  chmodSync2(tmpPath, 493);
13559
13762
  }
13560
- if (process.platform === "win32" && existsSync3(cachedPath)) {
13763
+ if (process.platform === "win32" && existsSync4(cachedPath)) {
13561
13764
  try {
13562
13765
  unlinkSync2(cachedPath);
13563
13766
  } catch {}
@@ -13574,7 +13777,7 @@ function normalizeBareVersion(version) {
13574
13777
  return version.startsWith("v") ? version.slice(1) : version;
13575
13778
  }
13576
13779
  function homeDirFromEnv(env) {
13577
- return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir5();
13780
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir6();
13578
13781
  }
13579
13782
  function cacheDirFromEnv(env) {
13580
13783
  if (process.platform === "win32") {
@@ -13586,7 +13789,7 @@ function cacheDirFromEnv(env) {
13586
13789
  }
13587
13790
  function cachedBinaryPathFromEnv(version, env, ext) {
13588
13791
  const binaryPath = join5(cacheDirFromEnv(env), version, `aft${ext}`);
13589
- return existsSync3(binaryPath) ? binaryPath : null;
13792
+ return existsSync4(binaryPath) ? binaryPath : null;
13590
13793
  }
13591
13794
  function isExpectedCachedBinary2(binaryPath, expectedVersion) {
13592
13795
  const expected = normalizeBareVersion(expectedVersion);
@@ -13675,7 +13878,7 @@ function findBinarySync(expectedVersion) {
13675
13878
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
13676
13879
  const req = createRequire2(import.meta.url);
13677
13880
  const resolved = req.resolve(packageBin);
13678
- if (existsSync3(resolved)) {
13881
+ if (existsSync4(resolved)) {
13679
13882
  const npmVersion = readBinaryVersion(resolved);
13680
13883
  if (npmVersion === null) {
13681
13884
  warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
@@ -13705,7 +13908,7 @@ function findBinarySync(expectedVersion) {
13705
13908
  }
13706
13909
  } catch {}
13707
13910
  const cargoPath = join5(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
13708
- if (existsSync3(cargoPath)) {
13911
+ if (existsSync4(cargoPath)) {
13709
13912
  const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
13710
13913
  if (usable)
13711
13914
  return usable;
@@ -13750,23 +13953,343 @@ function dataHome() {
13750
13953
  if (process.env.XDG_DATA_HOME)
13751
13954
  return process.env.XDG_DATA_HOME;
13752
13955
  if (process.platform === "win32") {
13753
- return process.env.LOCALAPPDATA || process.env.APPDATA || join6(homeDir(), "AppData", "Local");
13956
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join6(homeDir2(), "AppData", "Local");
13754
13957
  }
13755
- return join6(homeDir(), ".local", "share");
13958
+ return join6(homeDir2(), ".local", "share");
13756
13959
  }
13757
- function homeDir() {
13960
+ function homeDir2() {
13758
13961
  if (process.platform === "win32")
13759
- return process.env.USERPROFILE || process.env.HOME || homedir6();
13760
- return process.env.HOME || homedir6();
13962
+ return process.env.USERPROFILE || process.env.HOME || homedir7();
13963
+ return process.env.HOME || homedir7();
13761
13964
  }
13762
13965
  function resolveLegacyStorageRoot(harness) {
13763
13966
  if (harness === "pi")
13764
- return join6(homeDir(), ".pi", "agent", "aft");
13967
+ return join6(homeDir2(), ".pi", "agent", "aft");
13765
13968
  return join6(dataHome(), "opencode", "storage", "plugin", "aft");
13766
13969
  }
13767
13970
  function resolveCortexKitStorageRoot() {
13768
13971
  return join6(dataHome(), "cortexkit", "aft");
13769
13972
  }
13973
+ function stripJsoncForParse(input) {
13974
+ let out = "";
13975
+ let inString = false;
13976
+ let escaped = false;
13977
+ for (let i = 0;i < input.length; i++) {
13978
+ const ch = input[i];
13979
+ const next = input[i + 1];
13980
+ if (inString) {
13981
+ out += ch;
13982
+ if (escaped) {
13983
+ escaped = false;
13984
+ } else if (ch === "\\") {
13985
+ escaped = true;
13986
+ } else if (ch === '"') {
13987
+ inString = false;
13988
+ }
13989
+ continue;
13990
+ }
13991
+ if (ch === '"') {
13992
+ inString = true;
13993
+ out += ch;
13994
+ continue;
13995
+ }
13996
+ if (ch === "/" && next === "/") {
13997
+ while (i < input.length && input[i] !== `
13998
+ `)
13999
+ i++;
14000
+ out += `
14001
+ `;
14002
+ continue;
14003
+ }
14004
+ if (ch === "/" && next === "*") {
14005
+ i += 2;
14006
+ while (i < input.length && !(input[i] === "*" && input[i + 1] === "/"))
14007
+ i++;
14008
+ i++;
14009
+ out += " ";
14010
+ continue;
14011
+ }
14012
+ out += ch;
14013
+ }
14014
+ let withoutTrailingCommas = "";
14015
+ inString = false;
14016
+ escaped = false;
14017
+ for (let i = 0;i < out.length; i++) {
14018
+ const ch = out[i];
14019
+ if (inString) {
14020
+ withoutTrailingCommas += ch;
14021
+ if (escaped) {
14022
+ escaped = false;
14023
+ } else if (ch === "\\") {
14024
+ escaped = true;
14025
+ } else if (ch === '"') {
14026
+ inString = false;
14027
+ }
14028
+ continue;
14029
+ }
14030
+ if (ch === '"') {
14031
+ inString = true;
14032
+ withoutTrailingCommas += ch;
14033
+ continue;
14034
+ }
14035
+ if (ch === ",") {
14036
+ let j = i + 1;
14037
+ while (j < out.length && /\s/.test(out[j]))
14038
+ j++;
14039
+ if (out[j] === "}" || out[j] === "]")
14040
+ continue;
14041
+ }
14042
+ withoutTrailingCommas += ch;
14043
+ }
14044
+ return withoutTrailingCommas;
14045
+ }
14046
+ function sortJson(value) {
14047
+ if (Array.isArray(value))
14048
+ return value.map(sortJson);
14049
+ if (value && typeof value === "object") {
14050
+ const sorted = {};
14051
+ for (const key of Object.keys(value).sort()) {
14052
+ sorted[key] = sortJson(value[key]);
14053
+ }
14054
+ return sorted;
14055
+ }
14056
+ return value;
14057
+ }
14058
+ function normalizedJsoncSemantics(content) {
14059
+ return JSON.stringify(sortJson(JSON.parse(stripJsoncForParse(content))));
14060
+ }
14061
+ function fileSemanticsMatch(a, b) {
14062
+ try {
14063
+ return normalizedJsoncSemantics(a) === normalizedJsoncSemantics(b);
14064
+ } catch {
14065
+ return a === b;
14066
+ }
14067
+ }
14068
+ function sleepSync(ms) {
14069
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
14070
+ }
14071
+ function acquireConfigMigrationLock(lockDir) {
14072
+ const deadline = Date.now() + 30000;
14073
+ while (true) {
14074
+ try {
14075
+ mkdirSync4(lockDir, { recursive: false });
14076
+ return () => {
14077
+ try {
14078
+ rmSync2(lockDir, { recursive: true, force: true });
14079
+ } catch {}
14080
+ };
14081
+ } catch (err) {
14082
+ const code = err?.code;
14083
+ if (code !== "EEXIST")
14084
+ throw err;
14085
+ try {
14086
+ const ageMs = Date.now() - statSync2(lockDir).mtimeMs;
14087
+ if (ageMs > 60000) {
14088
+ rmSync2(lockDir, { recursive: true, force: true });
14089
+ continue;
14090
+ }
14091
+ } catch {}
14092
+ if (Date.now() >= deadline) {
14093
+ throw new Error(`timed out waiting for config migration lock ${lockDir}`);
14094
+ }
14095
+ sleepSync(25);
14096
+ }
14097
+ }
14098
+ }
14099
+ function atomicCopyConfigFile(sourcePath, targetPath) {
14100
+ mkdirSync4(dirname2(targetPath), { recursive: true });
14101
+ const tmpPath = join6(dirname2(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14102
+ let fd = null;
14103
+ try {
14104
+ fd = openSync3(tmpPath, "wx", 384);
14105
+ writeFileSync2(fd, readFileSync4(sourcePath));
14106
+ closeSync3(fd);
14107
+ fd = null;
14108
+ renameSync4(tmpPath, targetPath);
14109
+ } catch (err) {
14110
+ if (fd !== null) {
14111
+ try {
14112
+ closeSync3(fd);
14113
+ } catch {}
14114
+ }
14115
+ try {
14116
+ unlinkSync3(tmpPath);
14117
+ } catch {}
14118
+ throw err;
14119
+ }
14120
+ }
14121
+ function atomicWriteConfigFile(targetPath, content) {
14122
+ mkdirSync4(dirname2(targetPath), { recursive: true });
14123
+ const tmpPath = join6(dirname2(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14124
+ let fd = null;
14125
+ try {
14126
+ fd = openSync3(tmpPath, "wx", 384);
14127
+ writeFileSync2(fd, content);
14128
+ closeSync3(fd);
14129
+ fd = null;
14130
+ renameSync4(tmpPath, targetPath);
14131
+ } catch (err) {
14132
+ if (fd !== null) {
14133
+ try {
14134
+ closeSync3(fd);
14135
+ } catch {}
14136
+ }
14137
+ try {
14138
+ unlinkSync3(tmpPath);
14139
+ } catch {}
14140
+ throw err;
14141
+ }
14142
+ }
14143
+ var MOVED_MARKER_SUFFIX = ".MOVED_READPLEASE";
14144
+ function nonClobberingSidecarPath(desiredPath) {
14145
+ if (!existsSync5(desiredPath))
14146
+ return desiredPath;
14147
+ for (let index = 1;index < Number.MAX_SAFE_INTEGER; index++) {
14148
+ const candidate = `${desiredPath}.${index}`;
14149
+ if (!existsSync5(candidate))
14150
+ return candidate;
14151
+ }
14152
+ throw new Error(`could not find available preservation sidecar path for ${desiredPath}`);
14153
+ }
14154
+ function movedMarkerContent(targetPath, originalName, originalContent) {
14155
+ const header = [
14156
+ "// AFT configuration moved.",
14157
+ "//",
14158
+ "// AFT now reads its configuration from one shared CortexKit location",
14159
+ "// instead of a per-agent path. The settings that were in this file have",
14160
+ "// been moved to:",
14161
+ "//",
14162
+ `// ${targetPath}`,
14163
+ "//",
14164
+ "// Edit that file to change AFT settings. This location is no longer read",
14165
+ "// by AFT.",
14166
+ "//",
14167
+ `// To undo, rename this file back to "${originalName}" (and remove the`,
14168
+ "// CortexKit copy above if you want this location to take precedence).",
14169
+ "//",
14170
+ "// Your original settings are preserved below for reference.",
14171
+ "",
14172
+ ""
14173
+ ].join(`
14174
+ `);
14175
+ return `${header}${originalContent}`;
14176
+ }
14177
+ function markLegacySourcesMovedAside(sources, targetPath, logger) {
14178
+ const warnings = [];
14179
+ const info = logger?.info ?? logger?.log;
14180
+ for (const source of sources) {
14181
+ const desiredMarkerPath = `${source.path}${MOVED_MARKER_SUFFIX}`;
14182
+ const markerPath = nonClobberingSidecarPath(desiredMarkerPath);
14183
+ try {
14184
+ const original = readFileSync4(source.path, "utf-8");
14185
+ if (markerPath !== desiredMarkerPath) {
14186
+ logger?.warn?.(`Preserving existing legacy AFT marker ${desiredMarkerPath}; writing new marker to ${markerPath}`);
14187
+ }
14188
+ atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath, basename(source.path), original));
14189
+ unlinkSync3(source.path);
14190
+ info?.(`Moved legacy AFT config ${source.path} aside to ${markerPath}; AFT now reads ${targetPath}`);
14191
+ } catch (err) {
14192
+ const msg = err instanceof Error ? err.message : String(err);
14193
+ warnings.push(`AFT could not move legacy config ${source.path} aside (${msg}); it is now stale and ignored. Delete it manually — AFT reads ${targetPath}.`);
14194
+ logger?.warn?.(`Could not move legacy AFT config ${source.path} aside (${msg}); AFT reads ${targetPath}`);
14195
+ }
14196
+ }
14197
+ return warnings;
14198
+ }
14199
+ var PRESERVED_OLD_SUFFIX = "_OLD";
14200
+ function preservedOldContent(losingHarness, winningHarness, targetPath, originalContent) {
14201
+ const winnerLine = winningHarness ? `// configs differed, so ${winningHarness}'s config won (first harness to run the` : "// configs differed, so the config that was migrated first won and is the";
14202
+ const header = [
14203
+ `// AFT config from ${losingHarness} — preserved, NOT read.`,
14204
+ "//",
14205
+ "// AFT now reads one shared config for all harnesses. Your OpenCode and Pi",
14206
+ winnerLine,
14207
+ winningHarness ? "// migration) and is now the active config at:" : "// active config at:",
14208
+ "//",
14209
+ `// ${targetPath}`,
14210
+ "//",
14211
+ `// This file holds ${losingHarness}'s previous settings. Merge anything you`,
14212
+ "// want to keep into the active config above, then delete this file. AFT does",
14213
+ "// not read this path.",
14214
+ "",
14215
+ ""
14216
+ ].join(`
14217
+ `);
14218
+ return `${header}${originalContent}`;
14219
+ }
14220
+ function preserveDifferingSourceAsOld(source, winningHarness, targetPath, logger) {
14221
+ const losingHarness = source.harness ?? "pi";
14222
+ const desiredOldPath = `${targetPath}.${losingHarness}${PRESERVED_OLD_SUFFIX}`;
14223
+ const oldPath = nonClobberingSidecarPath(desiredOldPath);
14224
+ try {
14225
+ if (oldPath !== desiredOldPath) {
14226
+ logger?.warn?.(`Preserving existing ${losingHarness} config sidecar ${desiredOldPath}; writing new preserved config to ${oldPath}`);
14227
+ }
14228
+ atomicWriteConfigFile(oldPath, preservedOldContent(losingHarness, winningHarness, targetPath, source.content));
14229
+ } catch (err) {
14230
+ const msg = err instanceof Error ? err.message : String(err);
14231
+ logger?.warn?.(`Could not preserve ${losingHarness} config as ${oldPath} (${msg})`);
14232
+ }
14233
+ const usingDesc = winningHarness ? `${winningHarness}'s config` : "the existing shared config";
14234
+ return `AFT found different OpenCode and Pi configs during config unification. ` + `Using ${usingDesc} at ${targetPath}; preserved ${losingHarness}'s previous ` + `config at ${oldPath} — merge any settings you want to keep, then delete it.`;
14235
+ }
14236
+ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
14237
+ const uniquePaths = [...new Set([targetPath, ...paths])];
14238
+ return `AFT ${scope} config migration refused: ${reason}. ` + `Legacy and CortexKit config paths collapse to one file, but AFT will not overwrite or merge them automatically. ` + `Please consolidate manually into ${targetPath}. Paths: ${uniquePaths.join(" ; ")}`;
14239
+ }
14240
+ function migrateAftConfigFile(opts) {
14241
+ const warnings = [];
14242
+ const resolvedTarget = resolve4(opts.targetPath);
14243
+ const existingSources = opts.legacySources.filter((source) => existsSync5(source.path) && resolve4(source.path) !== resolvedTarget);
14244
+ const info = opts.logger?.info ?? opts.logger?.log;
14245
+ if (existingSources.length === 0) {
14246
+ return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
14247
+ }
14248
+ mkdirSync4(dirname2(opts.targetPath), { recursive: true });
14249
+ const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
14250
+ try {
14251
+ const sources = existingSources.map((source) => ({
14252
+ ...source,
14253
+ content: readFileSync4(source.path, "utf-8")
14254
+ }));
14255
+ if (existsSync5(opts.targetPath)) {
14256
+ const targetContent = readFileSync4(opts.targetPath, "utf-8");
14257
+ for (const source of sources) {
14258
+ if (fileSemanticsMatch(source.content, targetContent))
14259
+ continue;
14260
+ warnings.push(preserveDifferingSourceAsOld(source, undefined, opts.targetPath, opts.logger));
14261
+ }
14262
+ info?.(`AFT ${opts.scope} config already present at ${opts.targetPath}; reconciled ${sources.length} legacy source(s)`);
14263
+ warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
14264
+ return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
14265
+ }
14266
+ const winner = (opts.operatingHarness ? sources.find((source) => source.harness === opts.operatingHarness) : undefined) ?? sources[0];
14267
+ atomicCopyConfigFile(winner.path, opts.targetPath);
14268
+ info?.(`Migrated AFT ${opts.scope} config from ${winner.path} to ${opts.targetPath}`);
14269
+ for (const source of sources) {
14270
+ if (source.path === winner.path)
14271
+ continue;
14272
+ if (fileSemanticsMatch(source.content, winner.content))
14273
+ continue;
14274
+ warnings.push(preserveDifferingSourceAsOld(source, winner.harness, opts.targetPath, opts.logger));
14275
+ }
14276
+ warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
14277
+ return {
14278
+ migrated: true,
14279
+ conflict: false,
14280
+ sourcePath: winner.path,
14281
+ targetPath: opts.targetPath,
14282
+ warnings
14283
+ };
14284
+ } catch (err) {
14285
+ const message = visibleConfigMigrationWarning(opts.scope, opts.targetPath, existingSources.map((source) => source.path), `migration failed (${err instanceof Error ? err.message : String(err)})`);
14286
+ warnings.push(message);
14287
+ opts.logger?.warn?.(message);
14288
+ return { migrated: false, conflict: true, targetPath: opts.targetPath, warnings };
14289
+ } finally {
14290
+ release();
14291
+ }
14292
+ }
13770
14293
  function tail(value) {
13771
14294
  if (!value)
13772
14295
  return "";
@@ -13794,11 +14317,11 @@ async function ensureStorageMigrated(opts) {
13794
14317
  const newRoot = resolveCortexKitStorageRoot();
13795
14318
  const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
13796
14319
  const info = opts.logger?.info ?? opts.logger?.log;
13797
- if (existsSync4(targetMarker)) {
14320
+ if (existsSync5(targetMarker)) {
13798
14321
  info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
13799
14322
  return;
13800
14323
  }
13801
- if (!existsSync4(legacyRoot)) {
14324
+ if (!existsSync5(legacyRoot)) {
13802
14325
  info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
13803
14326
  return;
13804
14327
  }
@@ -13846,14 +14369,14 @@ async function ensureStorageMigrated(opts) {
13846
14369
  throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
13847
14370
  }
13848
14371
  // ../aft-bridge/dist/npm-resolver.js
13849
- import { readdirSync, statSync as statSync2 } from "node:fs";
13850
- import { homedir as homedir7 } from "node:os";
13851
- import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join7 } from "node:path";
14372
+ import { readdirSync, statSync as statSync3 } from "node:fs";
14373
+ import { homedir as homedir8 } from "node:os";
14374
+ import { delimiter, dirname as dirname3, isAbsolute as isAbsolute3, join as join7 } from "node:path";
13852
14375
  function defaultDeps() {
13853
14376
  return {
13854
14377
  platform: process.platform,
13855
14378
  env: process.env,
13856
- home: homedir7(),
14379
+ home: homedir8(),
13857
14380
  execPath: process.execPath
13858
14381
  };
13859
14382
  }
@@ -13862,7 +14385,7 @@ function npmBinaryName(platform) {
13862
14385
  }
13863
14386
  function isFile(p) {
13864
14387
  try {
13865
- return statSync2(p).isFile();
14388
+ return statSync3(p).isFile();
13866
14389
  } catch {
13867
14390
  return false;
13868
14391
  }
@@ -13872,7 +14395,7 @@ function npmFromPath(deps) {
13872
14395
  const raw = deps.env.PATH ?? deps.env.Path ?? "";
13873
14396
  for (const entry of raw.split(delimiter)) {
13874
14397
  const dir = entry.trim().replace(/^"|"$/g, "");
13875
- if (!dir || !isAbsolute2(dir))
14398
+ if (!dir || !isAbsolute3(dir))
13876
14399
  continue;
13877
14400
  if (isFile(join7(dir, name)))
13878
14401
  return dir;
@@ -13965,8 +14488,8 @@ function isNpmAvailable(deps = defaultDeps()) {
13965
14488
  // ../aft-bridge/dist/onnx-runtime.js
13966
14489
  import { execFileSync } from "node:child_process";
13967
14490
  import { createHash as createHash2 } from "node:crypto";
13968
- import { chmodSync as chmodSync3, closeSync as closeSync3, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync3, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
13969
- import { basename, dirname as dirname4, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve2, win32 } from "node:path";
14491
+ import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync4, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
14492
+ import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve5, win32 } from "node:path";
13970
14493
  import { Readable as Readable2 } from "node:stream";
13971
14494
  import { pipeline as pipeline2 } from "node:stream/promises";
13972
14495
  var ORT_VERSION = "1.24.4";
@@ -14036,7 +14559,7 @@ async function ensureOnnxRuntime(storageDir) {
14036
14559
  const libName = info?.libName ?? "libonnxruntime.dylib";
14037
14560
  const resolvedOrtDir = resolveCachedOnnxRuntimeDir(ortVersionDir, libName);
14038
14561
  const libPath = join8(resolvedOrtDir, libName);
14039
- if (existsSync5(libPath)) {
14562
+ if (existsSync6(libPath)) {
14040
14563
  const meta = readOnnxInstalledMeta(ortVersionDir);
14041
14564
  if (meta?.sha256) {
14042
14565
  try {
@@ -14098,7 +14621,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14098
14621
  abandoned = true;
14099
14622
  } else {
14100
14623
  try {
14101
- const ageMs = Date.now() - statSync3(stagingDir).mtimeMs;
14624
+ const ageMs = Date.now() - statSync4(stagingDir).mtimeMs;
14102
14625
  abandoned = ageMs > STALE_LOCK_MS;
14103
14626
  } catch {
14104
14627
  abandoned = true;
@@ -14113,7 +14636,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14113
14636
  if (abandoned) {
14114
14637
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
14115
14638
  try {
14116
- rmSync2(stagingDir, { recursive: true, force: true });
14639
+ rmSync3(stagingDir, { recursive: true, force: true });
14117
14640
  } catch (err) {
14118
14641
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
14119
14642
  }
@@ -14123,9 +14646,9 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14123
14646
  }
14124
14647
  function cleanupIncompleteTargetIfUnowned(ortDir) {
14125
14648
  try {
14126
- if (existsSync5(ortDir) && !existsSync5(join8(ortDir, ONNX_INSTALLED_META_FILE))) {
14649
+ if (existsSync6(ortDir) && !existsSync6(join8(ortDir, ONNX_INSTALLED_META_FILE))) {
14127
14650
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
14128
- rmSync2(ortDir, { recursive: true, force: true });
14651
+ rmSync3(ortDir, { recursive: true, force: true });
14129
14652
  }
14130
14653
  } catch (err) {
14131
14654
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -14135,7 +14658,7 @@ var REQUIRED_ORT_MAJOR = 1;
14135
14658
  var REQUIRED_ORT_MIN_MINOR = 20;
14136
14659
  var INVALID_ORT_VERSION = "<invalid>";
14137
14660
  function parseOnnxVersionFromPath(value) {
14138
- const name = basename(value);
14661
+ const name = basename2(value);
14139
14662
  const semverish = name.match(/(?:^|[._-])(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.(?:dylib|dll))?$/);
14140
14663
  if (semverish)
14141
14664
  return semverish[1].split(/[-+]/, 1)[0];
@@ -14152,7 +14675,7 @@ function parseOnnxVersionFromDirectoryPath(value) {
14152
14675
  }
14153
14676
  function isPathInsideRoot(root, candidate) {
14154
14677
  const rel = relative2(root, candidate);
14155
- return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute3(rel) && !win32.isAbsolute(rel);
14678
+ return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute4(rel) && !win32.isAbsolute(rel);
14156
14679
  }
14157
14680
  function detectOnnxVersion(libDir, libName) {
14158
14681
  try {
@@ -14168,7 +14691,7 @@ function detectOnnxVersion(libDir, libName) {
14168
14691
  return version;
14169
14692
  }
14170
14693
  const base = join8(libDir, libName);
14171
- if (existsSync5(base)) {
14694
+ if (existsSync6(base)) {
14172
14695
  try {
14173
14696
  const real = realpathSync(base);
14174
14697
  const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
@@ -14203,7 +14726,7 @@ function pathEntriesForPlatform() {
14203
14726
  return pathEnvValue().split(delimiter2).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
14204
14727
  if (!entry || entry === "." || entry.includes("\x00"))
14205
14728
  return false;
14206
- return isAbsolute3(entry) || win32.isAbsolute(entry);
14729
+ return isAbsolute4(entry) || win32.isAbsolute(entry);
14207
14730
  });
14208
14731
  }
14209
14732
  function directoryContainsLibrary(dir, libName) {
@@ -14219,10 +14742,10 @@ function directoryContainsLibrary(dir, libName) {
14219
14742
  }
14220
14743
  }
14221
14744
  function resolveCachedOnnxRuntimeDir(ortVersionDir, libName) {
14222
- if (existsSync5(join8(ortVersionDir, libName)))
14745
+ if (existsSync6(join8(ortVersionDir, libName)))
14223
14746
  return ortVersionDir;
14224
14747
  const libSubdir = join8(ortVersionDir, "lib");
14225
- if (existsSync5(join8(libSubdir, libName)))
14748
+ if (existsSync6(join8(libSubdir, libName)))
14226
14749
  return libSubdir;
14227
14750
  return ortVersionDir;
14228
14751
  }
@@ -14243,7 +14766,7 @@ function findSystemOnnxRuntime(libName) {
14243
14766
  if (!userProfile)
14244
14767
  return nugetPaths;
14245
14768
  const nugetPackageDir = join8(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
14246
- if (!existsSync5(nugetPackageDir))
14769
+ if (!existsSync6(nugetPackageDir))
14247
14770
  return nugetPaths;
14248
14771
  try {
14249
14772
  for (const entry of readdirSync2(nugetPackageDir, { withFileTypes: true })) {
@@ -14263,7 +14786,7 @@ function findSystemOnnxRuntime(libName) {
14263
14786
  const normalizeCase = process.platform === "win32" || process.platform === "darwin";
14264
14787
  const seen = new Set;
14265
14788
  const uniquePaths = searchPaths.filter((p) => {
14266
- let key = resolve2(p).replace(/[/\\]+$/, "");
14789
+ let key = resolve5(p).replace(/[/\\]+$/, "");
14267
14790
  if (normalizeCase)
14268
14791
  key = key.toLowerCase();
14269
14792
  if (seen.has(key))
@@ -14277,7 +14800,7 @@ function findSystemOnnxRuntime(libName) {
14277
14800
  if (process.platform === "win32") {
14278
14801
  if (!directoryContainsLibrary(dir, libName))
14279
14802
  continue;
14280
- } else if (!existsSync5(libPath)) {
14803
+ } else if (!existsSync6(libPath)) {
14281
14804
  continue;
14282
14805
  }
14283
14806
  const version = detectOnnxVersion(dir, libName);
@@ -14326,7 +14849,7 @@ async function downloadFileWithCap(url, destPath) {
14326
14849
  await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
14327
14850
  } catch (err) {
14328
14851
  try {
14329
- unlinkSync3(destPath);
14852
+ unlinkSync4(destPath);
14330
14853
  } catch {}
14331
14854
  throw err;
14332
14855
  } finally {
@@ -14343,15 +14866,15 @@ function validateExtractedTree(stagingRoot) {
14343
14866
  const lst = lstatSync(fullPath);
14344
14867
  if (lst.isSymbolicLink()) {
14345
14868
  const linkTarget = readlinkSync(fullPath);
14346
- const resolvedTarget = resolve2(dirname4(fullPath), linkTarget);
14869
+ const resolvedTarget = resolve5(dirname4(fullPath), linkTarget);
14347
14870
  const rel2 = relative2(realRoot, resolvedTarget);
14348
- if (rel2.startsWith("..") || isAbsolute3(rel2) || win32.isAbsolute(rel2)) {
14871
+ if (rel2.startsWith("..") || isAbsolute4(rel2) || win32.isAbsolute(rel2)) {
14349
14872
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
14350
14873
  }
14351
14874
  continue;
14352
14875
  }
14353
14876
  const rel = relative2(realRoot, fullPath);
14354
- if (rel.startsWith("..") || isAbsolute3(rel) || win32.isAbsolute(rel)) {
14877
+ if (rel.startsWith("..") || isAbsolute4(rel) || win32.isAbsolute(rel)) {
14355
14878
  throw new Error(`extracted entry ${fullPath} escapes staging root`);
14356
14879
  }
14357
14880
  if (lst.isDirectory()) {
@@ -14387,11 +14910,11 @@ async function downloadOnnxRuntime(info, targetDir) {
14387
14910
  await extractZipArchive(archivePath, tmpDir);
14388
14911
  }
14389
14912
  try {
14390
- unlinkSync3(archivePath);
14913
+ unlinkSync4(archivePath);
14391
14914
  } catch {}
14392
14915
  validateExtractedTree(tmpDir);
14393
14916
  const extractedDir = join8(tmpDir, info.assetName, "lib");
14394
- if (!existsSync5(extractedDir)) {
14917
+ if (!existsSync6(extractedDir)) {
14395
14918
  throw new Error(`Expected directory not found: ${extractedDir}`);
14396
14919
  }
14397
14920
  mkdirSync5(targetDir, { recursive: true });
@@ -14422,16 +14945,16 @@ async function downloadOnnxRuntime(info, targetDir) {
14422
14945
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
14423
14946
  }
14424
14947
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
14425
- rmSync2(tmpDir, { recursive: true, force: true });
14948
+ rmSync3(tmpDir, { recursive: true, force: true });
14426
14949
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
14427
14950
  return targetDir;
14428
14951
  } catch (err) {
14429
14952
  error(`Failed to download ONNX Runtime: ${err}`);
14430
14953
  try {
14431
- rmSync2(tmpDir, { recursive: true, force: true });
14954
+ rmSync3(tmpDir, { recursive: true, force: true });
14432
14955
  } catch {}
14433
14956
  try {
14434
- rmSync2(targetDir, { recursive: true, force: true });
14957
+ rmSync3(targetDir, { recursive: true, force: true });
14435
14958
  } catch {}
14436
14959
  return null;
14437
14960
  }
@@ -14448,7 +14971,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14448
14971
  }
14449
14972
  } catch (copyErr) {
14450
14973
  if (requiredLibs.has(libFile)) {
14451
- rmSync2(targetDir, { recursive: true, force: true });
14974
+ rmSync3(targetDir, { recursive: true, force: true });
14452
14975
  throw copyErr;
14453
14976
  }
14454
14977
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -14458,14 +14981,14 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14458
14981
  for (const link of symlinks) {
14459
14982
  const dst = join8(targetDir, link.name);
14460
14983
  try {
14461
- unlinkSync3(dst);
14984
+ unlinkSync4(dst);
14462
14985
  } catch {}
14463
14986
  const dstForContainment = join8(targetRoot, link.name);
14464
- const resolvedTarget = resolve2(dirname4(dstForContainment), link.target);
14987
+ const resolvedTarget = resolve5(dirname4(dstForContainment), link.target);
14465
14988
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
14466
14989
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
14467
14990
  if (requiredLibs.has(link.name)) {
14468
- rmSync2(targetDir, { recursive: true, force: true });
14991
+ rmSync3(targetDir, { recursive: true, force: true });
14469
14992
  throw new Error(message);
14470
14993
  }
14471
14994
  log(`ORT extract: skipping optional symlink ${link.name}: ${message}`);
@@ -14475,15 +14998,15 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14475
14998
  symlinkSync(link.target, dst);
14476
14999
  } catch (symlinkErr) {
14477
15000
  if (requiredLibs.has(link.name)) {
14478
- rmSync2(targetDir, { recursive: true, force: true });
15001
+ rmSync3(targetDir, { recursive: true, force: true });
14479
15002
  throw symlinkErr;
14480
15003
  }
14481
15004
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
14482
15005
  }
14483
15006
  }
14484
15007
  const requiredPath = join8(targetDir, info.libName);
14485
- if (!existsSync5(requiredPath)) {
14486
- rmSync2(targetDir, { recursive: true, force: true });
15008
+ if (!existsSync6(requiredPath)) {
15009
+ rmSync3(targetDir, { recursive: true, force: true });
14487
15010
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
14488
15011
  }
14489
15012
  }
@@ -14508,7 +15031,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
14508
15031
  ...sha256 ? { sha256 } : {},
14509
15032
  archiveSha256
14510
15033
  };
14511
- writeFileSync2(join8(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15034
+ writeFileSync3(join8(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
14512
15035
  } catch (err) {
14513
15036
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
14514
15037
  }
@@ -14516,9 +15039,9 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
14516
15039
  function readOnnxInstalledMeta(installDir) {
14517
15040
  const path2 = join8(installDir, ONNX_INSTALLED_META_FILE);
14518
15041
  try {
14519
- if (!statSync3(path2).isFile())
15042
+ if (!statSync4(path2).isFile())
14520
15043
  return null;
14521
- const raw = readFileSync3(path2, "utf8");
15044
+ const raw = readFileSync5(path2, "utf8");
14522
15045
  const parsed = JSON.parse(raw);
14523
15046
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
14524
15047
  return null;
@@ -14534,19 +15057,19 @@ function readOnnxInstalledMeta(installDir) {
14534
15057
  }
14535
15058
  function sha256File(path2) {
14536
15059
  const hash = createHash2("sha256");
14537
- hash.update(readFileSync3(path2));
15060
+ hash.update(readFileSync5(path2));
14538
15061
  return hash.digest("hex");
14539
15062
  }
14540
15063
  function acquireLock(lockPath) {
14541
15064
  const tryClaim = () => {
14542
15065
  try {
14543
- const fd = openSync3(lockPath, "wx");
15066
+ const fd = openSync4(lockPath, "wx");
14544
15067
  try {
14545
- writeFileSync2(fd, `${process.pid}
15068
+ writeFileSync3(fd, `${process.pid}
14546
15069
  ${new Date().toISOString()}
14547
15070
  `);
14548
15071
  } finally {
14549
- closeSync3(fd);
15072
+ closeSync4(fd);
14550
15073
  }
14551
15074
  return true;
14552
15075
  } catch (err) {
@@ -14562,12 +15085,12 @@ ${new Date().toISOString()}
14562
15085
  let owningPid = null;
14563
15086
  let lockMtimeMs = 0;
14564
15087
  try {
14565
- const raw = readFileSync3(lockPath, "utf8");
15088
+ const raw = readFileSync5(lockPath, "utf8");
14566
15089
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
14567
15090
  const parsed = Number.parseInt(firstLine, 10);
14568
15091
  if (Number.isFinite(parsed) && parsed > 0)
14569
15092
  owningPid = parsed;
14570
- lockMtimeMs = statSync3(lockPath).mtimeMs;
15093
+ lockMtimeMs = statSync4(lockPath).mtimeMs;
14571
15094
  } catch {
14572
15095
  return tryClaim();
14573
15096
  }
@@ -14579,7 +15102,7 @@ ${new Date().toISOString()}
14579
15102
  }
14580
15103
  log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
14581
15104
  try {
14582
- unlinkSync3(lockPath);
15105
+ unlinkSync4(lockPath);
14583
15106
  } catch {}
14584
15107
  return tryClaim();
14585
15108
  }
@@ -14587,7 +15110,7 @@ function releaseLock(lockPath) {
14587
15110
  try {
14588
15111
  let owningPid = null;
14589
15112
  try {
14590
- const raw = readFileSync3(lockPath, "utf8");
15113
+ const raw = readFileSync5(lockPath, "utf8");
14591
15114
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
14592
15115
  const parsed = Number.parseInt(firstLine, 10);
14593
15116
  if (Number.isFinite(parsed) && parsed > 0)
@@ -14599,601 +15122,93 @@ function releaseLock(lockPath) {
14599
15122
  warn(`[onnx] could not read lock ${lockPath} during release: ${readErr}`);
14600
15123
  return;
14601
15124
  }
14602
- if (owningPid !== process.pid) {
14603
- log(`[onnx] not releasing lock ${lockPath}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
14604
- return;
14605
- }
14606
- try {
14607
- unlinkSync3(lockPath);
14608
- } catch (unlinkErr) {
14609
- const code = unlinkErr.code;
14610
- if (code !== "ENOENT") {
14611
- warn(`[onnx] failed to release lock ${lockPath}: ${unlinkErr}`);
14612
- }
14613
- }
14614
- } catch (err) {
14615
- warn(`[onnx] unexpected error releasing lock ${lockPath}: ${err}`);
14616
- }
14617
- }
14618
- function tasklistPidFromCsvLine(line) {
14619
- const quoted = line.match(/"([^"]*)"/g);
14620
- if (quoted && quoted.length >= 2)
14621
- return quoted[1].slice(1, -1);
14622
- const cells = line.split(",").map((cell) => cell.trim().replace(/^"|"$/g, ""));
14623
- return cells[1] ?? null;
14624
- }
14625
- function isWindowsProcessAlive(pid) {
14626
- if (!Number.isInteger(pid) || pid <= 0)
14627
- return false;
14628
- try {
14629
- const output = execFileSync("tasklist.exe", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
14630
- encoding: "utf8",
14631
- timeout: 2000,
14632
- windowsHide: true
14633
- });
14634
- const expected = String(pid);
14635
- return output.split(/\r?\n/).some((line) => tasklistPidFromCsvLine(line) === expected);
14636
- } catch {
14637
- return false;
14638
- }
14639
- }
14640
- function isProcessAlive(pid) {
14641
- if (process.platform === "win32")
14642
- return isWindowsProcessAlive(pid);
14643
- try {
14644
- process.kill(pid, 0);
14645
- return true;
14646
- } catch (err) {
14647
- const code = err.code;
14648
- if (code === "ESRCH")
14649
- return false;
14650
- return true;
14651
- }
14652
- }
14653
- // ../aft-bridge/dist/pipe-strip.js
14654
- var NOISE_FILTERS = new Set([
14655
- "grep",
14656
- "rg",
14657
- "head",
14658
- "tail",
14659
- "cat",
14660
- "less",
14661
- "more",
14662
- "sed",
14663
- "awk",
14664
- "cut",
14665
- "sort",
14666
- "uniq",
14667
- "tr",
14668
- "column",
14669
- "fold"
14670
- ]);
14671
- var GREP_GUARD_FLAGS = new Set([
14672
- "c",
14673
- "count",
14674
- "q",
14675
- "quiet",
14676
- "o",
14677
- "only-matching",
14678
- "l",
14679
- "files-with-matches"
14680
- ]);
14681
- function maybeStripCompressorPipe(command, compressionEnabled) {
14682
- if (!compressionEnabled)
14683
- return { command, stripped: false };
14684
- const chain = splitTopLevelCommandChain(command);
14685
- if (chain === null)
14686
- return { command, stripped: false };
14687
- let stripped = false;
14688
- const droppedFilterChains = [];
14689
- const rebuilt = chain.map(({ segment, separator }) => {
14690
- const result = stripSinglePipelineSegment(segment);
14691
- if (result.stripped) {
14692
- stripped = true;
14693
- droppedFilterChains.push(result.filters);
14694
- }
14695
- return `${result.segment}${separator}`;
14696
- }).join("");
14697
- if (!stripped)
14698
- return { command, stripped: false };
14699
- return {
14700
- command: rebuilt,
14701
- stripped: true,
14702
- note: formatStripNote(droppedFilterChains)
14703
- };
14704
- }
14705
- function stripSinglePipelineSegment(segment) {
14706
- const leading = /^\s*/.exec(segment)?.[0] ?? "";
14707
- const trailing = /\s*$/.exec(segment)?.[0] ?? "";
14708
- const coreStart = leading.length;
14709
- const coreEnd = segment.length - trailing.length;
14710
- if (coreEnd <= coreStart)
14711
- return { segment, stripped: false };
14712
- const core = segment.slice(coreStart, coreEnd);
14713
- if (containsUnsplittableConstruct(core))
14714
- return { segment, stripped: false };
14715
- if (hasUnquotedBackground(core))
14716
- return { segment, stripped: false };
14717
- const stages = splitTopLevelPipeline(core);
14718
- if (stages.length < 2)
14719
- return { segment, stripped: false };
14720
- const firstStage = stages[0]?.trim() ?? "";
14721
- if (!isCompressorHandledRunner(firstStage))
14722
- return { segment, stripped: false };
14723
- const filterStages = stages.slice(1).map((stage) => stage.trim());
14724
- for (const stage of filterStages) {
14725
- if (!filterStageIsSafeToDrop(stage))
14726
- return { segment, stripped: false };
14727
- }
14728
- return {
14729
- segment: `${leading}${firstStage}${trailing}`,
14730
- stripped: true,
14731
- filters: filterStages.join(" | ")
14732
- };
14733
- }
14734
- function formatStripNote(droppedFilterChains) {
14735
- const filters = droppedFilterChains.map((filter) => `\`| ${filter}\``).join(", ");
14736
- return `[AFT dropped ${filters} (compressed:false to keep)]`;
14737
- }
14738
- function splitTopLevelCommandChain(command) {
14739
- const segments = [];
14740
- let start = 0;
14741
- let quote = null;
14742
- let escaped = false;
14743
- let inBacktick = false;
14744
- let parenDepth = 0;
14745
- for (let index = 0;index < command.length; index++) {
14746
- const char = command[index];
14747
- const next = command[index + 1];
14748
- if (escaped) {
14749
- escaped = false;
14750
- continue;
14751
- }
14752
- if (inBacktick) {
14753
- if (char === "\\")
14754
- escaped = true;
14755
- else if (char === "`")
14756
- inBacktick = false;
14757
- continue;
14758
- }
14759
- if (char === "\\" && quote !== "'") {
14760
- escaped = true;
14761
- continue;
14762
- }
14763
- if (quote) {
14764
- if (char === quote)
14765
- quote = null;
14766
- continue;
14767
- }
14768
- if (char === "'" || char === '"') {
14769
- quote = char;
14770
- continue;
14771
- }
14772
- if (char === "`") {
14773
- inBacktick = true;
14774
- continue;
14775
- }
14776
- if (char === "(") {
14777
- parenDepth++;
14778
- continue;
14779
- }
14780
- if (char === ")") {
14781
- if (parenDepth === 0)
14782
- return null;
14783
- parenDepth--;
14784
- continue;
14785
- }
14786
- if (parenDepth > 0)
14787
- continue;
14788
- if (char === `
14789
- ` || char === "\r")
14790
- return null;
14791
- if (char === "#" && (index === 0 || command[index - 1] === " " || command[index - 1] === "\t"))
14792
- return null;
14793
- if (char === "&" && next === "&") {
14794
- segments.push({ segment: command.slice(start, index), separator: "&&" });
14795
- start = index + 2;
14796
- index++;
14797
- continue;
14798
- }
14799
- if (char === "|" && next === "|") {
14800
- segments.push({ segment: command.slice(start, index), separator: "||" });
14801
- start = index + 2;
14802
- index++;
14803
- continue;
14804
- }
14805
- if (char === ";") {
14806
- segments.push({ segment: command.slice(start, index), separator: ";" });
14807
- start = index + 1;
14808
- }
14809
- }
14810
- if (escaped || quote || inBacktick || parenDepth !== 0)
14811
- return null;
14812
- segments.push({ segment: command.slice(start), separator: "" });
14813
- return segments;
14814
- }
14815
- function splitTopLevelPipeline(command) {
14816
- const stages = [];
14817
- let start = 0;
14818
- let quote = null;
14819
- let escaped = false;
14820
- for (let index = 0;index < command.length; index++) {
14821
- const char = command[index];
14822
- const next = command[index + 1];
14823
- const previous = command[index - 1];
14824
- if (escaped) {
14825
- escaped = false;
14826
- continue;
14827
- }
14828
- if (char === "\\" && quote !== "'") {
14829
- escaped = true;
14830
- continue;
14831
- }
14832
- if (quote) {
14833
- if (char === quote)
14834
- quote = null;
14835
- continue;
14836
- }
14837
- if (char === "'" || char === '"') {
14838
- quote = char;
14839
- continue;
14840
- }
14841
- if (char === "|" && previous !== "|" && next !== "|") {
14842
- stages.push(command.slice(start, index));
14843
- start = index + 1;
14844
- }
14845
- }
14846
- stages.push(command.slice(start));
14847
- return stages;
14848
- }
14849
- function isCompressorHandledRunner(stage) {
14850
- const tokens = tokenizeStage(stage);
14851
- if (tokens.length === 0)
14852
- return false;
14853
- if (tokens.some((token) => token === "&&" || token === "||" || token.includes(";"))) {
14854
- return false;
14855
- }
14856
- let tokenOffset = 0;
14857
- while (tokenOffset < tokens.length && isEnvAssignment(tokens[tokenOffset])) {
14858
- tokenOffset++;
14859
- }
14860
- const first = runnerName(tokens[tokenOffset]);
14861
- const runnerArgs = tokens.slice(tokenOffset + 1);
14862
- const second = runnerArgs[0];
14863
- const third = runnerArgs[1];
14864
- const rest = runnerArgs;
14865
- if (!first)
14866
- return false;
14867
- if (first === "bun") {
14868
- let args = rest;
14869
- if (args[0] === "--cwd")
14870
- args = args.slice(2);
14871
- else if (args[0]?.startsWith("--cwd="))
14872
- args = args.slice(1);
14873
- const sub = args[0];
14874
- const subNext = args[1];
14875
- return sub === "test" || sub === "run" && isJsVerificationScript(subNext);
14876
- }
14877
- if (first === "npm" || first === "pnpm") {
14878
- return second === "test" || second === "run" && isJsVerificationScript(third);
14879
- }
14880
- if (first === "yarn") {
14881
- return isJsVerificationScript(second) || second === "run" && isJsVerificationScript(third);
14882
- }
14883
- if (first === "deno")
14884
- return ["test", "lint", "check", "bench"].includes(second ?? "");
14885
- if (first === "npx") {
14886
- return ["tsc", "eslint", "vitest", "jest", "playwright", "biome"].includes(second ?? "");
14887
- }
14888
- if (first === "playwright")
14889
- return second === "test";
14890
- if (first === "cargo") {
14891
- return ["test", "build", "check", "clippy", "nextest"].includes(second ?? "");
14892
- }
14893
- if (first === "go")
14894
- return ["test", "build", "vet"].includes(second ?? "");
14895
- if (first === "gradle" || first === "gradlew") {
14896
- return hasBuildTask(rest, ["test", "check", "build", "assemble", "clean"]);
14897
- }
14898
- if (first === "mvn" || first === "mvnw") {
14899
- return hasBuildTask(rest, ["test", "verify", "package", "install", "clean"]);
14900
- }
14901
- if (first === "dotnet")
14902
- return ["test", "build"].includes(second ?? "");
14903
- if (first === "rspec")
14904
- return true;
14905
- if (first === "rake") {
14906
- const positionals = rest.filter((a) => !a.startsWith("-"));
14907
- if (positionals.length === 0)
14908
- return false;
14909
- if (positionals.some((a) => a.includes("/") || a.includes(".") || a.includes("=")))
14910
- return false;
14911
- return positionals.some((a) => a === "test" || a === "spec");
14912
- }
14913
- if (first === "phpunit" || first === "pest")
14914
- return true;
14915
- if (first === "xcodebuild")
14916
- return xcodebuildHasBuildAction(rest);
14917
- if (first === "swift")
14918
- return second === "test" || second === "build";
14919
- if (first === "make" || first === "gmake") {
14920
- return hasBuildTask(rest, ["test", "check", "lint", "clean"]);
14921
- }
14922
- return [
14923
- "vitest",
14924
- "jest",
14925
- "pytest",
14926
- "tsc",
14927
- "eslint",
14928
- "biome",
14929
- "ruff",
14930
- "mypy",
14931
- "tox",
14932
- "nox"
14933
- ].includes(first);
14934
- }
14935
- function isEnvAssignment(token) {
14936
- if (!token)
14937
- return false;
14938
- return /^[a-zA-Z_][a-zA-Z0-9_]*=/.test(token);
14939
- }
14940
- function runnerName(token) {
14941
- if (!token)
14942
- return "";
14943
- const slash = token.lastIndexOf("/");
14944
- return slash === -1 ? token : token.slice(slash + 1);
14945
- }
14946
- function hasBuildTask(args, tasks) {
14947
- const isAllowedTask = (arg) => tasks.some((task) => arg === task || arg.endsWith(`:${task}`));
14948
- const isFlagOrProperty = (arg) => arg.startsWith("-") || arg.includes("=");
14949
- let sawAllowed = false;
14950
- for (const arg of args) {
14951
- if (isFlagOrProperty(arg))
14952
- continue;
14953
- if (!isAllowedTask(arg))
14954
- return false;
14955
- sawAllowed = true;
14956
- }
14957
- return sawAllowed;
14958
- }
14959
- function isJsVerificationScript(token) {
14960
- if (!token)
14961
- return false;
14962
- return token.startsWith("test") || token === "typecheck" || token.startsWith("typecheck:");
14963
- }
14964
- var XCODEBUILD_VALUE_FLAGS = new Set([
14965
- "-scheme",
14966
- "-target",
14967
- "-project",
14968
- "-workspace",
14969
- "-configuration",
14970
- "-sdk",
14971
- "-destination",
14972
- "-arch",
14973
- "-derivedDataPath",
14974
- "-resultBundlePath",
14975
- "-xcconfig",
14976
- "-toolchain"
14977
- ]);
14978
- var XCODEBUILD_BUILD_ACTIONS = new Set([
14979
- "build",
14980
- "test",
14981
- "build-for-testing",
14982
- "test-without-building",
14983
- "analyze"
14984
- ]);
14985
- function xcodebuildHasBuildAction(args) {
14986
- for (let i = 0;i < args.length; i++) {
14987
- const arg = args[i];
14988
- if (arg.startsWith("-")) {
14989
- if (XCODEBUILD_VALUE_FLAGS.has(arg))
14990
- i++;
14991
- continue;
14992
- }
14993
- if (XCODEBUILD_BUILD_ACTIONS.has(arg))
14994
- return true;
14995
- }
14996
- return false;
14997
- }
14998
- function containsUnsplittableConstruct(command) {
14999
- let quote = null;
15000
- let escaped = false;
15001
- for (let i = 0;i < command.length; i++) {
15002
- const char = command[i];
15003
- if (escaped) {
15004
- escaped = false;
15005
- continue;
15006
- }
15007
- if (char === "\\" && quote !== "'") {
15008
- escaped = true;
15009
- continue;
15010
- }
15011
- if (quote === "'") {
15012
- if (char === "'")
15013
- quote = null;
15014
- continue;
15015
- }
15016
- if (quote === '"') {
15017
- if (char === '"')
15018
- quote = null;
15019
- else if (char === "`")
15020
- return true;
15021
- else if (char === "$" && command[i + 1] === "(")
15022
- return true;
15023
- continue;
15125
+ if (owningPid !== process.pid) {
15126
+ log(`[onnx] not releasing lock ${lockPath}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
15127
+ return;
15024
15128
  }
15025
- if (char === "'" || char === '"') {
15026
- quote = char;
15027
- continue;
15129
+ try {
15130
+ unlinkSync4(lockPath);
15131
+ } catch (unlinkErr) {
15132
+ const code = unlinkErr.code;
15133
+ if (code !== "ENOENT") {
15134
+ warn(`[onnx] failed to release lock ${lockPath}: ${unlinkErr}`);
15135
+ }
15028
15136
  }
15029
- if (char === "`")
15030
- return true;
15031
- if (char === "(" || char === ")")
15032
- return true;
15137
+ } catch (err) {
15138
+ warn(`[onnx] unexpected error releasing lock ${lockPath}: ${err}`);
15033
15139
  }
15034
- return false;
15035
15140
  }
15036
- var READS_FILE_OPERAND = new Set(["cat", "tac", "nl", "less", "more"]);
15037
- function filterStageIsSafeToDrop(stage) {
15038
- const head = tokenizeStage(stage)[0];
15039
- if (!head)
15040
- return false;
15041
- if (head === "wc")
15042
- return false;
15043
- if (!NOISE_FILTERS.has(head))
15044
- return false;
15045
- if (/[<>]/.test(stage))
15141
+ function tasklistPidFromCsvLine(line) {
15142
+ const quoted = line.match(/"([^"]*)"/g);
15143
+ if (quoted && quoted.length >= 2)
15144
+ return quoted[1].slice(1, -1);
15145
+ const cells = line.split(",").map((cell) => cell.trim().replace(/^"|"$/g, ""));
15146
+ return cells[1] ?? null;
15147
+ }
15148
+ function isWindowsProcessAlive(pid) {
15149
+ if (!Number.isInteger(pid) || pid <= 0)
15046
15150
  return false;
15047
- if (hasUnquotedBackground(stage))
15151
+ try {
15152
+ const output = execFileSync("tasklist.exe", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
15153
+ encoding: "utf8",
15154
+ timeout: 2000,
15155
+ windowsHide: true
15156
+ });
15157
+ const expected = String(pid);
15158
+ return output.split(/\r?\n/).some((line) => tasklistPidFromCsvLine(line) === expected);
15159
+ } catch {
15048
15160
  return false;
15049
- const args = tokenizeStage(stage).slice(1);
15050
- const hasFlag = (...names) => args.some((a) => names.some((n) => a === n || a.startsWith(`${n}=`)));
15051
- if (head === "grep" || head === "rg") {
15052
- if (hasIntentChangingGrepFlag(args))
15053
- return false;
15054
- if (countBareOperands(args) > 1)
15055
- return false;
15056
- return true;
15057
- }
15058
- if (head === "head" || head === "tail") {
15059
- if (bareOperands(args).some((op) => !/^\d+$/.test(op)))
15060
- return false;
15061
- return true;
15062
- }
15063
- if (READS_FILE_OPERAND.has(head)) {
15064
- if (countBareOperands(args) > 0)
15065
- return false;
15066
- return true;
15067
15161
  }
15068
- if (head === "sed") {
15069
- if (hasFlag("-i", "--in-place"))
15070
- return false;
15071
- if (countBareOperands(args) > 1)
15072
- return false;
15162
+ }
15163
+ function isProcessAlive(pid) {
15164
+ if (process.platform === "win32")
15165
+ return isWindowsProcessAlive(pid);
15166
+ try {
15167
+ process.kill(pid, 0);
15073
15168
  return true;
15074
- }
15075
- if (head === "awk") {
15076
- if (countBareOperands(args) > 1)
15169
+ } catch (err) {
15170
+ const code = err.code;
15171
+ if (code === "ESRCH")
15077
15172
  return false;
15078
15173
  return true;
15079
15174
  }
15080
- if (head === "sort" && hasFlag("-o", "--output"))
15081
- return false;
15082
- if (bareOperands(args).some((op) => op.includes("/") || op.includes(".")))
15083
- return false;
15084
- return true;
15085
15175
  }
15086
- function bareOperands(args) {
15087
- const out = [];
15088
- let afterDoubleDash = false;
15089
- for (const arg of args) {
15090
- if (!afterDoubleDash && arg === "--") {
15091
- afterDoubleDash = true;
15092
- continue;
15093
- }
15094
- if (!afterDoubleDash && arg.startsWith("-") && arg !== "-")
15095
- continue;
15096
- out.push(arg);
15176
+ // ../aft-bridge/dist/project-identity.js
15177
+ import { createHash as createHash3 } from "node:crypto";
15178
+ import { realpathSync as realpathSync2 } from "node:fs";
15179
+ import { resolve as resolve6 } from "node:path";
15180
+ function canonicalizeProjectRoot(dir) {
15181
+ const trimmed = dir.replace(/[/\\]+$/, "");
15182
+ let canonical;
15183
+ try {
15184
+ canonical = realpathSync2(trimmed);
15185
+ } catch {
15186
+ canonical = resolve6(trimmed);
15097
15187
  }
15098
- return out;
15188
+ return normalizeWindowsRoot(canonical);
15099
15189
  }
15100
- function countBareOperands(args) {
15101
- return bareOperands(args).length;
15102
- }
15103
- function hasUnquotedBackground(stage) {
15104
- let quote = null;
15105
- let escaped = false;
15106
- for (let i = 0;i < stage.length; i++) {
15107
- const char = stage[i];
15108
- if (escaped) {
15109
- escaped = false;
15110
- continue;
15111
- }
15112
- if (char === "\\" && quote !== "'") {
15113
- escaped = true;
15114
- continue;
15115
- }
15116
- if (quote) {
15117
- if (char === quote)
15118
- quote = null;
15119
- continue;
15120
- }
15121
- if (char === "'" || char === '"') {
15122
- quote = char;
15123
- continue;
15124
- }
15125
- if (char === "&") {
15126
- const prev = stage[i - 1];
15127
- const next = stage[i + 1];
15128
- if (prev === "&" || next === "&" || prev === ">" || next === ">")
15129
- continue;
15130
- return true;
15131
- }
15190
+ function normalizeWindowsRoot(p) {
15191
+ if (process.platform !== "win32")
15192
+ return p;
15193
+ let s = p;
15194
+ if (s.startsWith("\\\\?\\UNC\\")) {
15195
+ s = `\\\\${s.slice("\\\\?\\UNC\\".length)}`;
15196
+ } else if (s.startsWith("\\\\?\\")) {
15197
+ s = s.slice("\\\\?\\".length);
15132
15198
  }
15133
- return false;
15134
- }
15135
- function hasIntentChangingGrepFlag(args) {
15136
- for (const arg of args) {
15137
- if (arg === "--")
15138
- return false;
15139
- if (!arg.startsWith("-") || arg === "-")
15140
- continue;
15141
- if (arg.startsWith("--")) {
15142
- const flag = arg.slice(2).split("=", 1)[0];
15143
- if (GREP_GUARD_FLAGS.has(flag))
15144
- return true;
15145
- continue;
15146
- }
15147
- for (const flag of arg.slice(1)) {
15148
- if (GREP_GUARD_FLAGS.has(flag))
15149
- return true;
15199
+ if (s.length >= 2 && s[1] === ":") {
15200
+ const drive = s.charCodeAt(0);
15201
+ if (drive >= 97 && drive <= 122) {
15202
+ s = s[0].toUpperCase() + s.slice(1);
15150
15203
  }
15151
15204
  }
15152
- return false;
15205
+ return s;
15153
15206
  }
15154
- function tokenizeStage(stage) {
15155
- const tokens = [];
15156
- let current = "";
15157
- let quote = null;
15158
- let escaped = false;
15159
- for (let index = 0;index < stage.length; index++) {
15160
- const char = stage[index];
15161
- if (escaped) {
15162
- current += char;
15163
- escaped = false;
15164
- continue;
15165
- }
15166
- if (char === "\\" && quote !== "'") {
15167
- escaped = true;
15168
- continue;
15169
- }
15170
- if (quote) {
15171
- if (char === quote) {
15172
- quote = null;
15173
- } else {
15174
- current += char;
15175
- }
15176
- continue;
15177
- }
15178
- if (char === "'" || char === '"') {
15179
- quote = char;
15180
- continue;
15181
- }
15182
- if (/\s/.test(char)) {
15183
- if (current.length > 0) {
15184
- tokens.push(current);
15185
- current = "";
15186
- }
15187
- continue;
15188
- }
15189
- current += char;
15190
- }
15191
- if (current.length > 0)
15192
- tokens.push(current);
15193
- return tokens;
15207
+ function projectRootKeyHash(dir) {
15208
+ return createHash3("sha256").update(canonicalizeProjectRoot(dir)).digest("hex").slice(0, 16);
15194
15209
  }
15210
+
15195
15211
  // ../aft-bridge/dist/pool.js
15196
- import { realpathSync as realpathSync2 } from "node:fs";
15197
15212
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
15198
15213
  var DEFAULT_MAX_POOL_SIZE = 8;
15199
15214
  var CLEANUP_INTERVAL_MS = 60 * 1000;
@@ -15364,12 +15379,7 @@ class BridgePool {
15364
15379
  }
15365
15380
  }
15366
15381
  function normalizeKey(projectRoot) {
15367
- const stripped = projectRoot.replace(/[/\\]+$/, "");
15368
- try {
15369
- return realpathSync2(stripped);
15370
- } catch {
15371
- return stripped;
15372
- }
15382
+ return canonicalizeProjectRoot(projectRoot);
15373
15383
  }
15374
15384
  // ../aft-bridge/dist/tool-format.js
15375
15385
  function asPlainObject(value) {
@@ -15560,7 +15570,7 @@ function unwrapRustZoomBatchEnvelope(response) {
15560
15570
  return { names, responses };
15561
15571
  }
15562
15572
  // src/bg-notifications.ts
15563
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
15573
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
15564
15574
 
15565
15575
  // src/logger.ts
15566
15576
  import * as fs from "node:fs";
@@ -15724,7 +15734,7 @@ async function resolvePromptContext(client, sessionId) {
15724
15734
 
15725
15735
  // src/bg-notifications.ts
15726
15736
  function hashReminder(text) {
15727
- return createHash3("sha256").update(text).digest("hex").slice(0, 16);
15737
+ return createHash4("sha256").update(text).digest("hex").slice(0, 16);
15728
15738
  }
15729
15739
  var CONSUMED_TASKIDS_CAP = 256;
15730
15740
  var sessionBgStates = new Map;
@@ -16398,9 +16408,7 @@ function formatDuration(completion) {
16398
16408
  }
16399
16409
 
16400
16410
  // src/config.ts
16401
- import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
16402
- import { homedir as homedir8 } from "node:os";
16403
- import { join as join10 } from "node:path";
16411
+ import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
16404
16412
  var import_comment_json = __toESM(require_src2(), 1);
16405
16413
 
16406
16414
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
@@ -30062,89 +30070,7 @@ var AftConfigSchema = exports_external.object({
30062
30070
  auto_update: exports_external.boolean().optional(),
30063
30071
  bridge: BridgeConfigSchema.optional()
30064
30072
  }).strict();
30065
- function normalizeLspExtension(extension) {
30066
- return extension.trim().replace(/^\.+/, "");
30067
- }
30068
- function resolveLspConfigForConfigure(config2) {
30069
- const overrides = {};
30070
- const disabled = new Set(config2.lsp?.disabled ?? []);
30071
- let experimentalTy = config2.experimental?.lsp_ty;
30072
- switch (config2.lsp?.python ?? "auto") {
30073
- case "ty":
30074
- experimentalTy = true;
30075
- disabled.add("python");
30076
- break;
30077
- case "pyright":
30078
- experimentalTy = false;
30079
- disabled.add("ty");
30080
- break;
30081
- case "auto":
30082
- break;
30083
- }
30084
- if (experimentalTy !== undefined) {
30085
- overrides.experimental_lsp_ty = experimentalTy;
30086
- }
30087
- const servers = Object.entries(config2.lsp?.servers ?? {}).map(([id, server]) => {
30088
- const entry = {
30089
- id,
30090
- args: server.args,
30091
- root_markers: server.root_markers,
30092
- disabled: server.disabled
30093
- };
30094
- if (server.extensions && server.extensions.length > 0) {
30095
- entry.extensions = server.extensions.map(normalizeLspExtension);
30096
- }
30097
- if (server.binary) {
30098
- entry.binary = server.binary;
30099
- }
30100
- if (server.env && Object.keys(server.env).length > 0) {
30101
- entry.env = server.env;
30102
- }
30103
- if (server.initialization_options !== undefined) {
30104
- entry.initialization_options = server.initialization_options;
30105
- }
30106
- return entry;
30107
- });
30108
- if (servers.length > 0) {
30109
- overrides.lsp_servers = servers;
30110
- }
30111
- if (disabled.size > 0) {
30112
- overrides.disabled_lsp = [...disabled];
30113
- }
30114
- return overrides;
30115
- }
30116
- function resolveProjectOverridesForConfigure(config2) {
30117
- const overrides = {};
30118
- if (config2.format_on_edit !== undefined)
30119
- overrides.format_on_edit = config2.format_on_edit;
30120
- if (config2.formatter_timeout_secs !== undefined)
30121
- overrides.formatter_timeout_secs = config2.formatter_timeout_secs;
30122
- if (config2.validate_on_edit !== undefined)
30123
- overrides.validate_on_edit = config2.validate_on_edit;
30124
- if (config2.formatter !== undefined)
30125
- overrides.formatter = config2.formatter;
30126
- if (config2.checker !== undefined)
30127
- overrides.checker = config2.checker;
30128
- overrides.restrict_to_project_root = config2.restrict_to_project_root ?? false;
30129
- if (config2.search_index !== undefined)
30130
- overrides.search_index = config2.search_index;
30131
- if (config2.semantic_search !== undefined)
30132
- overrides.semantic_search = config2.semantic_search;
30133
- if (config2.callgraph_store !== undefined)
30134
- overrides.callgraph_store = config2.callgraph_store;
30135
- if (config2.callgraph_chunk_size !== undefined)
30136
- overrides.callgraph_chunk_size = config2.callgraph_chunk_size;
30137
- Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
30138
- Object.assign(overrides, resolveLspConfigForConfigure(config2));
30139
- if (config2.semantic !== undefined)
30140
- overrides.semantic = config2.semantic;
30141
- if (config2.inspect !== undefined)
30142
- overrides.inspect = config2.inspect;
30143
- if (config2.max_callgraph_files !== undefined)
30144
- overrides.max_callgraph_files = config2.max_callgraph_files;
30145
- return overrides;
30146
- }
30147
- var FOREGROUND_WAIT_WINDOW_DEFAULT_MS = 8000;
30073
+ var FOREGROUND_WAIT_WINDOW_DEFAULT_MS = 15000;
30148
30074
  var FOREGROUND_WAIT_WINDOW_MIN_MS = 5000;
30149
30075
  function resolveBashConfig(config2) {
30150
30076
  const top = config2.bash;
@@ -30203,23 +30129,6 @@ function resolveBashConfig(config2) {
30203
30129
  background: surfaceDefaultEnabled
30204
30130
  };
30205
30131
  }
30206
- function resolveExperimentalConfigForConfigure(config2) {
30207
- const overrides = {};
30208
- const bash = resolveBashConfig(config2);
30209
- overrides.experimental_bash_rewrite = bash.rewrite;
30210
- overrides.experimental_bash_compress = bash.compress;
30211
- overrides.experimental_bash_background = bash.background;
30212
- if (bash.long_running_reminder_enabled !== undefined) {
30213
- overrides.bash_long_running_reminder_enabled = bash.long_running_reminder_enabled;
30214
- }
30215
- if (bash.long_running_reminder_interval_ms !== undefined) {
30216
- overrides.bash_long_running_reminder_interval_ms = bash.long_running_reminder_interval_ms;
30217
- }
30218
- if (config2.experimental?.lsp_ty !== undefined) {
30219
- overrides.experimental_lsp_ty = config2.experimental.lsp_ty;
30220
- }
30221
- return overrides;
30222
- }
30223
30132
  var CONFIG_MIGRATIONS = [
30224
30133
  { oldKey: "experimental_search_index", newPath: ["search_index"] },
30225
30134
  { oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
@@ -30329,14 +30238,14 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
30329
30238
  }
30330
30239
  return movedKeys;
30331
30240
  }
30332
- function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
30333
- if (!existsSync6(configPath)) {
30241
+ function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
30242
+ if (!existsSync7(configPath)) {
30334
30243
  return { migrated: false, oldKeys: [] };
30335
30244
  }
30336
30245
  let tmpPath = null;
30337
30246
  let oldKeys = [];
30338
30247
  try {
30339
- const content = readFileSync4(configPath, "utf-8");
30248
+ const content = readFileSync6(configPath, "utf-8");
30340
30249
  const rawConfig = import_comment_json.parse(content);
30341
30250
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
30342
30251
  return { migrated: false, oldKeys: [] };
@@ -30353,14 +30262,14 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
30353
30262
  `)}
30354
30263
  ${serialized}` : serialized;
30355
30264
  tmpPath = `${configPath}.tmp.${process.pid}`;
30356
- writeFileSync3(tmpPath, nextContent, "utf-8");
30357
- renameSync4(tmpPath, configPath);
30265
+ writeFileSync4(tmpPath, nextContent, "utf-8");
30266
+ renameSync5(tmpPath, configPath);
30358
30267
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
30359
30268
  return { migrated: true, oldKeys };
30360
30269
  } catch (err) {
30361
30270
  if (tmpPath) {
30362
30271
  try {
30363
- unlinkSync4(tmpPath);
30272
+ unlinkSync5(tmpPath);
30364
30273
  } catch {}
30365
30274
  }
30366
30275
  if (isWritableMigrationError(err)) {
@@ -30371,17 +30280,6 @@ ${serialized}` : serialized;
30371
30280
  return { migrated: false, oldKeys: [] };
30372
30281
  }
30373
30282
  }
30374
- function detectConfigFile(basePath) {
30375
- const jsoncPath = `${basePath}.jsonc`;
30376
- const jsonPath = `${basePath}.json`;
30377
- if (existsSync6(jsoncPath)) {
30378
- return { format: "jsonc", path: jsoncPath };
30379
- }
30380
- if (existsSync6(jsonPath)) {
30381
- return { format: "json", path: jsonPath };
30382
- }
30383
- return { format: "none", path: jsonPath };
30384
- }
30385
30283
  function parseConfigPartially(rawConfig) {
30386
30284
  const fullResult = AftConfigSchema.safeParse(rawConfig);
30387
30285
  if (fullResult.success) {
@@ -30421,10 +30319,10 @@ function recordConfigParseFailure(configPath, errorMessage) {
30421
30319
  }
30422
30320
  function loadConfigFromPath(configPath) {
30423
30321
  try {
30424
- if (!existsSync6(configPath)) {
30322
+ if (!existsSync7(configPath)) {
30425
30323
  return null;
30426
30324
  }
30427
- const content = readFileSync4(configPath, "utf-8");
30325
+ const content = readFileSync6(configPath, "utf-8");
30428
30326
  const rawConfig = import_comment_json.parse(content);
30429
30327
  migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
30430
30328
  const cleanConfig = stripJsoncSymbols(rawConfig);
@@ -30627,27 +30525,43 @@ function resolveBridgePoolTransportOptions(config2) {
30627
30525
  hangThreshold: config2.bridge?.hang_threshold ?? DEFAULT_BRIDGE_HANG_THRESHOLD
30628
30526
  };
30629
30527
  }
30630
- function getOpenCodeConfigDir() {
30631
- const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
30632
- if (envDir) {
30633
- return envDir;
30634
- }
30635
- const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir8(), ".config");
30636
- return join10(xdgConfig, "opencode");
30528
+ function migrateAftConfigLocations(projectDirectory, logger = { log: log2, warn: warn2 }) {
30529
+ const paths = resolveCortexKitConfigPaths(projectDirectory);
30530
+ const legacy = resolveLegacyAftConfigSources(projectDirectory);
30531
+ return [
30532
+ migrateAftConfigFile({
30533
+ scope: "user",
30534
+ targetPath: paths.userConfigPath,
30535
+ legacySources: legacy.user,
30536
+ operatingHarness: "opencode",
30537
+ logger
30538
+ }),
30539
+ migrateAftConfigFile({
30540
+ scope: "project",
30541
+ targetPath: paths.projectConfigPath,
30542
+ legacySources: legacy.project,
30543
+ operatingHarness: "opencode",
30544
+ logger
30545
+ })
30546
+ ];
30547
+ }
30548
+ function resolveAftConfigPaths(projectDirectory) {
30549
+ const paths = resolveCortexKitConfigPaths(projectDirectory);
30550
+ migrateAftConfigFile2(paths.userConfigPath);
30551
+ migrateAftConfigFile2(paths.projectConfigPath);
30552
+ return paths;
30553
+ }
30554
+ function buildConfigTierConfigureParams(projectDirectory, processState = {}) {
30555
+ const paths = resolveAftConfigPaths(projectDirectory);
30556
+ return {
30557
+ ...processState,
30558
+ cortexkit_user_config_path: paths.userConfigPath,
30559
+ config: readConfigTiers(paths)
30560
+ };
30637
30561
  }
30638
30562
  function loadAftConfig(projectDirectory) {
30639
30563
  configLoadErrors = [];
30640
- const configDir = getOpenCodeConfigDir();
30641
- const userBasePath = join10(configDir, "aft");
30642
- migrateAftConfigFile(`${userBasePath}.jsonc`);
30643
- migrateAftConfigFile(`${userBasePath}.json`);
30644
- const userDetected = detectConfigFile(userBasePath);
30645
- const userConfigPath = userDetected.format !== "none" ? userDetected.path : `${userBasePath}.json`;
30646
- const projectBasePath = join10(projectDirectory, ".opencode", "aft");
30647
- migrateAftConfigFile(`${projectBasePath}.jsonc`);
30648
- migrateAftConfigFile(`${projectBasePath}.json`);
30649
- const projectDetected = detectConfigFile(projectBasePath);
30650
- const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : `${projectBasePath}.json`;
30564
+ const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
30651
30565
  let config2 = loadConfigFromPath(userConfigPath) ?? {};
30652
30566
  const projectConfig = loadConfigFromPath(projectConfigPath);
30653
30567
  if (projectConfig) {
@@ -30668,9 +30582,9 @@ function loadAftConfig(projectDirectory) {
30668
30582
  }
30669
30583
 
30670
30584
  // src/notifications.ts
30671
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
30585
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
30672
30586
  import { homedir as homedir9, platform } from "node:os";
30673
- import { join as join11 } from "node:path";
30587
+ import { join as join10 } from "node:path";
30674
30588
  function isTuiMode() {
30675
30589
  return process.env.OPENCODE_CLIENT === "cli";
30676
30590
  }
@@ -30693,25 +30607,25 @@ function getDesktopStatePath() {
30693
30607
  const os3 = platform();
30694
30608
  const home = homedir9();
30695
30609
  if (os3 === "darwin") {
30696
- return join11(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
30610
+ return join10(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
30697
30611
  }
30698
30612
  if (os3 === "linux") {
30699
- const xdgConfig = process.env.XDG_CONFIG_HOME || join11(home, ".config");
30700
- return join11(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
30613
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join10(home, ".config");
30614
+ return join10(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
30701
30615
  }
30702
30616
  if (os3 === "win32") {
30703
- const appData = process.env.APPDATA || join11(home, "AppData", "Roaming");
30704
- return join11(appData, "ai.opencode.desktop", "opencode.global.dat");
30617
+ const appData = process.env.APPDATA || join10(home, "AppData", "Roaming");
30618
+ return join10(appData, "ai.opencode.desktop", "opencode.global.dat");
30705
30619
  }
30706
30620
  return null;
30707
30621
  }
30708
30622
  function readDesktopState() {
30709
30623
  const statePath = getDesktopStatePath();
30710
- if (!statePath || !existsSync7(statePath)) {
30624
+ if (!statePath || !existsSync8(statePath)) {
30711
30625
  return { serverUrl: null };
30712
30626
  }
30713
30627
  try {
30714
- const raw = readFileSync5(statePath, "utf-8");
30628
+ const raw = readFileSync7(statePath, "utf-8");
30715
30629
  const state = JSON.parse(raw);
30716
30630
  let serverUrl = null;
30717
30631
  const serverStr = state.server;
@@ -30972,9 +30886,13 @@ function warningTitle(warning) {
30972
30886
  return "LSP binary is missing";
30973
30887
  case "config_parse_failed":
30974
30888
  return "Config failed to parse";
30889
+ case "config_key_dropped":
30890
+ return "Config key ignored";
30975
30891
  }
30976
30892
  }
30977
30893
  function formatConfigureWarningLine(warning) {
30894
+ if (warning.kind === "config_key_dropped")
30895
+ return `• ${warning.hint}`;
30978
30896
  const details = [];
30979
30897
  if (warning.language)
30980
30898
  details.push(`language: ${warning.language}`);
@@ -30990,6 +30908,8 @@ function formatConfigureWarningLine(warning) {
30990
30908
  ${warning.hint}`;
30991
30909
  }
30992
30910
  function formatConfigureWarningChat(warning) {
30911
+ if (warning.kind === "config_key_dropped")
30912
+ return `${WARNING_MARKER} ${warning.hint}`;
30993
30913
  return `${WARNING_MARKER} ${formatConfigureWarningLine(warning).replace(/^• /, "")}`;
30994
30914
  }
30995
30915
  function formatConfigureWarningsBatch(warnings) {
@@ -31099,7 +31019,7 @@ function isConfigureWarning(value) {
31099
31019
  if (!value || typeof value !== "object" || Array.isArray(value))
31100
31020
  return false;
31101
31021
  const warning = value;
31102
- return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed") && typeof warning.hint === "string";
31022
+ return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed" || warning.kind === "config_key_dropped") && typeof warning.hint === "string";
31103
31023
  }
31104
31024
  function configParseWarningsFromErrors(errors3) {
31105
31025
  return errors3.map((entry) => ({
@@ -31127,6 +31047,20 @@ function drainPendingConfigParseWarnings(projectRoot) {
31127
31047
  function coerceConfigureWarnings(warnings) {
31128
31048
  return warnings.filter(isConfigureWarning);
31129
31049
  }
31050
+ function isDroppedConfigKey(value) {
31051
+ if (!value || typeof value !== "object" || Array.isArray(value))
31052
+ return false;
31053
+ const dropped = value;
31054
+ return typeof dropped.key === "string" && typeof dropped.tier === "string" && typeof dropped.reason === "string";
31055
+ }
31056
+ function coerceDroppedKeyWarnings(droppedKeys) {
31057
+ if (!Array.isArray(droppedKeys))
31058
+ return [];
31059
+ return formatDroppedKeyWarnings(droppedKeys.filter(isDroppedConfigKey)).map((hint) => ({
31060
+ kind: "config_key_dropped",
31061
+ hint
31062
+ }));
31063
+ }
31130
31064
  function drainPendingEagerWarnings(projectRoot) {
31131
31065
  const pending = pendingEagerWarnings.get(projectRoot) ?? [];
31132
31066
  pendingEagerWarnings.delete(projectRoot);
@@ -31135,7 +31069,8 @@ function drainPendingEagerWarnings(projectRoot) {
31135
31069
  function enqueueConfigureWarningsForSession(context) {
31136
31070
  const validWarnings = [
31137
31071
  ...drainPendingConfigParseWarnings(context.projectRoot),
31138
- ...coerceConfigureWarnings(context.warnings)
31072
+ ...coerceConfigureWarnings(context.warnings),
31073
+ ...coerceDroppedKeyWarnings(context.configDroppedKeys)
31139
31074
  ];
31140
31075
  if (!context.sessionId) {
31141
31076
  if (validWarnings.length === 0)
@@ -31185,52 +31120,52 @@ async function flushConfigureWarningsOnIdle(sessionId) {
31185
31120
 
31186
31121
  // src/hooks/auto-update-checker/index.ts
31187
31122
  import {
31188
- closeSync as closeSync4,
31189
- existsSync as existsSync10,
31123
+ closeSync as closeSync5,
31124
+ existsSync as existsSync11,
31190
31125
  mkdirSync as mkdirSync6,
31191
- openSync as openSync4,
31192
- readFileSync as readFileSync8,
31193
- renameSync as renameSync5,
31194
- rmSync as rmSync4,
31195
- writeFileSync as writeFileSync6
31126
+ openSync as openSync5,
31127
+ readFileSync as readFileSync10,
31128
+ renameSync as renameSync6,
31129
+ rmSync as rmSync5,
31130
+ writeFileSync as writeFileSync7
31196
31131
  } from "node:fs";
31197
31132
  import { dirname as dirname7 } from "node:path";
31198
31133
 
31199
31134
  // src/hooks/auto-update-checker/cache.ts
31200
31135
  import { spawn as spawn2 } from "node:child_process";
31201
- import { cpSync, existsSync as existsSync9, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
31136
+ import { cpSync, existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync9, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
31202
31137
  import { tmpdir as tmpdir3 } from "node:os";
31203
- import { basename as basename2, dirname as dirname6, join as join14 } from "node:path";
31138
+ import { basename as basename3, dirname as dirname6, join as join13 } from "node:path";
31204
31139
  var import_comment_json3 = __toESM(require_src2(), 1);
31205
31140
 
31206
31141
  // src/hooks/auto-update-checker/checker.ts
31207
31142
  var import_comment_json2 = __toESM(require_src2(), 1);
31208
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
31143
+ import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync5, writeFileSync as writeFileSync5 } from "node:fs";
31209
31144
  import { homedir as homedir11 } from "node:os";
31210
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve3 } from "node:path";
31145
+ import { dirname as dirname5, isAbsolute as isAbsolute5, join as join12, resolve as resolve7 } from "node:path";
31211
31146
  import { fileURLToPath } from "node:url";
31212
31147
 
31213
31148
  // src/hooks/auto-update-checker/constants.ts
31214
31149
  import { homedir as homedir10, platform as platform2 } from "node:os";
31215
- import { join as join12 } from "node:path";
31150
+ import { join as join11 } from "node:path";
31216
31151
  var PACKAGE_NAME = "@cortexkit/aft-opencode";
31217
31152
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
31218
31153
  var NPM_FETCH_TIMEOUT = 1e4;
31219
31154
  function getOpenCodeCacheRoot() {
31220
31155
  if (platform2() === "win32") {
31221
- return join12(process.env.LOCALAPPDATA ?? homedir10(), "opencode");
31156
+ return join11(process.env.LOCALAPPDATA ?? homedir10(), "opencode");
31222
31157
  }
31223
- return join12(homedir10(), ".cache", "opencode");
31158
+ return join11(homedir10(), ".cache", "opencode");
31224
31159
  }
31225
31160
  function getOpenCodeConfigRoot() {
31226
31161
  if (platform2() === "win32") {
31227
- return join12(process.env.APPDATA ?? join12(homedir10(), "AppData", "Roaming"), "opencode");
31162
+ return join11(process.env.APPDATA ?? join11(homedir10(), "AppData", "Roaming"), "opencode");
31228
31163
  }
31229
- return join12(process.env.XDG_CONFIG_HOME ?? join12(homedir10(), ".config"), "opencode");
31164
+ return join11(process.env.XDG_CONFIG_HOME ?? join11(homedir10(), ".config"), "opencode");
31230
31165
  }
31231
- var CACHE_DIR = join12(getOpenCodeCacheRoot(), "packages");
31232
- var USER_OPENCODE_CONFIG = join12(getOpenCodeConfigRoot(), "opencode.json");
31233
- var USER_OPENCODE_CONFIG_JSONC = join12(getOpenCodeConfigRoot(), "opencode.jsonc");
31166
+ var CACHE_DIR = join11(getOpenCodeCacheRoot(), "packages");
31167
+ var USER_OPENCODE_CONFIG = join11(getOpenCodeConfigRoot(), "opencode.json");
31168
+ var USER_OPENCODE_CONFIG_JSONC = join11(getOpenCodeConfigRoot(), "opencode.jsonc");
31234
31169
 
31235
31170
  // src/hooks/auto-update-checker/types.ts
31236
31171
  var NpmPackageEnvelopeSchema = exports_external.object({
@@ -31288,8 +31223,8 @@ function extractChannel(version2) {
31288
31223
  }
31289
31224
  function getConfigPaths(directory) {
31290
31225
  return [
31291
- join13(directory, ".opencode", "opencode.json"),
31292
- join13(directory, ".opencode", "opencode.jsonc"),
31226
+ join12(directory, ".opencode", "opencode.json"),
31227
+ join12(directory, ".opencode", "opencode.jsonc"),
31293
31228
  USER_OPENCODE_CONFIG,
31294
31229
  USER_OPENCODE_CONFIG_JSONC
31295
31230
  ];
@@ -31302,26 +31237,26 @@ function resolvePathPluginSpec(spec, configPath) {
31302
31237
  return spec.replace(/^file:\/\//, "");
31303
31238
  }
31304
31239
  }
31305
- if (isAbsolute4(spec) || /^[A-Za-z]:[\\/]/.test(spec))
31240
+ if (isAbsolute5(spec) || /^[A-Za-z]:[\\/]/.test(spec))
31306
31241
  return spec;
31307
- return resolve3(dirname5(configPath), spec);
31242
+ return resolve7(dirname5(configPath), spec);
31308
31243
  }
31309
31244
  function getLocalDevPath(directory) {
31310
31245
  for (const configPath of getConfigPaths(directory)) {
31311
31246
  try {
31312
- if (!existsSync8(configPath))
31247
+ if (!existsSync9(configPath))
31313
31248
  continue;
31314
- const rawConfig = parseJsonConfig(readFileSync6(configPath, "utf-8"));
31249
+ const rawConfig = parseJsonConfig(readFileSync8(configPath, "utf-8"));
31315
31250
  const plugins = getPluginEntries(rawConfig);
31316
31251
  for (const entry of plugins) {
31317
31252
  if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
31318
31253
  continue;
31319
- if (entry.startsWith("file://") || entry.startsWith(".") || isAbsolute4(entry)) {
31254
+ if (entry.startsWith("file://") || entry.startsWith(".") || isAbsolute5(entry)) {
31320
31255
  const localPath = resolvePathPluginSpec(entry, configPath);
31321
31256
  const pkgPath = findPackageJsonUp(localPath);
31322
31257
  if (!pkgPath)
31323
31258
  continue;
31324
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync6(pkgPath, "utf-8")));
31259
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync8(pkgPath, "utf-8")));
31325
31260
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
31326
31261
  return localPath;
31327
31262
  }
@@ -31332,13 +31267,13 @@ function getLocalDevPath(directory) {
31332
31267
  }
31333
31268
  function findPackageJsonUp(startPath) {
31334
31269
  try {
31335
- const stat = statSync4(startPath);
31270
+ const stat = statSync5(startPath);
31336
31271
  let dir = stat.isDirectory() ? startPath : dirname5(startPath);
31337
31272
  for (let i = 0;i < 10; i++) {
31338
- const pkgPath = join13(dir, "package.json");
31339
- if (existsSync8(pkgPath)) {
31273
+ const pkgPath = join12(dir, "package.json");
31274
+ if (existsSync9(pkgPath)) {
31340
31275
  try {
31341
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync6(pkgPath, "utf-8")));
31276
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync8(pkgPath, "utf-8")));
31342
31277
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
31343
31278
  return pkgPath;
31344
31279
  } catch {}
@@ -31359,7 +31294,7 @@ function getLocalDevVersion(directory) {
31359
31294
  const pkgPath = findPackageJsonUp(localPath);
31360
31295
  if (!pkgPath)
31361
31296
  return null;
31362
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync6(pkgPath, "utf-8")));
31297
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync8(pkgPath, "utf-8")));
31363
31298
  return pkg.success ? pkg.data.version ?? null : null;
31364
31299
  } catch {
31365
31300
  return null;
@@ -31376,9 +31311,9 @@ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
31376
31311
  function findPluginEntry(directory) {
31377
31312
  for (const configPath of getConfigPaths(directory)) {
31378
31313
  try {
31379
- if (!existsSync8(configPath))
31314
+ if (!existsSync9(configPath))
31380
31315
  continue;
31381
- const rawConfig = parseJsonConfig(readFileSync6(configPath, "utf-8"));
31316
+ const rawConfig = parseJsonConfig(readFileSync8(configPath, "utf-8"));
31382
31317
  const plugins = getPluginEntries(rawConfig);
31383
31318
  for (const entry of plugins) {
31384
31319
  if (entry === PACKAGE_NAME) {
@@ -31396,7 +31331,7 @@ function findPluginEntry(directory) {
31396
31331
  }
31397
31332
  var cachedPackageVersion = null;
31398
31333
  function getSpecCachePackageJsonPath(spec) {
31399
- return join13(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
31334
+ return join12(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
31400
31335
  }
31401
31336
  function getCachedVersion(spec) {
31402
31337
  if (!spec && cachedPackageVersion)
@@ -31405,13 +31340,13 @@ function getCachedVersion(spec) {
31405
31340
  getCurrentRuntimePackageJsonPath(),
31406
31341
  spec ? getSpecCachePackageJsonPath(spec) : null,
31407
31342
  getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
31408
- join13(homedir11(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
31343
+ join12(homedir11(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
31409
31344
  ].filter(isString);
31410
31345
  for (const packageJsonPath of candidates) {
31411
31346
  try {
31412
- if (!existsSync8(packageJsonPath))
31347
+ if (!existsSync9(packageJsonPath))
31413
31348
  continue;
31414
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync6(packageJsonPath, "utf-8")));
31349
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync8(packageJsonPath, "utf-8")));
31415
31350
  if (pkg.success && pkg.data.version) {
31416
31351
  if (!spec)
31417
31352
  cachedPackageVersion = pkg.data.version;
@@ -31453,17 +31388,17 @@ async function getLatestVersion(channel = "latest", options = {}) {
31453
31388
  // src/hooks/auto-update-checker/cache.ts
31454
31389
  var pendingSnapshots = new Map;
31455
31390
  function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
31456
- const packageDir = join14(installDir, "node_modules", packageName);
31457
- const lockfilePath = join14(installDir, "package-lock.json");
31458
- const tempDir = mkdtempSync(join14(tmpdir3(), "aft-auto-update-"));
31459
- const stagedPackageDir = existsSync9(packageDir) ? join14(tempDir, "package") : null;
31391
+ const packageDir = join13(installDir, "node_modules", packageName);
31392
+ const lockfilePath = join13(installDir, "package-lock.json");
31393
+ const tempDir = mkdtempSync(join13(tmpdir3(), "aft-auto-update-"));
31394
+ const stagedPackageDir = existsSync10(packageDir) ? join13(tempDir, "package") : null;
31460
31395
  if (stagedPackageDir)
31461
31396
  cpSync(packageDir, stagedPackageDir, { recursive: true });
31462
31397
  return {
31463
31398
  packageJsonPath,
31464
- packageJson: existsSync9(packageJsonPath) ? readFileSync7(packageJsonPath, "utf-8") : null,
31399
+ packageJson: existsSync10(packageJsonPath) ? readFileSync9(packageJsonPath, "utf-8") : null,
31465
31400
  lockfilePath,
31466
- lockfile: existsSync9(lockfilePath) ? readFileSync7(lockfilePath, "utf-8") : null,
31401
+ lockfile: existsSync10(lockfilePath) ? readFileSync9(lockfilePath, "utf-8") : null,
31467
31402
  packageDir,
31468
31403
  stagedPackageDir,
31469
31404
  tempDir
@@ -31472,36 +31407,36 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
31472
31407
  function restoreAutoUpdateSnapshot(snapshot) {
31473
31408
  try {
31474
31409
  if (snapshot.packageJson === null)
31475
- rmSync3(snapshot.packageJsonPath, { force: true });
31410
+ rmSync4(snapshot.packageJsonPath, { force: true });
31476
31411
  else
31477
- writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
31412
+ writeFileSync6(snapshot.packageJsonPath, snapshot.packageJson);
31478
31413
  if (snapshot.lockfile === null)
31479
- rmSync3(snapshot.lockfilePath, { force: true });
31414
+ rmSync4(snapshot.lockfilePath, { force: true });
31480
31415
  else
31481
- writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
31482
- rmSync3(snapshot.packageDir, { recursive: true, force: true });
31416
+ writeFileSync6(snapshot.lockfilePath, snapshot.lockfile);
31417
+ rmSync4(snapshot.packageDir, { recursive: true, force: true });
31483
31418
  if (snapshot.stagedPackageDir) {
31484
31419
  cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
31485
31420
  }
31486
31421
  } finally {
31487
- rmSync3(snapshot.tempDir, { recursive: true, force: true });
31422
+ rmSync4(snapshot.tempDir, { recursive: true, force: true });
31488
31423
  }
31489
31424
  }
31490
31425
  function stripPackageNameFromPath(pathValue, packageName) {
31491
31426
  let current = pathValue;
31492
31427
  for (const segment of [...packageName.split("/")].reverse()) {
31493
- if (basename2(current) !== segment)
31428
+ if (basename3(current) !== segment)
31494
31429
  return null;
31495
31430
  current = dirname6(current);
31496
31431
  }
31497
31432
  return current;
31498
31433
  }
31499
31434
  function removeFromPackageLock(installDir, packageName) {
31500
- const lockPath = join14(installDir, "package-lock.json");
31501
- if (!existsSync9(lockPath))
31435
+ const lockPath = join13(installDir, "package-lock.json");
31436
+ if (!existsSync10(lockPath))
31502
31437
  return false;
31503
31438
  try {
31504
- const lock = import_comment_json3.parse(readFileSync7(lockPath, "utf-8"));
31439
+ const lock = import_comment_json3.parse(readFileSync9(lockPath, "utf-8"));
31505
31440
  let modified = false;
31506
31441
  if (lock.packages) {
31507
31442
  const key = `node_modules/${packageName}`;
@@ -31515,7 +31450,7 @@ function removeFromPackageLock(installDir, packageName) {
31515
31450
  modified = true;
31516
31451
  }
31517
31452
  if (modified) {
31518
- writeFileSync5(lockPath, JSON.stringify(lock, null, 2));
31453
+ writeFileSync6(lockPath, JSON.stringify(lock, null, 2));
31519
31454
  log2(`[auto-update-checker] Removed from package-lock.json: ${packageName}`);
31520
31455
  }
31521
31456
  return modified;
@@ -31524,10 +31459,10 @@ function removeFromPackageLock(installDir, packageName) {
31524
31459
  }
31525
31460
  }
31526
31461
  function ensureDependencyVersion(packageJsonPath, packageName, version2) {
31527
- if (!existsSync9(packageJsonPath))
31462
+ if (!existsSync10(packageJsonPath))
31528
31463
  return false;
31529
31464
  try {
31530
- const raw = import_comment_json3.parse(readFileSync7(packageJsonPath, "utf-8"));
31465
+ const raw = import_comment_json3.parse(readFileSync9(packageJsonPath, "utf-8"));
31531
31466
  const pkgJson = PackageJsonSchema.safeParse(raw);
31532
31467
  if (!pkgJson.success)
31533
31468
  return false;
@@ -31537,7 +31472,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
31537
31472
  return true;
31538
31473
  dependencies[packageName] = version2;
31539
31474
  nextPackageJson.dependencies = dependencies;
31540
- writeFileSync5(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
31475
+ writeFileSync6(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
31541
31476
  log2(`[auto-update-checker] Updated dependency in package.json: ${packageName} → ${version2}`);
31542
31477
  return true;
31543
31478
  } catch (err) {
@@ -31546,10 +31481,10 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
31546
31481
  }
31547
31482
  }
31548
31483
  function removeInstalledPackage(installDir, packageName) {
31549
- const packageDir = join14(installDir, "node_modules", packageName);
31550
- if (!existsSync9(packageDir))
31484
+ const packageDir = join13(installDir, "node_modules", packageName);
31485
+ if (!existsSync10(packageDir))
31551
31486
  return false;
31552
- rmSync3(packageDir, { recursive: true, force: true });
31487
+ rmSync4(packageDir, { recursive: true, force: true });
31553
31488
  log2(`[auto-update-checker] Package removed: ${packageDir}`);
31554
31489
  return true;
31555
31490
  }
@@ -31557,16 +31492,16 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
31557
31492
  if (runtimePackageJsonPath) {
31558
31493
  const packageDir = dirname6(runtimePackageJsonPath);
31559
31494
  const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
31560
- if (nodeModulesDir && basename2(nodeModulesDir) === "node_modules") {
31495
+ if (nodeModulesDir && basename3(nodeModulesDir) === "node_modules") {
31561
31496
  const installDir = dirname6(nodeModulesDir);
31562
- const packageJsonPath = join14(installDir, "package.json");
31563
- if (existsSync9(packageJsonPath))
31497
+ const packageJsonPath = join13(installDir, "package.json");
31498
+ if (existsSync10(packageJsonPath))
31564
31499
  return { installDir, packageJsonPath };
31565
31500
  }
31566
31501
  return null;
31567
31502
  }
31568
- const legacyPackageJsonPath = join14(dirname6(CACHE_DIR), "package.json");
31569
- if (existsSync9(legacyPackageJsonPath)) {
31503
+ const legacyPackageJsonPath = join13(dirname6(CACHE_DIR), "package.json");
31504
+ if (existsSync10(legacyPackageJsonPath)) {
31570
31505
  return { installDir: dirname6(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
31571
31506
  }
31572
31507
  return null;
@@ -31652,7 +31587,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
31652
31587
  if (!result.ok && snapshot) {
31653
31588
  restoreAutoUpdateSnapshot(snapshot);
31654
31589
  } else if (snapshot) {
31655
- rmSync3(snapshot.tempDir, { recursive: true, force: true });
31590
+ rmSync4(snapshot.tempDir, { recursive: true, force: true });
31656
31591
  }
31657
31592
  if (!result.ok) {
31658
31593
  warnNpmInstallFailure(result.reason ?? "npm install failed", stderrTail);
@@ -31745,8 +31680,8 @@ function claimCheckSlot(storageDir, intervalMs) {
31745
31680
  const lockPath = `${file2}.lock`;
31746
31681
  let lockFd;
31747
31682
  try {
31748
- lockFd = openSync4(lockPath, "wx");
31749
- writeFileSync6(lockFd, JSON.stringify({ pid: process.pid, startedMs: Date.now() }));
31683
+ lockFd = openSync5(lockPath, "wx");
31684
+ writeFileSync7(lockFd, JSON.stringify({ pid: process.pid, startedMs: Date.now() }));
31750
31685
  } catch (err) {
31751
31686
  if (err.code !== "EEXIST") {
31752
31687
  warn2(`[auto-update-checker] Could not acquire update lock: ${String(err)}`);
@@ -31756,9 +31691,9 @@ function claimCheckSlot(storageDir, intervalMs) {
31756
31691
  const lock = {
31757
31692
  release: () => {
31758
31693
  try {
31759
- closeSync4(lockFd);
31694
+ closeSync5(lockFd);
31760
31695
  } catch {}
31761
- rmSync4(lockPath, { force: true });
31696
+ rmSync5(lockPath, { force: true });
31762
31697
  }
31763
31698
  };
31764
31699
  try {
@@ -31778,10 +31713,10 @@ function claimCheckSlot(storageDir, intervalMs) {
31778
31713
  }
31779
31714
  }
31780
31715
  function hasRecentCheckTimestamp(file2, intervalMs) {
31781
- if (!existsSync10(file2))
31716
+ if (!existsSync11(file2))
31782
31717
  return false;
31783
31718
  try {
31784
- const raw = JSON.parse(readFileSync8(file2, "utf-8"));
31719
+ const raw = JSON.parse(readFileSync10(file2, "utf-8"));
31785
31720
  const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
31786
31721
  return Number.isFinite(last) && Date.now() - last < intervalMs;
31787
31722
  } catch {
@@ -31790,8 +31725,8 @@ function hasRecentCheckTimestamp(file2, intervalMs) {
31790
31725
  }
31791
31726
  function writeCheckTimestamp(file2) {
31792
31727
  const tmp = `${file2}.tmp.${process.pid}`;
31793
- writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
31794
- renameSync5(tmp, file2);
31728
+ writeFileSync7(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
31729
+ renameSync6(tmp, file2);
31795
31730
  }
31796
31731
  async function runStartupCheck(ctx, options) {
31797
31732
  if (options.signal.aborted)
@@ -31885,59 +31820,59 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
31885
31820
 
31886
31821
  // src/lsp-auto-install.ts
31887
31822
  import { spawn as spawn3 } from "node:child_process";
31888
- import { createHash as createHash4 } from "node:crypto";
31823
+ import { createHash as createHash5 } from "node:crypto";
31889
31824
  import {
31890
31825
  createReadStream,
31891
- existsSync as existsSync12,
31826
+ existsSync as existsSync13,
31892
31827
  mkdirSync as mkdirSync8,
31893
- readFileSync as readFileSync11,
31894
- renameSync as renameSync6,
31895
- rmSync as rmSync5,
31896
- statSync as statSync6,
31897
- writeFileSync as writeFileSync8
31828
+ readFileSync as readFileSync13,
31829
+ renameSync as renameSync7,
31830
+ rmSync as rmSync6,
31831
+ statSync as statSync7,
31832
+ writeFileSync as writeFileSync9
31898
31833
  } from "node:fs";
31899
- import { join as join17 } from "node:path";
31834
+ import { join as join16 } from "node:path";
31900
31835
 
31901
31836
  // src/lsp-cache.ts
31902
31837
  import {
31903
- closeSync as closeSync5,
31838
+ closeSync as closeSync6,
31904
31839
  mkdirSync as mkdirSync7,
31905
- openSync as openSync5,
31906
- readFileSync as readFileSync9,
31907
- statSync as statSync5,
31908
- unlinkSync as unlinkSync5,
31909
- writeFileSync as writeFileSync7
31840
+ openSync as openSync6,
31841
+ readFileSync as readFileSync11,
31842
+ statSync as statSync6,
31843
+ unlinkSync as unlinkSync6,
31844
+ writeFileSync as writeFileSync8
31910
31845
  } from "node:fs";
31911
31846
  import { homedir as homedir12 } from "node:os";
31912
- import { join as join15 } from "node:path";
31847
+ import { join as join14 } from "node:path";
31913
31848
  function aftCacheBase() {
31914
31849
  const override = process.env.AFT_CACHE_DIR;
31915
31850
  if (override && override.length > 0)
31916
31851
  return override;
31917
31852
  if (process.platform === "win32") {
31918
31853
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
31919
- const base2 = localAppData || join15(homedir12(), "AppData", "Local");
31920
- return join15(base2, "aft");
31854
+ const base2 = localAppData || join14(homedir12(), "AppData", "Local");
31855
+ return join14(base2, "aft");
31921
31856
  }
31922
- const base = process.env.XDG_CACHE_HOME || join15(homedir12(), ".cache");
31923
- return join15(base, "aft");
31857
+ const base = process.env.XDG_CACHE_HOME || join14(homedir12(), ".cache");
31858
+ return join14(base, "aft");
31924
31859
  }
31925
31860
  function lspCacheRoot() {
31926
- return join15(aftCacheBase(), "lsp-packages");
31861
+ return join14(aftCacheBase(), "lsp-packages");
31927
31862
  }
31928
31863
  function lspPackageDir(npmPackage) {
31929
- return join15(lspCacheRoot(), encodeURIComponent(npmPackage));
31864
+ return join14(lspCacheRoot(), encodeURIComponent(npmPackage));
31930
31865
  }
31931
31866
  function lspBinaryPath(npmPackage, binary) {
31932
- return join15(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
31867
+ return join14(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
31933
31868
  }
31934
31869
  function lspBinDir(npmPackage) {
31935
- return join15(lspPackageDir(npmPackage), "node_modules", ".bin");
31870
+ return join14(lspPackageDir(npmPackage), "node_modules", ".bin");
31936
31871
  }
31937
31872
  function isInstalled(npmPackage, binary) {
31938
31873
  for (const candidate of lspBinaryCandidates(binary)) {
31939
31874
  try {
31940
- if (statSync5(join15(lspBinDir(npmPackage), candidate)).isFile())
31875
+ if (statSync6(join14(lspBinDir(npmPackage), candidate)).isFile())
31941
31876
  return true;
31942
31877
  } catch {}
31943
31878
  }
@@ -31957,17 +31892,17 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
31957
31892
  installedAt: new Date().toISOString(),
31958
31893
  ...sha256 ? { sha256 } : {}
31959
31894
  };
31960
- writeFileSync7(join15(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
31895
+ writeFileSync8(join14(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
31961
31896
  } catch (err) {
31962
31897
  log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
31963
31898
  }
31964
31899
  }
31965
31900
  function readInstalledMetaIn(installDir) {
31966
- const path3 = join15(installDir, INSTALLED_META_FILE);
31901
+ const path3 = join14(installDir, INSTALLED_META_FILE);
31967
31902
  try {
31968
- if (!statSync5(path3).isFile())
31903
+ if (!statSync6(path3).isFile())
31969
31904
  return null;
31970
- const raw = readFileSync9(path3, "utf8");
31905
+ const raw = readFileSync11(path3, "utf8");
31971
31906
  const parsed = JSON.parse(raw);
31972
31907
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
31973
31908
  return null;
@@ -31987,7 +31922,7 @@ function readInstalledMeta(packageKey) {
31987
31922
  return readInstalledMetaIn(lspPackageDir(packageKey));
31988
31923
  }
31989
31924
  function lockPath(npmPackage) {
31990
- return join15(lspPackageDir(npmPackage), ".aft-installing");
31925
+ return join14(lspPackageDir(npmPackage), ".aft-installing");
31991
31926
  }
31992
31927
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
31993
31928
  function acquireInstallLock(lockKey) {
@@ -31995,13 +31930,13 @@ function acquireInstallLock(lockKey) {
31995
31930
  const lock = lockPath(lockKey);
31996
31931
  const tryClaim = () => {
31997
31932
  try {
31998
- const fd = openSync5(lock, "wx");
31933
+ const fd = openSync6(lock, "wx");
31999
31934
  try {
32000
- writeFileSync7(fd, `${process.pid}
31935
+ writeFileSync8(fd, `${process.pid}
32001
31936
  ${new Date().toISOString()}
32002
31937
  `);
32003
31938
  } finally {
32004
- closeSync5(fd);
31939
+ closeSync6(fd);
32005
31940
  }
32006
31941
  return true;
32007
31942
  } catch (err) {
@@ -32017,12 +31952,12 @@ ${new Date().toISOString()}
32017
31952
  let owningPid = null;
32018
31953
  let lockMtimeMs = 0;
32019
31954
  try {
32020
- const raw = readFileSync9(lock, "utf8");
31955
+ const raw = readFileSync11(lock, "utf8");
32021
31956
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
32022
31957
  const parsed = Number.parseInt(firstLine, 10);
32023
31958
  if (Number.isFinite(parsed) && parsed > 0)
32024
31959
  owningPid = parsed;
32025
- lockMtimeMs = statSync5(lock).mtimeMs;
31960
+ lockMtimeMs = statSync6(lock).mtimeMs;
32026
31961
  } catch {
32027
31962
  return tryClaim();
32028
31963
  }
@@ -32035,7 +31970,7 @@ ${new Date().toISOString()}
32035
31970
  }
32036
31971
  log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
32037
31972
  try {
32038
- unlinkSync5(lock);
31973
+ unlinkSync6(lock);
32039
31974
  } catch {}
32040
31975
  return tryClaim();
32041
31976
  }
@@ -32055,7 +31990,7 @@ function releaseInstallLock(lockKey) {
32055
31990
  try {
32056
31991
  let owningPid = null;
32057
31992
  try {
32058
- const raw = readFileSync9(lock, "utf8");
31993
+ const raw = readFileSync11(lock, "utf8");
32059
31994
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
32060
31995
  const parsed = Number.parseInt(firstLine, 10);
32061
31996
  if (Number.isFinite(parsed) && parsed > 0)
@@ -32072,7 +32007,7 @@ function releaseInstallLock(lockKey) {
32072
32007
  return;
32073
32008
  }
32074
32009
  try {
32075
- unlinkSync5(lock);
32010
+ unlinkSync6(lock);
32076
32011
  } catch (unlinkErr) {
32077
32012
  const code = unlinkErr.code;
32078
32013
  if (code !== "ENOENT") {
@@ -32094,9 +32029,9 @@ async function withInstallLock(lockKey, task) {
32094
32029
  }
32095
32030
  var VERSION_CHECK_FILE = ".aft-version-check";
32096
32031
  function readVersionCheck(npmPackage) {
32097
- const file2 = join15(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
32032
+ const file2 = join14(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
32098
32033
  try {
32099
- const raw = readFileSync9(file2, "utf8");
32034
+ const raw = readFileSync11(file2, "utf8");
32100
32035
  const parsed = JSON.parse(raw);
32101
32036
  if (typeof parsed.last_checked === "string") {
32102
32037
  return {
@@ -32111,12 +32046,12 @@ function readVersionCheck(npmPackage) {
32111
32046
  }
32112
32047
  function writeVersionCheck(npmPackage, latest) {
32113
32048
  mkdirSync7(lspPackageDir(npmPackage), { recursive: true });
32114
- const file2 = join15(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
32049
+ const file2 = join14(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
32115
32050
  const record2 = {
32116
32051
  last_checked: new Date().toISOString(),
32117
32052
  latest_eligible: latest
32118
32053
  };
32119
- writeFileSync7(file2, JSON.stringify(record2, null, 2));
32054
+ writeFileSync8(file2, JSON.stringify(record2, null, 2));
32120
32055
  }
32121
32056
  function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
32122
32057
  if (!record2)
@@ -32273,8 +32208,8 @@ var NPM_LSP_TABLE = [
32273
32208
  ];
32274
32209
 
32275
32210
  // src/lsp-project-relevance.ts
32276
- import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "node:fs";
32277
- import { join as join16 } from "node:path";
32211
+ import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync12 } from "node:fs";
32212
+ import { join as join15 } from "node:path";
32278
32213
  var MAX_WALK_DIRS = 200;
32279
32214
  var MAX_WALK_DEPTH = 4;
32280
32215
  var NOISE_DIRS = new Set([
@@ -32291,7 +32226,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
32291
32226
  if (!rootMarkers)
32292
32227
  return false;
32293
32228
  for (const marker of rootMarkers) {
32294
- if (existsSync11(join16(projectRoot, marker)))
32229
+ if (existsSync12(join15(projectRoot, marker)))
32295
32230
  return true;
32296
32231
  }
32297
32232
  return false;
@@ -32315,7 +32250,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
32315
32250
  }
32316
32251
  function readPackageJson(projectRoot) {
32317
32252
  try {
32318
- const raw = readFileSync10(join16(projectRoot, "package.json"), "utf8");
32253
+ const raw = readFileSync12(join15(projectRoot, "package.json"), "utf8");
32319
32254
  const parsed = JSON.parse(raw);
32320
32255
  if (typeof parsed !== "object" || parsed === null)
32321
32256
  return null;
@@ -32345,7 +32280,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
32345
32280
  for (const entry of entries) {
32346
32281
  if (entry.isDirectory()) {
32347
32282
  if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
32348
- queue.push({ dir: join16(current.dir, entry.name), depth: current.depth + 1 });
32283
+ queue.push({ dir: join15(current.dir, entry.name), depth: current.depth + 1 });
32349
32284
  }
32350
32285
  continue;
32351
32286
  }
@@ -32472,9 +32407,9 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
32472
32407
  }
32473
32408
  function ensureInstallAnchor(cwd) {
32474
32409
  try {
32475
- const stub = join17(cwd, "package.json");
32476
- if (!existsSync12(stub)) {
32477
- writeFileSync8(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
32410
+ const stub = join16(cwd, "package.json");
32411
+ if (!existsSync13(stub)) {
32412
+ writeFileSync9(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
32478
32413
  `);
32479
32414
  }
32480
32415
  } catch (err) {
@@ -32482,18 +32417,18 @@ function ensureInstallAnchor(cwd) {
32482
32417
  }
32483
32418
  }
32484
32419
  function runInstall(spec, version2, cwd, signal) {
32485
- return new Promise((resolve4) => {
32420
+ return new Promise((resolve8) => {
32486
32421
  const target = `${spec.npm}@${version2}`;
32487
32422
  log2(`[lsp] installing ${target} to ${cwd}`);
32488
32423
  if (signal?.aborted) {
32489
32424
  warn2(`[lsp] install ${target} aborted before spawn`);
32490
- resolve4(false);
32425
+ resolve8(false);
32491
32426
  return;
32492
32427
  }
32493
32428
  const npm = resolveNpm();
32494
32429
  if (!npm) {
32495
32430
  warn2(`[lsp] npm not found on PATH or known locations; cannot install ${target}`);
32496
- resolve4(false);
32431
+ resolve8(false);
32497
32432
  return;
32498
32433
  }
32499
32434
  ensureInstallAnchor(cwd);
@@ -32516,7 +32451,7 @@ function runInstall(spec, version2, cwd, signal) {
32516
32451
  return;
32517
32452
  settled = true;
32518
32453
  cleanup();
32519
- resolve4(ok);
32454
+ resolve8(ok);
32520
32455
  };
32521
32456
  const onAbort = () => {
32522
32457
  warn2(`[lsp] install ${target} aborted during shutdown`);
@@ -32617,7 +32552,7 @@ function cachedPackageDir(npmPackage) {
32617
32552
  return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
32618
32553
  }
32619
32554
  function hashInstalledBinary(spec) {
32620
- return new Promise((resolve4, reject) => {
32555
+ return new Promise((resolve8, reject) => {
32621
32556
  const candidates = process.platform === "win32" ? [
32622
32557
  lspBinaryPath(spec.npm, spec.binary),
32623
32558
  lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
@@ -32627,7 +32562,7 @@ function hashInstalledBinary(spec) {
32627
32562
  let pathToHash = null;
32628
32563
  for (const p of candidates) {
32629
32564
  try {
32630
- if (statSync6(p).isFile()) {
32565
+ if (statSync7(p).isFile()) {
32631
32566
  pathToHash = p;
32632
32567
  break;
32633
32568
  }
@@ -32637,11 +32572,11 @@ function hashInstalledBinary(spec) {
32637
32572
  reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
32638
32573
  return;
32639
32574
  }
32640
- const hash2 = createHash4("sha256");
32575
+ const hash2 = createHash5("sha256");
32641
32576
  const stream = createReadStream(pathToHash);
32642
32577
  stream.on("error", reject);
32643
32578
  stream.on("data", (chunk) => hash2.update(chunk));
32644
- stream.on("end", () => resolve4(hash2.digest("hex")));
32579
+ stream.on("end", () => resolve8(hash2.digest("hex")));
32645
32580
  });
32646
32581
  }
32647
32582
  function installedBinaryPath(spec) {
@@ -32653,23 +32588,23 @@ function installedBinaryPath(spec) {
32653
32588
  ] : [lspBinaryPath(spec.npm, spec.binary)];
32654
32589
  for (const candidate of candidates) {
32655
32590
  try {
32656
- if (statSync6(candidate).isFile())
32591
+ if (statSync7(candidate).isFile())
32657
32592
  return candidate;
32658
32593
  } catch {}
32659
32594
  }
32660
32595
  return null;
32661
32596
  }
32662
32597
  function sha256OfFileSync(path3) {
32663
- return createHash4("sha256").update(readFileSync11(path3)).digest("hex");
32598
+ return createHash5("sha256").update(readFileSync13(path3)).digest("hex");
32664
32599
  }
32665
32600
  function quarantineCachedNpmInstall(spec, reason) {
32666
32601
  const packageDir = lspPackageDir(spec.npm);
32667
- const dest = join17(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
32602
+ const dest = join16(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
32668
32603
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
32669
32604
  try {
32670
- mkdirSync8(join17(dest, ".."), { recursive: true });
32671
- rmSync5(dest, { recursive: true, force: true });
32672
- renameSync6(packageDir, dest);
32605
+ mkdirSync8(join16(dest, ".."), { recursive: true });
32606
+ rmSync6(dest, { recursive: true, force: true });
32607
+ renameSync7(packageDir, dest);
32673
32608
  } catch (err) {
32674
32609
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
32675
32610
  }
@@ -32745,25 +32680,25 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
32745
32680
 
32746
32681
  // src/lsp-github-install.ts
32747
32682
  import { execFileSync as execFileSync2 } from "node:child_process";
32748
- import { createHash as createHash5, randomBytes } from "node:crypto";
32683
+ import { createHash as createHash6, randomBytes } from "node:crypto";
32749
32684
  import {
32750
32685
  copyFileSync as copyFileSync4,
32751
32686
  createReadStream as createReadStream2,
32752
32687
  createWriteStream as createWriteStream3,
32753
- existsSync as existsSync13,
32688
+ existsSync as existsSync14,
32754
32689
  lstatSync as lstatSync2,
32755
32690
  mkdirSync as mkdirSync9,
32756
32691
  readdirSync as readdirSync4,
32757
- readFileSync as readFileSync12,
32692
+ readFileSync as readFileSync14,
32758
32693
  readlinkSync as readlinkSync2,
32759
32694
  realpathSync as realpathSync3,
32760
- renameSync as renameSync7,
32761
- rmSync as rmSync6,
32762
- statSync as statSync7,
32763
- unlinkSync as unlinkSync6,
32764
- writeFileSync as writeFileSync9
32695
+ renameSync as renameSync8,
32696
+ rmSync as rmSync7,
32697
+ statSync as statSync8,
32698
+ unlinkSync as unlinkSync7,
32699
+ writeFileSync as writeFileSync10
32765
32700
  } from "node:fs";
32766
- import { dirname as dirname8, join as join18, relative as relative3, resolve as resolve4 } from "node:path";
32701
+ import { dirname as dirname8, join as join17, relative as relative3, resolve as resolve8 } from "node:path";
32767
32702
  import { Readable as Readable3 } from "node:stream";
32768
32703
  import { pipeline as pipeline3 } from "node:stream/promises";
32769
32704
 
@@ -32853,26 +32788,26 @@ function detectHostPlatform() {
32853
32788
 
32854
32789
  // src/lsp-github-install.ts
32855
32790
  function ghCacheRoot() {
32856
- return join18(aftCacheBase(), "lsp-binaries");
32791
+ return join17(aftCacheBase(), "lsp-binaries");
32857
32792
  }
32858
32793
  function ghPackageDir(spec) {
32859
- return join18(ghCacheRoot(), spec.id);
32794
+ return join17(ghCacheRoot(), spec.id);
32860
32795
  }
32861
32796
  function ghBinDir(spec) {
32862
- return join18(ghPackageDir(spec), "bin");
32797
+ return join17(ghPackageDir(spec), "bin");
32863
32798
  }
32864
32799
  function ghExtractDir(spec) {
32865
- return join18(ghPackageDir(spec), "extracted");
32800
+ return join17(ghPackageDir(spec), "extracted");
32866
32801
  }
32867
32802
  var INSTALLED_META_FILE2 = ".aft-installed";
32868
32803
  function ghBinaryPath(spec, platform3) {
32869
32804
  const ext = platform3 === "win32" ? ".exe" : "";
32870
- return join18(ghBinDir(spec), `${spec.binary}${ext}`);
32805
+ return join17(ghBinDir(spec), `${spec.binary}${ext}`);
32871
32806
  }
32872
32807
  function isGithubInstalled(spec, platform3) {
32873
32808
  for (const candidate of ghBinaryCandidates(spec, platform3)) {
32874
32809
  try {
32875
- if (statSync7(join18(ghBinDir(spec), candidate)).isFile())
32810
+ if (statSync8(join17(ghBinDir(spec), candidate)).isFile())
32876
32811
  return true;
32877
32812
  } catch {}
32878
32813
  }
@@ -32885,10 +32820,10 @@ function ghBinaryCandidates(spec, platform3) {
32885
32820
  }
32886
32821
  function readGithubInstalledMetaIn(installDir) {
32887
32822
  try {
32888
- const path3 = join18(installDir, INSTALLED_META_FILE2);
32889
- if (!statSync7(path3).isFile())
32823
+ const path3 = join17(installDir, INSTALLED_META_FILE2);
32824
+ if (!statSync8(path3).isFile())
32890
32825
  return null;
32891
- const parsed = JSON.parse(readFileSync12(path3, "utf8"));
32826
+ const parsed = JSON.parse(readFileSync14(path3, "utf8"));
32892
32827
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
32893
32828
  return null;
32894
32829
  return {
@@ -32912,7 +32847,7 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
32912
32847
  binarySha256,
32913
32848
  ...archiveSha256 ? { archiveSha256 } : {}
32914
32849
  };
32915
- writeFileSync9(join18(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
32850
+ writeFileSync10(join17(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
32916
32851
  } catch (err) {
32917
32852
  warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
32918
32853
  }
@@ -32920,16 +32855,16 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
32920
32855
  var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
32921
32856
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
32922
32857
  function sha256OfFile(path3) {
32923
- return new Promise((resolve5, reject) => {
32924
- const hash2 = createHash5("sha256");
32858
+ return new Promise((resolve9, reject) => {
32859
+ const hash2 = createHash6("sha256");
32925
32860
  const stream = createReadStream2(path3);
32926
32861
  stream.on("error", reject);
32927
32862
  stream.on("data", (chunk) => hash2.update(chunk));
32928
- stream.on("end", () => resolve5(hash2.digest("hex")));
32863
+ stream.on("end", () => resolve9(hash2.digest("hex")));
32929
32864
  });
32930
32865
  }
32931
32866
  function sha256OfFileSync2(path3) {
32932
- return createHash5("sha256").update(readFileSync12(path3)).digest("hex");
32867
+ return createHash6("sha256").update(readFileSync14(path3)).digest("hex");
32933
32868
  }
32934
32869
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
32935
32870
  const candidates = [];
@@ -33122,7 +33057,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
33122
33057
  await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
33123
33058
  } catch (err) {
33124
33059
  try {
33125
- unlinkSync6(destPath);
33060
+ unlinkSync7(destPath);
33126
33061
  } catch {}
33127
33062
  throw err;
33128
33063
  } finally {
@@ -33161,7 +33096,7 @@ function validateExtraction(stagingRoot) {
33161
33096
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
33162
33097
  }
33163
33098
  for (const entry of entries) {
33164
- const full = join18(dir, entry);
33099
+ const full = join17(dir, entry);
33165
33100
  let lst;
33166
33101
  try {
33167
33102
  lst = lstatSync2(full);
@@ -33182,7 +33117,7 @@ function validateExtraction(stagingRoot) {
33182
33117
  throw new Error(`failed to realpath ${full}: ${err}`);
33183
33118
  }
33184
33119
  const rel = relative3(realStagingRoot, realFull);
33185
- if (rel.startsWith("..") || resolve4(realStagingRoot, rel) !== realFull) {
33120
+ if (rel.startsWith("..") || resolve8(realStagingRoot, rel) !== realFull) {
33186
33121
  throw new Error(`archive entry escapes staging root: ${full} → ${realFull} (zip-slip defense)`);
33187
33122
  }
33188
33123
  if (lst.isDirectory()) {
@@ -33229,7 +33164,7 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
33229
33164
  const suffix = randomBytes(8).toString("hex");
33230
33165
  const stagingDir = `${destDir}.staging-${suffix}`;
33231
33166
  try {
33232
- rmSync6(stagingDir, { recursive: true, force: true });
33167
+ rmSync7(stagingDir, { recursive: true, force: true });
33233
33168
  } catch {}
33234
33169
  mkdirSync9(stagingDir, { recursive: true });
33235
33170
  try {
@@ -33237,24 +33172,24 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
33237
33172
  runPlatformExtractor(archivePath, stagingDir, archiveType);
33238
33173
  validateExtraction(stagingDir);
33239
33174
  try {
33240
- rmSync6(destDir, { recursive: true, force: true });
33175
+ rmSync7(destDir, { recursive: true, force: true });
33241
33176
  } catch {}
33242
- renameSync7(stagingDir, destDir);
33177
+ renameSync8(stagingDir, destDir);
33243
33178
  } catch (err) {
33244
33179
  try {
33245
- rmSync6(stagingDir, { recursive: true, force: true });
33180
+ rmSync7(stagingDir, { recursive: true, force: true });
33246
33181
  } catch {}
33247
33182
  throw err;
33248
33183
  }
33249
33184
  }
33250
33185
  function quarantineCachedGithubInstall(spec, reason) {
33251
33186
  const packageDir = ghPackageDir(spec);
33252
- const dest = join18(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
33187
+ const dest = join17(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
33253
33188
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
33254
33189
  try {
33255
33190
  mkdirSync9(dirname8(dest), { recursive: true });
33256
- rmSync6(dest, { recursive: true, force: true });
33257
- renameSync7(packageDir, dest);
33191
+ rmSync7(dest, { recursive: true, force: true });
33192
+ renameSync8(packageDir, dest);
33258
33193
  } catch (err) {
33259
33194
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
33260
33195
  }
@@ -33262,9 +33197,9 @@ function quarantineCachedGithubInstall(spec, reason) {
33262
33197
  function validateCachedGithubInstall(spec, platform3) {
33263
33198
  const packageDir = ghPackageDir(spec);
33264
33199
  const meta3 = readGithubInstalledMetaIn(packageDir);
33265
- const binaryPath = ghBinaryCandidates(spec, platform3).map((candidate) => join18(ghBinDir(spec), candidate)).find((candidate) => {
33200
+ const binaryPath = ghBinaryCandidates(spec, platform3).map((candidate) => join17(ghBinDir(spec), candidate)).find((candidate) => {
33266
33201
  try {
33267
- return statSync7(candidate).isFile();
33202
+ return statSync8(candidate).isFile();
33268
33203
  } catch {
33269
33204
  return false;
33270
33205
  }
@@ -33340,7 +33275,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
33340
33275
  }
33341
33276
  const pkgDir = ghPackageDir(spec);
33342
33277
  const extractDir = ghExtractDir(spec);
33343
- const archivePath = join18(pkgDir, expected.name);
33278
+ const archivePath = join17(pkgDir, expected.name);
33344
33279
  log2(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
33345
33280
  try {
33346
33281
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -33354,7 +33289,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
33354
33289
  } catch (err) {
33355
33290
  error2(`[lsp] hash ${spec.id} failed: ${err}`);
33356
33291
  try {
33357
- unlinkSync6(archivePath);
33292
+ unlinkSync7(archivePath);
33358
33293
  } catch {}
33359
33294
  return null;
33360
33295
  }
@@ -33365,7 +33300,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
33365
33300
  if (previousArchiveSha256 !== archiveSha256) {
33366
33301
  error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
33367
33302
  try {
33368
- unlinkSync6(archivePath);
33303
+ unlinkSync7(archivePath);
33369
33304
  } catch {}
33370
33305
  return null;
33371
33306
  }
@@ -33377,11 +33312,11 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
33377
33312
  return null;
33378
33313
  } finally {
33379
33314
  try {
33380
- unlinkSync6(archivePath);
33315
+ unlinkSync7(archivePath);
33381
33316
  } catch {}
33382
33317
  }
33383
- const innerBinaryPath = join18(extractDir, spec.binaryPathInArchive(platform3, arch, version2));
33384
- if (!existsSync13(innerBinaryPath)) {
33318
+ const innerBinaryPath = join17(extractDir, spec.binaryPathInArchive(platform3, arch, version2));
33319
+ if (!existsSync14(innerBinaryPath)) {
33385
33320
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
33386
33321
  return null;
33387
33322
  }
@@ -33465,7 +33400,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
33465
33400
  if (!host) {
33466
33401
  for (const spec of GITHUB_LSP_TABLE) {
33467
33402
  try {
33468
- if (existsSync13(ghBinDir(spec))) {
33403
+ if (existsSync14(ghBinDir(spec))) {
33469
33404
  cachedBinDirs.push(ghBinDir(spec));
33470
33405
  }
33471
33406
  } catch {}
@@ -33703,33 +33638,77 @@ function renderScreen(state, rows = state.rows, cols = state.cols) {
33703
33638
  `);
33704
33639
  }
33705
33640
  function writeTerminal(terminal, data) {
33706
- return new Promise((resolve5) => terminal.write(data, resolve5));
33641
+ return new Promise((resolve9) => terminal.write(data, resolve9));
33642
+ }
33643
+
33644
+ // src/shared/rpc-notifications.ts
33645
+ var queue = [];
33646
+ var nextNotificationId = 1;
33647
+ var lastDrainAtBySession = new Map;
33648
+ var lastDrainAtAny = 0;
33649
+ var TUI_CONNECTED_WINDOW_MS = 3000;
33650
+ function pushNotification(type, payload, sessionId) {
33651
+ queue.push({ id: nextNotificationId++, type, payload, sessionId });
33652
+ if (queue.length > 100) {
33653
+ const newestPerSession = new Map;
33654
+ for (const notification of queue) {
33655
+ const previous = newestPerSession.get(notification.sessionId);
33656
+ if (previous === undefined || notification.id > previous) {
33657
+ newestPerSession.set(notification.sessionId, notification.id);
33658
+ }
33659
+ }
33660
+ const mustKeep = new Set(newestPerSession.values());
33661
+ const byNewest = [...queue].sort((a, b) => b.id - a.id);
33662
+ const kept = [];
33663
+ for (const notification of byNewest) {
33664
+ if (kept.length < 50 || mustKeep.has(notification.id))
33665
+ kept.push(notification);
33666
+ }
33667
+ queue = kept.sort((a, b) => a.id - b.id);
33668
+ }
33669
+ }
33670
+ function drainNotifications(lastReceivedId = 0, sessionId) {
33671
+ const now = Date.now();
33672
+ lastDrainAtAny = now;
33673
+ if (sessionId !== undefined)
33674
+ lastDrainAtBySession.set(sessionId, now);
33675
+ const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
33676
+ if (lastReceivedId > 0) {
33677
+ queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
33678
+ }
33679
+ return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
33680
+ }
33681
+ function isTuiConnected(sessionId) {
33682
+ const now = Date.now();
33683
+ if (sessionId !== undefined) {
33684
+ const at = lastDrainAtBySession.get(sessionId) ?? 0;
33685
+ return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS;
33686
+ }
33687
+ return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS;
33707
33688
  }
33708
33689
 
33709
33690
  // src/shared/rpc-server.ts
33710
33691
  import { randomBytes as randomBytes2 } from "node:crypto";
33711
33692
  import {
33712
- existsSync as existsSync14,
33693
+ existsSync as existsSync15,
33713
33694
  mkdirSync as mkdirSync10,
33714
33695
  readdirSync as readdirSync5,
33715
- readFileSync as readFileSync13,
33716
- renameSync as renameSync8,
33717
- unlinkSync as unlinkSync7,
33718
- writeFileSync as writeFileSync10
33696
+ readFileSync as readFileSync15,
33697
+ renameSync as renameSync9,
33698
+ unlinkSync as unlinkSync8,
33699
+ writeFileSync as writeFileSync11
33719
33700
  } from "node:fs";
33720
33701
  import { createServer } from "node:http";
33721
- import { dirname as dirname9, join as join20 } from "node:path";
33702
+ import { dirname as dirname9, join as join19 } from "node:path";
33722
33703
 
33723
33704
  // src/shared/rpc-utils.ts
33724
- import { createHash as createHash6 } from "node:crypto";
33725
- import { join as join19 } from "node:path";
33705
+ import { join as join18 } from "node:path";
33726
33706
  function projectHash(directory) {
33727
- const normalized = directory.replace(/\/+$/, "");
33728
- return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
33707
+ return projectRootKeyHash(directory);
33729
33708
  }
33730
33709
  function rpcPortFileDir(storageDir, directory) {
33731
33710
  const hash2 = projectHash(directory);
33732
- return join19(storageDir, "rpc", hash2, "ports");
33711
+ return join18(storageDir, "rpc", hash2, "ports");
33733
33712
  }
33734
33713
  function isPidAlive(pid) {
33735
33714
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
@@ -33782,13 +33761,13 @@ class AftRpcServer {
33782
33761
  constructor(storageDir, directory) {
33783
33762
  this.portsDir = rpcPortFileDir(storageDir, directory);
33784
33763
  this.instanceId = randomBytes2(8).toString("hex");
33785
- this.portFilePath = join20(this.portsDir, `${this.instanceId}.json`);
33764
+ this.portFilePath = join19(this.portsDir, `${this.instanceId}.json`);
33786
33765
  }
33787
33766
  handle(method, handler) {
33788
33767
  this.handlers.set(method, handler);
33789
33768
  }
33790
33769
  async start() {
33791
- return new Promise((resolve5, reject) => {
33770
+ return new Promise((resolve9, reject) => {
33792
33771
  const server = createServer((req, res) => this.dispatch(req, res));
33793
33772
  server.on("error", (err) => {
33794
33773
  warn2(`RPC server error: ${err.message}`);
@@ -33807,13 +33786,13 @@ class AftRpcServer {
33807
33786
  const dir = dirname9(this.portFilePath);
33808
33787
  mkdirSync10(dir, { recursive: true, mode: 448 });
33809
33788
  const tmpPath = `${this.portFilePath}.tmp`;
33810
- writeFileSync10(tmpPath, JSON.stringify({
33789
+ writeFileSync11(tmpPath, JSON.stringify({
33811
33790
  port: this.port,
33812
33791
  token: this.token,
33813
33792
  pid: process.pid,
33814
33793
  started_at: Date.now()
33815
33794
  }), { encoding: "utf-8", mode: 384 });
33816
- renameSync8(tmpPath, this.portFilePath);
33795
+ renameSync9(tmpPath, this.portFilePath);
33817
33796
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
33818
33797
  this.heartbeatTimer = setInterval(() => this.ensurePortFile(), PORT_FILE_HEARTBEAT_MS);
33819
33798
  this.heartbeatTimer.unref?.();
@@ -33821,7 +33800,7 @@ class AftRpcServer {
33821
33800
  } catch (err) {
33822
33801
  warn2(`Failed to write RPC port file: ${err}`);
33823
33802
  }
33824
- resolve5(this.port);
33803
+ resolve9(this.port);
33825
33804
  });
33826
33805
  server.unref();
33827
33806
  });
@@ -33836,17 +33815,17 @@ class AftRpcServer {
33836
33815
  for (const entry of entries) {
33837
33816
  if (!entry.endsWith(".json"))
33838
33817
  continue;
33839
- const filePath = join20(this.portsDir, entry);
33818
+ const filePath = join19(this.portsDir, entry);
33840
33819
  if (filePath === this.portFilePath)
33841
33820
  continue;
33842
33821
  try {
33843
- const record2 = parseRpcPortRecord(readFileSync13(filePath, "utf-8"));
33822
+ const record2 = parseRpcPortRecord(readFileSync15(filePath, "utf-8"));
33844
33823
  if (record2 === null) {
33845
- unlinkSync7(filePath);
33824
+ unlinkSync8(filePath);
33846
33825
  continue;
33847
33826
  }
33848
33827
  if (record2.pid !== undefined && !isPidAlive(record2.pid)) {
33849
- unlinkSync7(filePath);
33828
+ unlinkSync8(filePath);
33850
33829
  }
33851
33830
  } catch {}
33852
33831
  }
@@ -33855,16 +33834,16 @@ class AftRpcServer {
33855
33834
  if (!this.server || this.token === null)
33856
33835
  return;
33857
33836
  try {
33858
- if (existsSync14(this.portFilePath))
33837
+ if (existsSync15(this.portFilePath))
33859
33838
  return;
33860
33839
  const tmpPath = `${this.portFilePath}.tmp`;
33861
- writeFileSync10(tmpPath, JSON.stringify({
33840
+ writeFileSync11(tmpPath, JSON.stringify({
33862
33841
  port: this.port,
33863
33842
  token: this.token,
33864
33843
  pid: process.pid,
33865
33844
  started_at: Date.now()
33866
33845
  }), { encoding: "utf-8", mode: 384 });
33867
- renameSync8(tmpPath, this.portFilePath);
33846
+ renameSync9(tmpPath, this.portFilePath);
33868
33847
  log2(`RPC port file was missing; rewrote ${this.portFilePath}`);
33869
33848
  } catch {}
33870
33849
  }
@@ -33879,7 +33858,7 @@ class AftRpcServer {
33879
33858
  }
33880
33859
  this.token = null;
33881
33860
  try {
33882
- unlinkSync7(this.portFilePath);
33861
+ unlinkSync8(this.portFilePath);
33883
33862
  } catch {}
33884
33863
  }
33885
33864
  dispatch(req, res) {
@@ -34240,33 +34219,33 @@ function formatStatusMarkdown(status) {
34240
34219
 
34241
34220
  // src/shared/tui-config.ts
34242
34221
  var import_comment_json4 = __toESM(require_src2(), 1);
34243
- import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
34244
- import { dirname as dirname10, join as join22 } from "node:path";
34222
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
34223
+ import { dirname as dirname10, join as join21 } from "node:path";
34245
34224
 
34246
34225
  // src/shared/opencode-config-dir.ts
34247
34226
  import { homedir as homedir13 } from "node:os";
34248
- import { join as join21, resolve as resolve5 } from "node:path";
34227
+ import { join as join20, resolve as resolve9 } from "node:path";
34249
34228
  function getCliConfigDir() {
34250
34229
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
34251
34230
  if (envConfigDir) {
34252
- return resolve5(envConfigDir);
34231
+ return resolve9(envConfigDir);
34253
34232
  }
34254
34233
  if (process.platform === "win32") {
34255
- return join21(homedir13(), ".config", "opencode");
34234
+ return join20(homedir13(), ".config", "opencode");
34256
34235
  }
34257
- return join21(process.env.XDG_CONFIG_HOME || join21(homedir13(), ".config"), "opencode");
34236
+ return join20(process.env.XDG_CONFIG_HOME || join20(homedir13(), ".config"), "opencode");
34258
34237
  }
34259
- function getOpenCodeConfigDir2(_options) {
34238
+ function getOpenCodeConfigDir(_options) {
34260
34239
  return getCliConfigDir();
34261
34240
  }
34262
34241
  function getOpenCodeConfigPaths(options) {
34263
- const configDir = getOpenCodeConfigDir2(options);
34242
+ const configDir = getOpenCodeConfigDir(options);
34264
34243
  return {
34265
34244
  configDir,
34266
- configJson: join21(configDir, "opencode.json"),
34267
- configJsonc: join21(configDir, "opencode.jsonc"),
34268
- packageJson: join21(configDir, "package.json"),
34269
- omoConfig: join21(configDir, "magic-context.jsonc")
34245
+ configJson: join20(configDir, "opencode.json"),
34246
+ configJsonc: join20(configDir, "opencode.jsonc"),
34247
+ packageJson: join20(configDir, "package.json"),
34248
+ omoConfig: join20(configDir, "magic-context.jsonc")
34270
34249
  };
34271
34250
  }
34272
34251
 
@@ -34275,11 +34254,11 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
34275
34254
  var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
34276
34255
  function resolveTuiConfigPath() {
34277
34256
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
34278
- const jsoncPath = join22(configDir, "tui.jsonc");
34279
- const jsonPath = join22(configDir, "tui.json");
34280
- if (existsSync15(jsoncPath))
34257
+ const jsoncPath = join21(configDir, "tui.jsonc");
34258
+ const jsonPath = join21(configDir, "tui.json");
34259
+ if (existsSync16(jsoncPath))
34281
34260
  return jsoncPath;
34282
- if (existsSync15(jsonPath))
34261
+ if (existsSync16(jsonPath))
34283
34262
  return jsonPath;
34284
34263
  return jsonPath;
34285
34264
  }
@@ -34287,8 +34266,8 @@ function ensureTuiPluginEntry() {
34287
34266
  try {
34288
34267
  const configPath = resolveTuiConfigPath();
34289
34268
  let config2 = {};
34290
- if (existsSync15(configPath)) {
34291
- config2 = import_comment_json4.parse(readFileSync14(configPath, "utf-8")) ?? {};
34269
+ if (existsSync16(configPath)) {
34270
+ config2 = import_comment_json4.parse(readFileSync16(configPath, "utf-8")) ?? {};
34292
34271
  }
34293
34272
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
34294
34273
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -34297,7 +34276,7 @@ function ensureTuiPluginEntry() {
34297
34276
  plugins.push(PLUGIN_ENTRY);
34298
34277
  config2.plugin = plugins;
34299
34278
  mkdirSync11(dirname10(configPath), { recursive: true });
34300
- writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
34279
+ writeFileSync12(configPath, `${import_comment_json4.stringify(config2, null, 2)}
34301
34280
  `);
34302
34281
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
34303
34282
  return true;
@@ -34365,8 +34344,8 @@ function installProcessHandlers() {
34365
34344
  }
34366
34345
  signalShutdownStarted = true;
34367
34346
  process.exitCode = SIGNAL_EXIT_CODES[sig];
34368
- const timeout = new Promise((resolve6) => {
34369
- setTimeout(resolve6, SIGNAL_CLEANUP_TIMEOUT_MS);
34347
+ const timeout = new Promise((resolve10) => {
34348
+ setTimeout(resolve10, SIGNAL_CLEANUP_TIMEOUT_MS);
34370
34349
  });
34371
34350
  Promise.race([runCleanups(sig), timeout]).finally(() => {
34372
34351
  process.exit(SIGNAL_EXIT_CODES[sig]);
@@ -34477,7 +34456,6 @@ function instrumentToolMap(tools) {
34477
34456
  import { tool as tool3 } from "@opencode-ai/plugin";
34478
34457
 
34479
34458
  // src/tools/_shared.ts
34480
- import * as fs3 from "node:fs";
34481
34459
  import * as os3 from "node:os";
34482
34460
  import * as path3 from "node:path";
34483
34461
  import { tool as tool2 } from "@opencode-ai/plugin";
@@ -34485,12 +34463,7 @@ var z2 = tool2.schema;
34485
34463
  var optionalInt = (min, max) => z2.number().int().min(min).max(max).optional();
34486
34464
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
34487
34465
  function canonicalizeDirectory(dir) {
34488
- const trimmed = dir.replace(/[/\\]+$/, "");
34489
- try {
34490
- return fs3.realpathSync(trimmed);
34491
- } catch {
34492
- return path3.resolve(trimmed);
34493
- }
34466
+ return canonicalizeProjectRoot(dir);
34494
34467
  }
34495
34468
  function projectRootFor(runtime) {
34496
34469
  const cached2 = getSessionDirectoryCached(runtime.sessionID);
@@ -34559,7 +34532,7 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
34559
34532
  }
34560
34533
 
34561
34534
  // src/tools/permissions.ts
34562
- import * as fs4 from "node:fs";
34535
+ import * as fs3 from "node:fs";
34563
34536
  import * as path4 from "node:path";
34564
34537
  var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.15.5 or newer for permission asks; please upgrade OpenCode";
34565
34538
  var RESTRICT_NOTICE_THROTTLE_MS = 5 * 60 * 1000;
@@ -34646,7 +34619,7 @@ function normalizePath(p) {
34646
34619
  return p;
34647
34620
  const resolved = path4.resolve(windowsPath(p));
34648
34621
  try {
34649
- return fs4.realpathSync.native(resolved);
34622
+ return fs3.realpathSync.native(resolved);
34650
34623
  } catch {
34651
34624
  return resolved;
34652
34625
  }
@@ -34828,8 +34801,9 @@ function astTools(ctx) {
34828
34801
  params.paths = paths;
34829
34802
  if (!isEmptyParam(args.globs))
34830
34803
  params.globs = args.globs;
34831
- if (args.contextLines !== undefined)
34832
- params.context = Number(args.contextLines);
34804
+ const contextLines = coerceOptionalInt(args.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
34805
+ if (contextLines !== undefined)
34806
+ params.context = contextLines;
34833
34807
  const response = await callBridge(ctx, context, "ast_search", params);
34834
34808
  if (response.success === false) {
34835
34809
  throw new Error(response.message || "ast_search failed");
@@ -34913,7 +34887,7 @@ ${hint}`;
34913
34887
  dryRun: z3.boolean().optional().describe("Preview changes without applying (default: false)")
34914
34888
  },
34915
34889
  execute: async (args, context) => {
34916
- const isDryRun = args.dryRun === true;
34890
+ const isDryRun = coerceBoolean(args.dryRun);
34917
34891
  const paths = await resolveAstPaths(ctx, context, args.paths);
34918
34892
  const externalDenied = await checkAstPathsPermission(ctx, context, paths);
34919
34893
  if (externalDenied)
@@ -34945,7 +34919,7 @@ ${hint}`;
34945
34919
  params.paths = paths;
34946
34920
  if (!isEmptyParam(args.globs))
34947
34921
  params.globs = args.globs;
34948
- params.dry_run = args.dryRun === true;
34922
+ params.dry_run = coerceBoolean(args.dryRun);
34949
34923
  const response = await callBridge(ctx, context, "ast_replace", params);
34950
34924
  if (response.success === false) {
34951
34925
  throw new Error(response.message || "ast_replace failed");
@@ -35118,8 +35092,8 @@ function resolveForegroundWaitMs(configured) {
35118
35092
  }
35119
35093
  function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn) {
35120
35094
  const searchSteer = aftSearchRegistered ? "use aft_search (concepts, identifiers, regex, literals), read, aft_outline, or aft_zoom instead" : "use the grep tool, read, aft_outline, or aft_zoom instead";
35121
- const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output." : "";
35122
- const tasks = backgroundOn ? ' Pass background: true to run in the background and get a taskId for bash_status/bash_watch/bash_kill. Pass pty: true for interactive programs (REPLs, TUIs) and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically). Use bash_watch to wait for exit or output patterns (sync blocks, async notifies). Do not loop bash_status to wait.' : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
35095
+ const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep." : "";
35096
+ const tasks = backgroundOn ? ' Commands run in the foreground and return inline; a long-running one auto-promotes to background and delivers a completion reminder when it finishes so for the common "I am waiting on this result" case, just run it and wait, no flags needed. Use background: true yourself ONLY when you have other useful work to do while it runs; then bash_watch waits on the task (sync blocks until exit/pattern, async notifies) and bash_status peeks at it — never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one), and never loop bash_status to wait. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: "screen" }) plus bash_write.' : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
35123
35097
  return `Execute shell commands.${compression}${tasks}
35124
35098
 
35125
35099
  DO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — ${searchSteer}.`;
@@ -35200,14 +35174,12 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35200
35174
  const description = args2.description;
35201
35175
  const metadata = context.metadata;
35202
35176
  const rawCommand = args2.command;
35203
- const compressionEnabled = bashCfg.compress && args2.compressed !== false;
35204
- const pipeStrip = maybeStripCompressorPipe(rawCommand, compressionEnabled);
35205
- const command = pipeStrip.command;
35177
+ const command = rawCommand;
35206
35178
  const cwd = args2.workdir ?? context.directory;
35207
35179
  const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
35208
35180
  const backgroundDisabled = !bashCfg.background;
35209
- const requestedPty = !backgroundDisabled && args2.pty === true;
35210
- const requestedBackground = !backgroundDisabled && (args2.background === true || requestedPty);
35181
+ const requestedPty = !backgroundDisabled && coerceBoolean(args2.pty);
35182
+ const requestedBackground = !backgroundDisabled && (coerceBoolean(args2.background) || requestedPty);
35211
35183
  if (requestedPty && isSubagent) {
35212
35184
  throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
35213
35185
  }
@@ -35215,7 +35187,10 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35215
35187
  const subagentForcedForeground = isSubagent && !allowSubagentBg;
35216
35188
  const blockToCompletion = subagentForcedForeground || backgroundDisabled;
35217
35189
  const effectiveBackground = blockToCompletion ? false : requestedBackground;
35218
- const rawTimeout = args2.timeout;
35190
+ const rawTimeout = coerceOptionalInt(args2.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
35191
+ const ptyRows = coerceOptionalInt(args2.ptyRows, "ptyRows", 1, 60);
35192
+ const ptyCols = coerceOptionalInt(args2.ptyCols, "ptyCols", 1, 140);
35193
+ const compressed = coerceBoolean(args2.compressed, true);
35219
35194
  const effectiveTimeout = effectiveBackground || backgroundDisabled ? rawTimeout : resolveBashKillTimeout(rawTimeout, bashCfg.foreground_wait_window_ms);
35220
35195
  if (subagentForcedForeground && requestedBackground) {
35221
35196
  sessionLog(context.sessionID, "[bash] subagent + background:true → converting to foreground (subagent would lose task_id)");
@@ -35229,10 +35204,10 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35229
35204
  description,
35230
35205
  background: effectiveBackground,
35231
35206
  notify_on_completion: effectiveBackground,
35232
- compressed: args2.compressed,
35207
+ compressed,
35233
35208
  pty: requestedPty,
35234
- pty_rows: args2.ptyRows,
35235
- pty_cols: args2.ptyCols,
35209
+ pty_rows: ptyRows,
35210
+ pty_cols: ptyCols,
35236
35211
  permissions_requested: true
35237
35212
  }, callBashBridge, {
35238
35213
  onProgress: ({ text }) => {
@@ -35251,7 +35226,6 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35251
35226
  let startedLine = formatBackgroundLaunch(taskId, requestedPty);
35252
35227
  if (isSubagent && allowSubagentBg)
35253
35228
  startedLine += subagentGuidance(taskId);
35254
- startedLine = appendPipeStripNote(startedLine, pipeStrip.note);
35255
35229
  const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
35256
35230
  metadata?.(metadataPayload2);
35257
35231
  return { output: startedLine, title: uiTitle, metadata: metadataPayload2 };
@@ -35265,7 +35239,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35265
35239
  throw new Error(status.message ?? "bash_status failed");
35266
35240
  }
35267
35241
  if (isTerminalStatus(status.status)) {
35268
- const rendered2 = maybeAppendGrepSearchHint(appendPipeStripNote(formatForegroundResult(status), pipeStrip.note), command, aftSearchRegistered, projectRootFor(context));
35242
+ const rendered2 = maybeAppendGrepSearchHint(formatForegroundResult(status), command, aftSearchRegistered, projectRootFor(context));
35269
35243
  const metadataPayload2 = foregroundMetadata(description, status, rendered2);
35270
35244
  metadata?.(metadataPayload2);
35271
35245
  return { output: rendered2, title: uiTitle, metadata: metadataPayload2 };
@@ -35285,7 +35259,6 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35285
35259
  let message = formatPromotionMessage(taskId, effectiveTimeout, foregroundWaitMs);
35286
35260
  if (isSubagent && allowSubagentBg)
35287
35261
  message += subagentGuidance(taskId);
35288
- message = appendPipeStripNote(message, pipeStrip.note);
35289
35262
  const metadataPayload2 = { description, output: message, status: "running", taskId };
35290
35263
  metadata?.(metadataPayload2);
35291
35264
  return { output: message, title: uiTitle, metadata: metadataPayload2 };
@@ -35320,7 +35293,6 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35320
35293
  rendered += `
35321
35294
  [exit code: ${exit}]`;
35322
35295
  }
35323
- rendered = appendPipeStripNote(rendered, pipeStrip.note);
35324
35296
  return {
35325
35297
  output: rendered,
35326
35298
  title: description ?? shortenCommand(command),
@@ -35501,7 +35473,7 @@ function conflictTools(ctx) {
35501
35473
  }
35502
35474
 
35503
35475
  // src/tools/hoisted.ts
35504
- import * as fs6 from "node:fs";
35476
+ import * as fs5 from "node:fs";
35505
35477
  import * as path5 from "node:path";
35506
35478
  import { tool as tool8 } from "@opencode-ai/plugin";
35507
35479
 
@@ -35641,6 +35613,16 @@ function normalizeUnicode(str) {
35641
35613
  function normalizeIndent(str) {
35642
35614
  return str.replace(/^[\t ]+/, (m) => " ".repeat(m.length));
35643
35615
  }
35616
+ var REFLOW_NON_WS_TOLERANCE = 8;
35617
+ function normalizeReflowWhitespace(str) {
35618
+ return str.replace(/\s+/gu, " ").trim();
35619
+ }
35620
+ function stripReflowWhitespace(str) {
35621
+ return str.replace(/\s+/gu, "");
35622
+ }
35623
+ function hasReflowContent(str) {
35624
+ return /\S/u.test(str);
35625
+ }
35644
35626
  function tryMatch(lines, pattern, startIndex, compare, eof) {
35645
35627
  if (eof) {
35646
35628
  const fromEnd = lines.length - pattern.length;
@@ -35655,6 +35637,7 @@ function tryMatch(lines, pattern, startIndex, compare, eof) {
35655
35637
  if (matches)
35656
35638
  return fromEnd;
35657
35639
  }
35640
+ return -1;
35658
35641
  }
35659
35642
  for (let i = startIndex;i <= lines.length - pattern.length; i++) {
35660
35643
  let matches = true;
@@ -35669,24 +35652,69 @@ function tryMatch(lines, pattern, startIndex, compare, eof) {
35669
35652
  }
35670
35653
  return -1;
35671
35654
  }
35655
+ function findReflowMatch(lines, pattern, startIndex) {
35656
+ const needleText = pattern.join(`
35657
+ `);
35658
+ const normalizedNeedle = normalizeReflowWhitespace(needleText);
35659
+ const needleNonWhitespace = stripReflowWhitespace(needleText);
35660
+ if (normalizedNeedle.length === 0 || needleNonWhitespace.length === 0)
35661
+ return null;
35662
+ const minNonWhitespace = Math.max(0, needleNonWhitespace.length - REFLOW_NON_WS_TOLERANCE);
35663
+ const maxNonWhitespace = needleNonWhitespace.length + REFLOW_NON_WS_TOLERANCE;
35664
+ const matches = [];
35665
+ const seen = new Set;
35666
+ for (let start = Math.max(0, startIndex);start < lines.length; start++) {
35667
+ if (!hasReflowContent(lines[start]))
35668
+ continue;
35669
+ let windowNonWhitespaceLen = 0;
35670
+ for (let end = start + 1;end <= lines.length; end++) {
35671
+ const line = lines[end - 1];
35672
+ windowNonWhitespaceLen += stripReflowWhitespace(line).length;
35673
+ if (windowNonWhitespaceLen > maxNonWhitespace)
35674
+ break;
35675
+ if (windowNonWhitespaceLen < minNonWhitespace)
35676
+ continue;
35677
+ if (!hasReflowContent(line))
35678
+ continue;
35679
+ const windowText = lines.slice(start, end).join(`
35680
+ `);
35681
+ const windowNonWhitespace = stripReflowWhitespace(windowText);
35682
+ if (windowNonWhitespace !== needleNonWhitespace)
35683
+ continue;
35684
+ if (normalizeReflowWhitespace(windowText) !== normalizedNeedle)
35685
+ continue;
35686
+ const key = `${start}:${end}`;
35687
+ if (!seen.has(key)) {
35688
+ seen.add(key);
35689
+ matches.push({ found: start, lineCount: end - start });
35690
+ }
35691
+ }
35692
+ }
35693
+ return matches.length === 1 ? matches[0] : null;
35694
+ }
35672
35695
  function seekSequenceTiered(lines, pattern, startIndex, eof = false) {
35673
35696
  if (pattern.length === 0)
35674
35697
  return null;
35675
35698
  const exact = tryMatch(lines, pattern, startIndex, (a, b) => a === b, eof);
35676
35699
  if (exact !== -1)
35677
- return { found: exact, tier: "exact" };
35700
+ return { found: exact, tier: "exact", lineCount: pattern.length };
35678
35701
  const rstrip = tryMatch(lines, pattern, startIndex, (a, b) => a.trimEnd() === b.trimEnd(), eof);
35679
35702
  if (rstrip !== -1)
35680
- return { found: rstrip, tier: "rstrip" };
35703
+ return { found: rstrip, tier: "rstrip", lineCount: pattern.length };
35681
35704
  const trim = tryMatch(lines, pattern, startIndex, (a, b) => a.trim() === b.trim(), eof);
35682
35705
  if (trim !== -1)
35683
- return { found: trim, tier: "trim" };
35706
+ return { found: trim, tier: "trim", lineCount: pattern.length };
35684
35707
  const indent = tryMatch(lines, pattern, startIndex, (a, b) => normalizeIndent(a).trimEnd() === normalizeIndent(b).trimEnd(), eof);
35685
35708
  if (indent !== -1)
35686
- return { found: indent, tier: "indent" };
35709
+ return { found: indent, tier: "indent", lineCount: pattern.length };
35687
35710
  const unicode = tryMatch(lines, pattern, startIndex, (a, b) => normalizeUnicode(a.trim()) === normalizeUnicode(b.trim()), eof);
35688
35711
  if (unicode !== -1)
35689
- return { found: unicode, tier: "unicode" };
35712
+ return { found: unicode, tier: "unicode", lineCount: pattern.length };
35713
+ if (eof)
35714
+ return null;
35715
+ const reflow = findReflowMatch(lines, pattern, startIndex);
35716
+ if (reflow)
35717
+ return { ...reflow, tier: "reflow" };
35690
35718
  return null;
35691
35719
  }
35692
35720
  function seekSequence(lines, pattern, startIndex, eof = false) {
@@ -35732,11 +35760,11 @@ function applyUpdateChunks(originalContent, filePath, chunks) {
35732
35760
  let lineIndex = 0;
35733
35761
  for (const chunk of chunks) {
35734
35762
  if (chunk.change_context) {
35735
- const contextIdx = seekSequence(originalLines, [chunk.change_context], lineIndex);
35736
- if (contextIdx === -1) {
35763
+ const contextMatch = seekSequenceTiered(originalLines, [chunk.change_context], lineIndex);
35764
+ if (!contextMatch) {
35737
35765
  throw new Error(`Failed to find context '${chunk.change_context}' in ${filePath}`);
35738
35766
  }
35739
- lineIndex = contextIdx + 1;
35767
+ lineIndex = contextMatch.found + contextMatch.lineCount;
35740
35768
  }
35741
35769
  if (chunk.old_lines.length === 0) {
35742
35770
  let insertionIdx;
@@ -35752,17 +35780,17 @@ function applyUpdateChunks(originalContent, filePath, chunks) {
35752
35780
  }
35753
35781
  let pattern = chunk.old_lines;
35754
35782
  let newSlice = chunk.new_lines;
35755
- let found = seekSequence(originalLines, pattern, lineIndex, chunk.is_end_of_file);
35756
- if (found === -1 && pattern.length > 0 && pattern[pattern.length - 1] === "") {
35783
+ let match = seekSequenceTiered(originalLines, pattern, lineIndex, chunk.is_end_of_file);
35784
+ if (!match && pattern.length > 0 && pattern[pattern.length - 1] === "") {
35757
35785
  pattern = pattern.slice(0, -1);
35758
35786
  if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") {
35759
35787
  newSlice = newSlice.slice(0, -1);
35760
35788
  }
35761
- found = seekSequence(originalLines, pattern, lineIndex, chunk.is_end_of_file);
35789
+ match = seekSequenceTiered(originalLines, pattern, lineIndex, chunk.is_end_of_file);
35762
35790
  }
35763
- if (found !== -1) {
35764
- replacements.push([found, pattern.length, newSlice]);
35765
- lineIndex = found + pattern.length;
35791
+ if (match) {
35792
+ replacements.push([match.found, match.lineCount, newSlice]);
35793
+ lineIndex = match.found + match.lineCount;
35766
35794
  } else {
35767
35795
  const newSliceTrimmed = newSlice.filter((line) => line.trim().length > 0);
35768
35796
  const alreadyApplied = newSliceTrimmed.length > 0 && seekSequence(originalLines, newSliceTrimmed, 0, chunk.is_end_of_file) !== -1;
@@ -35779,7 +35807,7 @@ Closest match starts at line ${closest.lineNumber} ` + `(${closest.matchedLines}
35779
35807
  ` + ` expected: ${JSON.stringify(expectedLine)}
35780
35808
  ` + ` actual: ${JSON.stringify(actualLine)}`;
35781
35809
  }
35782
- const triedTiers = "exact, trimEnd, trim, indent (tab/space), unicode";
35810
+ const triedTiers = "exact, trimEnd, trim, indent (tab/space), unicode, reflow (whitespace-normalized)";
35783
35811
  const alreadyAppliedHint = alreadyApplied ? `
35784
35812
 
35785
35813
  Hint: the replacement content for this hunk already appears in the file. ` + "The patch may have been partially applied in a prior turn — re-read the file " + "to confirm which hunks still need to apply." : "";
@@ -35804,7 +35832,7 @@ ${chunk.old_lines.join(`
35804
35832
  }
35805
35833
 
35806
35834
  // src/tools/bash_watch.ts
35807
- import * as fs5 from "node:fs/promises";
35835
+ import * as fs4 from "node:fs/promises";
35808
35836
  import { tool as tool6 } from "@opencode-ai/plugin";
35809
35837
  var z6 = tool6.schema;
35810
35838
  var BASH_WAIT_POLL_INTERVAL_MS = 100;
@@ -35826,7 +35854,7 @@ function createBashWatchTool(ctx) {
35826
35854
  },
35827
35855
  execute: async (args, context) => {
35828
35856
  const taskId = args.taskId;
35829
- const requestedAsync = args.background === true;
35857
+ const requestedAsync = coerceBoolean(args.background);
35830
35858
  const waitFor = parseWaitPattern(args.pattern);
35831
35859
  const bashCfg = resolveBashConfig(ctx.config);
35832
35860
  const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
@@ -35838,7 +35866,7 @@ function createBashWatchTool(ctx) {
35838
35866
  }
35839
35867
  const notifyParams = {
35840
35868
  task_id: taskId,
35841
- once: args.once !== false
35869
+ once: coerceBoolean(args.once, true)
35842
35870
  };
35843
35871
  if (waitFor.kind === "regex")
35844
35872
  notifyParams.regex = waitFor.source;
@@ -35865,11 +35893,11 @@ function createBashWatchTool(ctx) {
35865
35893
  return `Watch registered: ${registered.watch_id} on task ${taskId}
35866
35894
  A notification will fire when the pattern matches or the task exits.`;
35867
35895
  }
35868
- const effectiveWaitMs = subagentForcedSync ? MAX_BASH_STATUS_WAIT_TIMEOUT_MS : Math.min(args.timeoutMs ?? DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS, MAX_BASH_STATUS_WAIT_TIMEOUT_MS);
35896
+ const effectiveWaitMs = subagentForcedSync ? MAX_BASH_STATUS_WAIT_TIMEOUT_MS : Math.min(coerceOptionalInt(args.timeoutMs, "timeoutMs", 1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS) ?? DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS, MAX_BASH_STATUS_WAIT_TIMEOUT_MS);
35869
35897
  const data = await waitForBashStatus(ctx, context, taskId, undefined, waitFor, effectiveWaitMs);
35870
35898
  const waited = data.waited;
35871
35899
  if (waited?.reason === "user_message") {
35872
- const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, args.once !== false);
35900
+ const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true));
35873
35901
  const metadata2 = context.metadata;
35874
35902
  metadata2?.({ taskId, status: data.status, waited, convertedToAsync: true });
35875
35903
  return convertedText;
@@ -36068,7 +36096,7 @@ async function readNewTaskOutput(runtime, taskId, data, cursor) {
36068
36096
  };
36069
36097
  }
36070
36098
  async function readFileBytesFrom(outputPath, cursor) {
36071
- const handle = await fs5.open(outputPath, "r");
36099
+ const handle = await fs4.open(outputPath, "r");
36072
36100
  try {
36073
36101
  const chunks = [];
36074
36102
  let offset = cursor;
@@ -36396,7 +36424,7 @@ function diagnosticsOnEditDefault(ctx) {
36396
36424
  }
36397
36425
  async function readCurrentFileForPreview(filePath) {
36398
36426
  try {
36399
- return await fs6.promises.readFile(filePath, "utf-8");
36427
+ return await fs5.promises.readFile(filePath, "utf-8");
36400
36428
  } catch (error50) {
36401
36429
  if (error50 && typeof error50 === "object" && "code" in error50 && error50.code === "ENOENT") {
36402
36430
  return "";
@@ -36426,7 +36454,7 @@ async function readPatchPreviewContent(virtualFiles, filePath, action, patchPath
36426
36454
  return virtualContent;
36427
36455
  }
36428
36456
  try {
36429
- return await fs6.promises.readFile(filePath, "utf-8");
36457
+ return await fs5.promises.readFile(filePath, "utf-8");
36430
36458
  } catch (error50) {
36431
36459
  throw new Error(`Failed to ${action} ${patchPath}: ${formatError2(error50)}`);
36432
36460
  }
@@ -36442,7 +36470,7 @@ async function buildApplyPatchPreview(hunks, projectRoot) {
36442
36470
  switch (hunk.type) {
36443
36471
  case "add": {
36444
36472
  const virtualContent = virtualPatchContent(virtualFiles, filePath);
36445
- const exists = virtualContent !== undefined ? virtualContent !== null : fs6.existsSync(filePath);
36473
+ const exists = virtualContent !== undefined ? virtualContent !== null : fs5.existsSync(filePath);
36446
36474
  if (exists) {
36447
36475
  throw new Error(`Failed to create ${hunk.path}: file already exists. Use *** Update File: to modify, or *** Delete File: first if you want to replace it entirely.`);
36448
36476
  }
@@ -36550,7 +36578,7 @@ function createReadTool(ctx) {
36550
36578
  const label = isImage ? "Image" : "PDF";
36551
36579
  let fileSize = 0;
36552
36580
  try {
36553
- const stat = await import("node:fs/promises").then((fs7) => fs7.stat(filePath));
36581
+ const stat = await import("node:fs/promises").then((fs6) => fs6.stat(filePath));
36554
36582
  fileSize = stat.size;
36555
36583
  } catch {}
36556
36584
  const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
@@ -36566,12 +36594,16 @@ function createReadTool(ctx) {
36566
36594
  }
36567
36595
  };
36568
36596
  }
36569
- let startLine = args.startLine;
36570
- let endLine = args.endLine;
36571
- if (startLine === undefined && args.offset !== undefined) {
36572
- startLine = args.offset;
36573
- if (args.limit !== undefined) {
36574
- endLine = Number(args.offset) + Number(args.limit) - 1;
36597
+ const rawStartLine = coerceOptionalInt(args.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
36598
+ const rawEndLine = coerceOptionalInt(args.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
36599
+ const rawLimit = coerceOptionalInt(args.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
36600
+ const rawOffset = coerceOptionalInt(args.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
36601
+ let startLine = rawStartLine;
36602
+ let endLine = rawEndLine;
36603
+ if (startLine === undefined && rawOffset !== undefined) {
36604
+ startLine = rawOffset;
36605
+ if (rawLimit !== undefined) {
36606
+ endLine = rawOffset + rawLimit - 1;
36575
36607
  }
36576
36608
  }
36577
36609
  const params = { file: filePath };
@@ -36579,8 +36611,8 @@ function createReadTool(ctx) {
36579
36611
  params.start_line = startLine;
36580
36612
  if (endLine !== undefined)
36581
36613
  params.end_line = endLine;
36582
- if (args.limit !== undefined && args.offset === undefined)
36583
- params.limit = args.limit;
36614
+ if (rawLimit !== undefined && rawOffset === undefined)
36615
+ params.limit = rawLimit;
36584
36616
  const data = await callBridge(ctx, context, "read", params);
36585
36617
  if (data.success === false) {
36586
36618
  throw new Error(data.message || "read failed");
@@ -36598,7 +36630,7 @@ function createReadTool(ctx) {
36598
36630
  return { output: data.message, title: dp, metadata: { title: dp } };
36599
36631
  }
36600
36632
  let output = data.content;
36601
- const agentSpecifiedRange = args.startLine !== undefined || args.endLine !== undefined || args.offset !== undefined || args.limit !== undefined;
36633
+ const agentSpecifiedRange = rawStartLine !== undefined || rawEndLine !== undefined || rawOffset !== undefined || rawLimit !== undefined;
36602
36634
  const footer = formatReadFooter2(agentSpecifiedRange, data);
36603
36635
  if (footer)
36604
36636
  output += footer;
@@ -36779,6 +36811,7 @@ function createEditTool(ctx, writeToolName = "write") {
36779
36811
  return permissionDeniedResponse(denial2);
36780
36812
  }
36781
36813
  const params = { file: filePath };
36814
+ const occurrence = coerceOptionalInt(args.occurrence, "occurrence", 0, Number.MAX_SAFE_INTEGER);
36782
36815
  let command;
36783
36816
  if (typeof args.appendContent === "string") {
36784
36817
  command = "edit_match";
@@ -36812,10 +36845,10 @@ function createEditTool(ctx, writeToolName = "write") {
36812
36845
  command = "edit_match";
36813
36846
  params.match = args.oldString;
36814
36847
  params.replacement = args.newString ?? "";
36815
- if (args.replaceAll !== undefined)
36816
- params.replace_all = args.replaceAll;
36817
- if (args.occurrence !== undefined)
36818
- params.occurrence = args.occurrence;
36848
+ if (coerceBoolean(args.replaceAll))
36849
+ params.replace_all = true;
36850
+ if (occurrence !== undefined)
36851
+ params.occurrence = occurrence;
36819
36852
  } else {
36820
36853
  const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content', or an 'edits' array.";
36821
36854
  throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
@@ -36979,7 +37012,7 @@ function createApplyPatchTool(ctx) {
36979
37012
  const rememberAffectedPath = (abs) => {
36980
37013
  affectedAbs.add(abs);
36981
37014
  if (!initiallyExistsAbs.has(abs)) {
36982
- initiallyExistsAbs.set(abs, fs6.existsSync(abs));
37015
+ initiallyExistsAbs.set(abs, fs5.existsSync(abs));
36983
37016
  }
36984
37017
  };
36985
37018
  for (const h of hunks) {
@@ -37041,7 +37074,7 @@ function createApplyPatchTool(ctx) {
37041
37074
  const filePath = resolvePathFromProjectRoot(projectRoot, hunk.path);
37042
37075
  switch (hunk.type) {
37043
37076
  case "add": {
37044
- if (fs6.existsSync(filePath)) {
37077
+ if (fs5.existsSync(filePath)) {
37045
37078
  const msg = `Failed to create ${hunk.path}: file already exists. Use *** Update File: to modify, or *** Delete File: first if you want to replace it entirely.`;
37046
37079
  results.push(msg);
37047
37080
  failures.push({ index: hunkIndex, path: hunk.path });
@@ -37082,9 +37115,9 @@ function createApplyPatchTool(ctx) {
37082
37115
  results.push(msg);
37083
37116
  failures.push({ index: hunkIndex, path: hunk.path });
37084
37117
  const filePath2 = resolvePathFromProjectRoot(projectRoot, hunk.path);
37085
- if (fs6.existsSync(filePath2)) {
37118
+ if (fs5.existsSync(filePath2)) {
37086
37119
  try {
37087
- fs6.rmSync(filePath2, { force: true });
37120
+ fs5.rmSync(filePath2, { force: true });
37088
37121
  } catch {}
37089
37122
  }
37090
37123
  }
@@ -37092,7 +37125,7 @@ function createApplyPatchTool(ctx) {
37092
37125
  }
37093
37126
  case "delete": {
37094
37127
  try {
37095
- const before = await fs6.promises.readFile(filePath, "utf-8").catch(() => "");
37128
+ const before = await fs5.promises.readFile(filePath, "utf-8").catch(() => "");
37096
37129
  const deleteResult = await callBridge(ctx, context, "delete_file", {
37097
37130
  file: filePath
37098
37131
  });
@@ -37118,7 +37151,7 @@ function createApplyPatchTool(ctx) {
37118
37151
  }
37119
37152
  case "update": {
37120
37153
  try {
37121
- const original = await fs6.promises.readFile(filePath, "utf-8");
37154
+ const original = await fs5.promises.readFile(filePath, "utf-8");
37122
37155
  const newContent = applyUpdateChunks(original, filePath, hunk.chunks);
37123
37156
  const targetPath = hunk.move_path ? resolvePathFromProjectRoot(projectRoot, hunk.move_path) : filePath;
37124
37157
  const writeResult = await callBridge(ctx, context, "write", {
@@ -37183,7 +37216,7 @@ ${diagLines}`);
37183
37216
  if (rollbackResult.success === false) {
37184
37217
  throw new Error(rollbackResult.message ?? "checkpoint restore failed");
37185
37218
  }
37186
- if (newlyCreatedAbs.has(targetPath) && fs6.existsSync(targetPath)) {
37219
+ if (newlyCreatedAbs.has(targetPath) && fs5.existsSync(targetPath)) {
37187
37220
  const cleanupResult = await callBridge(ctx, context, "delete_file", {
37188
37221
  file: targetPath
37189
37222
  });
@@ -37312,7 +37345,7 @@ function createDeleteTool(ctx) {
37312
37345
  if (inputs.length === 0) {
37313
37346
  throw new Error("delete: `files` must be a non-empty array of paths");
37314
37347
  }
37315
- const recursive = args.recursive === true;
37348
+ const recursive = coerceBoolean(args.recursive);
37316
37349
  const projectRoot = await resolveProjectRoot(ctx, context);
37317
37350
  const absolutePaths = inputs.map((f) => resolvePathFromProjectRoot(projectRoot, f));
37318
37351
  {
@@ -37708,7 +37741,7 @@ function createInspectTier2IdleScheduler(options) {
37708
37741
  }
37709
37742
  function inspectTools(ctx) {
37710
37743
  const inspectTool = {
37711
- description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) run as background scans triggered on session idle; calls may return cached `stale_categories: [...]` results or `pending_categories: [...]` while a refresh is in progress (waits up to 1s for fresh data before falling back to cached).\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
37744
+ description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) waits for a fresh reuse scan up to a short deadline; if a category is still scanning the response reports `complete: false` with `pending_categories: [...]` rather than a fabricated clean count.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
37712
37745
 
37713
37746
  ` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
37714
37747
  args: {
@@ -37723,7 +37756,9 @@ function inspectTools(ctx) {
37723
37756
  return permissionDeniedResponse(scoped.denial);
37724
37757
  const scope = scoped.scope;
37725
37758
  const topK = args.topK === undefined || args.topK === null ? undefined : args.topK;
37726
- const response = await callBridge(ctx, context, "inspect", { sections, scope, topK });
37759
+ const response = await callBridge(ctx, context, "inspect", { sections, scope, topK }, {
37760
+ keepBridgeOnTimeout: true
37761
+ });
37727
37762
  if (response.success === false) {
37728
37763
  throw new Error(response.message || "inspect failed");
37729
37764
  }
@@ -37800,8 +37835,9 @@ function navigationTools(ctx) {
37800
37835
  file: filePath,
37801
37836
  symbol: args.symbol
37802
37837
  };
37803
- if (args.depth !== undefined)
37804
- params.depth = Number(args.depth);
37838
+ const depth = coerceOptionalInt(args.depth, "depth", 1, Number.MAX_SAFE_INTEGER);
37839
+ if (depth !== undefined)
37840
+ params.depth = depth;
37805
37841
  if (!isEmptyParam(args.expression))
37806
37842
  params.expression = args.expression;
37807
37843
  if (!isEmptyParam(args.toSymbol))
@@ -37859,8 +37895,8 @@ function readingTools(ctx) {
37859
37895
  files: z12.boolean().optional().describe("Directory-only mode: when true, target must be a directory or array of directories and the result is a flat file tree with path, language, symbol count, and byte size instead of a symbol outline.")
37860
37896
  },
37861
37897
  execute: async (args, context) => {
37862
- const target = args.target;
37863
- const filesMode = args.files === true;
37898
+ const target = coerceTargetParam(args.target);
37899
+ const filesMode = coerceBoolean(args.files);
37864
37900
  const hasUrl = typeof target === "string" && (target.startsWith("http://") || target.startsWith("https://"));
37865
37901
  const isArray = Array.isArray(target) && target.length > 0;
37866
37902
  if (filesMode) {
@@ -37988,7 +38024,8 @@ function readingTools(ctx) {
37988
38024
  const hasUrl = !isEmptyParam(args.url);
37989
38025
  const hasTargets = hasTargetsProvided(args.targets);
37990
38026
  const hasSymbols = !isEmptyParam(args.symbols);
37991
- const wantCallgraph = args.callgraph === true;
38027
+ const wantCallgraph = coerceBoolean(args.callgraph);
38028
+ const contextLines = coerceOptionalInt(args.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
37992
38029
  const zoomTitle = buildZoomTitle(args);
37993
38030
  const zoomDisplay = { title: zoomTitle };
37994
38031
  if (hasFilePath)
@@ -38000,8 +38037,8 @@ function readingTools(ctx) {
38000
38037
  }
38001
38038
  if (hasTargets)
38002
38039
  zoomDisplay.targets = JSON.stringify(args.targets);
38003
- if (args.contextLines !== undefined)
38004
- zoomDisplay.contextLines = args.contextLines;
38040
+ if (contextLines !== undefined)
38041
+ zoomDisplay.contextLines = contextLines;
38005
38042
  if (wantCallgraph)
38006
38043
  zoomDisplay.callgraph = true;
38007
38044
  const withMeta = (output) => ({
@@ -38034,8 +38071,8 @@ function readingTools(ctx) {
38034
38071
  file: resolvedTargets[index],
38035
38072
  symbol: t.symbol
38036
38073
  };
38037
- if (args.contextLines !== undefined)
38038
- params2.context_lines = args.contextLines;
38074
+ if (contextLines !== undefined)
38075
+ params2.context_lines = contextLines;
38039
38076
  if (wantCallgraph)
38040
38077
  params2.callgraph = true;
38041
38078
  return callBridge(ctx, context, "zoom", params2).catch((err) => ({
@@ -38067,8 +38104,8 @@ function readingTools(ctx) {
38067
38104
  if (symbolsArray) {
38068
38105
  const results = await Promise.all(symbolsArray.map((sym) => {
38069
38106
  const params2 = { file: file2, symbol: sym };
38070
- if (args.contextLines !== undefined)
38071
- params2.context_lines = args.contextLines;
38107
+ if (contextLines !== undefined)
38108
+ params2.context_lines = contextLines;
38072
38109
  if (wantCallgraph)
38073
38110
  params2.callgraph = true;
38074
38111
  return callBridge(ctx, context, "zoom", params2).catch((err) => ({
@@ -38091,8 +38128,8 @@ function readingTools(ctx) {
38091
38128
  return withMeta(formatZoomBatchResult(targetLabel, symbolsArray, results).text);
38092
38129
  }
38093
38130
  const params = { file: file2 };
38094
- if (args.contextLines !== undefined)
38095
- params.context_lines = args.contextLines;
38131
+ if (contextLines !== undefined)
38132
+ params.context_lines = contextLines;
38096
38133
  if (wantCallgraph)
38097
38134
  params.callgraph = true;
38098
38135
  const data = await callBridge(ctx, context, "zoom", params);
@@ -38274,6 +38311,9 @@ function refactoringTools(ctx) {
38274
38311
  },
38275
38312
  execute: async (args, context) => {
38276
38313
  const op = args.op;
38314
+ const startLine = coerceOptionalInt(args.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
38315
+ const endLine = coerceOptionalInt(args.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
38316
+ const callSiteLine = coerceOptionalInt(args.callSiteLine, "callSiteLine", 1, Number.MAX_SAFE_INTEGER);
38277
38317
  if ((op === "move" || op === "inline") && isEmptyParam(args.symbol)) {
38278
38318
  throw new Error(`'symbol' is required for '${op}' op`);
38279
38319
  }
@@ -38283,12 +38323,12 @@ function refactoringTools(ctx) {
38283
38323
  if (op === "extract") {
38284
38324
  if (isEmptyParam(args.name))
38285
38325
  throw new Error("'name' is required for 'extract' op");
38286
- if (args.startLine === undefined)
38326
+ if (startLine === undefined)
38287
38327
  throw new Error("'startLine' is required for 'extract' op");
38288
- if (args.endLine === undefined)
38328
+ if (endLine === undefined)
38289
38329
  throw new Error("'endLine' is required for 'extract' op");
38290
38330
  }
38291
- if (op === "inline" && args.callSiteLine === undefined) {
38331
+ if (op === "inline" && callSiteLine === undefined) {
38292
38332
  throw new Error("'callSiteLine' is required for 'inline' op");
38293
38333
  }
38294
38334
  const filePath = await resolvePathArg(ctx, context, args.filePath);
@@ -38329,12 +38369,15 @@ function refactoringTools(ctx) {
38329
38369
  break;
38330
38370
  case "extract":
38331
38371
  params.name = args.name;
38332
- params.start_line = Number(args.startLine);
38333
- params.end_line = Number(args.endLine) + 1;
38372
+ if (startLine === undefined || endLine === undefined) {
38373
+ throw new Error("'startLine' and 'endLine' are required for 'extract' op");
38374
+ }
38375
+ params.start_line = startLine;
38376
+ params.end_line = endLine + 1;
38334
38377
  break;
38335
38378
  case "inline":
38336
38379
  params.symbol = args.symbol;
38337
- params.call_site_line = Number(args.callSiteLine);
38380
+ params.call_site_line = callSiteLine;
38338
38381
  break;
38339
38382
  }
38340
38383
  const hints = await queryLspHints(ctx.client, args.symbol ?? args.name, undefined, context.sessionID);
@@ -38497,7 +38540,7 @@ function safetyTools(ctx) {
38497
38540
  }
38498
38541
 
38499
38542
  // src/tools/search.ts
38500
- import * as fs7 from "node:fs";
38543
+ import * as fs6 from "node:fs";
38501
38544
  import * as path7 from "node:path";
38502
38545
  function arg2(schema) {
38503
38546
  return schema;
@@ -38533,7 +38576,7 @@ function absoluteSearchPath(projectRoot, target) {
38533
38576
  return resolvePathFromProjectRoot(projectRoot, expandTilde2(target));
38534
38577
  }
38535
38578
  function searchPathExists(projectRoot, target) {
38536
- return fs7.existsSync(absoluteSearchPath(projectRoot, target));
38579
+ return fs6.existsSync(absoluteSearchPath(projectRoot, target));
38537
38580
  }
38538
38581
  function splitSearchPathArg(projectRoot, raw) {
38539
38582
  if (searchPathExists(projectRoot, raw) || !/\s/.test(raw)) {
@@ -38576,7 +38619,7 @@ ${note}` : note;
38576
38619
  }
38577
38620
  function searchPathKind(projectRoot, target, defaultKind) {
38578
38621
  try {
38579
- const stat = fs7.lstatSync(absoluteSearchPath(projectRoot, target));
38622
+ const stat = fs6.lstatSync(absoluteSearchPath(projectRoot, target));
38580
38623
  if (defaultKind === "file") {
38581
38624
  return stat.isDirectory() ? "directory" : "file";
38582
38625
  }
@@ -38771,7 +38814,8 @@ function semanticTools(ctx) {
38771
38814
  args: {
38772
38815
  query: arg3(z15.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
38773
38816
  topK: arg3(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
38774
- hint: arg3(z15.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'."))
38817
+ hint: arg3(z15.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'.")),
38818
+ includeTests: arg3(z15.boolean().optional().describe("Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."))
38775
38819
  },
38776
38820
  execute: async (args, context) => {
38777
38821
  if (isEmptyParam(args.query) || typeof args.query !== "string" || args.query.trim().length === 0) {
@@ -38786,10 +38830,12 @@ function semanticTools(ctx) {
38786
38830
  }
38787
38831
  const bridgeParams = {
38788
38832
  query,
38789
- top_k: args.topK ?? 10
38833
+ top_k: coerceOptionalInt(args.topK, "topK", 1, 100) ?? 10
38790
38834
  };
38791
38835
  if (hint)
38792
38836
  bridgeParams.hint = hint;
38837
+ if (typeof args.includeTests === "boolean")
38838
+ bridgeParams.include_tests = args.includeTests;
38793
38839
  const response = await callBridge(ctx, context, "semantic_search", bridgeParams);
38794
38840
  if (response.success === false) {
38795
38841
  const message = typeof response.message === "string" && response.message.length > 0 ? response.message : "semantic_search failed";
@@ -38827,7 +38873,7 @@ function buildWorkflowHints(opts) {
38827
38873
  const hasBgBash = hasBash && opts.bashBackgroundEnabled && !opts.disabledTools.has(bashStatusName);
38828
38874
  if (hasBash && opts.bashCompressionEnabled) {
38829
38875
  sections.push([
38830
- "**Test/build output**: bash output is auto-compressed failures and the summary are always kept. DO NOT pipe test/build commands through filters to summarize; that hides failures:",
38876
+ "**Test/build output**: bash output is auto-compressed for non-piped commands. Piped commands run verbatim and show the pipeline's output. For AFT's test/build summary, run the runner without filters:",
38831
38877
  "- `bun test | grep fail` → run `bun test`",
38832
38878
  "- `cargo test 2>&1 | tail -20` → run `cargo test`",
38833
38879
  "- `npm run build | head -50` → run `npm run build`"
@@ -38866,11 +38912,9 @@ function buildWorkflowHints(opts) {
38866
38912
  }
38867
38913
  if (hasBash && hasBgBash) {
38868
38914
  sections.push([
38869
- `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`taskId\` immediately.`,
38870
- "1. Nothing else useful to do (a build/test/validation whose result is the next thing you need) sync `bash_watch` to block until it exits (pass a longer timeoutMs for long commands; the user can interrupt).",
38871
- "2. Useful parallel work available end your turn; the completion reminder delivers the result. (Or spawn a subagent for the side work.)",
38872
- "3. Want to react to a specific early output line → async `bash_watch` (background:true + pattern).",
38873
- "Never loop `bash_status` to wait — it's a one-shot inspector, not a wait primitive."
38915
+ `**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — \`${bashName}({ command })\` waits and returns the result in ONE step, and if it outlives the short wait window it auto-promotes to background and delivers a completion reminder when it finishes. Don't reach for \`background: true\` for the common "I'm waiting on this result" case — that costs an extra turn.`,
38916
+ "- `background: true` is ONLY for when you have OTHER useful work to do while it runs: start it, do the other work, and the completion reminder delivers the result (or spawn a subagent for the side work). Do NOT background a command and then immediately `bash_watch` it that spends a whole extra turn waiting for something foreground returns in one.",
38917
+ "- `bash_watch` is for blocking on an ALREADY-backgrounded task once you've run out of parallel work (sync the user can interrupt), or reacting to a specific early output line (async: background:true + pattern). Never loop `bash_status` to wait — it's a one-shot inspector."
38874
38918
  ].join(`
38875
38919
  `));
38876
38920
  sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`${bashStatusName}({ taskId, outputMode: "screen" })\`, and send input with \`${bashWriteName}({ taskId, input: "..." })\`.`);
@@ -38902,11 +38946,20 @@ function buildHintsFromConfig(config2, disabledTools) {
38902
38946
  setActiveLogger(bridgeLogger);
38903
38947
  var STATUS_COMMAND = "aft-status";
38904
38948
  var SENTINEL_PREFIX = "__AFT_STATUS_";
38905
- function isTuiMode2() {
38906
- return process.env.OPENCODE_CLIENT === "cli";
38907
- }
38949
+ var HTTP_SERVER_RESPONSE_TYPE_ID = "~effect/http/HttpServerResponse";
38950
+ var HTTP_COOKIES_TYPE_ID = "~effect/http/Cookies";
38951
+ var HTTP_BODY_TYPE_ID = "~effect/http/HttpBody";
38952
+ var ERROR_REPORTER_IGNORE = "~effect/ErrorReporter/ignore";
38908
38953
  function throwSentinel(command) {
38909
- throw new Error(`${SENTINEL_PREFIX}${command.toUpperCase().replace(/-/g, "_")}_HANDLED__`);
38954
+ const sentinel = new Error(`${SENTINEL_PREFIX}${command.toUpperCase().replace(/-/g, "_")}_HANDLED__`);
38955
+ sentinel[HTTP_SERVER_RESPONSE_TYPE_ID] = HTTP_SERVER_RESPONSE_TYPE_ID;
38956
+ sentinel[ERROR_REPORTER_IGNORE] = true;
38957
+ sentinel.status = 204;
38958
+ sentinel.statusText = undefined;
38959
+ sentinel.headers = {};
38960
+ sentinel.cookies = { [HTTP_COOKIES_TYPE_ID]: HTTP_COOKIES_TYPE_ID, cookies: {} };
38961
+ sentinel.body = { [HTTP_BODY_TYPE_ID]: HTTP_BODY_TYPE_ID, _tag: "Empty" };
38962
+ throw sentinel;
38910
38963
  }
38911
38964
  var PLUGIN_VERSION = (() => {
38912
38965
  try {
@@ -38916,30 +38969,35 @@ var PLUGIN_VERSION = (() => {
38916
38969
  return "0.0.0";
38917
38970
  }
38918
38971
  })();
38919
- var ANNOUNCEMENT_VERSION = "0.39.0";
38972
+ var ANNOUNCEMENT_VERSION = "0.40.0";
38920
38973
  var ANNOUNCEMENT_FEATURES = [
38921
- "Accurate Rust dead code: constructors and associated functions reached through receiver-type inference are no longer mis-reported as dead.",
38922
- "Code Health scans reparse only the file you changed instead of the whole project on every edit a single-file edit dropped from ~3s of scan work to ~130ms on this repo, byte-identical results.",
38923
- "R language support in outline, zoom, and the AST tools.",
38924
- "Edit approval prompts now show the pending diff instead of 'No diff provided', so you can review a change before approving it."
38974
+ "Groundwork for Subconscious, the CortexKit daemon: configuration now lives in one place under ~/.config/cortexkit/ and <project>/.cortexkit/ (auto-migrated). The daemon integration is dormant nothing changes in how you run AFT today.",
38975
+ "Bash output never hides a failure: piped commands run exactly as written with their real exit status, cancelled tasks report as failed, and a failing run is never compressed to a clean-looking summary.",
38976
+ "The top aft_search result is now the complete, ready-to-edit symbol (no re-read needed); test files are hidden from results by default.",
38977
+ "format_on_edit is now off by default, and bash commands default to the foreground."
38925
38978
  ];
38926
38979
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
38927
38980
  var plugin = async (input) => initializePluginForDirectory(input);
38928
38981
  async function initializePluginForDirectory(input) {
38929
38982
  const binaryPath = await findBinary(PLUGIN_VERSION);
38930
38983
  await ensureStorageMigrated({ harness: "opencode", binaryPath, logger: bridgeLogger });
38984
+ const deliverConfigMigrationWarnings = (directory, messages) => {
38985
+ for (const message of messages) {
38986
+ sendWarning({ client: input.client, directory }, message).catch((err) => {
38987
+ warn2(`[config] failed to deliver migration warning: ${err}`);
38988
+ });
38989
+ }
38990
+ };
38991
+ deliverConfigMigrationWarnings(input.directory, migrateAftConfigLocations(input.directory, bridgeLogger).flatMap((result) => result.warnings));
38931
38992
  const aftConfig = loadAftConfig(input.directory);
38932
38993
  enqueueConfigParseWarnings(input.directory, getConfigLoadErrors());
38933
38994
  const autoUpdateAbort = new AbortController;
38934
- const configOverrides = {
38935
- ...resolveProjectOverridesForConfigure(aftConfig),
38936
- bash_permissions: true
38937
- };
38938
- if (aftConfig.url_fetch_allow_private !== undefined) {
38939
- configOverrides.url_fetch_allow_private = aftConfig.url_fetch_allow_private;
38940
- }
38995
+ const storageDir = resolveCortexKitStorageRoot();
38996
+ const configOverrides = buildConfigTierConfigureParams(input.directory, {
38997
+ bash_permissions: true,
38998
+ storage_dir: storageDir
38999
+ });
38941
39000
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
38942
- configOverrides.storage_dir = resolveCortexKitStorageRoot();
38943
39001
  let onnxRuntimePromise = null;
38944
39002
  if (aftConfig.semantic_search && isFastembedSemanticBackend) {
38945
39003
  const storageDir2 = configOverrides.storage_dir;
@@ -39019,11 +39077,10 @@ ${lines}
39019
39077
  minVersion: PLUGIN_VERSION,
39020
39078
  projectConfigLoader: (projectRoot) => {
39021
39079
  try {
39022
- const projectConfig = loadAftConfig(projectRoot);
39023
- enqueueConfigParseWarnings(projectRoot, getConfigLoadErrors());
39024
- return resolveProjectOverridesForConfigure(projectConfig);
39080
+ deliverConfigMigrationWarnings(projectRoot, migrateAftConfigLocations(projectRoot, bridgeLogger).flatMap((result) => result.warnings));
39081
+ return buildConfigTierConfigureParams(projectRoot);
39025
39082
  } catch (err) {
39026
- warn2(`loadAftConfig(${projectRoot}) failed; falling back to plugin-init config: ${err instanceof Error ? err.message : String(err)}`);
39083
+ warn2(`readConfigTiers(${projectRoot}) failed; falling back to plugin-init config: ${err instanceof Error ? err.message : String(err)}`);
39027
39084
  return {};
39028
39085
  }
39029
39086
  },
@@ -39055,7 +39112,7 @@ ${lines}
39055
39112
  versionUpgradePromises.set(minVersion, upgradePromise);
39056
39113
  return upgradePromise;
39057
39114
  },
39058
- onConfigureWarnings: ({ projectRoot, sessionId, client, warnings }) => {
39115
+ onConfigureWarnings: ({ projectRoot, sessionId, client, warnings, configDroppedKeys }) => {
39059
39116
  const bridge = pool.getActiveBridgeForRoot(projectRoot);
39060
39117
  if (!bridge)
39061
39118
  return;
@@ -39066,6 +39123,7 @@ ${lines}
39066
39123
  client,
39067
39124
  bridge,
39068
39125
  warnings,
39126
+ configDroppedKeys,
39069
39127
  fallbackClient: input.client,
39070
39128
  storageDir: configOverrides.storage_dir,
39071
39129
  pluginVersion: PLUGIN_VERSION,
@@ -39175,7 +39233,12 @@ ${lines}
39175
39233
  }
39176
39234
  return { ...response, served_directory: servedDirectory };
39177
39235
  });
39178
- const storageDir = configOverrides.storage_dir;
39236
+ rpcServer.handle("consume-notifications", async (params) => {
39237
+ const rawLastReceivedId = Number(params.lastReceivedId ?? 0);
39238
+ const sessionId = typeof params.sessionId === "string" && params.sessionId.length > 0 ? params.sessionId : undefined;
39239
+ const messages = drainNotifications(Number.isFinite(rawLastReceivedId) ? rawLastReceivedId : 0, sessionId);
39240
+ return { messages };
39241
+ });
39179
39242
  rpcServer.handle("get-announcement", async () => {
39180
39243
  if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0) {
39181
39244
  return { show: false };
@@ -39374,9 +39437,13 @@ Install: ${getManualInstallHint()}`).catch(() => {});
39374
39437
  inspectTier2Idle.clear(toolInput.sessionID);
39375
39438
  },
39376
39439
  "command.execute.before": async (commandInput, _output) => {
39377
- if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
39440
+ if (commandInput.command !== STATUS_COMMAND) {
39378
39441
  return;
39379
39442
  }
39443
+ if (isTuiConnected(commandInput.sessionID)) {
39444
+ pushNotification("action", { action: "show-status-dialog", sessionId: commandInput.sessionID }, commandInput.sessionID);
39445
+ throwSentinel(commandInput.command);
39446
+ }
39380
39447
  const sessionDir = await getSessionDirectory(input.client, commandInput.sessionID, input.directory) ?? input.directory;
39381
39448
  const bridge = ctx.pool.getActiveBridgeForRoot(sessionDir) ?? ctx.pool.getBridge(sessionDir);
39382
39449
  const cached2 = bridge.getCachedStatus();