@cortexkit/aft-pi 0.63.5 → 0.65.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
@@ -7871,7 +7871,7 @@ var require_src2 = __commonJS(function(exports, module) {
7871
7871
  });
7872
7872
 
7873
7873
  // src/index.ts
7874
- import { createRequire as createRequire2 } from "node:module";
7874
+ import { createRequire as createRequire3 } from "node:module";
7875
7875
 
7876
7876
  // ../aft-bridge/dist/active-logger.js
7877
7877
  var ACTIVE_LOGGER_SYMBOL = Symbol.for("aft-bridge-active-logger");
@@ -8082,7 +8082,14 @@ var TERMINAL_BASH_STATUSES = new Set(TERMINAL_BG_TASK_STATUSES);
8082
8082
  function isTerminalBashStatus(status, compatibilityAliases = false) {
8083
8083
  return typeof status === "string" && (TERMINAL_BASH_STATUSES.has(status) || compatibilityAliases && (status === "cancelled" || status === "timeout"));
8084
8084
  }
8085
+
8085
8086
  // ../aft-bridge/dist/bash-format.js
8087
+ function formatUnknownPtyExit(taskId, data) {
8088
+ if (data.status !== "fate_unknown" || data.status_reason !== "AFT could not read how it ended") {
8089
+ return;
8090
+ }
8091
+ return `Task ${taskId}: exit status unknown (AFT could not read how it ended)`;
8092
+ }
8086
8093
  function isTerminalStatus(status) {
8087
8094
  return isTerminalBashStatus(status);
8088
8095
  }
@@ -8114,6 +8121,79 @@ function relativePathEscapesRoot(rel, platform = process.platform) {
8114
8121
  return rel === ".." || rel.startsWith("../") || platform === "win32" && rel.startsWith("..\\") || (platform === "win32" ? win32.isAbsolute(rel) : posix.isAbsolute(rel));
8115
8122
  }
8116
8123
 
8124
+ // ../aft-bridge/dist/wait-clock.js
8125
+ var SUSPEND_SKEW_MS = 1000;
8126
+ var RESUME_ALLOWANCE_MS = 1e4;
8127
+ var DEADLINE_POLL_MS = 250;
8128
+
8129
+ class WaitClock {
8130
+ wallNow;
8131
+ awakeNow;
8132
+ wall;
8133
+ awake;
8134
+ skew = 0;
8135
+ elapsed;
8136
+ suspendCount = 0;
8137
+ lastSuspendEnd = Number.NEGATIVE_INFINITY;
8138
+ constructor(wallNow = Date.now, awakeNow = () => performance.now()) {
8139
+ this.wallNow = wallNow;
8140
+ this.awakeNow = awakeNow;
8141
+ this.wall = wallNow();
8142
+ this.awake = awakeNow();
8143
+ this.elapsed = this.awake;
8144
+ }
8145
+ now() {
8146
+ const wall = this.wallNow();
8147
+ const awake = this.awakeNow();
8148
+ const active = Math.max(0, awake - this.awake);
8149
+ this.skew = Math.max(0, this.skew + wall - this.wall - active);
8150
+ this.elapsed += active;
8151
+ if (this.skew >= SUSPEND_SKEW_MS) {
8152
+ this.elapsed += this.skew;
8153
+ this.lastSuspendEnd = this.elapsed;
8154
+ this.skew = 0;
8155
+ this.suspendCount++;
8156
+ }
8157
+ this.wall = wall;
8158
+ this.awake = awake;
8159
+ return this.elapsed;
8160
+ }
8161
+ }
8162
+
8163
+ class RequestDeadline {
8164
+ clock;
8165
+ allowResume;
8166
+ deadline;
8167
+ suspendCount;
8168
+ resumed = false;
8169
+ constructor(clock, timeoutMs, allowResume = true) {
8170
+ this.clock = clock;
8171
+ this.allowResume = allowResume;
8172
+ this.deadline = clock.now() + timeoutMs;
8173
+ this.suspendCount = clock.suspendCount;
8174
+ }
8175
+ remaining() {
8176
+ const now = this.clock.now();
8177
+ if (now >= this.deadline && this.allowResume && !this.resumed && this.clock.suspendCount > this.suspendCount && this.clock.lastSuspendEnd >= this.deadline) {
8178
+ this.resumed = true;
8179
+ this.deadline = now + RESUME_ALLOWANCE_MS;
8180
+ }
8181
+ return Math.max(0, this.deadline - now);
8182
+ }
8183
+ }
8184
+ function deadlineTimer(scheduler, remaining, expired) {
8185
+ let timer;
8186
+ const check = () => {
8187
+ const left = remaining();
8188
+ if (left <= 0)
8189
+ expired();
8190
+ else
8191
+ timer = scheduler.setTimeout(check, Math.min(left, DEADLINE_POLL_MS));
8192
+ };
8193
+ check();
8194
+ return () => scheduler.clearTimeout(timer);
8195
+ }
8196
+
8117
8197
  // ../aft-bridge/dist/bash-hints.js
8118
8198
  var DEFAULT_PRIMARY_WATCH_TIMEOUT_MS = 30000;
8119
8199
  var MAX_WATCH_TIMEOUT_MS = 1800000;
@@ -8141,7 +8221,8 @@ function watchPollDelayMs(elapsedMs) {
8141
8221
  return 1000;
8142
8222
  }
8143
8223
  var watchClock = {
8144
- now: () => performance.now(),
8224
+ source: new WaitClock,
8225
+ now: () => watchClock.source.now(),
8145
8226
  sleep: abortableSleep
8146
8227
  };
