@cortexkit/aft-opencode 0.17.0 → 0.17.2

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 existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
7806
+ import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
- import { homedir as homedir8 } from "os";
7809
- import { join as join16 } from "path";
7808
+ import { homedir as homedir10 } from "os";
7809
+ import { join as join19 } from "path";
7810
7810
 
7811
7811
  // src/config.ts
7812
7812
  var import_comment_json = __toESM(require_src2(), 1);
@@ -21481,7 +21481,8 @@ var AftConfigSchema = exports_external.object({
21481
21481
  lsp: LspConfigSchema.optional(),
21482
21482
  url_fetch_allow_private: exports_external.boolean().optional(),
21483
21483
  semantic: SemanticConfigSchema.optional(),
21484
- max_callgraph_files: exports_external.number().int().positive().optional()
21484
+ max_callgraph_files: exports_external.number().int().positive().optional(),
21485
+ auto_update: exports_external.boolean().optional()
21485
21486
  });
21486
21487
  function normalizeLspExtension(extension) {
21487
21488
  return extension.trim().replace(/^\.+/, "");
@@ -21665,6 +21666,8 @@ function getStrippedTopLevelKeys(override) {
21665
21666
  stripped.push("url_fetch_allow_private");
21666
21667
  if (override.max_callgraph_files !== undefined)
21667
21668
  stripped.push("max_callgraph_files");
21669
+ if (override.auto_update !== undefined)
21670
+ stripped.push("auto_update");
21668
21671
  return stripped;
21669
21672
  }
21670
21673
  function mergeConfigs(base, override) {
@@ -21869,52 +21872,565 @@ async function fetchLatestTag() {
21869
21872
  }
21870
21873
  }
21871
21874
 
21872
- // src/lsp-auto-install.ts
21875
+ // src/hooks/auto-update-checker/cache.ts
21876
+ var import_comment_json3 = __toESM(require_src2(), 1);
21873
21877
  import { spawn } from "child_process";
21878
+ import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
21879
+ import { basename, dirname as dirname2, join as join6 } from "path";
21880
+
21881
+ // src/hooks/auto-update-checker/checker.ts
21882
+ var import_comment_json2 = __toESM(require_src2(), 1);
21883
+ import { existsSync as existsSync3, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
21884
+ import { homedir as homedir4 } from "os";
21885
+ import { dirname, isAbsolute, join as join5, resolve } from "path";
21886
+ import { fileURLToPath } from "url";
21887
+
21888
+ // src/hooks/auto-update-checker/constants.ts
21889
+ import { homedir as homedir3, platform } from "os";
21890
+ import { join as join4 } from "path";
21891
+ var PACKAGE_NAME = "@cortexkit/aft-opencode";
21892
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org";
21893
+ var NPM_FETCH_TIMEOUT = 1e4;
21894
+ function getOpenCodeCacheRoot() {
21895
+ if (platform() === "win32") {
21896
+ return join4(process.env.LOCALAPPDATA ?? homedir3(), "opencode");
21897
+ }
21898
+ return join4(homedir3(), ".cache", "opencode");
21899
+ }
21900
+ function getOpenCodeConfigRoot() {
21901
+ if (platform() === "win32") {
21902
+ return join4(process.env.APPDATA ?? join4(homedir3(), "AppData", "Roaming"), "opencode");
21903
+ }
21904
+ return join4(process.env.XDG_CONFIG_HOME ?? join4(homedir3(), ".config"), "opencode");
21905
+ }
21906
+ var CACHE_DIR = join4(getOpenCodeCacheRoot(), "packages");
21907
+ var USER_OPENCODE_CONFIG = join4(getOpenCodeConfigRoot(), "opencode.json");
21908
+ var USER_OPENCODE_CONFIG_JSONC = join4(getOpenCodeConfigRoot(), "opencode.jsonc");
21909
+
21910
+ // src/hooks/auto-update-checker/types.ts
21911
+ var NpmPackageEnvelopeSchema = exports_external.object({
21912
+ "dist-tags": exports_external.record(exports_external.string(), exports_external.string()).optional().default({})
21913
+ });
21914
+ var OpencodePluginTupleSchema = exports_external.tuple([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]);
21915
+ var OpencodeConfigSchema = exports_external.object({
21916
+ plugin: exports_external.array(exports_external.union([exports_external.string(), OpencodePluginTupleSchema])).optional()
21917
+ });
21918
+ var PackageJsonSchema = exports_external.object({
21919
+ name: exports_external.string().optional(),
21920
+ version: exports_external.string().optional(),
21921
+ dependencies: exports_external.record(exports_external.string(), exports_external.string()).optional()
21922
+ }).passthrough();
21923
+
21924
+ // src/hooks/auto-update-checker/checker.ts
21925
+ function isString(value) {
21926
+ return typeof value === "string";
21927
+ }
21928
+ function pluginSpecifier(entry) {
21929
+ return typeof entry === "string" ? entry : entry[0];
21930
+ }
21931
+ function getPluginEntries(config2) {
21932
+ const parsed = OpencodeConfigSchema.safeParse(config2);
21933
+ if (!parsed.success)
21934
+ return [];
21935
+ return (parsed.data.plugin ?? []).map(pluginSpecifier).filter(isString);
21936
+ }
21937
+ function parseJsonConfig(content) {
21938
+ try {
21939
+ return import_comment_json2.parse(content);
21940
+ } catch (err) {
21941
+ warn(`[auto-update-checker] Failed to parse OpenCode config: ${String(err)}`);
21942
+ return null;
21943
+ }
21944
+ }
21945
+ function isPrereleaseVersion(version2) {
21946
+ return version2.includes("-");
21947
+ }
21948
+ function isDistTag(version2) {
21949
+ return !/^\d/.test(version2);
21950
+ }
21951
+ function extractChannel(version2) {
21952
+ if (!version2)
21953
+ return "latest";
21954
+ if (isDistTag(version2))
21955
+ return version2;
21956
+ if (isPrereleaseVersion(version2)) {
21957
+ const prereleasePart = version2.split("-")[1];
21958
+ const channelMatch = prereleasePart?.match(/^(alpha|beta|rc|canary|next)/);
21959
+ if (channelMatch?.[1])
21960
+ return channelMatch[1];
21961
+ }
21962
+ return "latest";
21963
+ }
21964
+ function getConfigPaths(directory) {
21965
+ return [
21966
+ join5(directory, ".opencode", "opencode.json"),
21967
+ join5(directory, ".opencode", "opencode.jsonc"),
21968
+ USER_OPENCODE_CONFIG,
21969
+ USER_OPENCODE_CONFIG_JSONC
21970
+ ];
21971
+ }
21972
+ function resolvePathPluginSpec(spec, configPath) {
21973
+ if (spec.startsWith("file://")) {
21974
+ try {
21975
+ return fileURLToPath(spec);
21976
+ } catch {
21977
+ return spec.replace(/^file:\/\//, "");
21978
+ }
21979
+ }
21980
+ if (isAbsolute(spec) || /^[A-Za-z]:[\\/]/.test(spec))
21981
+ return spec;
21982
+ return resolve(dirname(configPath), spec);
21983
+ }
21984
+ function getLocalDevPath(directory) {
21985
+ for (const configPath of getConfigPaths(directory)) {
21986
+ try {
21987
+ if (!existsSync3(configPath))
21988
+ continue;
21989
+ const rawConfig = parseJsonConfig(readFileSync2(configPath, "utf-8"));
21990
+ const plugins = getPluginEntries(rawConfig);
21991
+ for (const entry of plugins) {
21992
+ if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
21993
+ continue;
21994
+ if (entry.startsWith("file://") || entry.startsWith(".") || isAbsolute(entry)) {
21995
+ const localPath = resolvePathPluginSpec(entry, configPath);
21996
+ const pkgPath = findPackageJsonUp(localPath);
21997
+ if (!pkgPath)
21998
+ continue;
21999
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync2(pkgPath, "utf-8")));
22000
+ if (pkg.success && pkg.data.name === PACKAGE_NAME)
22001
+ return localPath;
22002
+ }
22003
+ }
22004
+ } catch {}
22005
+ }
22006
+ return null;
22007
+ }
22008
+ function findPackageJsonUp(startPath) {
22009
+ try {
22010
+ const stat = statSync(startPath);
22011
+ let dir = stat.isDirectory() ? startPath : dirname(startPath);
22012
+ for (let i = 0;i < 10; i++) {
22013
+ const pkgPath = join5(dir, "package.json");
22014
+ if (existsSync3(pkgPath)) {
22015
+ try {
22016
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync2(pkgPath, "utf-8")));
22017
+ if (pkg.success && pkg.data.name === PACKAGE_NAME)
22018
+ return pkgPath;
22019
+ } catch {}
22020
+ }
22021
+ const parent = dirname(dir);
22022
+ if (parent === dir)
22023
+ break;
22024
+ dir = parent;
22025
+ }
22026
+ } catch {}
22027
+ return null;
22028
+ }
22029
+ function getLocalDevVersion(directory) {
22030
+ const localPath = getLocalDevPath(directory);
22031
+ if (!localPath)
22032
+ return null;
22033
+ try {
22034
+ const pkgPath = findPackageJsonUp(localPath);
22035
+ if (!pkgPath)
22036
+ return null;
22037
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync2(pkgPath, "utf-8")));
22038
+ return pkg.success ? pkg.data.version ?? null : null;
22039
+ } catch {
22040
+ return null;
22041
+ }
22042
+ }
22043
+ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
22044
+ try {
22045
+ return findPackageJsonUp(dirname(fileURLToPath(currentModuleUrl)));
22046
+ } catch (err) {
22047
+ warn(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
22048
+ return null;
22049
+ }
22050
+ }
22051
+ function findPluginEntry(directory) {
22052
+ for (const configPath of getConfigPaths(directory)) {
22053
+ try {
22054
+ if (!existsSync3(configPath))
22055
+ continue;
22056
+ const rawConfig = parseJsonConfig(readFileSync2(configPath, "utf-8"));
22057
+ const plugins = getPluginEntries(rawConfig);
22058
+ for (const entry of plugins) {
22059
+ if (entry === PACKAGE_NAME) {
22060
+ return { entry, isPinned: false, pinnedVersion: null, configPath };
22061
+ }
22062
+ if (entry.startsWith(`${PACKAGE_NAME}@`)) {
22063
+ const pinnedVersion = entry.slice(PACKAGE_NAME.length + 1);
22064
+ const isPinned = pinnedVersion !== "latest";
22065
+ return { entry, isPinned, pinnedVersion: isPinned ? pinnedVersion : null, configPath };
22066
+ }
22067
+ }
22068
+ } catch {}
22069
+ }
22070
+ return null;
22071
+ }
22072
+ var cachedPackageVersion = null;
22073
+ function getSpecCachePackageJsonPath(spec) {
22074
+ return join5(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
22075
+ }
22076
+ function getCachedVersion(spec) {
22077
+ if (!spec && cachedPackageVersion)
22078
+ return cachedPackageVersion;
22079
+ const candidates = [
22080
+ getCurrentRuntimePackageJsonPath(),
22081
+ spec ? getSpecCachePackageJsonPath(spec) : null,
22082
+ getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
22083
+ join5(homedir4(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
22084
+ ].filter(isString);
22085
+ for (const packageJsonPath of candidates) {
22086
+ try {
22087
+ if (!existsSync3(packageJsonPath))
22088
+ continue;
22089
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync2(packageJsonPath, "utf-8")));
22090
+ if (pkg.success && pkg.data.version) {
22091
+ if (!spec)
22092
+ cachedPackageVersion = pkg.data.version;
22093
+ return pkg.data.version;
22094
+ }
22095
+ } catch {}
22096
+ }
22097
+ return null;
22098
+ }
22099
+ function buildRegistryUrl(registryUrl) {
22100
+ return `${registryUrl.replace(/\/+$/, "")}/${encodeURIComponent(PACKAGE_NAME).replace("%2F", "/")}`;
22101
+ }
22102
+ async function getLatestVersion(channel = "latest", options = {}) {
22103
+ const controller = new AbortController;
22104
+ const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs ?? NPM_FETCH_TIMEOUT);
22105
+ const abortHandler = () => controller.abort();
22106
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
22107
+ try {
22108
+ if (options.signal?.aborted)
22109
+ return null;
22110
+ const response = await fetch(buildRegistryUrl(options.registryUrl ?? NPM_REGISTRY_URL), {
22111
+ signal: controller.signal,
22112
+ headers: { Accept: "application/json" }
22113
+ });
22114
+ if (!response.ok)
22115
+ return null;
22116
+ const data = NpmPackageEnvelopeSchema.safeParse(await response.json());
22117
+ if (!data.success)
22118
+ return null;
22119
+ return data.data["dist-tags"][channel] ?? data.data["dist-tags"].latest ?? null;
22120
+ } catch {
22121
+ return null;
22122
+ } finally {
22123
+ options.signal?.removeEventListener("abort", abortHandler);
22124
+ clearTimeout(timeoutId);
22125
+ }
22126
+ }
22127
+
22128
+ // src/hooks/auto-update-checker/cache.ts
22129
+ function stripPackageNameFromPath(pathValue, packageName) {
22130
+ let current = pathValue;
22131
+ for (const segment of [...packageName.split("/")].reverse()) {
22132
+ if (basename(current) !== segment)
22133
+ return null;
22134
+ current = dirname2(current);
22135
+ }
22136
+ return current;
22137
+ }
22138
+ function removeFromBunLock(installDir, packageName) {
22139
+ const lockPath = join6(installDir, "bun.lock");
22140
+ if (!existsSync4(lockPath))
22141
+ return false;
22142
+ try {
22143
+ const lock = import_comment_json3.parse(readFileSync3(lockPath, "utf-8"));
22144
+ let modified = false;
22145
+ if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
22146
+ delete lock.workspaces[""].dependencies[packageName];
22147
+ modified = true;
22148
+ }
22149
+ if (lock.packages?.[packageName]) {
22150
+ delete lock.packages[packageName];
22151
+ modified = true;
22152
+ }
22153
+ if (modified) {
22154
+ writeFileSync2(lockPath, JSON.stringify(lock, null, 2));
22155
+ log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
22156
+ }
22157
+ return modified;
22158
+ } catch {
22159
+ return false;
22160
+ }
22161
+ }
22162
+ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
22163
+ if (!existsSync4(packageJsonPath))
22164
+ return false;
22165
+ try {
22166
+ const raw = import_comment_json3.parse(readFileSync3(packageJsonPath, "utf-8"));
22167
+ const pkgJson = PackageJsonSchema.safeParse(raw);
22168
+ if (!pkgJson.success)
22169
+ return false;
22170
+ const nextPackageJson = { ...pkgJson.data };
22171
+ const dependencies = { ...nextPackageJson.dependencies ?? {} };
22172
+ if (dependencies[packageName] === version2)
22173
+ return true;
22174
+ dependencies[packageName] = version2;
22175
+ nextPackageJson.dependencies = dependencies;
22176
+ writeFileSync2(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
22177
+ log(`[auto-update-checker] Updated dependency in package.json: ${packageName} \u2192 ${version2}`);
22178
+ return true;
22179
+ } catch (err) {
22180
+ warn(`[auto-update-checker] Failed to update package.json dependency: ${String(err)}`);
22181
+ return false;
22182
+ }
22183
+ }
22184
+ function removeInstalledPackage(installDir, packageName) {
22185
+ const packageDir = join6(installDir, "node_modules", packageName);
22186
+ if (!existsSync4(packageDir))
22187
+ return false;
22188
+ rmSync(packageDir, { recursive: true, force: true });
22189
+ log(`[auto-update-checker] Package removed: ${packageDir}`);
22190
+ return true;
22191
+ }
22192
+ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
22193
+ if (runtimePackageJsonPath) {
22194
+ const packageDir = dirname2(runtimePackageJsonPath);
22195
+ const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
22196
+ if (nodeModulesDir && basename(nodeModulesDir) === "node_modules") {
22197
+ const installDir = dirname2(nodeModulesDir);
22198
+ const packageJsonPath = join6(installDir, "package.json");
22199
+ if (existsSync4(packageJsonPath))
22200
+ return { installDir, packageJsonPath };
22201
+ }
22202
+ return null;
22203
+ }
22204
+ const legacyPackageJsonPath = join6(dirname2(CACHE_DIR), "package.json");
22205
+ if (existsSync4(legacyPackageJsonPath)) {
22206
+ return { installDir: dirname2(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
22207
+ }
22208
+ return null;
22209
+ }
22210
+ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
22211
+ try {
22212
+ const installContext = resolveInstallContext(runtimePackageJsonPath);
22213
+ if (!installContext) {
22214
+ warn("[auto-update-checker] No install context found for auto-update");
22215
+ return null;
22216
+ }
22217
+ if (!ensureDependencyVersion(installContext.packageJsonPath, packageName, version2))
22218
+ return null;
22219
+ const packageRemoved = removeInstalledPackage(installContext.installDir, packageName);
22220
+ const lockRemoved = removeFromBunLock(installContext.installDir, packageName);
22221
+ if (!packageRemoved && !lockRemoved) {
22222
+ log(`[auto-update-checker] No cached package artifacts removed for ${packageName}; continuing with updated dependency spec`);
22223
+ }
22224
+ return installContext.installDir;
22225
+ } catch (err) {
22226
+ warn(`[auto-update-checker] Failed to prepare package update: ${String(err)}`);
22227
+ return null;
22228
+ }
22229
+ }
22230
+ async function runBunInstallSafe(installDir, options = {}) {
22231
+ let timeout = null;
22232
+ try {
22233
+ if (options.signal?.aborted)
22234
+ return false;
22235
+ const proc = spawn("bun", ["install"], {
22236
+ cwd: installDir,
22237
+ stdio: "pipe"
22238
+ });
22239
+ const abortProcess = () => {
22240
+ try {
22241
+ proc.kill();
22242
+ } catch {}
22243
+ };
22244
+ options.signal?.addEventListener("abort", abortProcess, { once: true });
22245
+ const exitPromise = new Promise((resolveExit) => {
22246
+ proc.on("error", () => resolveExit(false));
22247
+ proc.on("exit", (code) => resolveExit(code === 0));
22248
+ });
22249
+ const timeoutPromise = new Promise((resolveTimeout) => {
22250
+ timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
22251
+ });
22252
+ const result = await Promise.race([exitPromise, timeoutPromise]);
22253
+ options.signal?.removeEventListener("abort", abortProcess);
22254
+ if (result === "timeout" || options.signal?.aborted) {
22255
+ abortProcess();
22256
+ return false;
22257
+ }
22258
+ return result;
22259
+ } catch (err) {
22260
+ warn(`[auto-update-checker] bun install error: ${String(err)}`);
22261
+ return false;
22262
+ } finally {
22263
+ if (timeout)
22264
+ clearTimeout(timeout);
22265
+ }
22266
+ }
22267
+
22268
+ // src/hooks/auto-update-checker/index.ts
22269
+ function createAutoUpdateCheckerHook(ctx, options = {}) {
22270
+ const {
22271
+ enabled = true,
22272
+ showStartupToast = true,
22273
+ autoUpdate = true,
22274
+ npmRegistryUrl = NPM_REGISTRY_URL,
22275
+ fetchTimeoutMs = NPM_FETCH_TIMEOUT,
22276
+ signal = new AbortController().signal
22277
+ } = options;
22278
+ let hasChecked = false;
22279
+ return async ({ event }) => {
22280
+ if (!enabled)
22281
+ return;
22282
+ if (event.type !== "session.created")
22283
+ return;
22284
+ if (hasChecked)
22285
+ return;
22286
+ if (getParentId(event.properties))
22287
+ return;
22288
+ hasChecked = true;
22289
+ setTimeout(() => {
22290
+ runStartupCheck(ctx, {
22291
+ showStartupToast,
22292
+ autoUpdate,
22293
+ npmRegistryUrl,
22294
+ fetchTimeoutMs,
22295
+ signal
22296
+ }).catch((err) => {
22297
+ warn(`[auto-update-checker] Background update check failed: ${String(err)}`);
22298
+ });
22299
+ }, 0);
22300
+ };
22301
+ }
22302
+ function getParentId(properties) {
22303
+ if (!properties || typeof properties !== "object" || Array.isArray(properties))
22304
+ return null;
22305
+ const info = properties.info;
22306
+ if (!info || typeof info !== "object" || Array.isArray(info))
22307
+ return null;
22308
+ const parentID = info.parentID;
22309
+ return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
22310
+ }
22311
+ async function runStartupCheck(ctx, options) {
22312
+ if (options.signal.aborted)
22313
+ return;
22314
+ const cachedVersion = getCachedVersion();
22315
+ const localDevVersion = getLocalDevVersion(ctx.directory);
22316
+ const displayVersion = localDevVersion ?? cachedVersion;
22317
+ if (localDevVersion) {
22318
+ if (options.showStartupToast) {
22319
+ showToast(ctx, `AFT ${displayVersion} (dev)`, "Running in local development mode.", "info");
22320
+ }
22321
+ log("[auto-update-checker] Local development mode");
22322
+ return;
22323
+ }
22324
+ if (options.showStartupToast) {
22325
+ showToast(ctx, `AFT ${displayVersion ?? "unknown"}`, "@cortexkit/aft-opencode is active.", "info");
22326
+ }
22327
+ await runBackgroundUpdateCheck(ctx, options);
22328
+ }
22329
+ async function runBackgroundUpdateCheck(ctx, options) {
22330
+ if (options.signal.aborted)
22331
+ return;
22332
+ const pluginInfo = findPluginEntry(ctx.directory);
22333
+ if (!pluginInfo) {
22334
+ log("[auto-update-checker] Plugin not found in config");
22335
+ return;
22336
+ }
22337
+ const cachedVersion = getCachedVersion(pluginInfo.entry);
22338
+ const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
22339
+ if (!currentVersion) {
22340
+ log("[auto-update-checker] No version found (cached or pinned)");
22341
+ return;
22342
+ }
22343
+ const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
22344
+ const latestVersion = await getLatestVersion(channel, {
22345
+ registryUrl: options.npmRegistryUrl,
22346
+ timeoutMs: options.fetchTimeoutMs,
22347
+ signal: options.signal
22348
+ });
22349
+ if (!latestVersion) {
22350
+ warn(`[auto-update-checker] Failed to fetch latest version for channel: ${channel}`);
22351
+ showToast(ctx, "AFT update check failed", "Could not check npm for @cortexkit/aft-opencode updates. Continuing with the cached version.", "warning", 8000);
22352
+ return;
22353
+ }
22354
+ if (currentVersion === latestVersion) {
22355
+ log(`[auto-update-checker] Already on latest version for channel: ${channel}`);
22356
+ return;
22357
+ }
22358
+ log(`[auto-update-checker] Update available (${channel}): ${currentVersion} \u2192 ${latestVersion}`);
22359
+ if (pluginInfo.isPinned) {
22360
+ showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available. Version is pinned; update your OpenCode plugin config to upgrade.`, "info", 8000);
22361
+ log("[auto-update-checker] Version is pinned; skipping auto-update");
22362
+ return;
22363
+ }
22364
+ if (!options.autoUpdate) {
22365
+ showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
22366
+ log("[auto-update-checker] Auto-update disabled, notification only");
22367
+ return;
22368
+ }
22369
+ const installDir = preparePackageUpdate(latestVersion, PACKAGE_NAME);
22370
+ if (!installDir) {
22371
+ showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available. Auto-update could not prepare the active install.`, "warning", 8000);
22372
+ warn("[auto-update-checker] Failed to prepare install root for auto-update");
22373
+ return;
22374
+ }
22375
+ const installSuccess = await runBunInstallSafe(installDir, { signal: options.signal });
22376
+ if (installSuccess) {
22377
+ showToast(ctx, "AFT Updated!", `v${currentVersion} \u2192 v${latestVersion}
22378
+ Restart OpenCode to apply.`, "success", 8000);
22379
+ log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
22380
+ return;
22381
+ }
22382
+ showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
22383
+ warn("[auto-update-checker] bun install failed; update not installed");
22384
+ }
22385
+ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
22386
+ ctx.client.tui.showToast({ body: { title, message, variant, duration: duration3 } }).catch(() => {});
22387
+ }
22388
+
22389
+ // src/lsp-auto-install.ts
22390
+ import { spawn as spawn2 } from "child_process";
21874
22391
  import { createHash } from "crypto";
