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