@cortexkit/aft-opencode 0.15.3 → 0.15.5

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
@@ -7803,7 +7803,7 @@ var require_src2 = __commonJS((exports, module) => {
7803
7803
  });
7804
7804
 
7805
7805
  // src/index.ts
7806
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
7806
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
7808
  import { homedir as homedir7 } from "os";
7809
7809
  import { join as join13 } from "path";
@@ -21454,6 +21454,7 @@ var AftConfigSchema = exports_external.object({
21454
21454
  restrict_to_project_root: exports_external.boolean().optional(),
21455
21455
  experimental_search_index: exports_external.boolean().optional(),
21456
21456
  experimental_semantic_search: exports_external.boolean().optional(),
21457
+ url_fetch_allow_private: exports_external.boolean().optional(),
21457
21458
  semantic: SemanticConfigSchema.optional(),
21458
21459
  max_callgraph_files: exports_external.number().int().positive().optional()
21459
21460
  });
@@ -21696,11 +21697,6 @@ async function ensureBinary(version2) {
21696
21697
  log(`No cached binary for ${version2}, downloading...`);
21697
21698
  return downloadBinary(version2);
21698
21699
  }
21699
- const legacyCached = getCachedBinaryPath();
21700
- if (legacyCached) {
21701
- log(`Found cached binary: ${legacyCached}`);
21702
- return legacyCached;
21703
- }
21704
21700
  log("No cached binary found, downloading latest...");
21705
21701
  return downloadBinary();
21706
21702
  }