21875
- import { createReadStream, statSync as statSync2 } from "fs";
22392
+ import { createReadStream, statSync as statSync3 } from "fs";
21876
22393
 
21877
22394
  // src/lsp-cache.ts
21878
22395
  import {
21879
22396
  closeSync,
21880
- existsSync as existsSync3,
21881
22397
  mkdirSync as mkdirSync2,
21882
22398
  openSync,
21883
- readFileSync as readFileSync2,
21884
- statSync,
22399
+ readFileSync as readFileSync4,
22400
+ statSync as statSync2,
21885
22401
  unlinkSync as unlinkSync2,
21886
- writeFileSync
22402
+ writeFileSync as writeFileSync3
21887
22403
  } from "fs";
21888
- import { homedir as homedir3 } from "os";
21889
- import { join as join4 } from "path";
22404
+ import { homedir as homedir5 } from "os";
22405
+ import { join as join7 } from "path";
21890
22406
  function aftCacheBase() {
21891
22407
  const override = process.env.AFT_CACHE_DIR;
21892
22408
  if (override && override.length > 0)
21893
22409
  return override;
21894
22410
  if (process.platform === "win32") {
21895
22411
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
21896
- const base2 = localAppData || join4(homedir3(), "AppData", "Local");
21897
- return join4(base2, "aft");
22412
+ const base2 = localAppData || join7(homedir5(), "AppData", "Local");
22413
+ return join7(base2, "aft");
21898
22414
  }
21899
- const base = process.env.XDG_CACHE_HOME || join4(homedir3(), ".cache");
21900
- return join4(base, "aft");
22415
+ const base = process.env.XDG_CACHE_HOME || join7(homedir5(), ".cache");
22416
+ return join7(base, "aft");
21901
22417
  }
21902
22418
  function lspCacheRoot() {
21903
- return join4(aftCacheBase(), "lsp-packages");
22419
+ return join7(aftCacheBase(), "lsp-packages");
21904
22420
  }
21905
22421
  function lspPackageDir(npmPackage) {
21906
- return join4(lspCacheRoot(), encodeURIComponent(npmPackage));
22422
+ return join7(lspCacheRoot(), encodeURIComponent(npmPackage));
21907
22423
  }