8147
8228
  function abortableSleep(ms, signal) {
@@ -8287,6 +8368,7 @@ var GREP_SEARCH_AFT_SEARCH_HINT = "DO NOT search code by running grep/rg in bash
8287
8368
  var GREP_SEARCH_GREP_HINT = "DO NOT search code by running grep/rg in bash — it is unindexed, unranked, and serial. Use the `grep` tool instead (indexed and ranked).";
8288
8369
  var GREP_SEARCH_HINT_PREFIX = "DO NOT search code by running grep/rg in bash —";
8289
8370
  var GREP_SEARCH_FRESHNESS_WINDOW_MS = 60000;
8371
+ var MTIME_CLOCK_SLACK_MS = 1;
8290
8372
  function maybeAppendConflictsHint(output) {
8291
8373
  if (!output.includes("Automatic merge failed; fix conflicts"))
8292
8374
  return output;
@@ -8294,15 +8376,15 @@ function maybeAppendConflictsHint(output) {
8294
8376
  return output;
8295
8377
  return output + CONFLICT_HINT;
8296
8378
  }
8297
- function commandInvokesCodeSearch(command) {
8298
- const statements = splitTopLevelStatements(command);
8379
+ function commandInvokesCodeSearch(command, platform = process.platform) {
8380
+ const statements = splitTopLevelStatements(command, platform);
8299
8381
  if (statements === null)
8300
8382
  return false;
8301
8383
  for (const statement of statements) {
8302
- const firstStage = firstPipelineStage(statement);
8384
+ const firstStage = firstPipelineStage(statement, platform);
8303
8385
  if (firstStage === null)
8304
8386
  continue;
8305
- const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
8387
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0), platform);
8306
8388
  if (firstToken === null)
8307
8389
  continue;
8308
8390
  if (firstToken.token === "grep" || firstToken.token === "rg")
@@ -8310,42 +8392,43 @@ function commandInvokesCodeSearch(command) {
8310
8392
  }
8311
8393
  return false;
8312
8394
  }
8313
- function maybeAppendGrepSearchHint(output, command, aftSearchRegistered, projectRoot) {
8395
+ function maybeAppendGrepSearchHint(output, command, aftSearchRegistered, projectRoot, platform = process.platform, workdir) {
8314
8396
  if (output === "")
8315
8397
  return output;
8316
- if (!commandInvokesCodeSearch(command))
8398
+ if (!commandInvokesCodeSearch(command, platform))
8317
8399
  return output;
8318
8400
  if (output.includes(GREP_SEARCH_HINT_PREFIX))
8319
8401
  return output;
8320
- if (shouldSuppressGrepSearchHint(command, projectRoot))
8402
+ if (shouldSuppressGrepSearchHint(command, projectRoot, platform, workdir))
8321
8403
  return output;
8322
8404
  const hint = aftSearchRegistered ? GREP_SEARCH_AFT_SEARCH_HINT : GREP_SEARCH_GREP_HINT;
8323
8405
  return `${output}
8324
8406
 
8325
8407
  ${hint}`;
8326
8408
  }
8327
- function shouldSuppressGrepSearchHint(command, projectRoot) {
8328
- const statements = splitTopLevelStatements(command);
8409
+ function shouldSuppressGrepSearchHint(command, projectRoot, platform, workdir) {
8410
+ const statements = splitTopLevelStatements(command, platform);
8329
8411
  if (statements === null)
8330
8412
  return false;
8331
- if (allSearchPathOperandsAreDynamic(statements))
8413
+ if (allSearchPathOperandsAreDynamic(statements, platform))
8332
8414
  return true;
8333
8415
  const root = projectRoot?.trim();
8334
8416
  if (!root)
8335
8417
  return false;
8336
- const resolvedRoot = path.resolve(root);
8337
- let effectiveCwd = resolvedRoot;
8418
+ const paths = platform === "win32" ? path.win32 : path.posix;
8419
+ const resolvedRoot = paths.resolve(root);
8420
+ let effectiveCwd = paths.resolve(resolvedRoot, workdir ?? ".");
8338
8421
  let sawCodeSearchStatement = false;
8339
8422
  for (const statement of statements) {
8340
- const firstStage = firstPipelineStage(statement);
8423
+ const firstStage = firstPipelineStage(statement, platform);
8341
8424
  if (firstStage === null)
8342
8425
  continue;
8343
- const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
8426
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0), platform);
8344
8427
  if (firstToken === null)
8345
8428
  continue;
8346
8429
  const head = firstToken.token;
8347
8430
  if (head === "cd") {
8348
- effectiveCwd = nextCwdAfterCd(effectiveCwd, firstStage, firstToken.end);
8431
+ effectiveCwd = nextCwdAfterCd(effectiveCwd, firstStage, firstToken.end, platform);
8349
8432
  continue;
8350
8433
  }
8351
8434
  if (head !== "grep" && head !== "rg")
@@ -8353,20 +8436,23 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
8353
8436
  sawCodeSearchStatement = true;
8354
8437
  if (effectiveCwd === null)
8355
8438
  continue;
8356
- if (!isDirInsideProject(resolvedRoot, effectiveCwd))
8439
+ if (!isDirInsideProject(resolvedRoot, effectiveCwd, platform))
8357
8440
  continue;
8358
- const operands = collectPathOperands(firstStage, firstToken.end);
8441
+ const operands = collectPathOperands(firstStage, firstToken.end, platform);
8359
8442
  if (operands.length === 0)
8360
8443
  return false;
8361
8444
  let sawInProjectOperand = false;
8362
8445
  for (const operand of operands) {
8363
8446
  if (isDynamicPathOperand(operand))
8364
8447
  continue;
8365
- const resolvedOperand = resolvePathOperand(effectiveCwd, operand);
8366
- if (!isPathInsideProject(resolvedRoot, effectiveCwd, operand))
8448
+ const resolvedOperand = resolvePathOperand(effectiveCwd, operand, platform);
8449
+ if (!isPathInsideProject(resolvedRoot, effectiveCwd, operand, platform))
8450
+ continue;
8451
+ const suppress = shouldSuppressResolvedPath(resolvedOperand);
8452
+ if (suppress === undefined)
8367
8453
  continue;
8368
8454
  sawInProjectOperand = true;
8369
- if (shouldSuppressResolvedPath(resolvedOperand))
8455
+ if (suppress)
8370
8456
  return true;
8371
8457
  }
8372
8458
  if (sawInProjectOperand)
@@ -8374,19 +8460,19 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
8374
8460
  }
8375
8461
  return sawCodeSearchStatement;
8376
8462
  }
8377
- function allSearchPathOperandsAreDynamic(statements) {
8463
+ function allSearchPathOperandsAreDynamic(statements, platform) {
8378
8464
  let sawDynamicOnlySearch = false;
8379
8465
  for (const statement of statements) {
8380
- const firstStage = firstPipelineStage(statement);
8466
+ const firstStage = firstPipelineStage(statement, platform);
8381
8467
  if (firstStage === null)
8382
8468
  continue;
8383
- const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
8469
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0), platform);
8384
8470
  if (firstToken === null)
8385
8471
  continue;
8386
8472
  const head = firstToken.token;
8387
8473
  if (head !== "grep" && head !== "rg")
8388
8474
  continue;
8389
- const operands = collectPathOperands(firstStage, firstToken.end);
8475
+ const operands = collectPathOperands(firstStage, firstToken.end, platform);
8390
8476
  if (operands.length === 0)
8391
8477
  return false;
8392
8478
  if (operands.some((operand) => !isDynamicPathOperand(operand)))
@@ -8395,11 +8481,12 @@ function allSearchPathOperandsAreDynamic(statements) {
8395
8481
  }
8396
8482
  return sawDynamicOnlySearch;
8397
8483
  }
8398
- function nextCwdAfterCd(currentCwd, firstStage, afterCd) {
8484
+ function nextCwdAfterCd(currentCwd, firstStage, afterCd, platform) {
8485
+ const paths = platform === "win32" ? path.win32 : path.posix;
8399
8486
  let index = skipSpaces(firstStage, afterCd);
8400
8487
  let target = null;
8401
8488
  while (index < firstStage.length) {
8402
- const tokenResult = readShellToken(firstStage, index);
8489
+ const tokenResult = readShellToken(firstStage, index, platform);
8403
8490
  if (tokenResult === null)
8404
8491
  break;
8405
8492
  const { token, end } = tokenResult;
@@ -8414,28 +8501,29 @@ function nextCwdAfterCd(currentCwd, firstStage, afterCd) {
8414
8501
  break;
8415
8502
  }
8416
8503
  if (target === null)
8417
- return path.resolve(os.homedir());
8504
+ return paths.resolve(os.homedir());
8418
8505
  if (target.includes("$") || target.includes("`"))
8419
8506
  return null;
8420
- const expanded = expandTilde(target);
8421
- if (path.isAbsolute(expanded))
8422
- return path.resolve(expanded);
8507
+ const expanded = expandTilde(target, platform);
8508
+ if (paths.isAbsolute(expanded))
8509
+ return paths.resolve(expanded);
8423
8510
  if (currentCwd === null)
8424
8511
  return null;
8425
- return path.resolve(currentCwd, expanded);
8512
+ return paths.resolve(currentCwd, expanded);
8426
8513
  }
8427
- function isDirInsideProject(resolvedRoot, dir) {
8428
- const rel = path.relative(resolvedRoot, path.resolve(dir));
8429
- return !relativePathEscapesRoot(rel);
8514
+ function isDirInsideProject(resolvedRoot, dir, platform) {
8515
+ const paths = platform === "win32" ? path.win32 : path.posix;
8516
+ const rel = paths.relative(resolvedRoot, paths.resolve(dir));
8517
+ return !relativePathEscapesRoot(rel, platform);
8430
8518
  }
8431
- function collectPathOperands(firstStage, startAfterCommand) {
8519
+ function collectPathOperands(firstStage, startAfterCommand, platform) {
8432
8520
  const operands = [];
8433
8521
  let index = skipSpaces(firstStage, startAfterCommand);
8434
8522
  let sawPattern = false;
8435
8523
  let optionsEnded = false;
8436
8524
  let optionValue;
8437
8525
  while (index < firstStage.length) {
8438
- const tokenResult = readShellToken(firstStage, index);
8526
+ const tokenResult = readShellToken(firstStage, index, platform);
8439
8527
  if (tokenResult === null)
8440
8528
  break;
8441
8529
  const { token, end } = tokenResult;
@@ -8494,22 +8582,25 @@ function collectPathOperands(firstStage, startAfterCommand) {
8494
8582
  function isDynamicPathOperand(token) {
8495
8583
  return token.startsWith("~") || token.includes("$") || token.includes("`");
8496
8584
  }
8497
- function expandTilde(target) {
8585
+ function expandTilde(target, platform) {
8498
8586
  if (!target.startsWith("~"))
8499
8587
  return target;
8500
8588
  if (target === "~" || target.startsWith("~/")) {
8501
- return path.join(os.homedir(), target.slice(1));
8589
+ const paths = platform === "win32" ? path.win32 : path.posix;
8590
+ return paths.join(os.homedir(), target.slice(1));
8502
8591
  }
8503
8592
  return target;
8504
8593
  }
8505
- function resolvePathOperand(projectRoot, operand) {
8506
- const expanded = expandTilde(operand);
8507
- return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(projectRoot, expanded);
8594
+ function resolvePathOperand(projectRoot, operand, platform) {
8595
+ const paths = platform === "win32" ? path.win32 : path.posix;
8596
+ const expanded = expandTilde(operand, platform);
8597
+ return paths.isAbsolute(expanded) ? paths.resolve(expanded) : paths.resolve(projectRoot, expanded);
8508
8598
  }
8509
- function isPathInsideProject(resolvedRoot, baseCwd, operand) {
8510
- const resolved = resolvePathOperand(baseCwd, operand);
8511
- const rel = path.relative(resolvedRoot, resolved);
8512
- return !relativePathEscapesRoot(rel);
8599
+ function isPathInsideProject(resolvedRoot, baseCwd, operand, platform) {
8600
+ const paths = platform === "win32" ? path.win32 : path.posix;
8601
+ const resolved = resolvePathOperand(baseCwd, operand, platform);
8602
+ const rel = paths.relative(resolvedRoot, resolved);
8603
+ return !relativePathEscapesRoot(rel, platform);
8513
8604
  }
8514
8605
  function shouldSuppressResolvedPath(resolved) {
8515
8606
  try {
@@ -8517,12 +8608,12 @@ function shouldSuppressResolvedPath(resolved) {
8517
8608
  if (stats.isFile())
8518
8609
  return true;
8519
8610
  const ageMs = Date.now() - stats.mtimeMs;
8520
- return ageMs >= 0 && ageMs < GREP_SEARCH_FRESHNESS_WINDOW_MS;
8611
+ return ageMs > -MTIME_CLOCK_SLACK_MS && ageMs < GREP_SEARCH_FRESHNESS_WINDOW_MS;
8521
8612
  } catch {
8522
- return false;
8613
+ return;
8523
8614
  }
8524
8615
  }
8525
- function splitTopLevelStatements(command) {
8616
+ function splitTopLevelStatements(command, platform = process.platform) {
8526
8617
  const statements = [];
8527
8618
  let start = 0;
8528
8619
  let quote = "none";
@@ -8541,7 +8632,7 @@ function splitTopLevelStatements(command) {
8541
8632
  continue;
8542
8633
  }
8543
8634
  if (quote === "double") {
8544
- if (ch === "\\")
8635
+ if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform))
8545
8636
  escaped = true;
8546
8637
  else if (ch === '"')
8547
8638
  quote = "none";
@@ -8552,7 +8643,7 @@ function splitTopLevelStatements(command) {
8552
8643
  inBacktick = false;
8553
8644
  continue;
8554
8645
  }
8555
- if (ch === "\\") {
8646
+ if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform)) {
8556
8647
  escaped = true;
8557
8648
  continue;
8558
8649
  }
@@ -8595,7 +8686,7 @@ function splitTopLevelStatements(command) {
8595
8686
  statements.push(command.slice(start));
8596
8687
  return statements;
8597
8688
  }
8598
- function firstPipelineStage(command) {
8689
+ function firstPipelineStage(command, platform = process.platform) {
8599
8690
  let quote = "none";
8600
8691
  let firstPipeIndex;
8601
8692
  for (let index = 0;index < command.length; index++) {
@@ -8608,7 +8699,7 @@ function firstPipelineStage(command) {
8608
8699
  if (quote === "double") {
8609
8700
  if (ch === '"') {
8610
8701
  quote = "none";
8611
- } else if (ch === "\\") {
8702
+ } else if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform)) {
8612
8703
  index++;
8613
8704
  } else if (ch === "`") {
8614
8705
  return null;
@@ -8619,7 +8710,7 @@ function firstPipelineStage(command) {
8619
8710
  quote = "single";
8620
8711
  } else if (ch === '"') {
8621
8712
  quote = "double";
8622
- } else if (ch === "\\") {
8713
+ } else if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform)) {
8623
8714
  index++;
8624
8715
  } else if (ch === "`") {
8625
8716
  return null;
@@ -8635,7 +8726,7 @@ function firstPipelineStage(command) {
8635
8726
  return null;
8636
8727
  return command.slice(0, firstPipeIndex ?? command.length).trim();
8637
8728
  }
8638
- function readShellToken(command, start) {
8729
+ function readShellToken(command, start, platform = process.platform) {
8639
8730
  let quote = "none";
8640
8731
  let token = "";
8641
8732
  let index = start;
@@ -8652,9 +8743,11 @@ function readShellToken(command, start) {
8652
8743
  if (quote === "double") {
8653
8744
  if (ch === '"') {
8654
8745
  quote = "none";
8655
- } else if (ch === "\\") {
8746
+ } else if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform)) {
8656
8747
  index++;
8657
- token += command[index] ?? "\\";
8748
+ if (command[index] !== `
8749
+ `)
8750
+ token += command[index];
8658
8751
  } else if (ch === "`") {
8659
8752
  return null;
8660
8753
  } else {
@@ -8670,7 +8763,7 @@ function readShellToken(command, start) {
8670
8763
  quote = "single";
8671
8764
  } else if (ch === '"') {
8672
8765
  quote = "double";
8673
- } else if (ch === "\\") {
8766
+ } else if (ch === "\\" && backslashEscapesNext(command[index + 1], quote, platform)) {
8674
8767
  index++;
8675
8768
  token += command[index] ?? "\\";
8676
8769
  } else if (ch === "`") {
@@ -8683,6 +8776,11 @@ function readShellToken(command, start) {
8683
8776
  return null;
8684
8777
  return { token, end: index };
8685
8778
  }
8779
+ function backslashEscapesNext(next, quote, platform) {
8780
+ if (quote === "double")
8781
+ return next !== undefined && '$`"\\\n'.includes(next);
8782
+ return platform !== "win32";
8783
+ }
8686
8784
  function isTokenBoundary(ch) {
8687
8785
  return ch === "|" || ch === ";" || ch === "&" || ch === "<" || ch === ">";
8688
8786
  }
@@ -8694,7 +8792,7 @@ function skipSpaces(input, start) {
8694
8792
  }
8695
8793
  // ../aft-bridge/dist/bash-host-fallback.js
8696
8794
  import { existsSync } from "node:fs";
8697
- import { join as join4 } from "node:path";
8795
+ import { join as join3 } from "node:path";
8698
8796
 
8699
8797
  // ../aft-bridge/dist/child-process.js
8700
8798
  import { execFile as nodeExecFile, execFileSync as nodeExecFileSync, execSync as nodeExecSync, spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
@@ -8702,7 +8800,7 @@ import { execFile as nodeExecFile, execFileSync as nodeExecFileSync, execSync as
8702
8800
  // ../aft-bridge/dist/test-child-environment.js
8703
8801
  import { mkdirSync, realpathSync } from "node:fs";
8704
8802
  import { homedir as homedir3, tmpdir } from "node:os";
8705
- import { dirname, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
8803
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
8706
8804
  var DIRECTORIES = [
8707
8805
  "HOME",
8708
8806
  "USERPROFILE",
@@ -8722,8 +8820,8 @@ function toolchainHomes(inherited) {
8722
8820
  if (!global[TOOLCHAINS]) {
8723
8821
  const home = process.platform === "win32" ? inherited.USERPROFILE || inherited.HOME || homedir3() : inherited.HOME || homedir3();
8724
8822
  global[TOOLCHAINS] = {
8725
- RUSTUP_HOME: inherited.RUSTUP_HOME || join2(home, ".rustup"),
8726
- CARGO_HOME: inherited.CARGO_HOME || join2(home, ".cargo")
8823
+ RUSTUP_HOME: inherited.RUSTUP_HOME || join(home, ".rustup"),
8824
+ CARGO_HOME: inherited.CARGO_HOME || join(home, ".cargo")
8727
8825
  };
8728
8826
  }
8729
8827
  return global[TOOLCHAINS];
@@ -8750,31 +8848,31 @@ function isolatedAftEnvironment(root, inherited = process.env) {
8750
8848
  "aft-cache"
8751
8849
  ];
8752
8850
  for (const [index, key] of DIRECTORIES.entries()) {
8753
- env[key] = join2(root, names[index]);
8851
+ env[key] = join(root, names[index]);
8754
8852
  mkdirSync(env[key], { recursive: true });
8755
8853
  }
8756
8854
  delete env.AFT_ALLOW_PRODUCTION_MIGRATION;
8757
- createdRoots().add(resolve2(root));
8855
+ createdRoots().add(resolve(root));
8758
8856
  return env;
8759
8857
  }
8760
8858
  function canonical(path) {
8761
- let ancestor = resolve2(path);
8859
+ let ancestor = resolve(path);
8762
8860
  const tail = [];
8763
8861
  while (true) {
8764
8862
  try {
8765
- return join2(realpathSync(ancestor), ...tail);
8863
+ return join(realpathSync(ancestor), ...tail);
8766
8864
  } catch {
8767
8865
  const parent = dirname(ancestor);
8768
8866
  if (parent === ancestor)
8769
- return resolve2(path);
8770
- tail.unshift(relative2(parent, ancestor));
8867
+ return resolve(path);
8868
+ tail.unshift(relative(parent, ancestor));
8771
8869
  ancestor = parent;
8772
8870
  }
8773
8871
  }
8774
8872
  }
8775
8873
  function within(path, root) {
8776
- const tail = relative2(canonical(root), canonical(path));
8777
- return tail === "" || !tail.startsWith("..") && !isAbsolute2(tail);
8874
+ const tail = relative(canonical(root), canonical(path));
8875
+ return tail === "" || !tail.startsWith("..") && !isAbsolute(tail);
8778
8876
  }
8779
8877
  function testChildEnvironment(env) {
8780
8878
  const root = process.env.AFT_TEST_ISOLATION_ROOT;
@@ -8850,7 +8948,7 @@ function withPathPrepended(env, dir, platform = process.platform) {
8850
8948
 
8851
8949
  // ../aft-bridge/dist/storage-paths.js
8852
8950
  import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
8853
- import { join as join3, resolve as resolve3 } from "node:path";
8951
+ import { join as join2, resolve as resolve2 } from "node:path";
8854
8952
  function environmentValue(context, name) {
8855
8953
  const value = (context.lookup ?? ((key) => process.env[key]))(name);
8856
8954
  return value !== undefined && value.length > 0 ? value : undefined;
@@ -8872,21 +8970,21 @@ function resolveDataHome(context = {}) {
8872
8970
  return localAppData;
8873
8971
  const userProfile = environmentValue(context, "USERPROFILE");
8874
8972
  if (userProfile)
8875
- return join3(userProfile, "AppData", "Local");
8973
+ return join2(userProfile, "AppData", "Local");
8876
8974
  }
8877
8975
  const home = environmentValue(context, "HOME");
8878
8976
  if (home)
8879
- return join3(home, ".local", "share");
8880
- return join3(".local", "share");
8977
+ return join2(home, ".local", "share");
8978
+ return join2(".local", "share");
8881
8979
  }
8882
8980
  function resolveStoragePath(raw, context = {}) {
8883
8981
  let expanded = raw;
8884
8982
  if (raw === "~") {
8885
8983
  expanded = homeDir(context);
8886
8984
  } else if (raw.startsWith("~/") || raw.startsWith("~\\")) {
8887
- expanded = join3(homeDir(context), raw.slice(2));
8985
+ expanded = join2(homeDir(context), raw.slice(2));
8888
8986
  }
8889
- return resolve3(context.currentDirectory ?? process.cwd(), expanded);
8987
+ return resolve2(context.currentDirectory ?? process.cwd(), expanded);
8890
8988
  }
8891
8989
  function resolveCortexKitStorageRoot(context = {}) {
8892
8990
  const override = environmentValue(context, "AFT_STORAGE_DIR");
@@ -8894,8 +8992,8 @@ function resolveCortexKitStorageRoot(context = {}) {
8894
8992
  return resolveStoragePath(override, context);
8895
8993
  const legacyCache = environmentValue(context, "AFT_CACHE_DIR");
8896
8994
  if (legacyCache)
8897
- return join3(resolveStoragePath(legacyCache, context), "aft");
8898
- return resolveStoragePath(join3(resolveDataHome(context), "cortexkit", "aft"), context);
8995
+ return join2(resolveStoragePath(legacyCache, context), "aft");
8996
+ return resolveStoragePath(join2(resolveDataHome(context), "cortexkit", "aft"), context);
8899
8997
  }
8900
8998
  function resolveAftStorageRoot(configuredRoot, context = {}) {
8901
8999
  if (environmentValue(context, "AFT_STORAGE_DIR")) {
@@ -8906,14 +9004,14 @@ function resolveAftStorageRoot(configuredRoot, context = {}) {
8906
9004
  return resolveCortexKitStorageRoot(context);
8907
9005
  }
8908
9006
  function resolveAftLogPath(filename, configuredRoot, context = {}) {
8909
- return join3(resolveAftStorageRoot(configuredRoot, context), "logs", filename);
9007
+ return join2(resolveAftStorageRoot(configuredRoot, context), "logs", filename);
8910
9008
  }
8911
9009
  function isTestEnvironment(context = {}) {
8912
9010
  return environmentValue(context, "BUN_TEST") === "1" || environmentValue(context, "NODE_ENV") === "test";
8913
9011
  }
8914
9012
  function resolvePluginLogPath(context = {}) {
8915
9013
  if (isTestEnvironment(context)) {
8916
- return join3(environmentValue(context, "TMPDIR") ?? tmpdir2(), `aft-plugin-tests-${process.pid}`, "logs", "aft-plugin-test.log");
9014
+ return join2(environmentValue(context, "TMPDIR") ?? tmpdir2(), `aft-plugin-tests-${process.pid}`, "logs", "aft-plugin-test.log");
8917
9015
  }
8918
9016
  return resolveAftLogPath("aft-plugin.log", undefined, context);
8919
9017
  }
@@ -8923,9 +9021,10 @@ var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no
8923
9021
  var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
8924
9022
  var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
8925
9023
  var BASH_HOST_FALLBACK_REFUSAL = "AFT module connection is down";
8926
- var BASH_RUNON_DESCRIPTION = 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options).';
8927
- var BASH_RETURN_PATHS_DESCRIPTION = "With runon: up to 64 files to copy back from the build server after the command, as paths relative to workdir inside the project (10 MiB in total). Each verified file replaces the local file at the same path; the result lists every path as returned or not returned, with the reason.";
8928
- var BASH_RUNON_GUIDANCE = 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.';
9024
+ var BASH_RUNON_MEMORY_DESCRIPTION = "On Linux, add `,Ngb` to ask for N GiB of memory, for example `linux,2c,16gb` for a Rust build.";
9025
+ var BASH_RUNON_DESCRIPTION = 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options).' + ` ${BASH_RUNON_MEMORY_DESCRIPTION}`;
9026
+ var BASH_RETURN_PATHS_DESCRIPTION = "With runon: up to 64 files to copy back from the build server after the command, as paths relative to workdir inside the project (up to 128 MiB in total). Each verified file replaces the local file at the same path; the result lists every path as returned or not returned, with the reason.";
9027
+ var BASH_RUNON_GUIDANCE = 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. A runon job\'s file changes are not copied back; only output, the exit code and return_paths files return. Edit locally first; name a file in return_paths to regenerate it remotely. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.';
8929
9028
  function pathKeyForPlatform(env, platform) {
8930
9029
  return platform === "win32" ? Object.keys(env).find((key) => key.toLowerCase() === "path") : "PATH";
8931
9030
  }
@@ -8947,8 +9046,8 @@ function hostFallbackEnvWithShims(env, platform) {
8947
9046
  const pathKey = pathKeyForPlatform(normalized, platform);
8948
9047
  const inherited = pathKey === undefined ? undefined : normalized[pathKey];
8949
9048
  const storageRoot = normalized.AFT_STORAGE_DIR ? resolveStoragePath(normalized.AFT_STORAGE_DIR) : resolveCortexKitStorageRoot();
8950
- const shimsDir = join4(storageRoot, "shims");
8951
- if (!existsSync(join4(shimsDir, platform === "win32" ? "gh.cmd" : "gh")))
9049
+ const shimsDir = join3(storageRoot, "shims");
9050
+ if (!existsSync(join3(shimsDir, platform === "win32" ? "gh.cmd" : "gh")))
8952
9051
  return normalized;
8953
9052
  const separator = platform === "win32" ? ";" : ":";
8954
9053
  const entries = (inherited ?? "").split(separator).filter((entry) => entry.length > 0);
@@ -9180,6 +9279,7 @@ function bashAcksConfirmed(response, targets) {
9180
9279
  }
9181
9280
  // ../aft-bridge/dist/bash-permissions.js
9182
9281
  var BASH_PERMISSION_NOT_SETTLED = "bash permission not settled after {rounds} rounds: still asking for {kind} {path} (operation: {operation}; granted already: {granted})";
9282
+ var BASH_PERMISSION_DENIED = "Permission denied for this shell command: {parts} ({host reason}). The parts are checked together, so one denied part denies the whole command; run the allowed parts in a separate call.";
9183
9283
 
9184
9284
  class BashPermissionLoopError extends Error {
9185
9285
  }
@@ -9197,6 +9297,10 @@ ${ask.command ?? ""}
9197
9297
  Working directory:
9198
9298
  ${ask.cwd ?? ""}`;
9199
9299
  }
9300
+ function permissionAskPath(ask) {
9301
+ const paths = ask.patterns?.length ? ask.patterns : bashPermissionRetryGrants(ask);
9302
+ return ask.kind === "escalation" && ask.command ? `${ask.command}${ask.cwd ? ` @ ${ask.cwd}` : ""}` : paths.length ? paths.join(", ") : "<no asks returned>";
9303
+ }
9200
9304
  async function withBashPermissionLoop(options) {
9201
9305
  const granted = new Set;
9202
9306
  const alreadyGranted = (ask) => {
@@ -9218,20 +9322,22 @@ async function withBashPermissionLoop(options) {
9218
9322
  already_granted: alreadyGranted(ask)
9219
9323
  }))
9220
9324
  });
9221
- const unsettled = (cause) => {
9325
+ const unsettled = () => {
9222
9326
  const message = (asks.length ? asks : [{}]).map((ask) => {
9223
- const paths = ask.patterns?.length ? ask.patterns : bashPermissionRetryGrants(ask);
9224
- const path = ask.kind === "escalation" && ask.command ? `${ask.command}${ask.cwd ? ` @ ${ask.cwd}` : ""}` : paths.length ? paths.join(", ") : "<no asks returned>";
9225
- return BASH_PERMISSION_NOT_SETTLED.replace("{rounds}", String(round)).replace("{kind}", ask.kind ?? "unknown").replace("{operation}", ask.operation ?? options.operation ?? "bash").replace("{granted}", alreadyGranted(ask) ? "yes" : "no").replace("{path}", () => path);
9327
+ return BASH_PERMISSION_NOT_SETTLED.replace("{rounds}", String(round)).replace("{kind}", ask.kind ?? "unknown").replace("{operation}", ask.operation ?? options.operation ?? "bash").replace("{granted}", alreadyGranted(ask) ? "yes" : "no").replace("{path}", () => permissionAskPath(ask));
9226
9328
  }).join("; ");
9227
- return new BashPermissionLoopError(cause instanceof Error ? `${message}: ${cause.message}` : message, { cause });
9329
+ return new BashPermissionLoopError(message);
9228
9330
  };
9229
9331
  if (asks.length === 0 || asks.every(alreadyGranted) || round >= 8)
9230
9332
  throw unsettled();
9333
+ const pendingAsks = asks.filter((ask) => !alreadyGranted(ask));
9231
9334
  try {
9232
- await options.approve(asks.filter((ask) => !alreadyGranted(ask)));
9335
+ await options.approve(pendingAsks);
9233
9336
  } catch (error) {
9234
- throw unsettled(error);
9337
+ const parts = pendingAsks.map(permissionAskPath).join(", ");
9338
+ const reason = error instanceof Error && error.message ? error.message.trim().replace(/\.$/, "") : "";
9339
+ const message = reason ? BASH_PERMISSION_DENIED.replace("{host reason}", () => reason) : BASH_PERMISSION_DENIED.replace(" ({host reason})", "");
9340
+ throw new BashPermissionLoopError(message.replace("{parts}", () => parts), { cause: error });
9235
9341
  }
9236
9342
  for (const ask of asks) {
9237
9343
  for (const grant of bashPermissionRetryGrants(ask))
@@ -9700,7 +9806,7 @@ async function cachedFileSha256(path) {
9700
9806
  }
9701
9807
  // ../aft-bridge/dist/bridge.js
9702
9808
  import { homedir as homedir5 } from "node:os";
9703
- import { join as join5 } from "node:path";
9809
+ import { join as join4 } from "node:path";
9704
9810
  import { StringDecoder } from "node:string_decoder";
9705
9811
 
9706
9812
  // ../aft-bridge/dist/command-timeouts.js
@@ -9838,6 +9944,7 @@ function compareSemver(a, b) {
9838
9944
  }
9839
9945
  var SYSTEM_CLOCK = {
9840
9946
  now: () => Date.now(),
9947
+ awakeNow: () => performance.now(),
9841
9948
  setTimeout: (callback, ms) => setTimeout(callback, ms),
9842
9949
  clearTimeout: (timer) => clearTimeout(timer)
9843
9950
  };
@@ -9954,6 +10061,7 @@ class BinaryBridge {
9954
10061
  hangWindowMinMs;
9955
10062
  hangProbeTimeoutMs;
9956
10063
  clock;
10064
+ waitClock;
9957
10065
  lateShellReplyGraceMs;
9958
10066
  maxRestarts;
9959
10067
  configured = false;
@@ -9990,6 +10098,7 @@ class BinaryBridge {
9990
10098
  this.hangWindowMinMs = options?.hangWindowMinMs ?? DEFAULT_MIN_HANG_WINDOW_MS;
9991
10099
  this.hangProbeTimeoutMs = options?.hangProbeTimeoutMs ?? DEFAULT_HANG_PROBE_TIMEOUT_MS;
9992
10100
  this.clock = options?.clock ?? SYSTEM_CLOCK;
10101
+ this.waitClock = new WaitClock(() => this.clock.now(), () => this.clock.awakeNow?.() ?? this.clock.now());
9993
10102
  this.lateShellReplyGraceMs = options?.lateShellReplyGraceMs ?? DEFAULT_LATE_SHELL_REPLY_GRACE_MS;
9994
10103
  this.maxRestarts = options?.maxRestarts ?? 3;
9995
10104
  this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
@@ -10356,11 +10465,17 @@ class BinaryBridge {
10356
10465
  const keepBridgeOnTimeout = passive || command === "bash_drain_completions" || options?.keepBridgeOnTimeout === true;
10357
10466
  const child = this.process;
10358
10467
  const response = await new Promise((resolve, reject) => {
10359
- const timer = this.clock.setTimeout(() => {
10468
+ const deadline = new RequestDeadline(this.waitClock, effectiveTimeoutMs);
10469
+ const expire = () => {
10360
10470
  const entry = this.pending.get(id);
10361
10471
  if (!entry)
10362
10472
  return;
10363
- if (keepBridgeOnTimeout && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
10473
+ const remaining = deadline.remaining();
10474
+ if (remaining > 0) {
10475
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
10476
+ return;
10477
+ }
10478
+ if (keepBridgeOnTimeout && !deadline.resumed && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
10364
10479
  return;
10365
10480
  }
10366
10481
  this.pending.delete(id);
@@ -10373,11 +10488,12 @@ class BinaryBridge {
10373
10488
  this.warnVia(timeoutMsg);
10374
10489
  }
10375
10490
  if (keepBridgeOnTimeout) {
10376
- entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
10491
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`, deadline.resumed && SHELL_TASK_COMMANDS.has(command) ? { kind: "unknown" } : undefined));
10377
10492
  return;
10378
10493
  }
10379
10494
  entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry. A timed-out mutation may still complete; inspect before repeating it.`));
10380
- }, effectiveTimeoutMs);
10495
+ };
10496
+ const timer = this.clock.setTimeout(expire, Math.min(effectiveTimeoutMs, DEADLINE_POLL_MS));
10381
10497
  const entry = {
10382
10498
  resolve,
10383
10499
  reject,
@@ -10608,11 +10724,11 @@ class BinaryBridge {
10608
10724
  })();
10609
10725
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
10610
10726
  const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
10611
- const ortLibraryPath = ortDir == null ? null : join5(ortDir, this.platform === "win32" ? "onnxruntime.dll" : this.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
10727
+ const ortLibraryPath = ortDir == null ? null : join4(ortDir, this.platform === "win32" ? "onnxruntime.dll" : this.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
10612
10728
  const env = { ...process.env };
10613
10729
  this.logVia(`bridge.spawnProcess: useFastembedBackend=${useFastembedBackend}, ` + `parentORT=${process.env.ORT_DYLIB_PATH ?? "(unset)"}, ` + `ortLibraryPath=${ortLibraryPath ?? "(none)"}`);
10614
10730
  if (useFastembedBackend) {
10615
- env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join5(this.configOverrides.storage_dir, "semantic", "models") : join5(homedir5() || "", ".cache", "fastembed"));
10731
+ env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join4(this.configOverrides.storage_dir, "semantic", "models") : join4(homedir5() || "", ".cache", "fastembed"));
10616
10732
  if (process.env.ORT_DYLIB_PATH) {
10617
10733
  this.logVia(`ORT_DYLIB_PATH inherited from parent env: ${process.env.ORT_DYLIB_PATH}`);
10618
10734
  } else if (ortLibraryPath) {
@@ -10933,14 +11049,21 @@ class BinaryBridge {
10933
11049
  };
10934
11050
  entry.resolve = (response) => settle(shellOutcomeFromLateReply(response));
10935
11051
  this.clock.clearTimeout(entry.timer);
10936
- entry.timer = this.clock.setTimeout(() => {
11052
+ const deadline = new RequestDeadline(this.waitClock, graceMs, false);
11053
+ const expire = () => {
10937
11054
  if (this.pending.get(id) !== entry)
10938
11055
  return;
11056
+ const remaining = deadline.remaining();
11057
+ if (remaining > 0) {
11058
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
11059
+ return;
11060
+ }
10939
11061
  this.pending.delete(id);
10940
11062
  entry.onSettled?.();
10941
11063
  this.warnVia(`Request "${entry.command}" (id=${id}) got no reply within ${graceMs}ms after its deadline`);
10942
11064
  settle({ kind: "unknown" });
10943
- }, graceMs);
11065
+ };
11066
+ entry.timer = this.clock.setTimeout(expire, Math.min(graceMs, DEADLINE_POLL_MS));
10944
11067
  return true;
10945
11068
  }
10946
11069
  accountForBashTaskResponse(command, response) {
@@ -11188,7 +11311,7 @@ class BinaryBridge {
11188
11311
  }
11189
11312
  // ../aft-bridge/dist/cache-paths.js
11190
11313
  import { homedir as homedir6 } from "node:os";
11191
- import { join as join6 } from "node:path";
11314
+ import { join as join5 } from "node:path";
11192
11315
  function homeDir2(env) {
11193
11316
  return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir6();
11194
11317
  }
@@ -11196,20 +11319,20 @@ function getAftCacheRoot(env = process.env) {
11196
11319
  if (env.AFT_CACHE_DIR)
11197
11320
  return env.AFT_CACHE_DIR;
11198
11321
  if (process.platform === "win32") {
11199
- const base = env.LOCALAPPDATA || env.APPDATA || join6(homeDir2(env), "AppData", "Local");
11200
- return join6(base, "aft");
11322
+ const base = env.LOCALAPPDATA || env.APPDATA || join5(homeDir2(env), "AppData", "Local");
11323
+ return join5(base, "aft");
11201
11324
  }
11202
- const base = env.XDG_CACHE_HOME || join6(homeDir2(env), ".cache");
11203
- return join6(base, "aft");
11325
+ const base = env.XDG_CACHE_HOME || join5(homeDir2(env), ".cache");
11326
+ return join5(base, "aft");
11204
11327
  }
11205
11328
  function getAftBinaryCacheDir(env = process.env) {
11206
- return join6(getAftCacheRoot(env), "bin");
11329
+ return join5(getAftCacheRoot(env), "bin");
11207
11330
  }
11208
11331
  function getAftLspPackagesDir(env = process.env) {
11209
- return join6(getAftCacheRoot(env), "lsp-packages");
11332
+ return join5(getAftCacheRoot(env), "lsp-packages");
11210
11333
  }
11211
11334
  function getAftLspBinariesDir(env = process.env) {
11212
- return join6(getAftCacheRoot(env), "lsp-binaries");
11335
+ return join5(getAftCacheRoot(env), "lsp-binaries");
11213
11336
  }
11214
11337
  // ../aft-bridge/dist/callgraph-format.js
11215
11338
  var UNRESOLVED_SUMMARY_NAME_LIMIT = 10;
@@ -11309,15 +11432,57 @@ function hubSummaryLine(response, theme) {
11309
11432
  const message = summary ? asString(summary.message) : undefined;
11310
11433
  return message ? theme.fg("warning", message) : undefined;
11311
11434
  }
11435
+ var ROOT_KINDS = ["main", "test", "attribute", "public_api", "no_caller_found"];
11436
+ var ROOT_LABELS = {
11437
+ main: "main",
11438
+ test: "test",
11439
+ attribute: "attribute",
11440
+ public_api: "public API",
11441
+ no_caller_found: "no caller found"
11442
+ };
11443
+ function rootCounted(kind, count) {
11444
+ const plural = count === 1 ? "" : "s";
11445
+ switch (kind) {
11446
+ case "main":
11447
+ return count === 1 ? "main" : `${count} main functions`;
11448
+ case "test":
11449
+ return `${count} test${plural}`;
11450
+ case "attribute":
11451
+ return `${count} function${plural} with an entry attribute`;
11452
+ case "public_api":
11453
+ return `${count} public API function${plural}`;
11454
+ default:
11455
+ return `${count} function${plural} with no caller found`;
11456
+ }
11457
+ }
11458
+ function rootSummary(record) {
11459
+ const counts = asRecord(record.root_counts);
11460
+ if (!counts)
11461
+ return;
11462
+ const parts = ROOT_KINDS.flatMap((kind) => {
11463
+ const count = asNumber(counts[kind]);
11464
+ return count && count > 0 ? [rootCounted(kind, count)] : [];
11465
+ });
11466
+ return parts.length > 0 ? parts.join(", ") : undefined;
11467
+ }
11468
+ function rootTag(record) {
11469
+ const kind = asString(record.root);
11470
+ const label = kind ? ROOT_LABELS[kind] : undefined;
11471
+ if (!kind || !label)
11472
+ return;
11473
+ const attribute = asString(record.attribute);
11474
+ return kind === "attribute" && attribute ? `[root: attribute ${attribute}]` : `[root: ${label}]`;
11475
+ }
11312
11476
  function renderTracePath(path, index, lines, theme) {
11313
11477
  lines.push(`Path ${index + 1}`);
11478
+ const root = rootTag(path);
11314
11479
  asRecords(path.hops).forEach((hop, hopIndex) => {
11315
11480
  const symbol = asString(hop.symbol) ?? "(unknown)";
11316
11481
  const file = shortenHomePath(asString(hop.file) ?? "(unknown file)");
11317
11482
  const line = asNumber(hop.line);
11318
- const entry = hop.is_entry_point === true ? " [entry]" : "";
11483
+ const tag = hopIndex === 0 ? root ? ` ${theme.fg("warning", root)}` : "" : hop.exported === true ? " [exported]" : "";
11319
11484
  const nameMatch = nameMatchEdgeMarker(hop, theme);
11320
- lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
11485
+ lines.push(treeLine(hopIndex + 1, `${symbol}${tag} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
11321
11486
  });
11322
11487
  }
11323
11488
  function renderCallersGroupLines(group, theme, callbackLabel) {
@@ -11422,18 +11587,18 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME, op
11422
11587
  const hubSummary = hubSummaryLine(record, theme);
11423
11588
  const totalPaths = asNumber(record.total_paths) ?? paths.length;
11424
11589
  const totalPathsIsLowerBound = asBoolean(record.total_paths_is_lower_bound) ?? false;
11425
- const entryPoints = asNumber(record.entry_points_found) ?? 0;
11590
+ const roots = rootSummary(record);
11426
11591
  const sections = [
11427
11592
  joinNonEmpty([
11428
11593
  theme.fg("success", `${totalPathsIsLowerBound ? "at least " : ""}${totalPaths} path${totalPaths === 1 ? "" : "s"}`),
11429
- theme.fg("muted", `${entryPoints} entry point${entryPoints === 1 ? "" : "s"}`),
11594
+ roots ? theme.fg("muted", `roots: ${roots}`) : undefined,
11430
11595
  warning
11431
11596
  ])
11432
11597
  ];
11433
11598
  if (hubSummary)
11434
11599
  sections.push(hubSummary);
11435
11600
  if (paths.length === 0)
11436
- sections.push(theme.fg("muted", "No entry paths found."));
11601
+ sections.push(theme.fg("muted", "No paths found."));
11437
11602
  paths.forEach((path, index) => {
11438
11603
  const lines = [];
11439
11604
  renderTracePath(path, index, lines, theme);
@@ -11466,13 +11631,17 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME, op
11466
11631
  sections.unshift(theme.fg("warning", testFilterNote));
11467
11632
  if (hubSummary)
11468
11633
  sections.push(hubSummary);
11634
+ const roots = rootSummary(record);
11635
+ if (roots)
11636
+ sections.push(theme.fg("muted", `Reaches ${roots}`));
11469
11637
  if (callers.length === 0)
11470
11638
  sections.push(theme.fg("muted", "No impacted callers found."));
11471
11639
  callers.forEach((caller) => {
11472
11640
  const file = shortenHomePath(asString(caller.caller_file) ?? "(unknown file)");
11473
11641
  const symbol = asString(caller.caller_symbol) ?? "(unknown)";
11474
11642
  const line = asNumber(caller.line) ?? 0;
11475
- const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
11643
+ const root = rootTag(caller);
11644
+ const entry = root ? ` ${theme.fg("warning", root)}` : "";
11476
11645
  const nameMatch = nameMatchEdgeMarker(caller, theme);
11477
11646
  const reference = caller.relation === "callback_reference" ? asString(record.callback_reference_label) ?? "" : "";
11478
11647
  const expression = asString(caller.call_expression);
@@ -11652,7 +11821,7 @@ function isFindReplaceOnlyEdit(args) {
11652
11821
  }
11653
11822
  // ../aft-bridge/dist/config-error.js
11654
11823
  import { homedir as homedir7 } from "node:os";
11655
- import { isAbsolute as isAbsolute3, join as join7 } from "node:path";
11824
+ import { isAbsolute as isAbsolute2, join as join6 } from "node:path";
11656
11825
  var CONFIG_ERROR_RESTART_NOTE = "AFT reads its config only at startup, so restart the host after fixing it.";
11657
11826
  function formatConfigErrorMessage(detail) {
11658
11827
  const trimmed = detail.trim();
@@ -11662,10 +11831,10 @@ function formatConfigErrorMessage(detail) {
11662
11831
  function resolveSubcConnectionFilePath(raw, home = homedir7()) {
11663
11832
  const trimmed = raw.trim();
11664
11833
  if (trimmed.startsWith("~"))
11665
- return join7(home, trimmed.slice(1).replace(/^[/\\]/, ""));
11666
- if (isAbsolute3(trimmed))
11834
+ return join6(home, trimmed.slice(1).replace(/^[/\\]/, ""));
11835
+ if (isAbsolute2(trimmed))
11667
11836
  return trimmed;
11668
- return join7(home, trimmed);
11837
+ return join6(home, trimmed);
11669
11838
  }
11670
11839
  function formatSubcConnectionMissingMessage(raw, resolved) {
11671
11840
  return `subc.connection_file is set to "${raw.trim()}" (resolved: ${resolved}) but no subc ` + `connection file exists there. Start the Subconscious daemon, correct the path, ` + `or remove subc.connection_file from your user config to use the standalone bridge.`;
@@ -11726,7 +11895,7 @@ function stripHarnessSpecificConfigKeys(value, keys) {
11726
11895
  }
11727
11896
  // ../aft-bridge/dist/config-tiers.js
11728
11897
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
11729
- import { resolve as resolve4 } from "node:path";
11898
+ import { resolve as resolve3 } from "node:path";
11730
11899
  function readConfigTiers(opts) {
11731
11900
  const tiers = [];
11732
11901
  try {
@@ -11734,7 +11903,7 @@ function readConfigTiers(opts) {
11734
11903
  const doc = readFileSync2(opts.userConfigPath, "utf-8");
11735
11904
  tiers.push({
11736
11905
  tier: "user",
11737
- source: resolve4(opts.userConfigPath),
11906
+ source: resolve3(opts.userConfigPath),
11738
11907
  doc
11739
11908
  });
11740
11909
  }
@@ -11744,7 +11913,7 @@ function readConfigTiers(opts) {
11744
11913
  const doc = readFileSync2(opts.projectConfigPath, "utf-8");
11745
11914
  tiers.push({
11746
11915
  tier: "project",
11747
- source: resolve4(opts.projectConfigPath),
11916
+ source: resolve3(opts.projectConfigPath),
11748
11917
  doc
11749
11918
  });
11750
11919
  }
@@ -11758,8 +11927,9 @@ function formatDroppedKeyWarnings(dropped) {
11758
11927
  return dropped.map((item) => `Ignoring ${item.key} from ${item.tier} config (${item.reason})`);
11759
11928
  }
11760
11929
  // ../aft-bridge/dist/config-watch.js
11761
- import { readFileSync as readFileSync3, statSync as statSync3, watch } from "node:fs";
11762
- import { basename, dirname as dirname2 } from "node:path";
11930
+ import { createHash as createHash2 } from "node:crypto";
11931
+ import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync3, readSync as readSync2, realpathSync as realpathSync2, statSync as statSync3, watch } from "node:fs";
11932
+ import { basename, dirname as dirname2, resolve as resolve4 } from "node:path";
11763
11933
  var CONFIG_WATCH_DEBOUNCE_MS = 150;
11764
11934
  var CONFIG_LIVE_KEEP_NOTE = "AFT keeps using the last valid configuration until the file is fixed.";
11765
11935
  function pathKey(name) {
@@ -11813,8 +11983,8 @@ function aftLiveConfigKeys(resolveBash) {
11813
11983
  pathKey("restrict_to_project_root"),
11814
11984
  pathKey("revision_reads"),
11815
11985
  pathKey("inspect.diagnostics_timeout_ms"),
11816
- pathKey("inspect.tier2_idle_minutes"),
11817
11986
  pathKey("lsp.idle_minutes"),
11987
+ pathKey("lsp.max_servers"),
11818
11988
  ...[
11819
11989
  "diagnostics",
11820
11990
  "todos",
@@ -11885,6 +12055,29 @@ function readTextOrNull(path) {
11885
12055
  var CONFIG_WATCH_REVALIDATE_MS = 2000;
11886
12056
  var CONFIG_WATCH_REJECTED_RETRIES = 3;
11887
12057
  var CONFIG_WATCH_MAX_DELAY_MS = 1000;
12058
+ var CONFIG_READ_LIMIT = 1024 * 1024;
12059
+ function contentFingerprint(path) {
12060
+ let fd;
12061
+ try {
12062
+ fd = openSync2(path, "r");
12063
+ const metadata = fstatSync(fd, { bigint: true });
12064
+ const bytes = Buffer.alloc(Math.min(Number(metadata.size), CONFIG_READ_LIMIT));
12065
+ let offset = 0;
12066
+ while (offset < bytes.length) {
12067
+ const read = readSync2(fd, bytes, offset, bytes.length - offset, null);
12068
+ if (read === 0)
12069
+ break;
12070
+ offset += read;
12071
+ }
12072
+ const hash = createHash2("sha256").update(bytes.subarray(0, offset)).digest("hex");
12073
+ return `${metadata.size}:${metadata.mtimeNs}:${hash}`;
12074
+ } catch {
12075
+ return null;
12076
+ } finally {
12077
+ if (fd !== undefined)
12078
+ closeSync2(fd);
12079
+ }
12080
+ }
11888
12081
  function identityOf(dir) {
11889
12082
  try {
11890
12083
  const stat = statSync3(dir, { bigint: true });
@@ -11900,6 +12093,13 @@ function watchDirFor(file) {
11900
12093
  dir = dirname2(dir);
11901
12094
  return dir;
11902
12095
  }
12096
+ function canonicalDirectory(dir) {
12097
+ try {
12098
+ return realpathSync2(dir);
12099
+ } catch {
12100
+ return resolve4(dir);
12101
+ }
12102
+ }
11903
12103
  function watchAftConfigFiles(options) {
11904
12104
  const debounceMs = options.debounceMs ?? CONFIG_WATCH_DEBOUNCE_MS;
11905
12105
  const lastSeen = new Map;
@@ -11913,8 +12113,11 @@ function watchAftConfigFiles(options) {
11913
12113
  let firstPendingAt = null;
11914
12114
  let stopped = false;
11915
12115
  const watchers = new Map;
12116
+ const targets = new Map;
12117
+ const fallbackSeen = new Map;
11916
12118
  const watchImpl = options.watchImpl ?? watch;
11917
12119
  let rejectedRetries = 0;
12120
+ let recheckAfterArming = false;
11918
12121
  const check = () => {
11919
12122
  if (retryTimer)
11920
12123
  clearTimeout(retryTimer);
@@ -11923,7 +12126,8 @@ function watchAftConfigFiles(options) {
11923
12126
  firstPendingAt = null;
11924
12127
  if (stopped)
11925
12128
  return;
11926
- revalidate();
12129
+ if (revalidate())
12130
+ recheckAfterArming = true;
11927
12131
  const seen = new Map;
11928
12132
  for (const path of options.paths) {
11929
12133
  const text = readTextOrNull(path);
@@ -11966,8 +12170,34 @@ function watchAftConfigFiles(options) {
11966
12170
  timer.unref?.();
11967
12171
  };
11968
12172
  function revalidate() {
11969
- const wanted = new Set(options.paths.map(watchDirFor));
12173
+ const wanted = new Set;
12174
+ const wantedCanonical = new Set;
12175
+ const wantDirectory = (dir) => {
12176
+ const canonical = canonicalDirectory(dir);
12177
+ if (wantedCanonical.has(canonical))
12178
+ return;
12179
+ wantedCanonical.add(canonical);
12180
+ wanted.add(dir);
12181
+ };
12182
+ for (const path of options.paths)
12183
+ wantDirectory(watchDirFor(path));
11970
12184
  let moved = false;
12185
+ for (const path of options.paths) {
12186
+ try {
12187
+ const resolved = realpathSync2(path);
12188
+ const target = resolved === resolve4(path) ? undefined : resolved;
12189
+ if (targets.get(path) !== target) {
12190
+ if (target)
12191
+ targets.set(path, target);
12192
+ else
12193
+ targets.delete(path);
12194
+ fallbackSeen.delete(path);
12195
+ moved = true;
12196
+ }
12197
+ } catch {}
12198
+ }
12199
+ for (const target of targets.values())
12200
+ wantDirectory(watchDirFor(target));
11971
12201
  for (const [dir, entry] of watchers) {
11972
12202
  if (!wanted.has(dir) || entry.stale || identityOf(dir) !== entry.identity) {
11973
12203
  entry.watcher.close();
@@ -12006,14 +12236,29 @@ function watchAftConfigFiles(options) {
12006
12236
  moved = true;
12007
12237
  } catch {}
12008
12238
  }
12239
+ const watchedCanonical = new Set(Array.from(watchers.keys(), canonicalDirectory));
12240
+ for (const [path, target] of targets) {
12241
+ if (watchedCanonical.has(canonicalDirectory(dirname2(target)))) {
12242
+ fallbackSeen.delete(path);
12243
+ continue;
12244
+ }
12245
+ const fingerprint = contentFingerprint(path);
12246
+ if (!fallbackSeen.has(path) || fallbackSeen.get(path) !== fingerprint)
12247
+ moved = true;
12248
+ fallbackSeen.set(path, fingerprint);
12249
+ }
12009
12250
  return moved;
12010
12251
  }
12011
- revalidate();
12252
+ recheckAfterArming = revalidate();
12012
12253
  if (options.checkAtStart)
12013
12254
  check();
12014
12255
  const revalidateTimer = setInterval(() => {
12015
- if (!stopped && revalidate())
12256
+ if (stopped)
12257
+ return;
12258
+ const moved = revalidate();
12259
+ if (moved || recheckAfterArming)
12016
12260
  schedule();
12261
+ recheckAfterArming = moved;
12017
12262
  }, CONFIG_WATCH_REVALIDATE_MS);
12018
12263
  revalidateTimer.unref?.();
12019
12264
  return () => {
@@ -12056,6 +12301,10 @@ function aftLiveSecurityKeys(keys) {
12056
12301
  return minutes(next) > minutes(current);
12057
12302
  }
12058
12303
  },
12304
+ {
12305
+ key: pick("lsp.max_servers"),
12306
+ loosens: (current, next) => (typeof next === "number" ? next : 8) > (typeof current === "number" ? current : 8)
12307
+ },
12059
12308
  ...[
12060
12309
  "diagnostics",
12061
12310
  "todos",
@@ -12162,10 +12411,10 @@ function startLiveConfigReload(options) {
12162
12411
  return { reload, stop };
12163
12412
  }
12164
12413
  // ../aft-bridge/dist/downloader.js
12165
- import { createHash as createHash2, randomUUID } from "node:crypto";
12166
- import { chmodSync, closeSync as closeSync2, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync as openSync2, readdirSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync4, unlinkSync as unlinkSync2, writeSync } from "node:fs";
12414
+ import { createHash as createHash3, randomUUID } from "node:crypto";
12415
+ import { chmodSync, closeSync as closeSync3, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync as openSync3, readdirSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync4, unlinkSync as unlinkSync2, writeSync } from "node:fs";
12167
12416
  import { hostname } from "node:os";
12168
- import { join as join8 } from "node:path";
12417
+ import { join as join7 } from "node:path";
12169
12418
  import { Readable } from "node:stream";
12170
12419
  import { pipeline } from "node:stream/promises";
12171
12420
 
@@ -12281,7 +12530,7 @@ function getBinaryName() {
12281
12530
  function getCachedBinaryPath(version) {
12282
12531
  if (!version)
12283
12532
  return null;
12284
- const binaryPath = join8(getAftBinaryCacheDir(), version, getBinaryName());
12533
+ const binaryPath = join7(getAftBinaryCacheDir(), version, getBinaryName());
12285
12534
  return existsSync3(binaryPath) ? binaryPath : null;
12286
12535
  }
12287
12536
  async function downloadBinary(version) {
@@ -12298,16 +12547,16 @@ async function downloadBinary(version) {
12298
12547
  return null;
12299
12548
  }
12300
12549
  const tag = rawTag.startsWith("v") ? rawTag : `v${rawTag}`;
12301
- const versionedCacheDir = join8(getAftBinaryCacheDir(), tag);
12550
+ const versionedCacheDir = join7(getAftBinaryCacheDir(), tag);
12302
12551
  const binaryName = getBinaryName();
12303
- const binaryPath = join8(versionedCacheDir, binaryName);
12552
+ const binaryPath = join7(versionedCacheDir, binaryName);
12304
12553
  if (existsSync3(binaryPath) && await isExpectedCachedBinary(binaryPath, tag)) {
12305
12554
  return binaryPath;
12306
12555
  }
12307
12556
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
12308
12557
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
12309
12558
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
12310
- const lockPath = join8(versionedCacheDir, ".download.lock");
12559
+ const lockPath = join7(versionedCacheDir, ".download.lock");
12311
12560
  let releaseLock = null;
12312
12561
  let binaryController = null;
12313
12562
  let checksumController = null;
@@ -12381,7 +12630,7 @@ async function downloadBinary(version) {
12381
12630
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
12382
12631
  return null;
12383
12632
  }
12384
- const hash = createHash2("sha256");
12633
+ const hash = createHash3("sha256");
12385
12634
  let bytesWritten = 0;
12386
12635
  const guard = new TransformStream({
12387
12636
  transform(chunk, controller) {
@@ -12533,7 +12782,7 @@ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
12533
12782
  if (!entry.isFile() || !entry.name.startsWith(tempPrefix) || !entry.name.endsWith(".tmp")) {
12534
12783
  continue;
12535
12784
  }
12536
- const tempPath = join8(versionedCacheDir, entry.name);
12785
+ const tempPath = join7(versionedCacheDir, entry.name);
12537
12786
  const ageMs = Date.now() - statSync4(tempPath).mtimeMs;
12538
12787
  if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
12539
12788
  unlinkSync2(tempPath);
@@ -12548,11 +12797,11 @@ async function acquireDownloadLock(lockPath, timing = {}) {
12548
12797
  while (true) {
12549
12798
  try {
12550
12799
  const owner = createDownloadLockOwner();
12551
- const fd = openSync2(lockPath, "wx");
12800
+ const fd = openSync3(lockPath, "wx");
12552
12801
  try {
12553
12802
  writeSync(fd, owner);
12554
12803
  } finally {
12555
- closeSync2(fd);
12804
+ closeSync3(fd);
12556
12805
  }
12557
12806
  return () => {
12558
12807
  try {
@@ -12623,8 +12872,8 @@ import { appendFile, lstat, mkdir, open, rename, rm, stat } from "node:fs/promis
12623
12872
  import { dirname as dirname3 } from "node:path";
12624
12873
 
12625
12874
  // ../aft-bridge/dist/private-storage.js
12626
- import { closeSync as closeSync3, constants, copyFileSync as copyFileSync2, fchmodSync, fstatSync, lstatSync, mkdirSync as mkdirSync3, openSync as openSync3, readSync as readSync2, writeSync as writeSync2 } from "node:fs";
12627
- import { isAbsolute as isAbsolute4, join as join9, parse, relative as relative3, resolve as resolve5, sep } from "node:path";
12875
+ import { closeSync as closeSync4, constants, copyFileSync as copyFileSync2, fchmodSync, fstatSync as fstatSync2, lstatSync, mkdirSync as mkdirSync3, openSync as openSync4, readSync as readSync3, writeSync as writeSync2 } from "node:fs";
12876
+ import { isAbsolute as isAbsolute3, join as join8, parse, relative as relative2, resolve as resolve5, sep } from "node:path";
12628
12877
  var PRIVATE_FILE_MODE = 384;
12629
12878
  var PRIVATE_DIRECTORY_MODE = 448;
12630
12879
  function privateMkdirSync(path, recursive = true) {
@@ -12638,13 +12887,13 @@ function privateCopyFileSync(source, destination) {
12638
12887
  copyFileSync2(source, destination);
12639
12888
  return;
12640
12889
  }
12641
- const input = openSync3(source, "r");
12890
+ const input = openSync4(source, "r");
12642
12891
  let output;
12643
12892
  try {
12644
- output = openSync3(destination, "wx", PRIVATE_FILE_MODE);
12893
+ output = openSync4(destination, "wx", PRIVATE_FILE_MODE);
12645
12894
  const buffer = Buffer.allocUnsafe(64 * 1024);
12646
12895
  for (;; ) {
12647
- const bytes = readSync2(input, buffer);
12896
+ const bytes = readSync3(input, buffer);
12648
12897
  if (bytes === 0)
12649
12898
  break;
12650
12899
  let offset = 0;
@@ -12652,9 +12901,9 @@ function privateCopyFileSync(source, destination) {
12652
12901
  offset += writeSync2(output, buffer, offset, bytes - offset);
12653
12902
  }
12654
12903
  } finally {
12655
- closeSync3(input);
12904
+ closeSync4(input);
12656
12905
  if (output !== undefined)
12657
- closeSync3(output);
12906
+ closeSync4(output);
12658
12907
  }
12659
12908
  }
12660
12909
  var warned = new Set;
@@ -12663,21 +12912,21 @@ function openPrivateStorageDir(root, path = root) {
12663
12912
  if (!root || base === parse(base).root) {
12664
12913
  throw new Error(`refusing filesystem root as private storage: ${base}`);
12665
12914
  }
12666
- const rel = relative3(base, resolve5(path));
12667
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute4(rel))
12915
+ const rel = relative2(base, resolve5(path));
12916
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute3(rel))
12668
12917
  return;
12669
12918
  privateMkdirSync(path);
12670
12919
  if (process.platform === "win32")
12671
12920
  return;
12672
12921
  let current = base;
12673
12922
  for (const component of ["", ...rel.split(sep).filter(Boolean)]) {
12674
- current = join9(current, component);
12923
+ current = join8(current, component);
12675
12924
  let fd;
12676
12925
  try {
12677
12926
  if (lstatSync(current).isSymbolicLink())
12678
12927
  return;
12679
- fd = openSync3(current, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
12680
- if ((fstatSync(fd).mode & 63) !== 0)
12928
+ fd = openSync4(current, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
12929
+ if ((fstatSync2(fd).mode & 63) !== 0)
12681
12930
  fchmodSync(fd, PRIVATE_DIRECTORY_MODE);
12682
12931
  } catch (error) {
12683
12932
  if (!warned.has(current)) {
@@ -12688,7 +12937,7 @@ function openPrivateStorageDir(root, path = root) {
12688
12937
  return;
12689
12938
  } finally {
12690
12939
  if (fd !== undefined)
12691
- closeSync3(fd);
12940
+ closeSync4(fd);
12692
12941
  }
12693
12942
  }
12694
12943
  }
@@ -12924,21 +13173,23 @@ ${reflowText}`;
12924
13173
  }
12925
13174
  return " Auto-formatted.";
12926
13175
  }
12927
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
13176
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/client.js
12928
13177
  import { promises as fs3 } from "node:fs";
13178
+ import { createHash as createHash4 } from "node:crypto";
12929
13179
  import { debuglog } from "node:util";
12930
13180
 
12931
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
13181
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/auth.js
12932
13182
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
12933
13183
 
12934
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
13184
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/socket.js
12935
13185
  import net from "node:net";
12936
13186
 
12937
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/envelope.js
13187
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/envelope.js
12938
13188
  var PROTOCOL_VERSION = 2;
12939
13189
  var HEADER_LEN = 21;
12940
13190
  var FROZEN_PREFIX_LEN = 5;
12941
13191
  var MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
13192
+ var MAX_DELIVERED_FRAME_LEN = 4 * 1024 * 1024;
12942
13193
  var FrameType;
12943
13194
  (function(FrameType) {
12944
13195
  FrameType[FrameType["Request"] = 0] = "Request";
@@ -12976,6 +13227,8 @@ var FLAG_PRIORITY_SHIFT = 1;
12976
13227
  var FLAG_LAST = 8;
12977
13228
  var FLAG_ADMISSION_MASK = 48;
12978
13229
  var FLAG_ADMISSION_SHIFT = 4;
13230
+ var DAEMON_ORIGIN_FLAG = 64;
13231
+ var SUBSCRIPTION_FLAG = 128;
12979
13232
  var FLAG_RESERVED_MASK = 0;
12980
13233
  function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Normal) {
12981
13234
  let flags = 0;
@@ -13110,7 +13363,7 @@ function encodeFrame(frame) {
13110
13363
  return output;
13111
13364
  }
13112
13365
 
13113
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
13366
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/socket.js
13114
13367
  class SocketClosedError extends Error {
13115
13368
  }
13116
13369
 
@@ -13160,14 +13413,12 @@ class SubcSocket {
13160
13413
  sock.on("data", (chunk) => {
13161
13414
  this.chunks.push(chunk);
13162
13415
  this.buffered += chunk.length;
13163
- if (this.waiter) {
13416
+ if (this.waiter && this.waiter.extendOnProgress && this.buffered < this.waiter.need) {
13164
13417
  if (this.waiter.immediate) {
13165
13418
  clearImmediate(this.waiter.immediate);
13166
13419
  this.waiter.immediate = null;
13167
13420
  }
13168
- if (this.waiter.extendOnProgress && this.buffered < this.waiter.need) {
13169
- this.armWaiterTimer(this.waiter);
13170
- }
13421
+ this.armWaiterTimer(this.waiter);
13171
13422
  }
13172
13423
  this.tryServe();
13173
13424
  });
@@ -13184,12 +13435,18 @@ class SubcSocket {
13184
13435
  return this.sock.localPort ?? null;
13185
13436
  }
13186
13437
  static connect(host, port, deadlineMs) {
13438
+ return SubcSocket.connectTo({ host, port }, `${host}:${port}`, deadlineMs);
13439
+ }
13440
+ static connectLocal(path, deadlineMs) {
13441
+ return SubcSocket.connectTo({ path }, path, deadlineMs);
13442
+ }
13443
+ static connectTo(options, endpoint, deadlineMs) {
13187
13444
  return new Promise((resolve, reject) => {
13188
- const sock = net.connect({ host, port });
13445
+ const sock = net.connect(options);
13189
13446
  sock.setNoDelay(true);
13190
13447
  const timer = setTimeout(() => {
13191
13448
  sock.destroy();
13192
- reject(new SocketTimeoutError(`timed out connecting to ${host}:${port}`));
13449
+ reject(new SocketTimeoutError(`timed out connecting to ${endpoint}`));
13193
13450
  }, Math.max(0, deadlineMs - Date.now()));
13194
13451
  sock.once("connect", () => {
13195
13452
  clearTimeout(timer);
@@ -13406,7 +13663,7 @@ class SubcSocket {
13406
13663
  }
13407
13664
  }
13408
13665
 
13409
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
13666
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/auth.js
13410
13667
  var NONCE_LEN = 32;
13411
13668
  var MAX_AUTH_MESSAGE_LEN = 4096;
13412
13669
  var SERVER_PROOF_DOMAIN = "subc-server-v1";
@@ -13480,7 +13737,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
13480
13737
  await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
13481
13738
  }
13482
13739
 
13483
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/connection-file.js
13740
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/connection-file.js
13484
13741
  import { promises as fs2 } from "node:fs";
13485
13742
  var SCHEMA_VERSION = 1;
13486
13743
  var MIN_KEY_LEN = 32;
@@ -13527,7 +13784,10 @@ function verifyOwnerOnly(path, stat, expectedUid) {
13527
13784
  async function readConnectionFile(path) {
13528
13785
  return readConnectionFileForUid(path, process.geteuid?.());
13529
13786
  }
13530
- async function readConnectionFileForUid(path, expectedUid) {
13787
+ async function readConnectionFileWithLocalEndpoint(path) {
13788
+ return readConnectionFileForUid(path, process.geteuid?.(), true);
13789
+ }
13790
+ async function readConnectionFileForUid(path, expectedUid, includeLocalEndpoint = false) {
13531
13791
  const handle = await fs2.open(path, "r");
13532
13792
  let raw;
13533
13793
  try {
@@ -13566,13 +13826,26 @@ async function readConnectionFileForUid(path, expectedUid) {
13566
13826
  daemonVer: parsed.daemon_ver ?? ""
13567
13827
  };
13568
13828
  validate(info);
13829
+ if (includeLocalEndpoint && parsed.local_endpoint != null) {
13830
+ const endpoint = parsed.local_endpoint;
13831
+ if (endpoint.kind === "unix" && typeof endpoint.path === "string" && endpoint.path.length > 0) {
13832
+ info.localEndpoint = { kind: "unix", path: endpoint.path };
13833
+ } else if (endpoint.kind === "pipe" && typeof endpoint.name === "string" && endpoint.name.length > 0) {
13834
+ info.localEndpoint = { kind: "pipe", name: endpoint.name };
13835
+ } else {
13836
+ throw new ConnectionFileError("connection file local_endpoint must name a unix path or pipe name");
13837
+ }
13838
+ }
13569
13839
  return info;
13570
13840
  }
13571
13841
 
13572
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
13573
- import { closeSync as closeSync4, fstatSync as fstatSync2, readSync as readSync3 } from "node:fs";
13842
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
13843
+ import { closeSync as closeSync5, fstatSync as fstatSync3, readSync as readSync4 } from "node:fs";
13844
+ import { connect } from "node:net";
13574
13845
  var SUBC_LAUNCH_NONCE_ENV = "SUBC_LAUNCH_NONCE";
13575
13846
  var SUBC_LAUNCH_NONCE_FD_ENV = "SUBC_LAUNCH_NONCE_FD";
13847
+ var SUBC_LAUNCH_NONCE_PIPE_ENV = "SUBC_LAUNCH_NONCE_PIPE";
13848
+ var SUBC_LAUNCH_NONCE_PIPE_FALLBACK_ENV = "SUBC_LAUNCH_NONCE_PIPE_FALLBACK";
13576
13849
  class LaunchNonceError extends Error {
13577
13850
  name = "LaunchNonceError";
13578
13851
  kind;
@@ -13591,9 +13864,21 @@ class LaunchNonceError extends Error {
13591
13864
  this.foundInode = details.foundInode;
13592
13865
  }
13593
13866
  }
13594
- function launchNonceOrUndefined() {
13595
- const result = processCell().result();
13596
- return result.ok ? result.nonce ?? undefined : undefined;
13867
+ function isLaunchNonceError(error) {
13868
+ return error instanceof Error && error.name === "LaunchNonceError" && typeof error.kind === "string";
13869
+ }
13870
+ async function launchNonceAsync() {
13871
+ const cell = processCell();
13872
+ return unwrap(await (cell.resultAsync?.() ?? cell.result()));
13873
+ }
13874
+ async function launchNonceOrUndefinedAsync() {
13875
+ try {
13876
+ return await launchNonceAsync();
13877
+ } catch (error) {
13878
+ if (isLaunchNonceError(error))
13879
+ return;
13880
+ throw error;
13881
+ }
13597
13882
  }
13598
13883
  var PROCESS_CELL_KEY = Symbol.for("@cortexkit/subc-client/launch-nonce/v1");
13599
13884
  function processCell() {
@@ -13613,20 +13898,60 @@ function unwrap(result) {
13613
13898
 
13614
13899
  class LaunchNonceCell {
13615
13900
  cached;
13901
+ pending;
13616
13902
  descriptorReads = 0;
13617
13903
  lookup;
13618
13904
  platform;
13619
13905
  stat;
13620
- constructor(lookup, platform = process.platform, stat = (fd) => fstatSync2(fd, { bigint: true })) {
13906
+ pipeReader;
13907
+ constructor(lookup, platform = process.platform, stat = (fd) => fstatSync3(fd, { bigint: true }), pipeReader = readLaunchNoncePipe) {
13621
13908
  this.lookup = lookup;
13622
13909
  this.platform = platform;
13623
13910
  this.stat = stat;
13911
+ this.pipeReader = pipeReader;
13624
13912
  }
13625
13913
  result() {
13914
+ if (this.cached === undefined && this.platform === "win32" && this.lookup(SUBC_LAUNCH_NONCE_PIPE_ENV) !== undefined) {
13915
+ const fallback = this.permittedPipeFallback();
13916
+ if (fallback === undefined) {
13917
+ throw new LaunchNonceError("PipeNeedsAsync", "call launchNonceAsync() before the synchronous reader when SUBC_LAUNCH_NONCE_PIPE is set");
13918
+ }
13919
+ this.cached = { ok: true, nonce: fallback };
13920
+ }
13626
13921
  if (this.cached === undefined)
13627
13922
  this.cached = this.read();
13628
13923
  return this.cached;
13629
13924
  }
13925
+ async resultAsync() {
13926
+ if (this.cached !== undefined)
13927
+ return this.cached;
13928
+ if (this.pending !== undefined)
13929
+ return this.pending;
13930
+ const named = this.platform === "win32" ? this.lookup(SUBC_LAUNCH_NONCE_PIPE_ENV) : undefined;
13931
+ if (named === undefined)
13932
+ return this.result();
13933
+ const fallback = this.permittedPipeFallback();
13934
+ this.pending = this.pipeReader(named).then((nonce) => ({ ok: true, nonce: fallback !== undefined && nonce.value !== fallback.value ? fallback : nonce }), (error) => {
13935
+ if (fallback !== undefined)
13936
+ return { ok: true, nonce: fallback };
13937
+ if (!isLaunchNonceError(error))
13938
+ throw error;
13939
+ return { ok: false, error };
13940
+ }).then((result) => {
13941
+ this.cached ??= result;
13942
+ return this.cached;
13943
+ });
13944
+ return this.pending;
13945
+ }
13946
+ permittedPipeFallback() {
13947
+ if (this.lookup(SUBC_LAUNCH_NONCE_PIPE_FALLBACK_ENV) !== "env")
13948
+ return;
13949
+ const value = this.lookup(SUBC_LAUNCH_NONCE_ENV);
13950
+ return value ? makeLaunchNonce(value, "env") : undefined;
13951
+ }
13952
+ async getAsync() {
13953
+ return unwrap(await this.resultAsync());
13954
+ }
13630
13955
  get() {
13631
13956
  return unwrap(this.result());
13632
13957
  }
@@ -13665,7 +13990,7 @@ class LaunchNonceCell {
13665
13990
  const chunk = Buffer.alloc(256);
13666
13991
  let got;
13667
13992
  try {
13668
- got = readSync3(fd, chunk, 0, chunk.length, null);
13993
+ got = readSync4(fd, chunk, 0, chunk.length, null);
13669
13994
  } catch (error) {
13670
13995
  if (isWouldBlock(error))
13671
13996
  throw emptyError(fd);
@@ -13677,7 +14002,7 @@ class LaunchNonceCell {
13677
14002
  const parts = [Buffer.from(chunk.subarray(0, got))];
13678
14003
  try {
13679
14004
  for (;; ) {
13680
- const n = readSync3(fd, chunk, 0, chunk.length, null);
14005
+ const n = readSync4(fd, chunk, 0, chunk.length, null);
13681
14006
  if (n === 0)
13682
14007
  break;
13683
14008
  parts.push(Buffer.from(chunk.subarray(0, n)));
@@ -13754,11 +14079,201 @@ function errnoOf(error) {
13754
14079
  }
13755
14080
  function closeQuietly(fd) {
13756
14081
  try {
13757
- closeSync4(fd);
14082
+ closeSync5(fd);
13758
14083
  } catch {}
13759
14084
  }
14085
+ async function readLaunchNoncePipe(name, connector = (path) => connect(path), openWindowMs = 500, readWindowMs = 2000) {
14086
+ const deadline = Date.now() + openWindowMs;
14087
+ for (;; ) {
14088
+ try {
14089
+ const bytes = await pipeAttempt(name, connector, Math.max(1, deadline - Date.now()), readWindowMs);
14090
+ if (bytes.length === 0)
14091
+ throw new LaunchNonceError("PipeEmpty", "the launch nonce named pipe is empty");
14092
+ let value;
14093
+ try {
14094
+ value = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14095
+ } catch {
14096
+ throw new LaunchNonceError("PipeNotUtf8", "the launch nonce named pipe held bytes that are not UTF-8");
14097
+ }
14098
+ return makeLaunchNonce(value, "pipe");
14099
+ } catch (error) {
14100
+ if (isLaunchNonceError(error))
14101
+ throw error;
14102
+ const busy = error?.code === "EBUSY" || errnoOf(error) === 231;
14103
+ if (!busy || Date.now() >= deadline) {
14104
+ throw new LaunchNonceError("PipeNotOpen", "SUBC_LAUNCH_NONCE_PIPE names a pipe that could not be opened", { errno: errnoOf(error) });
14105
+ }
14106
+ await new Promise((resolve) => setTimeout(resolve, Math.min(10, Math.max(1, deadline - Date.now()))));
14107
+ }
14108
+ }
14109
+ }
14110
+ function pipeAttempt(name, connector, openWindowMs, readWindowMs) {
14111
+ return new Promise((resolve, reject) => {
14112
+ let socket;
14113
+ try {
14114
+ socket = connector(name);
14115
+ } catch (error) {
14116
+ reject(error);
14117
+ return;
14118
+ }
14119
+ let connected = false;
14120
+ let settled = false;
14121
+ let size = 0;
14122
+ const chunks = [];
14123
+ let timer = setTimeout(() => finish(new LaunchNonceError("PipeNotOpen", "launch nonce pipe open timed out")), openWindowMs);
14124
+ function finish(error) {
14125
+ if (settled)
14126
+ return;
14127
+ settled = true;
14128
+ clearTimeout(timer);
14129
+ socket.destroy();
14130
+ if (error !== undefined)
14131
+ reject(error);
14132
+ else
14133
+ resolve(Buffer.concat(chunks));
14134
+ }
14135
+ socket.once("connect", () => {
14136
+ connected = true;
14137
+ clearTimeout(timer);
14138
+ timer = setTimeout(() => finish(new LaunchNonceError("PipeUnreadable", "launch nonce pipe read timed out")), readWindowMs);
14139
+ });
14140
+ socket.on("data", (chunk) => {
14141
+ size += chunk.length;
14142
+ if (size > 4096) {
14143
+ finish(new LaunchNonceError("PipeUnreadable", "launch nonce pipe exceeded its byte limit"));
14144
+ return;
14145
+ }
14146
+ chunks.push(Buffer.from(chunk));
14147
+ });
14148
+ socket.once("end", () => finish());
14149
+ socket.once("error", (error) => finish(connected ? new LaunchNonceError("PipeUnreadable", "could not read the launch nonce from the named pipe", { errno: errnoOf(error) }) : error));
14150
+ socket.once("close", () => {
14151
+ if (!settled)
14152
+ finish(new LaunchNonceError("PipeUnreadable", "launch nonce pipe closed before EOF"));
14153
+ });
14154
+ });
14155
+ }
13760
14156
 
13761
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/route-handle.js
14157
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/request-gate.js
14158
+ class Lane {
14159
+ limit;
14160
+ used = 0;
14161
+ waiters = [];
14162
+ failure;
14163
+ constructor(limit) {
14164
+ this.limit = limit;
14165
+ }
14166
+ acquire(timeoutMs, signal) {
14167
+ if (this.failure)
14168
+ return Promise.reject(this.failure);
14169
+ if (signal?.aborted)
14170
+ return Promise.reject(new Error("request cancelled before write"));
14171
+ if (timeoutMs !== undefined && timeoutMs <= 0)
14172
+ return Promise.reject(new Error("request deadline elapsed waiting for a slot"));
14173
+ if (this.used < this.limit && this.waiters.length === 0) {
14174
+ this.used++;
14175
+ return Promise.resolve(this.releaser());
14176
+ }
14177
+ return new Promise((resolve, reject) => {
14178
+ let timer;
14179
+ const clean = () => {
14180
+ if (timer !== undefined)
14181
+ clearTimeout(timer);
14182
+ signal?.removeEventListener("abort", abort);
14183
+ };
14184
+ const waiter = {
14185
+ grant: () => {
14186
+ clean();
14187
+ resolve(this.releaser());
14188
+ },
14189
+ fail: (error) => {
14190
+ clean();
14191
+ reject(error);
14192
+ }
14193
+ };
14194
+ const abort = () => {
14195
+ const index = this.waiters.indexOf(waiter);
14196
+ if (index < 0)
14197
+ return;
14198
+ this.waiters.splice(index, 1);
14199
+ waiter.fail(new Error("request cancelled before write"));
14200
+ };
14201
+ this.waiters.push(waiter);
14202
+ signal?.addEventListener("abort", abort, { once: true });
14203
+ if (timeoutMs !== undefined)
14204
+ timer = setTimeout(() => {
14205
+ const index = this.waiters.indexOf(waiter);
14206
+ if (index < 0)
14207
+ return;
14208
+ this.waiters.splice(index, 1);
14209
+ waiter.fail(new Error("request deadline elapsed waiting for a slot"));
14210
+ }, timeoutMs);
14211
+ });
14212
+ }
14213
+ close(error) {
14214
+ this.failure = error;
14215
+ for (const waiter of this.waiters.splice(0))
14216
+ waiter.fail(error);
14217
+ }
14218
+ releaser() {
14219
+ let released = false;
14220
+ return () => {
14221
+ if (released)
14222
+ return;
14223
+ released = true;
14224
+ const waiter = this.waiters.shift();
14225
+ if (waiter)
14226
+ waiter.grant();
14227
+ else
14228
+ this.used--;
14229
+ };
14230
+ }
14231
+ }
14232
+
14233
+ class RequestGate {
14234
+ total;
14235
+ watches;
14236
+ slots = new Set;
14237
+ closed = new AbortController;
14238
+ get signal() {
14239
+ return this.closed.signal;
14240
+ }
14241
+ constructor(requestWindow, watchWindow) {
14242
+ this.total = new Lane(requestWindow);
14243
+ if (watchWindow !== undefined)
14244
+ this.watches = new Lane(watchWindow);
14245
+ }
14246
+ async acquire(watch, timeoutMs, signal) {
14247
+ const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
14248
+ const watchRelease = watch && this.watches ? await this.watches.acquire(timeoutMs, signal) : undefined;
14249
+ try {
14250
+ const totalRelease = await this.total.acquire(deadline === undefined ? undefined : deadline - Date.now(), signal);
14251
+ const slot = {
14252
+ inFlight: false,
14253
+ release: () => {
14254
+ if (!this.slots.delete(slot))
14255
+ return;
14256
+ totalRelease();
14257
+ watchRelease?.();
14258
+ }
14259
+ };
14260
+ this.slots.add(slot);
14261
+ return slot;
14262
+ } catch (error) {
14263
+ watchRelease?.();
14264
+ throw error;
14265
+ }
14266
+ }
14267
+ close(error) {
14268
+ this.total.close(error);
14269
+ this.watches?.close(error);
14270
+ this.closed.abort(error);
14271
+ for (const slot of this.slots)
14272
+ slot.release();
14273
+ }
14274
+ }
14275
+
14276
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/route-handle.js
13762
14277
  var connectionToken = new WeakMap;
13763
14278
  var reverseRequests = new WeakMap;
13764
14279
 
@@ -13851,10 +14366,13 @@ function sameRouteHandle(left, right) {
13851
14366
  return left === right;
13852
14367
  }
13853
14368
 
13854
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
14369
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/client.js
13855
14370
  var debug = debuglog("subc-client");
13856
14371
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
13857
14372
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
14373
+ var DEFAULT_SLEEP_GRACE_MS = 15000;
14374
+ var SLEEP_DETECTION_THRESHOLD_MS = 2000;
14375
+ var MIN_REQUEST_BUDGET_MS = 1;
13858
14376
  var TIMEOUT_ARBITRATION_GRACE_MS = 50;
13859
14377
  var LIVENESS_PROBE_WINDOW_MS = 2000;
13860
14378
  var REQUEST_DEADLINE_MARKER = "request_deadline";
@@ -13890,6 +14408,14 @@ class SubcCallError extends Error {
13890
14408
  }
13891
14409
  }
13892
14410
 
14411
+ class DaemonUnavailableError extends SubcCallError {
14412
+ recovery = "user_action_required";
14413
+ constructor(cause) {
14414
+ super("not_sent", "CortexKit's daemon is not running or can't be reached on this computer, so this tool call was not sent. Retrying won't start it. Stop calling CortexKit tools and ask the user to start the daemon (run `ck setup`, or restart the CortexKit service), then try again after they confirm it is running.", errorCode(cause), cause);
14415
+ this.name = "DaemonUnavailableError";
14416
+ }
14417
+ }
14418
+
13893
14419
  class SubcError extends Error {
13894
14420
  code;
13895
14421
  detail;
@@ -14014,6 +14540,14 @@ class SubcClient {
14014
14540
  reconnecting = null;
14015
14541
  generation = 1;
14016
14542
  routeOpenGateInstance;
14543
+ requestGates;
14544
+ wireSlots;
14545
+ get gates() {
14546
+ return this.requestGates ??= new Map;
14547
+ }
14548
+ get slots() {
14549
+ return this.wireSlots ??= new Map;
14550
+ }
14017
14551
  readerActive = false;
14018
14552
  constructor(sock, currentConn, opts) {
14019
14553
  this.sock = sock;
@@ -14029,8 +14563,15 @@ class SubcClient {
14029
14563
  }
14030
14564
  static async connect(opts) {
14031
14565
  const normalized = normalizeConnectOptions(opts);
14032
- const opened = await SubcClient.openConnection(normalized);
14033
- return new SubcClient(opened.sock, opened.conn, normalized);
14566
+ try {
14567
+ const opened = await SubcClient.openConnection(normalized);
14568
+ return new SubcClient(opened.sock, opened.conn, normalized);
14569
+ } catch (err) {
14570
+ if (errorCode(err) === "ENOENT" || errorCode(err) === "ECONNREFUSED") {
14571
+ throw new DaemonUnavailableError(err);
14572
+ }
14573
+ throw err;
14574
+ }
14034
14575
  }
14035
14576
  async catalogList(moduleId) {
14036
14577
  const body = this.encode(moduleId === undefined ? { op: "catalog.list" } : { op: "catalog.list", module_id: moduleId });
@@ -14066,17 +14607,23 @@ class SubcClient {
14066
14607
  }
14067
14608
  async sendRouteOpen(target, identity, opts = {}) {
14068
14609
  const scope = validatedRouteScope(opts.scope);
14069
- const consumerIdentity = routeOpenConsumerIdentity(opts);
14610
+ const consumerIdentity = await routeOpenConsumerIdentity(opts);
14070
14611
  const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
14071
14612
  reverseRequests.seal();
14072
14613
  const consumerCapabilities = reverseRequests.capabilities();
14073
14614
  const body = this.encode({
14074
14615
  op: "route.open",
14616
+ busy_refusals: true,
14075
14617
  target,
14076
14618
  identity,
14077
14619
  ...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
14078
14620
  ...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {},
14079
- ...scope ? { scope: { owner: scope.owner, ref: scope.ref, scope_epoch: scope.scopeEpoch } } : {}
14621
+ ...scope ? { scope: {
14622
+ owner: scope.owner,
14623
+ ref: scope.ref,
14624
+ scope_epoch: scope.scopeEpoch,
14625
+ ...scope.bindProof !== undefined ? { bind_proof: scope.bindProof } : {}
14626
+ } } : {}
14080
14627
  });
14081
14628
  let installed = null;
14082
14629
  const install = (frame) => {
@@ -14087,6 +14634,8 @@ class SubcClient {
14087
14634
  throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
14088
14635
  }
14089
14636
  installed = this.installRoute(parsed.route_channel, parsed.route_epoch, reverseRequests);
14637
+ if (parsed.request_window !== undefined)
14638
+ this.gates.set(installed, new RequestGate(parsed.request_window, parsed.watch_window));
14090
14639
  this.routeModules.set(parsed.route_channel, target.module_id);
14091
14640
  return true;
14092
14641
  };
@@ -14116,7 +14665,7 @@ class SubcClient {
14116
14665
  const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
14117
14666
  const priority = opts.priority ?? Priority.Interactive;
14118
14667
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
14119
- const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary, opts.signal);
14668
+ const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary, opts.signal, validatedSleepGraceMs(opts.sleepGraceMs ?? this.opts.sleepGraceMs ?? DEFAULT_SLEEP_GRACE_MS));
14120
14669
  return this.decodeReply(reply);
14121
14670
  }
14122
14671
  async call(moduleId, method, params, opts = {}) {
@@ -14132,10 +14681,12 @@ class SubcClient {
14132
14681
  }
14133
14682
  async managedCall(moduleId, body, opts) {
14134
14683
  let retriedUnknownChannel = false;
14684
+ const startedAt = opts.timeoutMs === undefined ? undefined : this.opts.now();
14685
+ let attemptOpts = opts;
14135
14686
  for (;; ) {
14136
- const routeHandle = await this.cachedRouteHandle(moduleId, opts);
14687
+ const routeHandle = await this.cachedRouteHandle(moduleId, attemptOpts);
14137
14688
  try {
14138
- return await this.managedRequest(routeHandle, body, opts);
14689
+ return await this.managedRequest(routeHandle, body, attemptOpts);
14139
14690
  } catch (err) {
14140
14691
  if (!(err instanceof SubcCallError))
14141
14692
  throw this.terminalCallError("managed call failed", err);
@@ -14145,6 +14696,15 @@ class SubcClient {
14145
14696
  this.evictRouteHandle(routeHandle);
14146
14697
  continue;
14147
14698
  }
14699
+ if (isDataPlaneDrainRefusal(err.code) && !retriedUnknownChannel && !this.closeStarted && !opts.signal?.aborted) {
14700
+ const remainingMs = opts.timeoutMs === undefined || startedAt === undefined ? undefined : opts.timeoutMs - (this.opts.now() - startedAt);
14701
+ if (remainingMs === undefined || remainingMs > 0) {
14702
+ retriedUnknownChannel = true;
14703
+ this.evictRouteHandle(routeHandle);
14704
+ attemptOpts = remainingMs === undefined ? opts : { ...opts, timeoutMs: remainingMs };
14705
+ continue;
14706
+ }
14707
+ }
14148
14708
  if (deadBindCode && retriedUnknownChannel) {
14149
14709
  this.evictRouteHandle(routeHandle);
14150
14710
  }
@@ -14174,6 +14734,8 @@ class SubcClient {
14174
14734
  }
14175
14735
  subscribe(handle, body, onEvent, opts = {}) {
14176
14736
  this.assertLiveHandle(handle);
14737
+ if (this.gates.has(handle))
14738
+ return this.subscribeGated(handle, body, onEvent, opts);
14177
14739
  const bytes = body instanceof Uint8Array ? body : this.encode(body);
14178
14740
  const priority = opts.priority ?? Priority.Interactive;
14179
14741
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
@@ -14181,22 +14743,43 @@ class SubcClient {
14181
14743
  const key = pendingKey(handle, corr);
14182
14744
  let subscriptionPending = null;
14183
14745
  let resolveClosed = null;
14746
+ let resolveEnded;
14747
+ const ended = new Promise((resolve) => {
14748
+ resolveEnded = resolve;
14749
+ });
14750
+ const endFailure = (err, frame) => {
14751
+ if (frame?.header.ty === FrameType.Error) {
14752
+ const error = err;
14753
+ resolveEnded({ kind: "error", code: error.code, message: error.message, detail: error.detail });
14754
+ } else if (err instanceof SubcError && err.closeReason && err.closeReason !== "connection_lost") {
14755
+ resolveEnded({ kind: "route_closed", reason: err.closeReason });
14756
+ } else {
14757
+ resolveEnded({ kind: "connection_lost" });
14758
+ }
14759
+ };
14184
14760
  const closed = new Promise((resolve, reject) => {
14185
14761
  if (this.closedErr) {
14762
+ endFailure(this.closedErr);
14186
14763
  reject(this.closedErr);
14187
14764
  return;
14188
14765
  }
14189
14766
  resolveClosed = resolve;
14190
14767
  subscriptionPending = {
14191
14768
  handle,
14192
- resolve: () => resolve(),
14193
- reject,
14769
+ resolve: (frame) => {
14770
+ resolveEnded(frame.header.ty === FrameType.Response ? { kind: "response", body: frame.body } : { kind: "stream_end" });
14771
+ resolve();
14772
+ },
14773
+ reject: (err, frame) => {
14774
+ endFailure(err, frame);
14775
+ reject(err);
14776
+ },
14194
14777
  onProgress: onEvent,
14195
14778
  timer: null,
14196
14779
  subscription: true
14197
14780
  };
14198
14781
  this.pending.set(key, subscriptionPending);
14199
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, bytes);
14782
+ const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission) | SUBSCRIPTION_FLAG, handle.channel, handle.epoch, corr, bytes);
14200
14783
  writeBorrowed(this.sock, encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch((err) => {
14201
14784
  const pending = this.pending.get(key);
14202
14785
  if (pending)
@@ -14209,11 +14792,14 @@ class SubcClient {
14209
14792
  return;
14210
14793
  cancelled = true;
14211
14794
  if (subscriptionPending && resolveClosed)
14212
- this.settle(key, subscriptionPending, resolveClosed);
14795
+ this.settle(key, subscriptionPending, () => {
14796
+ resolveEnded({ kind: "cancelled" });
14797
+ resolveClosed?.();
14798
+ });
14213
14799
  if (this.isLiveHandle(handle))
14214
14800
  this.cancel(handle, corr, priority);
14215
14801
  };
14216
- return { unsubscribe, closed };
14802
+ return { unsubscribe, closed, ended };
14217
14803
  }
14218
14804
  attachCancelSignal(signal, key, handle, corr, priority) {
14219
14805
  if (!handle)
@@ -14279,7 +14865,7 @@ class SubcClient {
14279
14865
  this.sendRouteGoodbye(handle);
14280
14866
  }
14281
14867
  async closeManagedRoute(target, identity, opts = {}) {
14282
- const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities(), validatedRouteScope(opts.scope));
14868
+ const key = routeCacheKey(target, identity, await routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities(), validatedRouteScope(opts.scope));
14283
14869
  const cached = this.routes.get(key);
14284
14870
  if (!cached)
14285
14871
  return;
@@ -14332,10 +14918,21 @@ class SubcClient {
14332
14918
  });
14333
14919
  }
14334
14920
  static async openConnection(opts) {
14335
- const conn = await readConnectionFile(opts.connectionFile);
14921
+ const conn = opts.transport === "local" ? await readConnectionFileWithLocalEndpoint(opts.connectionFile) : await readConnectionFile(opts.connectionFile);
14336
14922
  const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS);
14337
14923
  const endpoint = conn.endpoints[0];
14338
- const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
14924
+ let sock;
14925
+ if (opts.transport === "local") {
14926
+ const local = conn.localEndpoint;
14927
+ if (!local)
14928
+ throw new SubcError("connection file has no local_endpoint", "local_endpoint_absent");
14929
+ if (local.kind === "unix" !== (process.platform !== "win32")) {
14930
+ throw new SubcError("unsupported local endpoint kind on this platform", "local_endpoint_unsupported");
14931
+ }
14932
+ sock = await SubcSocket.connectLocal(local.kind === "unix" ? local.path : local.name, deadline);
14933
+ } else {
14934
+ sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
14935
+ }
14339
14936
  try {
14340
14937
  await authenticateClient(sock, conn, deadline);
14341
14938
  } catch (err) {
@@ -14347,7 +14944,125 @@ class SubcClient {
14347
14944
  async controlRpc(body, acceptFrame, onLateResponse) {
14348
14945
  return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
14349
14946
  }
14350
- send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false, signal) {
14947
+ send(...args) {
14948
+ const [handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary, signal, sleepGraceMs] = args;
14949
+ if (!handle || !this.gates.has(handle))
14950
+ return this.sendAttempt(...args);
14951
+ return this.gatedRequest(handle, false, timeoutMs, signal, (slot, remaining) => this.sendAttempt(handle, body, priority, admission, remaining, onProgress, acceptFrame, onLateResponse, binary, signal, sleepGraceMs, slot));
14952
+ }
14953
+ async gatedRequest(handle, watch, timeoutMs, signal, attempt) {
14954
+ const gate = this.gates.get(handle);
14955
+ const deadline = this.opts.now() + (timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
14956
+ let slot;
14957
+ try {
14958
+ slot = await gate.acquire(watch, deadline - this.opts.now(), signal);
14959
+ } catch (error) {
14960
+ throw this.notSentCallError("request was not sent while waiting for a route slot", error);
14961
+ }
14962
+ let delay = 25;
14963
+ let busyDeadlineError;
14964
+ try {
14965
+ for (;; ) {
14966
+ if (gate.signal.aborted)
14967
+ throw gate.signal.reason;
14968
+ if (signal?.aborted)
14969
+ throw this.notSentCallError("request cancelled before write", signal.reason);
14970
+ this.assertLiveHandle(handle);
14971
+ const budget = deadline - this.opts.now();
14972
+ if (budget < MIN_REQUEST_BUDGET_MS)
14973
+ throw busyDeadlineError ?? this.notSentCallError("request deadline elapsed before write", new Error("less than 1ms remains before the request deadline"));
14974
+ try {
14975
+ return await attempt(slot, budget);
14976
+ } catch (error) {
14977
+ const refusal = error instanceof SubcError ? error : error instanceof SubcCallError && error.cause instanceof SubcError ? error.cause : undefined;
14978
+ if (!refusal || !isRouteBusyRetryable(errorFrameFlags.get(refusal) ?? 0, refusal.code, this.gates.get(handle) === gate))
14979
+ throw error;
14980
+ const exhausted = () => new SubcCallError("not_sent", `daemon refused before forwarding: ${refusal.message}`, "route_busy", refusal);
14981
+ busyDeadlineError = exhausted();
14982
+ const remaining = deadline - this.opts.now();
14983
+ if (remaining <= 0 || signal?.aborted)
14984
+ throw exhausted();
14985
+ const ms = Math.min(remaining, delay * (0.5 + this.opts.random() * 0.5));
14986
+ delay = Math.min(1000, delay * 2);
14987
+ const proceed = await this.busySleep(ms, gate.signal, signal);
14988
+ if (gate.signal.aborted)
14989
+ throw gate.signal.reason;
14990
+ if (!proceed || this.opts.now() >= deadline)
14991
+ throw exhausted();
14992
+ }
14993
+ }
14994
+ } finally {
14995
+ if (!slot.inFlight)
14996
+ slot.release();
14997
+ }
14998
+ }
14999
+ async busySleep(ms, closed, signal) {
15000
+ if (closed.aborted || signal?.aborted)
15001
+ return false;
15002
+ let abort;
15003
+ const cancelled = new Promise((resolve) => {
15004
+ abort = () => resolve(false);
15005
+ });
15006
+ closed.addEventListener("abort", abort, { once: true });
15007
+ signal?.addEventListener("abort", abort, { once: true });
15008
+ try {
15009
+ return await Promise.race([this.opts.sleep(ms).then(() => true), cancelled]);
15010
+ } finally {
15011
+ closed.removeEventListener("abort", abort);
15012
+ signal?.removeEventListener("abort", abort);
15013
+ }
15014
+ }
15015
+ subscribeGated(handle, body, onEvent, opts) {
15016
+ const bytes = body instanceof Uint8Array ? body : this.encode(body);
15017
+ const priority = opts.priority ?? Priority.Interactive;
15018
+ const admission = opts.admissionClass ?? AdmissionClass.Normal;
15019
+ const abort = new AbortController;
15020
+ let current;
15021
+ let cancelled = false;
15022
+ let resolveClosed;
15023
+ let rejectClosed;
15024
+ let resolveEnded;
15025
+ const closed = new Promise((resolve, reject) => {
15026
+ resolveClosed = resolve;
15027
+ rejectClosed = reject;
15028
+ });
15029
+ const ended = new Promise((resolve) => {
15030
+ resolveEnded = resolve;
15031
+ });
15032
+ this.gatedRequest(handle, true, undefined, abort.signal, (slot, remaining) => this.sendAttempt(handle, bytes, priority, admission, remaining, onEvent, undefined, undefined, false, undefined, undefined, slot, true, (key, pending, corr) => {
15033
+ current = { key, pending, corr };
15034
+ })).then((frame) => {
15035
+ if (cancelled)
15036
+ return;
15037
+ resolveEnded(frame.header.ty === FrameType.Response ? { kind: "response", body: frame.body } : { kind: "stream_end" });
15038
+ resolveClosed();
15039
+ }, (error) => {
15040
+ if (cancelled)
15041
+ return;
15042
+ if (error instanceof SubcError && error.closeReason)
15043
+ resolveEnded(error.closeReason === "connection_lost" ? { kind: "connection_lost" } : { kind: "route_closed", reason: error.closeReason });
15044
+ else if (error instanceof SubcError || error instanceof SubcCallError)
15045
+ resolveEnded({ kind: "error", code: error.code, message: error.message, detail: error.detail });
15046
+ else
15047
+ resolveEnded({ kind: "connection_lost" });
15048
+ rejectClosed(error);
15049
+ });
15050
+ return { closed, ended, unsubscribe: () => {
15051
+ if (cancelled)
15052
+ return;
15053
+ cancelled = true;
15054
+ abort.abort();
15055
+ if (current && this.pending.get(current.key) === current.pending) {
15056
+ const { key, pending, corr } = current;
15057
+ this.settle(key, pending, () => pending.reject(new SubcError("subscription cancelled")));
15058
+ if (this.isLiveHandle(handle))
15059
+ this.cancel(handle, corr, priority);
15060
+ }
15061
+ resolveEnded({ kind: "cancelled" });
15062
+ resolveClosed();
15063
+ } };
15064
+ }
15065
+ sendAttempt(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false, signal, sleepGraceMs = this.opts.sleepGraceMs ?? DEFAULT_SLEEP_GRACE_MS, slot, subscription = false, registered) {
14351
15066
  if (handle)
14352
15067
  this.assertLiveHandle(handle);
14353
15068
  if (this.closedErr)
@@ -14361,7 +15076,7 @@ class SubcClient {
14361
15076
  const key = pendingKey(handle, corr);
14362
15077
  const channel = handle?.channel ?? 0;
14363
15078
  const epoch = handle?.epoch ?? 0;
14364
- const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), channel, epoch, corr, body);
15079
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission) | (subscription ? SUBSCRIPTION_FLAG : 0), channel, epoch, corr, body);
14365
15080
  return new Promise((resolve, reject) => {
14366
15081
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
14367
15082
  const pending = {
@@ -14371,11 +15086,27 @@ class SubcClient {
14371
15086
  onProgress,
14372
15087
  timer: null,
14373
15088
  acceptFrame,
14374
- onLateResponse
15089
+ onLateResponse,
15090
+ slot,
15091
+ subscription
14375
15092
  };
14376
- pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
15093
+ if (!subscription)
15094
+ this.armRequestTimeout(key, pending, channel, corr, ms, sleepGraceMs);
14377
15095
  this.pending.set(key, pending);
14378
- writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
15096
+ registered?.(key, pending, corr);
15097
+ if (slot && handle) {
15098
+ slot.inFlight = true;
15099
+ this.slots.set(key, { handle, slot });
15100
+ }
15101
+ const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
15102
+ if (slot && !write.queued) {
15103
+ this.slots.delete(key);
15104
+ slot.inFlight = false;
15105
+ slot.release();
15106
+ }
15107
+ write.completed.catch((error) => {
15108
+ if (error instanceof SocketWriteNotQueuedError || error instanceof SocketWriteQueuedError)
15109
+ error = error.cause ?? error;
14379
15110
  const current = this.pending.get(key);
14380
15111
  if (current)
14381
15112
  this.rejectPending(key, current, error instanceof Error ? error : new SubcError(String(error)));
@@ -14384,10 +15115,16 @@ class SubcClient {
14384
15115
  this.attachCancelSignal(signal, key, handle, corr, priority);
14385
15116
  });
14386
15117
  }
15118
+ armRequestTimeout(key, pending, channel, corr, ms, sleepGraceMs) {
15119
+ pending.sleepGrace = { wallStartMs: Date.now(), awakeStartMs: performance.now(), ms: sleepGraceMs, used: false };
15120
+ pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
15121
+ }
14387
15122
  arbitrateTimeout(key, pending, channel, corr, ms) {
14388
15123
  const settleAsTimeout = () => {
14389
15124
  if (pending.onLateResponse)
14390
15125
  this.lateResponses.set(key, pending.onLateResponse);
15126
+ if (pending.slot?.inFlight && pending.handle && this.isLiveHandle(pending.handle))
15127
+ this.cancel(pending.handle, corr);
14391
15128
  this.rejectPending(key, pending, new SubcError(this.timeoutMessage(channel, corr, ms), REQUEST_DEADLINE_MARKER));
14392
15129
  };
14393
15130
  const graceDeadline = Date.now() + this.opts.timeoutArbitrationGraceMs;
@@ -14399,6 +15136,17 @@ class SubcClient {
14399
15136
  setImmediate(arbitrate);
14400
15137
  return;
14401
15138
  }
15139
+ const sleep = pending.sleepGrace;
15140
+ if (sleep && !sleep.used && sleep.ms > 0) {
15141
+ const wallElapsed = Date.now() - sleep.wallStartMs;
15142
+ const awakeElapsed = performance.now() - sleep.awakeStartMs;
15143
+ const sleptMs = wallElapsed - awakeElapsed;
15144
+ if (sleptMs > SLEEP_DETECTION_THRESHOLD_MS) {
15145
+ sleep.used = true;
15146
+ pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), sleep.ms);
15147
+ return;
15148
+ }
15149
+ }
14402
15150
  settleAsTimeout();
14403
15151
  };
14404
15152
  setImmediate(arbitrate);
@@ -14409,7 +15157,7 @@ class SubcClient {
14409
15157
  const priority = opts.priority ?? Priority.Interactive;
14410
15158
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
14411
15159
  try {
14412
- const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary, opts.signal);
15160
+ const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary, opts.signal, validatedSleepGraceMs(opts.sleepGraceMs ?? this.opts.sleepGraceMs ?? DEFAULT_SLEEP_GRACE_MS));
14413
15161
  return this.decodeReply(reply);
14414
15162
  } catch (error) {
14415
15163
  if (error instanceof SubcCallError)
@@ -14417,7 +15165,13 @@ class SubcClient {
14417
15165
  throw this.terminalCallError("managed call failed", error);
14418
15166
  }
14419
15167
  }
14420
- sendManaged(handle, body, priority, admission, timeoutMs, onProgress, binary = false, signal) {
15168
+ sendManaged(...args) {
15169
+ const [handle, body, priority, admission, timeoutMs, onProgress, binary, signal, sleepGraceMs] = args;
15170
+ if (!this.gates.has(handle))
15171
+ return this.sendManagedAttempt(...args);
15172
+ return this.gatedRequest(handle, false, timeoutMs, signal, (slot, remaining) => this.sendManagedAttempt(handle, body, priority, admission, remaining, onProgress, binary, signal, sleepGraceMs, slot));
15173
+ }
15174
+ sendManagedAttempt(handle, body, priority, admission, timeoutMs, onProgress, binary = false, signal, sleepGraceMs = this.opts.sleepGraceMs ?? DEFAULT_SLEEP_GRACE_MS, slot) {
14421
15175
  try {
14422
15176
  this.assertLiveHandle(handle);
14423
15177
  } catch (error) {
@@ -14451,12 +15205,22 @@ class SubcClient {
14451
15205
  reject,
14452
15206
  onProgress,
14453
15207
  timer: null,
14454
- classifyFailure
15208
+ classifyFailure,
15209
+ slot
14455
15210
  };
14456
- pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
15211
+ this.armRequestTimeout(key, pending, handle.channel, corr, ms, sleepGraceMs);
14457
15212
  this.pending.set(key, pending);
15213
+ if (slot) {
15214
+ slot.inFlight = true;
15215
+ this.slots.set(key, { handle, slot });
15216
+ }
14458
15217
  const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
14459
15218
  handedToSocket = write.queued;
15219
+ if (slot && !write.queued) {
15220
+ this.slots.delete(key);
15221
+ slot.inFlight = false;
15222
+ slot.release();
15223
+ }
14460
15224
  write.completed.catch((error) => {
14461
15225
  const current = this.pending.get(key);
14462
15226
  if (current)
@@ -14473,7 +15237,7 @@ class SubcClient {
14473
15237
  throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
14474
15238
  }
14475
15239
  const target = { kind: opts.targetKind ?? this.opts.targetKind, module_id: moduleId };
14476
- const consumerIdentity = routeOpenConsumerIdentity(opts);
15240
+ const consumerIdentity = await routeOpenConsumerIdentity(opts);
14477
15241
  const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
14478
15242
  reverseRequests.seal();
14479
15243
  const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities(), scope);
@@ -14558,6 +15322,7 @@ class SubcClient {
14558
15322
  if ("handle" in outcome) {
14559
15323
  const { handle } = outcome;
14560
15324
  if (cached.closed) {
15325
+ this.failHandle(handle, new SubcError("route closed during route.open", "route_closed", undefined, "closed_by_caller"));
14561
15326
  this.liveRoutes.delete(handle.channel);
14562
15327
  this.sendRouteGoodbye(handle);
14563
15328
  throw this.routeClosedDuringOpen();
@@ -14681,6 +15446,9 @@ class SubcClient {
14681
15446
  return;
14682
15447
  } catch (err) {
14683
15448
  if (!isConsumerReconnectTransient(err) || attempt >= this.opts.reconnectBackoff.maxAttempts) {
15449
+ if (isConsumerReconnectTransient(err) && !(err instanceof AuthError)) {
15450
+ throw new DaemonUnavailableError(err);
15451
+ }
14684
15452
  if (err instanceof AuthError && attempt > 1) {
14685
15453
  throw new AuthError(`reconnect gave up after ${attempt} attempts: ${err.message} — the connection file and the daemon's key disagree persistently ` + `(daemon restarting in a loop, split connection-file paths, or a genuinely foreign daemon on this port); ` + `check the daemon, then restart this host app`);
14686
15454
  }
@@ -14803,6 +15571,16 @@ class SubcClient {
14803
15571
  }
14804
15572
  const key = pendingKey(handle, frame.header.corr);
14805
15573
  const pending = this.pending.get(key);
15574
+ if (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.Error || frame.header.ty === FrameType.StreamEnd) {
15575
+ const record = this.slots.get(key);
15576
+ if (record) {
15577
+ this.slots.delete(key);
15578
+ record.slot.inFlight = false;
15579
+ const busy = frame.header.ty === FrameType.Error && pending?.slot === record.slot && isRouteBusyRetryable(frame.header.flags, this.errorFromFrame(frame).code, !!handle && this.gates.has(handle));
15580
+ if (!busy)
15581
+ record.slot.release();
15582
+ }
15583
+ }
14806
15584
  if (pending) {
14807
15585
  if (pending.acceptFrame && !pending.acceptFrame(frame))
14808
15586
  return;
@@ -14818,7 +15596,7 @@ class SubcClient {
14818
15596
  this.settle(key, pending, () => pending.resolve(frame));
14819
15597
  return;
14820
15598
  case FrameType.Error:
14821
- this.settle(key, pending, () => pending.reject(this.errorFromFrame(frame)));
15599
+ this.settle(key, pending, () => pending.reject(this.errorFromFrame(frame), frame));
14822
15600
  return;
14823
15601
  default:
14824
15602
  return;
@@ -14955,6 +15733,14 @@ class SubcClient {
14955
15733
  return this.legacyChannelReasons.get(channel) ?? "unknown";
14956
15734
  }
14957
15735
  failHandle(handle, error) {
15736
+ this.gates.get(handle)?.close(error);
15737
+ this.gates.delete(handle);
15738
+ for (const [key, record] of this.slots) {
15739
+ if (sameRouteHandle(record.handle, handle)) {
15740
+ record.slot.release();
15741
+ this.slots.delete(key);
15742
+ }
15743
+ }
14958
15744
  for (const [key, pending] of this.pending) {
14959
15745
  if (pending.handle && sameRouteHandle(pending.handle, handle))
14960
15746
  this.rejectPending(key, pending, error);
@@ -14964,6 +15750,10 @@ class SubcClient {
14964
15750
  const routeError = err instanceof SubcError && err.closeReason ? err : new SubcError(err.message, errorCode(err), undefined, this.closeStarted ? "closed_by_caller" : "connection_lost");
14965
15751
  if (!this.closedErr)
14966
15752
  this.closedErr = err;
15753
+ for (const gate of this.gates.values())
15754
+ gate.close(routeError);
15755
+ this.gates.clear();
15756
+ this.slots.clear();
14967
15757
  this.routeEndReasons.clear();
14968
15758
  this.legacyChannelReasons.clear();
14969
15759
  this.routeModules.clear();
@@ -14997,6 +15787,9 @@ class SubcClient {
14997
15787
  return this.ingressEpochDropCount;
14998
15788
  }
14999
15789
  installRoute(channel, epoch, reverseRequests = new ReverseRequestRegistry) {
15790
+ const previous = this.liveRoutes.get(channel);
15791
+ if (previous && this.gates.has(previous))
15792
+ this.failHandle(previous, new SubcError("route replaced", "route_closed", undefined, "closed_by_caller"));
15000
15793
  const handle = createRouteHandle(channel, epoch, this.connectionToken, reverseRequests);
15001
15794
  this.routeEndReasons.delete(channel);
15002
15795
  this.legacyChannelReasons.delete(channel);
@@ -15067,9 +15860,15 @@ function isRetryableRouteOpenCode(code) {
15067
15860
  return false;
15068
15861
  if (code === "target_flow_unsupported")
15069
15862
  return false;
15863
+ if (code === "target_federated_unsupported")
15864
+ return false;
15070
15865
  if (code === "target_agent_run_unsupported")
15071
15866
  return false;
15072
- return code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout" || code === "scope_not_synced" || code === "scope_changed";
15867
+ if (code === "scope_bind_proof_required" || code === "scope_bind_proof_mismatch")
15868
+ return false;
15869
+ if (code === "scope_epoch_in_use")
15870
+ return false;
15871
+ return code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout" || code === "scope_not_synced" || code === "scope_changed" || code === "session_address_unavailable" || code === "bind_busy";
15073
15872
  }
15074
15873
  function equalJitter(delayMs, random) {
15075
15874
  const half = delayMs / 2;
@@ -15106,6 +15905,12 @@ var STALE_ROUTE_EPOCH = "stale_route_epoch";
15106
15905
  function isEstablishedRouteDead(_flags, code) {
15107
15906
  return code === UNKNOWN_CHANNEL || code === STALE_ROUTE_EPOCH;
15108
15907
  }
15908
+ function isRouteBusyRetryable(flags, code, routeOptedIn) {
15909
+ return routeOptedIn && (flags & DAEMON_ORIGIN_FLAG) !== 0 && code === "route_busy";
15910
+ }
15911
+ function isDataPlaneDrainRefusal(code) {
15912
+ return code === "module_reloading";
15913
+ }
15109
15914
  async function connectionFileExists(path) {
15110
15915
  try {
15111
15916
  await fs3.access(path);
@@ -15117,6 +15922,7 @@ async function connectionFileExists(path) {
15117
15922
  function normalizeConnectOptions(opts) {
15118
15923
  return {
15119
15924
  connectionFile: opts.connectionFile,
15925
+ transport: opts.transport ?? "tcp",
15120
15926
  handshakeTimeoutMs: opts.handshakeTimeoutMs,
15121
15927
  identity: opts.identity,
15122
15928
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
@@ -15126,11 +15932,18 @@ function normalizeConnectOptions(opts) {
15126
15932
  random: opts.random ?? Math.random,
15127
15933
  routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
15128
15934
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
15935
+ sleepGraceMs: validatedSleepGraceMs(opts.sleepGraceMs ?? DEFAULT_SLEEP_GRACE_MS),
15129
15936
  bodyReadTimeoutMs: opts.bodyReadTimeoutMs,
15130
15937
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
15131
15938
  onControlPush: opts.onControlPush
15132
15939
  };
15133
15940
  }
15941
+ function validatedSleepGraceMs(ms) {
15942
+ if (!Number.isFinite(ms) || ms < 0 || ms > 2147483647) {
15943
+ throw new RangeError("sleepGraceMs must be finite and in 0..2147483647 milliseconds");
15944
+ }
15945
+ return ms;
15946
+ }
15134
15947
  function isValidCapabilityIdentifier(identifier) {
15135
15948
  if (/\s/u.test(identifier))
15136
15949
  return false;
@@ -15153,7 +15966,13 @@ function isValidCapabilityIdentifier(identifier) {
15153
15966
  function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities, scope) {
15154
15967
  const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
15155
15968
  const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
15156
- const scopePart = scope ? JSON.stringify([scope.owner.kind, scope.owner.kind === "reserved" ? scope.owner.module_id : null, scope.ref, scope.scopeEpoch]) : "";
15969
+ const scopePart = scope ? JSON.stringify([
15970
+ scope.owner.kind,
15971
+ scope.owner.kind === "reserved" ? scope.owner.module_id : null,
15972
+ scope.ref,
15973
+ scope.scopeEpoch,
15974
+ scope.bindProof === undefined ? null : createHash4("sha256").update(scope.bindProof).digest("hex")
15975
+ ]) : "";
15157
15976
  return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}\x00${scopePart}`;
15158
15977
  }
15159
15978
  function validatedRouteScope(scope) {
@@ -15171,13 +15990,18 @@ function validatedRouteScope(scope) {
15171
15990
  if (!Number.isSafeInteger(scope.scopeEpoch) || scope.scopeEpoch < 0) {
15172
15991
  throw new SubcError("scope.scopeEpoch must be a safe non-negative integer", "invalid_scope");
15173
15992
  }
15174
- return { owner: { ...scope.owner }, ref: scope.ref, scopeEpoch: scope.scopeEpoch };
15993
+ return {
15994
+ owner: { ...scope.owner },
15995
+ ref: scope.ref,
15996
+ scopeEpoch: scope.scopeEpoch,
15997
+ ...scope.bindProof !== undefined ? { bindProof: scope.bindProof } : {}
15998
+ };
15175
15999
  }
15176
- function routeOpenConsumerIdentity(opts = {}) {
16000
+ async function routeOpenConsumerIdentity(opts = {}) {
15177
16001
  if (opts.consumerIdentity !== undefined)
15178
16002
  return opts.consumerIdentity ?? undefined;
15179
16003
  const moduleId = process.env[SUBC_MODULE_ID_ENV];
15180
- const launchNonce = launchNonceOrUndefined()?.value;
16004
+ const launchNonce = (await launchNonceOrUndefinedAsync())?.value;
15181
16005
  if (!moduleId || !launchNonce)
15182
16006
  return;
15183
16007
  return { module_id: moduleId, launch_nonce: launchNonce };
@@ -15629,20 +16453,20 @@ function getLifecycleRegistry() {
15629
16453
  }
15630
16454
 
15631
16455
  // ../aft-bridge/dist/project-identity.js
15632
- import { realpathSync as realpathSync2 } from "node:fs";
15633
- import { posix as posix2, resolve as resolve6, win32 as win322 } from "node:path";
16456
+ import { realpathSync as realpathSync3 } from "node:fs";
16457
+ import { posix as posix3, resolve as resolve6, win32 as win323 } from "node:path";
15634
16458
  function canonicalizeProjectRoot(dir) {
15635
16459
  const trimmed = trimProjectRootSeparators(dir);
15636
16460
  let canonical;
15637
16461
  try {
15638
- canonical = realpathSync2(trimmed);
16462
+ canonical = realpathSync3(trimmed);
15639
16463
  } catch {
15640
16464
  canonical = resolve6(trimmed);
15641
16465
  }
15642
16466
  return normalizeWindowsRoot(canonical);
15643
16467
  }
15644
16468
  function trimProjectRootSeparators(dir, platform = process.platform) {
15645
- const root = (platform === "win32" ? win322 : posix2).parse(dir).root;
16469
+ const root = (platform === "win32" ? win323 : posix3).parse(dir).root;
15646
16470
  const trimmed = dir.replace(/[/\\]+$/, "");
15647
16471
  return trimmed.length < root.length ? root : trimmed;
15648
16472
  }
@@ -16441,6 +17265,8 @@ class SubcTransportPool {
16441
17265
  harness;
16442
17266
  connectionFile;
16443
17267
  handshakeTimeoutMs;
17268
+ clock;
17269
+ waitClock;
16444
17270
  consumerIdentity;
16445
17271
  hostScopes;
16446
17272
  hostScopeCache = new Map;
@@ -16483,6 +17309,13 @@ class SubcTransportPool {
16483
17309
  editSlotSurvives;
16484
17310
  editSlotSurvivesCaptured = false;
16485
17311
  constructor(options) {
17312
+ this.clock = options.clock ?? {
17313
+ now: () => Date.now(),
17314
+ awakeNow: () => performance.now(),
17315
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
17316
+ clearTimeout: (timer) => clearTimeout(timer)
17317
+ };
17318
+ this.waitClock = new WaitClock(() => this.clock.now(), () => this.clock.awakeNow?.() ?? this.clock.now());
16486
17319
  this.connectionFile = options.connectionFile;
16487
17320
  this.harness = options.harness;
16488
17321
  this.handshakeTimeoutMs = options.handshakeTimeoutMs;
@@ -17109,11 +17942,12 @@ class SubcTransportPool {
17109
17942
  this.stopUnusedHostScopeRetry(record.identity.session);
17110
17943
  }
17111
17944
  async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
17112
- const callStartedAt = performance.now();
17945
+ const callStartedAt = this.waitClock.now();
17113
17946
  const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
17947
+ const deadline = new RequestDeadline(this.waitClock, callDeadlineMs);
17114
17948
  let scheduledDelayMs = 0;
17115
- const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
17116
- const remainingMs = () => callDeadlineMs - spentMs();
17949
+ const spentMs = () => Math.max(this.waitClock.now() - callStartedAt, scheduledDelayMs);
17950
+ const remainingMs = () => deadline.remaining() - Math.max(0, scheduledDelayMs - (this.waitClock.now() - callStartedAt));
17117
17951
  const root = asCanonicalRootPath(identity.project_root);
17118
17952
  let generation = expectedGeneration;
17119
17953
  if (this.lifecycleEnabled()) {
@@ -17134,12 +17968,12 @@ class SubcTransportPool {
17134
17968
  const remaining = remainingMs();
17135
17969
  if (remaining <= 0)
17136
17970
  throw keyChanged ?? callDeadlinePassedError(undefined, callDeadlineMs);
17137
- let timer;
17971
+ let cancelTimer;
17138
17972
  try {
17139
17973
  const acquired = await Promise.race([
17140
17974
  this.ensureClient(),
17141
17975
  new Promise((resolve) => {
17142
- timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
17976
+ cancelTimer = deadlineTimer(this.clock, remainingMs, () => resolve(CALL_DEADLINE_EXPIRED));
17143
17977
  })
17144
17978
  ]);
17145
17979
  if (acquired === CALL_DEADLINE_EXPIRED) {
@@ -17154,22 +17988,22 @@ class SubcTransportPool {
17154
17988
  if (spentMs() + this.nextRouteReopenDelayMs() >= callDeadlineMs)
17155
17989
  throw error;
17156
17990
  } finally {
17157
- clearTimeout(timer);
17991
+ cancelTimer?.();
17158
17992
  }
17159
17993
  const { delayMs, wait } = this.waitForRouteReopenBackoff();
17160
17994
  scheduledDelayMs = spentMs() + delayMs;
17161
- let retryTimer;
17995
+ let cancelRetryTimer;
17162
17996
  try {
17163
17997
  const waited = await Promise.race([
17164
17998
  wait,
17165
17999
  new Promise((resolve) => {
17166
- retryTimer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), callDeadlineMs - (performance.now() - callStartedAt));
18000
+ cancelRetryTimer = deadlineTimer(this.clock, remainingMs, () => resolve(CALL_DEADLINE_EXPIRED));
17167
18001
  })
17168
18002
  ]);
17169
18003
  if (waited === CALL_DEADLINE_EXPIRED)
17170
18004
  throw keyChanged;
17171
18005
  } finally {
17172
- clearTimeout(retryTimer);
18006
+ cancelRetryTimer?.();
17173
18007
  }
17174
18008
  }
17175
18009
  this.assertRecordLive(record);
@@ -17212,9 +18046,9 @@ class SubcTransportPool {
17212
18046
  const remaining = remainingMs();
17213
18047
  if (remaining <= 0)
17214
18048
  throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
17215
- let timer;
18049
+ let cancelTimer;
17216
18050
  const expired = new Promise((resolve) => {
17217
- timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
18051
+ cancelTimer = deadlineTimer(this.clock, remainingMs, () => resolve(CALL_DEADLINE_EXPIRED));
17218
18052
  });
17219
18053
  try {
17220
18054
  const opened = await Promise.race([openRoute(), expired]);
@@ -17223,7 +18057,7 @@ class SubcTransportPool {
17223
18057
  }
17224
18058
  return opened;
17225
18059
  } finally {
17226
- clearTimeout(timer);
18060
+ cancelTimer?.();
17227
18061
  }
17228
18062
  };
17229
18063
  const openRouteAfterReloadWindow = async () => {
@@ -17302,6 +18136,7 @@ class SubcTransportPool {
17302
18136
  try {
17303
18137
  request = client.request(route, body, {
17304
18138
  timeoutMs: Math.ceil(requestTimeoutMs),
18139
+ ...deadline.resumed ? { sleepGraceMs: 0 } : {},
17305
18140
  onProgress
17306
18141
  });
17307
18142
  } catch (error) {
@@ -17429,6 +18264,7 @@ class SubcTransportPool {
17429
18264
  this.connecting = this.connectFn({
17430
18265
  connectionFile: this.connectionFile,
17431
18266
  handshakeTimeoutMs: this.handshakeTimeoutMs,
18267
+ sleepGraceMs: RESUME_ALLOWANCE_MS,
17432
18268
  onControlPush: (push) => {
17433
18269
  if (owner && this.client === owner)
17434
18270
  this.onScopeRouteClose(push);
@@ -17617,7 +18453,9 @@ class SubcTransportPool {
17617
18453
  });
17618
18454
  retry = state.bindPending;
17619
18455
  } else {
17620
- retry = this.hostScope(client, record.identity);
18456
+ retry = this.hostScope(client, record.identity).finally(() => {
18457
+ this.scheduleHostScopeRetry(session, state);
18458
+ });
17621
18459
  }
17622
18460
  retry.catch((error) => {
17623
18461
  warn(`subc head scope: session=${session} background retry failed code=${error instanceof Error ? error.name : "Error"}`);
@@ -17785,12 +18623,18 @@ class SubcTransportPool {
17785
18623
  }
17786
18624
  return this.openScopedHostToolRoute(client, identity, record, entry, channel);
17787
18625
  }
17788
- bindHostToolRoute(client, identity, channel, reason, scope) {
18626
+ async bindHostToolRoute(client, identity, channel, reason, scope) {
17789
18627
  log(`subc head scope: session=${identity.session} bind=${scope ? "scoped" : "unscoped"} channel=${channel} root=${identity.project_root} reason=${reason}` + (scope ? ` ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}` : ""));
17790
- return client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
18628
+ const route = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
17791
18629
  consumerIdentity: this.consumerIdentity,
17792
18630
  ...scope ? { scope } : {}
17793
18631
  });
18632
+ const state = this.hostScopeCache.get(identity.session);
18633
+ if (scope && state?.unscopedReason && state.answer?.result === "scope" && state.answer.scope === scope) {
18634
+ log(`subc head scope: session=${identity.session} route=upgraded from=unscoped ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}`);
18635
+ state.unscopedReason = undefined;
18636
+ }
18637
+ return route;
17794
18638
  }
17795
18639
  async openScopedHostToolRoute(client, identity, record, entry, channel = "tool") {
17796
18640
  const decision = await this.hostScope(client, identity);
@@ -18422,7 +19266,8 @@ function taskIdFromError(error) {
18422
19266
  return;
18423
19267
  }
18424
19268
  // ../aft-bridge/dist/feature-config.js
18425
- import { createHash as createHash3 } from "node:crypto";
19269
+ import { createHash as createHash5 } from "node:crypto";
19270
+ import { createRequire } from "node:module";
18426
19271
  var CANONICAL_TOOLS = [
18427
19272
  "aft_callgraph",
18428
19273
  "aft_conflicts",
@@ -18472,23 +19317,115 @@ var REMOVED_OPENCODE_PATHS = [
18472
19317
  "opencode.server_url",
18473
19318
  "opencode.server_password_env"
18474
19319
  ];
18475
- function removedConfigKeyErrors(value) {
19320
+ var REMOVED_DATA_LAYER_PATHS = [
19321
+ "views.callgraph_copies",
19322
+ "views.enabled",
19323
+ "worktree.ram_overlay"
19324
+ ];
19325
+ var RETIRED_IDLE_CHECKOUT_HOURS = [
19326
+ "views.idle_checkout_hours",
19327
+ "idle.checkout_hours"
19328
+ ];
19329
+ var DATA_LAYER_KEYS_REFUSED_FROM = [0, 65];
19330
+ var DATA_LAYER_RETIRED_PATHS = ["views.idle_checkout_hours"];
19331
+ var IDLE_CHECKOUT_HOURS_DEFAULT = 168;
19332
+ var IDLE_CHECKOUT_HOURS_MIN = 1;
19333
+ var IDLE_CHECKOUT_HOURS_MAX = 24 * 365;
19334
+ function parseRelease(version) {
19335
+ const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(version);
19336
+ return match ? [Number(match[1]), Number(match[2])] : undefined;
19337
+ }
19338
+ var AFT_PACKAGE_NAMES = new Set([
19339
+ "@cortexkit/aft-bridge",
19340
+ "@cortexkit/aft-opencode",
19341
+ "@cortexkit/aft-pi",
19342
+ "@cortexkit/aft"
19343
+ ]);
19344
+ function readRunningRelease() {
19345
+ const req = createRequire(import.meta.url);
19346
+ for (const candidate of ["../package.json", "../../package.json", "../../../package.json"]) {
19347
+ try {
19348
+ const manifest = req(candidate);
19349
+ if (manifest.name && AFT_PACKAGE_NAMES.has(manifest.name) && manifest.version) {
19350
+ return parseRelease(manifest.version) ?? [0, 0];
19351
+ }
19352
+ } catch {}
19353
+ }
19354
+ return [0, 0];
19355
+ }
19356
+ var runningRelease;
19357
+ var runningReleaseOverrideForTest;
19358
+ function getRunningRelease() {
19359
+ if (runningReleaseOverrideForTest)
19360
+ return runningReleaseOverrideForTest;
19361
+ runningRelease ??= readRunningRelease();
19362
+ return runningRelease;
19363
+ }
19364
+ function releaseAtLeast(release, floor) {
19365
+ return release[0] !== floor[0] ? release[0] > floor[0] : release[1] >= floor[1];
19366
+ }
19367
+ function refusedFromVersion() {
19368
+ return `${DATA_LAYER_KEYS_REFUSED_FROM[0]}.${DATA_LAYER_KEYS_REFUSED_FROM[1]}.0`;
19369
+ }
19370
+ function removedConfigKeyErrors(value, release = getRunningRelease(), refuseRetired = true) {
18476
19371
  if (!isRecord2(value))
18477
19372
  return [];
18478
- const errors = removedOpencodeKeyErrors(value);
19373
+ const errors = removedBlockKeyErrors(value, release, refuseRetired);
18479
19374
  if (isRecord2(value.harnesses)) {
18480
19375
  for (const [name, block] of Object.entries(value.harnesses)) {
18481
19376
  if (!isRecord2(block))
18482
19377
  continue;
18483
- for (const error of removedOpencodeKeyErrors(block)) {
19378
+ for (const error of removedBlockKeyErrors(block, release, refuseRetired)) {
18484
19379
  errors.push(error.replace("removed_config_key:", `removed_config_key:harnesses.${name}.`));
18485
19380
  }
18486
19381
  }
18487
19382
  }
18488
19383
  return errors;
18489
19384
  }
18490
- function removedOpencodeKeyErrors(value) {
18491
- return REMOVED_OPENCODE_PATHS.filter((path) => hasPath(value, path)).map((path) => `removed_config_key:${path}; run aft doctor --fix`);
19385
+ function removedBlockKeyErrors(value, release, refuseRetired) {
19386
+ const paths = REMOVED_OPENCODE_PATHS.filter((path) => hasPath(value, path));
19387
+ if (refuseRetired)
19388
+ paths.push(...refusedRetiredNames(value));
19389
+ if (refuseRetired && releaseAtLeast(release, DATA_LAYER_KEYS_REFUSED_FROM))
19390
+ paths.push(...DATA_LAYER_RETIRED_PATHS.filter((path) => hasPath(value, path)));
19391
+ return paths.map((path) => `removed_config_key:${path}; run npx @cortexkit/aft doctor --fix`);
19392
+ }
19393
+ var REFUSED_RETIRED_PATHS = [
19394
+ "sandbox.enabled",
19395
+ "tool_surface",
19396
+ "hoist_builtin_tools",
19397
+ "enabled",
19398
+ "search_index",
19399
+ "experimental_search_index",
19400
+ "semantic_search",
19401
+ "experimental_semantic_search",
19402
+ "callgraph_store",
19403
+ "github.enabled",
19404
+ "gh_read",
19405
+ "gh_shim.enabled",
19406
+ "experimental_lsp_ty",
19407
+ "experimental_bash_rewrite",
19408
+ "experimental_bash_compress",
19409
+ "experimental_bash_background",
19410
+ "experimental.bash",
19411
+ "idle.lsp_ttl_minutes",
19412
+ "inspect.tier2_soft_deadline_ms",
19413
+ "inspect.max_drill_down_items",
19414
+ "views.callgraph_copies",
19415
+ "views.enabled",
19416
+ "worktree.ram_overlay"
19417
+ ];
19418
+ function refusedRetiredNames(block) {
19419
+ const names = REFUSED_RETIRED_PATHS.filter((path) => hasPath(block, path));
19420
+ if (Array.isArray(block.disabled_tools)) {
19421
+ for (const name of block.disabled_tools) {
19422
+ if (typeof name !== "string" || !Object.hasOwn(LEGACY_TOOL_ALIASES, name))
19423
+ continue;
19424
+ if (!names.includes(name))
19425
+ names.push(name);
19426
+ }
19427
+ }
19428
+ return names;
18492
19429
  }
18493
19430
  var RETIRED_PATHS = [
18494
19431
  ["sandbox.enabled", "sandbox.profile"],
@@ -18643,7 +19580,10 @@ function translateInspectLspPaths(map, prefix, out) {
18643
19580
  recordRetired(out, prefix, `inspect.${leaf}`);
18644
19581
  }
18645
19582
  }
18646
- var REMOVED_NO_EFFECT_PATHS = ["bridge.hang_threshold"];
19583
+ var REMOVED_NO_EFFECT_PATHS = [
19584
+ "bridge.hang_threshold",
19585
+ "inspect.tier2_idle_minutes"
19586
+ ];
18647
19587
  function dropNoEffectPaths(map, prefix, out) {
18648
19588
  for (const path of REMOVED_NO_EFFECT_PATHS) {
18649
19589
  const [container, leaf] = path.split(".");
@@ -18652,6 +19592,30 @@ function dropNoEffectPaths(map, prefix, out) {
18652
19592
  }
18653
19593
  }
18654
19594
  }
19595
+ function retiredIdleCheckoutHours(value) {
19596
+ const raw = typeof value === "number" && Number.isSafeInteger(value) ? value : IDLE_CHECKOUT_HOURS_DEFAULT;
19597
+ return Math.min(IDLE_CHECKOUT_HOURS_MAX, Math.max(IDLE_CHECKOUT_HOURS_MIN, raw));
19598
+ }
19599
+ function translateDataLayerPaths(map, prefix, out) {
19600
+ const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
19601
+ const hours = takeNested(map, "views", "idle_checkout_hours");
19602
+ if (hours.found) {
19603
+ recordRetired(out, prefix, retired);
19604
+ if (isRecord2(map.idle) && map.idle.checkout_hours !== undefined) {
19605
+ superseded(out, `${prefix}${retired}`, `${retired} is ignored because ${replacement} is set`);
19606
+ } else {
19607
+ if (!Object.hasOwn(map, "idle"))
19608
+ map.idle = {};
19609
+ if (isRecord2(map.idle))
19610
+ map.idle.checkout_hours = retiredIdleCheckoutHours(hours.value);
19611
+ }
19612
+ }
19613
+ for (const path of REMOVED_DATA_LAYER_PATHS) {
19614
+ const [container, leaf] = path.split(".");
19615
+ if (container && leaf && takeNested(map, container, leaf).found)
19616
+ recordRetired(out, prefix, path);
19617
+ }
19618
+ }
18655
19619
  function translateExperimentalPaths(map, prefix, out) {
18656
19620
  for (const [oldKey, leaf] of [
18657
19621
  ["experimental_lsp_ty", "lsp_ty"],
@@ -18684,23 +19648,25 @@ function translateExperimentalPaths(map, prefix, out) {
18684
19648
  const experimental = map.experimental;
18685
19649
  const legacy = experimental.bash;
18686
19650
  if (isRecord2(legacy)) {
18687
- if (!["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf)))
18688
- return;
19651
+ if (Object.keys(legacy).length === 0)
19652
+ recordRetired(out, prefix, "experimental.bash");
18689
19653
  for (const leaf of Object.keys(legacy))
18690
19654
  recordRetired(out, prefix, `experimental.bash.${leaf}`);
18691
19655
  if (Object.hasOwn(map, "bash")) {
18692
19656
  superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
18693
19657
  } else {
18694
- const bash = {
18695
- rewrite: legacy.rewrite === true,
18696
- compress: legacy.compress === true,
18697
- background: legacy.background === true
18698
- };
19658
+ const bash = {};
19659
+ if (["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf))) {
19660
+ bash.rewrite = legacy.rewrite === true;
19661
+ bash.compress = legacy.compress === true;
19662
+ bash.background = legacy.background === true;
19663
+ }
18699
19664
  for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
18700
19665
  if (Object.hasOwn(legacy, leaf))
18701
19666
  bash[leaf] = legacy[leaf];
18702
19667
  }
18703
- map.bash = bash;
19668
+ if (Object.keys(bash).length > 0)
19669
+ map.bash = bash;
18704
19670
  }
18705
19671
  } else {
18706
19672
  recordRetired(out, prefix, "experimental.bash");
@@ -18713,6 +19679,7 @@ function translateBlock(map, userBase, blockLabel, out) {
18713
19679
  const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
18714
19680
  translateGithubAliases(map, prefix, out);
18715
19681
  translateInspectLspPaths(map, prefix, out);
19682
+ translateDataLayerPaths(map, prefix, out);
18716
19683
  dropNoEffectPaths(map, prefix, out);
18717
19684
  translateExperimentalPaths(map, prefix, out);
18718
19685
  if (isRecord2(map.sandbox) && Object.hasOwn(map.sandbox, "enabled")) {
@@ -18751,8 +19718,6 @@ function translateBlock(map, userBase, blockLabel, out) {
18751
19718
  });
18752
19719
  }
18753
19720
  for (const [path] of RETIRED_PATHS) {
18754
- if (path === "experimental.bash")
18755
- continue;
18756
19721
  if (hasPath(map, path))
18757
19722
  recordRetired(out, prefix, path);
18758
19723
  }
@@ -18888,13 +19853,38 @@ var NO_EFFECT_RETIRED_NOTES = [
18888
19853
  "inspect.max_drill_down_items",
18889
19854
  "inspect.max_drill_down_items never had an effect, so the setting can be removed."
18890
19855
  ],
19856
+ [
19857
+ "inspect.tier2_idle_minutes",
19858
+ "inspect.tier2_idle_minutes no longer does anything: AFT runs whole-repository analyses only when aft_inspect is called, so the setting can be removed."
19859
+ ],
18891
19860
  [
18892
19861
  "inspect.tier2_soft_deadline_ms",
18893
19862
  "inspect.tier2_soft_deadline_ms never had an effect, so the setting can be removed."
18894
19863
  ]
18895
19864
  ];
18896
19865
  function noEffectNote(key) {
18897
- return NO_EFFECT_RETIRED_NOTES.find(([path]) => key === path || key.endsWith(`.${path}`))?.[1];
19866
+ return [...NO_EFFECT_RETIRED_NOTES, ...REMOVED_DATA_LAYER_NOTES].find(([path]) => namesPath(key, path))?.[1];
19867
+ }
19868
+ function namesPath(key, path) {
19869
+ return key === path || key.endsWith(`.${path}`);
19870
+ }
19871
+ var REMOVED_DATA_LAYER_NOTES = [
19872
+ [
19873
+ "views.callgraph_copies",
19874
+ "views.callgraph_copies is gone because no checkout borrows another checkout's call graph any more."
19875
+ ],
19876
+ [
19877
+ "views.enabled",
19878
+ "views.enabled is gone because AFT now keeps one index store per repository and always uses it."
19879
+ ],
19880
+ [
19881
+ "worktree.ram_overlay",
19882
+ "worktree.ram_overlay is gone because every worktree now has its own index state, so there is no borrowed index to patch in memory."
19883
+ ]
19884
+ ];
19885
+ function retiredIdleCheckoutHoursNote() {
19886
+ const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
19887
+ return `${retired} is now ${replacement}; from ${refusedFromVersion()} AFT refuses a user config file that still sets ${retired}.`;
18898
19888
  }
18899
19889
  function legacyConfigNoticeMessage(configPath, translation) {
18900
19890
  const equivalents = translation.retiredKeys.filter((key) => noEffectNote(key) === undefined);
@@ -18904,8 +19894,11 @@ function legacyConfigNoticeMessage(configPath, translation) {
18904
19894
  parts.push(`${configPath} uses retired keys (${equivalents.join(", ")}); AFT applied their current equivalents, with the same limits a project config has for those keys. Run \`npx @cortexkit/aft doctor --fix\` to update the file.`);
18905
19895
  if (translation.retiredEnabledFalse)
18906
19896
  parts.push(RETIRED_ENABLED_FALSE_INDEXES_NOTE);
19897
+ if (equivalents.some((key) => namesPath(key, RETIRED_IDLE_CHECKOUT_HOURS[0]))) {
19898
+ parts.push(retiredIdleCheckoutHoursNote());
19899
+ }
18907
19900
  }
18908
- for (const [, note] of NO_EFFECT_RETIRED_NOTES) {
19901
+ for (const [, note] of [...NO_EFFECT_RETIRED_NOTES, ...REMOVED_DATA_LAYER_NOTES]) {
18909
19902
  if (noEffect.has(note))
18910
19903
  parts.push(note);
18911
19904
  }
@@ -18961,7 +19954,12 @@ function validateResolvedConfig(value) {
18961
19954
  var ABSENT = { absent: true };
18962
19955
  function projectBlock(block) {
18963
19956
  const legacyPaths = {};
18964
- for (const path of [...RETIRED_PATHS.map(([retired]) => retired), ...REMOVED_NO_EFFECT_PATHS]) {
19957
+ for (const path of [
19958
+ ...RETIRED_PATHS.map(([retired]) => retired),
19959
+ ...REMOVED_NO_EFFECT_PATHS,
19960
+ ...DATA_LAYER_RETIRED_PATHS,
19961
+ ...REMOVED_DATA_LAYER_PATHS
19962
+ ]) {
18965
19963
  const dot = path.indexOf(".");
18966
19964
  if (dot < 0) {
18967
19965
  if (Object.hasOwn(block, path))
@@ -19040,7 +20038,7 @@ function canonicalJson(value) {
19040
20038
  return JSON.stringify(value);
19041
20039
  }
19042
20040
  function noticeDigest(projection) {
19043
- return createHash3("sha256").update(canonicalJson(projection), "utf8").digest("hex");
20041
+ return createHash5("sha256").update(canonicalJson(projection), "utf8").digest("hex");
19044
20042
  }
19045
20043
  var SEMANTIC_COST_NOTICE = "AFT indexes now default on; the local semantic backend may download an ONNX runtime and model and use CPU. Run npx @cortexkit/aft setup to change indexes.semantic.";
19046
20044
  var SEMANTIC_COST_NOTICE_DIGEST = noticeDigest({ notice: "semantic_default_cost_v1" });
@@ -19913,9 +20911,9 @@ async function createConcreteAftTransportPool(opts) {
19913
20911
  var LIFECYCLE_STATE = Symbol.for("@cortexkit/aft-bridge/location-lifecycle/v1");
19914
20912
  var RELEASE_LOCATION = Symbol.for("@cortexkit/aft-bridge/location-release/v1");
19915
20913
  // ../aft-bridge/dist/lsp-github-install.js
19916
- import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
19917
- import { closeSync as closeSync5, copyFileSync as copyFileSync3, createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync4, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, readlinkSync, readSync as readSync4, realpathSync as realpathSync3, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
19918
- import { dirname as dirname4, join as join11, relative as relative4, resolve as resolve7 } from "node:path";
20914
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
20915
+ import { closeSync as closeSync6, copyFileSync as copyFileSync3, createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync4, openSync as openSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, readlinkSync, readSync as readSync5, realpathSync as realpathSync4, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
20916
+ import { dirname as dirname4, join as join10, relative as relative3, resolve as resolve7 } from "node:path";
19919
20917
  import { Readable as Readable2 } from "node:stream";
19920
20918
  import { pipeline as pipeline2 } from "node:stream/promises";
19921
20919
 
@@ -20134,13 +21132,13 @@ function detectHostPlatform() {
20134
21132
 
20135
21133
  // ../aft-bridge/dist/tar-executable.js
20136
21134
  import { existsSync as existsSync5 } from "node:fs";
20137
- import { join as join10 } from "node:path";
21135
+ import { join as join9 } from "node:path";
20138
21136
  function windowsTarExecutable(options = {}) {
20139
21137
  const platform = options.platform ?? process.platform;
20140
21138
  if (platform !== "win32")
20141
21139
  return "tar";
20142
21140
  const env = options.env ?? process.env;
20143
- const systemTar = join10(env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
21141
+ const systemTar = join9(env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
20144
21142
  return (options.exists ?? existsSync5)(systemTar) ? systemTar : "tar";
20145
21143
  }
20146
21144
  function extractionCommand(args, privateStorage) {
@@ -20184,14 +21182,14 @@ function createGithubInstaller(services) {
20184
21182
  return getAftLspBinariesDir();
20185
21183
  }
20186
21184
  function ghPackageDir(spec) {
20187
- return join11(ghCacheRoot(), spec.id);
21185
+ return join10(ghCacheRoot(), spec.id);
20188
21186
  }
20189
21187
  function ghBinDir(spec) {
20190
21188
  const platform = detectHostPlatform()?.platform ?? "linux";
20191
21189
  return dirname4(ghBinaryPath(spec, platform));
20192
21190
  }
20193
21191
  function ghExtractDir(spec) {
20194
- return join11(ghPackageDir(spec), "extracted");
21192
+ return join10(ghPackageDir(spec), "extracted");
20195
21193
  }
20196
21194
  const INSTALLED_META_FILE = ".aft-installed";
20197
21195
  const INSTALL_FAILED_FILE = ".aft-install-failed.json";
@@ -20199,8 +21197,9 @@ function createGithubInstaller(services) {
20199
21197
  const INSTALL_LOCK_WAIT_MS = 10 * 60 * 1000;
20200
21198
  const INSTALL_LOCK_POLL_INTERVAL_MS = 1000;
20201
21199
  const INSTALL_LOCK_WAIT_TIMEOUT_REASON = "install lock held by another process for 10 minutes";
21200
+ const SMOKE_TIMEOUT_MS = 2000;
20202
21201
  function installFailureMarkerPath(spec) {
20203
- return join11(ghPackageDir(spec), INSTALL_FAILED_FILE);
21202
+ return join10(ghPackageDir(spec), INSTALL_FAILED_FILE);
20204
21203
  }
20205
21204
  function readInstallFailureMarker(spec) {
20206
21205
  try {
@@ -20244,15 +21243,15 @@ function createGithubInstaller(services) {
20244
21243
  const meta = readGithubInstalledMetaIn(ghPackageDir(spec));
20245
21244
  if (spec.layout === "distribution" && meta?.layout === "distribution" && isSafeVersion(meta.version)) {
20246
21245
  const arch = detectHostPlatform()?.arch ?? "x64";
20247
- return join11(ghExtractDir(spec), spec.binaryPathInArchive(platform, arch, stripTagV(meta.version)));
21246
+ return join10(ghExtractDir(spec), spec.binaryPathInArchive(platform, arch, stripTagV(meta.version)));
20248
21247
  }
20249
21248
  const ext = platform === "win32" ? ".exe" : "";
20250
- return join11(ghPackageDir(spec), "bin", `${spec.binary}${ext}`);
21249
+ return join10(ghPackageDir(spec), "bin", `${spec.binary}${ext}`);
20251
21250
  }
20252
21251
  function isGithubInstalled(spec, platform) {
20253
21252
  for (const candidate of ghBinaryCandidates(spec, platform)) {
20254
21253
  try {
20255
- if (statSync6(join11(ghBinDir(spec), candidate)).isFile())
21254
+ if (statSync6(join10(ghBinDir(spec), candidate)).isFile())
20256
21255
  return true;
20257
21256
  } catch {}
20258
21257
  }
@@ -20265,7 +21264,7 @@ function createGithubInstaller(services) {
20265
21264
  }
20266
21265
  function readGithubInstalledMetaIn(installDir) {
20267
21266
  try {
20268
- const path = join11(installDir, INSTALLED_META_FILE);
21267
+ const path = join10(installDir, INSTALLED_META_FILE);
20269
21268
  if (!statSync6(path).isFile())
20270
21269
  return null;
20271
21270
  const parsed = JSON.parse(readFileSync5(path, "utf8"));
@@ -20296,7 +21295,7 @@ function createGithubInstaller(services) {
20296
21295
  ...layout ? { layout } : {},
20297
21296
  ...smoke ? { smoke } : {}
20298
21297
  };
20299
- const path = join11(installDir, INSTALLED_META_FILE);
21298
+ const path = join10(installDir, INSTALLED_META_FILE);
20300
21299
  const tempPath = `${path}.tmp-${process.pid}-${randomBytes2(6).toString("hex")}`;
20301
21300
  try {
20302
21301
  writeFileSync2(tempPath, JSON.stringify(meta), { encoding: "utf8", flag: "wx" });
@@ -20312,7 +21311,7 @@ function createGithubInstaller(services) {
20312
21311
  const MAX_EXTRACT_BYTES = 1024 * 1024 * 1024;
20313
21312
  function sha256OfFile(path) {
20314
21313
  return new Promise((resolve, reject) => {
20315
- const hash = createHash4("sha256");
21314
+ const hash = createHash6("sha256");
20316
21315
  const stream = createReadStream2(path);
20317
21316
  stream.on("error", reject);
20318
21317
  stream.on("data", (chunk) => hash.update(chunk));
@@ -20320,17 +21319,17 @@ function createGithubInstaller(services) {
20320
21319
  });
20321
21320
  }
20322
21321
  function sha256OfFileSync(path) {
20323
- const hash = createHash4("sha256");
20324
- const fd = openSync4(path, "r");
21322
+ const hash = createHash6("sha256");
21323
+ const fd = openSync5(path, "r");
20325
21324
  try {
20326
21325
  const buffer = Buffer.allocUnsafe(1024 * 1024);
20327
- let read = readSync4(fd, buffer, 0, buffer.length, null);
21326
+ let read = readSync5(fd, buffer, 0, buffer.length, null);
20328
21327
  while (read > 0) {
20329
21328
  hash.update(buffer.subarray(0, read));
20330
- read = readSync4(fd, buffer, 0, buffer.length, null);
21329
+ read = readSync5(fd, buffer, 0, buffer.length, null);
20331
21330
  }
20332
21331
  } finally {
20333
- closeSync5(fd);
21332
+ closeSync6(fd);
20334
21333
  }
20335
21334
  return hash.digest("hex");
20336
21335
  }
@@ -20538,7 +21537,7 @@ function createGithubInstaller(services) {
20538
21537
  throw new Error(`too many redirects (>${maxRedirects})`);
20539
21538
  }
20540
21539
  function validateExtraction(stagingRoot) {
20541
- const realStagingRoot = realpathSync3(stagingRoot);
21540
+ const realStagingRoot = realpathSync4(stagingRoot);
20542
21541
  let totalBytes = 0;
20543
21542
  const walk = (dir) => {
20544
21543
  let entries;
@@ -20548,7 +21547,7 @@ function createGithubInstaller(services) {
20548
21547
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
20549
21548
  }
20550
21549
  for (const entry of entries) {
20551
- const full = join11(dir, entry);
21550
+ const full = join10(dir, entry);
20552
21551
  let lst;
20553
21552
  try {
20554
21553
  lst = lstatSync2(full);
@@ -20560,15 +21559,15 @@ function createGithubInstaller(services) {
20560
21559
  try {
20561
21560
  target = readlinkSync(full);
20562
21561
  } catch {}
20563
- throw new Error(`archive contains symlink ${relative4(realStagingRoot, full)} → ${target}; rejecting (zip-slip defense)`);
21562
+ throw new Error(`archive contains symlink ${relative3(realStagingRoot, full)} → ${target}; rejecting (zip-slip defense)`);
20564
21563
  }
20565
21564
  let realFull;
20566
21565
  try {
20567
- realFull = realpathSync3(full);
21566
+ realFull = realpathSync4(full);
20568
21567
  } catch (err) {
20569
21568
  throw new Error(`failed to realpath ${full}: ${err}`);
20570
21569
  }
20571
- const rel = relative4(realStagingRoot, realFull);
21570
+ const rel = relative3(realStagingRoot, realFull);
20572
21571
  if (relativePathEscapesRoot(rel) || resolve7(realStagingRoot, rel) !== realFull) {
20573
21572
  throw new Error(`archive entry escapes staging root: ${full} → ${realFull} (zip-slip defense)`);
20574
21573
  }
@@ -20709,7 +21708,7 @@ function createGithubInstaller(services) {
20709
21708
  }
20710
21709
  function quarantineCachedGithubInstall(spec, reason) {
20711
21710
  const packageDir = ghPackageDir(spec);
20712
- const dest = join11(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
21711
+ const dest = join10(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
20713
21712
  warn(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
20714
21713
  try {
20715
21714
  mkdirSync4(dirname4(dest), { recursive: true });
@@ -20719,7 +21718,7 @@ function createGithubInstaller(services) {
20719
21718
  warn(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
20720
21719
  }
20721
21720
  }
20722
- async function smokeGithubBinary(spec, binaryPath) {
21721
+ async function smokeGithubBinary(spec, binaryPath, timeoutMs = SMOKE_TIMEOUT_MS) {
20723
21722
  return new Promise((resolveSmoke) => {
20724
21723
  let child;
20725
21724
  let settled = false;
@@ -20739,8 +21738,8 @@ function createGithubInstaller(services) {
20739
21738
  const timer = setTimeout(() => {
20740
21739
  child?.kill("SIGKILL");
20741
21740
  child?.unref();
20742
- finish("timeout", "exceeded 2000 ms; keeping install");
20743
- }, 2000);
21741
+ finish("timeout", `exceeded ${timeoutMs} ms; keeping install`);
21742
+ }, timeoutMs);
20744
21743
  const capture = (chunk) => {
20745
21744
  const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
20746
21745
  const available = 64 * 1024 - output.length;
@@ -20764,19 +21763,19 @@ function createGithubInstaller(services) {
20764
21763
  function hasRequiredRuntime(spec, extractDir, version) {
20765
21764
  return (spec.requiredRuntimePaths?.(stripTagV(version)) ?? []).every(({ path, kind }) => {
20766
21765
  try {
20767
- const info = statSync6(join11(extractDir, path));
21766
+ const info = statSync6(join10(extractDir, path));
20768
21767
  return kind === "file" ? info.isFile() : info.isDirectory();
20769
21768
  } catch {
20770
21769
  return false;
20771
21770
  }
20772
21771
  });
20773
21772
  }
20774
- function validateCachedGithubInstall(spec, platform) {
21773
+ function validateCachedGithubInstall(spec, platform, smokeTimeoutMs = SMOKE_TIMEOUT_MS) {
20775
21774
  const lease = acquireInstallLock(spec.githubRepo);
20776
21775
  if (!lease)
20777
21776
  return null;
20778
21777
  try {
20779
- const validation = validateCachedGithubInstallUnderLock(spec, platform);
21778
+ const validation = validateCachedGithubInstallUnderLock(spec, platform, smokeTimeoutMs);
20780
21779
  if (typeof validation !== "boolean")
20781
21780
  return validation.finally(() => lease.release());
20782
21781
  lease.release();
@@ -20786,10 +21785,10 @@ function createGithubInstaller(services) {
20786
21785
  throw err;
20787
21786
  }
20788
21787
  }
20789
- function validateCachedGithubInstallUnderLock(spec, platform) {
21788
+ function validateCachedGithubInstallUnderLock(spec, platform, smokeTimeoutMs = SMOKE_TIMEOUT_MS) {
20790
21789
  const packageDir = ghPackageDir(spec);
20791
21790
  const meta = readGithubInstalledMetaIn(packageDir);
20792
- const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join11(ghBinDir(spec), candidate)).find((candidate) => {
21791
+ const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join10(ghBinDir(spec), candidate)).find((candidate) => {
20793
21792
  try {
20794
21793
  return statSync6(candidate).isFile();
20795
21794
  } catch {
@@ -20819,7 +21818,7 @@ function createGithubInstaller(services) {
20819
21818
  quarantineCachedGithubInstall(spec, "missing required runtime files");
20820
21819
  return false;
20821
21820
  }
20822
- const verdict = await smokeGithubBinary(spec, binaryPath);
21821
+ const verdict = await smokeGithubBinary(spec, binaryPath, smokeTimeoutMs);
20823
21822
  if (verdict === "failed") {
20824
21823
  quarantineCachedGithubInstall(spec, "cached binary failed runtime smoke check");
20825
21824
  return false;
@@ -20865,7 +21864,7 @@ function createGithubInstaller(services) {
20865
21864
  }
20866
21865
  const pkgDir = ghPackageDir(spec);
20867
21866
  const extractDir = ghExtractDir(spec);
20868
- const archivePath = join11(pkgDir, expected.name);
21867
+ const archivePath = join10(pkgDir, expected.name);
20869
21868
  log(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
20870
21869
  try {
20871
21870
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -20901,7 +21900,7 @@ function createGithubInstaller(services) {
20901
21900
  const binaryRelativePath = spec.binaryPathInArchive(platform, arch, version);
20902
21901
  try {
20903
21902
  await extractArchiveSafely(archivePath, extractDir, expected.archive, async (stagingDir) => {
20904
- const stagedBinary = join11(stagingDir, binaryRelativePath);
21903
+ const stagedBinary = join10(stagingDir, binaryRelativePath);
20905
21904
  if (!existsSync6(stagedBinary) || !statSync6(stagedBinary).isFile()) {
20906
21905
  throw new Error(`extracted binary not found at ${stagedBinary}`);
20907
21906
  }
@@ -20931,7 +21930,7 @@ function createGithubInstaller(services) {
20931
21930
  unlinkSync3(archivePath);
20932
21931
  } catch {}
20933
21932
  }
20934
- const innerBinaryPath = join11(extractDir, binaryRelativePath);
21933
+ const innerBinaryPath = join10(extractDir, binaryRelativePath);
20935
21934
  const targetBinary = spec.layout === "distribution" ? innerBinaryPath : ghBinaryPath(spec, platform);
20936
21935
  mkdirSync4(dirname4(targetBinary), { recursive: true });
20937
21936
  try {
@@ -21058,6 +22057,7 @@ function createGithubInstaller(services) {
21058
22057
  let installsStarted = 0;
21059
22058
  const lockWaitTimeoutMs = Math.max(0, options.__lockWaitTimeoutMsForTests ?? INSTALL_LOCK_WAIT_MS);
21060
22059
  const lockPollIntervalMs = Math.max(1, options.__lockPollIntervalMsForTests ?? INSTALL_LOCK_POLL_INTERVAL_MS);
22060
+ const smokeTimeoutMs = Math.max(1, options.__smokeTimeoutMsForTests ?? SMOKE_TIMEOUT_MS);
21061
22061
  function notifyCachedBinDirsChanged(directory) {
21062
22062
  try {
21063
22063
  options.onCachedBinDirsChanged?.(directory);
@@ -21070,7 +22070,7 @@ function createGithubInstaller(services) {
21070
22070
  while (true) {
21071
22071
  if (signal.aborted)
21072
22072
  return null;
21073
- const validation = await withInstallLock(spec.githubRepo, async () => validateCachedGithubInstallUnderLock(spec, platform));
22073
+ const validation = await withInstallLock(spec.githubRepo, async () => validateCachedGithubInstallUnderLock(spec, platform, smokeTimeoutMs));
21074
22074
  if (validation !== null)
21075
22075
  return validation;
21076
22076
  if (signal.aborted)
@@ -21099,7 +22099,7 @@ function createGithubInstaller(services) {
21099
22099
  }
21100
22100
  for (const spec of GITHUB_LSP_TABLE) {
21101
22101
  const controller = new AbortController;
21102
- const validation = isGithubInstalled(spec, host.platform) ? validateCachedGithubInstall(spec, host.platform) : false;
22102
+ const validation = isGithubInstalled(spec, host.platform) ? validateCachedGithubInstall(spec, host.platform, smokeTimeoutMs) : false;
21103
22103
  if (validation === true)
21104
22104
  cachedBinDirs.push(ghBinDir(spec));
21105
22105
  const cacheReady = (validation === null ? retryBusyCacheValidation(spec, host.platform, controller.signal) : Promise.resolve(validation)).then((healthy) => {
@@ -21154,7 +22154,7 @@ function createGithubInstaller(services) {
21154
22154
  const currentHost = detectHostPlatform();
21155
22155
  if (!currentHost)
21156
22156
  return [];
21157
- const directories = await Promise.all(GITHUB_LSP_TABLE.map(async (spec) => isGithubInstalled(spec, currentHost.platform) && await validateCachedGithubInstall(spec, currentHost.platform) ? ghBinDir(spec) : null));
22157
+ const directories = await Promise.all(GITHUB_LSP_TABLE.map(async (spec) => isGithubInstalled(spec, currentHost.platform) && await validateCachedGithubInstall(spec, currentHost.platform, smokeTimeoutMs) ? ghBinDir(spec) : null));
21158
22158
  return directories.filter((directory) => directory !== null);
21159
22159
  }
21160
22160
  };
@@ -21225,7 +22225,7 @@ import { stat as stat2 } from "node:fs/promises";
21225
22225
  // ../aft-bridge/dist/npm-resolver.js
21226
22226
  import { readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
21227
22227
  import { homedir as homedir9 } from "node:os";
21228
- import { dirname as dirname5, isAbsolute as isAbsolute5, join as join12 } from "node:path";
22228
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join11 } from "node:path";
21229
22229
  function defaultDeps() {
21230
22230
  return {
21231
22231
  platform: process.platform,
@@ -21252,16 +22252,16 @@ function npmFromPath(deps) {
21252
22252
  const separator = deps.platform === "win32" ? ";" : ":";
21253
22253
  for (const entry of raw.split(separator)) {
21254
22254
  const dir = entry.trim().replace(/^"|"$/g, "");
21255
- if (!dir || !isAbsolute5(dir))
22255
+ if (!dir || !isAbsolute4(dir))
21256
22256
  continue;
21257
- if (isFile(join12(dir, name)))
22257
+ if (isFile(join11(dir, name)))
21258
22258
  return dir;
21259
22259
  }
21260
22260
  return null;
21261
22261
  }
21262
22262
  function npmAdjacentToNode(deps) {
21263
22263
  const dir = dirname5(deps.execPath);
21264
- return isFile(join12(dir, npmBinaryName(deps.platform))) ? dir : null;
22264
+ return isFile(join11(dir, npmBinaryName(deps.platform))) ? dir : null;
21265
22265
  }
21266
22266
  function highestVersionedNodeBin(installsDir, name) {
21267
22267
  let entries;
@@ -21270,8 +22270,8 @@ function highestVersionedNodeBin(installsDir, name) {
21270
22270
  } catch {
21271
22271
  return null;
21272
22272
  }
21273
- const candidates = entries.filter((v) => isFile(join12(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
21274
- return candidates.length > 0 ? join12(installsDir, candidates[0], "bin") : null;
22273
+ const candidates = entries.filter((v) => isFile(join11(installsDir, v, "bin", name))).sort((a, b) => compareVersionsDesc(a, b));
22274
+ return candidates.length > 0 ? join11(installsDir, candidates[0], "bin") : null;
21275
22275
  }
21276
22276
  function compareVersionsDesc(a, b) {
21277
22277
  const pa = a.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
@@ -21296,22 +22296,22 @@ function wellKnownNpmDirs(deps) {
21296
22296
  const programFiles = env.ProgramFiles || "C:\\Program Files";
21297
22297
  const appData = env.APPDATA;
21298
22298
  const localAppData = env.LOCALAPPDATA;
21299
- push(join12(programFiles, "nodejs"));
22299
+ push(join11(programFiles, "nodejs"));
21300
22300
  if (appData)
21301
- push(join12(appData, "npm"));
22301
+ push(join11(appData, "npm"));
21302
22302
  if (localAppData)
21303
- push(join12(localAppData, "Volta", "bin"));
22303
+ push(join11(localAppData, "Volta", "bin"));
21304
22304
  if (env.NVM_SYMLINK)
21305
22305
  push(env.NVM_SYMLINK);
21306
22306
  } else {
21307
22307
  if (env.NVM_BIN)
21308
22308
  push(env.NVM_BIN);
21309
- push(highestVersionedNodeBin(join12(home, ".nvm", "versions", "node"), name));
21310
- push(highestVersionedNodeBin(join12(home, ".local", "share", "mise", "installs", "node"), name));
21311
- push(highestVersionedNodeBin(join12(home, ".asdf", "installs", "nodejs"), name));
21312
- push(join12(home, ".volta", "bin"));
21313
- push(join12(home, ".asdf", "shims"));
21314
- const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join12(home, ".local", "bin")]);
22309
+ push(highestVersionedNodeBin(join11(home, ".nvm", "versions", "node"), name));
22310
+ push(highestVersionedNodeBin(join11(home, ".local", "share", "mise", "installs", "node"), name));
22311
+ push(highestVersionedNodeBin(join11(home, ".asdf", "installs", "nodejs"), name));
22312
+ push(join11(home, ".volta", "bin"));
22313
+ push(join11(home, ".asdf", "shims"));
22314
+ const systemDirs = deps.systemNpmDirs ?? (platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/local/bin", "/usr/bin", join11(home, ".local", "bin")]);
21315
22315
  for (const dir of systemDirs)
21316
22316
  push(dir);
21317
22317
  }
@@ -21321,17 +22321,32 @@ function resolveNpm(deps = defaultDeps()) {
21321
22321
  const name = npmBinaryName(deps.platform);
21322
22322
  const onPath = npmFromPath(deps);
21323
22323
  if (onPath)
21324
- return { command: join12(onPath, name), binDir: onPath };
22324
+ return { command: join11(onPath, name), binDir: onPath };
21325
22325
  const adjacent = npmAdjacentToNode(deps);
21326
22326
  if (adjacent)
21327
- return { command: join12(adjacent, name), binDir: adjacent };
22327
+ return { command: join11(adjacent, name), binDir: adjacent };
21328
22328
  for (const dir of wellKnownNpmDirs(deps)) {
21329
- const candidate = join12(dir, name);
22329
+ const candidate = join11(dir, name);
21330
22330
  if (isFile(candidate))
21331
22331
  return { command: candidate, binDir: dir };
21332
22332
  }
21333
22333
  return null;
21334
22334
  }
22335
+ function npmCandidates(deps = defaultDeps()) {
22336
+ const name = npmBinaryName(deps.platform);
22337
+ const dirs = [
22338
+ ...(deps.env.PATH ?? "").split(deps.platform === "win32" ? ";" : ":").filter(isAbsolute4),
22339
+ dirname5(deps.execPath),
22340
+ ...wellKnownNpmDirs(deps)
22341
+ ];
22342
+ return [...new Set(dirs)].flatMap((binDir) => {
22343
+ return [
22344
+ join11(binDir, name),
22345
+ join11(binDir, "node_modules", "npm", "bin", "npm-cli.js"),
22346
+ join11(binDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
22347
+ ].filter(isFile).map((command) => ({ command, binDir }));
22348
+ });
22349
+ }
21335
22350
  function quoteCmdArgument(value) {
21336
22351
  if (/[\0\r\n"%]/.test(value)) {
21337
22352
  throw new Error(`npm argument cannot be represented safely for cmd.exe: ${JSON.stringify(value)}`);
@@ -21339,6 +22354,8 @@ function quoteCmdArgument(value) {
21339
22354
  return `"${value}"`;
21340
22355
  }
21341
22356
  function npmInvocation(resolved, npmArgs, platform = process.platform, env = process.env) {
22357
+ if (resolved.cliPath)
22358
+ return { command: resolved.command, args: [resolved.cliPath, ...npmArgs] };
21342
22359
  if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(resolved.command)) {
21343
22360
  return { command: resolved.command, args: [...npmArgs] };
21344
22361
  }
@@ -21495,7 +22512,7 @@ function terminateNpmProcessTree(child, invocation, env = process.env, gracePeri
21495
22512
  };
21496
22513
  child.once("exit", onChildExit);
21497
22514
  const systemRoot = env.SystemRoot ?? env.SYSTEMROOT;
21498
- const taskkill = systemRoot ? join12(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
22515
+ const taskkill = systemRoot ? join11(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
21499
22516
  try {
21500
22517
  killer = spawn(taskkill, ["/pid", String(child.pid), "/t", "/f"], {
21501
22518
  stdio: "ignore",
@@ -21591,10 +22608,11 @@ function probeNpmCutoffSupport(npm, clock) {
21591
22608
  });
21592
22609
  }
21593
22610
  async function npmSupportsCutoff(npm, clock) {
21594
- const identity = await stat2(npm.command).then(({ size, mtimeMs }) => ({ size, mtimeMs })).catch(() => {
22611
+ const key = npm.cliPath ? `${npm.command}\x00${npm.cliPath}` : npm.command;
22612
+ const identity = await stat2(npm.cliPath ?? npm.command).then(({ size, mtimeMs }) => ({ size, mtimeMs })).catch(() => {
21595
22613
  return;
21596
22614
  });
21597
- const existing = npmCutoffSupport.get(npm.command);
22615
+ const existing = npmCutoffSupport.get(key);
21598
22616
  const sameIdentity = existing && existing.identity?.size === identity?.size && existing.identity?.mtimeMs === identity?.mtimeMs;
21599
22617
  if (existing && sameIdentity) {
21600
22618
  if (!existing.settled || identity !== undefined && existing.settled.outcome !== "transient" || clock.now() < existing.settled.retryAt)
@@ -21605,7 +22623,7 @@ async function npmSupportsCutoff(npm, clock) {
21605
22623
  return outcome;
21606
22624
  });
21607
22625
  const entry = { identity, pending };
21608
- npmCutoffSupport.set(npm.command, entry);
22626
+ npmCutoffSupport.set(key, entry);
21609
22627
  return pending;
21610
22628
  }
21611
22629
  function lspPublicationCutoff(graceDays, pinned, now = Date.now()) {
@@ -21623,6 +22641,79 @@ async function lspNpmInstallInvocation(npm, target, before, clock = SYSTEM_CLOCK
21623
22641
  target
21624
22642
  ]);
21625
22643
  }
22644
+ // ../aft-bridge/dist/lsp-npm-selection.js
22645
+ import { readFile, realpath, stat as stat3 } from "node:fs/promises";
22646
+ import { dirname as dirname6, join as join12, resolve as resolve8 } from "node:path";
22647
+ var LSP_NPM_REFUSAL = "AFT can't install language servers with the npm at <path> (<reason>). Put a real npm 7 or newer first on PATH; pinning a server version won't help.";
22648
+ function lspNpmRefusal(path, reason) {
22649
+ return LSP_NPM_REFUSAL.replace("<path>", path).replace("<reason>", reason);
22650
+ }
22651
+ async function genuineNpm(candidate) {
22652
+ let executable;
22653
+ try {
22654
+ executable = await realpath(candidate.command);
22655
+ } catch (error) {
22656
+ return { reason: `probe failed: ${String(error)}` };
22657
+ }
22658
+ const windowsShim = /\.(?:cmd|bat)$/i.test(executable);
22659
+ let current = dirname6(executable);
22660
+ if (windowsShim)
22661
+ current = join12(current, "node_modules", "npm");
22662
+ for (;; ) {
22663
+ try {
22664
+ const pkg = JSON.parse(await readFile(join12(current, "package.json"), "utf8"));
22665
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.npm;
22666
+ if (pkg.name === "npm" && typeof bin === "string") {
22667
+ const cli = await realpath(resolve8(current, bin));
22668
+ if (!windowsShim && cli !== executable)
22669
+ return { reason: "not npm: command is not npm's declared CLI" };
22670
+ const major = /^(\d+)\.\d+\.\d+$/.exec(pkg.version)?.[1];
22671
+ if (!major)
22672
+ return { reason: "probe failed: npm manifest has an unknown version" };
22673
+ if (Number(major) < 7)
22674
+ return { reason: `old npm: ${pkg.version} (requires npm 7 or newer)` };
22675
+ if (windowsShim || process.platform === "win32" && /\.[cm]?js$/i.test(cli)) {
22676
+ const node = join12(candidate.binDir ?? dirname6(executable), "node.exe");
22677
+ if (!(await stat3(node)).isFile())
22678
+ return { reason: "probe failed: adjacent Node is missing" };
22679
+ return { npm: { command: node, binDir: dirname6(node), cliPath: cli }, reason: "" };
22680
+ }
22681
+ return { npm: { command: cli, binDir: candidate.binDir }, reason: "" };
22682
+ }
22683
+ } catch {}
22684
+ const parent = dirname6(current);
22685
+ if (parent === current || windowsShim)
22686
+ break;
22687
+ current = parent;
22688
+ }
22689
+ return { reason: "not npm: no matching npm package manifest and CLI" };
22690
+ }
22691
+ async function selectLspNpm(preferred, candidates, signal) {
22692
+ const aborted = () => ({
22693
+ refusal: lspNpmRefusal(preferred.command, "probe failed: installation aborted")
22694
+ });
22695
+ if (signal?.aborted)
22696
+ return aborted();
22697
+ const first = await genuineNpm(preferred);
22698
+ if (signal?.aborted)
22699
+ return aborted();
22700
+ if (first.npm)
22701
+ return { npm: first.npm };
22702
+ const seen = new Set([preferred.command]);
22703
+ for (const candidate of candidates ?? npmCandidates()) {
22704
+ if (signal?.aborted)
22705
+ return aborted();
22706
+ if (seen.has(candidate.command))
22707
+ continue;
22708
+ seen.add(candidate.command);
22709
+ const result = await genuineNpm(candidate);
22710
+ if (signal?.aborted)
22711
+ return aborted();
22712
+ if (result.npm)
22713
+ return { npm: result.npm };
22714
+ }
22715
+ return { refusal: lspNpmRefusal(preferred.command, first.reason) };
22716
+ }
21626
22717
  // ../aft-bridge/dist/message-detach.js
21627
22718
  var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach";
21628
22719
  var STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
@@ -21672,14 +22763,14 @@ function shouldInterruptWaitsForMessage(detachOnUserMessage, operatorText) {
21672
22763
  return detachOnUserMessage || containsStandaloneDetachKeyword(operatorText);
21673
22764
  }
21674
22765
  // ../aft-bridge/dist/migration.js
21675
- import { closeSync as closeSync7, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync as openSync6, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync3, statSync as statSync8, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
22766
+ import { closeSync as closeSync8, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync as openSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync3, statSync as statSync8, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
21676
22767
  import { homedir as homedir12, tmpdir as tmpdir3 } from "node:os";
21677
- import { basename as basename2, dirname as dirname8, join as join15, resolve as resolve9 } from "node:path";
22768
+ import { basename as basename2, dirname as dirname9, join as join15, resolve as resolve10 } from "node:path";
21678
22769
 
21679
22770
  // ../aft-bridge/dist/paths.js
21680
- import { existsSync as existsSync7, readFileSync as readFileSync6, realpathSync as realpathSync4, renameSync as renameSync4, writeFileSync as writeFileSync3 } from "node:fs";
22771
+ import { existsSync as existsSync7, readFileSync as readFileSync6, realpathSync as realpathSync5, renameSync as renameSync4, writeFileSync as writeFileSync3 } from "node:fs";
21681
22772
  import { homedir as homedir10 } from "node:os";
21682
- import { dirname as dirname6, isAbsolute as isAbsolute6, join as join13, resolve as resolve8 } from "node:path";
22773
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join13, resolve as resolve9 } from "node:path";
21683
22774
  function homeDir3() {
21684
22775
  if (process.platform === "win32")
21685
22776
  return process.env.USERPROFILE || process.env.HOME || homedir10();
@@ -21687,14 +22778,14 @@ function homeDir3() {
21687
22778
  }
21688
22779
  function configHome() {
21689
22780
  const xdg = process.env.XDG_CONFIG_HOME;
21690
- if (xdg && isAbsolute6(xdg))
22781
+ if (xdg && isAbsolute5(xdg))
21691
22782
  return xdg;
21692
22783
  return join13(homeDir3(), ".config");
21693
22784
  }
21694
22785
  function legacyOpenCodeConfigDir() {
21695
22786
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
21696
22787
  if (envDir)
21697
- return resolve8(envDir);
22788
+ return resolve9(envDir);
21698
22789
  return join13(configHome(), "opencode");
21699
22790
  }
21700
22791
  function legacyPiAgentDir() {
@@ -21742,9 +22833,9 @@ function resolveLegacyAftConfigSources(projectDirectory) {
21742
22833
  ];
21743
22834
  const identity = (path) => {
21744
22835
  try {
21745
- return realpathSync4(path);
22836
+ return realpathSync5(path);
21746
22837
  } catch {
21747
- return resolve8(path);
22838
+ return resolve9(path);
21748
22839
  }
21749
22840
  };
21750
22841
  const protectedPaths = new Set([
@@ -21768,7 +22859,7 @@ function repairRootScopedStorageFile(storageRoot, harness, fileName) {
21768
22859
  if (existsSync7(harnessPath) || !existsSync7(rootPath))
21769
22860
  return harnessPath;
21770
22861
  try {
21771
- openPrivateStorageDir(storageRoot, dirname6(harnessPath));
22862
+ openPrivateStorageDir(storageRoot, dirname7(harnessPath));
21772
22863
  renameSync4(rootPath, harnessPath);
21773
22864
  } catch {}
21774
22865
  return harnessPath;
@@ -21789,7 +22880,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
21789
22880
  return false;
21790
22881
  if (!lastVersion) {
21791
22882
  try {
21792
- openPrivateStorageDir(storageRoot, dirname6(versionFile));
22883
+ openPrivateStorageDir(storageRoot, dirname7(versionFile));
21793
22884
  writeFileSync3(versionFile, currentVersion, { mode: PRIVATE_FILE_MODE });
21794
22885
  } catch {}
21795
22886
  return false;
@@ -21801,7 +22892,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
21801
22892
  return;
21802
22893
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
21803
22894
  try {
21804
- openPrivateStorageDir(storageRoot, dirname6(versionFile));
22895
+ openPrivateStorageDir(storageRoot, dirname7(versionFile));
21805
22896
  writeFileSync3(versionFile, currentVersion, { mode: PRIVATE_FILE_MODE });
21806
22897
  } catch {}
21807
22898
  }
@@ -21850,10 +22941,10 @@ function decodeFileUrl(target) {
21850
22941
  }
21851
22942
 
21852
22943
  // ../aft-bridge/dist/resolver.js
21853
- import { closeSync as closeSync6, existsSync as existsSync8, promises as fsPromises, openSync as openSync5, readFileSync as readFileSync7, readSync as readSync5 } from "node:fs";
21854
- import { createRequire } from "node:module";
22944
+ import { closeSync as closeSync7, existsSync as existsSync8, promises as fsPromises, openSync as openSync6, readFileSync as readFileSync7, readSync as readSync6 } from "node:fs";
22945
+ import { createRequire as createRequire2 } from "node:module";
21855
22946
  import { homedir as homedir11 } from "node:os";
21856
- import { delimiter, dirname as dirname7, isAbsolute as isAbsolute7, join as join14 } from "node:path";
22947
+ import { delimiter, dirname as dirname8, isAbsolute as isAbsolute6, join as join14 } from "node:path";
21857
22948
  var ensureBinaryForResolver = ensureBinary;
21858
22949
  var npmPlatformPackageReader = null;
21859
22950
  var cacheCopiesInFlight = new Map;
@@ -21927,7 +23018,7 @@ function pathCandidates(env, ext) {
21927
23018
  const seen = new Set;
21928
23019
  const candidates = [];
21929
23020
  for (const dir of rawPath.split(delimiter)) {
21930
- if (!dir || !isAbsolute7(dir))
23021
+ if (!dir || !isAbsolute6(dir))
21931
23022
  continue;
21932
23023
  const candidate = join14(dir, `aft${ext}`);
21933
23024
  if (seen.has(candidate))
@@ -21941,9 +23032,9 @@ function pathCandidates(env, ext) {
21941
23032
  function isNativeExecutable(binaryPath) {
21942
23033
  let fd = null;
21943
23034
  try {
21944
- fd = openSync5(binaryPath, "r");
23035
+ fd = openSync6(binaryPath, "r");
21945
23036
  const buf = Buffer.alloc(4);
21946
- const read = readSync5(fd, buf, 0, 4, 0);
23037
+ const read = readSync6(fd, buf, 0, 4, 0);
21947
23038
  if (read < 2)
21948
23039
  return false;
21949
23040
  const b0 = buf[0];
@@ -21964,7 +23055,7 @@ function isNativeExecutable(binaryPath) {
21964
23055
  } finally {
21965
23056
  if (fd !== null) {
21966
23057
  try {
21967
- closeSync6(fd);
23058
+ closeSync7(fd);
21968
23059
  } catch {}
21969
23060
  }
21970
23061
  }
@@ -21994,7 +23085,7 @@ function explicitBinaryOverride(env) {
21994
23085
  }
21995
23086
  function ownPackageVersion() {
21996
23087
  try {
21997
- const req = createRequire(import.meta.url);
23088
+ const req = createRequire2(import.meta.url);
21998
23089
  return req("../package.json").version;
21999
23090
  } catch {
22000
23091
  return null;
@@ -22004,10 +23095,10 @@ function npmPlatformPackage(ext) {
22004
23095
  if (npmPlatformPackageReader)
22005
23096
  return npmPlatformPackageReader(ext);
22006
23097
  try {
22007
- const req = createRequire(import.meta.url);
23098
+ const req = createRequire2(import.meta.url);
22008
23099
  const manifestPath = req.resolve(`@cortexkit/aft-${platformKey()}/package.json`);
22009
23100
  const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
22010
- const binaryPath = join14(dirname7(manifestPath), "bin", `aft${ext}`);
23101
+ const binaryPath = join14(dirname8(manifestPath), "bin", `aft${ext}`);
22011
23102
  if (typeof manifest.version !== "string" || !existsSync8(binaryPath))
22012
23103
  return null;
22013
23104
  return { binaryPath, version: normalizeBareVersion(manifest.version) };
@@ -22314,19 +23405,19 @@ function acquireConfigMigrationLock(lockDir) {
22314
23405
  }
22315
23406
  }
22316
23407
  function atomicCopyConfigFile(sourcePath, targetPath) {
22317
- mkdirSync5(dirname8(targetPath), { recursive: true });
22318
- const tmpPath = join15(dirname8(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
23408
+ mkdirSync5(dirname9(targetPath), { recursive: true });
23409
+ const tmpPath = join15(dirname9(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
22319
23410
  let fd = null;
22320
23411
  try {
22321
- fd = openSync6(tmpPath, "wx", 384);
23412
+ fd = openSync7(tmpPath, "wx", 384);
22322
23413
  writeFileSync4(fd, readFileSync8(sourcePath));
22323
- closeSync7(fd);
23414
+ closeSync8(fd);
22324
23415
  fd = null;
22325
23416
  renameSync5(tmpPath, targetPath);
22326
23417
  } catch (err) {
22327
23418
  if (fd !== null) {
22328
23419
  try {
22329
- closeSync7(fd);
23420
+ closeSync8(fd);
22330
23421
  } catch {}
22331
23422
  }
22332
23423
  try {
@@ -22336,19 +23427,19 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
22336
23427
  }
22337
23428
  }
22338
23429
  function atomicWriteConfigFile(targetPath, content) {
22339
- mkdirSync5(dirname8(targetPath), { recursive: true });
22340
- const tmpPath = join15(dirname8(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
23430
+ mkdirSync5(dirname9(targetPath), { recursive: true });
23431
+ const tmpPath = join15(dirname9(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
22341
23432
  let fd = null;
22342
23433
  try {
22343
- fd = openSync6(tmpPath, "wx", 384);
23434
+ fd = openSync7(tmpPath, "wx", 384);
22344
23435
  writeFileSync4(fd, content);
22345
- closeSync7(fd);
23436
+ closeSync8(fd);
22346
23437
  fd = null;
22347
23438
  renameSync5(tmpPath, targetPath);
22348
23439
  } catch (err) {
22349
23440
  if (fd !== null) {
22350
23441
  try {
22351
- closeSync7(fd);
23442
+ closeSync8(fd);
22352
23443
  } catch {}
22353
23444
  }
22354
23445
  try {
@@ -22456,13 +23547,13 @@ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
22456
23547
  }
22457
23548
  function migrateAftConfigFile(opts) {
22458
23549
  const warnings = [];
22459
- const resolvedTarget = resolve9(opts.targetPath);
22460
- const existingSources = opts.legacySources.filter((source) => existsSync9(source.path) && resolve9(source.path) !== resolvedTarget);
23550
+ const resolvedTarget = resolve10(opts.targetPath);
23551
+ const existingSources = opts.legacySources.filter((source) => existsSync9(source.path) && resolve10(source.path) !== resolvedTarget);
22461
23552
  const info = opts.logger?.info ?? opts.logger?.log;
22462
23553
  if (existingSources.length === 0) {
22463
23554
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
22464
23555
  }
22465
- mkdirSync5(dirname8(opts.targetPath), { recursive: true });
23556
+ mkdirSync5(dirname9(opts.targetPath), { recursive: true });
22466
23557
  const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
22467
23558
  try {
22468
23559
  const sources = existingSources.map((source) => ({
@@ -22521,11 +23612,11 @@ function spawnErrorLabel(error) {
22521
23612
  function migrationLogPath(newRoot, harness, logger) {
22522
23613
  const desired = join15(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
22523
23614
  try {
22524
- mkdirSync5(dirname8(desired), { recursive: true });
23615
+ mkdirSync5(dirname9(desired), { recursive: true });
22525
23616
  return desired;
22526
23617
  } catch (err) {
22527
23618
  const fallback = join15(tmpdir3(), `aft-migration-${harness}-${Date.now()}.jsonl`);
22528
- logger?.warn?.(`Failed to create AFT migration log directory ${dirname8(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
23619
+ logger?.warn?.(`Failed to create AFT migration log directory ${dirname9(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
22529
23620
  return fallback;
22530
23621
  }
22531
23622
  }
@@ -22584,13 +23675,13 @@ async function ensureStorageMigrated(opts) {
22584
23675
  // ../aft-bridge/dist/migration-notices.js
22585
23676
  import { readFileSync as readFileSync9, renameSync as renameSync6, rmdirSync, statSync as statSync9, writeFileSync as writeFileSync5 } from "node:fs";
22586
23677
  import { homedir as homedir13 } from "node:os";
22587
- import { dirname as dirname9, join as join16, resolve as resolve10 } from "node:path";
23678
+ import { dirname as dirname10, join as join16, resolve as resolve11 } from "node:path";
22588
23679
  function migrationNoticeStorePath(env = process.env) {
22589
23680
  const stateHome = env.XDG_STATE_HOME && env.XDG_STATE_HOME.length > 0 ? env.XDG_STATE_HOME : join16(homedir13(), ".local", "state");
22590
23681
  return join16(stateHome, "cortexkit", "aft", "migration-notices.json");
22591
23682
  }
22592
23683
  function noticeKey(configPath, digest) {
22593
- return `${resolve10(configPath)}|${MIGRATION_POLICY_ID}|${digest}`;
23684
+ return `${resolve11(configPath)}|${MIGRATION_POLICY_ID}|${digest}`;
22594
23685
  }
22595
23686
  function readRecords(storePath) {
22596
23687
  try {
@@ -22603,7 +23694,7 @@ function readRecords(storePath) {
22603
23694
  var LOCK_STALE_MS = 1e4;
22604
23695
  function withStoreLock(storePath, action) {
22605
23696
  const lockPath = `${storePath}.lock`;
22606
- openPrivateStorageDir(dirname9(storePath));
23697
+ openPrivateStorageDir(dirname10(storePath));
22607
23698
  const deadline = Date.now() + 2000;
22608
23699
  for (;; ) {
22609
23700
  try {
@@ -22657,7 +23748,7 @@ function deliverMigrationNoticeOnce(options) {
22657
23748
  }
22658
23749
  }
22659
23750
  // ../aft-bridge/dist/onnx-probe.js
22660
- import { closeSync as closeSync8, existsSync as existsSync10, openSync as openSync7, readSync as readSync6, realpathSync as realpathSync5 } from "node:fs";
23751
+ import { closeSync as closeSync9, existsSync as existsSync10, openSync as openSync8, readSync as readSync7, realpathSync as realpathSync6 } from "node:fs";
22661
23752
  var MH_MAGIC = 4277009102;
22662
23753
  var MH_MAGIC_64 = 4277009103;
22663
23754
  var FAT_MAGIC = 3405691582;
@@ -22670,7 +23761,7 @@ var MAX_HEADER_BYTES = 4 * 1024 * 1024;
22670
23761
  var UNCHECKED = { loadable: true, checked: false };
22671
23762
  function readAt(fd, offset, length) {
22672
23763
  const buffer = Buffer.alloc(length);
22673
- const read = readSync6(fd, buffer, 0, length, offset);
23764
+ const read = readSync7(fd, buffer, 0, length, offset);
22674
23765
  return buffer.subarray(0, read);
22675
23766
  }
22676
23767
  function cpuTypeForArch(arch) {
@@ -22744,7 +23835,7 @@ function isUncheckableDependency(path) {
22744
23835
  function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
22745
23836
  let fd;
22746
23837
  try {
22747
- fd = openSync7(realpathSync5(libPath), "r");
23838
+ fd = openSync8(realpathSync6(libPath), "r");
22748
23839
  } catch (err) {
22749
23840
  return {
22750
23841
  loadable: false,
@@ -22778,13 +23869,13 @@ function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
22778
23869
  } catch {
22779
23870
  return UNCHECKED;
22780
23871
  } finally {
22781
- closeSync8(fd);
23872
+ closeSync9(fd);
22782
23873
  }
22783
23874
  }
22784
23875
  // ../aft-bridge/dist/onnx-runtime.js
22785
23876
  import { randomBytes as randomBytes3 } from "node:crypto";
22786
- import { chmodSync as chmodSync2, closeSync as closeSync9, copyFileSync as copyFileSync4, createWriteStream as createWriteStream3, existsSync as existsSync11, lstatSync as lstatSync3, openSync as openSync8, readdirSync as readdirSync4, readFileSync as readFileSync10, readlinkSync as readlinkSync2, realpathSync as realpathSync6, renameSync as renameSync7, rmSync as rmSync4, statSync as statSync10, symlinkSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
22787
- import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute8, join as join17, relative as relative5, resolve as resolve11, win32 as win323 } from "node:path";
23877
+ import { chmodSync as chmodSync2, closeSync as closeSync10, copyFileSync as copyFileSync4, createWriteStream as createWriteStream3, existsSync as existsSync11, lstatSync as lstatSync3, openSync as openSync9, readdirSync as readdirSync4, readFileSync as readFileSync10, readlinkSync as readlinkSync2, realpathSync as realpathSync7, renameSync as renameSync7, rmSync as rmSync4, statSync as statSync10, symlinkSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
23878
+ import { basename as basename3, dirname as dirname11, isAbsolute as isAbsolute7, join as join17, relative as relative4, resolve as resolve12, win32 as win324 } from "node:path";
22788
23879
  import { Readable as Readable3 } from "node:stream";
22789
23880
  import { pipeline as pipeline3 } from "node:stream/promises";
22790
23881
  var ORT_VERSION = "1.24.4";
@@ -23023,14 +24114,14 @@ function parseOnnxVersionFromDirectoryPath(value) {
23023
24114
  return null;
23024
24115
  }
23025
24116
  function isPathInsideRoot(root, candidate) {
23026
- const rel = relative5(root, candidate);
23027
- return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute8(rel) && !win323.isAbsolute(rel);
24117
+ const rel = relative4(root, candidate);
24118
+ return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute7(rel) && !win324.isAbsolute(rel);
23028
24119
  }
23029
24120
  function detectOnnxVersion(libDir, libName) {
23030
24121
  try {
23031
24122
  const entries = readdirSync4(libDir);
23032
24123
  try {
23033
- const resolved = parseOnnxVersionFromPath(realpathSync6(join17(libDir, libName)));
24124
+ const resolved = parseOnnxVersionFromPath(realpathSync7(join17(libDir, libName)));
23034
24125
  if (resolved && resolved !== INVALID_ORT_VERSION)
23035
24126
  return resolved;
23036
24127
  } catch {}
@@ -23047,7 +24138,7 @@ function detectOnnxVersion(libDir, libName) {
23047
24138
  const base = join17(libDir, libName);
23048
24139
  if (existsSync11(base)) {
23049
24140
  try {
23050
- const real = realpathSync6(base);
24141
+ const real = realpathSync7(base);
23051
24142
  const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
23052
24143
  if (version)
23053
24144
  return version;
@@ -23084,18 +24175,18 @@ function pathEntriesForPlatform() {
23084
24175
  return pathEnvValue().split(delimiter).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
23085
24176
  if (!entry || entry === "." || entry.includes("\x00"))
23086
24177
  return false;
23087
- return isAbsolute8(entry) || win323.isAbsolute(entry);
24178
+ return isAbsolute7(entry) || win324.isAbsolute(entry);
23088
24179
  });
23089
24180
  }
23090
24181
  function isWindowsSystem32Directory(dir) {
23091
24182
  if (process.platform !== "win32")
23092
24183
  return false;
23093
- const normalizedDir = win323.resolve(dir).replace(/[\\/]+$/, "").toLowerCase();
24184
+ const normalizedDir = win324.resolve(dir).replace(/[\\/]+$/, "").toLowerCase();
23094
24185
  const windowsRoots = [process.env.SystemRoot, process.env.windir, "C:\\Windows"];
23095
24186
  return windowsRoots.some((root) => {
23096
24187
  if (!root)
23097
24188
  return false;
23098
- return normalizedDir === win323.resolve(root, "System32").replace(/[\\/]+$/, "").toLowerCase();
24189
+ return normalizedDir === win324.resolve(root, "System32").replace(/[\\/]+$/, "").toLowerCase();
23099
24190
  });
23100
24191
  }
23101
24192
  function directoryContainsLibrary(dir, libName) {
@@ -23155,7 +24246,7 @@ function findSystemOnnxRuntime(libName, searchPathsOverride) {
23155
24246
  const normalizeCase = process.platform === "win32" || process.platform === "darwin";
23156
24247
  const seen = new Set;
23157
24248
  const uniquePaths = searchPaths.filter((p) => {
23158
- let key = resolve11(p).replace(/[/\\]+$/, "");
24249
+ let key = resolve12(p).replace(/[/\\]+$/, "");
23159
24250
  if (normalizeCase)
23160
24251
  key = key.toLowerCase();
23161
24252
  if (seen.has(key))
@@ -23212,7 +24303,7 @@ async function downloadFileWithCap(url, destPath) {
23212
24303
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
23213
24304
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
23214
24305
  }
23215
- privateMkdirSync(dirname10(destPath));
24306
+ privateMkdirSync(dirname11(destPath));
23216
24307
  let bytesWritten = 0;
23217
24308
  const guard = new TransformStream({
23218
24309
  transform(chunk, transformController) {
@@ -23239,7 +24330,7 @@ async function downloadFileWithCap(url, destPath) {
23239
24330
  }
23240
24331
  }
23241
24332
  function validateExtractedTree(stagingRoot) {
23242
- const realRoot = realpathSync6(stagingRoot);
24333
+ const realRoot = realpathSync7(stagingRoot);
23243
24334
  let totalBytes = 0;
23244
24335
  const walk = (dir) => {
23245
24336
  const entries = readdirSync4(dir);
@@ -23248,15 +24339,15 @@ function validateExtractedTree(stagingRoot) {
23248
24339
  const lst = lstatSync3(fullPath);
23249
24340
  if (lst.isSymbolicLink()) {
23250
24341
  const linkTarget = readlinkSync2(fullPath);
23251
- const resolvedTarget = resolve11(dirname10(fullPath), linkTarget);
23252
- const rel = relative5(realRoot, resolvedTarget);
23253
- if (relativePathEscapesRoot(rel) || win323.isAbsolute(rel)) {
24342
+ const resolvedTarget = resolve12(dirname11(fullPath), linkTarget);
24343
+ const rel = relative4(realRoot, resolvedTarget);
24344
+ if (relativePathEscapesRoot(rel) || win324.isAbsolute(rel)) {
23254
24345
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
23255
24346
  }
23256
24347
  continue;
23257
24348
  }
23258
- const rel = relative5(realRoot, fullPath);
23259
- if (relativePathEscapesRoot(rel) || win323.isAbsolute(rel)) {
24349
+ const rel = relative4(realRoot, fullPath);
24350
+ if (relativePathEscapesRoot(rel) || win324.isAbsolute(rel)) {
23260
24351
  throw new Error(`extracted entry ${fullPath} escapes staging root`);
23261
24352
  }
23262
24353
  if (lst.isDirectory()) {
@@ -23388,14 +24479,14 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
23388
24479
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
23389
24480
  }
23390
24481
  }
23391
- const targetRoot = realpathSync6(targetDir);
24482
+ const targetRoot = realpathSync7(targetDir);
23392
24483
  for (const link of symlinks) {
23393
24484
  const dst = join17(targetDir, link.name);
23394
24485
  try {
23395
24486
  unlinkSync5(dst);
23396
24487
  } catch {}
23397
24488
  const dstForContainment = join17(targetRoot, link.name);
23398
- const resolvedTarget = resolve11(dirname10(dstForContainment), link.target);
24489
+ const resolvedTarget = resolve12(dirname11(dstForContainment), link.target);
23399
24490
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
23400
24491
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
23401
24492
  if (requiredLibs.has(link.name)) {
@@ -23473,13 +24564,13 @@ function readOnnxInstalledMeta(installDir) {
23473
24564
  function acquireLock(lockPath) {
23474
24565
  const tryClaim = () => {
23475
24566
  try {
23476
- const fd = openSync8(lockPath, "wx", PRIVATE_FILE_MODE);
24567
+ const fd = openSync9(lockPath, "wx", PRIVATE_FILE_MODE);
23477
24568
  try {
23478
24569
  writeFileSync6(fd, `${process.pid}
23479
24570
  ${new Date().toISOString()}
23480
24571
  `);
23481
24572
  } finally {
23482
- closeSync9(fd);
24573
+ closeSync10(fd);
23483
24574
  }
23484
24575
  return true;
23485
24576
  } catch (err) {
@@ -25308,7 +26399,7 @@ function isBgCompletion(value) {
25308
26399
  function formatCompletion(completion) {
25309
26400
  const status = formatStatus(completion);
25310
26401
  const duration = formatDuration(completion);
25311
- const header = `- task ${completion.task_id} (${status}${duration ? `, ${duration}` : ""})`;
26402
+ const header = formatUnknownPtyExit(completion.task_id, completion) ?? `- task ${completion.task_id} (${status}${duration ? `, ${duration}` : ""})`;
25312
26403
  const previewBlock = formatOutputPreview(completion);
25313
26404
  const descendantWarning = completion.live_descendants_summary ? ` ${completion.live_descendants_summary}` : "";
25314
26405
  const captureWarning = completion.output_incomplete || completion.status === "failed" && completion.status_reason ? ` [${completion.status_reason || "PTY output may be incomplete"}]` : "";
@@ -25375,9 +26466,9 @@ import {
25375
26466
  // package.json
25376
26467
  var package_default = {
25377
26468
  name: "@cortexkit/aft-pi",
25378
- version: "0.63.5",
26469
+ version: "0.65.0",
25379
26470
  type: "module",
25380
- description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
26471
+ description: "Pi coding agent extension for Agent Foundational Toolkit (AFT) — tree-sitter and LSP-powered code analysis",
25381
26472
  main: "dist/index.js",
25382
26473
  types: "dist/index.d.ts",
25383
26474
  license: "MIT",
@@ -25399,18 +26490,18 @@ var package_default = {
25399
26490
  "test:e2e": "bun test src/__tests__/e2e/"
25400
26491
  },
25401
26492
  dependencies: {
25402
- "@cortexkit/aft-bridge": "0.63.5",
26493
+ "@cortexkit/aft-bridge": "0.65.0",
25403
26494
  "comment-json": "^5.0.0",
25404
26495
  diff: "^8.0.4",
25405
26496
  zod: "^4.4.3"
25406
26497
  },
25407
26498
  optionalDependencies: {
25408
- "@cortexkit/aft-darwin-arm64": "0.63.5",
25409
- "@cortexkit/aft-darwin-x64": "0.63.5",
25410
- "@cortexkit/aft-linux-arm64": "0.63.5",
25411
- "@cortexkit/aft-linux-x64": "0.63.5",
25412
- "@cortexkit/aft-win32-arm64": "0.63.5",
25413
- "@cortexkit/aft-win32-x64": "0.63.5"
26499
+ "@cortexkit/aft-darwin-arm64": "0.65.0",
26500
+ "@cortexkit/aft-darwin-x64": "0.65.0",
26501
+ "@cortexkit/aft-linux-arm64": "0.65.0",
26502
+ "@cortexkit/aft-linux-x64": "0.65.0",
26503
+ "@cortexkit/aft-win32-arm64": "0.65.0",
26504
+ "@cortexkit/aft-win32-x64": "0.65.0"
25414
26505
  },
25415
26506
  devDependencies: {
25416
26507
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -25490,22 +26581,15 @@ function readStatusBar(value) {
25490
26581
  const disabled = Array.isArray(bar.disabled_categories) ? bar.disabled_categories.filter((key) => typeof key === "string") : [];
25491
26582
  const errors = readOptionalNumber(bar.errors);
25492
26583
  const warnings = readOptionalNumber(bar.warnings);
25493
- const deadCode = readOptionalNumber(bar.dead_code);
25494
- const unusedExports = readOptionalNumber(bar.unused_exports);
25495
- const duplicates = readOptionalNumber(bar.duplicates);
25496
26584
  const todos = readOptionalNumber(bar.todos);
25497
- if (errors === null && warnings === null && deadCode === null && unusedExports === null && duplicates === null && todos === null && disabled.length === 0) {
26585
+ if (errors === null && warnings === null && todos === null && disabled.length === 0) {
25498
26586
  return;
25499
26587
  }
25500
26588
  return {
25501
26589
  ...disabled.length > 0 ? { disabled_categories: disabled } : {},
25502
26590
  ...errors !== null ? { errors } : {},
25503
26591
  ...warnings !== null ? { warnings } : {},
25504
- ...deadCode !== null ? { dead_code: deadCode } : {},
25505
- ...unusedExports !== null ? { unused_exports: unusedExports } : {},
25506
- ...duplicates !== null ? { duplicates } : {},
25507
- ...todos !== null ? { todos } : {},
25508
- ...bar.tier2_stale === true ? { tier2_stale: true } : {}
26592
+ ...todos !== null ? { todos } : {}
25509
26593
  };
25510
26594
  }
25511
26595
  function formatFlag(enabled) {
@@ -25620,11 +26704,11 @@ function coerceAftStatus(response) {
25620
26704
  warm_entries: readNumber(symbolCache.warm_entries)
25621
26705
  },
25622
26706
  storage_dir: readNullableString(response.storage_dir),
25623
- checkpoints_total: readNumber(response.checkpoints_total),
26707
+ checkpoints_total: readOptionalNumber(response.checkpoints_total),
25624
26708
  session: {
25625
26709
  id: readString(session.id, "__default__"),
25626
26710
  tracked_files: readNumber(session.tracked_files),
25627
- checkpoints: readNumber(session.checkpoints)
26711
+ checkpoints: readOptionalNumber(session.checkpoints)
25628
26712
  },
25629
26713
  compression: readCompression(response.compression),
25630
26714
  status_bar: readStatusBar(response.status_bar ?? response.status_bar_values)
@@ -25661,13 +26745,10 @@ function formatStatusDialogMessage(status) {
25661
26745
  const rows = [
25662
26746
  ["errors", sb.errors],
25663
26747
  ["warnings", sb.warnings],
25664
- ["dead code", sb.dead_code],
25665
- ["unused exports", sb.unused_exports],
25666
- ["duplicates", sb.duplicates],
25667
26748
  ["todos", sb.todos]
25668
26749
  ].filter((row) => typeof row[1] === "number");
25669
26750
  if (rows.length > 0) {
25670
- lines.push("", `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`);
26751
+ lines.push("", "Code Health");
25671
26752
  for (const [label, value] of rows)
25672
26753
  lines.push(`- ${label}: ${formatCount(value)}`);
25673
26754
  }
@@ -25713,7 +26794,7 @@ import { Type } from "typebox";
25713
26794
  // src/config.ts
25714
26795
  import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
25715
26796
  import { homedir as homedir14 } from "node:os";
25716
- import { isAbsolute as isAbsolute9 } from "node:path";
26797
+ import { isAbsolute as isAbsolute8 } from "node:path";
25717
26798
  var import_comment_json = __toESM(require_src2(), 1);
25718
26799
 
25719
26800
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/core/core.js
@@ -30505,9 +31586,8 @@ function clampBashWatchSyncMaxMs(value) {
30505
31586
  }
30506
31587
  function resolveBashConfig(config) {
30507
31588
  const top = config.bash;
30508
- const legacy = config.experimental?.bash;
30509
- const reminderEnabled = (typeof top === "object" && top !== null ? top.long_running_reminder_enabled : undefined) ?? legacy?.long_running_reminder_enabled;
30510
- const reminderInterval = (typeof top === "object" && top !== null ? top.long_running_reminder_interval_ms : undefined) ?? legacy?.long_running_reminder_interval_ms;
31589
+ const reminderEnabled = typeof top === "object" && top !== null ? top.long_running_reminder_enabled : undefined;
31590
+ const reminderInterval = typeof top === "object" && top !== null ? top.long_running_reminder_interval_ms : undefined;
30511
31591
  const topDetachOnUserMessage = typeof top === "object" && top !== null ? top.detach_on_user_message ?? true : true;
30512
31592
  const rawForegroundWait = typeof top === "object" && top !== null ? top.foreground_wait_window_ms : undefined;
30513
31593
  const foregroundWaitWindowMs = Math.max(FOREGROUND_WAIT_WINDOW_MIN_MS, rawForegroundWait ?? FOREGROUND_WAIT_WINDOW_DEFAULT_MS);
@@ -30551,13 +31631,6 @@ function resolveBashConfig(config) {
30551
31631
  detach_on_user_message: topDetachOnUserMessage
30552
31632
  };
30553
31633
  }
30554
- const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
30555
- if (hasLegacyFeatureFlag) {
30556
- const rewrite = legacy.rewrite === true;
30557
- const compress = legacy.compress === true;
30558
- const background = legacy.background === true;
30559
- return { ...base, enabled: true, rewrite, compress, background };
30560
- }
30561
31634
  return { ...base, enabled: true, rewrite: true, compress: true, background: true };
30562
31635
  }
30563
31636
  var FormatterEnum = _enum([
@@ -30587,9 +31660,9 @@ var ConfigureWarningsDeliveryEnum = _enum(["toast", "log", "chat"]);
30587
31660
  var IndexKindSchema = _enum(["search", "semantic", "callgraph"]);
30588
31661
  function isSupportedAbsoluteIndexPath(path) {
30589
31662
  if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
30590
- return isAbsolute9(homedir14());
31663
+ return isAbsolute8(homedir14());
30591
31664
  }
30592
- return isAbsolute9(path);
31665
+ return isAbsolute8(path);
30593
31666
  }
30594
31667
  var IndexRootSchema = object({
30595
31668
  path: string2().refine(isSupportedAbsoluteIndexPath, {
@@ -30646,6 +31719,7 @@ var LspServerEntrySchema = object({
30646
31719
  initialization_options: unknown().optional()
30647
31720
  });
30648
31721
  var LspConfigSchema = object({
31722
+ max_servers: number2().int().nonnegative().optional(),
30649
31723
  idle_minutes: union([
30650
31724
  number2().int().transform((value) => Math.min(1440, Math.max(5, value))),
30651
31725
  literal("never")
@@ -30659,13 +31733,6 @@ var LspConfigSchema = object({
30659
31733
  versions: record(string2().trim().min(1), string2().trim().min(1)).optional()
30660
31734
  });
30661
31735
  var ExperimentalConfigSchema = object({
30662
- bash: object({
30663
- rewrite: boolean2().optional(),
30664
- compress: boolean2().optional(),
30665
- background: boolean2().optional(),
30666
- long_running_reminder_enabled: boolean2().optional(),
30667
- long_running_reminder_interval_ms: number2().int().positive().optional()
30668
- }).optional(),
30669
31736
  lsp_ty: boolean2().optional()
30670
31737
  });
30671
31738
  var BashEnvSchema = record(string2(), string2()).superRefine((env, context) => {
@@ -30736,7 +31803,7 @@ var SubcConfigSchema = object({
30736
31803
  connection_file: string2().optional()
30737
31804
  });
30738
31805
  var GhShimConfigSchema = object({
30739
- binary_path: string2().trim().refine(isAbsolute9, "gh_shim.binary_path must be absolute").optional()
31806
+ binary_path: string2().trim().refine(isAbsolute8, "gh_shim.binary_path must be absolute").optional()
30740
31807
  });
30741
31808
  var RemoteExecConfigSchema = object({
30742
31809
  enabled: boolean2().optional(),
@@ -30763,7 +31830,6 @@ var PiConfigSchema = object({
30763
31830
  var InspectConfigSchema = object({
30764
31831
  enabled: boolean2().optional(),
30765
31832
  diagnostics_timeout_ms: number2().int().positive().optional().transform((value) => value === undefined ? undefined : clampInspectDiagnosticsTimeoutMs(value)),
30766
- tier2_idle_minutes: number2().min(0).optional(),
30767
31833
  tier2_pass_timeout_ms: number2().int().positive().optional(),
30768
31834
  categories: object({
30769
31835
  diagnostics: boolean2().optional(),
@@ -30781,14 +31847,12 @@ var InspectConfigSchema = object({
30781
31847
  function clampIdleRootTtlMinutes(value) {
30782
31848
  return Math.min(30, Math.max(5, value));
30783
31849
  }
31850
+ function clampIdleCheckoutHours(value) {
31851
+ return Math.min(IDLE_CHECKOUT_HOURS_MAX, Math.max(IDLE_CHECKOUT_HOURS_MIN, value));
31852
+ }
30784
31853
  var IdleConfigSchema = object({
30785
- root_ttl_minutes: number2().int().optional().transform((value) => value === undefined ? undefined : clampIdleRootTtlMinutes(value))
30786
- });
30787
- var ViewsConfigSchema = object({
30788
- enabled: boolean2().optional()
30789
- });
30790
- var WorktreeConfigSchema = object({
30791
- ram_overlay: boolean2().optional()
31854
+ root_ttl_minutes: number2().int().optional().transform((value) => value === undefined ? undefined : clampIdleRootTtlMinutes(value)),
31855
+ checkout_hours: number2().int().optional().transform((value) => value === undefined ? undefined : clampIdleCheckoutHours(value))
30792
31856
  });
30793
31857
  var BackupConfigSchema = object({
30794
31858
  enabled: boolean2().optional(),
@@ -30810,12 +31874,10 @@ var AftConfigFieldsSchema = object({
30810
31874
  indexes: IndexesConfigSchema.optional(),
30811
31875
  index: IndexConfigSchema.optional(),
30812
31876
  storage: object({ cache_dir: string2().nullable().optional() }).optional(),
30813
- views: ViewsConfigSchema.optional(),
30814
31877
  callgraph_chunk_size: number2().optional(),
30815
31878
  inspect: InspectConfigSchema.optional(),
30816
31879
  idle: IdleConfigSchema.optional(),
30817
31880
  backup: BackupConfigSchema.optional(),
30818
- worktree: WorktreeConfigSchema.optional(),
30819
31881
  sandbox: SandboxConfigSchema.optional(),
30820
31882
  bash: BashConfigSchema.optional(),
30821
31883
  experimental: ExperimentalConfigSchema.optional(),
@@ -30863,6 +31925,11 @@ function applyActiveHarnessOverride(config, trusted) {
30863
31925
  delete merged.indexes;
30864
31926
  else
30865
31927
  merged.indexes = indexes;
31928
+ const idle = mergeIdleConfig(base.idle, override.idle);
31929
+ if (idle === undefined)
31930
+ delete merged.idle;
31931
+ else
31932
+ merged.idle = idle;
30866
31933
  merged.sandbox = mergeSandboxConfig(base.sandbox, override.sandbox, true);
30867
31934
  if (!trusted && override.sandbox?.write_allow !== undefined) {
30868
31935
  merged.sandbox = { ...merged.sandbox, write_allow: override.sandbox.write_allow };
@@ -30965,7 +32032,10 @@ function loadConfigFromPath(configPath, tier) {
30965
32032
  recordConfigParseFailure(configPath, errorMsg);
30966
32033
  throw new ConfigRejectedError(["invalid_config_document"], configPath);
30967
32034
  }
30968
- const removed = removedConfigKeyErrors(cleanConfig);
32035
+ const removed = removedConfigKeyErrors(cleanConfig, undefined, false);
32036
+ if (tier === "user") {
32037
+ removed.push(...removedConfigKeyErrors(cleanConfig).filter((error) => [...REMOVED_DATA_LAYER_PATHS, ...DATA_LAYER_RETIRED_PATHS].some((path) => error.endsWith(`${path}; run npx @cortexkit/aft doctor --fix`))));
32038
+ }
30969
32039
  if (removed.length) {
30970
32040
  throw new ConfigRejectedError(removed);
30971
32041
  }
@@ -30988,8 +32058,10 @@ function loadConfigFromPath(configPath, tier) {
30988
32058
  warn2(text);
30989
32059
  }
30990
32060
  }
30991
- if (translation.legacyInput && tier === "user") {
30992
- log2(`Config ${configPath} uses retired keys (${translation.retiredKeys.join(", ")}); applied their current equivalents in memory`);
32061
+ if (tier === "user") {
32062
+ if (translation.legacyInput) {
32063
+ log2(`Config ${configPath} uses retired keys (${translation.retiredKeys.join(", ")}); applied their current equivalents in memory`);
32064
+ }
30993
32065
  } else if (translation.legacyInput) {
30994
32066
  configLoadNotices.push({
30995
32067
  configPath,
@@ -31075,6 +32147,9 @@ function mergeSemanticConfig(base, override) {
31075
32147
  }
31076
32148
  function mergeLspConfig(base, override) {
31077
32149
  const projectSafe = {};
32150
+ if (override?.max_servers !== undefined && override.max_servers <= (base?.max_servers ?? 8)) {
32151
+ projectSafe.max_servers = override.max_servers;
32152
+ }
31078
32153
  if (override?.python !== undefined)
31079
32154
  projectSafe.python = override.python;
31080
32155
  if (override?.diagnostics_on_edit !== undefined) {
@@ -31095,14 +32170,12 @@ function mergeLspConfig(base, override) {
31095
32170
  return;
31096
32171
  return Object.fromEntries(Object.entries(lsp).filter(([, v]) => v !== undefined));
31097
32172
  }
31098
- function mergeWorktreeConfig(baseWorktree, overrideWorktree) {
31099
- if (baseWorktree === undefined && overrideWorktree === undefined)
31100
- return;
31101
- if (overrideWorktree === undefined)
31102
- return baseWorktree;
31103
- if (baseWorktree === undefined)
31104
- return overrideWorktree;
31105
- return { ...baseWorktree, ...overrideWorktree };
32173
+ function mergeIdleConfig(baseIdle, overrideIdle) {
32174
+ const merged = {
32175
+ root_ttl_minutes: overrideIdle?.root_ttl_minutes ?? baseIdle?.root_ttl_minutes,
32176
+ checkout_hours: overrideIdle?.checkout_hours ?? baseIdle?.checkout_hours
32177
+ };
32178
+ return merged.root_ttl_minutes === undefined && merged.checkout_hours === undefined ? undefined : merged;
31106
32179
  }
31107
32180
  function mergeInspectConfig(baseInspect, overrideInspect) {
31108
32181
  const diagnosticsTimeoutConfigured = baseInspect?.diagnostics_timeout_ms !== undefined || overrideInspect?.diagnostics_timeout_ms !== undefined;
@@ -31182,19 +32255,10 @@ function mergeBashConfig(baseBash, overrideBash, mode = "tier") {
31182
32255
  return merged;
31183
32256
  }
31184
32257
  function mergeExperimentalConfig(base, override) {
31185
- const bash = {
31186
- ...base?.bash,
31187
- ...override?.bash
31188
- };
31189
32258
  const experimental = {
31190
32259
  ...base,
31191
32260
  ...override
31192
32261
  };
31193
- if (Object.values(bash).some((value) => value !== undefined)) {
31194
- experimental.bash = bash;
31195
- } else {
31196
- delete experimental.bash;
31197
- }
31198
32262
  if (Object.values(experimental).every((value) => value === undefined))
31199
32263
  return;
31200
32264
  return Object.fromEntries(Object.entries(experimental).filter(([, value]) => value !== undefined));
@@ -31221,11 +32285,9 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
31221
32285
  "revision_reads",
31222
32286
  "validate_on_edit",
31223
32287
  "configure_warnings_delivery",
31224
- "views",
31225
32288
  "callgraph_chunk_size",
31226
32289
  "inspect",
31227
32290
  "idle",
31228
- "worktree",
31229
32291
  "git",
31230
32292
  "pi",
31231
32293
  "experimental",
@@ -31340,7 +32402,7 @@ function mergeConfigs(base, override) {
31340
32402
  bash.project_env = projectEnv;
31341
32403
  }
31342
32404
  const inspect = mergeInspectConfig(base.inspect, override.inspect);
31343
- const worktree = mergeWorktreeConfig(base.worktree, override.worktree);
32405
+ const idle = mergeIdleConfig(base.idle, override.idle);
31344
32406
  const sandbox = mergeSandboxConfig(base.sandbox, override.sandbox);
31345
32407
  const remoteExec = mergeRemoteExecConfig(base.remote_exec, override.remote_exec);
31346
32408
  const backup = mergeProjectBackupConfig(base.backup, override.backup);
@@ -31350,7 +32412,7 @@ function mergeConfigs(base, override) {
31350
32412
  delete safeOverride.indexes;
31351
32413
  delete safeOverride.bash;
31352
32414
  delete safeOverride.inspect;
31353
- delete safeOverride.worktree;
32415
+ delete safeOverride.idle;
31354
32416
  delete safeOverride.pi;
31355
32417
  return {
31356
32418
  ...base,
@@ -31360,7 +32422,7 @@ function mergeConfigs(base, override) {
31360
32422
  ...lsp ? { lsp } : {},
31361
32423
  ...bash !== undefined ? { bash } : {},
31362
32424
  ...inspect !== undefined ? { inspect } : {},
31363
- ...worktree !== undefined ? { worktree } : {},
32425
+ ...idle !== undefined ? { idle } : {},
31364
32426
  ...sandbox !== undefined ? { sandbox } : {},
31365
32427
  ...remoteExec !== undefined ? { remote_exec: remoteExec } : {},
31366
32428
  ...backup !== undefined ? { backup } : {},
@@ -31680,9 +32742,9 @@ function detectPiHarness(api) {
31680
32742
  }
31681
32743
 
31682
32744
  // src/omp-approval.ts
31683
- import { realpathSync as realpathSync7 } from "node:fs";
32745
+ import { realpathSync as realpathSync8 } from "node:fs";
31684
32746
  import { homedir as homedir15 } from "node:os";
31685
- import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute10, join as join18, relative as relative6, resolve as resolve12, sep as sep2 } from "node:path";
32747
+ import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute9, join as join18, relative as relative5, resolve as resolve13, sep as sep2 } from "node:path";
31686
32748
  var OMP_TOOL_TIERS = {
31687
32749
  read: "read",
31688
32750
  grep: "read",
@@ -31727,7 +32789,7 @@ function expandPath(value) {
31727
32789
  if (decoded === "~")
31728
32790
  return homedir15();
31729
32791
  if (decoded.startsWith("~/") || decoded.startsWith(`~${sep2}`))
31730
- return resolve12(homedir15(), decoded.slice(2));
32792
+ return resolve13(homedir15(), decoded.slice(2));
31731
32793
  return decoded;
31732
32794
  }
31733
32795
  function realLocation(path) {
@@ -31735,12 +32797,12 @@ function realLocation(path) {
31735
32797
  let current = path;
31736
32798
  for (;; ) {
31737
32799
  try {
31738
- return missing.length > 0 ? join18(realpathSync7(current), ...missing) : realpathSync7(current);
32800
+ return missing.length > 0 ? join18(realpathSync8(current), ...missing) : realpathSync8(current);
31739
32801
  } catch (error) {
31740
32802
  if (error.code !== "ENOENT")
31741
32803
  throw error;
31742
32804
  }
31743
- const parent = dirname11(current);
32805
+ const parent = dirname12(current);
31744
32806
  if (parent === current)
31745
32807
  return path;
31746
32808
  missing.unshift(basename4(current));
@@ -31755,11 +32817,11 @@ function namesOutsidePath(root, value) {
31755
32817
  const expanded = expandPath(value);
31756
32818
  if (expanded.startsWith("file:"))
31757
32819
  return false;
31758
- const absolute = isAbsolute10(expanded) ? expanded : resolve12(root, expanded);
31759
- if (relativePathEscapesRoot(relative6(root, absolute)))
32820
+ const absolute = isAbsolute9(expanded) ? expanded : resolve13(root, expanded);
32821
+ if (relativePathEscapesRoot(relative5(root, absolute)))
31760
32822
  return true;
31761
32823
  try {
31762
- return relativePathEscapesRoot(relative6(realLocation(root), realLocation(absolute)));
32824
+ return relativePathEscapesRoot(relative5(realLocation(root), realLocation(absolute)));
31763
32825
  } catch {
31764
32826
  return true;
31765
32827
  }
@@ -31791,11 +32853,10 @@ function unquote(value) {
31791
32853
  const first = value[0];
31792
32854
  return value.length >= 2 && (first === '"' || first === "'") && value.endsWith(first) ? value.slice(1, -1) : value;
31793
32855
  }
31794
- function hashlinePatchPaths(patch) {
32856
+ function hashlinePatchSections(patch) {
31795
32857
  if (patch.trim().length === 0)
31796
32858
  return;
31797
- const paths = [];
31798
- let sections = 0;
32859
+ const sections = [];
31799
32860
  const rows = patch.split(`
31800
32861
  `);
31801
32862
  for (let index = 0;index < rows.length; index++) {
@@ -31811,28 +32872,31 @@ function hashlinePatchPaths(patch) {
31811
32872
  const hash = inner.indexOf("#");
31812
32873
  if (hash <= 0)
31813
32874
  return;
31814
- paths.push(inner.slice(0, hash));
31815
- sections++;
32875
+ sections.push({ path: inner.slice(0, hash), moves: [] });
31816
32876
  continue;
31817
32877
  }
31818
32878
  const directive = /^(PUT|CUT|REM|MV)(?:\s|$)/.exec(line);
31819
32879
  if (directive) {
31820
- if (sections === 0)
32880
+ const current = sections[sections.length - 1];
32881
+ if (current === undefined)
31821
32882
  return;
31822
32883
  if (directive[1] === "MV") {
31823
32884
  const destination = line.slice(2).trim();
31824
32885
  if (destination.length === 0 || /\s/.test(destination))
31825
32886
  return;
31826
- paths.push(unquote(destination));
32887
+ current.moves.push(unquote(destination));
31827
32888
  }
31828
32889
  continue;
31829
32890
  }
31830
- if (trimmed.length === 0 && sections === 0)
32891
+ if (trimmed.length === 0 && sections.length === 0)
31831
32892
  continue;
31832
- if (sections === 0 || !line.startsWith("+"))
32893
+ if (sections.length === 0 || !line.startsWith("+"))
31833
32894
  return;
31834
32895
  }
31835
- return sections > 0 ? paths : undefined;
32896
+ return sections.length > 0 ? sections : undefined;
32897
+ }
32898
+ function hashlinePatchPaths(patch) {
32899
+ return hashlinePatchSections(patch)?.flatMap((section) => [section.path, ...section.moves]);
31836
32900
  }
31837
32901
  var APPLY_PATCH_TARGETS = [
31838
32902
  "*** Add File:",
@@ -32328,8 +33392,8 @@ function renderInner(s, error, theme, innerWidth) {
32328
33392
  left2.push(kv("symbol cache", `${formatCountShort(s.symbol_cache.local_entries)} local · ${formatCountShort(s.symbol_cache.warm_entries)} warm`, theme));
32329
33393
  right2.push(theme.fg("muted", "Current session"));
32330
33394
  right2.push(kv("tracked files", String(s.session.tracked_files), theme));
32331
- right2.push(kv("checkpoints", String(s.session.checkpoints), theme));
32332
- right2.push(kv("all-session", String(s.checkpoints_total), theme));
33395
+ right2.push(kv("checkpoints", s.session.checkpoints?.toString() ?? "—", theme));
33396
+ right2.push(kv("all-session", s.checkpoints_total?.toString() ?? "—", theme));
32333
33397
  for (const line of renderColumns(left2, right2, colWidth))
32334
33398
  lines.push(line);
32335
33399
  lines.push("");
@@ -32497,9 +33561,9 @@ import { join as join21 } from "node:path";
32497
33561
  // src/lsp-cache.ts
32498
33562
  import { randomUUID as randomUUID3 } from "node:crypto";
32499
33563
  import {
32500
- closeSync as closeSync10,
33564
+ closeSync as closeSync11,
32501
33565
  mkdirSync as mkdirSync6,
32502
- openSync as openSync9,
33566
+ openSync as openSync10,
32503
33567
  readFileSync as readFileSync12,
32504
33568
  renameSync as renameSync9,
32505
33569
  rmSync as rmSync6,
@@ -32600,14 +33664,14 @@ function acquireInstallLock(lockKey) {
32600
33664
  const token = randomUUID3();
32601
33665
  const tryClaim = () => {
32602
33666
  try {
32603
- const fd = openSync9(lock, "wx");
33667
+ const fd = openSync10(lock, "wx");
32604
33668
  try {
32605
33669
  writeFileSync8(fd, `${process.pid}
32606
33670
  ${new Date().toISOString()}
32607
33671
  ${token}
32608
33672
  `);
32609
33673
  } finally {
32610
- closeSync10(fd);
33674
+ closeSync11(fd);
32611
33675
  }
32612
33676
  let retained = false;
32613
33677
  let released = false;
@@ -32770,11 +33834,11 @@ function claimLspAutoInstallPass() {
32770
33834
  };
32771
33835
  const tryClaim = () => {
32772
33836
  try {
32773
- const fd = openSync9(lock, "wx");
33837
+ const fd = openSync10(lock, "wx");
32774
33838
  try {
32775
33839
  writeFileSync8(fd, JSON.stringify(owner));
32776
33840
  } finally {
32777
- closeSync10(fd);
33841
+ closeSync11(fd);
32778
33842
  }
32779
33843
  return true;
32780
33844
  } catch (err) {
@@ -33178,18 +34242,26 @@ async function runInstall(spec, version, cwd, installLock, before, signal, deadl
33178
34242
  warn2(`[lsp] install ${target} aborted before spawn`);
33179
34243
  return false;
33180
34244
  }
33181
- const npm = resolveNpm();
33182
- if (!npm) {
34245
+ const preferredNpm = resolveNpm();
34246
+ if (!preferredNpm) {
33183
34247
  warn2(`[lsp] npm not found on PATH or known locations; cannot install ${target}`);
33184
34248
  return false;
33185
34249
  }
34250
+ const selected = await selectLspNpm(preferredNpm, undefined, signal);
34251
+ if (signal?.aborted)
34252
+ return false;
34253
+ if (!selected.npm) {
34254
+ warn2(`[lsp] ${selected.refusal}`);
34255
+ return false;
34256
+ }
34257
+ const npm = selected.npm;
33186
34258
  const invocation = await lspNpmInstallInvocation(npm, target, before);
33187
34259
  if (signal?.aborted) {
33188
34260
  warn2(`[lsp] install ${target} aborted during npm capability probe`);
33189
34261
  return false;
33190
34262
  }
33191
34263
  if (!invocation) {
33192
- warn2(`[lsp] npm cannot enforce the publication cutoff; cannot install ${target}`);
34264
+ warn2(`[lsp] ${lspNpmRefusal(npm.command, "probe failed: cannot verify publication-cutoff support")}`);
33193
34265
  return false;
33194
34266
  }
33195
34267
  ensureInstallAnchor(cwd);
@@ -33271,6 +34343,9 @@ async function runInstall(spec, version, cwd, installLock, before, signal, deadl
33271
34343
  log2(`[lsp] installed ${target}`);
33272
34344
  finish(true);
33273
34345
  } else {
34346
+ const rejectedOption = /--(?:no-save|before|ignore-scripts)\b/.exec(stderrBuf)?.[0];
34347
+ if (rejectedOption)
34348
+ warn2(`[lsp] ${lspNpmRefusal(npm.command, `rejected option: ${rejectedOption}`)}`);
33274
34349
  error2(`[lsp] install ${target} exited with code ${code}; last stderr:
33275
34350
  ${stderrBuf.trim()}`);
33276
34351
  finish(false);
@@ -34113,9 +35188,9 @@ import { StringEnum } from "@earendil-works/pi-ai";
34113
35188
  import { Type as Type4 } from "typebox";
34114
35189
 
34115
35190
  // src/tools/hoisted.ts
34116
- import { stat as stat3 } from "node:fs/promises";
35191
+ import { stat as stat4 } from "node:fs/promises";
34117
35192
  import { homedir as homedir16 } from "node:os";
34118
- import { isAbsolute as isAbsolute11, relative as relative7, resolve as resolve13, sep as sep3 } from "node:path";
35193
+ import { isAbsolute as isAbsolute10, relative as relative6, resolve as resolve14, sep as sep3 } from "node:path";
34119
35194
  import {
34120
35195
  renderDiff as renderDiff2
34121
35196
  } from "@earendil-works/pi-coding-agent";
@@ -34293,7 +35368,7 @@ function modelSupportsImages(extCtx) {
34293
35368
  }
34294
35369
  var NON_VISION_IMAGE_NOTE = "[Current model does not support images. The image will be omitted from this request.]";
34295
35370
  function containsPath(parent, child) {
34296
- const rel = relative7(parent, child);
35371
+ const rel = relative6(parent, child);
34297
35372
  return !relativePathEscapesRoot(rel);
34298
35373
  }
34299
35374
  function normalizePathInput(path) {
@@ -34303,17 +35378,17 @@ function normalizePathInput(path) {
34303
35378
  if (decoded === "~")
34304
35379
  return homedir16();
34305
35380
  if (decoded.startsWith(`~${sep3}`) || decoded.startsWith("~/")) {
34306
- return resolve13(homedir16(), decoded.slice(2));
35381
+ return resolve14(homedir16(), decoded.slice(2));
34307
35382
  }
34308
35383
  return decoded;
34309
35384
  }
34310
35385
  function absoluteSearchPath(cwd, target) {
34311
35386
  const expanded = normalizePathInput(target);
34312
- return isAbsolute11(expanded) ? expanded : resolve13(cwd, expanded);
35387
+ return isAbsolute10(expanded) ? expanded : resolve14(cwd, expanded);
34313
35388
  }
34314
35389
  async function searchPathExists(cwd, target) {
34315
35390
  try {
34316
- await stat3(absoluteSearchPath(cwd, target));
35391
+ await stat4(absoluteSearchPath(cwd, target));
34317
35392
  return true;
34318
35393
  } catch {
34319
35394
  return false;
@@ -34365,7 +35440,7 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
34365
35440
  if (!target)
34366
35441
  return;
34367
35442
  const expanded = normalizePathInput(target);
34368
- const absoluteTarget = isAbsolute11(expanded) ? expanded : resolve13(extCtx.cwd, expanded);
35443
+ const absoluteTarget = isAbsolute10(expanded) ? expanded : resolve14(extCtx.cwd, expanded);
34369
35444
  if (containsPath(extCtx.cwd, absoluteTarget))
34370
35445
  return;
34371
35446
  if (options.restrictToProjectRoot === false)
@@ -34374,7 +35449,7 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
34374
35449
  }
34375
35450
  async function assertExternalReadPermission(ctx, extCtx, target, restrictToProjectRoot = ctx.config.restrict_to_project_root ?? false) {
34376
35451
  const expanded = normalizePathInput(target);
34377
- const absoluteTarget = isAbsolute11(expanded) ? expanded : resolve13(extCtx.cwd, expanded);
35452
+ const absoluteTarget = isAbsolute10(expanded) ? expanded : resolve14(extCtx.cwd, expanded);
34378
35453
  if (!restrictToProjectRoot || containsPath(extCtx.cwd, absoluteTarget))
34379
35454
  return;
34380
35455
  try {
@@ -34707,7 +35782,13 @@ PDFs aren't supported on the Pi harness yet.`, response);
34707
35782
  const response = await callToolCallForSession(bridge, "edit", rawArgs, sessionId, extCtx);
34708
35783
  if (response.success === false)
34709
35784
  throw toolErrorFromResponse("edit", response);
34710
- return buildMutationResult(response);
35785
+ return buildHashlineMutationResult(response);
35786
+ },
35787
+ renderCall(args, theme, context) {
35788
+ return renderHashlineEditCall(args, theme, context);
35789
+ },
35790
+ renderResult(result, options = { expanded: false, isPartial: false }, theme, context) {
35791
+ return renderMutationResult(result, theme, context, options);
34711
35792
  }
34712
35793
  });
34713
35794
  }
@@ -34883,6 +35964,71 @@ ${formatDiagnosticsText(diagnostics)}`;
34883
35964
  }
34884
35965
  };
34885
35966
  }
35967
+ function buildHashlineMutationResult(response) {
35968
+ const result = buildMutationResult(response);
35969
+ const rows = asRecords2(asRecord3(response.metadata)?.files);
35970
+ const files = rows.map(hashlineFileDetails);
35971
+ if (files.length === 0)
35972
+ return result;
35973
+ const changed = files.filter((file) => file.diff !== undefined);
35974
+ const diff = changed.length === 0 ? undefined : files.length === 1 ? files[0]?.diff : files.map((file) => file.diff === undefined ? mutationFileHeader(file) : `${mutationFileHeader(file)}
35975
+ ${file.diff}`).join(`
35976
+ `);
35977
+ const firstChangedLine = rows.map((row) => firstChangedLineOfPatch(asString2(row.patch) ?? "")).find((line) => line !== undefined);
35978
+ return {
35979
+ ...result,
35980
+ details: {
35981
+ ...result.details,
35982
+ diff,
35983
+ firstChangedLine,
35984
+ additions: files.reduce((sum, file) => sum + file.additions, 0),
35985
+ deletions: files.reduce((sum, file) => sum + file.deletions, 0),
35986
+ files
35987
+ }
35988
+ };
35989
+ }
35990
+ function hashlineFileDetails(row) {
35991
+ const patch = asString2(row.patch) ?? "";
35992
+ const diff = formatUnifiedDiffForPi(patch);
35993
+ return {
35994
+ path: asString2(row.relativePath) ?? "",
35995
+ sourcePath: asString2(row.sourcePath) ?? asString2(row.movePath),
35996
+ diff: diff.length > 0 ? diff : undefined,
35997
+ additions: asNumber2(row.additions) ?? 0,
35998
+ deletions: asNumber2(row.deletions) ?? 0
35999
+ };
36000
+ }
36001
+ function firstChangedLineOfPatch(patch) {
36002
+ let newLine;
36003
+ for (const line of patch.split(`
36004
+ `)) {
36005
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
36006
+ if (hunk) {
36007
+ newLine = Number(hunk[1]);
36008
+ continue;
36009
+ }
36010
+ if (newLine === undefined)
36011
+ continue;
36012
+ if (line.startsWith("+") || line.startsWith("-"))
36013
+ return Math.max(1, newLine);
36014
+ if (line.startsWith(" "))
36015
+ newLine++;
36016
+ }
36017
+ return;
36018
+ }
36019
+ function mutationFileName(file) {
36020
+ return file.sourcePath ? `${shortenHomePath(file.sourcePath)} → ${shortenHomePath(file.path)}` : shortenHomePath(file.path);
36021
+ }
36022
+ function mutationFileHeader(file) {
36023
+ return `${mutationFileName(file)} (+${file.additions}/-${file.deletions})`;
36024
+ }
36025
+ function mutationFilesLabel(files) {
36026
+ const first = files?.[0];
36027
+ if (!first || !files)
36028
+ return;
36029
+ const name = mutationFileName(first);
36030
+ return files.length > 1 ? `${name} and ${files.length - 1} more` : name;
36031
+ }
34886
36032
  function formatGlobSkipReasonsNote(reasons) {
34887
36033
  if (!Array.isArray(reasons))
34888
36034
  return;
@@ -34934,6 +36080,29 @@ function renderMutationCall(toolName, filePath, theme, context) {
34934
36080
  text.setText(`${theme.fg("toolTitle", theme.bold(toolName))} ${pathDisplay}`);
34935
36081
  return text;
34936
36082
  }
36083
+ function renderHashlineEditCall(args, theme, context) {
36084
+ const text = reuseText2(context.lastComponent);
36085
+ const target = hashlinePatchTarget(asString2(asRecord3(args)?.patch));
36086
+ const targetDisplay = target ? `${theme.fg("accent", target.name)}${target.more > 0 ? theme.fg("muted", ` and ${target.more} more`) : ""}` : theme.fg("toolOutput", "...");
36087
+ text.setText(`${theme.fg("toolTitle", theme.bold("edit"))} ${targetDisplay}`);
36088
+ return text;
36089
+ }
36090
+ function hashlinePatchTarget(patch) {
36091
+ if (!patch)
36092
+ return;
36093
+ const sections = hashlinePatchSections(patch) ?? hashlinePatchSections(patch.slice(0, patch.lastIndexOf(`
36094
+ `) + 1));
36095
+ const first = sections?.[0];
36096
+ if (!sections || !first)
36097
+ return;
36098
+ const destination = first.moves[first.moves.length - 1];
36099
+ const name = destination ? `${shortenHomePath(first.path)} → ${shortenHomePath(destination)}` : shortenHomePath(first.path);
36100
+ const others = new Set(sections.flatMap((section) => [section.path, ...section.moves]));
36101
+ others.delete(first.path);
36102
+ if (destination)
36103
+ others.delete(destination);
36104
+ return { name, more: others.size };
36105
+ }
34937
36106
  function renderMutationResult(result, theme, context, options = { expanded: true }) {
34938
36107
  if (context.isError) {
34939
36108
  const errorText = result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join(`
@@ -34969,15 +36138,21 @@ ${summary}${suffix}`);
34969
36138
  const container = reuseContainer2(context.lastComponent);
34970
36139
  container.clear();
34971
36140
  container.addChild(new Spacer2(1));
34972
- let renderedDiff;
34973
- try {
34974
- renderedDiff = renderDiff2(diff);
34975
- } catch {
34976
- renderedDiff = diff;
36141
+ const files = details?.files ?? [];
36142
+ if (files.length > 1) {
36143
+ for (const file of files) {
36144
+ const counts = theme.fg("muted", ` (+${file.additions}/-${file.deletions})`);
36145
+ container.addChild(new Text3(`${theme.fg("accent", mutationFileName(file))}${counts}`, 1, 0));
36146
+ if (file.diff !== undefined) {
36147
+ container.addChild(new Text3(renderTerminalDiff(file.diff), 1, 0));
36148
+ }
36149
+ }
36150
+ } else {
36151
+ container.addChild(new Text3(renderTerminalDiff(diff), 1, 0));
34977
36152
  }
34978
- container.addChild(new Text3(normalizeTerminalText(renderedDiff), 1, 0));
34979
36153
  const rawPath = context.args && typeof context.args === "object" && typeof context.args.path === "string" ? context.args.path : undefined;
34980
- const summaryPath = rawPath ? ` ${shortenHomePath(rawPath)}` : "";
36154
+ const summaryLabel = rawPath ? shortenHomePath(rawPath) : mutationFilesLabel(details?.files);
36155
+ const summaryPath = summaryLabel ? ` ${summaryLabel}` : "";
34981
36156
  const summary = `edited${summaryPath} (+${details?.additions ?? 0}/-${details?.deletions ?? 0})`;
34982
36157
  return collapsibleResult({
34983
36158
  summary,
@@ -34986,11 +36161,20 @@ ${summary}${suffix}`);
34986
36161
  context
34987
36162
  });
34988
36163
  }
36164
+ function renderTerminalDiff(diff) {
36165
+ let renderedDiff;
36166
+ try {
36167
+ renderedDiff = renderDiff2(diff);
36168
+ } catch {
36169
+ renderedDiff = diff;
36170
+ }
36171
+ return normalizeTerminalText(renderedDiff);
36172
+ }
34989
36173
  async function resolvePathArg(cwd, path) {
34990
36174
  const expanded = normalizePathInput(path);
34991
36175
  const abs = absoluteSearchPath(cwd, path);
34992
36176
  try {
34993
- await stat3(abs);
36177
+ await stat4(abs);
34994
36178
  return abs;
34995
36179
  } catch {
34996
36180
  return expanded;
@@ -35565,7 +36749,7 @@ function getBashSpawnHook(pi) {
35565
36749
  function backgroundWaitSentence(c) {
35566
36750
  if (c.watch) {
35567
36751
  const noPolling = c.status ? ", and never loop `bash_status` to wait" : "";
35568
- return `then \`bash_watch\` handles only a short remaining wait (in a main session a watch defaults to 30s, max bash.watch_sync_max_ms, 120s by default; in a delegated session a watch without a timeout waits up to the worker wait limit, bash.worker_wait_max_ms, 30 minutes by default, then reports it is still running; watch again to keep waiting); for anything longer end the turn and let the completion reminder wake you, or use bash({wait:true}) when the result is needed before anything else — never background a command and immediately \`bash_watch\` it (that wastes a turn for what foreground returns in one)${noPolling}.`;
36752
+ return `then \`bash_watch\` handles only a short remaining wait (in a main session a watch defaults to 30s, max bash.watch_sync_max_ms, 120s by default; in a delegated session a watch without a timeout waits up to the worker wait limit, bash.worker_wait_max_ms, 30 minutes by default, then reports it is still running; watch again to keep waiting); for anything longer, first do the work that doesn't need this result, end the turn only when everything left needs it, and let the completion reminder wake you, or use bash({wait:true}) when the result is needed before anything else — never background a command and immediately \`bash_watch\` it (that wastes a turn for what foreground returns in one)${noPolling}.`;
35569
36753
  }
35570
36754
  return c.status ? "the task keeps running after the call returns, a completion reminder arrives when it exits, and `bash_status` reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else." : "the task keeps running after the call returns and a completion reminder arrives when it exits. Use bash({wait:true}) instead when the result is needed before anything else.";
35571
36755
  }
@@ -35602,7 +36786,7 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName =
35602
36786
  "PTY mode"
35603
36787
  ].join(", ") : bashCfg.compress ? "workdir and compressed output" : "workdir";
35604
36788
  const detachSentence = bashCfg.detach_on_user_message ? "Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery; the rest of the message is preserved, while a token-only message becomes `(requested background detach)`." : "Because `bash.detach_on_user_message` is false, a new message leaves this wait blocking; include the literal `&detach` anywhere to force detachment, and the token is stripped before delivery; the rest of the message is preserved, while a token-only message becomes `(requested background detach)`.";
35605
- const tasksSentence = bashCfg.background ? ` Commands run in the foreground and return inline; \`wait: true\` blocks until a long command finishes instead of auto-promoting (it blocks up to \`bash.worker_wait_max_ms\`, 30 minutes by default, then reports the command is still running; watch again to keep waiting); ${detachSentence} Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use \`background: true\` yourself ONLY when you have other useful work to do while it runs; ${backgroundWaitSentence(companions)} Do not change or delete files a background command uses until it finishes. A \`nohup … &\` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. \`pty: true\` runs interactive programs (REPLs, TUIs), implies background${ptyDriveClause(companions)}.` : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
36789
+ const tasksSentence = bashCfg.background ? ` Commands run in the foreground and return inline; \`wait: true\` blocks until a long command finishes instead of auto-promoting (it blocks up to \`bash.worker_wait_max_ms\`, 30 minutes by default, then reports the command is still running; watch again to keep waiting); ${detachSentence} Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use \`background: true\` yourself ONLY when you have other work to do while it runs, and any work that doesn't need this command's result counts; ${backgroundWaitSentence(companions)} Do not change or delete files a background command uses until it finishes. A \`nohup … &\` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. \`pty: true\` runs interactive programs (REPLs, TUIs), implies background${ptyDriveClause(companions)}.` : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
35606
36790
  const remoteRuns = () => !isPowerShell && remoteRunsOffered(ctx.config);
35607
36791
  pi.registerTool({
35608
36792
  name: registeredName,
@@ -35671,6 +36855,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
35671
36855
  }
35672
36856
  }
35673
36857
  const bridgeCommand = spawnContext.command;
36858
+ const workdir = spawnContext.cwd ?? params.workdir;
35674
36859
  let streamed = "";
35675
36860
  let usedHostFallback = false;
35676
36861
  let response;
@@ -35678,7 +36863,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
35678
36863
  response = await withOutsidePermissionOperation(() => callBashWithPermissionLoop(bridge, {
35679
36864
  command: bridgeCommand,
35680
36865
  timeout,
35681
- workdir: spawnContext.cwd ?? params.workdir,
36866
+ workdir,
35682
36867
  env: spawnContext.env,
35683
36868
  description: params.description,
35684
36869
  background: effectiveBackground,
@@ -35770,7 +36955,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
35770
36955
  task_id: taskId
35771
36956
  };
35772
36957
  const output = response.output ?? "";
35773
- return bashResult(usedHostFallback || isPowerShell ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36958
+ return bashResult(usedHostFallback || isPowerShell ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd, workdir), details);
35774
36959
  },
35775
36960
  renderCall(args, theme, context) {
35776
36961
  return renderBashCall(asString2(args?.command), asString2(args?.description), theme, context);
@@ -35798,8 +36983,8 @@ function registerBashCompanionTools(pi, ctx, enabled = { bashStatus: true, bashW
35798
36983
  pi.registerTool(createBashKillTool(ctx));
35799
36984
  }
35800
36985
  }
35801
- function withBashHints(output, command, aftSearchRegistered, projectRoot) {
35802
- return maybeAppendGrepSearchHint(maybeAppendConflictsHint(output), command, aftSearchRegistered, projectRoot);
36986
+ function withBashHints(output, command, aftSearchRegistered, projectRoot, workdir) {
36987
+ return maybeAppendGrepSearchHint(maybeAppendConflictsHint(output), command, aftSearchRegistered, projectRoot, undefined, workdir);
35803
36988
  }
35804
36989
  function createBashStatusTool(ctx) {
35805
36990
  return {
@@ -35833,7 +37018,7 @@ function createBashWatchTool(ctx) {
35833
37018
  return {
35834
37019
  name: "bash_watch",
35835
37020
  label: "bash_watch",
35836
- description: `Watch a background bash task. ${WATCH_SYNC_DEFAULTS_DESCRIPTION}. In a main session sync waits are for a short remaining wait on a task; for anything longer end the turn on \`bash({background:true})\` and let the completion reminder wake you, or use \`bash({wait:true})\` when the result is needed before anything else. The user can interrupt anytime; the wait auto-converts to an async notification. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use when you have parallel work or want to end your turn.${bashCompanionRegistered(ctx.config, "bash_status") ? " Never loop bash_status to wait." : ""}`,
37021
+ description: `Watch a background bash task. ${WATCH_SYNC_DEFAULTS_DESCRIPTION}. In a main session sync waits are for a short remaining wait on a task; for anything longer, first do the work that doesn't need the result, end the turn only when everything left needs it, and let the completion reminder wake you, or use \`bash({wait:true})\` when the result is needed before anything else. The user can interrupt anytime; the wait auto-converts to an async notification. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use when you have parallel work or want to end your turn.${bashCompanionRegistered(ctx.config, "bash_status") ? " Never loop bash_status to wait." : ""}`,
35837
37022
  promptSnippet: "Wait for or watch a background bash task",
35838
37023
  parameters: BashWatchParams,
35839
37024
  async execute(_toolCallId, params, signal, _onUpdate, extCtx) {
@@ -36300,7 +37485,7 @@ function formatWaitSummary(taskId, waited, details, watchRole) {
36300
37485
  async function formatBashStatus(extCtx, taskId, details, requestedOutputMode, watchRole) {
36301
37486
  const exit = typeof details.exit_code === "number" ? ` (exit ${details.exit_code})` : "";
36302
37487
  const dur = typeof details.duration_ms === "number" ? ` ${Math.round(details.duration_ms / 1000)}s` : "";
36303
- let text = `Task ${taskId}: ${details.status}${exit}${dur}`;
37488
+ let text = formatUnknownPtyExit(taskId, details) ?? `Task ${taskId}: ${details.status}${exit}${dur}`;
36304
37489
  if (details.live_descendants_summary) {
36305
37490
  text += ` · ${details.live_descendants_summary}`;
36306
37491
  }
@@ -36606,7 +37791,7 @@ function registerFsTools(pi, ctx, surface) {
36606
37791
  recursive: coerceBoolean(params.recursive)
36607
37792
  }, extCtx);
36608
37793
  if (response.success === false) {
36609
- throw new Error(response.text || response.message || "delete failed");
37794
+ throw toolErrorFromResponse("delete", response);
36610
37795
  }
36611
37796
  const deletedEntries = response.deleted ?? [];
36612
37797
  const skipped = response.skipped_files ?? [];
@@ -37244,7 +38429,7 @@ function registerNavigateTool(pi, ctx) {
37244
38429
  const navigateTool = withPathAliasPreparation({
37245
38430
  name: "aft_callgraph",
37246
38431
  label: "callgraph",
37247
- description: `Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect. ${zoomEnabled ? "Use aft_zoom with `callgraph:true` for one-level forward calls-out while reading source; use aft_callgraph only for reverse callers or multi-level traces so you do not double-fetch the same relationships." : "Use aft_callgraph call_tree (depth 1) for one-level forward calls-out; use it for reverse callers or multi-level traces."} All ops require both \`path\` and \`symbol\`. Use \`callers\` for call sites (before renaming/signature changes), \`impact\` for blast radius (what breaks if a symbol changes), \`call_tree\` for what a function calls, \`trace_to\` for how execution reaches a symbol from entry points, \`trace_to_symbol\` for the shortest path from one symbol to another (requires \`toSymbol\`; if ambiguous, the error returns candidate files — retry with \`toPath\`), \`trace_data\` to follow a value across assignments/params. Markers: ~ = edge resolved by name only (may point at the wrong same-named symbol); [unresolved] = callee not resolved to a definition, so the location shown is the call site. Unmarked edges are resolved exactly. By default, unresolved external/stdlib leaf calls in call_tree are collapsed into one summary per parent; pass includeUnresolved=true to show every unresolved edge individually. When a list is cut, the reply ends with \`shown N of M <unit> (<reason>) · narrow: <knobs>\`; absence of that line means the list is complete.`,
38432
+ description: `Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect. ${zoomEnabled ? "Use aft_zoom with `callgraph:true` for one-level forward calls-out while reading source; use aft_callgraph only for reverse callers or multi-level traces so you do not double-fetch the same relationships." : "Use aft_callgraph call_tree (depth 1) for one-level forward calls-out; use it for reverse callers or multi-level traces."} All ops require both \`path\` and \`symbol\`. Use \`callers\` for call sites (before renaming/signature changes), \`impact\` for blast radius (what breaks if a symbol changes), \`call_tree\` for what a function calls, \`trace_to\` for how execution reaches a symbol (each path runs back to a root labelled main, test, attribute, public API, or no caller found), \`trace_to_symbol\` for the shortest path from one symbol to another (requires \`toSymbol\`; if ambiguous, the error returns candidate files — retry with \`toPath\`), \`trace_data\` to follow a value across assignments/params. Markers: ~ = edge resolved by name only (may point at the wrong same-named symbol); [unresolved] = callee not resolved to a definition, so the location shown is the call site. Unmarked edges are resolved exactly. By default, unresolved external/stdlib leaf calls in call_tree are collapsed into one summary per parent; pass includeUnresolved=true to show every unresolved edge individually. When a list is cut, the reply ends with \`shown N of M <unit> (<reason>) · narrow: <knobs>\`; absence of that line means the list is complete.`,
37248
38433
  parameters: navigateParamsSchema(),
37249
38434
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
37250
38435
  if (isEmptyParam(params.path)) {
@@ -37882,7 +39067,7 @@ function registerSafetyTool(pi, ctx) {
37882
39067
 
37883
39068
  // src/tools/semantic.ts
37884
39069
  import { homedir as homedir17 } from "node:os";
37885
- import { isAbsolute as isAbsolute12, resolve as resolve14 } from "node:path";
39070
+ import { isAbsolute as isAbsolute11, resolve as resolve15 } from "node:path";
37886
39071
  import { Type as Type13 } from "typebox";
37887
39072
  var FILESYSTEM_SCAN_STATUS = "answered by a filesystem scan";
37888
39073
  function semanticHonestyNote(response, theme) {
@@ -37905,8 +39090,8 @@ function selectedProjectRoot(cwd, pathArg) {
37905
39090
  const target = pathArg?.trim();
37906
39091
  if (!target)
37907
39092
  return canonicalizeProjectRoot(cwd);
37908
- const expanded = target === "~" ? homedir17() : /^~[/\\]/u.test(target) ? resolve14(homedir17(), target.slice(2)) : target;
37909
- return canonicalizeProjectRoot(isAbsolute12(expanded) ? expanded : resolve14(cwd, expanded));
39093
+ const expanded = target === "~" ? homedir17() : /^~[/\\]/u.test(target) ? resolve15(homedir17(), target.slice(2)) : target;
39094
+ return canonicalizeProjectRoot(isAbsolute11(expanded) ? expanded : resolve15(cwd, expanded));
37910
39095
  }
37911
39096
  function continuityKey(projectRoot, query, includeTests, topK) {
37912
39097
  return JSON.stringify([
@@ -38503,93 +39688,52 @@ function registerShutdownCleanup(fn) {
38503
39688
  }
38504
39689
 
38505
39690
  // src/workflow-hints.ts
38506
- var HEADING = "## IMPORTANT NOTICE about your tools";
39691
+ var HEADING = "# Agent Foundational Toolkit";
38507
39692
  function hashlineTagSourceHint(bashRewritesMintTags, isRegistered = () => true) {
38508
39693
  return `**Hashline edit tags**: ${hashlineTagSourceSentence(bashRewritesMintTags, isRegistered)}`;
38509
39694
  }
38510
39695
  var HASHLINE_TAG_SOURCE_HINT = hashlineTagSourceHint(true);
38511
39696
  function buildWorkflowHints(opts) {
38512
- const sections = [];
38513
- const grepName = "grep";
38514
- const bashName = "bash";
38515
- const hasOutline = !opts.absentTools.has("aft_outline");
38516
- const hasZoom = !opts.absentTools.has("aft_zoom");
38517
- const readName = "read";
38518
- const hasRead = !opts.absentTools.has(readName);
38519
- const hasGrep = !opts.absentTools.has(grepName);
38520
- const hasSearch = !opts.absentTools.has("aft_search");
38521
- const hasNavigate = !opts.absentTools.has("aft_callgraph");
38522
- const hasInspect = !opts.absentTools.has("aft_inspect");
38523
- const hasBash = !opts.absentTools.has(bashName);
38524
- const hasBgBash = opts.bashBackgroundEnabled && hasBash && !opts.absentTools.has("bash_status");
38525
- const hasWatch = !opts.absentTools.has("bash_watch");
38526
- const hasWrite = !opts.absentTools.has("bash_write");
38527
- if (hasBash && opts.bashCompressionEnabled) {
38528
- sections.push([
38529
- "**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:",
38530
- "- `bun test | grep fail` → run `bun test`",
38531
- "- `cargo test 2>&1 | tail -20` → run `cargo test`",
38532
- "- `npm run build | head -50` → run `npm run build`"
38533
- ].join(`
38534
- `));
39697
+ const has = (name) => !opts.absentTools.has(name);
39698
+ const hasBash = has("bash");
39699
+ const hasBackground = hasBash && opts.bashBackgroundEnabled && has("bash_watch") && has("bash_status");
39700
+ const finding = [];
39701
+ if (has("aft_search")) {
39702
+ finding.push("start with aft_search; it takes concepts, identifiers, regex and literals.");
38535
39703
  }
38536
- if (hasOutline && hasZoom) {
38537
- sections.push(`**Web/URL access**: \`aft_outline({ target: "<url>" })\` first for structure, then \`aft_zoom({ batch: [{ target: "<url>", symbols: "<heading>" }] })\` for the specific section.`);
38538
- }
38539
- if (hasOutline && (hasGrep || hasSearch) && (hasZoom || hasRead)) {
38540
- const searchName = hasSearch ? "aft_search" : grepName;
38541
- const locate = hasSearch ? "`aft_search` is the primary code-search tool: one call auto-routes concepts, identifiers, regex, error strings, and literals." : `\`${grepName}\` (the tool — indexed and ranked) locates code.`;
38542
- const zoomSteer = hasZoom ? ", or `aft_zoom`" : "";
38543
- const searchSteer = hasSearch ? `use \`aft_search\` (concepts, identifiers, regex, literals), \`${readName}\`, \`aft_outline\`${zoomSteer} instead` : `use the \`${grepName}\` tool, \`${readName}\`, \`aft_outline\`${zoomSteer} instead`;
38544
- const bashSteer = hasBash ? ` If you are about to run grep, rg, sed, awk, find, or cat through ${bashName} to locate or read code: STOP — ${searchSteer}.` : "";
38545
- sections.push([
38546
- `**Code exploration**: ${locate} Then \`aft_outline\` for structure → \`${hasZoom ? "aft_zoom" : readName}\` for symbol(s). DO NOT run \`grep\`/\`rg\`/\`find\`/\`sed\`/\`cat\` through \`bash\` to locate or read code — the bash path is unindexed, unranked, serial, and routinely surfaces the wrong hit. Keep \`bash\` for shell facts (git state, file metadata, processes).${bashSteer} Reflex translations:`,
38547
- `- \`grep -rn "handleAuth" src/\` in bash → \`${searchName}({ query: "handleAuth" })\``,
38548
- `- \`find . -name "*.ts" | xargs grep watcher\` in bash → \`${searchName}({ query: "watcher invalidation" })\` (concepts work too)`,
38549
- `- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ path: "app.ts", startLine: 100, endLine: 160 })\``
38550
- ].join(`
38551
- `));
39704
+ if (has("aft_outline") && has("aft_zoom") && has("read")) {
39705
+ finding.push(`${finding.length ? "Then " : ""}aft_outline for structure, aft_zoom for named symbols, read for line ranges.`);
38552
39706
  }
38553
- if (hasInspect) {
38554
- sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat stale_categories/pending_categories as stale or incomplete cache state. AFT schedules a Tier-2 refresh after its next idle or inspect-triggered background run; use one later normal aft_inspect after that refresh, not a polling loop.");
38555
- sections.push("**AFT status bar**: tool results may end with a one-line health bar `[AFT E<errors> W<warnings> | D<dead-code> U<unused-exports> C<clone/dup-groups> | T<todos>]` — an IDE-style glance that appears when a count changes. `E`/`W` are live LSP diagnostics for files touched this session (your universal compile-error signal across every language with an LSP). A `~` before `D` means the dead-code/unused/dup counts predate your latest edit — run `aft_inspect` for current numbers and detail. A `?` in place of a number means that count has no value yet (not zero). When `E>0`, you likely just introduced errors; investigate before moving on.");
38556
- }
38557
- if (hasNavigate) {
38558
- sections.push([
38559
- "Use `aft_callgraph` for code-relationship questions instead of grep + read chains:",
38560
- "- `callers` — find all call sites before changing a function signature",
38561
- "- `impact` — blast radius (which functions/files will need updates)",
38562
- "- `trace_to` — how execution reaches this code from entry points (routes, exports, main)",
38563
- "- `trace_to_symbol` — shortest call path from one symbol to another",
38564
- "- `trace_data` — follow a value through assignments and parameters across files"
38565
- ].join(`
38566
- `));
39707
+ if (hasBash) {
39708
+ finding.push("Don't find or read code with grep, find, sed or cat in bash.");
38567
39709
  }
38568
- if (hasBgBash && hasWatch) {
38569
- sections.push([
38570
- `**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — use \`${bashName}({ command, wait: true })\` when you know it is long and need the result before anything else; if you send a new message, the wait detaches to background; otherwise omit \`wait\` so auto-promote can hand you a reminder while you work.`,
38571
- "- `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.",
38572
- "- `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."
38573
- ].join(`
38574
- `));
38575
- }
38576
- if (hasBgBash && hasWrite) {
38577
- 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 \`bash_status({ task_id, output_mode: "screen" })\`, and send input with \`bash_write({ task_id, input: "..." })\`.`);
39710
+ const commands = [];
39711
+ if (hasBash && opts.bashCompressionEnabled) {
39712
+ commands.push("don't pipe test or build output into tail, grep or head; AFT already shortens it.");
38578
39713
  }
38579
- if (opts.hashlineEffective === true) {
38580
- sections.push(hashlineTagSourceHint(hasBash && opts.bashRewriteEnabled !== false, (name) => !opts.absentTools.has(name)));
39714
+ if (hasBackground) {
39715
+ commands.push("Pass wait: true when you need a long command's result before anything else; a new message from the user detaches the wait.", "Otherwise a long command moves to the background and a reminder brings its result.", "Block on it with bash_watch only when nothing else is left; bash_watch with background: true and a pattern tells you when a line appears.", "Never loop bash_status.");
38581
39716
  }
38582
- if (sections.length === 0) {
39717
+ const sections = [
39718
+ finding.length ? `**Finding code**: ${finding.join(" ")}` : "",
39719
+ has("aft_callgraph") ? "**Relationships**: aft_callgraph for callers, impact, and execution or data-flow traces." : "",
39720
+ has("aft_outline") && has("aft_zoom") ? "**Web pages**: aft_outline with a URL as target, then aft_zoom on a heading." : "",
39721
+ "",
39722
+ has("aft_inspect") ? "**After edits**: run aft_inspect for diagnostics; the project's typecheck and tests are the final check." : "",
39723
+ has("aft_inspect") ? "**Status bar**: `[AFT E.. W.. | T..]` appears when a count changes. E above 0 usually means your last edit broke something; `?` means unknown, not zero." : "",
39724
+ commands.length ? `**Commands**: ${commands.join(" ")}` : ""
39725
+ ];
39726
+ const hashline = opts.hashlineEffective === true ? hashlineTagSourceHint(hasBash && opts.bashRewriteEnabled !== false, has) : "";
39727
+ if (!sections.some(Boolean) && !hashline)
38583
39728
  return null;
38584
- }
38585
- sections.unshift(`You are equipped with a non-standard tool set: indexed code search, symbol-level reading, structural editing, and code analysis that are faster, more precise, and far cheaper in tokens than stitching together command-line utilities in bash. Always reach for these tools first.
38586
-
38587
- **Parallel tool calls**: when several read-only operations are independent, emit them in ONE response instead of serializing — file reads, structure and symbol lookups, code search, diagnostics, and git status/diff/log. Sequence only when a call depends on a prior result or when a command mutates state.`);
38588
- return `${HEADING}
38589
-
38590
- ${sections.join(`
39729
+ sections[3] = "**Parallel calls**: send independent read-only calls in one response.";
39730
+ const text = `${HEADING}
38591
39731
 
39732
+ ${sections.filter(Boolean).join(`
38592
39733
  `)}`;
39734
+ return hashline ? `${text}
39735
+
39736
+ ${hashline}` : text;
38593
39737
  }
38594
39738
  function buildHintsFromConfig(config, absentTools, hashlineEffective = false) {
38595
39739
  const bashCfg = resolveBashConfig(config);
@@ -38694,18 +39838,18 @@ function createVersionMismatchHandler(getPool, ensureCompatibleBinary = ensureBi
38694
39838
  }
38695
39839
  var PLUGIN_VERSION = (() => {
38696
39840
  try {
38697
- const req = createRequire2(import.meta.url);
39841
+ const req = createRequire3(import.meta.url);
38698
39842
  return req("../package.json").version;
38699
39843
  } catch {
38700
39844
  return "0.0.0";
38701
39845
  }
38702
39846
  })();
38703
- var ANNOUNCEMENT_VERSION = "0.63.0";
39847
+ var ANNOUNCEMENT_VERSION = "0.65.0";
38704
39848
  var ANNOUNCEMENT_FEATURES = [
38705
- "Cap a shell command's total memory, background and terminal commands included, with `bash.memory_limit_mib`.",
38706
- "Shell results can list or diff the files a command changed: set `bash.changed_files` to `list` or `diff`.",
38707
- "Put rebuildable caches on another disk with `storage.cache_dir`.",
38708
- "Call graphs mark callback references and link constructors, Python edits keep their indentation, and many reported issues are fixed."
39849
+ "OpenCode 2: AFT's read-only tools also work inside Code Mode as `tools.aft.code.*`.",
39850
+ "TypeScript monorepos run one language server per TypeScript version instead of one per package; `lsp.max_servers` caps the total.",
39851
+ "Search and grep stay light on large growing files, and shell waits survive the computer going to sleep.",
39852
+ "`views.enabled`, `views.callgraph_copies` and `worktree.ram_overlay` are now refused in user config; `npx @cortexkit/aft doctor --fix` removes them."
38709
39853
  ];
38710
39854
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
38711
39855
  var pendingEagerWarnings = new Map;