@@ -22093,14 +22089,13 @@ async function downloadOnnxRuntime(info, targetDir) {
22093
22089
  const tmpDir = `${targetDir}.tmp.${process.pid}`;
22094
22090
  mkdirSync3(tmpDir, { recursive: true });
22095
22091
  const archivePath = join5(tmpDir, `onnxruntime.${info.archiveType}`);
22096
- const { execSync: execSyncDl } = await import("child_process");
22097
- execSyncDl(`curl -fsSL "${url2}" -o "${archivePath}"`, {
22092
+ const { execFileSync } = await import("child_process");
22093
+ execFileSync("curl", ["-fsSL", url2, "-o", archivePath], {
22098
22094
  stdio: "pipe",
22099
22095
  timeout: 120000
22100
22096
  });
22101
22097
  if (info.archiveType === "tgz") {
22102
- const { execSync } = await import("child_process");
22103
- execSync(`tar xzf "${archivePath}" -C "${tmpDir}"`, { stdio: "pipe" });
22098
+ execFileSync("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
22104
22099
  } else {
22105
22100
  await extractZipArchive(archivePath, tmpDir);
22106
22101
  }
@@ -22199,13 +22194,46 @@ import { homedir as homedir4 } from "os";
22199
22194
  import { join as join6 } from "path";
22200
22195
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
22201
22196
  var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
22197
+ var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
22202
22198
  function compareSemver(a, b) {
22203
- const pa = a.split(".").map(Number);
22204
- const pb = b.split(".").map(Number);
22199
+ const [aMain, aPre] = a.split("-", 2);
22200
+ const [bMain, bPre] = b.split("-", 2);
22201
+ const aParts = aMain.split(".").map(Number);
22202
+ const bParts = bMain.split(".").map(Number);
22205
22203
  for (let i = 0;i < 3; i++) {
22206
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
22207
- if (diff !== 0)
22208
- return diff;
22204
+ if (aParts[i] !== bParts[i])
22205
+ return (aParts[i] ?? 0) - (bParts[i] ?? 0);
22206
+ }
22207
+ if (!aPre && !bPre)
22208
+ return 0;
22209
+ if (!aPre)
22210
+ return 1;
22211
+ if (!bPre)
22212
+ return -1;
22213
+ const aIds = aPre.split(".");
22214
+ const bIds = bPre.split(".");
22215
+ for (let i = 0;i < Math.max(aIds.length, bIds.length); i++) {
22216
+ const ai = aIds[i];
22217
+ const bi = bIds[i];
22218
+ if (ai === undefined)
22219
+ return -1;
22220
+ if (bi === undefined)
22221
+ return 1;
22222
+ const aNum = /^\d+$/.test(ai);
22223
+ const bNum = /^\d+$/.test(bi);
22224
+ if (aNum && bNum) {
22225
+ const diff = Number.parseInt(ai, 10) - Number.parseInt(bi, 10);
22226
+ if (diff !== 0)
22227
+ return diff;
22228
+ } else if (aNum) {
22229
+ return -1;
22230
+ } else if (bNum) {
22231
+ return 1;
22232
+ } else {
22233
+ const cmp = ai.localeCompare(bi);
22234
+ if (cmp !== 0)
22235
+ return cmp;
22236
+ }
22209
22237
  }
22210
22238
  return 0;
22211
22239
  }
@@ -22402,6 +22430,7 @@ class BinaryBridge {
22402
22430
  stdio: ["pipe", "pipe", "pipe"],
22403
22431
  env
22404
22432
  });
22433
+ const currentChild = child;
22405
22434
  child.stdout?.on("data", (chunk) => {
22406
22435
  this.onStdoutData(chunk.toString("utf-8"));
22407
22436
  });
@@ -22417,10 +22446,14 @@ class BinaryBridge {
22417
22446
  }
22418
22447
  });
22419
22448
  child.on("error", (err) => {
22449
+ if (this.process !== currentChild)
22450
+ return;
22420
22451
  error48(`Process error: ${err.message}${this.formatStderrTail()}`);
22421
22452
  this.handleCrash();
22422
22453
  });
22423
22454
  child.on("exit", (code, signal) => {
22455
+ if (this.process !== currentChild)
22456
+ return;
22424
22457
  if (this._shuttingDown)
22425
22458
  return;
22426
22459
  log(`Process exited: code=${code}, signal=${signal}`);
@@ -22454,6 +22487,10 @@ class BinaryBridge {
22454
22487
  }
22455
22488
  onStdoutData(data) {
22456
22489
  this.stdoutBuffer += data;
22490
+ if (this.stdoutBuffer.length > MAX_STDOUT_BUFFER) {
22491
+ this.handleCrash(new Error(`aft bridge stdout buffer exceeded ${MAX_STDOUT_BUFFER} bytes \u2014 killing bridge`));
22492
+ return;
22493
+ }
22457
22494
  let newlineIdx;
22458
22495
  while ((newlineIdx = this.stdoutBuffer.indexOf(`
22459
22496
  `)) !== -1) {
@@ -22489,12 +22526,16 @@ class BinaryBridge {
22489
22526
  this.stderrTail = [];
22490
22527
  this.rejectAllPending(new Error(`[aft-plugin] Bridge restarted after timeout${tail}`));
22491
22528
  }
22492
- handleCrash() {
22529
+ handleCrash(cause) {
22530
+ const proc = this.process;
22493
22531
  this.process = null;
22532
+ if (proc && proc.exitCode === null && !proc.killed) {
22533
+ proc.kill("SIGKILL");
22534
+ }
22494
22535
  this.clearRestartResetTimer();
22495
22536
  this.configured = false;
22496
22537
  const tail = this.formatStderrTail();
22497
- this.rejectAllPending(new Error(`[aft-plugin] Binary crashed (restarts: ${this._restartCount})${tail}`));
22538
+ this.rejectAllPending(new Error(`[aft-plugin] Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}${tail}`));
22498
22539
  if (this._restartCount < this.maxRestarts) {
22499
22540
  const delay = 100 * 2 ** this._restartCount;
22500
22541
  this._restartCount++;
@@ -22508,8 +22549,10 @@ class BinaryBridge {
22508
22549
  }
22509
22550
  }
22510
22551
  }, delay);
22552
+ this.scheduleRestartCountReset();
22511
22553
  } else {
22512
22554
  error48(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
22555
+ this.scheduleRestartCountReset();
22513
22556
  }
22514
22557
  }
22515
22558
  rejectAllPending(error49) {
@@ -22627,12 +22670,9 @@ class BridgePool {
22627
22670
  }
22628
22671
  async replaceBinary(newPath) {
22629
22672
  this.binaryPath = newPath;
22630
- for (const [, entry] of this.bridges) {
22631
- try {
22632
- entry.bridge.shutdown();
22633
- } catch {}
22634
- }
22673
+ const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
22635
22674
  this.bridges.clear();
22675
+ await Promise.allSettled(shutdowns);
22636
22676
  log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
22637
22677
  }
22638
22678
  get size() {
@@ -22645,7 +22685,7 @@ function normalizeKey(projectRoot) {
22645
22685
 
22646
22686
  // src/resolver.ts
22647
22687
  import { execSync, spawnSync } from "child_process";
22648
- import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
22688
+ import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4, renameSync } from "fs";
22649
22689
  import { createRequire } from "module";
22650
22690
  import { homedir as homedir5 } from "os";
22651
22691
  import { join as join7 } from "path";
@@ -22673,7 +22713,6 @@ function copyToVersionedCache(npmBinaryPath) {
22673
22713
  if (process.platform !== "win32") {
22674
22714
  chmodSync3(tmpPath, 493);
22675
22715
  }
22676
- const { renameSync } = __require("fs");
22677
22716
  renameSync(tmpPath, cachedPath);
22678
22717
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
22679
22718
  return cachedPath;
@@ -22754,7 +22793,7 @@ async function findBinary() {
22754
22793
  "",
22755
22794
  "Install it using one of these methods:",
22756
22795
  " npm install @cortexkit/aft-opencode # installs platform-specific binary via npm",
22757
- " cargo install aft # from crates.io",
22796
+ " cargo install agent-file-tools # from crates.io",
22758
22797
  " cargo build --release # from source (binary at target/release/aft)",
22759
22798
  "",
22760
22799
  "Or add the aft directory to your PATH."
@@ -22763,7 +22802,8 @@ async function findBinary() {
22763
22802
  }
22764
22803
 
22765
22804
  // src/shared/rpc-server.ts
22766
- import { mkdirSync as mkdirSync5, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
22805
+ import { randomBytes } from "crypto";
22806
+ import { mkdirSync as mkdirSync5, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
22767
22807
  import { createServer } from "http";
22768
22808
  import { dirname } from "path";
22769
22809
 
@@ -22783,6 +22823,7 @@ function rpcPortFilePath(storageDir, directory) {
22783
22823
  class AftRpcServer {
22784
22824
  server = null;
22785
22825
  port = 0;
22826
+ token = null;
22786
22827
  handlers = new Map;
22787
22828
  portFilePath;
22788
22829
  constructor(storageDir, directory) {
@@ -22805,13 +22846,14 @@ class AftRpcServer {
22805
22846
  return;
22806
22847
  }
22807
22848
  this.port = addr.port;
22849
+ this.token = randomBytes(32).toString("hex");
22808
22850
  this.server = server;
22809
22851
  try {
22810
22852
  const dir = dirname(this.portFilePath);
22811
22853
  mkdirSync5(dir, { recursive: true });
22812
22854
  const tmpPath = `${this.portFilePath}.tmp`;
22813
- writeFileSync2(tmpPath, String(this.port), "utf-8");
22814
- renameSync(tmpPath, this.portFilePath);
22855
+ writeFileSync2(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
22856
+ renameSync2(tmpPath, this.portFilePath);
22815
22857
  log(`RPC server listening on 127.0.0.1:${this.port}`);
22816
22858
  } catch (err) {
22817
22859
  warn(`Failed to write RPC port file: ${err}`);
@@ -22826,6 +22868,7 @@ class AftRpcServer {
22826
22868
  this.server.close();
22827
22869
  this.server = null;
22828
22870
  }
22871
+ this.token = null;
22829
22872
  try {
22830
22873
  unlinkSync3(this.portFilePath);
22831
22874
  } catch {}
@@ -22869,8 +22912,14 @@ class AftRpcServer {
22869
22912
  res.end(JSON.stringify({ error: "Invalid JSON" }));
22870
22913
  return;
22871
22914
  }
22872
- log(`RPC call: ${method} params=${JSON.stringify(params).slice(0, 200)}`);
22873
- handler(params).then((result) => {
22915
+ if (params.token !== this.token) {
22916
+ res.writeHead(403, { "Content-Type": "application/json" });
22917
+ res.end(JSON.stringify({ error: "Forbidden" }));
22918
+ return;
22919
+ }
22920
+ const { token: _token, ...handlerParams } = params;
22921
+ log(`RPC call: ${method} params=${JSON.stringify(handlerParams).slice(0, 200)}`);
22922
+ handler(handlerParams).then((result) => {
22874
22923
  log(`RPC result: ${method} => ${JSON.stringify(result).slice(0, 200)}`);
22875
22924
  res.writeHead(200, { "Content-Type": "application/json" });
22876
22925
  res.end(JSON.stringify(result));
@@ -23112,6 +23161,7 @@ function ensureTuiPluginEntry() {
23112
23161
 
23113
23162
  // src/shared/url-fetch.ts
23114
23163
  import { createHash as createHash2 } from "crypto";
23164
+ import { lookup } from "dns/promises";
23115
23165
  import {
23116
23166
  existsSync as existsSync7,
23117
23167
  mkdirSync as mkdirSync7,
@@ -23120,10 +23170,12 @@ import {
23120
23170
  unlinkSync as unlinkSync4,
23121
23171
  writeFileSync as writeFileSync4
23122
23172
  } from "fs";
23173
+ import { isIP } from "net";
23123
23174
  import { join as join11 } from "path";
23124
23175
  var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
23125
23176
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
23126
23177
  var FETCH_TIMEOUT_MS = 30000;
23178
+ var MAX_REDIRECTS = 5;
23127
23179
  function cacheDir(storageDir) {
23128
23180
  return join11(storageDir, "url_cache");
23129
23181
  }
@@ -23136,6 +23188,138 @@ function metaPath(storageDir, hash2) {
23136
23188
  function contentPath(storageDir, hash2, extension) {
23137
23189
  return join11(cacheDir(storageDir), `${hash2}${extension}`);
23138
23190
  }
23191
+ function _isPrivateIpv4(address) {
23192
+ const parts = address.split(".").map((part) => Number.parseInt(part, 10));
23193
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part)))
23194
+ return true;
23195
+ const [a, b] = parts;
23196
+ return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254 || a >= 224;
23197
+ }
23198
+ function expandIpv6(addr) {
23199
+ if (addr === "::")
23200
+ return [0, 0, 0, 0, 0, 0, 0, 0];
23201
+ const dcMatches = addr.match(/::/g);
23202
+ if (dcMatches && dcMatches.length > 1)
23203
+ return null;
23204
+ let normalized = addr;
23205
+ const lastColon = normalized.lastIndexOf(":");
23206
+ if (lastColon !== -1) {
23207
+ const tail = normalized.slice(lastColon + 1);
23208
+ if (tail.includes(".")) {
23209
+ const octets = tail.split(".").map((p) => Number.parseInt(p, 10));
23210
+ if (octets.length !== 4 || octets.some((o) => Number.isNaN(o) || o < 0 || o > 255)) {
23211
+ return null;
23212
+ }
23213
+ const h1 = (octets[0] << 8 | octets[1]).toString(16);
23214
+ const h2 = (octets[2] << 8 | octets[3]).toString(16);
23215
+ normalized = `${normalized.slice(0, lastColon)}:${h1}:${h2}`;
23216
+ }
23217
+ }
23218
+ let parts;
23219
+ if (normalized.includes("::")) {
23220
+ const [left, right] = normalized.split("::");
23221
+ const leftParts = left ? left.split(":") : [];
23222
+ const rightParts = right ? right.split(":") : [];
23223
+ const fill = 8 - leftParts.length - rightParts.length;
23224
+ if (fill < 0)
23225
+ return null;
23226
+ parts = [...leftParts, ...Array(fill).fill("0"), ...rightParts];
23227
+ } else {
23228
+ parts = normalized.split(":");
23229
+ }
23230
+ if (parts.length !== 8)
23231
+ return null;
23232
+ const hextets = parts.map((p) => {
23233
+ const n = Number.parseInt(p, 16);
23234
+ return Number.isNaN(n) || n < 0 || n > 65535 ? -1 : n;
23235
+ });
23236
+ if (hextets.some((h) => h === -1))
23237
+ return null;
23238
+ return hextets;
23239
+ }
23240
+ function isPrivateIp(address) {
23241
+ if (!address.includes(":")) {
23242
+ return _isPrivateIpv4(address);
23243
+ }
23244
+ const lower = address.toLowerCase();
23245
+ const noZone = lower.split("%")[0];
23246
+ const hextets = expandIpv6(noZone);
23247
+ if (hextets) {
23248
+ const top6Zero = hextets.slice(0, 6).every((h) => h === 0);
23249
+ const isMapped = hextets[0] === 0 && hextets[1] === 0 && hextets[2] === 0 && hextets[3] === 0 && hextets[4] === 0 && hextets[5] === 65535;
23250
+ if (isMapped || top6Zero) {
23251
+ const a = hextets[6] >> 8 & 255;
23252
+ const b = hextets[6] & 255;
23253
+ const c = hextets[7] >> 8 & 255;
23254
+ const d = hextets[7] & 255;
23255
+ const ipv43 = `${a}.${b}.${c}.${d}`;
23256
+ if (top6Zero && hextets[6] === 0 && hextets[7] <= 1) {
23257
+ return true;
23258
+ }
23259
+ return _isPrivateIpv4(ipv43);
23260
+ }
23261
+ const firstHextet = hextets[0];
23262
+ return firstHextet >= 65152 && firstHextet <= 65215 || firstHextet >= 64512 && firstHextet <= 65023 || firstHextet >= 65280;
23263
+ }
23264
+ return true;
23265
+ }
23266
+ async function assertPublicUrl(url2, allowPrivate) {
23267
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
23268
+ throw new Error(`Only http:// and https:// URLs are supported, got: ${url2.protocol}`);
23269
+ }
23270
+ if (allowPrivate)
23271
+ return;
23272
+ const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
23273
+ if (isIP(hostname3) || hostname3.includes(":")) {
23274
+ if (isPrivateIp(hostname3)) {
23275
+ throw new Error(`Blocked private URL host ${url2.hostname} (${hostname3})`);
23276
+ }
23277
+ return;
23278
+ }
23279
+ const addresses = await lookup(hostname3, { all: true, verbatim: true });
23280
+ for (const { address } of addresses) {
23281
+ if (isPrivateIp(address)) {
23282
+ throw new Error(`Blocked private URL host ${url2.hostname} (${address})`);
23283
+ }
23284
+ }
23285
+ }
23286
+ function resolveRedirectUrl(currentUrl, location) {
23287
+ if (!location) {
23288
+ throw new Error(`Redirect from ${currentUrl.href} missing Location header`);
23289
+ }
23290
+ return new URL(location, currentUrl);
23291
+ }
23292
+ async function fetchWithRedirects(startUrl, allowPrivate) {
23293
+ let currentUrl = startUrl;
23294
+ for (let redirectCount = 0;redirectCount <= MAX_REDIRECTS; redirectCount++) {
23295
+ await assertPublicUrl(currentUrl, allowPrivate);
23296
+ const controller = new AbortController;
23297
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
23298
+ let response;
23299
+ try {
23300
+ response = await fetch(currentUrl.href, {
23301
+ signal: controller.signal,
23302
+ redirect: "manual",
23303
+ headers: {
23304
+ "user-agent": "aft-opencode-plugin",
23305
+ accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, text/plain;q=0.5"
23306
+ }
23307
+ });
23308
+ } catch (err) {
23309
+ throw new Error(`Failed to fetch ${currentUrl.href}: ${err.message}`);
23310
+ } finally {
23311
+ clearTimeout(timer);
23312
+ }
23313
+ if (response.status < 300 || response.status >= 400) {
23314
+ return response;
23315
+ }
23316
+ if (redirectCount === MAX_REDIRECTS) {
23317
+ throw new Error(`Too many redirects fetching ${startUrl.href}`);
23318
+ }
23319
+ currentUrl = resolveRedirectUrl(currentUrl, response.headers.get("location"));
23320
+ }
23321
+ throw new Error(`Too many redirects fetching ${startUrl.href}`);
23322
+ }
23139
23323
  function resolveExtension(contentType) {
23140
23324
  const lower = contentType.toLowerCase().split(";")[0].split(",")[0].trim();
23141
23325
  if (lower === "text/html" || lower === "application/xhtml+xml" || lower === "application/vnd.github.html" || lower === "application/vnd.github+html") {
@@ -23149,7 +23333,7 @@ function resolveExtension(contentType) {
23149
23333
  }
23150
23334
  return null;
23151
23335
  }
23152
- async function fetchUrlToTempFile(url2, storageDir) {
23336
+ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
23153
23337
  let parsed;
23154
23338
  try {
23155
23339
  parsed = new URL(url2);
@@ -23159,6 +23343,7 @@ async function fetchUrlToTempFile(url2, storageDir) {
23159
23343
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
23160
23344
  throw new Error(`Only http:// and https:// URLs are supported, got: ${parsed.protocol}`);
23161
23345
  }
23346
+ const allowPrivate = options.allowPrivate === true;
23162
23347
  const dir = cacheDir(storageDir);
23163
23348
  mkdirSync7(dir, { recursive: true });
23164
23349
  const hash2 = hashUrl(url2);
@@ -23175,23 +23360,7 @@ async function fetchUrlToTempFile(url2, storageDir) {
23175
23360
  } catch {}
23176
23361
  }
23177
23362
  log(`Fetching URL: ${url2}`);
23178
- const controller = new AbortController;
23179
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
23180
- let response;
23181
- try {
23182
- response = await fetch(url2, {
23183
- signal: controller.signal,
23184
- redirect: "follow",
23185
- headers: {
23186
- "user-agent": "aft-opencode-plugin",
23187
- accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, text/plain;q=0.5"
23188
- }
23189
- });
23190
- } catch (err) {
23191
- throw new Error(`Failed to fetch ${url2}: ${err.message}`);
23192
- } finally {
23193
- clearTimeout(timer);
23194
- }
23363
+ const response = await fetchWithRedirects(parsed, allowPrivate);
23195
23364
  if (!response.ok) {
23196
23365
  throw new Error(`HTTP ${response.status} ${response.statusText} fetching ${url2}`);
23197
23366
  }
@@ -23230,8 +23399,8 @@ async function fetchUrlToTempFile(url2, storageDir) {
23230
23399
  const contentFile = contentPath(storageDir, hash2, extension);
23231
23400
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
23232
23401
  writeFileSync4(tmpContent, body);
23233
- const { renameSync: renameSync2 } = await import("fs");
23234
- renameSync2(tmpContent, contentFile);
23402
+ const { renameSync: renameSync3 } = await import("fs");
23403
+ renameSync3(tmpContent, contentFile);
23235
23404
  const meta3 = {
23236
23405
  url: url2,
23237
23406
  contentType,
@@ -23240,7 +23409,7 @@ async function fetchUrlToTempFile(url2, storageDir) {
23240
23409
  };
23241
23410
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
23242
23411
  writeFileSync4(tmpMeta, JSON.stringify(meta3));
23243
- renameSync2(tmpMeta, metaFile);
23412
+ renameSync3(tmpMeta, metaFile);
23244
23413
  log(`URL cached (${total} bytes): ${url2}`);
23245
23414
  return contentFile;
23246
23415
  }
@@ -23879,44 +24048,156 @@ function buildUnifiedDiff(fp, before, after) {
23879
24048
  `);
23880
24049
  const afterLines = after.split(`
23881
24050
  `);
23882
- let diff = `Index: ${fp}
24051
+ const ops = diffLines(beforeLines, afterLines);
24052
+ if (ops.every((op) => op.tag === "eq")) {
24053
+ return `Index: ${fp}
23883
24054
  ===================================================================
23884
24055
  --- ${fp}
23885
24056
  +++ ${fp}
23886
24057
  `;
23887
- let firstChange = -1;
23888
- let lastChange = -1;
23889
- const maxLen = Math.max(beforeLines.length, afterLines.length);
23890
- for (let i = 0;i < maxLen; i++) {
23891
- if ((beforeLines[i] ?? "") !== (afterLines[i] ?? "")) {
23892
- if (firstChange === -1)
23893
- firstChange = i;
23894
- lastChange = i;
23895
- }
23896
- }
23897
- if (firstChange === -1)
23898
- return diff;
23899
- const ctxStart = Math.max(0, firstChange - 2);
23900
- const ctxEnd = Math.min(maxLen - 1, lastChange + 2);
23901
- diff += `@@ -${ctxStart + 1},${Math.min(beforeLines.length, ctxEnd + 1) - ctxStart} +${ctxStart + 1},${Math.min(afterLines.length, ctxEnd + 1) - ctxStart} @@
23902
- `;
23903
- for (let i = ctxStart;i <= ctxEnd; i++) {
23904
- const bl = i < beforeLines.length ? beforeLines[i] : undefined;
23905
- const al = i < afterLines.length ? afterLines[i] : undefined;
23906
- if (bl === al) {
23907
- diff += ` ${bl}
24058
+ }
24059
+ const CONTEXT = 3;
24060
+ const HUNK_GAP = CONTEXT * 2;
24061
+ const hunks = groupIntoHunks(ops, CONTEXT, HUNK_GAP, beforeLines.length, afterLines.length);
24062
+ let diff = `Index: ${fp}
24063
+ ===================================================================
24064
+ --- ${fp}
24065
+ +++ ${fp}
23908
24066
  `;
23909
- } else {
23910
- if (bl !== undefined)
23911
- diff += `-${bl}
24067
+ for (const hunk of hunks) {
24068
+ diff += `@@ -${hunk.beforeStart},${hunk.beforeCount} +${hunk.afterStart},${hunk.afterCount} @@
23912
24069
  `;
23913
- if (al !== undefined)
23914
- diff += `+${al}
24070
+ for (const line of hunk.lines) {
24071
+ diff += `${line}
23915
24072
  `;
23916
24073
  }
23917
24074
  }
23918
24075
  return diff;
23919
24076
  }
24077
+ function diffLines(a, b) {
24078
+ const n = a.length;
24079
+ const m = b.length;
24080
+ const dp = new Uint32Array((n + 1) * (m + 1));
24081
+ const w = m + 1;
24082
+ for (let i2 = 1;i2 <= n; i2++) {
24083
+ for (let j2 = 1;j2 <= m; j2++) {
24084
+ if (a[i2 - 1] === b[j2 - 1]) {
24085
+ dp[i2 * w + j2] = dp[(i2 - 1) * w + (j2 - 1)] + 1;
24086
+ } else {
24087
+ const up = dp[(i2 - 1) * w + j2];
24088
+ const left = dp[i2 * w + (j2 - 1)];
24089
+ dp[i2 * w + j2] = up >= left ? up : left;
24090
+ }
24091
+ }
24092
+ }
24093
+ const ops = [];
24094
+ let i = n;
24095
+ let j = m;
24096
+ while (i > 0 && j > 0) {
24097
+ if (a[i - 1] === b[j - 1]) {
24098
+ ops.push({ tag: "eq", beforeIdx: i - 1, afterIdx: j - 1, line: a[i - 1] });
24099
+ i--;
24100
+ j--;
24101
+ } else if (dp[(i - 1) * w + j] >= dp[i * w + (j - 1)]) {
24102
+ ops.push({ tag: "del", beforeIdx: i - 1, line: a[i - 1] });
24103
+ i--;
24104
+ } else {
24105
+ ops.push({ tag: "ins", afterIdx: j - 1, line: b[j - 1] });
24106
+ j--;
24107
+ }
24108
+ }
24109
+ while (i > 0) {
24110
+ ops.push({ tag: "del", beforeIdx: i - 1, line: a[i - 1] });
24111
+ i--;
24112
+ }
24113
+ while (j > 0) {
24114
+ ops.push({ tag: "ins", afterIdx: j - 1, line: b[j - 1] });
24115
+ j--;
24116
+ }
24117
+ ops.reverse();
24118
+ return ops;
24119
+ }
24120
+ function groupIntoHunks(ops, context, gap, beforeLen, afterLen) {
24121
+ const changeIdx = [];
24122
+ for (let k = 0;k < ops.length; k++) {
24123
+ if (ops[k].tag !== "eq")
24124
+ changeIdx.push(k);
24125
+ }
24126
+ if (changeIdx.length === 0)
24127
+ return [];
24128
+ const ranges = [];
24129
+ for (const idx of changeIdx) {
24130
+ const start = Math.max(0, idx - context);
24131
+ const end = Math.min(ops.length - 1, idx + context);
24132
+ if (ranges.length > 0 && start <= ranges[ranges.length - 1][1] + gap) {
24133
+ ranges[ranges.length - 1][1] = Math.max(ranges[ranges.length - 1][1], end);
24134
+ } else {
24135
+ ranges.push([start, end]);
24136
+ }
24137
+ }
24138
+ const hunks = [];
24139
+ for (const [start, end] of ranges) {
24140
+ let beforeStart = -1;
24141
+ let afterStart = -1;
24142
+ let beforeCount = 0;
24143
+ let afterCount = 0;
24144
+ const lines = [];
24145
+ for (let k = start;k <= end; k++) {
24146
+ const op = ops[k];
24147
+ if (op.tag === "eq") {
24148
+ if (beforeStart === -1)
24149
+ beforeStart = op.beforeIdx + 1;
24150
+ if (afterStart === -1)
24151
+ afterStart = op.afterIdx + 1;
24152
+ beforeCount++;
24153
+ afterCount++;
24154
+ lines.push(` ${op.line}`);
24155
+ } else if (op.tag === "del") {
24156
+ if (beforeStart === -1)
24157
+ beforeStart = op.beforeIdx + 1;
24158
+ if (afterStart === -1) {
24159
+ afterStart = inferAfterStart(ops, k, afterLen);
24160
+ }
24161
+ beforeCount++;
24162
+ lines.push(`-${op.line}`);
24163
+ } else {
24164
+ if (afterStart === -1)
24165
+ afterStart = op.afterIdx + 1;
24166
+ if (beforeStart === -1) {
24167
+ beforeStart = inferBeforeStart(ops, k, beforeLen);
24168
+ }
24169
+ afterCount++;
24170
+ lines.push(`+${op.line}`);
24171
+ }
24172
+ }
24173
+ if (beforeCount === 0)
24174
+ beforeStart = 0;
24175
+ if (afterCount === 0)
24176
+ afterStart = 0;
24177
+ hunks.push({ beforeStart, beforeCount, afterStart, afterCount, lines });
24178
+ }
24179
+ return hunks;
24180
+ }
24181
+ function inferAfterStart(ops, from, afterLen) {
24182
+ for (let k = from;k < ops.length; k++) {
24183
+ const op = ops[k];
24184
+ if (op.tag === "eq")
24185
+ return op.afterIdx + 1;
24186
+ if (op.tag === "ins")
24187
+ return op.afterIdx + 1;
24188
+ }
24189
+ return afterLen;
24190
+ }
24191
+ function inferBeforeStart(ops, from, beforeLen) {
24192
+ for (let k = from;k < ops.length; k++) {
24193
+ const op = ops[k];
24194
+ if (op.tag === "eq")
24195
+ return op.beforeIdx + 1;
24196
+ if (op.tag === "del")
24197
+ return op.beforeIdx + 1;
24198
+ }
24199
+ return beforeLen;
24200
+ }
23920
24201
  var z3 = tool3.schema;
23921
24202
  var READ_DESCRIPTION = `Read file contents or list directory entries.
23922
24203
 
@@ -24380,22 +24661,41 @@ function createApplyPatchTool(ctx) {
24380
24661
  if (hunks.length === 0) {
24381
24662
  throw new Error("Empty patch: no file operations found");
24382
24663
  }
24383
- const allPaths = hunks.map((h) => path4.relative(context.worktree, path4.resolve(context.directory, h.path)));
24664
+ const affectedAbs = new Set;
24665
+ const newlyCreatedAbs = new Set;
24666
+ for (const h of hunks) {
24667
+ const srcAbs = path4.resolve(context.directory, h.path);
24668
+ affectedAbs.add(srcAbs);
24669
+ if (h.type === "add") {
24670
+ newlyCreatedAbs.add(srcAbs);
24671
+ }
24672
+ if (h.type === "update" && h.move_path) {
24673
+ const dstAbs = path4.resolve(context.directory, h.move_path);
24674
+ affectedAbs.add(dstAbs);
24675
+ if (!fs3.existsSync(dstAbs)) {
24676
+ newlyCreatedAbs.add(dstAbs);
24677
+ }
24678
+ }
24679
+ }
24680
+ const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
24384
24681
  await context.ask({
24385
24682
  permission: "edit",
24386
- patterns: allPaths,
24683
+ patterns: relPaths,
24387
24684
  always: ["*"],
24388
24685
  metadata: {}
24389
24686
  });
24687
+ const checkpointPaths = Array.from(affectedAbs).filter((abs) => !newlyCreatedAbs.has(abs));
24390
24688
  const checkpointName = `apply_patch_${Date.now()}`;
24391
24689
  let checkpointCreated = false;
24392
- try {
24393
- await callBridge(ctx, context, "checkpoint", {
24394
- name: checkpointName,
24395
- files: allPaths.map((p) => path4.resolve(context.directory, p))
24396
- });
24397
- checkpointCreated = true;
24398
- } catch {}
24690
+ if (checkpointPaths.length > 0) {
24691
+ try {
24692
+ await callBridge(ctx, context, "checkpoint", {
24693
+ name: checkpointName,
24694
+ files: checkpointPaths
24695
+ });
24696
+ checkpointCreated = true;
24697
+ } catch {}
24698
+ }
24399
24699
  const results = [];
24400
24700
  const perFileDiffs = [];
24401
24701
  let patchFailed = false;
@@ -24472,16 +24772,32 @@ ${diagLines}`);
24472
24772
  }
24473
24773
  }
24474
24774
  if (patchFailed) {
24775
+ const rollbackNotes = [];
24475
24776
  if (checkpointCreated) {
24476
24777
  try {
24477
24778
  await callBridge(ctx, context, "restore_checkpoint", { name: checkpointName });
24478
- results.push("Patch failed \u2014 restored files to pre-patch state.");
24779
+ rollbackNotes.push("restored pre-existing files from checkpoint");
24479
24780
  } catch {
24480
- results.push("Patch failed \u2014 checkpoint restore also failed, files may be inconsistent.");
24781
+ rollbackNotes.push("checkpoint restore FAILED, pre-existing files may be inconsistent");
24782
+ }
24783
+ } else if (checkpointPaths.length > 0) {
24784
+ rollbackNotes.push("no checkpoint was created, pre-existing files may be inconsistent");
24785
+ }
24786
+ let newlyDeleted = 0;
24787
+ for (const createdAbs of newlyCreatedAbs) {
24788
+ if (!fs3.existsSync(createdAbs))
24789
+ continue;
24790
+ try {
24791
+ await callBridge(ctx, context, "delete_file", { file: createdAbs });
24792
+ newlyDeleted++;
24793
+ } catch {
24794
+ rollbackNotes.push(`failed to delete newly-created ${path4.relative(context.worktree, createdAbs)}`);
24481
24795
  }
24482
- } else {
24483
- results.push("Patch failed \u2014 no checkpoint was created, files may be inconsistent.");
24484
24796
  }
24797
+ if (newlyDeleted > 0) {
24798
+ rollbackNotes.push(`removed ${newlyDeleted} newly-created file(s)`);
24799
+ }
24800
+ results.push(rollbackNotes.length > 0 ? `Patch failed \u2014 ${rollbackNotes.join("; ")}.` : "Patch failed \u2014 nothing to roll back.");
24485
24801
  return results.join(`
24486
24802
  `);
24487
24803
  }
@@ -24852,7 +25168,9 @@ function readingTools(ctx) {
24852
25168
  throw new Error("Provide exactly ONE of 'filePath', 'files', 'directory', or 'url' \u2014 not multiple");
24853
25169
  }
24854
25170
  if (hasUrl) {
24855
- const cachedPath = await fetchUrlToTempFile(args.url, ctx.storageDir);
25171
+ const cachedPath = await fetchUrlToTempFile(args.url, ctx.storageDir, {
25172
+ allowPrivate: ctx.config.url_fetch_allow_private === true
25173
+ });
24856
25174
  const response2 = await callBridge(ctx, context, "outline", { file: cachedPath });
24857
25175
  if (response2.success === false) {
24858
25176
  throw new Error(response2.message || "outline failed");
@@ -24919,7 +25237,9 @@ Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lo
24919
25237
  if (hasFilePath && hasUrl) {
24920
25238
  throw new Error("Provide exactly ONE of 'filePath' or 'url' \u2014 not both");
24921
25239
  }
24922
- const file2 = hasUrl ? await fetchUrlToTempFile(args.url, ctx.storageDir) : args.filePath;
25240
+ const file2 = hasUrl ? await fetchUrlToTempFile(args.url, ctx.storageDir, {
25241
+ allowPrivate: ctx.config.url_fetch_allow_private === true
25242
+ }) : args.filePath;
24923
25243
  if (Array.isArray(args.symbols) && args.symbols.length > 0) {
24924
25244
  const results = await Promise.all(args.symbols.map((sym) => {
24925
25245
  const params2 = { file: file2, symbol: sym };
@@ -25524,8 +25844,7 @@ var plugin = async (input) => {
25524
25844
  configOverrides.formatter = aftConfig.formatter;
25525
25845
  if (aftConfig.checker !== undefined)
25526
25846
  configOverrides.checker = aftConfig.checker;
25527
- if (aftConfig.restrict_to_project_root !== undefined)
25528
- configOverrides.restrict_to_project_root = aftConfig.restrict_to_project_root;
25847
+ configOverrides.restrict_to_project_root = aftConfig.restrict_to_project_root ?? true;
25529
25848
  if (aftConfig.experimental_search_index !== undefined)
25530
25849
  configOverrides.experimental_search_index = aftConfig.experimental_search_index;
25531
25850
  if (aftConfig.experimental_semantic_search !== undefined)
@@ -25539,13 +25858,15 @@ var plugin = async (input) => {
25539
25858
  configOverrides.storage_dir = join13(dataHome, "opencode", "storage", "plugin", "aft");
25540
25859
  if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend) {
25541
25860
  const storageDir2 = configOverrides.storage_dir;
25542
- ensureOnnxRuntime(storageDir2).then((ortDir) => {
25543
- if (ortDir) {
25544
- configOverrides._ort_dylib_dir = ortDir;
25545
- } else if (!isOrtAutoDownloadSupported()) {
25546
- warn(`Semantic search requires ONNX Runtime. Install: ${getManualInstallHint()}`);
25547
- }
25548
- }, (err) => warn(`ONNX Runtime resolution failed: ${err}`));
25861
+ const ortDylibDir = await ensureOnnxRuntime(storageDir2).catch((err) => {
25862
+ warn(`ONNX Runtime setup failed: ${err instanceof Error ? err.message : String(err)}. Semantic search will be unavailable.`);
25863
+ return null;
25864
+ });
25865
+ if (ortDylibDir) {
25866
+ configOverrides._ort_dylib_dir = ortDylibDir;
25867
+ } else if (!isOrtAutoDownloadSupported()) {
25868
+ warn(`Semantic search requires ONNX Runtime. Install: ${getManualInstallHint()}`);
25869
+ }
25549
25870
  }
25550
25871
  let versionUpgradeAttempted = null;
25551
25872
  const pool = new BridgePool(binaryPath, {
@@ -25598,7 +25919,7 @@ var plugin = async (input) => {
25598
25919
  if (storageDir) {
25599
25920
  const versionFile = join13(storageDir, "last_announced_version");
25600
25921
  try {
25601
- if (existsSync8(versionFile)) {
25922
+ if (existsSync9(versionFile)) {
25602
25923
  const lastVersion = readFileSync5(versionFile, "utf-8").trim();
25603
25924
  if (lastVersion === ANNOUNCEMENT_VERSION)
25604
25925
  return { show: false };