21908
22424
  function lspBinaryPath(npmPackage, binary) {
21909
- return join4(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
22425
+ return join7(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
21910
22426
  }
21911
22427
  function lspBinDir(npmPackage) {
21912
- return join4(lspPackageDir(npmPackage), "node_modules", ".bin");
22428
+ return join7(lspPackageDir(npmPackage), "node_modules", ".bin");
21913
22429
  }
21914
22430
  function isInstalled(npmPackage, binary) {
21915
22431
  for (const candidate of lspBinaryCandidates(binary)) {
21916
22432
  try {
21917
- if (statSync(join4(lspBinDir(npmPackage), candidate)).isFile())
22433
+ if (statSync2(join7(lspBinDir(npmPackage), candidate)).isFile())
21918
22434
  return true;
21919
22435
  } catch {}
21920
22436
  }
@@ -21934,17 +22450,17 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
21934
22450
  installedAt: new Date().toISOString(),
21935
22451
  ...sha256 ? { sha256 } : {}
21936
22452
  };
21937
- writeFileSync(join4(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
22453
+ writeFileSync3(join7(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
21938
22454
  } catch (err) {
21939
22455
  log(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
21940
22456
  }
21941
22457
  }
21942
22458
  function readInstalledMetaIn(installDir) {
21943
- const path2 = join4(installDir, INSTALLED_META_FILE);
22459
+ const path2 = join7(installDir, INSTALLED_META_FILE);
21944
22460
  try {
21945
- if (!statSync(path2).isFile())
22461
+ if (!statSync2(path2).isFile())
21946
22462
  return null;
21947
- const raw = readFileSync2(path2, "utf8");
22463
+ const raw = readFileSync4(path2, "utf8");
21948
22464
  const parsed = JSON.parse(raw);
21949
22465
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
21950
22466
  return null;
@@ -21964,7 +22480,7 @@ function readInstalledMeta(packageKey) {
21964
22480
  return readInstalledMetaIn(lspPackageDir(packageKey));
21965
22481
  }
21966
22482
  function lockPath(npmPackage) {
21967
- return join4(lspPackageDir(npmPackage), ".aft-installing");
22483
+ return join7(lspPackageDir(npmPackage), ".aft-installing");
21968
22484
  }
21969
22485
  var STALE_LOCK_MS = 30 * 60 * 1000;
21970
22486
  function acquireInstallLock(lockKey) {
@@ -21974,7 +22490,7 @@ function acquireInstallLock(lockKey) {
21974
22490
  try {
21975
22491
  const fd = openSync(lock, "wx");
21976
22492
  try {
21977
- writeFileSync(fd, `${process.pid}
22493
+ writeFileSync3(fd, `${process.pid}
21978
22494
  ${new Date().toISOString()}
21979
22495
  `);
21980
22496
  } finally {
@@ -21994,12 +22510,12 @@ ${new Date().toISOString()}
21994
22510
  let owningPid = null;
21995
22511
  let lockMtimeMs = 0;
21996
22512
  try {
21997
- const raw = readFileSync2(lock, "utf8");
22513
+ const raw = readFileSync4(lock, "utf8");
21998
22514
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
21999
22515
  const parsed = Number.parseInt(firstLine, 10);
22000
22516
  if (Number.isFinite(parsed) && parsed > 0)
22001
22517
  owningPid = parsed;
22002
- lockMtimeMs = statSync(lock).mtimeMs;
22518
+ lockMtimeMs = statSync2(lock).mtimeMs;
22003
22519
  } catch {
22004
22520
  return tryClaim();
22005
22521
  }
@@ -22030,11 +22546,34 @@ function isProcessAlive(pid) {
22030
22546
  function releaseInstallLock(lockKey) {
22031
22547
  const lock = lockPath(lockKey);
22032
22548
  try {
22033
- if (existsSync3(lock)) {
22549
+ let owningPid = null;
22550
+ try {
22551
+ const raw = readFileSync4(lock, "utf8");
22552
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
22553
+ const parsed = Number.parseInt(firstLine, 10);
22554
+ if (Number.isFinite(parsed) && parsed > 0)
22555
+ owningPid = parsed;
22556
+ } catch (readErr) {
22557
+ const code = readErr.code;
22558
+ if (code === "ENOENT")
22559
+ return;
22560
+ warn(`[lsp] could not read install lock for ${lockKey} during release: ${readErr}`);
22561
+ return;
22562
+ }
22563
+ if (owningPid !== process.pid) {
22564
+ log(`[lsp] not releasing install lock for ${lockKey}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
22565
+ return;
22566
+ }
22567
+ try {
22034
22568
  unlinkSync2(lock);
22569
+ } catch (unlinkErr) {
22570
+ const code = unlinkErr.code;
22571
+ if (code !== "ENOENT") {
22572
+ warn(`[lsp] failed to release install lock for ${lockKey}: ${unlinkErr}`);
22573
+ }
22035
22574
  }
22036
22575
  } catch (err) {
22037
- warn(`[lsp] failed to release install lock for ${lockKey}: ${err}`);
22576
+ warn(`[lsp] unexpected error releasing install lock for ${lockKey}: ${err}`);
22038
22577
  }
22039
22578
  }
22040
22579
  async function withInstallLock(lockKey, task) {
@@ -22048,9 +22587,9 @@ async function withInstallLock(lockKey, task) {
22048
22587
  }
22049
22588
  var VERSION_CHECK_FILE = ".aft-version-check";
22050
22589
  function readVersionCheck(npmPackage) {
22051
- const file2 = join4(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22590
+ const file2 = join7(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22052
22591
  try {
22053
- const raw = readFileSync2(file2, "utf8");
22592
+ const raw = readFileSync4(file2, "utf8");
22054
22593
  const parsed = JSON.parse(raw);
22055
22594
  if (typeof parsed.last_checked === "string") {
22056
22595
  return {
@@ -22065,12 +22604,12 @@ function readVersionCheck(npmPackage) {
22065
22604
  }
22066
22605
  function writeVersionCheck(npmPackage, latest) {
22067
22606
  mkdirSync2(lspPackageDir(npmPackage), { recursive: true });
22068
- const file2 = join4(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22607
+ const file2 = join7(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
22069
22608
  const record2 = {
22070
22609
  last_checked: new Date().toISOString(),
22071
22610
  latest_eligible: latest
22072
22611
  };
22073
- writeFileSync(file2, JSON.stringify(record2, null, 2));
22612
+ writeFileSync3(file2, JSON.stringify(record2, null, 2));
22074
22613
  }
22075
22614
  function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
22076
22615
  if (!record2)
@@ -22136,6 +22675,9 @@ function assertSafeVersion(version2) {
22136
22675
  throw new Error(`unsafe version/tag string ${JSON.stringify(version2)}: must match ${SAFE_VERSION_RE.source}`);
22137
22676
  }
22138
22677
  }
22678
+ function isSafeVersion(version2) {
22679
+ return typeof version2 === "string" && version2.length > 0 && SAFE_VERSION_RE.test(version2);
22680
+ }
22139
22681
  function stripTagV(tag) {
22140
22682
  assertSafeVersion(tag);
22141
22683
  return tag.startsWith("v") ? tag.slice(1) : tag;
@@ -22210,8 +22752,8 @@ var NPM_LSP_TABLE = [
22210
22752
  ];
22211
22753
 
22212
22754
  // src/lsp-project-relevance.ts
22213
- import { existsSync as existsSync4, readdirSync } from "fs";
22214
- import { join as join5 } from "path";
22755
+ import { existsSync as existsSync5, readdirSync } from "fs";
22756
+ import { join as join8 } from "path";
22215
22757
  var MAX_WALK_DIRS = 200;
22216
22758
  var MAX_WALK_DEPTH = 4;
22217
22759
  var NOISE_DIRS = new Set([
@@ -22228,7 +22770,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
22228
22770
  if (!rootMarkers)
22229
22771
  return false;
22230
22772
  for (const marker of rootMarkers) {
22231
- if (existsSync4(join5(projectRoot, marker)))
22773
+ if (existsSync5(join8(projectRoot, marker)))
22232
22774
  return true;
22233
22775
  }
22234
22776
  return false;
@@ -22254,7 +22796,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
22254
22796
  for (const entry of entries) {
22255
22797
  if (entry.isDirectory()) {
22256
22798
  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 });
22799
+ queue.push({ dir: join8(current.dir, entry.name), depth: current.depth + 1 });
22258
22800
  }
22259
22801
  continue;
22260
22802
  }
@@ -22362,13 +22904,14 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
22362
22904
  }
22363
22905
  const cached2 = readVersionCheck(spec.npm);
22364
22906
  const weeklyMs = config2.graceDays * 24 * 60 * 60 * 1000;
22365
- if (!shouldRecheckVersion(cached2, weeklyMs) && cached2?.latest_eligible) {
22907
+ const cachedSafe = isSafeVersion(cached2?.latest_eligible ?? null);
22908
+ if (cached2 && !shouldRecheckVersion(cached2, weeklyMs) && cachedSafe) {
22366
22909
  return { version: cached2.latest_eligible, pinned: false, probe: null };
22367
22910
  }
22368
22911
  const probe = await probeRegistry(spec.npm, config2.graceDays, fetchImpl);
22369
22912
  if (!probe) {
22370
22913
  return {
22371
- version: cached2?.latest_eligible ?? null,
22914
+ version: cachedSafe ? cached2?.latest_eligible ?? null : null,
22372
22915
  pinned: false,
22373
22916
  probe: null
22374
22917
  };
@@ -22377,15 +22920,15 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
22377
22920
  return { version: probe.version, pinned: false, probe };
22378
22921
  }
22379
22922
  function runInstall(spec, version2, cwd, signal) {
22380
- return new Promise((resolve) => {
22923
+ return new Promise((resolve2) => {
22381
22924
  const target = `${spec.npm}@${version2}`;
22382
22925
  log(`[lsp] installing ${target} to ${cwd}`);
22383
22926
  if (signal?.aborted) {
22384
22927
  warn(`[lsp] install ${target} aborted before spawn`);
22385
- resolve(false);
22928
+ resolve2(false);
22386
22929
  return;
22387
22930
  }
22388
- const child = spawn("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
22931
+ const child = spawn2("bun", ["add", target, "--cwd", cwd, "--ignore-scripts", "--silent"], {
22389
22932
  stdio: ["ignore", "pipe", "pipe"]
22390
22933
  });
22391
22934
  child.unref();
@@ -22402,7 +22945,7 @@ function runInstall(spec, version2, cwd, signal) {
22402
22945
  return;
22403
22946
  settled = true;
22404
22947
  cleanup();
22405
- resolve(ok);
22948
+ resolve2(ok);
22406
22949
  };
22407
22950
  const onAbort = () => {
22408
22951
  warn(`[lsp] install ${target} aborted during shutdown`);
@@ -22503,7 +23046,7 @@ function cachedPackageDir(npmPackage) {
22503
23046
  return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
22504
23047
  }
22505
23048
  function hashInstalledBinary(spec) {
22506
- return new Promise((resolve, reject) => {
23049
+ return new Promise((resolve2, reject) => {
22507
23050
  const candidates = process.platform === "win32" ? [
22508
23051
  lspBinaryPath(spec.npm, spec.binary),
22509
23052
  lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
@@ -22513,7 +23056,7 @@ function hashInstalledBinary(spec) {
22513
23056
  let pathToHash = null;
22514
23057
  for (const p of candidates) {
22515
23058
  try {
22516
- if (statSync2(p).isFile()) {
23059
+ if (statSync3(p).isFile()) {
22517
23060
  pathToHash = p;
22518
23061
  break;
22519
23062
  }
@@ -22527,7 +23070,7 @@ function hashInstalledBinary(spec) {
22527
23070
  const stream = createReadStream(pathToHash);
22528
23071
  stream.on("error", reject);
22529
23072
  stream.on("data", (chunk) => hash2.update(chunk));
22530
- stream.on("end", () => resolve(hash2.digest("hex")));
23073
+ stream.on("end", () => resolve2(hash2.digest("hex")));
22531
23074
  });
22532
23075
  }
22533
23076
  function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
@@ -22589,85 +23132,85 @@ import {
22589
23132
  copyFileSync,
22590
23133
  createReadStream as createReadStream2,
22591
23134
  createWriteStream,
22592
- existsSync as existsSync5,
23135
+ existsSync as existsSync6,
22593
23136
  lstatSync,
22594
23137
  mkdirSync as mkdirSync3,
22595
23138
  readdirSync as readdirSync2,
22596
23139
  readlinkSync,
22597
23140
  realpathSync,
22598
23141
  renameSync,
22599
- rmSync,
22600
- statSync as statSync3,
23142
+ rmSync as rmSync2,
23143
+ statSync as statSync4,
22601
23144
  unlinkSync as unlinkSync3
22602
23145
  } from "fs";
22603
- import { dirname, join as join6, relative, resolve } from "path";
23146
+ import { dirname as dirname3, join as join9, relative, resolve as resolve2 } from "path";
22604
23147
  import { Readable } from "stream";
22605
23148
  import { pipeline } from "stream/promises";
22606
23149
 
22607
23150
  // src/lsp-github-table.ts
22608
- function exe(platform, name) {
22609
- return platform === "win32" ? `${name}.exe` : name;
23151
+ function exe(platform2, name) {
23152
+ return platform2 === "win32" ? `${name}.exe` : name;
22610
23153
  }
22611
23154
  var CLANGD = {
22612
23155
  id: "clangd",
22613
23156
  githubRepo: "clangd/clangd",
22614
23157
  binary: "clangd",
22615
- resolveAsset: (platform, _arch, version2) => {
22616
- const platformName = platform === "darwin" ? "mac" : platform === "linux" ? "linux" : "windows";
23158
+ resolveAsset: (platform2, _arch, version2) => {
23159
+ const platformName = platform2 === "darwin" ? "mac" : platform2 === "linux" ? "linux" : "windows";
22617
23160
  return { name: `clangd-${platformName}-${version2}.zip`, archive: "zip" };
22618
23161
  },
22619
- binaryPathInArchive: (platform, _arch, version2) => `clangd_${version2}/bin/${exe(platform, "clangd")}`
23162
+ binaryPathInArchive: (platform2, _arch, version2) => `clangd_${version2}/bin/${exe(platform2, "clangd")}`
22620
23163
  };
22621
23164
  var LUA_LS = {
22622
23165
  id: "lua-ls",
22623
23166
  githubRepo: "LuaLS/lua-language-server",
22624
23167
  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";
23168
+ resolveAsset: (platform2, arch, version2) => {
23169
+ const ext = platform2 === "win32" ? "zip" : "tar.gz";
23170
+ const platformName = platform2 === "darwin" ? "darwin" : platform2 === "linux" ? "linux" : "win32";
22628
23171
  const archName = arch === "arm64" ? "arm64" : "x64";
22629
23172
  return {
22630
23173
  name: `lua-language-server-${version2}-${platformName}-${archName}.${ext}`,
22631
23174
  archive: ext
22632
23175
  };
22633
23176
  },
22634
- binaryPathInArchive: (platform, _arch, _version) => `bin/${exe(platform, "lua-language-server")}`
23177
+ binaryPathInArchive: (platform2, _arch, _version) => `bin/${exe(platform2, "lua-language-server")}`
22635
23178
  };
22636
23179
  var ZLS = {
22637
23180
  id: "zls",
22638
23181
  githubRepo: "zigtools/zls",
22639
23182
  binary: "zls",
22640
- resolveAsset: (platform, arch, _version) => {
22641
- const ext = platform === "win32" ? "zip" : "tar.xz";
23183
+ resolveAsset: (platform2, arch, _version) => {
23184
+ const ext = platform2 === "win32" ? "zip" : "tar.xz";
22642
23185
  const archName = arch === "arm64" ? "aarch64" : "x86_64";
22643
- const platformName = platform === "darwin" ? "macos" : platform === "linux" ? "linux" : "windows";
23186
+ const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
22644
23187
  return { name: `zls-${archName}-${platformName}.${ext}`, archive: ext };
22645
23188
  },
22646
- binaryPathInArchive: (platform, _arch, _version) => exe(platform, "zls")
23189
+ binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "zls")
22647
23190
  };
22648
23191
  var TINYMIST = {
22649
23192
  id: "tinymist",
22650
23193
  githubRepo: "Myriad-Dreamin/tinymist",
22651
23194
  binary: "tinymist",
22652
- resolveAsset: (platform, arch, _version) => {
23195
+ resolveAsset: (platform2, arch, _version) => {
22653
23196
  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";
23197
+ const triple = platform2 === "darwin" ? "apple-darwin" : platform2 === "linux" ? "unknown-linux-gnu" : "pc-windows-msvc";
23198
+ const ext = platform2 === "win32" ? "zip" : "tar.gz";
22656
23199
  return { name: `tinymist-${archName}-${triple}.${ext}`, archive: ext };
22657
23200
  },
22658
- binaryPathInArchive: (platform, _arch, _version) => exe(platform, "tinymist")
23201
+ binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "tinymist")
22659
23202
  };
22660
23203
  var TEXLAB = {
22661
23204
  id: "texlab",
22662
23205
  githubRepo: "latex-lsp/texlab",
22663
23206
  binary: "texlab",
22664
- resolveAsset: (platform, arch, _version) => {
23207
+ resolveAsset: (platform2, arch, _version) => {
22665
23208
  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";
23209
+ const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
23210
+ const ext = platform2 === "win32" ? "zip" : "tar.gz";
22668
23211
  return { name: `texlab-${archName}-${platformName}.${ext}`, archive: ext };
22669
23212
  },
22670
- binaryPathInArchive: (platform, _arch, _version) => exe(platform, "texlab")
23213
+ binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "texlab")
22671
23214
  };
22672
23215
  var GITHUB_LSP_TABLE = [
22673
23216
  CLANGD,
@@ -22677,57 +23220,57 @@ var GITHUB_LSP_TABLE = [
22677
23220
  TEXLAB
22678
23221
  ];
22679
23222
  function detectHostPlatform() {
22680
- const platform = process.platform;
22681
- if (platform !== "darwin" && platform !== "linux" && platform !== "win32")
23223
+ const platform2 = process.platform;
23224
+ if (platform2 !== "darwin" && platform2 !== "linux" && platform2 !== "win32")
22682
23225
  return null;
22683
23226
  const arch = process.arch;
22684
23227
  if (arch === "x64")
22685
- return { platform, arch: "x64" };
23228
+ return { platform: platform2, arch: "x64" };
22686
23229
  if (arch === "arm64")
22687
- return { platform, arch: "arm64" };
23230
+ return { platform: platform2, arch: "arm64" };
22688
23231
  return null;
22689
23232
  }
22690
23233
 
22691
23234
  // src/lsp-github-install.ts
22692
23235
  function ghCacheRoot() {
22693
- return join6(aftCacheBase(), "lsp-binaries");
23236
+ return join9(aftCacheBase(), "lsp-binaries");
22694
23237
  }
22695
23238
  function ghPackageDir(spec) {
22696
- return join6(ghCacheRoot(), spec.id);
23239
+ return join9(ghCacheRoot(), spec.id);
22697
23240
  }
22698
23241
  function ghBinDir(spec) {
22699
- return join6(ghPackageDir(spec), "bin");
23242
+ return join9(ghPackageDir(spec), "bin");
22700
23243
  }
22701
23244
  function ghExtractDir(spec) {
22702
- return join6(ghPackageDir(spec), "extracted");
23245
+ return join9(ghPackageDir(spec), "extracted");
22703
23246
  }
22704
- function ghBinaryPath(spec, platform) {
22705
- const ext = platform === "win32" ? ".exe" : "";
22706
- return join6(ghBinDir(spec), `${spec.binary}${ext}`);
23247
+ function ghBinaryPath(spec, platform2) {
23248
+ const ext = platform2 === "win32" ? ".exe" : "";
23249
+ return join9(ghBinDir(spec), `${spec.binary}${ext}`);
22707
23250
  }
22708
- function isGithubInstalled(spec, platform) {
22709
- for (const candidate of ghBinaryCandidates(spec, platform)) {
23251
+ function isGithubInstalled(spec, platform2) {
23252
+ for (const candidate of ghBinaryCandidates(spec, platform2)) {
22710
23253
  try {
22711
- if (statSync3(join6(ghBinDir(spec), candidate)).isFile())
23254
+ if (statSync4(join9(ghBinDir(spec), candidate)).isFile())
22712
23255
  return true;
22713
23256
  } catch {}
22714
23257
  }
22715
23258
  return false;
22716
23259
  }
22717
- function ghBinaryCandidates(spec, platform) {
22718
- if (platform !== "win32")
23260
+ function ghBinaryCandidates(spec, platform2) {
23261
+ if (platform2 !== "win32")
22719
23262
  return [spec.binary];
22720
23263
  return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
22721
23264
  }
22722
23265
  var MAX_DOWNLOAD_BYTES = 256 * 1024 * 1024;
22723
23266
  var MAX_EXTRACT_BYTES = 1024 * 1024 * 1024;
22724
23267
  function sha256OfFile(path2) {
22725
- return new Promise((resolve2, reject) => {
23268
+ return new Promise((resolve3, reject) => {
22726
23269
  const hash2 = createHash2("sha256");
22727
23270
  const stream = createReadStream2(path2);
22728
23271
  stream.on("error", reject);
22729
23272
  stream.on("data", (chunk) => hash2.update(chunk));
22730
- stream.on("end", () => resolve2(hash2.digest("hex")));
23273
+ stream.on("end", () => resolve3(hash2.digest("hex")));
22731
23274
  });
22732
23275
  }
22733
23276
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
@@ -22814,8 +23357,10 @@ async function resolveTargetTag(spec, config2, fetchImpl, signal) {
22814
23357
  }
22815
23358
  const cached2 = readVersionCheck(spec.githubRepo);
22816
23359
  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);
23360
+ const cachedTag = cached2?.latest_eligible ?? null;
23361
+ const cachedSafe = isSafeVersion(cachedTag);
23362
+ if (cached2 && !shouldRecheckVersion(cached2, weeklyMs) && cachedSafe) {
23363
+ const release = await fetchReleaseByTag(spec.githubRepo, cachedTag, fetchImpl);
22819
23364
  if (release) {
22820
23365
  return {
22821
23366
  tag: release.tag,
@@ -22852,7 +23397,31 @@ function controlledTimeoutSignal(timeoutMs, parent) {
22852
23397
  }
22853
23398
  };
22854
23399
  }
23400
+ var ALLOWED_DOWNLOAD_HOSTS = new Set([
23401
+ "github.com",
23402
+ "api.github.com",
23403
+ "objects.githubusercontent.com",
23404
+ "release-assets.githubusercontent.com",
23405
+ "raw.githubusercontent.com",
23406
+ "codeload.github.com"
23407
+ ]);
23408
+ function assertAllowedDownloadUrl(rawUrl) {
23409
+ let parsed;
23410
+ try {
23411
+ parsed = new URL(rawUrl);
23412
+ } catch {
23413
+ throw new Error(`download url is not a valid URL: ${rawUrl}`);
23414
+ }
23415
+ if (parsed.protocol !== "https:") {
23416
+ throw new Error(`download url must be https (got ${parsed.protocol}): ${rawUrl}`);
23417
+ }
23418
+ if (!ALLOWED_DOWNLOAD_HOSTS.has(parsed.hostname.toLowerCase())) {
23419
+ throw new Error(`download url host ${parsed.hostname} is not in the GitHub allowlist: ${rawUrl}`);
23420
+ }
23421
+ return parsed;
23422
+ }
22855
23423
  async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
23424
+ assertAllowedDownloadUrl(url2);
22856
23425
  if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES) {
22857
23426
  throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES} (set lsp.versions to pin a smaller release if this is wrong)`);
22858
23427
  }
@@ -22870,7 +23439,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
22870
23439
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
22871
23440
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
22872
23441
  }
22873
- mkdirSync3(dirname(destPath), { recursive: true });
23442
+ mkdirSync3(dirname3(destPath), { recursive: true });
22874
23443
  let bytesWritten = 0;
22875
23444
  const guard = new TransformStream({
22876
23445
  transform(chunk, controller) {
@@ -22905,7 +23474,7 @@ function validateExtraction(stagingRoot) {
22905
23474
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
22906
23475
  }
22907
23476
  for (const entry of entries) {
22908
- const full = join6(dir, entry);
23477
+ const full = join9(dir, entry);
22909
23478
  let lst;
22910
23479
  try {
22911
23480
  lst = lstatSync(full);
@@ -22926,7 +23495,7 @@ function validateExtraction(stagingRoot) {
22926
23495
  throw new Error(`failed to realpath ${full}: ${err}`);
22927
23496
  }
22928
23497
  const rel = relative(realStagingRoot, realFull);
22929
- if (rel.startsWith("..") || resolve(realStagingRoot, rel) !== realFull) {
23498
+ if (rel.startsWith("..") || resolve2(realStagingRoot, rel) !== realFull) {
22930
23499
  throw new Error(`archive entry escapes staging root: ${full} \u2192 ${realFull} (zip-slip defense)`);
22931
23500
  }
22932
23501
  if (lst.isDirectory()) {
@@ -22947,19 +23516,19 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
22947
23516
  const suffix = randomBytes(8).toString("hex");
22948
23517
  const stagingDir = `${destDir}.staging-${suffix}`;
22949
23518
  try {
22950
- rmSync(stagingDir, { recursive: true, force: true });
23519
+ rmSync2(stagingDir, { recursive: true, force: true });
22951
23520
  } catch {}
22952
23521
  mkdirSync3(stagingDir, { recursive: true });
22953
23522
  try {
22954
23523
  runPlatformExtractor(archivePath, stagingDir, archiveType);
22955
23524
  validateExtraction(stagingDir);
22956
23525
  try {
22957
- rmSync(destDir, { recursive: true, force: true });
23526
+ rmSync2(destDir, { recursive: true, force: true });
22958
23527
  } catch {}
22959
23528
  renameSync(stagingDir, destDir);
22960
23529
  } catch (err) {
22961
23530
  try {
22962
- rmSync(stagingDir, { recursive: true, force: true });
23531
+ rmSync2(stagingDir, { recursive: true, force: true });
22963
23532
  } catch {}
22964
23533
  throw err;
22965
23534
  }
@@ -22995,11 +23564,11 @@ function runPlatformExtractor(archivePath, destDir, archiveType) {
22995
23564
  }
22996
23565
  throw new Error(`unsupported archive type: ${archiveType}`);
22997
23566
  }
22998
- async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal) {
23567
+ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal) {
22999
23568
  const version2 = stripTagV(tag);
23000
- const expected = spec.resolveAsset(platform, arch, version2);
23569
+ const expected = spec.resolveAsset(platform2, arch, version2);
23001
23570
  if (!expected) {
23002
- warn(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform}/${arch}`);
23571
+ warn(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform2}/${arch}`);
23003
23572
  return null;
23004
23573
  }
23005
23574
  const matchingAsset = assets.find((a) => a.name === expected.name);
@@ -23009,7 +23578,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
23009
23578
  }
23010
23579
  const pkgDir = ghPackageDir(spec);
23011
23580
  const extractDir = ghExtractDir(spec);
23012
- const archivePath = join6(pkgDir, expected.name);
23581
+ const archivePath = join9(pkgDir, expected.name);
23013
23582
  log(`[lsp] downloading ${spec.id} ${tag} \u2192 ${matchingAsset.url}`);
23014
23583
  try {
23015
23584
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -23048,16 +23617,16 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
23048
23617
  unlinkSync3(archivePath);
23049
23618
  } catch {}
23050
23619
  }
23051
- const innerBinaryPath = join6(extractDir, spec.binaryPathInArchive(platform, arch, version2));
23052
- if (!existsSync5(innerBinaryPath)) {
23620
+ const innerBinaryPath = join9(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
23621
+ if (!existsSync6(innerBinaryPath)) {
23053
23622
  error48(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
23054
23623
  return null;
23055
23624
  }
23056
- const targetBinary = ghBinaryPath(spec, platform);
23057
- mkdirSync3(dirname(targetBinary), { recursive: true });
23625
+ const targetBinary = ghBinaryPath(spec, platform2);
23626
+ mkdirSync3(dirname3(targetBinary), { recursive: true });
23058
23627
  try {
23059
23628
  copyFileSync(innerBinaryPath, targetBinary);
23060
- if (platform !== "win32") {
23629
+ if (platform2 !== "win32") {
23061
23630
  const { chmodSync: chmodSync2 } = await import("fs");
23062
23631
  chmodSync2(targetBinary, 493);
23063
23632
  }
@@ -23068,11 +23637,11 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
23068
23637
  log(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
23069
23638
  return archiveSha256;
23070
23639
  }
23071
- async function ensureGithubInstalled(spec, config2, fetchImpl, platform, arch, signal) {
23640
+ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
23072
23641
  const outcome = await withInstallLock(spec.githubRepo, async () => {
23073
23642
  const { tag, assets, blockedByGrace, reason } = await resolveTargetTag(spec, config2, fetchImpl, signal);
23074
23643
  if (!tag) {
23075
- const installed = isGithubInstalled(spec, platform);
23644
+ const installed = isGithubInstalled(spec, platform2);
23076
23645
  if (installed) {
23077
23646
  warn(`[lsp] no eligible release of ${spec.githubRepo} (grace=${config2.graceDays}d); keeping existing install`);
23078
23647
  return { started: false, reason: "kept existing install" };
@@ -23081,7 +23650,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform, arch, s
23081
23650
  warn(`[lsp] skipping ${spec.id}: ${fallbackReason}`);
23082
23651
  return { started: false, reason: fallbackReason };
23083
23652
  }
23084
- if (isGithubInstalled(spec, platform)) {
23653
+ if (isGithubInstalled(spec, platform2)) {
23085
23654
  const installedMeta = readInstalledMetaIn(ghPackageDir(spec));
23086
23655
  if (installedMeta && installedMeta.version === tag) {
23087
23656
  return { started: false, reason: "already installed" };
@@ -23092,7 +23661,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform, arch, s
23092
23661
  log(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
23093
23662
  }
23094
23663
  }
23095
- const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl, signal).catch((err) => {
23664
+ const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
23096
23665
  error48(`[lsp] github install ${spec.id} crashed: ${err}`);
23097
23666
  return null;
23098
23667
  });
@@ -23130,7 +23699,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
23130
23699
  if (!host) {
23131
23700
  for (const spec of GITHUB_LSP_TABLE) {
23132
23701
  try {
23133
- if (existsSync5(ghBinDir(spec))) {
23702
+ if (existsSync6(ghBinDir(spec))) {
23134
23703
  cachedBinDirs.push(ghBinDir(spec));
23135
23704
  }
23136
23705
  } catch {}
@@ -23290,9 +23859,9 @@ function normalizeToolMap(tools) {
23290
23859
  }
23291
23860
 
23292
23861
  // src/notifications.ts
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";
23862
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
23863
+ import { homedir as homedir6, platform as platform2 } from "os";
23864
+ import { join as join10 } from "path";
23296
23865
  function isTuiMode() {
23297
23866
  return process.env.OPENCODE_CLIENT === "cli";
23298
23867
  }
@@ -23313,28 +23882,28 @@ var WARNING_MARKER = `${AFT_MARKER} \u26A0\uFE0F`;
23313
23882
  var STATUS_MARKER = `${AFT_MARKER} \u2705`;
23314
23883
  var WARNED_TOOLS_FILE = "warned_tools.json";
23315
23884
  function getDesktopStatePath() {
23316
- const os2 = platform();
23317
- const home = homedir4();
23885
+ const os2 = platform2();
23886
+ const home = homedir6();
23318
23887
  if (os2 === "darwin") {
23319
- return join7(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
23888
+ return join10(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
23320
23889
  }
23321
23890
  if (os2 === "linux") {
23322
- const xdgConfig = process.env.XDG_CONFIG_HOME || join7(home, ".config");
23323
- return join7(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
23891
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join10(home, ".config");
23892
+ return join10(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
23324
23893
  }
23325
23894
  if (os2 === "win32") {
23326
- const appData = process.env.APPDATA || join7(home, "AppData", "Roaming");
23327
- return join7(appData, "ai.opencode.desktop", "opencode.global.dat");
23895
+ const appData = process.env.APPDATA || join10(home, "AppData", "Roaming");
23896
+ return join10(appData, "ai.opencode.desktop", "opencode.global.dat");
23328
23897
  }
23329
23898
  return null;
23330
23899
  }
23331
23900
  function readDesktopState(directory) {
23332
23901
  const statePath = getDesktopStatePath();
23333
- if (!statePath || !existsSync6(statePath)) {
23902
+ if (!statePath || !existsSync7(statePath)) {
23334
23903
  return { sessionId: null, serverUrl: null };
23335
23904
  }
23336
23905
  try {
23337
- const raw = readFileSync3(statePath, "utf-8");
23906
+ const raw = readFileSync5(statePath, "utf-8");
23338
23907
  const state = JSON.parse(raw);
23339
23908
  let serverUrl = null;
23340
23909
  const serverStr = state.server;
@@ -23428,10 +23997,10 @@ async function sendWarning(opts, message) {
23428
23997
  }
23429
23998
  async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
23430
23999
  if (storageDir) {
23431
- const versionFile = join7(storageDir, "last_announced_version");
24000
+ const versionFile = join10(storageDir, "last_announced_version");
23432
24001
  try {
23433
- if (existsSync6(versionFile)) {
23434
- const lastVersion = readFileSync3(versionFile, "utf-8").trim();
24002
+ if (existsSync7(versionFile)) {
24003
+ const lastVersion = readFileSync5(versionFile, "utf-8").trim();
23435
24004
  if (lastVersion === version2)
23436
24005
  return;
23437
24006
  }
@@ -23452,16 +24021,16 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
23452
24021
  if (storageDir) {
23453
24022
  try {
23454
24023
  mkdirSync4(storageDir, { recursive: true });
23455
- writeFileSync2(join7(storageDir, "last_announced_version"), version2);
24024
+ writeFileSync4(join10(storageDir, "last_announced_version"), version2);
23456
24025
  } catch {}
23457
24026
  }
23458
24027
  }
23459
24028
  function readWarnedTools(storageDir) {
23460
24029
  try {
23461
- const warnedToolsPath = join7(storageDir, WARNED_TOOLS_FILE);
23462
- if (!existsSync6(warnedToolsPath))
24030
+ const warnedToolsPath = join10(storageDir, WARNED_TOOLS_FILE);
24031
+ if (!existsSync7(warnedToolsPath))
23463
24032
  return {};
23464
- const parsed = JSON.parse(readFileSync3(warnedToolsPath, "utf-8"));
24033
+ const parsed = JSON.parse(readFileSync5(warnedToolsPath, "utf-8"));
23465
24034
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
23466
24035
  return {};
23467
24036
  const warned = {};
@@ -23478,8 +24047,8 @@ function readWarnedTools(storageDir) {
23478
24047
  function writeWarnedTools(storageDir, warned) {
23479
24048
  try {
23480
24049
  mkdirSync4(storageDir, { recursive: true });
23481
- const warnedToolsPath = join7(storageDir, WARNED_TOOLS_FILE);
23482
- writeFileSync2(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
24050
+ const warnedToolsPath = join10(storageDir, WARNED_TOOLS_FILE);
24051
+ writeFileSync4(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
23483
24052
  `);
23484
24053
  } catch {}
23485
24054
  }
@@ -23570,10 +24139,37 @@ async function cleanupWarnings(opts) {
23570
24139
  }
23571
24140
 
23572
24141
  // src/onnx-runtime.ts
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";
24142
+ import { execFileSync as execFileSync2 } from "child_process";
24143
+ import { createHash as createHash3 } from "crypto";
24144
+ import {
24145
+ chmodSync as chmodSync2,
24146
+ closeSync as closeSync2,
24147
+ copyFileSync as copyFileSync2,
24148
+ createWriteStream as createWriteStream2,
24149
+ existsSync as existsSync8,
24150
+ lstatSync as lstatSync2,
24151
+ mkdirSync as mkdirSync5,
24152
+ openSync as openSync2,
24153
+ readdirSync as readdirSync3,
24154
+ readFileSync as readFileSync6,
24155
+ readlinkSync as readlinkSync2,
24156
+ realpathSync as realpathSync2,
24157
+ rmSync as rmSync3,
24158
+ statSync as statSync5,
24159
+ symlinkSync,
24160
+ unlinkSync as unlinkSync4,
24161
+ writeFileSync as writeFileSync5
24162
+ } from "fs";
24163
+ import { dirname as dirname4, join as join11, relative as relative2, resolve as resolve3 } from "path";
24164
+ import { Readable as Readable2 } from "stream";
24165
+ import { pipeline as pipeline2 } from "stream/promises";
23575
24166
  var ORT_VERSION = "1.24.4";
23576
24167
  var ORT_REPO = "microsoft/onnxruntime";
24168
+ var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
24169
+ var MAX_EXTRACT_BYTES2 = 1 * 1024 * 1024 * 1024;
24170
+ var ONNX_LOCK_FILE = ".aft-onnx-installing";
24171
+ var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
24172
+ var STALE_LOCK_MS2 = 30 * 60 * 1000;
23577
24173
  var ORT_PLATFORM_MAP = {
23578
24174
  darwin: {
23579
24175
  arm64: {
@@ -23627,11 +24223,27 @@ function getManualInstallHint() {
23627
24223
  }
23628
24224
  async function ensureOnnxRuntime(storageDir) {
23629
24225
  const info = getPlatformInfo();
23630
- const ortDir = join8(storageDir, "onnxruntime", ORT_VERSION);
23631
- const libPath = join8(ortDir, info?.libName ?? "libonnxruntime.dylib");
23632
- if (existsSync7(libPath)) {
23633
- log(`ONNX Runtime found at ${ortDir}`);
23634
- return ortDir;
24226
+ const ortDir = join11(storageDir, "onnxruntime", ORT_VERSION);
24227
+ const libPath = join11(ortDir, info?.libName ?? "libonnxruntime.dylib");
24228
+ if (existsSync8(libPath)) {
24229
+ const meta3 = readOnnxInstalledMeta(ortDir);
24230
+ if (meta3?.sha256) {
24231
+ try {
24232
+ const currentHash = sha256File(libPath);
24233
+ if (currentHash !== meta3.sha256) {
24234
+ error48(`ONNX Runtime at ${ortDir}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${meta3.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-download from scratch.`);
24235
+ } else {
24236
+ log(`ONNX Runtime found at ${ortDir} (TOFU verified)`);
24237
+ return ortDir;
24238
+ }
24239
+ } catch (err) {
24240
+ warn(`Could not verify ONNX Runtime hash at ${ortDir}: ${err}`);
24241
+ return ortDir;
24242
+ }
24243
+ } else {
24244
+ log(`ONNX Runtime found at ${ortDir} (no recorded hash, accepting)`);
24245
+ return ortDir;
24246
+ }
23635
24247
  }
23636
24248
  const systemPath = findSystemOnnxRuntime(info?.libName);
23637
24249
  if (systemPath) {
@@ -23642,7 +24254,18 @@ async function ensureOnnxRuntime(storageDir) {
23642
24254
  warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
23643
24255
  return null;
23644
24256
  }
23645
- return downloadOnnxRuntime(info, ortDir);
24257
+ const onnxBaseDir = join11(storageDir, "onnxruntime");
24258
+ mkdirSync5(onnxBaseDir, { recursive: true });
24259
+ const lockPath2 = join11(onnxBaseDir, ONNX_LOCK_FILE);
24260
+ if (!acquireLock(lockPath2)) {
24261
+ warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath2}). Skipping.`);
24262
+ return null;
24263
+ }
24264
+ try {
24265
+ return await downloadOnnxRuntime(info, ortDir);
24266
+ } finally {
24267
+ releaseLock(lockPath2);
24268
+ }
23646
24269
  }
23647
24270
  function findSystemOnnxRuntime(libName) {
23648
24271
  if (!libName)
@@ -23654,40 +24277,119 @@ function findSystemOnnxRuntime(libName) {
23654
24277
  searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
23655
24278
  }
23656
24279
  for (const dir of searchPaths) {
23657
- if (existsSync7(join8(dir, libName))) {
24280
+ if (existsSync8(join11(dir, libName))) {
23658
24281
  return dir;
23659
24282
  }
23660
24283
  }
23661
24284
  return null;
23662
24285
  }
24286
+ async function downloadFileWithCap(url2, destPath) {
24287
+ const controller = new AbortController;
24288
+ const timeout = setTimeout(() => controller.abort(), 300000);
24289
+ try {
24290
+ const res = await fetch(url2, {
24291
+ headers: { accept: "application/octet-stream" },
24292
+ redirect: "follow",
24293
+ signal: controller.signal
24294
+ });
24295
+ if (!res.ok || !res.body) {
24296
+ throw new Error(`download failed (HTTP ${res.status})`);
24297
+ }
24298
+ const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
24299
+ if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
24300
+ throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
24301
+ }
24302
+ mkdirSync5(dirname4(destPath), { recursive: true });
24303
+ let bytesWritten = 0;
24304
+ const guard = new TransformStream({
24305
+ transform(chunk, transformController) {
24306
+ bytesWritten += chunk.byteLength;
24307
+ if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
24308
+ transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
24309
+ return;
24310
+ }
24311
+ transformController.enqueue(chunk);
24312
+ }
24313
+ });
24314
+ const guarded = res.body.pipeThrough(guard);
24315
+ const nodeStream = Readable2.fromWeb(guarded);
24316
+ await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
24317
+ } catch (err) {
24318
+ try {
24319
+ unlinkSync4(destPath);
24320
+ } catch {}
24321
+ throw err;
24322
+ } finally {
24323
+ clearTimeout(timeout);
24324
+ }
24325
+ }
24326
+ function validateExtractedTree(stagingRoot) {
24327
+ const realRoot = realpathSync2(stagingRoot);
24328
+ let totalBytes = 0;
24329
+ const walk = (dir) => {
24330
+ const entries = readdirSync3(dir);
24331
+ for (const entry of entries) {
24332
+ const fullPath = join11(dir, entry);
24333
+ const lst = lstatSync2(fullPath);
24334
+ if (lst.isSymbolicLink()) {
24335
+ const linkTarget = readlinkSync2(fullPath);
24336
+ const resolvedTarget = resolve3(dirname4(fullPath), linkTarget);
24337
+ const rel2 = relative2(realRoot, resolvedTarget);
24338
+ if (rel2.startsWith("..") || process.platform !== "win32" && rel2.startsWith("/")) {
24339
+ throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
24340
+ }
24341
+ continue;
24342
+ }
24343
+ const rel = relative2(realRoot, fullPath);
24344
+ if (rel.startsWith("..") || process.platform !== "win32" && rel.startsWith("/")) {
24345
+ throw new Error(`extracted entry ${fullPath} escapes staging root`);
24346
+ }
24347
+ if (lst.isDirectory()) {
24348
+ walk(fullPath);
24349
+ continue;
24350
+ }
24351
+ if (lst.isFile()) {
24352
+ totalBytes += lst.size;
24353
+ if (totalBytes > MAX_EXTRACT_BYTES2) {
24354
+ throw new Error(`extracted size ${totalBytes} exceeds max ${MAX_EXTRACT_BYTES2} (decompression bomb defense)`);
24355
+ }
24356
+ }
24357
+ }
24358
+ };
24359
+ walk(realRoot);
24360
+ }
23663
24361
  async function downloadOnnxRuntime(info, targetDir) {
23664
24362
  const url2 = `https://github.com/${ORT_REPO}/releases/download/v${ORT_VERSION}/${info.assetName}.${info.archiveType === "tgz" ? "tgz" : "zip"}`;
23665
24363
  log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
24364
+ const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
23666
24365
  try {
23667
- const tmpDir = `${targetDir}.tmp.${process.pid}`;
23668
24366
  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], {
23672
- stdio: "pipe",
23673
- timeout: 120000
23674
- });
24367
+ const archivePath = join11(tmpDir, `onnxruntime.${info.archiveType}`);
24368
+ await downloadFileWithCap(url2, archivePath);
24369
+ const archiveSha256 = sha256File(archivePath);
24370
+ log(`ONNX Runtime archive sha256=${archiveSha256}`);
23675
24371
  if (info.archiveType === "tgz") {
23676
- execFileSync2("tar", ["xzf", archivePath, "-C", tmpDir], { stdio: "pipe" });
24372
+ execFileSync2("tar", ["xzf", archivePath, "-C", tmpDir], {
24373
+ stdio: "pipe",
24374
+ timeout: 120000
24375
+ });
23677
24376
  } else {
23678
24377
  await extractZipArchive(archivePath, tmpDir);
23679
24378
  }
23680
- const extractedDir = join8(tmpDir, info.assetName, "lib");
23681
- if (!existsSync7(extractedDir)) {
24379
+ try {
24380
+ unlinkSync4(archivePath);
24381
+ } catch {}
24382
+ validateExtractedTree(tmpDir);
24383
+ const extractedDir = join11(tmpDir, info.assetName, "lib");
24384
+ if (!existsSync8(extractedDir)) {
23682
24385
  throw new Error(`Expected directory not found: ${extractedDir}`);
23683
24386
  }
23684
24387
  mkdirSync5(targetDir, { recursive: true });
23685
24388
  const libFiles = readdirSync3(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
23686
- const { lstatSync: lstatSync2, symlinkSync, readlinkSync: readlinkSync2, copyFileSync: cpFile } = await import("fs");
23687
24389
  const realFiles = [];
23688
24390
  const symlinks = [];
23689
24391
  for (const libFile of libFiles) {
23690
- const src = join8(extractedDir, libFile);
24392
+ const src = join11(extractedDir, libFile);
23691
24393
  try {
23692
24394
  const stat = lstatSync2(src);
23693
24395
  log(`ORT extract: ${libFile} \u2014 isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
@@ -23702,10 +24404,10 @@ async function downloadOnnxRuntime(info, targetDir) {
23702
24404
  }
23703
24405
  }
23704
24406
  for (const libFile of realFiles) {
23705
- const src = join8(extractedDir, libFile);
23706
- const dst = join8(targetDir, libFile);
24407
+ const src = join11(extractedDir, libFile);
24408
+ const dst = join11(targetDir, libFile);
23707
24409
  try {
23708
- cpFile(src, dst);
24410
+ copyFileSync2(src, dst);
23709
24411
  if (process.platform !== "win32") {
23710
24412
  chmodSync2(dst, 493);
23711
24413
  }
@@ -23714,27 +24416,35 @@ async function downloadOnnxRuntime(info, targetDir) {
23714
24416
  }
23715
24417
  }
23716
24418
  for (const link of symlinks) {
23717
- const dst = join8(targetDir, link.name);
24419
+ const dst = join11(targetDir, link.name);
23718
24420
  try {
23719
24421
  unlinkSync4(dst);
23720
24422
  } catch {}
23721
24423
  symlinkSync(link.target, dst);
23722
24424
  }
23723
- const { rmSync: rmSync2 } = await import("fs");
23724
- rmSync2(tmpDir, { recursive: true, force: true });
24425
+ const libPath = join11(targetDir, info.libName);
24426
+ let libHash = null;
24427
+ try {
24428
+ libHash = sha256File(libPath);
24429
+ } catch (err) {
24430
+ warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
24431
+ }
24432
+ writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
24433
+ rmSync3(tmpDir, { recursive: true, force: true });
23725
24434
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
23726
24435
  return targetDir;
23727
24436
  } catch (err) {
23728
24437
  error48(`Failed to download ONNX Runtime: ${err}`);
23729
24438
  try {
23730
- const { rmSync: rmSync2 } = await import("fs");
23731
- rmSync2(`${targetDir}.tmp.${process.pid}`, { recursive: true, force: true });
24439
+ rmSync3(tmpDir, { recursive: true, force: true });
24440
+ } catch {}
24441
+ try {
24442
+ rmSync3(targetDir, { recursive: true, force: true });
23732
24443
  } catch {}
23733
24444
  return null;
23734
24445
  }
23735
24446
  }
23736
24447
  async function extractZipArchive(archivePath, destinationDir) {
23737
- const { execFileSync: execFileSync2 } = await import("child_process");
23738
24448
  if (process.platform === "win32") {
23739
24449
  execFileSync2("tar.exe", ["-xf", archivePath, "-C", destinationDir], {
23740
24450
  stdio: "pipe",
@@ -23747,11 +24457,138 @@ async function extractZipArchive(archivePath, destinationDir) {
23747
24457
  timeout: 120000
23748
24458
  });
23749
24459
  }
24460
+ function writeOnnxInstalledMeta(installDir, version2, sha256, archiveSha256) {
24461
+ try {
24462
+ const meta3 = {
24463
+ version: version2,
24464
+ installedAt: new Date().toISOString(),
24465
+ ...sha256 ? { sha256 } : {},
24466
+ archiveSha256
24467
+ };
24468
+ writeFileSync5(join11(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
24469
+ } catch (err) {
24470
+ log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
24471
+ }
24472
+ }
24473
+ function readOnnxInstalledMeta(installDir) {
24474
+ const path2 = join11(installDir, ONNX_INSTALLED_META_FILE);
24475
+ try {
24476
+ if (!statSync5(path2).isFile())
24477
+ return null;
24478
+ const raw = readFileSync6(path2, "utf8");
24479
+ const parsed = JSON.parse(raw);
24480
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
24481
+ return null;
24482
+ return {
24483
+ version: parsed.version,
24484
+ installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
24485
+ ...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
24486
+ ...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
24487
+ };
24488
+ } catch {
24489
+ return null;
24490
+ }
24491
+ }
24492
+ function sha256File(path2) {
24493
+ const hash2 = createHash3("sha256");
24494
+ hash2.update(readFileSync6(path2));
24495
+ return hash2.digest("hex");
24496
+ }
24497
+ function acquireLock(lockPath2) {
24498
+ const tryClaim = () => {
24499
+ try {
24500
+ const fd = openSync2(lockPath2, "wx");
24501
+ try {
24502
+ writeFileSync5(fd, `${process.pid}
24503
+ ${new Date().toISOString()}
24504
+ `);
24505
+ } finally {
24506
+ closeSync2(fd);
24507
+ }
24508
+ return true;
24509
+ } catch (err) {
24510
+ const code = err.code;
24511
+ if (code === "EEXIST")
24512
+ return false;
24513
+ warn(`[onnx] unexpected error acquiring lock ${lockPath2}: ${err}`);
24514
+ return false;
24515
+ }
24516
+ };
24517
+ if (tryClaim())
24518
+ return true;
24519
+ let owningPid = null;
24520
+ let lockMtimeMs = 0;
24521
+ try {
24522
+ const raw = readFileSync6(lockPath2, "utf8");
24523
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
24524
+ const parsed = Number.parseInt(firstLine, 10);
24525
+ if (Number.isFinite(parsed) && parsed > 0)
24526
+ owningPid = parsed;
24527
+ lockMtimeMs = statSync5(lockPath2).mtimeMs;
24528
+ } catch {
24529
+ return tryClaim();
24530
+ }
24531
+ const age = Date.now() - lockMtimeMs;
24532
+ const ageWithinFresh = Math.abs(age) < STALE_LOCK_MS2;
24533
+ const skipLiveness = process.platform === "win32";
24534
+ const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive2(owningPid);
24535
+ if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
24536
+ return false;
24537
+ }
24538
+ log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
24539
+ try {
24540
+ unlinkSync4(lockPath2);
24541
+ } catch {}
24542
+ return tryClaim();
24543
+ }
24544
+ function releaseLock(lockPath2) {
24545
+ try {
24546
+ let owningPid = null;
24547
+ try {
24548
+ const raw = readFileSync6(lockPath2, "utf8");
24549
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
24550
+ const parsed = Number.parseInt(firstLine, 10);
24551
+ if (Number.isFinite(parsed) && parsed > 0)
24552
+ owningPid = parsed;
24553
+ } catch (readErr) {
24554
+ const code = readErr.code;
24555
+ if (code === "ENOENT")
24556
+ return;
24557
+ warn(`[onnx] could not read lock ${lockPath2} during release: ${readErr}`);
24558
+ return;
24559
+ }
24560
+ if (owningPid !== process.pid) {
24561
+ log(`[onnx] not releasing lock ${lockPath2}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
24562
+ return;
24563
+ }
24564
+ try {
24565
+ unlinkSync4(lockPath2);
24566
+ } catch (unlinkErr) {
24567
+ const code = unlinkErr.code;
24568
+ if (code !== "ENOENT") {
24569
+ warn(`[onnx] failed to release lock ${lockPath2}: ${unlinkErr}`);
24570
+ }
24571
+ }
24572
+ } catch (err) {
24573
+ warn(`[onnx] unexpected error releasing lock ${lockPath2}: ${err}`);
24574
+ }
24575
+ }
24576
+ function isProcessAlive2(pid) {
24577
+ try {
24578
+ process.kill(pid, 0);
24579
+ return true;
24580
+ } catch (err) {
24581
+ const code = err.code;
24582
+ if (code === "ESRCH")
24583
+ return false;
24584
+ return true;
24585
+ }
24586
+ }
23750
24587
 
23751
24588
  // src/bridge.ts
23752
- import { spawn as spawn2 } from "child_process";
23753
- import { homedir as homedir5 } from "os";
23754
- import { join as join9 } from "path";
24589
+ import { spawn as spawn3 } from "child_process";
24590
+ import { homedir as homedir7 } from "os";
24591
+ import { join as join12 } from "path";
23755
24592
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
23756
24593
  var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
23757
24594
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
@@ -23893,14 +24730,14 @@ class BinaryBridge {
23893
24730
  const line = `${JSON.stringify(request)}
23894
24731
  `;
23895
24732
  const effectiveTimeoutMs = options?.timeoutMs ?? this.timeoutMs;
23896
- return new Promise((resolve2, reject) => {
24733
+ return new Promise((resolve4, reject) => {
23897
24734
  const timer = setTimeout(() => {
23898
24735
  this.pending.delete(id);
23899
24736
  warn(`Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms \u2014 restarting bridge`);
23900
24737
  reject(new Error(`[aft-plugin] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
23901
24738
  this.handleTimeout();
23902
24739
  }, effectiveTimeoutMs);
23903
- this.pending.set(id, { resolve: resolve2, reject, timer });
24740
+ this.pending.set(id, { resolve: resolve4, reject, timer });
23904
24741
  if (!this.process?.stdin?.writable) {
23905
24742
  this.pending.delete(id);
23906
24743
  clearTimeout(timer);
@@ -23943,15 +24780,15 @@ class BinaryBridge {
23943
24780
  if (this.process) {
23944
24781
  const proc = this.process;
23945
24782
  this.process = null;
23946
- return new Promise((resolve2) => {
24783
+ return new Promise((resolve4) => {
23947
24784
  const forceKillTimer = setTimeout(() => {
23948
24785
  proc.kill("SIGKILL");
23949
- resolve2();
24786
+ resolve4();
23950
24787
  }, 5000);
23951
24788
  proc.once("exit", () => {
23952
24789
  clearTimeout(forceKillTimer);
23953
24790
  log("Process exited during shutdown");
23954
- resolve2();
24791
+ resolve4();
23955
24792
  });
23956
24793
  proc.kill("SIGTERM");
23957
24794
  });
@@ -23993,19 +24830,19 @@ class BinaryBridge {
23993
24830
  })();
23994
24831
  const useFastembedBackend = semanticBackend === undefined || semanticBackend === "fastembed" || semanticBackend === "";
23995
24832
  const ortDir = typeof this.configOverrides._ort_dylib_dir === "string" && useFastembedBackend ? this.configOverrides._ort_dylib_dir : null;
23996
- const ortLibraryPath = ortDir == null ? null : join9(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
24833
+ const ortLibraryPath = ortDir == null ? null : join12(ortDir, process.platform === "win32" ? "onnxruntime.dll" : process.platform === "darwin" ? "libonnxruntime.dylib" : "libonnxruntime.so");
23997
24834
  const envPath = process.platform === "win32" && ortDir ? `${ortDir};${process.env.PATH ?? ""}` : process.env.PATH;
23998
24835
  const env = {
23999
24836
  ...process.env,
24000
24837
  ...envPath ? { PATH: envPath } : {}
24001
24838
  };
24002
24839
  if (useFastembedBackend) {
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"));
24840
+ env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join12(this.configOverrides.storage_dir, "semantic", "models") : join12(homedir7() || "", ".cache", "fastembed"));
24004
24841
  if (ortLibraryPath) {
24005
24842
  env.ORT_DYLIB_PATH = ortLibraryPath;
24006
24843
  }
24007
24844
  }
24008
- const child = spawn2(this.binaryPath, [], {
24845
+ const child = spawn3(this.binaryPath, [], {
24009
24846
  cwd: this.cwd,
24010
24847
  stdio: ["pipe", "pipe", "pipe"],
24011
24848
  env
@@ -24266,10 +25103,10 @@ function normalizeKey(projectRoot) {
24266
25103
 
24267
25104
  // src/resolver.ts
24268
25105
  import { execSync, spawnSync } from "child_process";
24269
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
25106
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
24270
25107
  import { createRequire } from "module";
24271
- import { homedir as homedir6 } from "os";
24272
- import { join as join10 } from "path";
25108
+ import { homedir as homedir8 } from "os";
25109
+ import { join as join13 } from "path";
24273
25110
  function copyToVersionedCache(npmBinaryPath) {
24274
25111
  try {
24275
25112
  const result = spawnSync(npmBinaryPath, ["--version"], {
@@ -24283,14 +25120,14 @@ function copyToVersionedCache(npmBinaryPath) {
24283
25120
  const version2 = rawVersion.replace(/^aft\s+/, "");
24284
25121
  const tag = version2.startsWith("v") ? version2 : `v${version2}`;
24285
25122
  const cacheDir = getCacheDir();
24286
- const versionedDir = join10(cacheDir, tag);
25123
+ const versionedDir = join13(cacheDir, tag);
24287
25124
  const ext = process.platform === "win32" ? ".exe" : "";
24288
- const cachedPath = join10(versionedDir, `aft${ext}`);
24289
- if (existsSync8(cachedPath))
25125
+ const cachedPath = join13(versionedDir, `aft${ext}`);
25126
+ if (existsSync9(cachedPath))
24290
25127
  return cachedPath;
24291
25128
  mkdirSync6(versionedDir, { recursive: true });
24292
25129
  const tmpPath = `${cachedPath}.tmp`;
24293
- copyFileSync2(npmBinaryPath, tmpPath);
25130
+ copyFileSync3(npmBinaryPath, tmpPath);
24294
25131
  if (process.platform !== "win32") {
24295
25132
  chmodSync3(tmpPath, 493);
24296
25133
  }
@@ -24302,14 +25139,14 @@ function copyToVersionedCache(npmBinaryPath) {
24302
25139
  return null;
24303
25140
  }
24304
25141
  }
24305
- function platformKey(platform2 = process.platform, arch = process.arch) {
24306
- const archMap = PLATFORM_ARCH_MAP[platform2];
25142
+ function platformKey(platform3 = process.platform, arch = process.arch) {
25143
+ const archMap = PLATFORM_ARCH_MAP[platform3];
24307
25144
  if (!archMap) {
24308
- throw new Error(`Unsupported platform: ${platform2} (arch: ${arch}). ` + `Supported platforms: ${Object.keys(PLATFORM_ARCH_MAP).join(", ")}`);
25145
+ throw new Error(`Unsupported platform: ${platform3} (arch: ${arch}). ` + `Supported platforms: ${Object.keys(PLATFORM_ARCH_MAP).join(", ")}`);
24309
25146
  }
24310
25147
  const key = archMap[arch];
24311
25148
  if (!key) {
24312
- throw new Error(`Unsupported architecture: ${arch} on platform ${platform2}. ` + `Supported architectures for ${platform2}: ${Object.keys(archMap).join(", ")}`);
25149
+ throw new Error(`Unsupported architecture: ${arch} on platform ${platform3}. ` + `Supported architectures for ${platform3}: ${Object.keys(archMap).join(", ")}`);
24313
25150
  }
24314
25151
  return key;
24315
25152
  }
@@ -24333,7 +25170,7 @@ function findBinarySync() {
24333
25170
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
24334
25171
  const req = createRequire(import.meta.url);
24335
25172
  const resolved = req.resolve(packageBin);
24336
- if (existsSync8(resolved)) {
25173
+ if (existsSync9(resolved)) {
24337
25174
  const copied = copyToVersionedCache(resolved);
24338
25175
  return copied ?? resolved;
24339
25176
  }
@@ -24347,8 +25184,8 @@ function findBinarySync() {
24347
25184
  if (result)
24348
25185
  return result;
24349
25186
  } catch {}
24350
- const cargoPath = join10(homedir6(), ".cargo", "bin", `aft${ext}`);
24351
- if (existsSync8(cargoPath))
25187
+ const cargoPath = join13(homedir8(), ".cargo", "bin", `aft${ext}`);
25188
+ if (existsSync9(cargoPath))
24352
25189
  return cargoPath;
24353
25190
  return null;
24354
25191
  }
@@ -24384,20 +25221,20 @@ async function findBinary() {
24384
25221
 
24385
25222
  // src/shared/rpc-server.ts
24386
25223
  import { randomBytes as randomBytes2 } from "crypto";
24387
- import { mkdirSync as mkdirSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync3 } from "fs";
25224
+ import { mkdirSync as mkdirSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
24388
25225
  import { createServer } from "http";
24389
- import { dirname as dirname2 } from "path";
25226
+ import { dirname as dirname5 } from "path";
24390
25227
 
24391
25228
  // src/shared/rpc-utils.ts
24392
- import { createHash as createHash3 } from "crypto";
24393
- import { join as join11 } from "path";
25229
+ import { createHash as createHash4 } from "crypto";
25230
+ import { join as join14 } from "path";
24394
25231
  function projectHash(directory) {
24395
25232
  const normalized = directory.replace(/\/+$/, "");
24396
- return createHash3("sha256").update(normalized).digest("hex").slice(0, 16);
25233
+ return createHash4("sha256").update(normalized).digest("hex").slice(0, 16);
24397
25234
  }
24398
25235
  function rpcPortFilePath(storageDir, directory) {
24399
25236
  const hash2 = projectHash(directory);
24400
- return join11(storageDir, "rpc", hash2, "port");
25237
+ return join14(storageDir, "rpc", hash2, "port");
24401
25238
  }
24402
25239
 
24403
25240
  // src/shared/rpc-server.ts
@@ -24414,7 +25251,7 @@ class AftRpcServer {
24414
25251
  this.handlers.set(method, handler);
24415
25252
  }
24416
25253
  async start() {
24417
- return new Promise((resolve2, reject) => {
25254
+ return new Promise((resolve4, reject) => {
24418
25255
  const server = createServer((req, res) => this.dispatch(req, res));
24419
25256
  server.on("error", (err) => {
24420
25257
  warn(`RPC server error: ${err.message}`);
@@ -24430,16 +25267,16 @@ class AftRpcServer {
24430
25267
  this.token = randomBytes2(32).toString("hex");
24431
25268
  this.server = server;
24432
25269
  try {
24433
- const dir = dirname2(this.portFilePath);
25270
+ const dir = dirname5(this.portFilePath);
24434
25271
  mkdirSync7(dir, { recursive: true });
24435
25272
  const tmpPath = `${this.portFilePath}.tmp`;
24436
- writeFileSync3(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
25273
+ writeFileSync6(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
24437
25274
  renameSync3(tmpPath, this.portFilePath);
24438
25275
  log(`RPC server listening on 127.0.0.1:${this.port}`);
24439
25276
  } catch (err) {
24440
25277
  warn(`Failed to write RPC port file: ${err}`);
24441
25278
  }
24442
- resolve2(this.port);
25279
+ resolve4(this.port);
24443
25280
  });
24444
25281
  server.unref();
24445
25282
  });
@@ -24672,22 +25509,22 @@ function formatStatusMarkdown(status) {
24672
25509
  }
24673
25510
 
24674
25511
  // src/shared/tui-config.ts
24675
- var import_comment_json2 = __toESM(require_src2(), 1);
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";
25512
+ var import_comment_json4 = __toESM(require_src2(), 1);
25513
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
25514
+ import { dirname as dirname6, join as join16 } from "path";
24678
25515
 
24679
25516
  // src/shared/opencode-config-dir.ts
24680
- import { homedir as homedir7 } from "os";
24681
- import { join as join12, resolve as resolve2 } from "path";
25517
+ import { homedir as homedir9 } from "os";
25518
+ import { join as join15, resolve as resolve4 } from "path";
24682
25519
  function getCliConfigDir() {
24683
25520
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
24684
25521
  if (envConfigDir) {
24685
- return resolve2(envConfigDir);
25522
+ return resolve4(envConfigDir);
24686
25523
  }
24687
25524
  if (process.platform === "win32") {
24688
- return join12(homedir7(), ".config", "opencode");
25525
+ return join15(homedir9(), ".config", "opencode");
24689
25526
  }
24690
- return join12(process.env.XDG_CONFIG_HOME || join12(homedir7(), ".config"), "opencode");
25527
+ return join15(process.env.XDG_CONFIG_HOME || join15(homedir9(), ".config"), "opencode");
24691
25528
  }
24692
25529
  function getOpenCodeConfigDir2(_options) {
24693
25530
  return getCliConfigDir();
@@ -24696,10 +25533,10 @@ function getOpenCodeConfigPaths(options) {
24696
25533
  const configDir = getOpenCodeConfigDir2(options);
24697
25534
  return {
24698
25535
  configDir,
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")
25536
+ configJson: join15(configDir, "opencode.json"),
25537
+ configJsonc: join15(configDir, "opencode.jsonc"),
25538
+ packageJson: join15(configDir, "package.json"),
25539
+ omoConfig: join15(configDir, "magic-context.jsonc")
24703
25540
  };
24704
25541
  }
24705
25542
 
@@ -24708,11 +25545,11 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
24708
25545
  var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
24709
25546
  function resolveTuiConfigPath() {
24710
25547
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
24711
- const jsoncPath = join13(configDir, "tui.jsonc");
24712
- const jsonPath = join13(configDir, "tui.json");
24713
- if (existsSync9(jsoncPath))
25548
+ const jsoncPath = join16(configDir, "tui.jsonc");
25549
+ const jsonPath = join16(configDir, "tui.json");
25550
+ if (existsSync10(jsoncPath))
24714
25551
  return jsoncPath;
24715
- if (existsSync9(jsonPath))
25552
+ if (existsSync10(jsonPath))
24716
25553
  return jsonPath;
24717
25554
  return jsonPath;
24718
25555
  }
@@ -24720,8 +25557,8 @@ function ensureTuiPluginEntry() {
24720
25557
  try {
24721
25558
  const configPath = resolveTuiConfigPath();
24722
25559
  let config2 = {};
24723
- if (existsSync9(configPath)) {
24724
- config2 = import_comment_json2.parse(readFileSync4(configPath, "utf-8")) ?? {};
25560
+ if (existsSync10(configPath)) {
25561
+ config2 = import_comment_json4.parse(readFileSync7(configPath, "utf-8")) ?? {};
24725
25562
  }
24726
25563
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
24727
25564
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -24729,8 +25566,8 @@ function ensureTuiPluginEntry() {
24729
25566
  }
24730
25567
  plugins.push(PLUGIN_ENTRY);
24731
25568
  config2.plugin = plugins;
24732
- mkdirSync8(dirname3(configPath), { recursive: true });
24733
- writeFileSync4(configPath, `${import_comment_json2.stringify(config2, null, 2)}
25569
+ mkdirSync8(dirname6(configPath), { recursive: true });
25570
+ writeFileSync7(configPath, `${import_comment_json4.stringify(config2, null, 2)}
24734
25571
  `);
24735
25572
  log(`[aft-plugin] added TUI plugin entry to ${configPath}`);
24736
25573
  return true;
@@ -24741,33 +25578,33 @@ function ensureTuiPluginEntry() {
24741
25578
  }
24742
25579
 
24743
25580
  // src/shared/url-fetch.ts
24744
- import { createHash as createHash4 } from "crypto";
25581
+ import { createHash as createHash5 } from "crypto";
24745
25582
  import { lookup } from "dns/promises";
24746
25583
  import {
24747
- existsSync as existsSync10,
25584
+ existsSync as existsSync11,
24748
25585
  mkdirSync as mkdirSync9,
24749
25586
  readdirSync as readdirSync4,
24750
- readFileSync as readFileSync5,
25587
+ readFileSync as readFileSync8,
24751
25588
  unlinkSync as unlinkSync6,
24752
- writeFileSync as writeFileSync5
25589
+ writeFileSync as writeFileSync8
24753
25590
  } from "fs";
24754
25591
  import { isIP } from "net";
24755
- import { join as join14 } from "path";
25592
+ import { join as join17 } from "path";
24756
25593
  var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
24757
25594
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
24758
25595
  var FETCH_TIMEOUT_MS = 30000;
24759
25596
  var MAX_REDIRECTS = 5;
24760
25597
  function cacheDir(storageDir) {
24761
- return join14(storageDir, "url_cache");
25598
+ return join17(storageDir, "url_cache");
24762
25599
  }
24763
25600
  function hashUrl(url2) {
24764
- return createHash4("sha256").update(url2).digest("hex").slice(0, 16);
25601
+ return createHash5("sha256").update(url2).digest("hex").slice(0, 16);
24765
25602
  }
24766
25603
  function metaPath(storageDir, hash2) {
24767
- return join14(cacheDir(storageDir), `${hash2}.meta.json`);
25604
+ return join17(cacheDir(storageDir), `${hash2}.meta.json`);
24768
25605
  }
24769
25606
  function contentPath(storageDir, hash2, extension) {
24770
- return join14(cacheDir(storageDir), `${hash2}${extension}`);
25607
+ return join17(cacheDir(storageDir), `${hash2}${extension}`);
24771
25608
  }
24772
25609
  function _isPrivateIpv4(address) {
24773
25610
  const parts = address.split(".").map((part) => Number.parseInt(part, 10));
@@ -24929,12 +25766,12 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
24929
25766
  mkdirSync9(dir, { recursive: true });
24930
25767
  const hash2 = hashUrl(url2);
24931
25768
  const metaFile = metaPath(storageDir, hash2);
24932
- if (existsSync10(metaFile)) {
25769
+ if (existsSync11(metaFile)) {
24933
25770
  try {
24934
- const meta4 = JSON.parse(readFileSync5(metaFile, "utf8"));
25771
+ const meta4 = JSON.parse(readFileSync8(metaFile, "utf8"));
24935
25772
  const age = Date.now() - meta4.fetchedAt;
24936
25773
  const cached2 = contentPath(storageDir, hash2, meta4.extension);
24937
- if (age < CACHE_TTL_MS && existsSync10(cached2)) {
25774
+ if (age < CACHE_TTL_MS && existsSync11(cached2)) {
24938
25775
  log(`URL cache hit: ${url2} (${Math.round(age / 1000)}s old)`);
24939
25776
  return cached2;
24940
25777
  }
@@ -24979,7 +25816,7 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
24979
25816
  const body = Buffer.concat(chunks);
24980
25817
  const contentFile = contentPath(storageDir, hash2, extension);
24981
25818
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
24982
- writeFileSync5(tmpContent, body);
25819
+ writeFileSync8(tmpContent, body);
24983
25820
  const { renameSync: renameSync4 } = await import("fs");
24984
25821
  renameSync4(tmpContent, contentFile);
24985
25822
  const meta3 = {
@@ -24989,28 +25826,28 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
24989
25826
  fetchedAt: Date.now()
24990
25827
  };
24991
25828
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
24992
- writeFileSync5(tmpMeta, JSON.stringify(meta3));
25829
+ writeFileSync8(tmpMeta, JSON.stringify(meta3));
24993
25830
  renameSync4(tmpMeta, metaFile);
24994
25831
  log(`URL cached (${total} bytes): ${url2}`);
24995
25832
  return contentFile;
24996
25833
  }
24997
25834
  function cleanupUrlCache(storageDir) {
24998
25835
  const dir = cacheDir(storageDir);
24999
- if (!existsSync10(dir))
25836
+ if (!existsSync11(dir))
25000
25837
  return;
25001
25838
  let removed = 0;
25002
25839
  try {
25003
25840
  for (const entry of readdirSync4(dir)) {
25004
25841
  if (!entry.endsWith(".meta.json"))
25005
25842
  continue;
25006
- const metaFile = join14(dir, entry);
25843
+ const metaFile = join17(dir, entry);
25007
25844
  try {
25008
- const meta3 = JSON.parse(readFileSync5(metaFile, "utf8"));
25845
+ const meta3 = JSON.parse(readFileSync8(metaFile, "utf8"));
25009
25846
  const age = Date.now() - meta3.fetchedAt;
25010
25847
  if (age > CACHE_TTL_MS) {
25011
25848
  const hash2 = entry.slice(0, -".meta.json".length);
25012
25849
  const content = contentPath(storageDir, hash2, meta3.extension);
25013
- if (existsSync10(content))
25850
+ if (existsSync11(content))
25014
25851
  unlinkSync6(content);
25015
25852
  unlinkSync6(metaFile);
25016
25853
  removed++;
@@ -26056,11 +26893,11 @@ LSP errors detected, please fix:
26056
26893
  };
26057
26894
  }
26058
26895
  function getEditDescription(writeToolName) {
26059
- return `Edit a file by finding and replacing text, or by targeting named symbols.
26896
+ return `Edit a file by finding and replacing text, or by targeting named symbols. To write or overwrite a whole file, use the \`${writeToolName}\` tool \u2014 \`edit\` requires an explicit edit mode and will not silently overwrite a file from \`content\` alone.
26060
26897
 
26061
26898
  **Modes** (determined by which parameters you provide):
26062
26899
 
26063
- Mode priority: operations > edits > symbol (without oldString) > oldString (find/replace) > content-only (${writeToolName})
26900
+ Mode priority: operations > edits > symbol (without oldString) > oldString (find/replace). If none match, the call is rejected \u2014 there is no implicit "write" fallback.
26064
26901
 
26065
26902
  1. **Multi-file transaction** \u2014 pass \`operations\` array
26066
26903
  Edits across multiple files with checkpoint-based rollback on failure.
@@ -26123,6 +26960,10 @@ function createEditTool(ctx, writeToolName = "write") {
26123
26960
  dryRun: z3.boolean().optional().describe("Preview changes without applying (returns diff, default: false)")
26124
26961
  },
26125
26962
  execute: async (args, context) => {
26963
+ const argsRecord = args;
26964
+ if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
26965
+ throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. For line-range edits, nest them inside the `edits` array: `edits: [{ startLine: N, endLine: M, content: \"...\" }]`. For find/replace, use `oldString`/`newString` instead.");
26966
+ }
26126
26967
  if (Array.isArray(args.operations)) {
26127
26968
  const ops = args.operations;
26128
26969
  const files = ops.map((op) => op.file).filter(Boolean);
@@ -26185,12 +27026,9 @@ function createEditTool(ctx, writeToolName = "write") {
26185
27026
  params.replace_all = args.replaceAll;
26186
27027
  if (args.occurrence !== undefined)
26187
27028
  params.occurrence = args.occurrence;
26188
- } else if (typeof args.content === "string") {
26189
- command = "write";
26190
- params.content = args.content;
26191
- params.create_dirs = true;
26192
27029
  } else {
26193
- throw new Error("Provide 'oldString' + 'newString', 'symbol' + 'content', 'edits' array, or 'content' for write");
27030
+ const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content', 'edits' array, or 'operations' array.";
27031
+ throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
26194
27032
  }
26195
27033
  if (args.dryRun)
26196
27034
  params.dry_run = true;
@@ -26590,7 +27428,29 @@ function aftPrefixedTools(ctx) {
26590
27428
  ...aftEditTool,
26591
27429
  execute: async (args, context) => {
26592
27430
  const argRecord = args;
26593
- const normalizedArgs = argRecord.mode !== undefined && argRecord.filePath === undefined && typeof argRecord.file === "string" ? { ...argRecord, filePath: argRecord.file } : argRecord;
27431
+ const normalizedArgs = argRecord.mode !== undefined && argRecord.filePath === undefined && typeof argRecord.file === "string" ? { ...argRecord, filePath: argRecord.file } : { ...argRecord };
27432
+ if (normalizedArgs.mode === "write" && typeof normalizedArgs.filePath === "string" && typeof normalizedArgs.content === "string") {
27433
+ const file2 = normalizedArgs.filePath;
27434
+ const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
27435
+ const relPath = path4.relative(context.worktree, filePath);
27436
+ await context.ask({
27437
+ permission: "edit",
27438
+ patterns: [relPath],
27439
+ always: ["*"],
27440
+ metadata: { filepath: filePath }
27441
+ });
27442
+ const writeParams = {
27443
+ file: filePath,
27444
+ content: normalizedArgs.content,
27445
+ create_dirs: normalizedArgs.create_dirs !== false,
27446
+ diagnostics: true
27447
+ };
27448
+ if (normalizedArgs.dryRun === true || normalizedArgs.dry_run === true) {
27449
+ writeParams.dry_run = true;
27450
+ }
27451
+ const data = await callBridge(ctx, context, "write", writeParams);
27452
+ return JSON.stringify(data);
27453
+ }
26594
27454
  return aftEditTool.execute(normalizedArgs, context);
26595
27455
  }
26596
27456
  },
@@ -26692,7 +27552,8 @@ function lspTools(ctx) {
26692
27552
  ` + `}
26693
27553
  ` + `
26694
27554
  ` + `**Reading the response honestly:**
26695
- ` + "- `total: 0, complete: true, lsp_servers_used: [{status: 'pull_ok'}]` \u2192 file is genuinely clean.\n" + "- `total: 0, lsp_servers_used: []` \u2192 no server registered for this extension; nothing was checked.\n" + "- `lsp_servers_used: [{status: 'binary_not_installed: bash-language-server'}]` \u2192 install the server to get diagnostics.\n" + "- `complete: false` (directory mode) \u2192 some files in the directory weren't checked; see `unchecked_files`.",
27555
+ ` + "- `total: 0, complete: true, lsp_servers_used: [{status: 'pull_ok'}]` \u2192 file is genuinely clean.\n" + "- `total: 0, lsp_servers_used: []` \u2192 **nothing was checked** (no server registered for this extension). Tell the user, don't claim 'no errors'.\n" + "- `lsp_servers_used: [{status: 'binary_not_installed: <name>'}]` \u2192 server matched the extension but its binary isn't on PATH. Tell the user to install it.\n" + "- `lsp_servers_used: [{status: 'no_root_marker (...)'}]` \u2192 server is registered but couldn't find a workspace root marker walking up from this file. The user's project layout doesn't match what the server expects.\n" + "- `complete: false` (directory mode) \u2192 some files in the directory weren't checked; see `unchecked_files`.\n" + `
27556
+ ` + "**When this tool gives an unhelpful answer**, run `bunx --bun @cortexkit/aft doctor lsp <filePath>` from a terminal to get a full per-server breakdown (registered servers, binary resolution, root-marker resolution, spawn outcome).",
26696
27557
  args: {
26697
27558
  filePath: z5.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
26698
27559
  directory: z5.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
@@ -26776,7 +27637,7 @@ function navigationTools(ctx) {
26776
27637
 
26777
27638
  // src/tools/reading.ts
26778
27639
  import { readdir } from "fs/promises";
26779
- import { extname as extname2, join as join15, resolve as resolve6 } from "path";
27640
+ import { extname as extname2, join as join18, resolve as resolve8 } from "path";
26780
27641
  import { tool as tool7 } from "@opencode-ai/plugin";
26781
27642
  var OUTLINE_EXTENSIONS = new Set([
26782
27643
  ".ts",
@@ -26854,7 +27715,7 @@ function readingTools(ctx) {
26854
27715
  if (!dirArg && typeof args.filePath === "string" && !Array.isArray(args.files)) {
26855
27716
  try {
26856
27717
  const { stat } = await import("fs/promises");
26857
- const resolved = resolve6(context.directory, args.filePath);
27718
+ const resolved = resolve8(context.directory, args.filePath);
26858
27719
  const st = await stat(resolved);
26859
27720
  if (st.isDirectory()) {
26860
27721
  dirArg = args.filePath;
@@ -26862,7 +27723,7 @@ function readingTools(ctx) {
26862
27723
  } catch {}
26863
27724
  }
26864
27725
  if (dirArg) {
26865
- const dirPath = resolve6(context.directory, dirArg);
27726
+ const dirPath = resolve8(context.directory, dirArg);
26866
27727
  const files = await discoverSourceFiles(dirPath);
26867
27728
  if (files.length === 0) {
26868
27729
  return JSON.stringify({
@@ -26970,12 +27831,12 @@ async function discoverSourceFiles(dir, maxFiles = 200) {
26970
27831
  return;
26971
27832
  if (entry.isDirectory()) {
26972
27833
  if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
26973
- await walk(join15(current, entry.name));
27834
+ await walk(join18(current, entry.name));
26974
27835
  }
26975
27836
  } else if (entry.isFile()) {
26976
27837
  const ext = extname2(entry.name).toLowerCase();
26977
27838
  if (OUTLINE_EXTENSIONS.has(ext)) {
26978
- files.push(join15(current, entry.name));
27839
+ files.push(join18(current, entry.name));
26979
27840
  }
26980
27841
  }
26981
27842
  }
@@ -27548,6 +28409,7 @@ var ANNOUNCEMENT_FEATURES = [];
27548
28409
  var plugin = async (input) => {
27549
28410
  const binaryPath = await findBinary();
27550
28411
  const aftConfig = loadAftConfig(input.directory);
28412
+ const autoUpdateAbort = new AbortController;
27551
28413
  const configOverrides = {};
27552
28414
  if (aftConfig.format_on_edit !== undefined)
27553
28415
  configOverrides.format_on_edit = aftConfig.format_on_edit;
@@ -27568,8 +28430,8 @@ var plugin = async (input) => {
27568
28430
  if (aftConfig.max_callgraph_files !== undefined)
27569
28431
  configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
27570
28432
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
27571
- const dataHome = process.env.XDG_DATA_HOME || join16(homedir8(), ".local", "share");
27572
- configOverrides.storage_dir = join16(dataHome, "opencode", "storage", "plugin", "aft");
28433
+ const dataHome = process.env.XDG_DATA_HOME || join19(homedir10(), ".local", "share");
28434
+ configOverrides.storage_dir = join19(dataHome, "opencode", "storage", "plugin", "aft");
27573
28435
  if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend) {
27574
28436
  const storageDir2 = configOverrides.storage_dir;
27575
28437
  const ortDylibDir = await ensureOnnxRuntime(storageDir2).catch((err) => {
@@ -27703,10 +28565,10 @@ ${lines}
27703
28565
  return { show: false };
27704
28566
  }
27705
28567
  if (storageDir) {
27706
- const versionFile = join16(storageDir, "last_announced_version");
28568
+ const versionFile = join19(storageDir, "last_announced_version");
27707
28569
  try {
27708
- if (existsSync12(versionFile)) {
27709
- const lastVersion = readFileSync6(versionFile, "utf-8").trim();
28570
+ if (existsSync13(versionFile)) {
28571
+ const lastVersion = readFileSync9(versionFile, "utf-8").trim();
27710
28572
  if (lastVersion === ANNOUNCEMENT_VERSION)
27711
28573
  return { show: false };
27712
28574
  }
@@ -27718,7 +28580,7 @@ ${lines}
27718
28580
  if (storageDir && ANNOUNCEMENT_VERSION) {
27719
28581
  try {
27720
28582
  mkdirSync10(storageDir, { recursive: true });
27721
- writeFileSync6(join16(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
28583
+ writeFileSync9(join19(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
27722
28584
  } catch {}
27723
28585
  }
27724
28586
  return { success: true };
@@ -27801,6 +28663,11 @@ Install: ${getManualInstallHint()}`).catch(() => {});
27801
28663
  }
27802
28664
  return {
27803
28665
  tool: allTools,
28666
+ event: createAutoUpdateCheckerHook(input, {
28667
+ enabled: true,
28668
+ autoUpdate: aftConfig.auto_update ?? true,
28669
+ signal: autoUpdateAbort.signal
28670
+ }),
27804
28671
  "command.execute.before": async (commandInput, _output) => {
27805
28672
  if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
27806
28673
  return;
@@ -27849,6 +28716,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
27849
28716
  };
27850
28717
  },
27851
28718
  dispose: async () => {
28719
+ autoUpdateAbort.abort();
27852
28720
  unregisterShutdown();
27853
28721
  await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
27854
28722
  rpcServer.stop();