@cortexkit/aft-pi 0.16.0 → 0.17.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
@@ -17,8 +17,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
17
17
 
18
18
  // src/index.ts
19
19
  import { createRequire as createRequire3 } from "node:module";
20
- import { homedir as homedir7 } from "node:os";
21
- import { join as join9 } from "node:path";
20
+ import { homedir as homedir8 } from "node:os";
21
+ import { join as join12 } from "node:path";
22
22
 
23
23
  // src/shared/status.ts
24
24
  function asRecord(value) {
@@ -13880,7 +13880,10 @@ var LspServerSchema = LspServerEntrySchema.extend({
13880
13880
  var LspConfigSchema = exports_external.object({
13881
13881
  servers: exports_external.record(exports_external.string().trim().min(1), LspServerEntrySchema).optional(),
13882
13882
  disabled: exports_external.array(exports_external.string().trim().min(1)).optional(),
13883
- python: exports_external.enum(["pyright", "ty", "auto"]).optional()
13883
+ python: exports_external.enum(["pyright", "ty", "auto"]).optional(),
13884
+ auto_install: exports_external.boolean().optional(),
13885
+ grace_days: exports_external.number().int().positive().optional(),
13886
+ versions: exports_external.record(exports_external.string().trim().min(1), exports_external.string().trim().min(1)).optional()
13884
13887
  });
13885
13888
  var AftConfigSchema = exports_external.object({
13886
13889
  format_on_edit: exports_external.boolean().optional(),
@@ -13908,7 +13911,7 @@ function resolveLspConfigForConfigure(config2) {
13908
13911
  switch (config2.lsp?.python ?? "auto") {
13909
13912
  case "ty":
13910
13913
  experimentalTy = true;
13911
- disabled.add("pyright");
13914
+ disabled.add("python");
13912
13915
  break;
13913
13916
  case "pyright":
13914
13917
  experimentalTy = false;
@@ -14060,26 +14063,76 @@ function mergeSemanticConfig(base, override) {
14060
14063
  return;
14061
14064
  return Object.fromEntries(Object.entries(semantic).filter(([, v]) => v !== undefined));
14062
14065
  }
14066
+ function mergeLspConfig(base, override) {
14067
+ const projectSafe = {};
14068
+ if (override?.python !== undefined)
14069
+ projectSafe.python = override.python;
14070
+ const userDisabled = base?.disabled ?? [];
14071
+ const lsp = {
14072
+ ...base,
14073
+ ...projectSafe,
14074
+ ...userDisabled.length > 0 ? { disabled: [...userDisabled] } : {}
14075
+ };
14076
+ if (Object.values(lsp).every((v) => v === undefined))
14077
+ return;
14078
+ return Object.fromEntries(Object.entries(lsp).filter(([, v]) => v !== undefined));
14079
+ }
14080
+ function getProjectLspStrippedKeys(lsp) {
14081
+ if (!lsp)
14082
+ return [];
14083
+ const strippedKeys = [];
14084
+ if (lsp.servers !== undefined)
14085
+ strippedKeys.push("lsp.servers");
14086
+ if (lsp.versions !== undefined)
14087
+ strippedKeys.push("lsp.versions");
14088
+ if (lsp.auto_install !== undefined)
14089
+ strippedKeys.push("lsp.auto_install");
14090
+ if (lsp.grace_days !== undefined)
14091
+ strippedKeys.push("lsp.grace_days");
14092
+ if (lsp.disabled !== undefined)
14093
+ strippedKeys.push("lsp.disabled");
14094
+ return strippedKeys;
14095
+ }
14096
+ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
14097
+ "tool_surface",
14098
+ "format_on_edit",
14099
+ "validate_on_edit",
14100
+ "experimental_search_index",
14101
+ "experimental_semantic_search",
14102
+ "experimental_lsp_ty"
14103
+ ]);
14104
+ function pickProjectSafeFields(override) {
14105
+ const safe = {};
14106
+ for (const key of PROJECT_SAFE_TOP_LEVEL_FIELDS) {
14107
+ if (override[key] !== undefined) {
14108
+ safe[key] = override[key];
14109
+ }
14110
+ }
14111
+ return safe;
14112
+ }
14113
+ function getStrippedTopLevelKeys(override) {
14114
+ const stripped = [];
14115
+ if (override.restrict_to_project_root !== undefined)
14116
+ stripped.push("restrict_to_project_root");
14117
+ if (override.url_fetch_allow_private !== undefined)
14118
+ stripped.push("url_fetch_allow_private");
14119
+ if (override.max_callgraph_files !== undefined)
14120
+ stripped.push("max_callgraph_files");
14121
+ return stripped;
14122
+ }
14063
14123
  function mergeConfigs(base, override) {
14064
14124
  const disabledTools = [...base.disabled_tools ?? [], ...override.disabled_tools ?? []];
14065
14125
  const formatter = { ...base.formatter, ...override.formatter };
14066
14126
  const checker = { ...base.checker, ...override.checker };
14067
14127
  const semantic = mergeSemanticConfig(base.semantic, override.semantic);
14068
- const lspServers = { ...base.lsp?.servers, ...override.lsp?.servers };
14069
- const disabledLsp = [...base.lsp?.disabled ?? [], ...override.lsp?.disabled ?? []];
14070
- const lsp = {
14071
- ...base.lsp,
14072
- ...override.lsp,
14073
- ...Object.keys(lspServers).length > 0 ? { servers: lspServers } : {},
14074
- ...disabledLsp.length > 0 ? { disabled: [...new Set(disabledLsp)] } : {}
14075
- };
14076
- const { semantic: _stripSemantic, ...safeOverride } = override;
14128
+ const lsp = mergeLspConfig(base.lsp, override.lsp);
14129
+ const safeOverride = pickProjectSafeFields(override);
14077
14130
  return {
14078
14131
  ...base,
14079
14132
  ...safeOverride,
14080
14133
  ...Object.keys(formatter).length > 0 ? { formatter } : {},
14081
14134
  ...Object.keys(checker).length > 0 ? { checker } : {},
14082
- ...Object.values(lsp).some((value) => value !== undefined) ? { lsp } : {},
14135
+ ...lsp ? { lsp } : {},
14083
14136
  semantic,
14084
14137
  ...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
14085
14138
  };
@@ -14100,14 +14153,1377 @@ function loadAftConfig(projectDirectory) {
14100
14153
  if (projectConfig.semantic?.backend !== undefined || projectConfig.semantic?.base_url !== undefined || projectConfig.semantic?.api_key_env !== undefined) {
14101
14154
  warn("Ignoring semantic.backend/base_url/api_key_env from project config (security: use user config for external backends)");
14102
14155
  }
14156
+ const strippedLspKeys = getProjectLspStrippedKeys(projectConfig.lsp);
14157
+ if (strippedLspKeys.length > 0) {
14158
+ warn(`Ignoring ${strippedLspKeys.join(", ")} from project config ${projectConfigPath} (security: these LSP settings only honor user-level config)`);
14159
+ }
14160
+ const strippedTopLevelKeys = getStrippedTopLevelKeys(projectConfig);
14161
+ if (strippedTopLevelKeys.length > 0) {
14162
+ warn(`Ignoring ${strippedTopLevelKeys.join(", ")} from project config ${projectConfigPath} (security: these settings only honor user-level config — a project should not weaken security boundaries for the user)`);
14163
+ }
14103
14164
  config2 = mergeConfigs(config2, projectConfig);
14104
14165
  }
14105
14166
  return config2;
14106
14167
  }
14107
14168
 
14108
- // src/notifications.ts
14109
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
14169
+ // src/lsp-auto-install.ts
14170
+ import { spawn } from "node:child_process";
14171
+ import { createHash } from "node:crypto";
14172
+ import { createReadStream, statSync as statSync2 } from "node:fs";
14173
+
14174
+ // src/lsp-cache.ts
14175
+ import {
14176
+ closeSync,
14177
+ existsSync as existsSync2,
14178
+ mkdirSync,
14179
+ openSync,
14180
+ readFileSync as readFileSync2,
14181
+ statSync,
14182
+ unlinkSync,
14183
+ writeFileSync
14184
+ } from "node:fs";
14185
+ import { homedir as homedir2 } from "node:os";
14110
14186
  import { join as join3 } from "node:path";
14187
+ function aftCacheBase() {
14188
+ const override = process.env.AFT_CACHE_DIR;
14189
+ if (override && override.length > 0)
14190
+ return override;
14191
+ if (process.platform === "win32") {
14192
+ const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
14193
+ const base2 = localAppData || join3(homedir2(), "AppData", "Local");
14194
+ return join3(base2, "aft");
14195
+ }
14196
+ const base = process.env.XDG_CACHE_HOME || join3(homedir2(), ".cache");
14197
+ return join3(base, "aft");
14198
+ }
14199
+ function lspCacheRoot() {
14200
+ return join3(aftCacheBase(), "lsp-packages");
14201
+ }
14202
+ function lspPackageDir(npmPackage) {
14203
+ return join3(lspCacheRoot(), encodeURIComponent(npmPackage));
14204
+ }
14205
+ function lspBinaryPath(npmPackage, binary) {
14206
+ return join3(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
14207
+ }
14208
+ function lspBinDir(npmPackage) {
14209
+ return join3(lspPackageDir(npmPackage), "node_modules", ".bin");
14210
+ }
14211
+ function isInstalled(npmPackage, binary) {
14212
+ for (const candidate of lspBinaryCandidates(binary)) {
14213
+ try {
14214
+ if (statSync(join3(lspBinDir(npmPackage), candidate)).isFile())
14215
+ return true;
14216
+ } catch {}
14217
+ }
14218
+ return false;
14219
+ }
14220
+ function lspBinaryCandidates(binary) {
14221
+ if (process.platform !== "win32")
14222
+ return [binary];
14223
+ return [binary, `${binary}.cmd`, `${binary}.exe`, `${binary}.bat`];
14224
+ }
14225
+ var INSTALLED_META_FILE = ".aft-installed";
14226
+ function writeInstalledMetaIn(installDir, version2, sha256) {
14227
+ try {
14228
+ mkdirSync(installDir, { recursive: true });
14229
+ const meta3 = {
14230
+ version: version2,
14231
+ installedAt: new Date().toISOString(),
14232
+ ...sha256 ? { sha256 } : {}
14233
+ };
14234
+ writeFileSync(join3(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
14235
+ } catch (err) {
14236
+ log(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
14237
+ }
14238
+ }
14239
+ function readInstalledMetaIn(installDir) {
14240
+ const path2 = join3(installDir, INSTALLED_META_FILE);
14241
+ try {
14242
+ if (!statSync(path2).isFile())
14243
+ return null;
14244
+ const raw = readFileSync2(path2, "utf8");
14245
+ const parsed = JSON.parse(raw);
14246
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
14247
+ return null;
14248
+ return {
14249
+ version: parsed.version,
14250
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
14251
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {}
14252
+ };
14253
+ } catch {
14254
+ return null;
14255
+ }
14256
+ }
14257
+ function writeInstalledMeta(packageKey, version2, sha256) {
14258
+ writeInstalledMetaIn(lspPackageDir(packageKey), version2, sha256);
14259
+ }
14260
+ function readInstalledMeta(packageKey) {
14261
+ return readInstalledMetaIn(lspPackageDir(packageKey));
14262
+ }
14263
+ function lockPath(npmPackage) {
14264
+ return join3(lspPackageDir(npmPackage), ".aft-installing");
14265
+ }
14266
+ var STALE_LOCK_MS = 30 * 60 * 1000;
14267
+ function acquireInstallLock(lockKey) {
14268
+ mkdirSync(lspPackageDir(lockKey), { recursive: true });
14269
+ const lock = lockPath(lockKey);
14270
+ const tryClaim = () => {
14271
+ try {
14272
+ const fd = openSync(lock, "wx");
14273
+ try {
14274
+ writeFileSync(fd, `${process.pid}
14275
+ ${new Date().toISOString()}
14276
+ `);
14277
+ } finally {
14278
+ closeSync(fd);
14279
+ }
14280
+ return true;
14281
+ } catch (err) {
14282
+ const code = err.code;
14283
+ if (code === "EEXIST")
14284
+ return false;
14285
+ warn(`[lsp] unexpected error acquiring install lock for ${lockKey}: ${err}`);
14286
+ return false;
14287
+ }
14288
+ };
14289
+ if (tryClaim())
14290
+ return true;
14291
+ let owningPid = null;
14292
+ let lockMtimeMs = 0;
14293
+ try {
14294
+ const raw = readFileSync2(lock, "utf8");
14295
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
14296
+ const parsed = Number.parseInt(firstLine, 10);
14297
+ if (Number.isFinite(parsed) && parsed > 0)
14298
+ owningPid = parsed;
14299
+ lockMtimeMs = statSync(lock).mtimeMs;
14300
+ } catch {
14301
+ return tryClaim();
14302
+ }
14303
+ const age = Date.now() - lockMtimeMs;
14304
+ const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS;
14305
+ const skipLiveness = process.platform === "win32";
14306
+ const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive(owningPid);
14307
+ if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
14308
+ return false;
14309
+ }
14310
+ log(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
14311
+ try {
14312
+ unlinkSync(lock);
14313
+ } catch {}
14314
+ return tryClaim();
14315
+ }
14316
+ function isProcessAlive(pid) {
14317
+ try {
14318
+ process.kill(pid, 0);
14319
+ return true;
14320
+ } catch (err) {
14321
+ const code = err.code;
14322
+ if (code === "ESRCH")
14323
+ return false;
14324
+ return true;
14325
+ }
14326
+ }
14327
+ function releaseInstallLock(lockKey) {
14328
+ const lock = lockPath(lockKey);
14329
+ try {
14330
+ if (existsSync2(lock)) {
14331
+ unlinkSync(lock);
14332
+ }
14333
+ } catch (err) {
14334
+ warn(`[lsp] failed to release install lock for ${lockKey}: ${err}`);
14335
+ }
14336
+ }
14337
+ async function withInstallLock(lockKey, task) {
14338
+ if (!acquireInstallLock(lockKey))
14339
+ return null;
14340
+ try {
14341
+ return await task();
14342
+ } finally {
14343
+ releaseInstallLock(lockKey);
14344
+ }
14345
+ }
14346
+ var VERSION_CHECK_FILE = ".aft-version-check";
14347
+ function readVersionCheck(npmPackage) {
14348
+ const file2 = join3(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
14349
+ try {
14350
+ const raw = readFileSync2(file2, "utf8");
14351
+ const parsed = JSON.parse(raw);
14352
+ if (typeof parsed.last_checked === "string") {
14353
+ return {
14354
+ last_checked: parsed.last_checked,
14355
+ latest_eligible: typeof parsed.latest_eligible === "string" ? parsed.latest_eligible : null
14356
+ };
14357
+ }
14358
+ return null;
14359
+ } catch {
14360
+ return null;
14361
+ }
14362
+ }
14363
+ function writeVersionCheck(npmPackage, latest) {
14364
+ mkdirSync(lspPackageDir(npmPackage), { recursive: true });
14365
+ const file2 = join3(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
14366
+ const record2 = {
14367
+ last_checked: new Date().toISOString(),
14368
+ latest_eligible: latest
14369
+ };
14370
+ writeFileSync(file2, JSON.stringify(record2, null, 2));
14371
+ }
14372
+ function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
14373
+ if (!record2)
14374
+ return true;
14375
+ const age = Date.now() - new Date(record2.last_checked).getTime();
14376
+ if (Number.isNaN(age) || age < 0)
14377
+ return true;
14378
+ return age >= weeklyCheckIntervalMs;
14379
+ }
14380
+
14381
+ // src/lsp-github-probe.ts
14382
+ function pickEligibleRelease(releases, graceDays, now = Date.now()) {
14383
+ const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
14384
+ const candidates = releases.filter((r) => !r.draft && !r.prerelease && typeof r.published_at === "string").map((r) => {
14385
+ const ts = Date.parse(r.published_at);
14386
+ return { release: r, ts };
14387
+ }).filter((c) => !Number.isNaN(c.ts)).sort((a, b) => b.ts - a.ts);
14388
+ const eligible = candidates.filter((c) => c.ts <= cutoff);
14389
+ const blockedByGrace = candidates.length > 0 && eligible.length === 0;
14390
+ const chosen = eligible[0]?.release;
14391
+ if (!chosen) {
14392
+ return { tag: null, assets: [], blockedByGrace };
14393
+ }
14394
+ return {
14395
+ tag: chosen.tag_name,
14396
+ assets: (chosen.assets ?? []).map((a) => ({
14397
+ name: a.name,
14398
+ url: a.browser_download_url,
14399
+ size: a.size
14400
+ })),
14401
+ blockedByGrace: false
14402
+ };
14403
+ }
14404
+ async function probeGithubReleases(githubRepo, graceDays, fetchImpl = fetch) {
14405
+ const url2 = `https://api.github.com/repos/${githubRepo}/releases?per_page=30`;
14406
+ try {
14407
+ const headers = { accept: "application/vnd.github+json" };
14408
+ if (process.env.GITHUB_TOKEN) {
14409
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
14410
+ }
14411
+ const res = await fetchImpl(url2, {
14412
+ headers,
14413
+ signal: AbortSignal.timeout(1e4)
14414
+ });
14415
+ if (!res.ok) {
14416
+ warn(`[lsp] github releases probe failed for ${githubRepo}: HTTP ${res.status}`);
14417
+ return null;
14418
+ }
14419
+ const json2 = await res.json();
14420
+ if (!Array.isArray(json2)) {
14421
+ warn(`[lsp] unexpected response shape from github releases for ${githubRepo}`);
14422
+ return null;
14423
+ }
14424
+ return pickEligibleRelease(json2, graceDays);
14425
+ } catch (err) {
14426
+ warn(`[lsp] github releases probe failed for ${githubRepo}: ${err}`);
14427
+ return null;
14428
+ }
14429
+ }
14430
+ var SAFE_VERSION_RE = /^[A-Za-z0-9._+-]+$/;
14431
+ function assertSafeVersion(version2) {
14432
+ if (!SAFE_VERSION_RE.test(version2)) {
14433
+ throw new Error(`unsafe version/tag string ${JSON.stringify(version2)}: must match ${SAFE_VERSION_RE.source}`);
14434
+ }
14435
+ }
14436
+ function stripTagV(tag) {
14437
+ assertSafeVersion(tag);
14438
+ return tag.startsWith("v") ? tag.slice(1) : tag;
14439
+ }
14440
+
14441
+ // src/lsp-npm-table.ts
14442
+ var NPM_LSP_TABLE = [
14443
+ {
14444
+ id: "typescript",
14445
+ npm: "typescript-language-server",
14446
+ binary: "typescript-language-server",
14447
+ extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
14448
+ rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
14449
+ },
14450
+ {
14451
+ id: "python",
14452
+ npm: "pyright",
14453
+ binary: "pyright-langserver",
14454
+ extensions: ["py", "pyi"],
14455
+ rootMarkers: ["pyproject.toml", "pyrightconfig.json", "requirements.txt"]
14456
+ },
14457
+ {
14458
+ id: "yaml",
14459
+ npm: "yaml-language-server",
14460
+ binary: "yaml-language-server",
14461
+ extensions: ["yaml", "yml"]
14462
+ },
14463
+ {
14464
+ id: "bash",
14465
+ npm: "bash-language-server",
14466
+ binary: "bash-language-server",
14467
+ extensions: ["sh", "bash", "zsh"]
14468
+ },
14469
+ {
14470
+ id: "dockerfile",
14471
+ npm: "dockerfile-language-server-nodejs",
14472
+ binary: "docker-langserver",
14473
+ extensions: ["dockerfile"],
14474
+ rootMarkers: ["Dockerfile", "dockerfile"]
14475
+ },
14476
+ {
14477
+ id: "vue",
14478
+ npm: "@vue/language-server",
14479
+ binary: "vue-language-server",
14480
+ extensions: ["vue"]
14481
+ },
14482
+ {
14483
+ id: "astro",
14484
+ npm: "@astrojs/language-server",
14485
+ binary: "astro-ls",
14486
+ extensions: ["astro"]
14487
+ },
14488
+ {
14489
+ id: "svelte",
14490
+ npm: "svelte-language-server",
14491
+ binary: "svelteserver",
14492
+ extensions: ["svelte"]
14493
+ },
14494
+ {
14495
+ id: "php-intelephense",
14496
+ npm: "intelephense",
14497
+ binary: "intelephense",
14498
+ extensions: ["php"]
14499
+ },
14500
+ {
14501
+ id: "biome",
14502
+ npm: "@biomejs/biome",
14503
+ binary: "biome",
14504
+ extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts", "json", "jsonc"],
14505
+ rootMarkers: ["biome.json", "biome.jsonc"]
14506
+ }
14507
+ ];
14508
+
14509
+ // src/lsp-project-relevance.ts
14510
+ import { existsSync as existsSync3, readdirSync } from "node:fs";
14511
+ import { join as join4 } from "node:path";
14512
+ var MAX_WALK_DIRS = 200;
14513
+ var MAX_WALK_DEPTH = 4;
14514
+ var NOISE_DIRS = new Set([
14515
+ ".git",
14516
+ ".next",
14517
+ ".venv",
14518
+ "__pycache__",
14519
+ "build",
14520
+ "dist",
14521
+ "node_modules",
14522
+ "target"
14523
+ ]);
14524
+ function hasRootMarker(projectRoot, rootMarkers) {
14525
+ if (!rootMarkers)
14526
+ return false;
14527
+ for (const marker of rootMarkers) {
14528
+ if (existsSync3(join4(projectRoot, marker)))
14529
+ return true;
14530
+ }
14531
+ return false;
14532
+ }
14533
+ function relevantExtensionsInProject(projectRoot, extToServer) {
14534
+ const wanted = new Set(Object.keys(extToServer).map((ext) => ext.toLowerCase()));
14535
+ const found = new Set;
14536
+ if (wanted.size === 0)
14537
+ return found;
14538
+ const queue = [{ dir: projectRoot, depth: 0 }];
14539
+ let visitedDirs = 0;
14540
+ while (queue.length > 0 && visitedDirs < MAX_WALK_DIRS) {
14541
+ const current = queue.shift();
14542
+ if (!current)
14543
+ break;
14544
+ visitedDirs += 1;
14545
+ let entries;
14546
+ try {
14547
+ entries = readdirSync(current.dir, { withFileTypes: true });
14548
+ } catch {
14549
+ continue;
14550
+ }
14551
+ for (const entry of entries) {
14552
+ if (entry.isDirectory()) {
14553
+ if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
14554
+ queue.push({ dir: join4(current.dir, entry.name), depth: current.depth + 1 });
14555
+ }
14556
+ continue;
14557
+ }
14558
+ if (!entry.isFile())
14559
+ continue;
14560
+ const ext = extensionOf(entry.name);
14561
+ if (ext && wanted.has(ext))
14562
+ found.add(ext);
14563
+ }
14564
+ }
14565
+ return found;
14566
+ }
14567
+ function extensionOf(fileName) {
14568
+ const dot = fileName.lastIndexOf(".");
14569
+ if (dot < 0 || dot === fileName.length - 1)
14570
+ return null;
14571
+ return fileName.slice(dot + 1).toLowerCase();
14572
+ }
14573
+
14574
+ // src/lsp-registry-probe.ts
14575
+ var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
14576
+ function pickEligibleVersion(response, graceDays, now = Date.now()) {
14577
+ const times = response.time || {};
14578
+ const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
14579
+ const candidates = [];
14580
+ for (const [version2, publishedAt] of Object.entries(times)) {
14581
+ if (version2 === "created" || version2 === "modified")
14582
+ continue;
14583
+ if (version2.includes("-"))
14584
+ continue;
14585
+ if (typeof publishedAt !== "string")
14586
+ continue;
14587
+ const ts = Date.parse(publishedAt);
14588
+ if (Number.isNaN(ts))
14589
+ continue;
14590
+ candidates.push({ version: version2, publishedAt, ts });
14591
+ }
14592
+ candidates.sort((a, b) => b.ts - a.ts);
14593
+ const eligible = candidates.filter((c) => c.ts <= cutoff);
14594
+ const blockedByGrace = candidates.length > 0 && eligible.length === 0;
14595
+ return {
14596
+ version: eligible[0]?.version ?? null,
14597
+ blockedByGrace,
14598
+ eligible: eligible.map(({ version: version2, publishedAt }) => ({ version: version2, publishedAt }))
14599
+ };
14600
+ }
14601
+ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
14602
+ const encoded = encodeURIComponent(npmPackage).replace(/^%40/, "@");
14603
+ const url2 = `${NPM_REGISTRY_BASE}/${encoded}`;
14604
+ try {
14605
+ const res = await fetchImpl(url2, {
14606
+ headers: { accept: "application/json" },
14607
+ signal: AbortSignal.timeout(1e4)
14608
+ });
14609
+ if (!res.ok) {
14610
+ warn(`[lsp] registry probe failed for ${npmPackage}: HTTP ${res.status}`);
14611
+ return null;
14612
+ }
14613
+ const json2 = await res.json();
14614
+ return pickEligibleVersion(json2, graceDays);
14615
+ } catch (err) {
14616
+ warn(`[lsp] registry probe failed for ${npmPackage}: ${err}`);
14617
+ return null;
14618
+ }
14619
+ }
14620
+
14621
+ // src/lsp-auto-install.ts
14622
+ function isProjectRelevant(spec, projectRoot, projectExtensions) {
14623
+ if (hasRootMarker(projectRoot, spec.rootMarkers))
14624
+ return true;
14625
+ const extensions = projectExtensions();
14626
+ return spec.extensions.some((ext) => extensions.has(ext.toLowerCase()));
14627
+ }
14628
+ var npmExtToServerIds = buildExtensionMap(NPM_LSP_TABLE);
14629
+ function buildExtensionMap(specs) {
14630
+ const byExt = {};
14631
+ for (const spec of specs) {
14632
+ for (const ext of spec.extensions) {
14633
+ const key = ext.toLowerCase();
14634
+ byExt[key] ??= [];
14635
+ byExt[key].push(spec.id);
14636
+ }
14637
+ }
14638
+ return byExt;
14639
+ }
14640
+ var inFlightAutoInstalls = new Set;
14641
+ function trackInFlightAutoInstall(controller, promise2) {
14642
+ const entry = { controller, promise: promise2 };
14643
+ inFlightAutoInstalls.add(entry);
14644
+ promise2.then(() => inFlightAutoInstalls.delete(entry), () => inFlightAutoInstalls.delete(entry));
14645
+ return promise2;
14646
+ }
14647
+ async function abortInFlightAutoInstalls() {
14648
+ const installs = Array.from(inFlightAutoInstalls);
14649
+ for (const install of installs) {
14650
+ install.controller.abort();
14651
+ }
14652
+ await Promise.allSettled(installs.map((install) => install.promise));
14653
+ }
14654
+ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
14655
+ const pinned = config2.versions[spec.npm];
14656
+ if (pinned) {
14657
+ assertSafeVersion(pinned);
14658
+ return { version: pinned, pinned: true, probe: null };
14659
+ }
14660
+ const cached2 = readVersionCheck(spec.npm);
14661
+ const weeklyMs = config2.graceDays * 24 * 60 * 60 * 1000;
14662
+ if (!shouldRecheckVersion(cached2, weeklyMs) && cached2?.latest_eligible) {
14663
+ return { version: cached2.latest_eligible, pinned: false, probe: null };
14664
+ }
14665
+ const probe = await probeRegistry(spec.npm, config2.graceDays, fetchImpl);
14666
+ if (!probe) {
14667
+ return {
14668
+ version: cached2?.latest_eligible ?? null,
14669
+ pinned: false,
14670
+ probe: null
14671
+ };
14672
+ }
14673
+ writeVersionCheck(spec.npm, probe.version);
14674
+ return { version: probe.version, pinned: false, probe };
14675
+ }
14676
+ function runInstall(spec, version2, cwd, signal) {
14677
+ return new Promise((resolve) => {
14678
+ const target = `${spec.npm}@${version2}`;
14679
+ log(`[lsp] installing ${target} to ${cwd}`);
14680
+ if (signal?.aborted) {
14681
+ warn(`[lsp] install ${target} aborted before spawn`);
14682
+ resolve(false);
14683
+ return;
14684
+ }
14685
+ const child = spawn("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
14686
+ stdio: ["ignore", "pipe", "pipe"]
14687
+ });
14688
+ child.unref();
14689
+ let stderrBuf = "";
14690
+ let settled = false;
14691
+ let killTimer = null;
14692
+ const cleanup = () => {
14693
+ signal?.removeEventListener("abort", onAbort);
14694
+ if (killTimer)
14695
+ clearTimeout(killTimer);
14696
+ };
14697
+ const finish = (ok) => {
14698
+ if (settled)
14699
+ return;
14700
+ settled = true;
14701
+ cleanup();
14702
+ resolve(ok);
14703
+ };
14704
+ const onAbort = () => {
14705
+ warn(`[lsp] install ${target} aborted during shutdown`);
14706
+ child.kill("SIGTERM");
14707
+ killTimer = setTimeout(() => {
14708
+ if (!settled)
14709
+ child.kill("SIGKILL");
14710
+ }, 5000);
14711
+ killTimer.unref?.();
14712
+ };
14713
+ signal?.addEventListener("abort", onAbort, { once: true });
14714
+ if (signal?.aborted)
14715
+ onAbort();
14716
+ child.stdout?.on("data", () => {});
14717
+ child.stderr?.on("data", (chunk) => {
14718
+ const text = String(chunk);
14719
+ stderrBuf += text;
14720
+ if (stderrBuf.length > 4096) {
14721
+ stderrBuf = stderrBuf.slice(stderrBuf.length - 4096);
14722
+ }
14723
+ });
14724
+ child.on("error", (err) => {
14725
+ error48(`[lsp] install ${target} failed to spawn: ${err}`);
14726
+ finish(false);
14727
+ });
14728
+ child.on("exit", (code) => {
14729
+ if (code === 0) {
14730
+ log(`[lsp] installed ${target}`);
14731
+ finish(true);
14732
+ } else {
14733
+ error48(`[lsp] install ${target} exited with code ${code}; last stderr:
14734
+ ${stderrBuf.trim()}`);
14735
+ finish(false);
14736
+ }
14737
+ });
14738
+ });
14739
+ }
14740
+ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
14741
+ const outcome = await withInstallLock(spec.npm, async () => {
14742
+ const { version: version2, probe } = await resolveTargetVersion(spec, config2, fetchImpl);
14743
+ if (!version2) {
14744
+ const installed = isInstalled(spec.npm, spec.binary);
14745
+ if (installed) {
14746
+ warn(`[lsp] no eligible version of ${spec.npm} (grace=${config2.graceDays}d); keeping existing install`);
14747
+ return { started: false, reason: "kept existing install" };
14748
+ }
14749
+ const blocked = probe?.blockedByGrace ? `all versions are within ${config2.graceDays}-day grace window` : "registry probe failed";
14750
+ warn(`[lsp] skipping ${spec.npm}: ${blocked}`);
14751
+ return { started: false, reason: blocked };
14752
+ }
14753
+ if (isInstalled(spec.npm, spec.binary)) {
14754
+ const installedMeta = readInstalledMeta(spec.npm);
14755
+ if (installedMeta && installedMeta.version === version2) {
14756
+ if (installedMeta.sha256) {
14757
+ const currentHash = await hashInstalledBinary(spec).catch((err) => {
14758
+ warn(`[lsp] could not hash existing ${spec.npm} binary for TOFU check: ${err}`);
14759
+ return null;
14760
+ });
14761
+ if (currentHash && currentHash !== installedMeta.sha256) {
14762
+ error48(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
14763
+ return {
14764
+ started: false,
14765
+ reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} — see plugin log`
14766
+ };
14767
+ }
14768
+ }
14769
+ return { started: false, reason: "already installed" };
14770
+ }
14771
+ if (installedMeta) {
14772
+ log(`[lsp] reinstalling ${spec.npm}: cached ${installedMeta.version} ≠ target ${version2}`);
14773
+ } else {
14774
+ log(`[lsp] reinstalling ${spec.npm}@${version2}: no installed-version metadata recorded`);
14775
+ }
14776
+ }
14777
+ const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
14778
+ error48(`[lsp] background install ${spec.npm} crashed: ${err}`);
14779
+ return false;
14780
+ });
14781
+ if (!ok) {
14782
+ return { started: true, reason: "install failed (see plugin log)" };
14783
+ }
14784
+ const installedHash = await hashInstalledBinary(spec).catch((err) => {
14785
+ warn(`[lsp] could not hash newly-installed ${spec.npm} binary: ${err}`);
14786
+ return null;
14787
+ });
14788
+ if (installedHash) {
14789
+ log(`[lsp] ${spec.npm}@${version2} installed sha256=${installedHash}`);
14790
+ }
14791
+ writeInstalledMeta(spec.npm, version2, installedHash ?? undefined);
14792
+ return { started: true };
14793
+ });
14794
+ if (outcome === null) {
14795
+ return { started: false, reason: "another install in progress" };
14796
+ }
14797
+ return outcome;
14798
+ }
14799
+ function cachedPackageDir(npmPackage) {
14800
+ return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
14801
+ }
14802
+ function hashInstalledBinary(spec) {
14803
+ return new Promise((resolve, reject) => {
14804
+ const candidates = process.platform === "win32" ? [
14805
+ lspBinaryPath(spec.npm, spec.binary),
14806
+ lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
14807
+ lspBinaryPath(spec.npm, `${spec.binary}.exe`),
14808
+ lspBinaryPath(spec.npm, `${spec.binary}.bat`)
14809
+ ] : [lspBinaryPath(spec.npm, spec.binary)];
14810
+ let pathToHash = null;
14811
+ for (const p of candidates) {
14812
+ try {
14813
+ if (statSync2(p).isFile()) {
14814
+ pathToHash = p;
14815
+ break;
14816
+ }
14817
+ } catch {}
14818
+ }
14819
+ if (!pathToHash) {
14820
+ reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
14821
+ return;
14822
+ }
14823
+ const hash2 = createHash("sha256");
14824
+ const stream = createReadStream(pathToHash);
14825
+ stream.on("error", reject);
14826
+ stream.on("data", (chunk) => hash2.update(chunk));
14827
+ stream.on("end", () => resolve(hash2.digest("hex")));
14828
+ });
14829
+ }
14830
+ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
14831
+ const cachedBinDirs = [];
14832
+ const skipped = [];
14833
+ const installPromises = [];
14834
+ let installsStarted = 0;
14835
+ let projectExtensions = null;
14836
+ const getProjectExtensions = () => {
14837
+ projectExtensions ??= relevantExtensionsInProject(projectRoot, npmExtToServerIds);
14838
+ return projectExtensions;
14839
+ };
14840
+ for (const spec of NPM_LSP_TABLE) {
14841
+ if (isInstalled(spec.npm, spec.binary)) {
14842
+ cachedBinDirs.push(lspBinDir(spec.npm));
14843
+ }
14844
+ if (config2.disabled.has(spec.id)) {
14845
+ skipped.push({ id: spec.id, reason: "disabled by config" });
14846
+ continue;
14847
+ }
14848
+ if (!config2.autoInstall) {
14849
+ skipped.push({ id: spec.id, reason: "auto_install: false" });
14850
+ continue;
14851
+ }
14852
+ if (!isProjectRelevant(spec, projectRoot, getProjectExtensions)) {
14853
+ skipped.push({ id: spec.id, reason: "not relevant to project" });
14854
+ continue;
14855
+ }
14856
+ installsStarted += 1;
14857
+ const controller = new AbortController;
14858
+ const promise2 = ensureServerInstalled(spec, config2, fetchImpl, controller.signal).then((outcome) => {
14859
+ if (!outcome.started)
14860
+ installsStarted -= 1;
14861
+ if (outcome.reason && outcome.reason !== "already installed") {
14862
+ skipped.push({ id: spec.id, reason: outcome.reason });
14863
+ }
14864
+ }, (err) => {
14865
+ installsStarted -= 1;
14866
+ const reason = err instanceof Error ? err.message : String(err);
14867
+ skipped.push({ id: spec.id, reason: `install error: ${reason}` });
14868
+ error48(`[lsp] background install ${spec.npm} promise rejected: ${reason}`);
14869
+ });
14870
+ installPromises.push(trackInFlightAutoInstall(controller, promise2));
14871
+ }
14872
+ return {
14873
+ cachedBinDirs,
14874
+ get installsStarted() {
14875
+ return installsStarted;
14876
+ },
14877
+ skipped,
14878
+ installsComplete: Promise.all(installPromises).then(() => {})
14879
+ };
14880
+ }
14881
+
14882
+ // src/lsp-github-install.ts
14883
+ import { execFileSync } from "node:child_process";
14884
+ import { createHash as createHash2, randomBytes } from "node:crypto";
14885
+ import {
14886
+ copyFileSync,
14887
+ createReadStream as createReadStream2,
14888
+ createWriteStream,
14889
+ existsSync as existsSync4,
14890
+ lstatSync,
14891
+ mkdirSync as mkdirSync2,
14892
+ readdirSync as readdirSync2,
14893
+ readlinkSync,
14894
+ realpathSync,
14895
+ renameSync,
14896
+ rmSync,
14897
+ statSync as statSync3,
14898
+ unlinkSync as unlinkSync2
14899
+ } from "node:fs";
14900
+ import { dirname, join as join5, relative, resolve } from "node:path";
14901
+ import { Readable } from "node:stream";
14902
+ import { pipeline } from "node:stream/promises";
14903
+
14904
+ // src/lsp-github-table.ts
14905
+ function exe(platform, name) {
14906
+ return platform === "win32" ? `${name}.exe` : name;
14907
+ }
14908
+ var CLANGD = {
14909
+ id: "clangd",
14910
+ githubRepo: "clangd/clangd",
14911
+ binary: "clangd",
14912
+ resolveAsset: (platform, _arch, version2) => {
14913
+ const platformName = platform === "darwin" ? "mac" : platform === "linux" ? "linux" : "windows";
14914
+ return { name: `clangd-${platformName}-${version2}.zip`, archive: "zip" };
14915
+ },
14916
+ binaryPathInArchive: (platform, _arch, version2) => `clangd_${version2}/bin/${exe(platform, "clangd")}`
14917
+ };
14918
+ var LUA_LS = {
14919
+ id: "lua-ls",
14920
+ githubRepo: "LuaLS/lua-language-server",
14921
+ binary: "lua-language-server",
14922
+ resolveAsset: (platform, arch, version2) => {
14923
+ const ext = platform === "win32" ? "zip" : "tar.gz";
14924
+ const platformName = platform === "darwin" ? "darwin" : platform === "linux" ? "linux" : "win32";
14925
+ const archName = arch === "arm64" ? "arm64" : "x64";
14926
+ return {
14927
+ name: `lua-language-server-${version2}-${platformName}-${archName}.${ext}`,
14928
+ archive: ext
14929
+ };
14930
+ },
14931
+ binaryPathInArchive: (platform, _arch, _version) => `bin/${exe(platform, "lua-language-server")}`
14932
+ };
14933
+ var ZLS = {
14934
+ id: "zls",
14935
+ githubRepo: "zigtools/zls",
14936
+ binary: "zls",
14937
+ resolveAsset: (platform, arch, _version) => {
14938
+ const ext = platform === "win32" ? "zip" : "tar.xz";
14939
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
14940
+ const platformName = platform === "darwin" ? "macos" : platform === "linux" ? "linux" : "windows";
14941
+ return { name: `zls-${archName}-${platformName}.${ext}`, archive: ext };
14942
+ },
14943
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "zls")
14944
+ };
14945
+ var TINYMIST = {
14946
+ id: "tinymist",
14947
+ githubRepo: "Myriad-Dreamin/tinymist",
14948
+ binary: "tinymist",
14949
+ resolveAsset: (platform, arch, _version) => {
14950
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
14951
+ const triple = platform === "darwin" ? "apple-darwin" : platform === "linux" ? "unknown-linux-gnu" : "pc-windows-msvc";
14952
+ const ext = platform === "win32" ? "zip" : "tar.gz";
14953
+ return { name: `tinymist-${archName}-${triple}.${ext}`, archive: ext };
14954
+ },
14955
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "tinymist")
14956
+ };
14957
+ var TEXLAB = {
14958
+ id: "texlab",
14959
+ githubRepo: "latex-lsp/texlab",
14960
+ binary: "texlab",
14961
+ resolveAsset: (platform, arch, _version) => {
14962
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
14963
+ const platformName = platform === "darwin" ? "macos" : platform === "linux" ? "linux" : "windows";
14964
+ const ext = platform === "win32" ? "zip" : "tar.gz";
14965
+ return { name: `texlab-${archName}-${platformName}.${ext}`, archive: ext };
14966
+ },
14967
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "texlab")
14968
+ };
14969
+ var GITHUB_LSP_TABLE = [
14970
+ CLANGD,
14971
+ LUA_LS,
14972
+ ZLS,
14973
+ TINYMIST,
14974
+ TEXLAB
14975
+ ];
14976
+ function detectHostPlatform() {
14977
+ const platform = process.platform;
14978
+ if (platform !== "darwin" && platform !== "linux" && platform !== "win32")
14979
+ return null;
14980
+ const arch = process.arch;
14981
+ if (arch === "x64")
14982
+ return { platform, arch: "x64" };
14983
+ if (arch === "arm64")
14984
+ return { platform, arch: "arm64" };
14985
+ return null;
14986
+ }
14987
+
14988
+ // src/lsp-github-install.ts
14989
+ function ghCacheRoot() {
14990
+ return join5(aftCacheBase(), "lsp-binaries");
14991
+ }
14992
+ function ghPackageDir(spec) {
14993
+ return join5(ghCacheRoot(), spec.id);
14994
+ }
14995
+ function ghBinDir(spec) {
14996
+ return join5(ghPackageDir(spec), "bin");
14997
+ }
14998
+ function ghExtractDir(spec) {
14999
+ return join5(ghPackageDir(spec), "extracted");
15000
+ }
15001
+ function ghBinaryPath(spec, platform) {
15002
+ const ext = platform === "win32" ? ".exe" : "";
15003
+ return join5(ghBinDir(spec), `${spec.binary}${ext}`);
15004
+ }
15005
+ function isGithubInstalled(spec, platform) {
15006
+ for (const candidate of ghBinaryCandidates(spec, platform)) {
15007
+ try {
15008
+ if (statSync3(join5(ghBinDir(spec), candidate)).isFile())
15009
+ return true;
15010
+ } catch {}
15011
+ }
15012
+ return false;
15013
+ }
15014
+ function ghBinaryCandidates(spec, platform) {
15015
+ if (platform !== "win32")
15016
+ return [spec.binary];
15017
+ return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
15018
+ }
15019
+ var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
15020
+ var MAX_EXTRACT_BYTES = 1024 * 1024 * 1024;
15021
+ function sha256OfFile(path2) {
15022
+ return new Promise((resolve2, reject) => {
15023
+ const hash2 = createHash2("sha256");
15024
+ const stream = createReadStream2(path2);
15025
+ stream.on("error", reject);
15026
+ stream.on("data", (chunk) => hash2.update(chunk));
15027
+ stream.on("end", () => resolve2(hash2.digest("hex")));
15028
+ });
15029
+ }
15030
+ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
15031
+ const candidates = [];
15032
+ candidates.push(tag);
15033
+ if (!tag.startsWith("v")) {
15034
+ candidates.push(`v${tag}`);
15035
+ } else {
15036
+ candidates.push(tag.slice(1));
15037
+ }
15038
+ const headers = {
15039
+ accept: "application/vnd.github+json",
15040
+ "user-agent": "aft-opencode",
15041
+ "x-github-api-version": "2022-11-28"
15042
+ };
15043
+ if (process.env.GITHUB_TOKEN) {
15044
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
15045
+ }
15046
+ for (const candidate of candidates) {
15047
+ const url2 = `https://api.github.com/repos/${githubRepo}/releases/tags/${encodeURIComponent(candidate)}`;
15048
+ const timeout = controlledTimeoutSignal(15000, signal);
15049
+ try {
15050
+ const res = await fetchImpl(url2, {
15051
+ headers,
15052
+ redirect: "follow",
15053
+ signal: timeout.signal
15054
+ });
15055
+ if (res.status === 404)
15056
+ continue;
15057
+ if (!res.ok) {
15058
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: HTTP ${res.status}`);
15059
+ return null;
15060
+ }
15061
+ const json2 = await res.json();
15062
+ if (!json2.tag_name || !Array.isArray(json2.assets)) {
15063
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: malformed response`);
15064
+ return null;
15065
+ }
15066
+ const assets = json2.assets.filter((a) => typeof a.name === "string" && typeof a.browser_download_url === "string").map((a) => ({
15067
+ name: a.name,
15068
+ url: a.browser_download_url,
15069
+ size: typeof a.size === "number" ? a.size : undefined
15070
+ }));
15071
+ return { tag: json2.tag_name, assets };
15072
+ } catch (err) {
15073
+ if (signal?.aborted) {
15074
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: aborted`);
15075
+ return null;
15076
+ }
15077
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: ${err}`);
15078
+ } finally {
15079
+ timeout.cleanup();
15080
+ }
15081
+ }
15082
+ return null;
15083
+ }
15084
+ async function resolveTargetTag(spec, config2, fetchImpl, signal) {
15085
+ const pinned = config2.versions[spec.githubRepo];
15086
+ if (pinned) {
15087
+ try {
15088
+ assertSafeVersion(pinned);
15089
+ } catch (err) {
15090
+ return {
15091
+ tag: null,
15092
+ assets: [],
15093
+ blockedByGrace: false,
15094
+ reason: `invalid pinned version ${JSON.stringify(pinned)}: ${err instanceof Error ? err.message : String(err)}`
15095
+ };
15096
+ }
15097
+ const release = await fetchReleaseByTag(spec.githubRepo, pinned, fetchImpl, signal);
15098
+ if (release) {
15099
+ return {
15100
+ tag: release.tag,
15101
+ assets: release.assets,
15102
+ blockedByGrace: false
15103
+ };
15104
+ }
15105
+ return {
15106
+ tag: null,
15107
+ assets: [],
15108
+ blockedByGrace: false,
15109
+ reason: `pinned tag ${pinned} not found on GitHub`
15110
+ };
15111
+ }
15112
+ const cached2 = readVersionCheck(spec.githubRepo);
15113
+ const weeklyMs = config2.graceDays * 24 * 60 * 60 * 1000;
15114
+ if (!shouldRecheckVersion(cached2, weeklyMs) && cached2?.latest_eligible) {
15115
+ const release = await fetchReleaseByTag(spec.githubRepo, cached2.latest_eligible, fetchImpl);
15116
+ if (release) {
15117
+ return {
15118
+ tag: release.tag,
15119
+ assets: release.assets,
15120
+ blockedByGrace: false
15121
+ };
15122
+ }
15123
+ }
15124
+ const probe = await probeGithubReleases(spec.githubRepo, config2.graceDays, fetchImpl);
15125
+ if (!probe) {
15126
+ return {
15127
+ tag: null,
15128
+ assets: [],
15129
+ blockedByGrace: false,
15130
+ reason: "github releases probe failed"
15131
+ };
15132
+ }
15133
+ writeVersionCheck(spec.githubRepo, probe.tag);
15134
+ return { tag: probe.tag, assets: probe.assets, blockedByGrace: probe.blockedByGrace };
15135
+ }
15136
+ function controlledTimeoutSignal(timeoutMs, parent) {
15137
+ const controller = new AbortController;
15138
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
15139
+ timeout.unref?.();
15140
+ const abort = () => controller.abort();
15141
+ parent?.addEventListener("abort", abort, { once: true });
15142
+ if (parent?.aborted)
15143
+ abort();
15144
+ return {
15145
+ signal: controller.signal,
15146
+ cleanup: () => {
15147
+ clearTimeout(timeout);
15148
+ parent?.removeEventListener("abort", abort);
15149
+ }
15150
+ };
15151
+ }
15152
+ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
15153
+ if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES) {
15154
+ throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES} (set lsp.versions to pin a smaller release if this is wrong)`);
15155
+ }
15156
+ const timeout = controlledTimeoutSignal(120000, signal);
15157
+ try {
15158
+ const res = await fetchImpl(url2, {
15159
+ headers: { accept: "application/octet-stream" },
15160
+ redirect: "follow",
15161
+ signal: timeout.signal
15162
+ });
15163
+ if (!res.ok || !res.body) {
15164
+ throw new Error(`download failed (${res.status})`);
15165
+ }
15166
+ const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
15167
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
15168
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
15169
+ }
15170
+ mkdirSync2(dirname(destPath), { recursive: true });
15171
+ let bytesWritten = 0;
15172
+ const guard = new TransformStream({
15173
+ transform(chunk, controller) {
15174
+ bytesWritten += chunk.byteLength;
15175
+ if (bytesWritten > MAX_DOWNLOAD_BYTES) {
15176
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
15177
+ return;
15178
+ }
15179
+ controller.enqueue(chunk);
15180
+ }
15181
+ });
15182
+ const guarded = res.body.pipeThrough(guard);
15183
+ const nodeStream = Readable.fromWeb(guarded);
15184
+ await pipeline(nodeStream, createWriteStream(destPath), { signal: timeout.signal });
15185
+ } catch (err) {
15186
+ try {
15187
+ unlinkSync2(destPath);
15188
+ } catch {}
15189
+ throw err;
15190
+ } finally {
15191
+ timeout.cleanup();
15192
+ }
15193
+ }
15194
+ function validateExtraction(stagingRoot) {
15195
+ const realStagingRoot = realpathSync(stagingRoot);
15196
+ let totalBytes = 0;
15197
+ const walk = (dir) => {
15198
+ let entries;
15199
+ try {
15200
+ entries = readdirSync2(dir);
15201
+ } catch (err) {
15202
+ throw new Error(`failed to read staging dir ${dir}: ${err}`);
15203
+ }
15204
+ for (const entry of entries) {
15205
+ const full = join5(dir, entry);
15206
+ let lst;
15207
+ try {
15208
+ lst = lstatSync(full);
15209
+ } catch (err) {
15210
+ throw new Error(`failed to lstat ${full}: ${err}`);
15211
+ }
15212
+ if (lst.isSymbolicLink()) {
15213
+ let target = "<unreadable>";
15214
+ try {
15215
+ target = readlinkSync(full);
15216
+ } catch {}
15217
+ throw new Error(`archive contains symlink ${relative(realStagingRoot, full)} → ${target}; rejecting (zip-slip defense)`);
15218
+ }
15219
+ let realFull;
15220
+ try {
15221
+ realFull = realpathSync(full);
15222
+ } catch (err) {
15223
+ throw new Error(`failed to realpath ${full}: ${err}`);
15224
+ }
15225
+ const rel = relative(realStagingRoot, realFull);
15226
+ if (rel.startsWith("..") || resolve(realStagingRoot, rel) !== realFull) {
15227
+ throw new Error(`archive entry escapes staging root: ${full} → ${realFull} (zip-slip defense)`);
15228
+ }
15229
+ if (lst.isDirectory()) {
15230
+ walk(full);
15231
+ } else if (lst.isFile()) {
15232
+ totalBytes += lst.size;
15233
+ if (totalBytes > MAX_EXTRACT_BYTES) {
15234
+ throw new Error(`extracted archive exceeds ${MAX_EXTRACT_BYTES} bytes (decompression bomb defense): saw ${totalBytes} bytes before hitting the cap`);
15235
+ }
15236
+ } else {
15237
+ throw new Error(`archive contains non-file/non-dir entry: ${full}`);
15238
+ }
15239
+ }
15240
+ };
15241
+ walk(realStagingRoot);
15242
+ }
15243
+ function extractArchiveSafely(archivePath, destDir, archiveType) {
15244
+ const suffix = randomBytes(8).toString("hex");
15245
+ const stagingDir = `${destDir}.staging-${suffix}`;
15246
+ try {
15247
+ rmSync(stagingDir, { recursive: true, force: true });
15248
+ } catch {}
15249
+ mkdirSync2(stagingDir, { recursive: true });
15250
+ try {
15251
+ runPlatformExtractor(archivePath, stagingDir, archiveType);
15252
+ validateExtraction(stagingDir);
15253
+ try {
15254
+ rmSync(destDir, { recursive: true, force: true });
15255
+ } catch {}
15256
+ renameSync(stagingDir, destDir);
15257
+ } catch (err) {
15258
+ try {
15259
+ rmSync(stagingDir, { recursive: true, force: true });
15260
+ } catch {}
15261
+ throw err;
15262
+ }
15263
+ }
15264
+ function runPlatformExtractor(archivePath, destDir, archiveType) {
15265
+ if (archiveType === "zip") {
15266
+ if (process.platform === "win32") {
15267
+ execFileSync("tar.exe", ["-xf", archivePath, "-C", destDir], {
15268
+ stdio: "pipe",
15269
+ timeout: 180000
15270
+ });
15271
+ return;
15272
+ }
15273
+ execFileSync("unzip", ["-q", "-o", archivePath, "-d", destDir], {
15274
+ stdio: "pipe",
15275
+ timeout: 180000
15276
+ });
15277
+ return;
15278
+ }
15279
+ if (archiveType === "tar.gz") {
15280
+ execFileSync("tar", ["-xzf", archivePath, "-C", destDir], {
15281
+ stdio: "pipe",
15282
+ timeout: 180000
15283
+ });
15284
+ return;
15285
+ }
15286
+ if (archiveType === "tar.xz") {
15287
+ execFileSync("tar", ["-xf", archivePath, "-C", destDir], {
15288
+ stdio: "pipe",
15289
+ timeout: 180000
15290
+ });
15291
+ return;
15292
+ }
15293
+ throw new Error(`unsupported archive type: ${archiveType}`);
15294
+ }
15295
+ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal) {
15296
+ const version2 = stripTagV(tag);
15297
+ const expected = spec.resolveAsset(platform, arch, version2);
15298
+ if (!expected) {
15299
+ warn(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform}/${arch}`);
15300
+ return null;
15301
+ }
15302
+ const matchingAsset = assets.find((a) => a.name === expected.name);
15303
+ if (!matchingAsset) {
15304
+ warn(`[lsp] ${spec.id}: asset ${expected.name} not found in release ${tag} (${assets.length} assets available)`);
15305
+ return null;
15306
+ }
15307
+ const pkgDir = ghPackageDir(spec);
15308
+ const extractDir = ghExtractDir(spec);
15309
+ const archivePath = join5(pkgDir, expected.name);
15310
+ log(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
15311
+ try {
15312
+ await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
15313
+ } catch (err) {
15314
+ error48(`[lsp] download ${spec.id} failed: ${err}`);
15315
+ return null;
15316
+ }
15317
+ let archiveSha256;
15318
+ try {
15319
+ archiveSha256 = await sha256OfFile(archivePath);
15320
+ } catch (err) {
15321
+ error48(`[lsp] hash ${spec.id} failed: ${err}`);
15322
+ try {
15323
+ unlinkSync2(archivePath);
15324
+ } catch {}
15325
+ return null;
15326
+ }
15327
+ log(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
15328
+ const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
15329
+ if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
15330
+ if (previousMeta.sha256 !== archiveSha256) {
15331
+ error48(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding.`);
15332
+ try {
15333
+ unlinkSync2(archivePath);
15334
+ } catch {}
15335
+ return null;
15336
+ }
15337
+ }
15338
+ try {
15339
+ extractArchiveSafely(archivePath, extractDir, expected.archive);
15340
+ } catch (err) {
15341
+ error48(`[lsp] extract ${spec.id} failed: ${err}`);
15342
+ return null;
15343
+ } finally {
15344
+ try {
15345
+ unlinkSync2(archivePath);
15346
+ } catch {}
15347
+ }
15348
+ const innerBinaryPath = join5(extractDir, spec.binaryPathInArchive(platform, arch, version2));
15349
+ if (!existsSync4(innerBinaryPath)) {
15350
+ error48(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
15351
+ return null;
15352
+ }
15353
+ const targetBinary = ghBinaryPath(spec, platform);
15354
+ mkdirSync2(dirname(targetBinary), { recursive: true });
15355
+ try {
15356
+ copyFileSync(innerBinaryPath, targetBinary);
15357
+ if (platform !== "win32") {
15358
+ const { chmodSync } = await import("node:fs");
15359
+ chmodSync(targetBinary, 493);
15360
+ }
15361
+ } catch (err) {
15362
+ error48(`[lsp] ${spec.id}: failed to place binary at ${targetBinary}: ${err}`);
15363
+ return null;
15364
+ }
15365
+ log(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
15366
+ return archiveSha256;
15367
+ }
15368
+ async function ensureGithubInstalled(spec, config2, fetchImpl, platform, arch, signal) {
15369
+ const outcome = await withInstallLock(spec.githubRepo, async () => {
15370
+ const { tag, assets, blockedByGrace, reason } = await resolveTargetTag(spec, config2, fetchImpl, signal);
15371
+ if (!tag) {
15372
+ const installed = isGithubInstalled(spec, platform);
15373
+ if (installed) {
15374
+ warn(`[lsp] no eligible release of ${spec.githubRepo} (grace=${config2.graceDays}d); keeping existing install`);
15375
+ return { started: false, reason: "kept existing install" };
15376
+ }
15377
+ const fallbackReason = reason ?? (blockedByGrace ? `all releases are within ${config2.graceDays}-day grace window` : "github releases probe failed");
15378
+ warn(`[lsp] skipping ${spec.id}: ${fallbackReason}`);
15379
+ return { started: false, reason: fallbackReason };
15380
+ }
15381
+ if (isGithubInstalled(spec, platform)) {
15382
+ const installedMeta = readInstalledMetaIn(ghPackageDir(spec));
15383
+ if (installedMeta && installedMeta.version === tag) {
15384
+ return { started: false, reason: "already installed" };
15385
+ }
15386
+ if (installedMeta) {
15387
+ log(`[lsp] reinstalling ${spec.id}: cached ${installedMeta.version} ≠ target ${tag}`);
15388
+ } else {
15389
+ log(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
15390
+ }
15391
+ }
15392
+ const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal).catch((err) => {
15393
+ error48(`[lsp] github install ${spec.id} crashed: ${err}`);
15394
+ return null;
15395
+ });
15396
+ if (!archiveSha256) {
15397
+ return { started: true, reason: "install failed (see plugin log)" };
15398
+ }
15399
+ writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
15400
+ return { started: true };
15401
+ });
15402
+ if (outcome === null) {
15403
+ return { started: false, reason: "another install in progress" };
15404
+ }
15405
+ return outcome;
15406
+ }
15407
+ var inFlightGithubInstalls = new Set;
15408
+ function trackInFlightGithubInstall(controller, promise2) {
15409
+ const entry = { controller, promise: promise2 };
15410
+ inFlightGithubInstalls.add(entry);
15411
+ promise2.then(() => inFlightGithubInstalls.delete(entry), () => inFlightGithubInstalls.delete(entry));
15412
+ return promise2;
15413
+ }
15414
+ async function abortInFlightGithubInstalls() {
15415
+ const installs = Array.from(inFlightGithubInstalls);
15416
+ for (const install of installs) {
15417
+ install.controller.abort();
15418
+ }
15419
+ await Promise.allSettled(installs.map((install) => install.promise));
15420
+ }
15421
+ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
15422
+ const cachedBinDirs = [];
15423
+ const skipped = [];
15424
+ const installPromises = [];
15425
+ let installsStarted = 0;
15426
+ const host = detectHostPlatform();
15427
+ if (!host) {
15428
+ for (const spec of GITHUB_LSP_TABLE) {
15429
+ try {
15430
+ if (existsSync4(ghBinDir(spec))) {
15431
+ cachedBinDirs.push(ghBinDir(spec));
15432
+ }
15433
+ } catch {}
15434
+ }
15435
+ return {
15436
+ cachedBinDirs,
15437
+ installsStarted: 0,
15438
+ skipped,
15439
+ installsComplete: Promise.resolve()
15440
+ };
15441
+ }
15442
+ for (const spec of GITHUB_LSP_TABLE) {
15443
+ if (isGithubInstalled(spec, host.platform)) {
15444
+ cachedBinDirs.push(ghBinDir(spec));
15445
+ }
15446
+ if (config2.disabled.has(spec.id)) {
15447
+ skipped.push({ id: spec.id, reason: "disabled by config" });
15448
+ continue;
15449
+ }
15450
+ if (!config2.autoInstall) {
15451
+ skipped.push({ id: spec.id, reason: "auto_install: false" });
15452
+ continue;
15453
+ }
15454
+ if (!relevantServers.has(spec.id)) {
15455
+ skipped.push({ id: spec.id, reason: "not relevant to project" });
15456
+ continue;
15457
+ }
15458
+ installsStarted += 1;
15459
+ const controller = new AbortController;
15460
+ const promise2 = ensureGithubInstalled(spec, config2, fetchImpl, host.platform, host.arch, controller.signal).then((outcome) => {
15461
+ if (!outcome.started)
15462
+ installsStarted -= 1;
15463
+ if (outcome.reason && outcome.reason !== "already installed") {
15464
+ skipped.push({ id: spec.id, reason: outcome.reason });
15465
+ }
15466
+ }, (err) => {
15467
+ installsStarted -= 1;
15468
+ const reason = err instanceof Error ? err.message : String(err);
15469
+ skipped.push({ id: spec.id, reason: `install error: ${reason}` });
15470
+ error48(`[lsp] github install ${spec.id} promise rejected: ${reason}`);
15471
+ });
15472
+ installPromises.push(trackInFlightGithubInstall(controller, promise2));
15473
+ }
15474
+ return {
15475
+ cachedBinDirs,
15476
+ get installsStarted() {
15477
+ return installsStarted;
15478
+ },
15479
+ skipped,
15480
+ installsComplete: Promise.all(installPromises).then(() => {})
15481
+ };
15482
+ }
15483
+ function discoverRelevantGithubServers(projectRoot) {
15484
+ const extToServerIds = {
15485
+ c: ["clangd"],
15486
+ "c++": ["clangd"],
15487
+ cc: ["clangd"],
15488
+ cpp: ["clangd"],
15489
+ cxx: ["clangd"],
15490
+ h: ["clangd"],
15491
+ "h++": ["clangd"],
15492
+ hpp: ["clangd"],
15493
+ hh: ["clangd"],
15494
+ hxx: ["clangd"],
15495
+ lua: ["lua-ls"],
15496
+ zig: ["zls"],
15497
+ zon: ["zls"],
15498
+ typ: ["tinymist"],
15499
+ typc: ["tinymist"],
15500
+ tex: ["texlab"],
15501
+ bib: ["texlab"]
15502
+ };
15503
+ const rootMarkers = {
15504
+ clangd: ["compile_commands.json", "compile_flags.txt", ".clangd"],
15505
+ "lua-ls": [".luarc.json", ".luarc.jsonc", ".stylua.toml", "stylua.toml"],
15506
+ zls: ["build.zig"],
15507
+ tinymist: ["typst.toml"],
15508
+ texlab: [".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]
15509
+ };
15510
+ const relevant = new Set;
15511
+ for (const spec of GITHUB_LSP_TABLE) {
15512
+ if (hasRootMarker(projectRoot, rootMarkers[spec.id]))
15513
+ relevant.add(spec.id);
15514
+ }
15515
+ const extensions = relevantExtensionsInProject(projectRoot, extToServerIds);
15516
+ for (const ext of extensions) {
15517
+ for (const id of extToServerIds[ext] ?? []) {
15518
+ relevant.add(id);
15519
+ }
15520
+ }
15521
+ return relevant;
15522
+ }
15523
+
15524
+ // src/notifications.ts
15525
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
15526
+ import { join as join6 } from "node:path";
14111
15527
  var WARNING_MARKER = "\uD83D\uDD27 AFT: ⚠️";
14112
15528
  var WARNED_TOOLS_FILE = "warned_tools.json";
14113
15529
  function sendIgnoredMessage(client, _sessionId, text) {
@@ -14124,10 +15540,10 @@ function sendIgnoredMessage(client, _sessionId, text) {
14124
15540
  }
14125
15541
  function readWarnedTools(storageDir) {
14126
15542
  try {
14127
- const warnedToolsPath = join3(storageDir, WARNED_TOOLS_FILE);
14128
- if (!existsSync2(warnedToolsPath))
15543
+ const warnedToolsPath = join6(storageDir, WARNED_TOOLS_FILE);
15544
+ if (!existsSync5(warnedToolsPath))
14129
15545
  return {};
14130
- const parsed = JSON.parse(readFileSync2(warnedToolsPath, "utf-8"));
15546
+ const parsed = JSON.parse(readFileSync3(warnedToolsPath, "utf-8"));
14131
15547
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
14132
15548
  return {};
14133
15549
  const warned = {};
@@ -14143,9 +15559,9 @@ function readWarnedTools(storageDir) {
14143
15559
  }
14144
15560
  function writeWarnedTools(storageDir, warned) {
14145
15561
  try {
14146
- mkdirSync(storageDir, { recursive: true });
14147
- const warnedToolsPath = join3(storageDir, WARNED_TOOLS_FILE);
14148
- writeFileSync(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
15562
+ mkdirSync3(storageDir, { recursive: true });
15563
+ const warnedToolsPath = join6(storageDir, WARNED_TOOLS_FILE);
15564
+ writeFileSync2(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
14149
15565
  `);
14150
15566
  } catch {}
14151
15567
  }
@@ -14204,8 +15620,8 @@ async function deliverConfigureWarnings(opts, warnings) {
14204
15620
  }
14205
15621
 
14206
15622
  // src/onnx-runtime.ts
14207
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, unlinkSync } from "node:fs";
14208
- import { join as join4 } from "node:path";
15623
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "node:fs";
15624
+ import { join as join7 } from "node:path";
14209
15625
  var ORT_VERSION = "1.24.4";
14210
15626
  var ORT_REPO = "microsoft/onnxruntime";
14211
15627
  var ORT_PLATFORM_MAP = {
@@ -14258,9 +15674,9 @@ function getManualInstallHint() {
14258
15674
  }
14259
15675
  async function ensureOnnxRuntime(storageDir) {
14260
15676
  const info = getPlatformInfo();
14261
- const ortDir = join4(storageDir, "onnxruntime", ORT_VERSION);
14262
- const libPath = join4(ortDir, info?.libName ?? "libonnxruntime.dylib");
14263
- if (existsSync3(libPath)) {
15677
+ const ortDir = join7(storageDir, "onnxruntime", ORT_VERSION);
15678
+ const libPath = join7(ortDir, info?.libName ?? "libonnxruntime.dylib");
15679
+ if (existsSync6(libPath)) {
14264
15680
  log(`ONNX Runtime found at ${ortDir}`);
14265
15681
  return ortDir;
14266
15682
  }
@@ -14285,7 +15701,7 @@ function findSystemOnnxRuntime(libName) {
14285
15701
  searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
14286
15702
  }
14287
15703
  for (const dir of searchPaths) {
14288
- if (existsSync3(join4(dir, libName))) {
15704
+ if (existsSync6(join7(dir, libName))) {
14289
15705
  return dir;
14290
15706
  }
14291
15707
  }
@@ -14296,34 +15712,34 @@ async function downloadOnnxRuntime(info, targetDir) {
14296
15712
  log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
14297
15713
  try {
14298
15714
  const tmpDir = `${targetDir}.tmp.${process.pid}`;
14299
- mkdirSync2(tmpDir, { recursive: true });
14300
- const archivePath = join4(tmpDir, `onnxruntime.${info.archiveType}`);
14301
- const { execFileSync } = await import("node:child_process");
14302
- execFileSync("curl", ["-fsSL", url2, "-o", archivePath], {
15715
+ mkdirSync4(tmpDir, { recursive: true });
15716
+ const archivePath = join7(tmpDir, `onnxruntime.${info.archiveType}`);
15717
+ const { execFileSync: execFileSync2 } = await import("node:child_process");
15718
+ execFileSync2("curl", ["-fsSL", url2, "-o", archivePath], {
14303
15719
  stdio: "pipe",
14304
15720
  timeout: 120000
14305
15721
  });
14306
15722
  if (info.archiveType === "tgz") {
14307
- execFileSync("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
15723
+ execFileSync2("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
14308
15724
  } else {
14309
15725
  await extractZipArchive(archivePath, tmpDir);
14310
15726
  }
14311
- const extractedDir = join4(tmpDir, info.assetName, "lib");
14312
- if (!existsSync3(extractedDir)) {
15727
+ const extractedDir = join7(tmpDir, info.assetName, "lib");
15728
+ if (!existsSync6(extractedDir)) {
14313
15729
  throw new Error(`Expected directory not found: ${extractedDir}`);
14314
15730
  }
14315
- mkdirSync2(targetDir, { recursive: true });
14316
- const libFiles = readdirSync(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
14317
- const { lstatSync, symlinkSync, readlinkSync, copyFileSync: cpFile } = await import("node:fs");
15731
+ mkdirSync4(targetDir, { recursive: true });
15732
+ const libFiles = readdirSync3(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
15733
+ const { lstatSync: lstatSync2, symlinkSync, readlinkSync: readlinkSync2, copyFileSync: cpFile } = await import("node:fs");
14318
15734
  const realFiles = [];
14319
15735
  const symlinks = [];
14320
15736
  for (const libFile of libFiles) {
14321
- const src = join4(extractedDir, libFile);
15737
+ const src = join7(extractedDir, libFile);
14322
15738
  try {
14323
- const stat = lstatSync(src);
15739
+ const stat = lstatSync2(src);
14324
15740
  log(`ORT extract: ${libFile} — isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
14325
15741
  if (stat.isSymbolicLink()) {
14326
- symlinks.push({ name: libFile, target: readlinkSync(src) });
15742
+ symlinks.push({ name: libFile, target: readlinkSync2(src) });
14327
15743
  } else {
14328
15744
  realFiles.push(libFile);
14329
15745
  }
@@ -14333,8 +15749,8 @@ async function downloadOnnxRuntime(info, targetDir) {
14333
15749
  }
14334
15750
  }
14335
15751
  for (const libFile of realFiles) {
14336
- const src = join4(extractedDir, libFile);
14337
- const dst = join4(targetDir, libFile);
15752
+ const src = join7(extractedDir, libFile);
15753
+ const dst = join7(targetDir, libFile);
14338
15754
  try {
14339
15755
  cpFile(src, dst);
14340
15756
  if (process.platform !== "win32") {
@@ -14345,65 +15761,47 @@ async function downloadOnnxRuntime(info, targetDir) {
14345
15761
  }
14346
15762
  }
14347
15763
  for (const link of symlinks) {
14348
- const dst = join4(targetDir, link.name);
15764
+ const dst = join7(targetDir, link.name);
14349
15765
  try {
14350
- unlinkSync(dst);
15766
+ unlinkSync3(dst);
14351
15767
  } catch {}
14352
15768
  symlinkSync(link.target, dst);
14353
15769
  }
14354
- const { rmSync } = await import("node:fs");
14355
- rmSync(tmpDir, { recursive: true, force: true });
15770
+ const { rmSync: rmSync2 } = await import("node:fs");
15771
+ rmSync2(tmpDir, { recursive: true, force: true });
14356
15772
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
14357
15773
  return targetDir;
14358
15774
  } catch (err) {
14359
15775
  error48(`Failed to download ONNX Runtime: ${err}`);
14360
15776
  try {
14361
- const { rmSync } = await import("node:fs");
14362
- rmSync(`${targetDir}.tmp.${process.pid}`, { recursive: true, force: true });
15777
+ const { rmSync: rmSync2 } = await import("node:fs");
15778
+ rmSync2(`${targetDir}.tmp.${process.pid}`, { recursive: true, force: true });
14363
15779
  } catch {}
14364
15780
  return null;
14365
15781
  }
14366
15782
  }
14367
15783
  async function extractZipArchive(archivePath, destinationDir) {
14368
- const { execFileSync } = await import("node:child_process");
15784
+ const { execFileSync: execFileSync2 } = await import("node:child_process");
14369
15785
  if (process.platform === "win32") {
14370
- let powershellError;
14371
- try {
14372
- execFileSync("powershell.exe", [
14373
- "-NoProfile",
14374
- "-NonInteractive",
14375
- "-ExecutionPolicy",
14376
- "Bypass",
14377
- "-Command",
14378
- "& { Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force }",
14379
- archivePath,
14380
- destinationDir
14381
- ], { stdio: "pipe", timeout: 120000 });
14382
- return;
14383
- } catch (err) {
14384
- powershellError = err;
14385
- warn(`PowerShell Expand-Archive failed, falling back to cmd/tar: ${String(err)}`);
14386
- }
14387
- try {
14388
- execFileSync("cmd.exe", ["/d", "/s", "/c", `tar -xf "${archivePath}" -C "${destinationDir}"`], { stdio: "pipe", timeout: 120000 });
14389
- return;
14390
- } catch (cmdError) {
14391
- throw new Error(`ZIP extraction failed via PowerShell and cmd/tar. PowerShell: ${String(powershellError)} | cmd/tar: ${String(cmdError)}`);
14392
- }
15786
+ execFileSync2("tar.exe", ["-xf", archivePath, "-C", destinationDir], {
15787
+ stdio: "pipe",
15788
+ timeout: 120000
15789
+ });
15790
+ return;
14393
15791
  }
14394
- execFileSync("unzip", ["-q", archivePath, "-d", destinationDir], {
15792
+ execFileSync2("unzip", ["-q", archivePath, "-d", destinationDir], {
14395
15793
  stdio: "pipe",
14396
15794
  timeout: 120000
14397
15795
  });
14398
15796
  }
14399
15797
 
14400
15798
  // src/pool.ts
14401
- import { realpathSync } from "node:fs";
15799
+ import { realpathSync as realpathSync2 } from "node:fs";
14402
15800
 
14403
15801
  // src/bridge.ts
14404
- import { spawn } from "node:child_process";
14405
- import { homedir as homedir2 } from "node:os";
14406
- import { join as join5 } from "node:path";
15802
+ import { spawn as spawn2 } from "node:child_process";
15803
+ import { homedir as homedir3 } from "node:os";
15804
+ import { join as join8 } from "node:path";
14407
15805
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
14408
15806
  var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
14409
15807
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
@@ -14545,14 +15943,14 @@ class BinaryBridge {
14545
15943
  const line = `${JSON.stringify(request)}
14546
15944
  `;
14547
15945
  const effectiveTimeoutMs = options?.timeoutMs ?? this.timeoutMs;
14548
- return new Promise((resolve, reject) => {
15946
+ return new Promise((resolve2, reject) => {
14549
15947
  const timer = setTimeout(() => {
14550
15948
  this.pending.delete(id);
14551
15949
  warn(`Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms — restarting bridge`);
14552
15950
  reject(new Error(`[aft-pi] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
14553
15951
  this.handleTimeout();
14554
15952
  }, effectiveTimeoutMs);
14555
- this.pending.set(id, { resolve, reject, timer });
15953
+ this.pending.set(id, { resolve: resolve2, reject, timer });
14556
15954
  if (!this.process?.stdin?.writable) {
14557
15955
  this.pending.delete(id);
14558
15956
  clearTimeout(timer);
@@ -14595,15 +15993,15 @@ class BinaryBridge {
14595
15993
  if (this.process) {
14596
15994
  const proc = this.process;
14597
15995
  this.process = null;
14598
- return new Promise((resolve) => {
15996
+ return new Promise((resolve2) => {
14599
15997
  const forceKillTimer = setTimeout(() => {
14600
15998
  proc.kill("SIGKILL");
14601
- resolve();
15999
+ resolve2();
14602
16000
  }, 5000);
14603
16001
  proc.once("exit", () => {
14604
16002
  clearTimeout(forceKillTimer);
14605
16003
  log("Process exited during shutdown");
14606
- resolve();
16004
+ resolve2();
14607
16005
  });
14608
16006
  proc.kill("SIGTERM");
14609
16007
  });
@@ -14645,19 +16043,19 @@ class BinaryBridge {
14645
16043
  })();
14646
16044
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
14647
16045
  const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
14648
- const ortLibraryPath = ortDir == null ? null : join5(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
16046
+ const ortLibraryPath = ortDir == null ? null : join8(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
14649
16047
  const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
14650
16048
  const env = {
14651
16049
  ...process.env,
14652
16050
  ...envPath ? { PATH: envPath } : {}
14653
16051
  };
14654
16052
  if (useFastembedBackend) {
14655
- env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join5(this.configOverrides.storage_dir, "semantic", "models") : join5(homedir2() || "", ".cache", "fastembed"));
16053
+ env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join8(this.configOverrides.storage_dir, "semantic", "models") : join8(homedir3() || "", ".cache", "fastembed"));
14656
16054
  if (ortLibraryPath) {
14657
16055
  env.ORT_DYLIB_PATH = ortLibraryPath;
14658
16056
  }
14659
16057
  }
14660
- const child = spawn(this.binaryPath, [], {
16058
+ const child = spawn2(this.binaryPath, [], {
14661
16059
  cwd: this.cwd,
14662
16060
  stdio: ["pipe", "pipe", "pipe"],
14663
16061
  env
@@ -14915,7 +16313,7 @@ class BridgePool {
14915
16313
  function canonicalKey(directory) {
14916
16314
  const stripped = directory.replace(/[/\\]+$/, "");
14917
16315
  try {
14918
- return realpathSync(stripped);
16316
+ return realpathSync2(stripped);
14919
16317
  } catch {
14920
16318
  return stripped;
14921
16319
  }
@@ -14923,15 +16321,15 @@ function canonicalKey(directory) {
14923
16321
 
14924
16322
  // src/resolver.ts
14925
16323
  import { execSync, spawnSync } from "node:child_process";
14926
- import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4, renameSync } from "node:fs";
16324
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "node:fs";
14927
16325
  import { createRequire as createRequire2 } from "node:module";
14928
- import { homedir as homedir4 } from "node:os";
14929
- import { join as join7 } from "node:path";
16326
+ import { homedir as homedir5 } from "node:os";
16327
+ import { join as join10 } from "node:path";
14930
16328
 
14931
16329
  // src/downloader.ts
14932
- import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "node:fs";
14933
- import { homedir as homedir3 } from "node:os";
14934
- import { join as join6 } from "node:path";
16330
+ import { chmodSync as chmodSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "node:fs";
16331
+ import { homedir as homedir4 } from "node:os";
16332
+ import { join as join9 } from "node:path";
14935
16333
 
14936
16334
  // src/platform.ts
14937
16335
  var PLATFORM_ARCH_MAP = {
@@ -14952,11 +16350,11 @@ var REPO = "cortexkit/aft";
14952
16350
  function getCacheDir() {
14953
16351
  if (process.platform === "win32") {
14954
16352
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
14955
- const base2 = localAppData || join6(homedir3(), "AppData", "Local");
14956
- return join6(base2, "aft", "bin");
16353
+ const base2 = localAppData || join9(homedir4(), "AppData", "Local");
16354
+ return join9(base2, "aft", "bin");
14957
16355
  }
14958
- const base = process.env.XDG_CACHE_HOME || join6(homedir3(), ".cache");
14959
- return join6(base, "aft", "bin");
16356
+ const base = process.env.XDG_CACHE_HOME || join9(homedir4(), ".cache");
16357
+ return join9(base, "aft", "bin");
14960
16358
  }
14961
16359
  function getBinaryName() {
14962
16360
  return process.platform === "win32" ? "aft.exe" : "aft";
@@ -14964,8 +16362,8 @@ function getBinaryName() {
14964
16362
  function getCachedBinaryPath(version2) {
14965
16363
  if (!version2)
14966
16364
  return null;
14967
- const binaryPath = join6(getCacheDir(), version2, getBinaryName());
14968
- return existsSync4(binaryPath) ? binaryPath : null;
16365
+ const binaryPath = join9(getCacheDir(), version2, getBinaryName());
16366
+ return existsSync7(binaryPath) ? binaryPath : null;
14969
16367
  }
14970
16368
  async function downloadBinary(version2) {
14971
16369
  const platformKey = `${process.platform}-${process.arch}`;
@@ -14979,18 +16377,18 @@ async function downloadBinary(version2) {
14979
16377
  error48("Could not determine latest release version.");
14980
16378
  return null;
14981
16379
  }
14982
- const versionedCacheDir = join6(getCacheDir(), tag);
16380
+ const versionedCacheDir = join9(getCacheDir(), tag);
14983
16381
  const binaryName = getBinaryName();
14984
- const binaryPath = join6(versionedCacheDir, binaryName);
14985
- if (existsSync4(binaryPath)) {
16382
+ const binaryPath = join9(versionedCacheDir, binaryName);
16383
+ if (existsSync7(binaryPath)) {
14986
16384
  return binaryPath;
14987
16385
  }
14988
16386
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
14989
16387
  const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
14990
16388
  log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
14991
16389
  try {
14992
- if (!existsSync4(versionedCacheDir)) {
14993
- mkdirSync3(versionedCacheDir, { recursive: true });
16390
+ if (!existsSync7(versionedCacheDir)) {
16391
+ mkdirSync5(versionedCacheDir, { recursive: true });
14994
16392
  }
14995
16393
  const [binaryResponse, checksumResponse] = await Promise.all([
14996
16394
  fetch(downloadUrl, { redirect: "follow" }),
@@ -15010,29 +16408,29 @@ async function downloadBinary(version2) {
15010
16408
  warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
15011
16409
  return null;
15012
16410
  }
15013
- const { createHash } = await import("node:crypto");
15014
- const actualHash = createHash("sha256").update(Buffer.from(arrayBuffer)).digest("hex");
16411
+ const { createHash: createHash3 } = await import("node:crypto");
16412
+ const actualHash = createHash3("sha256").update(Buffer.from(arrayBuffer)).digest("hex");
15015
16413
  if (actualHash !== expectedHash) {
15016
16414
  throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. The binary may have been tampered with.`);
15017
16415
  }
15018
16416
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
15019
16417
  const tmpPath = `${binaryPath}.tmp`;
15020
- const { writeFileSync: writeFileSync2 } = await import("node:fs");
15021
- writeFileSync2(tmpPath, Buffer.from(arrayBuffer));
16418
+ const { writeFileSync: writeFileSync3 } = await import("node:fs");
16419
+ writeFileSync3(tmpPath, Buffer.from(arrayBuffer));
15022
16420
  if (process.platform !== "win32") {
15023
16421
  chmodSync2(tmpPath, 493);
15024
16422
  }
15025
- const { renameSync } = await import("node:fs");
15026
- renameSync(tmpPath, binaryPath);
16423
+ const { renameSync: renameSync2 } = await import("node:fs");
16424
+ renameSync2(tmpPath, binaryPath);
15027
16425
  log(`AFT binary ready at ${binaryPath}`);
15028
16426
  return binaryPath;
15029
16427
  } catch (err) {
15030
16428
  const msg = err instanceof Error ? err.message : String(err);
15031
16429
  error48(`Failed to download AFT binary: ${msg}`);
15032
16430
  const tmpPath = `${binaryPath}.tmp`;
15033
- if (existsSync4(tmpPath)) {
16431
+ if (existsSync7(tmpPath)) {
15034
16432
  try {
15035
- unlinkSync2(tmpPath);
16433
+ unlinkSync4(tmpPath);
15036
16434
  } catch {}
15037
16435
  }
15038
16436
  return null;
@@ -15097,18 +16495,18 @@ function copyToVersionedCache(npmBinaryPath) {
15097
16495
  const version2 = rawVersion.replace(/^aft\s+/, "");
15098
16496
  const tag = version2.startsWith("v") ? version2 : `v${version2}`;
15099
16497
  const cacheDir = getCacheDir();
15100
- const versionedDir = join7(cacheDir, tag);
16498
+ const versionedDir = join10(cacheDir, tag);
15101
16499
  const ext = process.platform === "win32" ? ".exe" : "";
15102
- const cachedPath = join7(versionedDir, `aft${ext}`);
15103
- if (existsSync5(cachedPath))
16500
+ const cachedPath = join10(versionedDir, `aft${ext}`);
16501
+ if (existsSync8(cachedPath))
15104
16502
  return cachedPath;
15105
- mkdirSync4(versionedDir, { recursive: true });
16503
+ mkdirSync6(versionedDir, { recursive: true });
15106
16504
  const tmpPath = `${cachedPath}.tmp`;
15107
- copyFileSync(npmBinaryPath, tmpPath);
16505
+ copyFileSync2(npmBinaryPath, tmpPath);
15108
16506
  if (process.platform !== "win32") {
15109
16507
  chmodSync3(tmpPath, 493);
15110
16508
  }
15111
- renameSync(tmpPath, cachedPath);
16509
+ renameSync2(tmpPath, cachedPath);
15112
16510
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
15113
16511
  return cachedPath;
15114
16512
  } catch (err) {
@@ -15147,7 +16545,7 @@ function findBinarySync() {
15147
16545
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
15148
16546
  const req = createRequire2(import.meta.url);
15149
16547
  const resolved = req.resolve(packageBin);
15150
- if (existsSync5(resolved)) {
16548
+ if (existsSync8(resolved)) {
15151
16549
  const copied = copyToVersionedCache(resolved);
15152
16550
  return copied ?? resolved;
15153
16551
  }
@@ -15161,8 +16559,8 @@ function findBinarySync() {
15161
16559
  if (result)
15162
16560
  return result;
15163
16561
  } catch {}
15164
- const cargoPath = join7(homedir4(), ".cargo", "bin", `aft${ext}`);
15165
- if (existsSync5(cargoPath))
16562
+ const cargoPath = join10(homedir5(), ".cargo", "bin", `aft${ext}`);
16563
+ if (existsSync8(cargoPath))
15166
16564
  return cargoPath;
15167
16565
  return null;
15168
16566
  }
@@ -15252,7 +16650,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
15252
16650
  import { Type } from "@sinclair/typebox";
15253
16651
 
15254
16652
  // src/tools/render-helpers.ts
15255
- import { homedir as homedir5 } from "node:os";
16653
+ import { homedir as homedir6 } from "node:os";
15256
16654
  import { renderDiff } from "@mariozechner/pi-coding-agent";
15257
16655
  import { Container, Spacer, Text } from "@mariozechner/pi-tui";
15258
16656
  function reuseText(last) {
@@ -15262,7 +16660,7 @@ function reuseContainer(last) {
15262
16660
  return last instanceof Container ? last : new Container;
15263
16661
  }
15264
16662
  function shortenPath(path2) {
15265
- const home = homedir5();
16663
+ const home = homedir6();
15266
16664
  if (path2.startsWith(home))
15267
16665
  return `~${path2.slice(home.length)}`;
15268
16666
  return path2;
@@ -15764,8 +17162,8 @@ function registerFsTools(pi, ctx, surface) {
15764
17162
 
15765
17163
  // src/tools/hoisted.ts
15766
17164
  import { stat } from "node:fs/promises";
15767
- import { homedir as homedir6 } from "node:os";
15768
- import { resolve } from "node:path";
17165
+ import { homedir as homedir7 } from "node:os";
17166
+ import { resolve as resolve2 } from "node:path";
15769
17167
  import {
15770
17168
  renderDiff as renderDiff2
15771
17169
  } from "@mariozechner/pi-coding-agent";
@@ -16118,13 +17516,13 @@ ${summary}${suffix}`);
16118
17516
  return container;
16119
17517
  }
16120
17518
  function shortenPath2(path2) {
16121
- const home = homedir6();
17519
+ const home = homedir7();
16122
17520
  if (path2.startsWith(home))
16123
17521
  return `~${path2.slice(home.length)}`;
16124
17522
  return path2;
16125
17523
  }
16126
17524
  async function resolvePathArg(cwd, path2) {
16127
- const abs = resolve(cwd, path2);
17525
+ const abs = resolve2(cwd, path2);
16128
17526
  try {
16129
17527
  await stat(abs);
16130
17528
  return abs;
@@ -16498,12 +17896,12 @@ function registerNavigateTool(pi, ctx) {
16498
17896
 
16499
17897
  // src/tools/reading.ts
16500
17898
  import { stat as stat2 } from "node:fs/promises";
16501
- import { resolve as resolve2 } from "node:path";
17899
+ import { resolve as resolve3 } from "node:path";
16502
17900
  import { Type as Type8 } from "@sinclair/typebox";
16503
17901
 
16504
17902
  // src/shared/discover-files.ts
16505
17903
  import { readdir } from "node:fs/promises";
16506
- import { extname, join as join8 } from "node:path";
17904
+ import { extname, join as join11 } from "node:path";
16507
17905
  var OUTLINE_EXTENSIONS = new Set([
16508
17906
  ".ts",
16509
17907
  ".tsx",
@@ -16575,12 +17973,12 @@ async function discoverSourceFiles(dir, maxFiles = 200) {
16575
17973
  return;
16576
17974
  if (entry.isDirectory()) {
16577
17975
  if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
16578
- await walk(join8(current, entry.name));
17976
+ await walk(join11(current, entry.name));
16579
17977
  }
16580
17978
  } else if (entry.isFile()) {
16581
17979
  const ext = extname(entry.name).toLowerCase();
16582
17980
  if (OUTLINE_EXTENSIONS.has(ext)) {
16583
- files.push(join8(current, entry.name));
17981
+ files.push(join11(current, entry.name));
16584
17982
  }
16585
17983
  }
16586
17984
  }
@@ -16691,14 +18089,14 @@ function registerReadingTools(pi, ctx, surface) {
16691
18089
  let dirArg = hasDirectory ? params.directory : undefined;
16692
18090
  if (!dirArg && hasFilePath) {
16693
18091
  try {
16694
- const resolved = resolve2(extCtx.cwd, params.filePath);
18092
+ const resolved = resolve3(extCtx.cwd, params.filePath);
16695
18093
  const st = await stat2(resolved);
16696
18094
  if (st.isDirectory())
16697
18095
  dirArg = params.filePath;
16698
18096
  } catch {}
16699
18097
  }
16700
18098
  if (dirArg) {
16701
- const dirPath = resolve2(extCtx.cwd, dirArg);
18099
+ const dirPath = resolve3(extCtx.cwd, dirArg);
16702
18100
  const files = await discoverSourceFiles(dirPath);
16703
18101
  if (files.length === 0) {
16704
18102
  return textResult(`No source files found under ${dirArg}`);
@@ -17272,7 +18670,7 @@ function coerceConfigureWarnings(warnings) {
17272
18670
  return warnings.filter(isConfigureWarning);
17273
18671
  }
17274
18672
  function resolveStorageDir() {
17275
- return join9(homedir7(), ".pi", "agent", "aft");
18673
+ return join12(homedir8(), ".pi", "agent", "aft");
17276
18674
  }
17277
18675
  function resolveToolSurface(config2) {
17278
18676
  const surface = config2.tool_surface ?? "recommended";
@@ -17355,16 +18753,85 @@ async function src_default(pi) {
17355
18753
  warn(`Failed to prepare ONNX Runtime: ${err instanceof Error ? err.message : String(err)}`);
17356
18754
  }
17357
18755
  }
17358
- const configOverrides = {
17359
- ...config2,
17360
- ...resolveLspConfigForConfigure(config2),
17361
- restrict_to_project_root: config2.restrict_to_project_root ?? true,
17362
- storage_dir: storageDir
17363
- };
17364
- delete configOverrides.lsp;
18756
+ const configOverrides = {};
18757
+ if (config2.format_on_edit !== undefined)
18758
+ configOverrides.format_on_edit = config2.format_on_edit;
18759
+ if (config2.validate_on_edit !== undefined)
18760
+ configOverrides.validate_on_edit = config2.validate_on_edit;
18761
+ if (config2.formatter !== undefined)
18762
+ configOverrides.formatter = config2.formatter;
18763
+ if (config2.checker !== undefined)
18764
+ configOverrides.checker = config2.checker;
18765
+ configOverrides.restrict_to_project_root = config2.restrict_to_project_root ?? true;
18766
+ if (config2.experimental_search_index !== undefined)
18767
+ configOverrides.experimental_search_index = config2.experimental_search_index;
18768
+ if (config2.experimental_semantic_search !== undefined)
18769
+ configOverrides.experimental_semantic_search = config2.experimental_semantic_search;
18770
+ Object.assign(configOverrides, resolveLspConfigForConfigure(config2));
18771
+ if (config2.semantic !== undefined)
18772
+ configOverrides.semantic = config2.semantic;
18773
+ if (config2.max_callgraph_files !== undefined)
18774
+ configOverrides.max_callgraph_files = config2.max_callgraph_files;
18775
+ if (config2.url_fetch_allow_private !== undefined)
18776
+ configOverrides.url_fetch_allow_private = config2.url_fetch_allow_private;
18777
+ configOverrides.storage_dir = storageDir;
17365
18778
  if (ortDylibDir) {
17366
18779
  configOverrides._ort_dylib_dir = ortDylibDir;
17367
18780
  }
18781
+ try {
18782
+ const lspAutoInstall = config2.lsp?.auto_install ?? true;
18783
+ const lspGraceDays = config2.lsp?.grace_days ?? 7;
18784
+ const lspVersions = config2.lsp?.versions ?? {};
18785
+ const lspDisabled = new Set(config2.lsp?.disabled ?? []);
18786
+ const projectRoot = process.cwd();
18787
+ const npmResult = runAutoInstall(projectRoot, {
18788
+ autoInstall: lspAutoInstall,
18789
+ graceDays: lspGraceDays,
18790
+ versions: lspVersions,
18791
+ disabled: lspDisabled
18792
+ });
18793
+ const relevantGithub = discoverRelevantGithubServers(projectRoot);
18794
+ const ghResult = runGithubAutoInstall(relevantGithub, {
18795
+ autoInstall: lspAutoInstall,
18796
+ graceDays: lspGraceDays,
18797
+ versions: lspVersions,
18798
+ disabled: lspDisabled
18799
+ });
18800
+ const mergedBinDirs = [...npmResult.cachedBinDirs, ...ghResult.cachedBinDirs];
18801
+ if (mergedBinDirs.length > 0) {
18802
+ configOverrides.lsp_paths_extra = mergedBinDirs;
18803
+ }
18804
+ if (npmResult.installsStarted > 0 || ghResult.installsStarted > 0) {
18805
+ log(`[lsp] auto-install: ${npmResult.installsStarted} npm + ${ghResult.installsStarted} github install(s) running in background`);
18806
+ }
18807
+ Promise.all([npmResult.installsComplete, ghResult.installsComplete]).then(() => {
18808
+ const actionable = [...npmResult.skipped, ...ghResult.skipped].filter((s) => {
18809
+ const r = s.reason.toLowerCase();
18810
+ if (r === "auto_install: false")
18811
+ return false;
18812
+ if (r === "disabled by config")
18813
+ return false;
18814
+ if (r === "not relevant to project")
18815
+ return false;
18816
+ if (r === "already installed")
18817
+ return false;
18818
+ if (r === "another install in progress")
18819
+ return false;
18820
+ return true;
18821
+ });
18822
+ if (actionable.length === 0)
18823
+ return;
18824
+ const lines = actionable.map((s) => ` • ${s.id}: ${s.reason}`).join(`
18825
+ `);
18826
+ warn(`[lsp] skipped or failed to install ${actionable.length} server(s):
18827
+ ${lines}
18828
+ ` + 'Pin a working version with `lsp.versions: { "<package>": "<version>" }` if grace is blocking, ' + "or set `lsp.auto_install: false` to suppress.");
18829
+ }).catch((err) => {
18830
+ warn(`[lsp] install-summary aggregation failed: ${err}`);
18831
+ });
18832
+ } catch (err) {
18833
+ warn(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
18834
+ }
17368
18835
  const pool = new BridgePool(binaryPath, {
17369
18836
  minVersion: PLUGIN_VERSION,
17370
18837
  onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
@@ -17421,6 +18888,7 @@ async function src_default(pi) {
17421
18888
  registerStatusCommand(pi, ctx);
17422
18889
  pi.on("session_shutdown", async () => {
17423
18890
  try {
18891
+ await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
17424
18892
  await pool.shutdown();
17425
18893
  log("Bridge pool shut down");
17426
18894
  } catch (err) {
@@ -17429,6 +18897,7 @@ async function src_default(pi) {
17429
18897
  });
17430
18898
  registerShutdownCleanup(async () => {
17431
18899
  try {
18900
+ await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
17432
18901
  await pool.shutdown();
17433
18902
  } catch (err) {
17434
18903
  warn(`Error during process shutdown: ${err instanceof Error ? err.message : String(err)}`);