@cortexkit/aft-opencode 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
@@ -7803,10 +7803,10 @@ var require_src2 = __commonJS((exports, module) => {
7803
7803
  });
7804
7804
 
7805
7805
  // src/index.ts
7806
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
7806
+ import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
- import { homedir as homedir7 } from "os";
7809
- import { join as join13 } from "path";
7808
+ import { homedir as homedir8 } from "os";
7809
+ import { join as join16 } from "path";
7810
7810
 
7811
7811
  // src/config.ts
7812
7812
  var import_comment_json = __toESM(require_src2(), 1);
@@ -21461,7 +21461,10 @@ var LspServerSchema = LspServerEntrySchema.extend({
21461
21461
  var LspConfigSchema = exports_external.object({
21462
21462
  servers: exports_external.record(exports_external.string().trim().min(1), LspServerEntrySchema).optional(),
21463
21463
  disabled: exports_external.array(exports_external.string().trim().min(1)).optional(),
21464
- python: exports_external.enum(["pyright", "ty", "auto"]).optional()
21464
+ python: exports_external.enum(["pyright", "ty", "auto"]).optional(),
21465
+ auto_install: exports_external.boolean().optional(),
21466
+ grace_days: exports_external.number().int().positive().optional(),
21467
+ versions: exports_external.record(exports_external.string().trim().min(1), exports_external.string().trim().min(1)).optional()
21465
21468
  });
21466
21469
  var AftConfigSchema = exports_external.object({
21467
21470
  format_on_edit: exports_external.boolean().optional(),
@@ -21490,7 +21493,7 @@ function resolveLspConfigForConfigure(config2) {
21490
21493
  switch (config2.lsp?.python ?? "auto") {
21491
21494
  case "ty":
21492
21495
  experimentalTy = true;
21493
- disabled.add("pyright");
21496
+ disabled.add("python");
21494
21497
  break;
21495
21498
  case "pyright":
21496
21499
  experimentalTy = false;
@@ -21604,26 +21607,79 @@ function mergeSemanticConfig(baseSemantic, overrideSemantic) {
21604
21607
  }
21605
21608
  return Object.fromEntries(Object.entries(semantic).filter(([, value]) => value !== undefined));
21606
21609
  }
21610
+ function mergeLspConfig(baseLsp, overrideLsp) {
21611
+ const projectLsp = {};
21612
+ if (overrideLsp?.python !== undefined)
21613
+ projectLsp.python = overrideLsp.python;
21614
+ const userDisabled = baseLsp?.disabled ?? [];
21615
+ const lsp = {
21616
+ ...baseLsp,
21617
+ ...projectLsp,
21618
+ ...userDisabled.length > 0 ? { disabled: [...userDisabled] } : {}
21619
+ };
21620
+ if (Object.values(lsp).every((value) => value === undefined)) {
21621
+ return;
21622
+ }
21623
+ return Object.fromEntries(Object.entries(lsp).filter(([, value]) => value !== undefined));
21624
+ }
21625
+ function getProjectLspStrippedKeys(lsp) {
21626
+ if (!lsp) {
21627
+ return [];
21628
+ }
21629
+ const strippedKeys = [];
21630
+ if (lsp.servers !== undefined)
21631
+ strippedKeys.push("lsp.servers");
21632
+ if (lsp.versions !== undefined)
21633
+ strippedKeys.push("lsp.versions");
21634
+ if (lsp.auto_install !== undefined)
21635
+ strippedKeys.push("lsp.auto_install");
21636
+ if (lsp.grace_days !== undefined)
21637
+ strippedKeys.push("lsp.grace_days");
21638
+ if (lsp.disabled !== undefined)
21639
+ strippedKeys.push("lsp.disabled");
21640
+ return strippedKeys;
21641
+ }
21642
+ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
21643
+ "tool_surface",
21644
+ "hoist_builtin_tools",
21645
+ "format_on_edit",
21646
+ "validate_on_edit",
21647
+ "experimental_search_index",
21648
+ "experimental_semantic_search",
21649
+ "experimental_lsp_ty"
21650
+ ]);
21651
+ function pickProjectSafeFields(override) {
21652
+ const safe = {};
21653
+ for (const key of PROJECT_SAFE_TOP_LEVEL_FIELDS) {
21654
+ if (override[key] !== undefined) {
21655
+ safe[key] = override[key];
21656
+ }
21657
+ }
21658
+ return safe;
21659
+ }
21660
+ function getStrippedTopLevelKeys(override) {
21661
+ const stripped = [];
21662
+ if (override.restrict_to_project_root !== undefined)
21663
+ stripped.push("restrict_to_project_root");
21664
+ if (override.url_fetch_allow_private !== undefined)
21665
+ stripped.push("url_fetch_allow_private");
21666
+ if (override.max_callgraph_files !== undefined)
21667
+ stripped.push("max_callgraph_files");
21668
+ return stripped;
21669
+ }
21607
21670
  function mergeConfigs(base, override) {
21608
21671
  const disabledTools = [...base.disabled_tools ?? [], ...override.disabled_tools ?? []];
21609
21672
  const formatter = { ...base.formatter, ...override.formatter };
21610
21673
  const checker = { ...base.checker, ...override.checker };
21611
21674
  const semantic = mergeSemanticConfig(base.semantic, override.semantic);
21612
- const lspServers = { ...base.lsp?.servers, ...override.lsp?.servers };
21613
- const disabledLsp = [...base.lsp?.disabled ?? [], ...override.lsp?.disabled ?? []];
21614
- const lsp = {
21615
- ...base.lsp,
21616
- ...override.lsp,
21617
- ...Object.keys(lspServers).length > 0 ? { servers: lspServers } : {},
21618
- ...disabledLsp.length > 0 ? { disabled: [...new Set(disabledLsp)] } : {}
21619
- };
21620
- const { semantic: _stripSemantic, ...safeOverride } = override;
21675
+ const lsp = mergeLspConfig(base.lsp, override.lsp);
21676
+ const safeOverride = pickProjectSafeFields(override);
21621
21677
  return {
21622
21678
  ...base,
21623
21679
  ...safeOverride,
21624
21680
  ...Object.keys(formatter).length > 0 ? { formatter } : {},
21625
21681
  ...Object.keys(checker).length > 0 ? { checker } : {},
21626
- ...Object.values(lsp).some((value) => value !== undefined) ? { lsp } : {},
21682
+ ...lsp ? { lsp } : {},
21627
21683
  semantic,
21628
21684
  ...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
21629
21685
  };
@@ -21650,6 +21706,14 @@ function loadAftConfig(projectDirectory) {
21650
21706
  if (projectConfig.semantic?.backend !== undefined || projectConfig.semantic?.base_url !== undefined || projectConfig.semantic?.api_key_env !== undefined) {
21651
21707
  warn("Ignoring semantic.backend/base_url/api_key_env from project config (security: use user config for external backends)");
21652
21708
  }
21709
+ const strippedLspKeys = getProjectLspStrippedKeys(projectConfig.lsp);
21710
+ if (strippedLspKeys.length > 0) {
21711
+ warn(`Ignoring ${strippedLspKeys.join(", ")} from project config ${projectConfigPath} (security: these LSP settings only honor user-level config)`);
21712
+ }
21713
+ const strippedTopLevelKeys = getStrippedTopLevelKeys(projectConfig);
21714
+ if (strippedTopLevelKeys.length > 0) {
21715
+ warn(`Ignoring ${strippedTopLevelKeys.join(", ")} from project config ${projectConfigPath} (security: these settings only honor user-level config \u2014 a project should not weaken security boundaries for the user)`);
21716
+ }
21653
21717
  config2 = mergeConfigs(config2, projectConfig);
21654
21718
  }
21655
21719
  return config2;
@@ -21805,6 +21869,1361 @@ async function fetchLatestTag() {
21805
21869
  }
21806
21870
  }
21807
21871
 
21872
+ // src/lsp-auto-install.ts
21873
+ import { spawn } from "child_process";
21874
+ import { createHash } from "crypto";
21875
+ import { createReadStream, statSync as statSync2 } from "fs";
21876
+
21877
+ // src/lsp-cache.ts
21878
+ import {
21879
+ closeSync,
21880
+ existsSync as existsSync3,
21881
+ mkdirSync as mkdirSync2,
21882
+ openSync,
21883
+ readFileSync as readFileSync2,
21884
+ statSync,
21885
+ unlinkSync as unlinkSync2,
21886
+ writeFileSync
21887
+ } from "fs";
21888
+ import { homedir as homedir3 } from "os";
21889
+ import { join as join4 } from "path";
21890
+ function aftCacheBase() {
21891
+ const override = process.env.AFT_CACHE_DIR;
21892
+ if (override && override.length > 0)
21893
+ return override;
21894
+ if (process.platform === "win32") {
21895
+ const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
21896
+ const base2 = localAppData || join4(homedir3(), "AppData", "Local");
21897
+ return join4(base2, "aft");
21898
+ }
21899
+ const base = process.env.XDG_CACHE_HOME || join4(homedir3(), ".cache");
21900
+ return join4(base, "aft");
21901
+ }
21902
+ function lspCacheRoot() {
21903
+ return join4(aftCacheBase(), "lsp-packages");
21904
+ }
21905
+ function lspPackageDir(npmPackage) {
21906
+ return join4(lspCacheRoot(), encodeURIComponent(npmPackage));
21907
+ }
21908
+ function lspBinaryPath(npmPackage, binary) {
21909
+ return join4(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
21910
+ }
21911
+ function lspBinDir(npmPackage) {
21912
+ return join4(lspPackageDir(npmPackage), "node_modules", ".bin");
21913
+ }
21914
+ function isInstalled(npmPackage, binary) {
21915
+ for (const candidate of lspBinaryCandidates(binary)) {
21916
+ try {
21917
+ if (statSync(join4(lspBinDir(npmPackage), candidate)).isFile())
21918
+ return true;
21919
+ } catch {}
21920
+ }
21921
+ return false;
21922
+ }
21923
+ function lspBinaryCandidates(binary) {
21924
+ if (process.platform !== "win32")
21925
+ return [binary];
21926
+ return [binary, `${binary}.cmd`, `${binary}.exe`, `${binary}.bat`];
21927
+ }
21928
+ var INSTALLED_META_FILE = ".aft-installed";
21929
+ function writeInstalledMetaIn(installDir, version2, sha256) {
21930
+ try {
21931
+ mkdirSync2(installDir, { recursive: true });
21932
+ const meta3 = {
21933
+ version: version2,
21934
+ installedAt: new Date().toISOString(),
21935
+ ...sha256 ? { sha256 } : {}
21936
+ };
21937
+ writeFileSync(join4(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
21938
+ } catch (err) {
21939
+ log(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
21940
+ }
21941
+ }
21942
+ function readInstalledMetaIn(installDir) {
21943
+ const path2 = join4(installDir, INSTALLED_META_FILE);
21944
+ try {
21945
+ if (!statSync(path2).isFile())
21946
+ return null;
21947
+ const raw = readFileSync2(path2, "utf8");
21948
+ const parsed = JSON.parse(raw);
21949
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
21950
+ return null;
21951
+ return {
21952
+ version: parsed.version,
21953
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
21954
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {}
21955
+ };
21956
+ } catch {
21957
+ return null;
21958
+ }
21959
+ }
21960
+ function writeInstalledMeta(packageKey, version2, sha256) {
21961
+ writeInstalledMetaIn(lspPackageDir(packageKey), version2, sha256);
21962
+ }
21963
+ function readInstalledMeta(packageKey) {
21964
+ return readInstalledMetaIn(lspPackageDir(packageKey));
21965
+ }
21966
+ function lockPath(npmPackage) {
21967
+ return join4(lspPackageDir(npmPackage), ".aft-installing");
21968
+ }
21969
+ var STALE_LOCK_MS = 30 * 60 * 1000;
21970
+ function acquireInstallLock(lockKey) {
21971
+ mkdirSync2(lspPackageDir(lockKey), { recursive: true });
21972
+ const lock = lockPath(lockKey);
21973
+ const tryClaim = () => {
21974
+ try {
21975
+ const fd = openSync(lock, "wx");
21976
+ try {
21977
+ writeFileSync(fd, `${process.pid}
21978
+ ${new Date().toISOString()}
21979
+ `);
21980
+ } finally {
21981
+ closeSync(fd);
21982
+ }
21983
+ return true;
21984
+ } catch (err) {
21985
+ const code = err.code;
21986
+ if (code === "EEXIST")
21987
+ return false;
21988
+ warn(`[lsp] unexpected error acquiring install lock for ${lockKey}: ${err}`);
21989
+ return false;
21990
+ }
21991
+ };
21992
+ if (tryClaim())
21993
+ return true;
21994
+ let owningPid = null;
21995
+ let lockMtimeMs = 0;
21996
+ try {
21997
+ const raw = readFileSync2(lock, "utf8");
21998
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
21999
+ const parsed = Number.parseInt(firstLine, 10);
22000
+ if (Number.isFinite(parsed) && parsed > 0)
22001
+ owningPid = parsed;
22002
+ lockMtimeMs = statSync(lock).mtimeMs;
22003
+ } catch {
22004
+ return tryClaim();
22005
+ }
22006
+ const age = Date.now() - lockMtimeMs;
22007
+ const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS;
22008
+ const skipLiveness = process.platform === "win32";
22009
+ const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive(owningPid);
22010
+ if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
22011
+ return false;
22012
+ }
22013
+ log(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
22014
+ try {
22015
+ unlinkSync2(lock);
22016
+ } catch {}
22017
+ return tryClaim();
22018
+ }
22019
+ function isProcessAlive(pid) {
22020
+ try {
22021
+ process.kill(pid, 0);
22022
+ return true;
22023
+ } catch (err) {
22024
+ const code = err.code;
22025
+ if (code === "ESRCH")
22026
+ return false;
22027
+ return true;
22028
+ }
22029
+ }
22030
+ function releaseInstallLock(lockKey) {
22031
+ const lock = lockPath(lockKey);
22032
+ try {
22033
+ if (existsSync3(lock)) {
22034
+ unlinkSync2(lock);
22035
+ }
22036
+ } catch (err) {
22037
+ warn(`[lsp] failed to release install lock for ${lockKey}: ${err}`);
22038
+ }
22039
+ }
22040
+ async function withInstallLock(lockKey, task) {
22041
+ if (!acquireInstallLock(lockKey))
22042
+ return null;
22043
+ try {
22044
+ return await task();
22045
+ } finally {
22046
+ releaseInstallLock(lockKey);
22047
+ }
22048
+ }
22049
+ var VERSION_CHECK_FILE = ".aft-version-check";
22050
+ function readVersionCheck(npmPackage) {
22051
+ const file2 = join4(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22052
+ try {
22053
+ const raw = readFileSync2(file2, "utf8");
22054
+ const parsed = JSON.parse(raw);
22055
+ if (typeof parsed.last_checked === "string") {
22056
+ return {
22057
+ last_checked: parsed.last_checked,
22058
+ latest_eligible: typeof parsed.latest_eligible === "string" ? parsed.latest_eligible : null
22059
+ };
22060
+ }
22061
+ return null;
22062
+ } catch {
22063
+ return null;
22064
+ }
22065
+ }
22066
+ function writeVersionCheck(npmPackage, latest) {
22067
+ mkdirSync2(lspPackageDir(npmPackage), { recursive: true });
22068
+ const file2 = join4(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22069
+ const record2 = {
22070
+ last_checked: new Date().toISOString(),
22071
+ latest_eligible: latest
22072
+ };
22073
+ writeFileSync(file2, JSON.stringify(record2, null, 2));
22074
+ }
22075
+ function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
22076
+ if (!record2)
22077
+ return true;
22078
+ const age = Date.now() - new Date(record2.last_checked).getTime();
22079
+ if (Number.isNaN(age) || age < 0)
22080
+ return true;
22081
+ return age >= weeklyCheckIntervalMs;
22082
+ }
22083
+
22084
+ // src/lsp-github-probe.ts
22085
+ function pickEligibleRelease(releases, graceDays, now = Date.now()) {
22086
+ const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
22087
+ const candidates = releases.filter((r) => !r.draft && !r.prerelease && typeof r.published_at === "string").map((r) => {
22088
+ const ts = Date.parse(r.published_at);
22089
+ return { release: r, ts };
22090
+ }).filter((c) => !Number.isNaN(c.ts)).sort((a, b) => b.ts - a.ts);
22091
+ const eligible = candidates.filter((c) => c.ts <= cutoff);
22092
+ const blockedByGrace = candidates.length > 0 && eligible.length === 0;
22093
+ const chosen = eligible[0]?.release;
22094
+ if (!chosen) {
22095
+ return { tag: null, assets: [], blockedByGrace };
22096
+ }
22097
+ return {
22098
+ tag: chosen.tag_name,
22099
+ assets: (chosen.assets ?? []).map((a) => ({
22100
+ name: a.name,
22101
+ url: a.browser_download_url,
22102
+ size: a.size
22103
+ })),
22104
+ blockedByGrace: false
22105
+ };
22106
+ }
22107
+ async function probeGithubReleases(githubRepo, graceDays, fetchImpl = fetch) {
22108
+ const url2 = `https://api.github.com/repos/${githubRepo}/releases?per_page=30`;
22109
+ try {
22110
+ const headers = { accept: "application/vnd.github+json" };
22111
+ if (process.env.GITHUB_TOKEN) {
22112
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
22113
+ }
22114
+ const res = await fetchImpl(url2, {
22115
+ headers,
22116
+ signal: AbortSignal.timeout(1e4)
22117
+ });
22118
+ if (!res.ok) {
22119
+ warn(`[lsp] github releases probe failed for ${githubRepo}: HTTP ${res.status}`);
22120
+ return null;
22121
+ }
22122
+ const json2 = await res.json();
22123
+ if (!Array.isArray(json2)) {
22124
+ warn(`[lsp] unexpected response shape from github releases for ${githubRepo}`);
22125
+ return null;
22126
+ }
22127
+ return pickEligibleRelease(json2, graceDays);
22128
+ } catch (err) {
22129
+ warn(`[lsp] github releases probe failed for ${githubRepo}: ${err}`);
22130
+ return null;
22131
+ }
22132
+ }
22133
+ var SAFE_VERSION_RE = /^[A-Za-z0-9._+-]+$/;
22134
+ function assertSafeVersion(version2) {
22135
+ if (!SAFE_VERSION_RE.test(version2)) {
22136
+ throw new Error(`unsafe version/tag string ${JSON.stringify(version2)}: must match ${SAFE_VERSION_RE.source}`);
22137
+ }
22138
+ }
22139
+ function stripTagV(tag) {
22140
+ assertSafeVersion(tag);
22141
+ return tag.startsWith("v") ? tag.slice(1) : tag;
22142
+ }
22143
+
22144
+ // src/lsp-npm-table.ts
22145
+ var NPM_LSP_TABLE = [
22146
+ {
22147
+ id: "typescript",
22148
+ npm: "typescript-language-server",
22149
+ binary: "typescript-language-server",
22150
+ extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
22151
+ rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
22152
+ },
22153
+ {
22154
+ id: "python",
22155
+ npm: "pyright",
22156
+ binary: "pyright-langserver",
22157
+ extensions: ["py", "pyi"],
22158
+ rootMarkers: ["pyproject.toml", "pyrightconfig.json", "requirements.txt"]
22159
+ },
22160
+ {
22161
+ id: "yaml",
22162
+ npm: "yaml-language-server",
22163
+ binary: "yaml-language-server",
22164
+ extensions: ["yaml", "yml"]
22165
+ },
22166
+ {
22167
+ id: "bash",
22168
+ npm: "bash-language-server",
22169
+ binary: "bash-language-server",
22170
+ extensions: ["sh", "bash", "zsh"]
22171
+ },
22172
+ {
22173
+ id: "dockerfile",
22174
+ npm: "dockerfile-language-server-nodejs",
22175
+ binary: "docker-langserver",
22176
+ extensions: ["dockerfile"],
22177
+ rootMarkers: ["Dockerfile", "dockerfile"]
22178
+ },
22179
+ {
22180
+ id: "vue",
22181
+ npm: "@vue/language-server",
22182
+ binary: "vue-language-server",
22183
+ extensions: ["vue"]
22184
+ },
22185
+ {
22186
+ id: "astro",
22187
+ npm: "@astrojs/language-server",
22188
+ binary: "astro-ls",
22189
+ extensions: ["astro"]
22190
+ },
22191
+ {
22192
+ id: "svelte",
22193
+ npm: "svelte-language-server",
22194
+ binary: "svelteserver",
22195
+ extensions: ["svelte"]
22196
+ },
22197
+ {
22198
+ id: "php-intelephense",
22199
+ npm: "intelephense",
22200
+ binary: "intelephense",
22201
+ extensions: ["php"]
22202
+ },
22203
+ {
22204
+ id: "biome",
22205
+ npm: "@biomejs/biome",
22206
+ binary: "biome",
22207
+ extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts", "json", "jsonc"],
22208
+ rootMarkers: ["biome.json", "biome.jsonc"]
22209
+ }
22210
+ ];
22211
+
22212
+ // src/lsp-project-relevance.ts
22213
+ import { existsSync as existsSync4, readdirSync } from "fs";
22214
+ import { join as join5 } from "path";
22215
+ var MAX_WALK_DIRS = 200;
22216
+ var MAX_WALK_DEPTH = 4;
22217
+ var NOISE_DIRS = new Set([
22218
+ ".git",
22219
+ ".next",
22220
+ ".venv",
22221
+ "__pycache__",
22222
+ "build",
22223
+ "dist",
22224
+ "node_modules",
22225
+ "target"
22226
+ ]);
22227
+ function hasRootMarker(projectRoot, rootMarkers) {
22228
+ if (!rootMarkers)
22229
+ return false;
22230
+ for (const marker of rootMarkers) {
22231
+ if (existsSync4(join5(projectRoot, marker)))
22232
+ return true;
22233
+ }
22234
+ return false;
22235
+ }
22236
+ function relevantExtensionsInProject(projectRoot, extToServer) {
22237
+ const wanted = new Set(Object.keys(extToServer).map((ext) => ext.toLowerCase()));
22238
+ const found = new Set;
22239
+ if (wanted.size === 0)
22240
+ return found;
22241
+ const queue = [{ dir: projectRoot, depth: 0 }];
22242
+ let visitedDirs = 0;
22243
+ while (queue.length > 0 && visitedDirs < MAX_WALK_DIRS) {
22244
+ const current = queue.shift();
22245
+ if (!current)
22246
+ break;
22247
+ visitedDirs += 1;
22248
+ let entries;
22249
+ try {
22250
+ entries = readdirSync(current.dir, { withFileTypes: true });
22251
+ } catch {
22252
+ continue;
22253
+ }
22254
+ for (const entry of entries) {
22255
+ if (entry.isDirectory()) {
22256
+ if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
22257
+ queue.push({ dir: join5(current.dir, entry.name), depth: current.depth + 1 });
22258
+ }
22259
+ continue;
22260
+ }
22261
+ if (!entry.isFile())
22262
+ continue;
22263
+ const ext = extensionOf(entry.name);
22264
+ if (ext && wanted.has(ext))
22265
+ found.add(ext);
22266
+ }
22267
+ }
22268
+ return found;
22269
+ }
22270
+ function extensionOf(fileName) {
22271
+ const dot = fileName.lastIndexOf(".");
22272
+ if (dot < 0 || dot === fileName.length - 1)
22273
+ return null;
22274
+ return fileName.slice(dot + 1).toLowerCase();
22275
+ }
22276
+
22277
+ // src/lsp-registry-probe.ts
22278
+ var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
22279
+ function pickEligibleVersion(response, graceDays, now = Date.now()) {
22280
+ const times = response.time || {};
22281
+ const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
22282
+ const candidates = [];
22283
+ for (const [version2, publishedAt] of Object.entries(times)) {
22284
+ if (version2 === "created" || version2 === "modified")
22285
+ continue;
22286
+ if (version2.includes("-"))
22287
+ continue;
22288
+ if (typeof publishedAt !== "string")
22289
+ continue;
22290
+ const ts = Date.parse(publishedAt);
22291
+ if (Number.isNaN(ts))
22292
+ continue;
22293
+ candidates.push({ version: version2, publishedAt, ts });
22294
+ }
22295
+ candidates.sort((a, b) => b.ts - a.ts);
22296
+ const eligible = candidates.filter((c) => c.ts <= cutoff);
22297
+ const blockedByGrace = candidates.length > 0 && eligible.length === 0;
22298
+ return {
22299
+ version: eligible[0]?.version ?? null,
22300
+ blockedByGrace,
22301
+ eligible: eligible.map(({ version: version2, publishedAt }) => ({ version: version2, publishedAt }))
22302
+ };
22303
+ }
22304
+ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
22305
+ const encoded = encodeURIComponent(npmPackage).replace(/^%40/, "@");
22306
+ const url2 = `${NPM_REGISTRY_BASE}/${encoded}`;
22307
+ try {
22308
+ const res = await fetchImpl(url2, {
22309
+ headers: { accept: "application/json" },
22310
+ signal: AbortSignal.timeout(1e4)
22311
+ });
22312
+ if (!res.ok) {
22313
+ warn(`[lsp] registry probe failed for ${npmPackage}: HTTP ${res.status}`);
22314
+ return null;
22315
+ }
22316
+ const json2 = await res.json();
22317
+ return pickEligibleVersion(json2, graceDays);
22318
+ } catch (err) {
22319
+ warn(`[lsp] registry probe failed for ${npmPackage}: ${err}`);
22320
+ return null;
22321
+ }
22322
+ }
22323
+
22324
+ // src/lsp-auto-install.ts
22325
+ function isProjectRelevant(spec, projectRoot, projectExtensions) {
22326
+ if (hasRootMarker(projectRoot, spec.rootMarkers))
22327
+ return true;
22328
+ const extensions = projectExtensions();
22329
+ return spec.extensions.some((ext) => extensions.has(ext.toLowerCase()));
22330
+ }
22331
+ var npmExtToServerIds = buildExtensionMap(NPM_LSP_TABLE);
22332
+ function buildExtensionMap(specs) {
22333
+ const byExt = {};
22334
+ for (const spec of specs) {
22335
+ for (const ext of spec.extensions) {
22336
+ const key = ext.toLowerCase();
22337
+ byExt[key] ??= [];
22338
+ byExt[key].push(spec.id);
22339
+ }
22340
+ }
22341
+ return byExt;
22342
+ }
22343
+ var inFlightAutoInstalls = new Set;
22344
+ function trackInFlightAutoInstall(controller, promise2) {
22345
+ const entry = { controller, promise: promise2 };
22346
+ inFlightAutoInstalls.add(entry);
22347
+ promise2.then(() => inFlightAutoInstalls.delete(entry), () => inFlightAutoInstalls.delete(entry));
22348
+ return promise2;
22349
+ }
22350
+ async function abortInFlightAutoInstalls() {
22351
+ const installs = Array.from(inFlightAutoInstalls);
22352
+ for (const install of installs) {
22353
+ install.controller.abort();
22354
+ }
22355
+ await Promise.allSettled(installs.map((install) => install.promise));
22356
+ }
22357
+ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
22358
+ const pinned = config2.versions[spec.npm];
22359
+ if (pinned) {
22360
+ assertSafeVersion(pinned);
22361
+ return { version: pinned, pinned: true, probe: null };
22362
+ }
22363
+ const cached2 = readVersionCheck(spec.npm);
22364
+ const weeklyMs = config2.graceDays * 24 * 60 * 60 * 1000;
22365
+ if (!shouldRecheckVersion(cached2, weeklyMs) && cached2?.latest_eligible) {
22366
+ return { version: cached2.latest_eligible, pinned: false, probe: null };
22367
+ }
22368
+ const probe = await probeRegistry(spec.npm, config2.graceDays, fetchImpl);
22369
+ if (!probe) {
22370
+ return {
22371
+ version: cached2?.latest_eligible ?? null,
22372
+ pinned: false,
22373
+ probe: null
22374
+ };
22375
+ }
22376
+ writeVersionCheck(spec.npm, probe.version);
22377
+ return { version: probe.version, pinned: false, probe };
22378
+ }
22379
+ function runInstall(spec, version2, cwd, signal) {
22380
+ return new Promise((resolve) => {
22381
+ const target = `${spec.npm}@${version2}`;
22382
+ log(`[lsp] installing ${target} to ${cwd}`);
22383
+ if (signal?.aborted) {
22384
+ warn(`[lsp] install ${target} aborted before spawn`);
22385
+ resolve(false);
22386
+ return;
22387
+ }
22388
+ const child = spawn("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
22389
+ stdio: ["ignore", "pipe", "pipe"]
22390
+ });
22391
+ child.unref();
22392
+ let stderrBuf = "";
22393
+ let settled = false;
22394
+ let killTimer = null;
22395
+ const cleanup = () => {
22396
+ signal?.removeEventListener("abort", onAbort);
22397
+ if (killTimer)
22398
+ clearTimeout(killTimer);
22399
+ };
22400
+ const finish = (ok) => {
22401
+ if (settled)
22402
+ return;
22403
+ settled = true;
22404
+ cleanup();
22405
+ resolve(ok);
22406
+ };
22407
+ const onAbort = () => {
22408
+ warn(`[lsp] install ${target} aborted during shutdown`);
22409
+ child.kill("SIGTERM");
22410
+ killTimer = setTimeout(() => {
22411
+ if (!settled)
22412
+ child.kill("SIGKILL");
22413
+ }, 5000);
22414
+ killTimer.unref?.();
22415
+ };
22416
+ signal?.addEventListener("abort", onAbort, { once: true });
22417
+ if (signal?.aborted)
22418
+ onAbort();
22419
+ child.stdout?.on("data", () => {});
22420
+ child.stderr?.on("data", (chunk) => {
22421
+ const text = String(chunk);
22422
+ stderrBuf += text;
22423
+ if (stderrBuf.length > 4096) {
22424
+ stderrBuf = stderrBuf.slice(stderrBuf.length - 4096);
22425
+ }
22426
+ });
22427
+ child.on("error", (err) => {
22428
+ error48(`[lsp] install ${target} failed to spawn: ${err}`);
22429
+ finish(false);
22430
+ });
22431
+ child.on("exit", (code) => {
22432
+ if (code === 0) {
22433
+ log(`[lsp] installed ${target}`);
22434
+ finish(true);
22435
+ } else {
22436
+ error48(`[lsp] install ${target} exited with code ${code}; last stderr:
22437
+ ${stderrBuf.trim()}`);
22438
+ finish(false);
22439
+ }
22440
+ });
22441
+ });
22442
+ }
22443
+ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
22444
+ const outcome = await withInstallLock(spec.npm, async () => {
22445
+ const { version: version2, probe } = await resolveTargetVersion(spec, config2, fetchImpl);
22446
+ if (!version2) {
22447
+ const installed = isInstalled(spec.npm, spec.binary);
22448
+ if (installed) {
22449
+ warn(`[lsp] no eligible version of ${spec.npm} (grace=${config2.graceDays}d); keeping existing install`);
22450
+ return { started: false, reason: "kept existing install" };
22451
+ }
22452
+ const blocked = probe?.blockedByGrace ? `all versions are within ${config2.graceDays}-day grace window` : "registry probe failed";
22453
+ warn(`[lsp] skipping ${spec.npm}: ${blocked}`);
22454
+ return { started: false, reason: blocked };
22455
+ }
22456
+ if (isInstalled(spec.npm, spec.binary)) {
22457
+ const installedMeta = readInstalledMeta(spec.npm);
22458
+ if (installedMeta && installedMeta.version === version2) {
22459
+ if (installedMeta.sha256) {
22460
+ const currentHash = await hashInstalledBinary(spec).catch((err) => {
22461
+ warn(`[lsp] could not hash existing ${spec.npm} binary for TOFU check: ${err}`);
22462
+ return null;
22463
+ });
22464
+ if (currentHash && currentHash !== installedMeta.sha256) {
22465
+ error48(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
22466
+ return {
22467
+ started: false,
22468
+ reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} \u2014 see plugin log`
22469
+ };
22470
+ }
22471
+ }
22472
+ return { started: false, reason: "already installed" };
22473
+ }
22474
+ if (installedMeta) {
22475
+ log(`[lsp] reinstalling ${spec.npm}: cached ${installedMeta.version} \u2260 target ${version2}`);
22476
+ } else {
22477
+ log(`[lsp] reinstalling ${spec.npm}@${version2}: no installed-version metadata recorded`);
22478
+ }
22479
+ }
22480
+ const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
22481
+ error48(`[lsp] background install ${spec.npm} crashed: ${err}`);
22482
+ return false;
22483
+ });
22484
+ if (!ok) {
22485
+ return { started: true, reason: "install failed (see plugin log)" };
22486
+ }
22487
+ const installedHash = await hashInstalledBinary(spec).catch((err) => {
22488
+ warn(`[lsp] could not hash newly-installed ${spec.npm} binary: ${err}`);
22489
+ return null;
22490
+ });
22491
+ if (installedHash) {
22492
+ log(`[lsp] ${spec.npm}@${version2} installed sha256=${installedHash}`);
22493
+ }
22494
+ writeInstalledMeta(spec.npm, version2, installedHash ?? undefined);
22495
+ return { started: true };
22496
+ });
22497
+ if (outcome === null) {
22498
+ return { started: false, reason: "another install in progress" };
22499
+ }
22500
+ return outcome;
22501
+ }
22502
+ function cachedPackageDir(npmPackage) {
22503
+ return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
22504
+ }
22505
+ function hashInstalledBinary(spec) {
22506
+ return new Promise((resolve, reject) => {
22507
+ const candidates = process.platform === "win32" ? [
22508
+ lspBinaryPath(spec.npm, spec.binary),
22509
+ lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
22510
+ lspBinaryPath(spec.npm, `${spec.binary}.exe`),
22511
+ lspBinaryPath(spec.npm, `${spec.binary}.bat`)
22512
+ ] : [lspBinaryPath(spec.npm, spec.binary)];
22513
+ let pathToHash = null;
22514
+ for (const p of candidates) {
22515
+ try {
22516
+ if (statSync2(p).isFile()) {
22517
+ pathToHash = p;
22518
+ break;
22519
+ }
22520
+ } catch {}
22521
+ }
22522
+ if (!pathToHash) {
22523
+ reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
22524
+ return;
22525
+ }
22526
+ const hash2 = createHash("sha256");
22527
+ const stream = createReadStream(pathToHash);
22528
+ stream.on("error", reject);
22529
+ stream.on("data", (chunk) => hash2.update(chunk));
22530
+ stream.on("end", () => resolve(hash2.digest("hex")));
22531
+ });
22532
+ }
22533
+ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
22534
+ const cachedBinDirs = [];
22535
+ const skipped = [];
22536
+ const installPromises = [];
22537
+ let installsStarted = 0;
22538
+ let projectExtensions = null;
22539
+ const getProjectExtensions = () => {
22540
+ projectExtensions ??= relevantExtensionsInProject(projectRoot, npmExtToServerIds);
22541
+ return projectExtensions;
22542
+ };
22543
+ for (const spec of NPM_LSP_TABLE) {
22544
+ if (isInstalled(spec.npm, spec.binary)) {
22545
+ cachedBinDirs.push(lspBinDir(spec.npm));
22546
+ }
22547
+ if (config2.disabled.has(spec.id)) {
22548
+ skipped.push({ id: spec.id, reason: "disabled by config" });
22549
+ continue;
22550
+ }
22551
+ if (!config2.autoInstall) {
22552
+ skipped.push({ id: spec.id, reason: "auto_install: false" });
22553
+ continue;
22554
+ }
22555
+ if (!isProjectRelevant(spec, projectRoot, getProjectExtensions)) {
22556
+ skipped.push({ id: spec.id, reason: "not relevant to project" });
22557
+ continue;
22558
+ }
22559
+ installsStarted += 1;
22560
+ const controller = new AbortController;
22561
+ const promise2 = ensureServerInstalled(spec, config2, fetchImpl, controller.signal).then((outcome) => {
22562
+ if (!outcome.started)
22563
+ installsStarted -= 1;
22564
+ if (outcome.reason && outcome.reason !== "already installed") {
22565
+ skipped.push({ id: spec.id, reason: outcome.reason });
22566
+ }
22567
+ }, (err) => {
22568
+ installsStarted -= 1;
22569
+ const reason = err instanceof Error ? err.message : String(err);
22570
+ skipped.push({ id: spec.id, reason: `install error: ${reason}` });
22571
+ error48(`[lsp] background install ${spec.npm} promise rejected: ${reason}`);
22572
+ });
22573
+ installPromises.push(trackInFlightAutoInstall(controller, promise2));
22574
+ }
22575
+ return {
22576
+ cachedBinDirs,
22577
+ get installsStarted() {
22578
+ return installsStarted;
22579
+ },
22580
+ skipped,
22581
+ installsComplete: Promise.all(installPromises).then(() => {})
22582
+ };
22583
+ }
22584
+
22585
+ // src/lsp-github-install.ts
22586
+ import { execFileSync } from "child_process";
22587
+ import { createHash as createHash2, randomBytes } from "crypto";
22588
+ import {
22589
+ copyFileSync,
22590
+ createReadStream as createReadStream2,
22591
+ createWriteStream,
22592
+ existsSync as existsSync5,
22593
+ lstatSync,
22594
+ mkdirSync as mkdirSync3,
22595
+ readdirSync as readdirSync2,
22596
+ readlinkSync,
22597
+ realpathSync,
22598
+ renameSync,
22599
+ rmSync,
22600
+ statSync as statSync3,
22601
+ unlinkSync as unlinkSync3
22602
+ } from "fs";
22603
+ import { dirname, join as join6, relative, resolve } from "path";
22604
+ import { Readable } from "stream";
22605
+ import { pipeline } from "stream/promises";
22606
+
22607
+ // src/lsp-github-table.ts
22608
+ function exe(platform, name) {
22609
+ return platform === "win32" ? `${name}.exe` : name;
22610
+ }
22611
+ var CLANGD = {
22612
+ id: "clangd",
22613
+ githubRepo: "clangd/clangd",
22614
+ binary: "clangd",
22615
+ resolveAsset: (platform, _arch, version2) => {
22616
+ const platformName = platform === "darwin" ? "mac" : platform === "linux" ? "linux" : "windows";
22617
+ return { name: `clangd-${platformName}-${version2}.zip`, archive: "zip" };
22618
+ },
22619
+ binaryPathInArchive: (platform, _arch, version2) => `clangd_${version2}/bin/${exe(platform, "clangd")}`
22620
+ };
22621
+ var LUA_LS = {
22622
+ id: "lua-ls",
22623
+ githubRepo: "LuaLS/lua-language-server",
22624
+ binary: "lua-language-server",
22625
+ resolveAsset: (platform, arch, version2) => {
22626
+ const ext = platform === "win32" ? "zip" : "tar.gz";
22627
+ const platformName = platform === "darwin" ? "darwin" : platform === "linux" ? "linux" : "win32";
22628
+ const archName = arch === "arm64" ? "arm64" : "x64";
22629
+ return {
22630
+ name: `lua-language-server-${version2}-${platformName}-${archName}.${ext}`,
22631
+ archive: ext
22632
+ };
22633
+ },
22634
+ binaryPathInArchive: (platform, _arch, _version) => `bin/${exe(platform, "lua-language-server")}`
22635
+ };
22636
+ var ZLS = {
22637
+ id: "zls",
22638
+ githubRepo: "zigtools/zls",
22639
+ binary: "zls",
22640
+ resolveAsset: (platform, arch, _version) => {
22641
+ const ext = platform === "win32" ? "zip" : "tar.xz";
22642
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
22643
+ const platformName = platform === "darwin" ? "macos" : platform === "linux" ? "linux" : "windows";
22644
+ return { name: `zls-${archName}-${platformName}.${ext}`, archive: ext };
22645
+ },
22646
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "zls")
22647
+ };
22648
+ var TINYMIST = {
22649
+ id: "tinymist",
22650
+ githubRepo: "Myriad-Dreamin/tinymist",
22651
+ binary: "tinymist",
22652
+ resolveAsset: (platform, arch, _version) => {
22653
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
22654
+ const triple = platform === "darwin" ? "apple-darwin" : platform === "linux" ? "unknown-linux-gnu" : "pc-windows-msvc";
22655
+ const ext = platform === "win32" ? "zip" : "tar.gz";
22656
+ return { name: `tinymist-${archName}-${triple}.${ext}`, archive: ext };
22657
+ },
22658
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "tinymist")
22659
+ };
22660
+ var TEXLAB = {
22661
+ id: "texlab",
22662
+ githubRepo: "latex-lsp/texlab",
22663
+ binary: "texlab",
22664
+ resolveAsset: (platform, arch, _version) => {
22665
+ const archName = arch === "arm64" ? "aarch64" : "x86_64";
22666
+ const platformName = platform === "darwin" ? "macos" : platform === "linux" ? "linux" : "windows";
22667
+ const ext = platform === "win32" ? "zip" : "tar.gz";
22668
+ return { name: `texlab-${archName}-${platformName}.${ext}`, archive: ext };
22669
+ },
22670
+ binaryPathInArchive: (platform, _arch, _version) => exe(platform, "texlab")
22671
+ };
22672
+ var GITHUB_LSP_TABLE = [
22673
+ CLANGD,
22674
+ LUA_LS,
22675
+ ZLS,
22676
+ TINYMIST,
22677
+ TEXLAB
22678
+ ];
22679
+ function detectHostPlatform() {
22680
+ const platform = process.platform;
22681
+ if (platform !== "darwin" && platform !== "linux" && platform !== "win32")
22682
+ return null;
22683
+ const arch = process.arch;
22684
+ if (arch === "x64")
22685
+ return { platform, arch: "x64" };
22686
+ if (arch === "arm64")
22687
+ return { platform, arch: "arm64" };
22688
+ return null;
22689
+ }
22690
+
22691
+ // src/lsp-github-install.ts
22692
+ function ghCacheRoot() {
22693
+ return join6(aftCacheBase(), "lsp-binaries");
22694
+ }
22695
+ function ghPackageDir(spec) {
22696
+ return join6(ghCacheRoot(), spec.id);
22697
+ }
22698
+ function ghBinDir(spec) {
22699
+ return join6(ghPackageDir(spec), "bin");
22700
+ }
22701
+ function ghExtractDir(spec) {
22702
+ return join6(ghPackageDir(spec), "extracted");
22703
+ }
22704
+ function ghBinaryPath(spec, platform) {
22705
+ const ext = platform === "win32" ? ".exe" : "";
22706
+ return join6(ghBinDir(spec), `${spec.binary}${ext}`);
22707
+ }
22708
+ function isGithubInstalled(spec, platform) {
22709
+ for (const candidate of ghBinaryCandidates(spec, platform)) {
22710
+ try {
22711
+ if (statSync3(join6(ghBinDir(spec), candidate)).isFile())
22712
+ return true;
22713
+ } catch {}
22714
+ }
22715
+ return false;
22716
+ }
22717
+ function ghBinaryCandidates(spec, platform) {
22718
+ if (platform !== "win32")
22719
+ return [spec.binary];
22720
+ return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
22721
+ }
22722
+ var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
22723
+ var MAX_EXTRACT_BYTES = 1024 * 1024 * 1024;
22724
+ function sha256OfFile(path2) {
22725
+ return new Promise((resolve2, reject) => {
22726
+ const hash2 = createHash2("sha256");
22727
+ const stream = createReadStream2(path2);
22728
+ stream.on("error", reject);
22729
+ stream.on("data", (chunk) => hash2.update(chunk));
22730
+ stream.on("end", () => resolve2(hash2.digest("hex")));
22731
+ });
22732
+ }
22733
+ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
22734
+ const candidates = [];
22735
+ candidates.push(tag);
22736
+ if (!tag.startsWith("v")) {
22737
+ candidates.push(`v${tag}`);
22738
+ } else {
22739
+ candidates.push(tag.slice(1));
22740
+ }
22741
+ const headers = {
22742
+ accept: "application/vnd.github+json",
22743
+ "user-agent": "aft-opencode",
22744
+ "x-github-api-version": "2022-11-28"
22745
+ };
22746
+ if (process.env.GITHUB_TOKEN) {
22747
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
22748
+ }
22749
+ for (const candidate of candidates) {
22750
+ const url2 = `https://api.github.com/repos/${githubRepo}/releases/tags/${encodeURIComponent(candidate)}`;
22751
+ const timeout = controlledTimeoutSignal(15000, signal);
22752
+ try {
22753
+ const res = await fetchImpl(url2, {
22754
+ headers,
22755
+ redirect: "follow",
22756
+ signal: timeout.signal
22757
+ });
22758
+ if (res.status === 404)
22759
+ continue;
22760
+ if (!res.ok) {
22761
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: HTTP ${res.status}`);
22762
+ return null;
22763
+ }
22764
+ const json2 = await res.json();
22765
+ if (!json2.tag_name || !Array.isArray(json2.assets)) {
22766
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: malformed response`);
22767
+ return null;
22768
+ }
22769
+ const assets = json2.assets.filter((a) => typeof a.name === "string" && typeof a.browser_download_url === "string").map((a) => ({
22770
+ name: a.name,
22771
+ url: a.browser_download_url,
22772
+ size: typeof a.size === "number" ? a.size : undefined
22773
+ }));
22774
+ return { tag: json2.tag_name, assets };
22775
+ } catch (err) {
22776
+ if (signal?.aborted) {
22777
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: aborted`);
22778
+ return null;
22779
+ }
22780
+ warn(`[lsp] github release-by-tag ${githubRepo}@${candidate}: ${err}`);
22781
+ } finally {
22782
+ timeout.cleanup();
22783
+ }
22784
+ }
22785
+ return null;
22786
+ }
22787
+ async function resolveTargetTag(spec, config2, fetchImpl, signal) {
22788
+ const pinned = config2.versions[spec.githubRepo];
22789
+ if (pinned) {
22790
+ try {
22791
+ assertSafeVersion(pinned);
22792
+ } catch (err) {
22793
+ return {
22794
+ tag: null,
22795
+ assets: [],
22796
+ blockedByGrace: false,
22797
+ reason: `invalid pinned version ${JSON.stringify(pinned)}: ${err instanceof Error ? err.message : String(err)}`
22798
+ };
22799
+ }
22800
+ const release = await fetchReleaseByTag(spec.githubRepo, pinned, fetchImpl, signal);
22801
+ if (release) {
22802
+ return {
22803
+ tag: release.tag,
22804
+ assets: release.assets,
22805
+ blockedByGrace: false
22806
+ };
22807
+ }
22808
+ return {
22809
+ tag: null,
22810
+ assets: [],
22811
+ blockedByGrace: false,
22812
+ reason: `pinned tag ${pinned} not found on GitHub`
22813
+ };
22814
+ }
22815
+ const cached2 = readVersionCheck(spec.githubRepo);
22816
+ const weeklyMs = config2.graceDays * 24 * 60 * 60 * 1000;
22817
+ if (!shouldRecheckVersion(cached2, weeklyMs) && cached2?.latest_eligible) {
22818
+ const release = await fetchReleaseByTag(spec.githubRepo, cached2.latest_eligible, fetchImpl);
22819
+ if (release) {
22820
+ return {
22821
+ tag: release.tag,
22822
+ assets: release.assets,
22823
+ blockedByGrace: false
22824
+ };
22825
+ }
22826
+ }
22827
+ const probe = await probeGithubReleases(spec.githubRepo, config2.graceDays, fetchImpl);
22828
+ if (!probe) {
22829
+ return {
22830
+ tag: null,
22831
+ assets: [],
22832
+ blockedByGrace: false,
22833
+ reason: "github releases probe failed"
22834
+ };
22835
+ }
22836
+ writeVersionCheck(spec.githubRepo, probe.tag);
22837
+ return { tag: probe.tag, assets: probe.assets, blockedByGrace: probe.blockedByGrace };
22838
+ }
22839
+ function controlledTimeoutSignal(timeoutMs, parent) {
22840
+ const controller = new AbortController;
22841
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
22842
+ timeout.unref?.();
22843
+ const abort = () => controller.abort();
22844
+ parent?.addEventListener("abort", abort, { once: true });
22845
+ if (parent?.aborted)
22846
+ abort();
22847
+ return {
22848
+ signal: controller.signal,
22849
+ cleanup: () => {
22850
+ clearTimeout(timeout);
22851
+ parent?.removeEventListener("abort", abort);
22852
+ }
22853
+ };
22854
+ }
22855
+ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
22856
+ if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES) {
22857
+ throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES} (set lsp.versions to pin a smaller release if this is wrong)`);
22858
+ }
22859
+ const timeout = controlledTimeoutSignal(120000, signal);
22860
+ try {
22861
+ const res = await fetchImpl(url2, {
22862
+ headers: { accept: "application/octet-stream" },
22863
+ redirect: "follow",
22864
+ signal: timeout.signal
22865
+ });
22866
+ if (!res.ok || !res.body) {
22867
+ throw new Error(`download failed (${res.status})`);
22868
+ }
22869
+ const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
22870
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
22871
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
22872
+ }
22873
+ mkdirSync3(dirname(destPath), { recursive: true });
22874
+ let bytesWritten = 0;
22875
+ const guard = new TransformStream({
22876
+ transform(chunk, controller) {
22877
+ bytesWritten += chunk.byteLength;
22878
+ if (bytesWritten > MAX_DOWNLOAD_BYTES) {
22879
+ controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
22880
+ return;
22881
+ }
22882
+ controller.enqueue(chunk);
22883
+ }
22884
+ });
22885
+ const guarded = res.body.pipeThrough(guard);
22886
+ const nodeStream = Readable.fromWeb(guarded);
22887
+ await pipeline(nodeStream, createWriteStream(destPath), { signal: timeout.signal });
22888
+ } catch (err) {
22889
+ try {
22890
+ unlinkSync3(destPath);
22891
+ } catch {}
22892
+ throw err;
22893
+ } finally {
22894
+ timeout.cleanup();
22895
+ }
22896
+ }
22897
+ function validateExtraction(stagingRoot) {
22898
+ const realStagingRoot = realpathSync(stagingRoot);
22899
+ let totalBytes = 0;
22900
+ const walk = (dir) => {
22901
+ let entries;
22902
+ try {
22903
+ entries = readdirSync2(dir);
22904
+ } catch (err) {
22905
+ throw new Error(`failed to read staging dir ${dir}: ${err}`);
22906
+ }
22907
+ for (const entry of entries) {
22908
+ const full = join6(dir, entry);
22909
+ let lst;
22910
+ try {
22911
+ lst = lstatSync(full);
22912
+ } catch (err) {
22913
+ throw new Error(`failed to lstat ${full}: ${err}`);
22914
+ }
22915
+ if (lst.isSymbolicLink()) {
22916
+ let target = "<unreadable>";
22917
+ try {
22918
+ target = readlinkSync(full);
22919
+ } catch {}
22920
+ throw new Error(`archive contains symlink ${relative(realStagingRoot, full)} \u2192 ${target}; rejecting (zip-slip defense)`);
22921
+ }
22922
+ let realFull;
22923
+ try {
22924
+ realFull = realpathSync(full);
22925
+ } catch (err) {
22926
+ throw new Error(`failed to realpath ${full}: ${err}`);
22927
+ }
22928
+ const rel = relative(realStagingRoot, realFull);
22929
+ if (rel.startsWith("..") || resolve(realStagingRoot, rel) !== realFull) {
22930
+ throw new Error(`archive entry escapes staging root: ${full} \u2192 ${realFull} (zip-slip defense)`);
22931
+ }
22932
+ if (lst.isDirectory()) {
22933
+ walk(full);
22934
+ } else if (lst.isFile()) {
22935
+ totalBytes += lst.size;
22936
+ if (totalBytes > MAX_EXTRACT_BYTES) {
22937
+ throw new Error(`extracted archive exceeds ${MAX_EXTRACT_BYTES} bytes (decompression bomb defense): saw ${totalBytes} bytes before hitting the cap`);
22938
+ }
22939
+ } else {
22940
+ throw new Error(`archive contains non-file/non-dir entry: ${full}`);
22941
+ }
22942
+ }
22943
+ };
22944
+ walk(realStagingRoot);
22945
+ }
22946
+ function extractArchiveSafely(archivePath, destDir, archiveType) {
22947
+ const suffix = randomBytes(8).toString("hex");
22948
+ const stagingDir = `${destDir}.staging-${suffix}`;
22949
+ try {
22950
+ rmSync(stagingDir, { recursive: true, force: true });
22951
+ } catch {}
22952
+ mkdirSync3(stagingDir, { recursive: true });
22953
+ try {
22954
+ runPlatformExtractor(archivePath, stagingDir, archiveType);
22955
+ validateExtraction(stagingDir);
22956
+ try {
22957
+ rmSync(destDir, { recursive: true, force: true });
22958
+ } catch {}
22959
+ renameSync(stagingDir, destDir);
22960
+ } catch (err) {
22961
+ try {
22962
+ rmSync(stagingDir, { recursive: true, force: true });
22963
+ } catch {}
22964
+ throw err;
22965
+ }
22966
+ }
22967
+ function runPlatformExtractor(archivePath, destDir, archiveType) {
22968
+ if (archiveType === "zip") {
22969
+ if (process.platform === "win32") {
22970
+ execFileSync("tar.exe", ["-xf", archivePath, "-C", destDir], {
22971
+ stdio: "pipe",
22972
+ timeout: 180000
22973
+ });
22974
+ return;
22975
+ }
22976
+ execFileSync("unzip", ["-q", "-o", archivePath, "-d", destDir], {
22977
+ stdio: "pipe",
22978
+ timeout: 180000
22979
+ });
22980
+ return;
22981
+ }
22982
+ if (archiveType === "tar.gz") {
22983
+ execFileSync("tar", ["-xzf", archivePath, "-C", destDir], {
22984
+ stdio: "pipe",
22985
+ timeout: 180000
22986
+ });
22987
+ return;
22988
+ }
22989
+ if (archiveType === "tar.xz") {
22990
+ execFileSync("tar", ["-xf", archivePath, "-C", destDir], {
22991
+ stdio: "pipe",
22992
+ timeout: 180000
22993
+ });
22994
+ return;
22995
+ }
22996
+ throw new Error(`unsupported archive type: ${archiveType}`);
22997
+ }
22998
+ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal) {
22999
+ const version2 = stripTagV(tag);
23000
+ const expected = spec.resolveAsset(platform, arch, version2);
23001
+ if (!expected) {
23002
+ warn(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform}/${arch}`);
23003
+ return null;
23004
+ }
23005
+ const matchingAsset = assets.find((a) => a.name === expected.name);
23006
+ if (!matchingAsset) {
23007
+ warn(`[lsp] ${spec.id}: asset ${expected.name} not found in release ${tag} (${assets.length} assets available)`);
23008
+ return null;
23009
+ }
23010
+ const pkgDir = ghPackageDir(spec);
23011
+ const extractDir = ghExtractDir(spec);
23012
+ const archivePath = join6(pkgDir, expected.name);
23013
+ log(`[lsp] downloading ${spec.id} ${tag} \u2192 ${matchingAsset.url}`);
23014
+ try {
23015
+ await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
23016
+ } catch (err) {
23017
+ error48(`[lsp] download ${spec.id} failed: ${err}`);
23018
+ return null;
23019
+ }
23020
+ let archiveSha256;
23021
+ try {
23022
+ archiveSha256 = await sha256OfFile(archivePath);
23023
+ } catch (err) {
23024
+ error48(`[lsp] hash ${spec.id} failed: ${err}`);
23025
+ try {
23026
+ unlinkSync3(archivePath);
23027
+ } catch {}
23028
+ return null;
23029
+ }
23030
+ log(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
23031
+ const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
23032
+ if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
23033
+ if (previousMeta.sha256 !== archiveSha256) {
23034
+ error48(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
23035
+ try {
23036
+ unlinkSync3(archivePath);
23037
+ } catch {}
23038
+ return null;
23039
+ }
23040
+ }
23041
+ try {
23042
+ extractArchiveSafely(archivePath, extractDir, expected.archive);
23043
+ } catch (err) {
23044
+ error48(`[lsp] extract ${spec.id} failed: ${err}`);
23045
+ return null;
23046
+ } finally {
23047
+ try {
23048
+ unlinkSync3(archivePath);
23049
+ } catch {}
23050
+ }
23051
+ const innerBinaryPath = join6(extractDir, spec.binaryPathInArchive(platform, arch, version2));
23052
+ if (!existsSync5(innerBinaryPath)) {
23053
+ error48(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
23054
+ return null;
23055
+ }
23056
+ const targetBinary = ghBinaryPath(spec, platform);
23057
+ mkdirSync3(dirname(targetBinary), { recursive: true });
23058
+ try {
23059
+ copyFileSync(innerBinaryPath, targetBinary);
23060
+ if (platform !== "win32") {
23061
+ const { chmodSync: chmodSync2 } = await import("fs");
23062
+ chmodSync2(targetBinary, 493);
23063
+ }
23064
+ } catch (err) {
23065
+ error48(`[lsp] ${spec.id}: failed to place binary at ${targetBinary}: ${err}`);
23066
+ return null;
23067
+ }
23068
+ log(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
23069
+ return archiveSha256;
23070
+ }
23071
+ async function ensureGithubInstalled(spec, config2, fetchImpl, platform, arch, signal) {
23072
+ const outcome = await withInstallLock(spec.githubRepo, async () => {
23073
+ const { tag, assets, blockedByGrace, reason } = await resolveTargetTag(spec, config2, fetchImpl, signal);
23074
+ if (!tag) {
23075
+ const installed = isGithubInstalled(spec, platform);
23076
+ if (installed) {
23077
+ warn(`[lsp] no eligible release of ${spec.githubRepo} (grace=${config2.graceDays}d); keeping existing install`);
23078
+ return { started: false, reason: "kept existing install" };
23079
+ }
23080
+ const fallbackReason = reason ?? (blockedByGrace ? `all releases are within ${config2.graceDays}-day grace window` : "github releases probe failed");
23081
+ warn(`[lsp] skipping ${spec.id}: ${fallbackReason}`);
23082
+ return { started: false, reason: fallbackReason };
23083
+ }
23084
+ if (isGithubInstalled(spec, platform)) {
23085
+ const installedMeta = readInstalledMetaIn(ghPackageDir(spec));
23086
+ if (installedMeta && installedMeta.version === tag) {
23087
+ return { started: false, reason: "already installed" };
23088
+ }
23089
+ if (installedMeta) {
23090
+ log(`[lsp] reinstalling ${spec.id}: cached ${installedMeta.version} \u2260 target ${tag}`);
23091
+ } else {
23092
+ log(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
23093
+ }
23094
+ }
23095
+ const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal).catch((err) => {
23096
+ error48(`[lsp] github install ${spec.id} crashed: ${err}`);
23097
+ return null;
23098
+ });
23099
+ if (!archiveSha256) {
23100
+ return { started: true, reason: "install failed (see plugin log)" };
23101
+ }
23102
+ writeInstalledMetaIn(ghPackageDir(spec), tag, archiveSha256);
23103
+ return { started: true };
23104
+ });
23105
+ if (outcome === null) {
23106
+ return { started: false, reason: "another install in progress" };
23107
+ }
23108
+ return outcome;
23109
+ }
23110
+ var inFlightGithubInstalls = new Set;
23111
+ function trackInFlightGithubInstall(controller, promise2) {
23112
+ const entry = { controller, promise: promise2 };
23113
+ inFlightGithubInstalls.add(entry);
23114
+ promise2.then(() => inFlightGithubInstalls.delete(entry), () => inFlightGithubInstalls.delete(entry));
23115
+ return promise2;
23116
+ }
23117
+ async function abortInFlightGithubInstalls() {
23118
+ const installs = Array.from(inFlightGithubInstalls);
23119
+ for (const install of installs) {
23120
+ install.controller.abort();
23121
+ }
23122
+ await Promise.allSettled(installs.map((install) => install.promise));
23123
+ }
23124
+ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
23125
+ const cachedBinDirs = [];
23126
+ const skipped = [];
23127
+ const installPromises = [];
23128
+ let installsStarted = 0;
23129
+ const host = detectHostPlatform();
23130
+ if (!host) {
23131
+ for (const spec of GITHUB_LSP_TABLE) {
23132
+ try {
23133
+ if (existsSync5(ghBinDir(spec))) {
23134
+ cachedBinDirs.push(ghBinDir(spec));
23135
+ }
23136
+ } catch {}
23137
+ }
23138
+ return {
23139
+ cachedBinDirs,
23140
+ installsStarted: 0,
23141
+ skipped,
23142
+ installsComplete: Promise.resolve()
23143
+ };
23144
+ }
23145
+ for (const spec of GITHUB_LSP_TABLE) {
23146
+ if (isGithubInstalled(spec, host.platform)) {
23147
+ cachedBinDirs.push(ghBinDir(spec));
23148
+ }
23149
+ if (config2.disabled.has(spec.id)) {
23150
+ skipped.push({ id: spec.id, reason: "disabled by config" });
23151
+ continue;
23152
+ }
23153
+ if (!config2.autoInstall) {
23154
+ skipped.push({ id: spec.id, reason: "auto_install: false" });
23155
+ continue;
23156
+ }
23157
+ if (!relevantServers.has(spec.id)) {
23158
+ skipped.push({ id: spec.id, reason: "not relevant to project" });
23159
+ continue;
23160
+ }
23161
+ installsStarted += 1;
23162
+ const controller = new AbortController;
23163
+ const promise2 = ensureGithubInstalled(spec, config2, fetchImpl, host.platform, host.arch, controller.signal).then((outcome) => {
23164
+ if (!outcome.started)
23165
+ installsStarted -= 1;
23166
+ if (outcome.reason && outcome.reason !== "already installed") {
23167
+ skipped.push({ id: spec.id, reason: outcome.reason });
23168
+ }
23169
+ }, (err) => {
23170
+ installsStarted -= 1;
23171
+ const reason = err instanceof Error ? err.message : String(err);
23172
+ skipped.push({ id: spec.id, reason: `install error: ${reason}` });
23173
+ error48(`[lsp] github install ${spec.id} promise rejected: ${reason}`);
23174
+ });
23175
+ installPromises.push(trackInFlightGithubInstall(controller, promise2));
23176
+ }
23177
+ return {
23178
+ cachedBinDirs,
23179
+ get installsStarted() {
23180
+ return installsStarted;
23181
+ },
23182
+ skipped,
23183
+ installsComplete: Promise.all(installPromises).then(() => {})
23184
+ };
23185
+ }
23186
+ function discoverRelevantGithubServers(projectRoot) {
23187
+ const extToServerIds = {
23188
+ c: ["clangd"],
23189
+ "c++": ["clangd"],
23190
+ cc: ["clangd"],
23191
+ cpp: ["clangd"],
23192
+ cxx: ["clangd"],
23193
+ h: ["clangd"],
23194
+ "h++": ["clangd"],
23195
+ hpp: ["clangd"],
23196
+ hh: ["clangd"],
23197
+ hxx: ["clangd"],
23198
+ lua: ["lua-ls"],
23199
+ zig: ["zls"],
23200
+ zon: ["zls"],
23201
+ typ: ["tinymist"],
23202
+ typc: ["tinymist"],
23203
+ tex: ["texlab"],
23204
+ bib: ["texlab"]
23205
+ };
23206
+ const rootMarkers = {
23207
+ clangd: ["compile_commands.json", "compile_flags.txt", ".clangd"],
23208
+ "lua-ls": [".luarc.json", ".luarc.jsonc", ".stylua.toml", "stylua.toml"],
23209
+ zls: ["build.zig"],
23210
+ tinymist: ["typst.toml"],
23211
+ texlab: [".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]
23212
+ };
23213
+ const relevant = new Set;
23214
+ for (const spec of GITHUB_LSP_TABLE) {
23215
+ if (hasRootMarker(projectRoot, rootMarkers[spec.id]))
23216
+ relevant.add(spec.id);
23217
+ }
23218
+ const extensions = relevantExtensionsInProject(projectRoot, extToServerIds);
23219
+ for (const ext of extensions) {
23220
+ for (const id of extToServerIds[ext] ?? []) {
23221
+ relevant.add(id);
23222
+ }
23223
+ }
23224
+ return relevant;
23225
+ }
23226
+
21808
23227
  // src/metadata-store.ts
21809
23228
  var pendingStore = new Map;
21810
23229
  var STALE_TIMEOUT_MS = 15 * 60 * 1000;
@@ -21871,9 +23290,9 @@ function normalizeToolMap(tools) {
21871
23290
  }
21872
23291
 
21873
23292
  // src/notifications.ts
21874
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
21875
- import { homedir as homedir3, platform } from "os";
21876
- import { join as join4 } from "path";
23293
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
23294
+ import { homedir as homedir4, platform } from "os";
23295
+ import { join as join7 } from "path";
21877
23296
  function isTuiMode() {
21878
23297
  return process.env.OPENCODE_CLIENT === "cli";
21879
23298
  }
@@ -21895,27 +23314,27 @@ var STATUS_MARKER = `${AFT_MARKER} \u2705`;
21895
23314
  var WARNED_TOOLS_FILE = "warned_tools.json";
21896
23315
  function getDesktopStatePath() {
21897
23316
  const os2 = platform();
21898
- const home = homedir3();
23317
+ const home = homedir4();
21899
23318
  if (os2 === "darwin") {
21900
- return join4(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
23319
+ return join7(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
21901
23320
  }
21902
23321
  if (os2 === "linux") {
21903
- const xdgConfig = process.env.XDG_CONFIG_HOME || join4(home, ".config");
21904
- return join4(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
23322
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join7(home, ".config");
23323
+ return join7(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
21905
23324
  }
21906
23325
  if (os2 === "win32") {
21907
- const appData = process.env.APPDATA || join4(home, "AppData", "Roaming");
21908
- return join4(appData, "ai.opencode.desktop", "opencode.global.dat");
23326
+ const appData = process.env.APPDATA || join7(home, "AppData", "Roaming");
23327
+ return join7(appData, "ai.opencode.desktop", "opencode.global.dat");
21909
23328
  }
21910
23329
  return null;
21911
23330
  }
21912
23331
  function readDesktopState(directory) {
21913
23332
  const statePath = getDesktopStatePath();
21914
- if (!statePath || !existsSync3(statePath)) {
23333
+ if (!statePath || !existsSync6(statePath)) {
21915
23334
  return { sessionId: null, serverUrl: null };
21916
23335
  }
21917
23336
  try {
21918
- const raw = readFileSync2(statePath, "utf-8");
23337
+ const raw = readFileSync3(statePath, "utf-8");
21919
23338
  const state = JSON.parse(raw);
21920
23339
  let serverUrl = null;
21921
23340
  const serverStr = state.server;
@@ -22009,10 +23428,10 @@ async function sendWarning(opts, message) {
22009
23428
  }
22010
23429
  async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
22011
23430
  if (storageDir) {
22012
- const versionFile = join4(storageDir, "last_announced_version");
23431
+ const versionFile = join7(storageDir, "last_announced_version");
22013
23432
  try {
22014
- if (existsSync3(versionFile)) {
22015
- const lastVersion = readFileSync2(versionFile, "utf-8").trim();
23433
+ if (existsSync6(versionFile)) {
23434
+ const lastVersion = readFileSync3(versionFile, "utf-8").trim();
22016
23435
  if (lastVersion === version2)
22017
23436
  return;
22018
23437
  }
@@ -22032,17 +23451,17 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
22032
23451
  }
22033
23452
  if (storageDir) {
22034
23453
  try {
22035
- mkdirSync2(storageDir, { recursive: true });
22036
- writeFileSync(join4(storageDir, "last_announced_version"), version2);
23454
+ mkdirSync4(storageDir, { recursive: true });
23455
+ writeFileSync2(join7(storageDir, "last_announced_version"), version2);
22037
23456
  } catch {}
22038
23457
  }
22039
23458
  }
22040
23459
  function readWarnedTools(storageDir) {
22041
23460
  try {
22042
- const warnedToolsPath = join4(storageDir, WARNED_TOOLS_FILE);
22043
- if (!existsSync3(warnedToolsPath))
23461
+ const warnedToolsPath = join7(storageDir, WARNED_TOOLS_FILE);
23462
+ if (!existsSync6(warnedToolsPath))
22044
23463
  return {};
22045
- const parsed = JSON.parse(readFileSync2(warnedToolsPath, "utf-8"));
23464
+ const parsed = JSON.parse(readFileSync3(warnedToolsPath, "utf-8"));
22046
23465
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
22047
23466
  return {};
22048
23467
  const warned = {};
@@ -22058,9 +23477,9 @@ function readWarnedTools(storageDir) {
22058
23477
  }
22059
23478
  function writeWarnedTools(storageDir, warned) {
22060
23479
  try {
22061
- mkdirSync2(storageDir, { recursive: true });
22062
- const warnedToolsPath = join4(storageDir, WARNED_TOOLS_FILE);
22063
- writeFileSync(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
23480
+ mkdirSync4(storageDir, { recursive: true });
23481
+ const warnedToolsPath = join7(storageDir, WARNED_TOOLS_FILE);
23482
+ writeFileSync2(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
22064
23483
  `);
22065
23484
  } catch {}
22066
23485
  }
@@ -22151,8 +23570,8 @@ async function cleanupWarnings(opts) {
22151
23570
  }
22152
23571
 
22153
23572
  // src/onnx-runtime.ts
22154
- import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2 } from "fs";
22155
- import { join as join5 } from "path";
23573
+ import { chmodSync as chmodSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
23574
+ import { join as join8 } from "path";
22156
23575
  var ORT_VERSION = "1.24.4";
22157
23576
  var ORT_REPO = "microsoft/onnxruntime";
22158
23577
  var ORT_PLATFORM_MAP = {
@@ -22208,9 +23627,9 @@ function getManualInstallHint() {
22208
23627
  }
22209
23628
  async function ensureOnnxRuntime(storageDir) {
22210
23629
  const info = getPlatformInfo();
22211
- const ortDir = join5(storageDir, "onnxruntime", ORT_VERSION);
22212
- const libPath = join5(ortDir, info?.libName ?? "libonnxruntime.dylib");
22213
- if (existsSync4(libPath)) {
23630
+ const ortDir = join8(storageDir, "onnxruntime", ORT_VERSION);
23631
+ const libPath = join8(ortDir, info?.libName ?? "libonnxruntime.dylib");
23632
+ if (existsSync7(libPath)) {
22214
23633
  log(`ONNX Runtime found at ${ortDir}`);
22215
23634
  return ortDir;
22216
23635
  }
@@ -22235,7 +23654,7 @@ function findSystemOnnxRuntime(libName) {
22235
23654
  searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
22236
23655
  }
22237
23656
  for (const dir of searchPaths) {
22238
- if (existsSync4(join5(dir, libName))) {
23657
+ if (existsSync7(join8(dir, libName))) {
22239
23658
  return dir;
22240
23659
  }
22241
23660
  }
@@ -22246,34 +23665,34 @@ async function downloadOnnxRuntime(info, targetDir) {
22246
23665
  log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
22247
23666
  try {
22248
23667
  const tmpDir = `${targetDir}.tmp.${process.pid}`;
22249
- mkdirSync3(tmpDir, { recursive: true });
22250
- const archivePath = join5(tmpDir, `onnxruntime.${info.archiveType}`);
22251
- const { execFileSync } = await import("child_process");
22252
- execFileSync("curl", ["-fsSL", url2, "-o", archivePath], {
23668
+ mkdirSync5(tmpDir, { recursive: true });
23669
+ const archivePath = join8(tmpDir, `onnxruntime.${info.archiveType}`);
23670
+ const { execFileSync: execFileSync2 } = await import("child_process");
23671
+ execFileSync2("curl", ["-fsSL", url2, "-o", archivePath], {
22253
23672
  stdio: "pipe",
22254
23673
  timeout: 120000
22255
23674
  });
22256
23675
  if (info.archiveType === "tgz") {
22257
- execFileSync("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
23676
+ execFileSync2("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
22258
23677
  } else {
22259
23678
  await extractZipArchive(archivePath, tmpDir);
22260
23679
  }
22261
- const extractedDir = join5(tmpDir, info.assetName, "lib");
22262
- if (!existsSync4(extractedDir)) {
23680
+ const extractedDir = join8(tmpDir, info.assetName, "lib");
23681
+ if (!existsSync7(extractedDir)) {
22263
23682
  throw new Error(`Expected directory not found: ${extractedDir}`);
22264
23683
  }
22265
- mkdirSync3(targetDir, { recursive: true });
22266
- const libFiles = readdirSync(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
22267
- const { lstatSync, symlinkSync, readlinkSync, copyFileSync: cpFile } = await import("fs");
23684
+ mkdirSync5(targetDir, { recursive: true });
23685
+ const libFiles = readdirSync3(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
23686
+ const { lstatSync: lstatSync2, symlinkSync, readlinkSync: readlinkSync2, copyFileSync: cpFile } = await import("fs");
22268
23687
  const realFiles = [];
22269
23688
  const symlinks = [];
22270
23689
  for (const libFile of libFiles) {
22271
- const src = join5(extractedDir, libFile);
23690
+ const src = join8(extractedDir, libFile);
22272
23691
  try {
22273
- const stat = lstatSync(src);
23692
+ const stat = lstatSync2(src);
22274
23693
  log(`ORT extract: ${libFile} \u2014 isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
22275
23694
  if (stat.isSymbolicLink()) {
22276
- symlinks.push({ name: libFile, target: readlinkSync(src) });
23695
+ symlinks.push({ name: libFile, target: readlinkSync2(src) });
22277
23696
  } else {
22278
23697
  realFiles.push(libFile);
22279
23698
  }
@@ -22283,8 +23702,8 @@ async function downloadOnnxRuntime(info, targetDir) {
22283
23702
  }
22284
23703
  }
22285
23704
  for (const libFile of realFiles) {
22286
- const src = join5(extractedDir, libFile);
22287
- const dst = join5(targetDir, libFile);
23705
+ const src = join8(extractedDir, libFile);
23706
+ const dst = join8(targetDir, libFile);
22288
23707
  try {
22289
23708
  cpFile(src, dst);
22290
23709
  if (process.platform !== "win32") {
@@ -22295,62 +23714,44 @@ async function downloadOnnxRuntime(info, targetDir) {
22295
23714
  }
22296
23715
  }
22297
23716
  for (const link of symlinks) {
22298
- const dst = join5(targetDir, link.name);
23717
+ const dst = join8(targetDir, link.name);
22299
23718
  try {
22300
- unlinkSync2(dst);
23719
+ unlinkSync4(dst);
22301
23720
  } catch {}
22302
23721
  symlinkSync(link.target, dst);
22303
23722
  }
22304
- const { rmSync } = await import("fs");
22305
- rmSync(tmpDir, { recursive: true, force: true });
23723
+ const { rmSync: rmSync2 } = await import("fs");
23724
+ rmSync2(tmpDir, { recursive: true, force: true });
22306
23725
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
22307
23726
  return targetDir;
22308
23727
  } catch (err) {
22309
23728
  error48(`Failed to download ONNX Runtime: ${err}`);
22310
23729
  try {
22311
- const { rmSync } = await import("fs");
22312
- rmSync(`${targetDir}.tmp.${process.pid}`, { recursive: true, force: true });
23730
+ const { rmSync: rmSync2 } = await import("fs");
23731
+ rmSync2(`${targetDir}.tmp.${process.pid}`, { recursive: true, force: true });
22313
23732
  } catch {}
22314
23733
  return null;
22315
23734
  }
22316
23735
  }
22317
23736
  async function extractZipArchive(archivePath, destinationDir) {
22318
- const { execFileSync } = await import("child_process");
23737
+ const { execFileSync: execFileSync2 } = await import("child_process");
22319
23738
  if (process.platform === "win32") {
22320
- let powershellError;
22321
- try {
22322
- execFileSync("powershell.exe", [
22323
- "-NoProfile",
22324
- "-NonInteractive",
22325
- "-ExecutionPolicy",
22326
- "Bypass",
22327
- "-Command",
22328
- "& { Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force }",
22329
- archivePath,
22330
- destinationDir
22331
- ], { stdio: "pipe", timeout: 120000 });
22332
- return;
22333
- } catch (err) {
22334
- powershellError = err;
22335
- warn(`PowerShell Expand-Archive failed, falling back to cmd/tar: ${String(err)}`);
22336
- }
22337
- try {
22338
- execFileSync("cmd.exe", ["/d", "/s", "/c", `tar -xf "${archivePath}" -C "${destinationDir}"`], { stdio: "pipe", timeout: 120000 });
22339
- return;
22340
- } catch (cmdError) {
22341
- throw new Error(`ZIP extraction failed via PowerShell and cmd/tar. PowerShell: ${String(powershellError)} | cmd/tar: ${String(cmdError)}`);
22342
- }
23739
+ execFileSync2("tar.exe", ["-xf", archivePath, "-C", destinationDir], {
23740
+ stdio: "pipe",
23741
+ timeout: 120000
23742
+ });
23743
+ return;
22343
23744
  }
22344
- execFileSync("unzip", ["-q", archivePath, "-d", destinationDir], {
23745
+ execFileSync2("unzip", ["-q", archivePath, "-d", destinationDir], {
22345
23746
  stdio: "pipe",
22346
23747
  timeout: 120000
22347
23748
  });
22348
23749
  }
22349
23750
 
22350
23751
  // src/bridge.ts
22351
- import { spawn } from "child_process";
22352
- import { homedir as homedir4 } from "os";
22353
- import { join as join6 } from "path";
23752
+ import { spawn as spawn2 } from "child_process";
23753
+ import { homedir as homedir5 } from "os";
23754
+ import { join as join9 } from "path";
22354
23755
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
22355
23756
  var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
22356
23757
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
@@ -22492,14 +23893,14 @@ class BinaryBridge {
22492
23893
  const line = `${JSON.stringify(request)}
22493
23894
  `;
22494
23895
  const effectiveTimeoutMs = options?.timeoutMs ?? this.timeoutMs;
22495
- return new Promise((resolve, reject) => {
23896
+ return new Promise((resolve2, reject) => {
22496
23897
  const timer = setTimeout(() => {
22497
23898
  this.pending.delete(id);
22498
23899
  warn(`Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms \u2014 restarting bridge`);
22499
23900
  reject(new Error(`[aft-plugin] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
22500
23901
  this.handleTimeout();
22501
23902
  }, effectiveTimeoutMs);
22502
- this.pending.set(id, { resolve, reject, timer });
23903
+ this.pending.set(id, { resolve: resolve2, reject, timer });
22503
23904
  if (!this.process?.stdin?.writable) {
22504
23905
  this.pending.delete(id);
22505
23906
  clearTimeout(timer);
@@ -22542,15 +23943,15 @@ class BinaryBridge {
22542
23943
  if (this.process) {
22543
23944
  const proc = this.process;
22544
23945
  this.process = null;
22545
- return new Promise((resolve) => {
23946
+ return new Promise((resolve2) => {
22546
23947
  const forceKillTimer = setTimeout(() => {
22547
23948
  proc.kill("SIGKILL");
22548
- resolve();
23949
+ resolve2();
22549
23950
  }, 5000);
22550
23951
  proc.once("exit", () => {
22551
23952
  clearTimeout(forceKillTimer);
22552
23953
  log("Process exited during shutdown");
22553
- resolve();
23954
+ resolve2();
22554
23955
  });
22555
23956
  proc.kill("SIGTERM");
22556
23957
  });
@@ -22592,19 +23993,19 @@ class BinaryBridge {
22592
23993
  })();
22593
23994
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
22594
23995
  const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
22595
- const ortLibraryPath = ortDir == null ? null : join6(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
23996
+ const ortLibraryPath = ortDir == null ? null : join9(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
22596
23997
  const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
22597
23998
  const env = {
22598
23999
  ...process.env,
22599
24000
  ...envPath ? { PATH: envPath } : {}
22600
24001
  };
22601
24002
  if (useFastembedBackend) {
22602
- env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join6(this.configOverrides.storage_dir, "semantic", "models") : join6(homedir4() || "", ".cache", "fastembed"));
24003
+ env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join9(this.configOverrides.storage_dir, "semantic", "models") : join9(homedir5() || "", ".cache", "fastembed"));
22603
24004
  if (ortLibraryPath) {
22604
24005
  env.ORT_DYLIB_PATH = ortLibraryPath;
22605
24006
  }
22606
24007
  }
22607
- const child = spawn(this.binaryPath, [], {
24008
+ const child = spawn2(this.binaryPath, [], {
22608
24009
  cwd: this.cwd,
22609
24010
  stdio: ["pipe", "pipe", "pipe"],
22610
24011
  env
@@ -22865,10 +24266,10 @@ function normalizeKey(projectRoot) {
22865
24266
 
22866
24267
  // src/resolver.ts
22867
24268
  import { execSync, spawnSync } from "child_process";
22868
- import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4, renameSync } from "fs";
24269
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
22869
24270
  import { createRequire } from "module";
22870
- import { homedir as homedir5 } from "os";
22871
- import { join as join7 } from "path";
24271
+ import { homedir as homedir6 } from "os";
24272
+ import { join as join10 } from "path";
22872
24273
  function copyToVersionedCache(npmBinaryPath) {
22873
24274
  try {
22874
24275
  const result = spawnSync(npmBinaryPath, ["--version"], {
@@ -22882,18 +24283,18 @@ function copyToVersionedCache(npmBinaryPath) {
22882
24283
  const version2 = rawVersion.replace(/^aft\s+/, "");
22883
24284
  const tag = version2.startsWith("v") ? version2 : `v${version2}`;
22884
24285
  const cacheDir = getCacheDir();
22885
- const versionedDir = join7(cacheDir, tag);
24286
+ const versionedDir = join10(cacheDir, tag);
22886
24287
  const ext = process.platform === "win32" ? ".exe" : "";
22887
- const cachedPath = join7(versionedDir, `aft${ext}`);
22888
- if (existsSync5(cachedPath))
24288
+ const cachedPath = join10(versionedDir, `aft${ext}`);
24289
+ if (existsSync8(cachedPath))
22889
24290
  return cachedPath;
22890
- mkdirSync4(versionedDir, { recursive: true });
24291
+ mkdirSync6(versionedDir, { recursive: true });
22891
24292
  const tmpPath = `${cachedPath}.tmp`;
22892
- copyFileSync(npmBinaryPath, tmpPath);
24293
+ copyFileSync2(npmBinaryPath, tmpPath);
22893
24294
  if (process.platform !== "win32") {
22894
24295
  chmodSync3(tmpPath, 493);
22895
24296
  }
22896
- renameSync(tmpPath, cachedPath);
24297
+ renameSync2(tmpPath, cachedPath);
22897
24298
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
22898
24299
  return cachedPath;
22899
24300
  } catch (err) {
@@ -22932,7 +24333,7 @@ function findBinarySync() {
22932
24333
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
22933
24334
  const req = createRequire(import.meta.url);
22934
24335
  const resolved = req.resolve(packageBin);
22935
- if (existsSync5(resolved)) {
24336
+ if (existsSync8(resolved)) {
22936
24337
  const copied = copyToVersionedCache(resolved);
22937
24338
  return copied ?? resolved;
22938
24339
  }
@@ -22946,8 +24347,8 @@ function findBinarySync() {
22946
24347
  if (result)
22947
24348
  return result;
22948
24349
  } catch {}
22949
- const cargoPath = join7(homedir5(), ".cargo", "bin", `aft${ext}`);
22950
- if (existsSync5(cargoPath))
24350
+ const cargoPath = join10(homedir6(), ".cargo", "bin", `aft${ext}`);
24351
+ if (existsSync8(cargoPath))
22951
24352
  return cargoPath;
22952
24353
  return null;
22953
24354
  }
@@ -22982,21 +24383,21 @@ async function findBinary() {
22982
24383
  }
22983
24384
 
22984
24385
  // src/shared/rpc-server.ts
22985
- import { randomBytes } from "crypto";
22986
- import { mkdirSync as mkdirSync5, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
24386
+ import { randomBytes as randomBytes2 } from "crypto";
24387
+ import { mkdirSync as mkdirSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync3 } from "fs";
22987
24388
  import { createServer } from "http";
22988
- import { dirname } from "path";
24389
+ import { dirname as dirname2 } from "path";
22989
24390
 
22990
24391
  // src/shared/rpc-utils.ts
22991
- import { createHash } from "crypto";
22992
- import { join as join8 } from "path";
24392
+ import { createHash as createHash3 } from "crypto";
24393
+ import { join as join11 } from "path";
22993
24394
  function projectHash(directory) {
22994
24395
  const normalized = directory.replace(/\/+$/, "");
22995
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
24396
+ return createHash3("sha256").update(normalized).digest("hex").slice(0, 16);
22996
24397
  }
22997
24398
  function rpcPortFilePath(storageDir, directory) {
22998
24399
  const hash2 = projectHash(directory);
22999
- return join8(storageDir, "rpc", hash2, "port");
24400
+ return join11(storageDir, "rpc", hash2, "port");
23000
24401
  }
23001
24402
 
23002
24403
  // src/shared/rpc-server.ts
@@ -23013,7 +24414,7 @@ class AftRpcServer {
23013
24414
  this.handlers.set(method, handler);
23014
24415
  }
23015
24416
  async start() {
23016
- return new Promise((resolve, reject) => {
24417
+ return new Promise((resolve2, reject) => {
23017
24418
  const server = createServer((req, res) => this.dispatch(req, res));
23018
24419
  server.on("error", (err) => {
23019
24420
  warn(`RPC server error: ${err.message}`);
@@ -23026,19 +24427,19 @@ class AftRpcServer {
23026
24427
  return;
23027
24428
  }
23028
24429
  this.port = addr.port;
23029
- this.token = randomBytes(32).toString("hex");
24430
+ this.token = randomBytes2(32).toString("hex");
23030
24431
  this.server = server;
23031
24432
  try {
23032
- const dir = dirname(this.portFilePath);
23033
- mkdirSync5(dir, { recursive: true });
24433
+ const dir = dirname2(this.portFilePath);
24434
+ mkdirSync7(dir, { recursive: true });
23034
24435
  const tmpPath = `${this.portFilePath}.tmp`;
23035
- writeFileSync2(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
23036
- renameSync2(tmpPath, this.portFilePath);
24436
+ writeFileSync3(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
24437
+ renameSync3(tmpPath, this.portFilePath);
23037
24438
  log(`RPC server listening on 127.0.0.1:${this.port}`);
23038
24439
  } catch (err) {
23039
24440
  warn(`Failed to write RPC port file: ${err}`);
23040
24441
  }
23041
- resolve(this.port);
24442
+ resolve2(this.port);
23042
24443
  });
23043
24444
  server.unref();
23044
24445
  });
@@ -23050,7 +24451,7 @@ class AftRpcServer {
23050
24451
  }
23051
24452
  this.token = null;
23052
24453
  try {
23053
- unlinkSync3(this.portFilePath);
24454
+ unlinkSync5(this.portFilePath);
23054
24455
  } catch {}
23055
24456
  }
23056
24457
  dispatch(req, res) {
@@ -23272,21 +24673,21 @@ function formatStatusMarkdown(status) {
23272
24673
 
23273
24674
  // src/shared/tui-config.ts
23274
24675
  var import_comment_json2 = __toESM(require_src2(), 1);
23275
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
23276
- import { dirname as dirname2, join as join10 } from "path";
24676
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
24677
+ import { dirname as dirname3, join as join13 } from "path";
23277
24678
 
23278
24679
  // src/shared/opencode-config-dir.ts
23279
- import { homedir as homedir6 } from "os";
23280
- import { join as join9, resolve } from "path";
24680
+ import { homedir as homedir7 } from "os";
24681
+ import { join as join12, resolve as resolve2 } from "path";
23281
24682
  function getCliConfigDir() {
23282
24683
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
23283
24684
  if (envConfigDir) {
23284
- return resolve(envConfigDir);
24685
+ return resolve2(envConfigDir);
23285
24686
  }
23286
24687
  if (process.platform === "win32") {
23287
- return join9(homedir6(), ".config", "opencode");
24688
+ return join12(homedir7(), ".config", "opencode");
23288
24689
  }
23289
- return join9(process.env.XDG_CONFIG_HOME || join9(homedir6(), ".config"), "opencode");
24690
+ return join12(process.env.XDG_CONFIG_HOME || join12(homedir7(), ".config"), "opencode");
23290
24691
  }
23291
24692
  function getOpenCodeConfigDir2(_options) {
23292
24693
  return getCliConfigDir();
@@ -23295,10 +24696,10 @@ function getOpenCodeConfigPaths(options) {
23295
24696
  const configDir = getOpenCodeConfigDir2(options);
23296
24697
  return {
23297
24698
  configDir,
23298
- configJson: join9(configDir, "opencode.json"),
23299
- configJsonc: join9(configDir, "opencode.jsonc"),
23300
- packageJson: join9(configDir, "package.json"),
23301
- omoConfig: join9(configDir, "magic-context.jsonc")
24699
+ configJson: join12(configDir, "opencode.json"),
24700
+ configJsonc: join12(configDir, "opencode.jsonc"),
24701
+ packageJson: join12(configDir, "package.json"),
24702
+ omoConfig: join12(configDir, "magic-context.jsonc")
23302
24703
  };
23303
24704
  }
23304
24705
 
@@ -23307,11 +24708,11 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
23307
24708
  var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
23308
24709
  function resolveTuiConfigPath() {
23309
24710
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
23310
- const jsoncPath = join10(configDir, "tui.jsonc");
23311
- const jsonPath = join10(configDir, "tui.json");
23312
- if (existsSync6(jsoncPath))
24711
+ const jsoncPath = join13(configDir, "tui.jsonc");
24712
+ const jsonPath = join13(configDir, "tui.json");
24713
+ if (existsSync9(jsoncPath))
23313
24714
  return jsoncPath;
23314
- if (existsSync6(jsonPath))
24715
+ if (existsSync9(jsonPath))
23315
24716
  return jsonPath;
23316
24717
  return jsonPath;
23317
24718
  }
@@ -23319,8 +24720,8 @@ function ensureTuiPluginEntry() {
23319
24720
  try {
23320
24721
  const configPath = resolveTuiConfigPath();
23321
24722
  let config2 = {};
23322
- if (existsSync6(configPath)) {
23323
- config2 = import_comment_json2.parse(readFileSync3(configPath, "utf-8")) ?? {};
24723
+ if (existsSync9(configPath)) {
24724
+ config2 = import_comment_json2.parse(readFileSync4(configPath, "utf-8")) ?? {};
23324
24725
  }
23325
24726
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
23326
24727
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -23328,8 +24729,8 @@ function ensureTuiPluginEntry() {
23328
24729
  }
23329
24730
  plugins.push(PLUGIN_ENTRY);
23330
24731
  config2.plugin = plugins;
23331
- mkdirSync6(dirname2(configPath), { recursive: true });
23332
- writeFileSync3(configPath, `${import_comment_json2.stringify(config2, null, 2)}
24732
+ mkdirSync8(dirname3(configPath), { recursive: true });
24733
+ writeFileSync4(configPath, `${import_comment_json2.stringify(config2, null, 2)}
23333
24734
  `);
23334
24735
  log(`[aft-plugin] added TUI plugin entry to ${configPath}`);
23335
24736
  return true;
@@ -23340,33 +24741,33 @@ function ensureTuiPluginEntry() {
23340
24741
  }
23341
24742
 
23342
24743
  // src/shared/url-fetch.ts
23343
- import { createHash as createHash2 } from "crypto";
24744
+ import { createHash as createHash4 } from "crypto";
23344
24745
  import { lookup } from "dns/promises";
23345
24746
  import {
23346
- existsSync as existsSync7,
23347
- mkdirSync as mkdirSync7,
23348
- readdirSync as readdirSync2,
23349
- readFileSync as readFileSync4,
23350
- unlinkSync as unlinkSync4,
23351
- writeFileSync as writeFileSync4
24747
+ existsSync as existsSync10,
24748
+ mkdirSync as mkdirSync9,
24749
+ readdirSync as readdirSync4,
24750
+ readFileSync as readFileSync5,
24751
+ unlinkSync as unlinkSync6,
24752
+ writeFileSync as writeFileSync5
23352
24753
  } from "fs";
23353
24754
  import { isIP } from "net";
23354
- import { join as join11 } from "path";
24755
+ import { join as join14 } from "path";
23355
24756
  var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
23356
24757
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
23357
24758
  var FETCH_TIMEOUT_MS = 30000;
23358
24759
  var MAX_REDIRECTS = 5;
23359
24760
  function cacheDir(storageDir) {
23360
- return join11(storageDir, "url_cache");
24761
+ return join14(storageDir, "url_cache");
23361
24762
  }
23362
24763
  function hashUrl(url2) {
23363
- return createHash2("sha256").update(url2).digest("hex").slice(0, 16);
24764
+ return createHash4("sha256").update(url2).digest("hex").slice(0, 16);
23364
24765
  }
23365
24766
  function metaPath(storageDir, hash2) {
23366
- return join11(cacheDir(storageDir), `${hash2}.meta.json`);
24767
+ return join14(cacheDir(storageDir), `${hash2}.meta.json`);
23367
24768
  }
23368
24769
  function contentPath(storageDir, hash2, extension) {
23369
- return join11(cacheDir(storageDir), `${hash2}${extension}`);
24770
+ return join14(cacheDir(storageDir), `${hash2}${extension}`);
23370
24771
  }
23371
24772
  function _isPrivateIpv4(address) {
23372
24773
  const parts = address.split(".").map((part) => Number.parseInt(part, 10));
@@ -23525,15 +24926,15 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
23525
24926
  }
23526
24927
  const allowPrivate = options.allowPrivate === true;
23527
24928
  const dir = cacheDir(storageDir);
23528
- mkdirSync7(dir, { recursive: true });
24929
+ mkdirSync9(dir, { recursive: true });
23529
24930
  const hash2 = hashUrl(url2);
23530
24931
  const metaFile = metaPath(storageDir, hash2);
23531
- if (existsSync7(metaFile)) {
24932
+ if (existsSync10(metaFile)) {
23532
24933
  try {
23533
- const meta4 = JSON.parse(readFileSync4(metaFile, "utf8"));
24934
+ const meta4 = JSON.parse(readFileSync5(metaFile, "utf8"));
23534
24935
  const age = Date.now() - meta4.fetchedAt;
23535
24936
  const cached2 = contentPath(storageDir, hash2, meta4.extension);
23536
- if (age < CACHE_TTL_MS && existsSync7(cached2)) {
24937
+ if (age < CACHE_TTL_MS && existsSync10(cached2)) {
23537
24938
  log(`URL cache hit: ${url2} (${Math.round(age / 1000)}s old)`);
23538
24939
  return cached2;
23539
24940
  }
@@ -23578,9 +24979,9 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
23578
24979
  const body = Buffer.concat(chunks);
23579
24980
  const contentFile = contentPath(storageDir, hash2, extension);
23580
24981
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
23581
- writeFileSync4(tmpContent, body);
23582
- const { renameSync: renameSync3 } = await import("fs");
23583
- renameSync3(tmpContent, contentFile);
24982
+ writeFileSync5(tmpContent, body);
24983
+ const { renameSync: renameSync4 } = await import("fs");
24984
+ renameSync4(tmpContent, contentFile);
23584
24985
  const meta3 = {
23585
24986
  url: url2,
23586
24987
  contentType,
@@ -23588,35 +24989,35 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
23588
24989
  fetchedAt: Date.now()
23589
24990
  };
23590
24991
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
23591
- writeFileSync4(tmpMeta, JSON.stringify(meta3));
23592
- renameSync3(tmpMeta, metaFile);
24992
+ writeFileSync5(tmpMeta, JSON.stringify(meta3));
24993
+ renameSync4(tmpMeta, metaFile);
23593
24994
  log(`URL cached (${total} bytes): ${url2}`);
23594
24995
  return contentFile;
23595
24996
  }
23596
24997
  function cleanupUrlCache(storageDir) {
23597
24998
  const dir = cacheDir(storageDir);
23598
- if (!existsSync7(dir))
24999
+ if (!existsSync10(dir))
23599
25000
  return;
23600
25001
  let removed = 0;
23601
25002
  try {
23602
- for (const entry of readdirSync2(dir)) {
25003
+ for (const entry of readdirSync4(dir)) {
23603
25004
  if (!entry.endsWith(".meta.json"))
23604
25005
  continue;
23605
- const metaFile = join11(dir, entry);
25006
+ const metaFile = join14(dir, entry);
23606
25007
  try {
23607
- const meta3 = JSON.parse(readFileSync4(metaFile, "utf8"));
25008
+ const meta3 = JSON.parse(readFileSync5(metaFile, "utf8"));
23608
25009
  const age = Date.now() - meta3.fetchedAt;
23609
25010
  if (age > CACHE_TTL_MS) {
23610
25011
  const hash2 = entry.slice(0, -".meta.json".length);
23611
25012
  const content = contentPath(storageDir, hash2, meta3.extension);
23612
- if (existsSync7(content))
23613
- unlinkSync4(content);
23614
- unlinkSync4(metaFile);
25013
+ if (existsSync10(content))
25014
+ unlinkSync6(content);
25015
+ unlinkSync6(metaFile);
23615
25016
  removed++;
23616
25017
  }
23617
25018
  } catch {
23618
25019
  try {
23619
- unlinkSync4(metaFile);
25020
+ unlinkSync6(metaFile);
23620
25021
  removed++;
23621
25022
  } catch {}
23622
25023
  }
@@ -25375,7 +26776,7 @@ function navigationTools(ctx) {
25375
26776
 
25376
26777
  // src/tools/reading.ts
25377
26778
  import { readdir } from "fs/promises";
25378
- import { extname as extname2, join as join12, resolve as resolve5 } from "path";
26779
+ import { extname as extname2, join as join15, resolve as resolve6 } from "path";
25379
26780
  import { tool as tool7 } from "@opencode-ai/plugin";
25380
26781
  var OUTLINE_EXTENSIONS = new Set([
25381
26782
  ".ts",
@@ -25453,7 +26854,7 @@ function readingTools(ctx) {
25453
26854
  if (!dirArg && typeof args.filePath === "string" && !Array.isArray(args.files)) {
25454
26855
  try {
25455
26856
  const { stat } = await import("fs/promises");
25456
- const resolved = resolve5(context.directory, args.filePath);
26857
+ const resolved = resolve6(context.directory, args.filePath);
25457
26858
  const st = await stat(resolved);
25458
26859
  if (st.isDirectory()) {
25459
26860
  dirArg = args.filePath;
@@ -25461,7 +26862,7 @@ function readingTools(ctx) {
25461
26862
  } catch {}
25462
26863
  }
25463
26864
  if (dirArg) {
25464
- const dirPath = resolve5(context.directory, dirArg);
26865
+ const dirPath = resolve6(context.directory, dirArg);
25465
26866
  const files = await discoverSourceFiles(dirPath);
25466
26867
  if (files.length === 0) {
25467
26868
  return JSON.stringify({
@@ -25569,12 +26970,12 @@ async function discoverSourceFiles(dir, maxFiles = 200) {
25569
26970
  return;
25570
26971
  if (entry.isDirectory()) {
25571
26972
  if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
25572
- await walk(join12(current, entry.name));
26973
+ await walk(join15(current, entry.name));
25573
26974
  }
25574
26975
  } else if (entry.isFile()) {
25575
26976
  const ext = extname2(entry.name).toLowerCase();
25576
26977
  if (OUTLINE_EXTENSIONS.has(ext)) {
25577
- files.push(join12(current, entry.name));
26978
+ files.push(join15(current, entry.name));
25578
26979
  }
25579
26980
  }
25580
26981
  }
@@ -26167,8 +27568,8 @@ var plugin = async (input) => {
26167
27568
  if (aftConfig.max_callgraph_files !== undefined)
26168
27569
  configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
26169
27570
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
26170
- const dataHome = process.env.XDG_DATA_HOME || join13(homedir7(), ".local", "share");
26171
- configOverrides.storage_dir = join13(dataHome, "opencode", "storage", "plugin", "aft");
27571
+ const dataHome = process.env.XDG_DATA_HOME || join16(homedir8(), ".local", "share");
27572
+ configOverrides.storage_dir = join16(dataHome, "opencode", "storage", "plugin", "aft");
26172
27573
  if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend) {
26173
27574
  const storageDir2 = configOverrides.storage_dir;
26174
27575
  const ortDylibDir = await ensureOnnxRuntime(storageDir2).catch((err) => {
@@ -26181,6 +27582,63 @@ var plugin = async (input) => {
26181
27582
  warn(`Semantic search requires ONNX Runtime. Install: ${getManualInstallHint()}`);
26182
27583
  }
26183
27584
  }
27585
+ try {
27586
+ const lspAutoInstall = aftConfig.lsp?.auto_install ?? true;
27587
+ const lspGraceDays = aftConfig.lsp?.grace_days ?? 7;
27588
+ const lspVersions = aftConfig.lsp?.versions ?? {};
27589
+ const lspDisabled = new Set(aftConfig.lsp?.disabled ?? []);
27590
+ const npmResult = runAutoInstall(input.directory, {
27591
+ autoInstall: lspAutoInstall,
27592
+ graceDays: lspGraceDays,
27593
+ versions: lspVersions,
27594
+ disabled: lspDisabled
27595
+ });
27596
+ const relevantGithub = discoverRelevantGithubServers(input.directory);
27597
+ const ghResult = runGithubAutoInstall(relevantGithub, {
27598
+ autoInstall: lspAutoInstall,
27599
+ graceDays: lspGraceDays,
27600
+ versions: lspVersions,
27601
+ disabled: lspDisabled
27602
+ });
27603
+ const mergedBinDirs = [...npmResult.cachedBinDirs, ...ghResult.cachedBinDirs];
27604
+ if (mergedBinDirs.length > 0) {
27605
+ configOverrides.lsp_paths_extra = mergedBinDirs;
27606
+ }
27607
+ if (npmResult.installsStarted > 0 || ghResult.installsStarted > 0) {
27608
+ log(`[lsp] auto-install: ${npmResult.installsStarted} npm + ${ghResult.installsStarted} github install(s) running in background`);
27609
+ }
27610
+ Promise.all([npmResult.installsComplete, ghResult.installsComplete]).then(() => {
27611
+ const actionable = [...npmResult.skipped, ...ghResult.skipped].filter((s) => {
27612
+ const r = s.reason.toLowerCase();
27613
+ if (r === "auto_install: false")
27614
+ return false;
27615
+ if (r === "disabled by config")
27616
+ return false;
27617
+ if (r === "not relevant to project")
27618
+ return false;
27619
+ if (r === "already installed")
27620
+ return false;
27621
+ if (r === "another install in progress")
27622
+ return false;
27623
+ return true;
27624
+ });
27625
+ if (actionable.length === 0)
27626
+ return;
27627
+ const lines = actionable.map((s) => ` \u2022 ${s.id}: ${s.reason}`).join(`
27628
+ `);
27629
+ const message = `AFT skipped or failed to install ${actionable.length} LSP server(s):
27630
+ ${lines}
27631
+
27632
+ ` + "See `/aft-status` for details, or check the plugin log. " + 'Pin a working version with `lsp.versions: { "<package>": "<version>" }` if grace is blocking, ' + "or set `lsp.auto_install: false` to suppress this entirely.";
27633
+ sendWarning({ client: input.client, directory: input.directory }, message).catch((err) => {
27634
+ warn(`[lsp] failed to deliver install summary: ${err}`);
27635
+ });
27636
+ }).catch((err) => {
27637
+ warn(`[lsp] install-summary aggregation failed: ${err}`);
27638
+ });
27639
+ } catch (err) {
27640
+ warn(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
27641
+ }
26184
27642
  let versionUpgradeAttempted = null;
26185
27643
  const pool = new BridgePool(binaryPath, {
26186
27644
  minVersion: PLUGIN_VERSION,
@@ -26228,6 +27686,7 @@ var plugin = async (input) => {
26228
27686
  setSharedBridgePool(pool);
26229
27687
  const rpcServer = new AftRpcServer(configOverrides.storage_dir, input.directory);
26230
27688
  const unregisterShutdown = registerShutdownCleanup(async () => {
27689
+ await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
26231
27690
  try {
26232
27691
  rpcServer.stop();
26233
27692
  } catch {}
@@ -26244,10 +27703,10 @@ var plugin = async (input) => {
26244
27703
  return { show: false };
26245
27704
  }
26246
27705
  if (storageDir) {
26247
- const versionFile = join13(storageDir, "last_announced_version");
27706
+ const versionFile = join16(storageDir, "last_announced_version");
26248
27707
  try {
26249
- if (existsSync9(versionFile)) {
26250
- const lastVersion = readFileSync5(versionFile, "utf-8").trim();
27708
+ if (existsSync12(versionFile)) {
27709
+ const lastVersion = readFileSync6(versionFile, "utf-8").trim();
26251
27710
  if (lastVersion === ANNOUNCEMENT_VERSION)
26252
27711
  return { show: false };
26253
27712
  }
@@ -26258,8 +27717,8 @@ var plugin = async (input) => {
26258
27717
  rpcServer.handle("mark-announced", async () => {
26259
27718
  if (storageDir && ANNOUNCEMENT_VERSION) {
26260
27719
  try {
26261
- mkdirSync8(storageDir, { recursive: true });
26262
- writeFileSync5(join13(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
27720
+ mkdirSync10(storageDir, { recursive: true });
27721
+ writeFileSync6(join16(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
26263
27722
  } catch {}
26264
27723
  }
26265
27724
  return { success: true };
@@ -26389,11 +27848,12 @@ Install: ${getManualInstallHint()}`).catch(() => {});
26389
27848
  }
26390
27849
  };
26391
27850
  },
26392
- dispose: () => {
27851
+ dispose: async () => {
26393
27852
  unregisterShutdown();
27853
+ await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
26394
27854
  rpcServer.stop();
26395
27855
  clearSharedBridgePool();
26396
- return pool.shutdown();
27856
+ await pool.shutdown();
26397
27857
  }
26398
27858
  };
26399
27859
  };