@cortexkit/aft-opencode 0.54.0 → 0.55.1
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/bash-wait-detach.d.ts.map +1 -1
- package/dist/bg-notifications.d.ts +1 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/constants.d.ts +15 -3
- package/dist/hooks/auto-update-checker/constants.d.ts.map +1 -1
- package/dist/index.js +719 -219
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-cache.d.ts +9 -6
- package/dist/lsp-cache.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -8842,6 +8842,21 @@ class BinaryBridge {
|
|
|
8842
8842
|
}
|
|
8843
8843
|
return this.sendWithVersionMismatchRetry(command, dispatchParams, options, true);
|
|
8844
8844
|
}
|
|
8845
|
+
listenForRequestAbort(requestId, signal) {
|
|
8846
|
+
if (!signal)
|
|
8847
|
+
return () => {};
|
|
8848
|
+
let fired = false;
|
|
8849
|
+
const onAbort = () => {
|
|
8850
|
+
if (fired)
|
|
8851
|
+
return;
|
|
8852
|
+
fired = true;
|
|
8853
|
+
this.send("cancel_request", { id: requestId }, { keepBridgeOnTimeout: true }).catch(() => {});
|
|
8854
|
+
};
|
|
8855
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
8856
|
+
if (signal.aborted)
|
|
8857
|
+
onAbort();
|
|
8858
|
+
return () => signal.removeEventListener("abort", onAbort);
|
|
8859
|
+
}
|
|
8845
8860
|
async toolCall(sessionId, name, rawArgs = {}, options) {
|
|
8846
8861
|
const params = { name, arguments: rawArgs };
|
|
8847
8862
|
if (sessionId)
|
|
@@ -8864,7 +8879,7 @@ class BinaryBridge {
|
|
|
8864
8879
|
if (this._shuttingDown) {
|
|
8865
8880
|
throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
|
|
8866
8881
|
}
|
|
8867
|
-
if (Object.hasOwn(params, "id")) {
|
|
8882
|
+
if (Object.hasOwn(params, "id") && command !== "cancel_request") {
|
|
8868
8883
|
throw new Error("params cannot contain reserved key 'id'");
|
|
8869
8884
|
}
|
|
8870
8885
|
const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
|
|
@@ -8918,7 +8933,7 @@ class BinaryBridge {
|
|
|
8918
8933
|
}
|
|
8919
8934
|
const id = String(this.nextId++);
|
|
8920
8935
|
let request;
|
|
8921
|
-
if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
8936
|
+
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
8922
8937
|
const nested = { ...params };
|
|
8923
8938
|
const reserved = {};
|
|
8924
8939
|
for (const key of ["session_id", "lsp_hints"]) {
|
|
@@ -8938,11 +8953,12 @@ class BinaryBridge {
|
|
|
8938
8953
|
const child = this.process;
|
|
8939
8954
|
const response = await new Promise((resolve3, reject) => {
|
|
8940
8955
|
const timer = setTimeout(() => {
|
|
8941
|
-
const
|
|
8942
|
-
if (!
|
|
8956
|
+
const entry2 = this.pending.get(id);
|
|
8957
|
+
if (!entry2)
|
|
8943
8958
|
return;
|
|
8944
8959
|
this.pending.delete(id);
|
|
8945
|
-
clearTimeout(
|
|
8960
|
+
clearTimeout(entry2.timer);
|
|
8961
|
+
entry2.onSettled?.();
|
|
8946
8962
|
if (keepBridgeOnTimeout) {
|
|
8947
8963
|
const timeoutMsg2 = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`;
|
|
8948
8964
|
if (requestSessionId) {
|
|
@@ -8950,7 +8966,7 @@ class BinaryBridge {
|
|
|
8950
8966
|
} else {
|
|
8951
8967
|
this.warnVia(timeoutMsg2);
|
|
8952
8968
|
}
|
|
8953
|
-
|
|
8969
|
+
entry2.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
8954
8970
|
return;
|
|
8955
8971
|
}
|
|
8956
8972
|
const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
|
|
@@ -8965,13 +8981,20 @@ class BinaryBridge {
|
|
|
8965
8981
|
this.warnVia(timeoutMsg);
|
|
8966
8982
|
}
|
|
8967
8983
|
if (keepWarm) {
|
|
8968
|
-
|
|
8984
|
+
entry2.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
|
|
8969
8985
|
return;
|
|
8970
8986
|
}
|
|
8971
|
-
|
|
8987
|
+
entry2.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
8972
8988
|
this.handleTimeout(requestSessionId);
|
|
8973
8989
|
}, effectiveTimeoutMs);
|
|
8974
|
-
|
|
8990
|
+
const entry = {
|
|
8991
|
+
resolve: resolve3,
|
|
8992
|
+
reject,
|
|
8993
|
+
timer,
|
|
8994
|
+
onProgress: options?.onProgress,
|
|
8995
|
+
command
|
|
8996
|
+
};
|
|
8997
|
+
this.pending.set(id, entry);
|
|
8975
8998
|
if (!child?.stdin?.writable) {
|
|
8976
8999
|
this.pending.delete(id);
|
|
8977
9000
|
clearTimeout(timer);
|
|
@@ -8985,11 +9008,12 @@ class BinaryBridge {
|
|
|
8985
9008
|
const handleWriteFailure = (cause) => {
|
|
8986
9009
|
const writeError = cause instanceof Error ? cause : new Error(String(cause));
|
|
8987
9010
|
const error2 = new BridgeTransportUnknownOutcomeError(`${this.errorPrefix} Failed to write to stdin: ${writeError.message}`, { cause: writeError });
|
|
8988
|
-
const
|
|
8989
|
-
if (
|
|
9011
|
+
const entry2 = this.pending.get(id);
|
|
9012
|
+
if (entry2) {
|
|
8990
9013
|
this.pending.delete(id);
|
|
8991
|
-
clearTimeout(
|
|
8992
|
-
|
|
9014
|
+
clearTimeout(entry2.timer);
|
|
9015
|
+
entry2.onSettled?.();
|
|
9016
|
+
entry2.reject(error2);
|
|
8993
9017
|
}
|
|
8994
9018
|
if (this.process === child)
|
|
8995
9019
|
this.invalidateTransportProcess(error2);
|
|
@@ -9002,6 +9026,9 @@ class BinaryBridge {
|
|
|
9002
9026
|
} catch (err) {
|
|
9003
9027
|
handleWriteFailure(err);
|
|
9004
9028
|
}
|
|
9029
|
+
if (this.pending.has(id)) {
|
|
9030
|
+
entry.onSettled = this.listenForRequestAbort(id, options?.abortSignal);
|
|
9031
|
+
}
|
|
9005
9032
|
});
|
|
9006
9033
|
if (command === "configure" && response.success === true && options?.markConfiguredOnSuccess !== false) {
|
|
9007
9034
|
this.configured = true;
|
|
@@ -9370,6 +9397,7 @@ class BinaryBridge {
|
|
|
9370
9397
|
if (requestId && entry) {
|
|
9371
9398
|
this.pending.delete(requestId);
|
|
9372
9399
|
clearTimeout(entry.timer);
|
|
9400
|
+
entry.onSettled?.();
|
|
9373
9401
|
entry.resolve({
|
|
9374
9402
|
success: false,
|
|
9375
9403
|
code: "permission_required",
|
|
@@ -9411,6 +9439,7 @@ class BinaryBridge {
|
|
|
9411
9439
|
return;
|
|
9412
9440
|
this.pending.delete(id);
|
|
9413
9441
|
clearTimeout(entry.timer);
|
|
9442
|
+
entry.onSettled?.();
|
|
9414
9443
|
this.consecutiveRequestTimeouts = 0;
|
|
9415
9444
|
this.scheduleRestartCountReset();
|
|
9416
9445
|
this.accountForBashTaskResponse(entry.command, response);
|
|
@@ -9501,6 +9530,7 @@ class BinaryBridge {
|
|
|
9501
9530
|
rejectAllPending(error2) {
|
|
9502
9531
|
for (const [_id, entry] of this.pending) {
|
|
9503
9532
|
clearTimeout(entry.timer);
|
|
9533
|
+
entry.onSettled?.();
|
|
9504
9534
|
entry.reject(error2);
|
|
9505
9535
|
}
|
|
9506
9536
|
this.pending.clear();
|
|
@@ -9525,6 +9555,12 @@ import { join as join5 } from "node:path";
|
|
|
9525
9555
|
function homeDir2(env) {
|
|
9526
9556
|
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
|
|
9527
9557
|
}
|
|
9558
|
+
function getOpenCodeCacheRoot(env = process.env, home = homedir4()) {
|
|
9559
|
+
return join5(env.XDG_CACHE_HOME || join5(home, ".cache"), "opencode");
|
|
9560
|
+
}
|
|
9561
|
+
function getOpenCodeConfigRoot(env = process.env, home = homedir4()) {
|
|
9562
|
+
return join5(env.XDG_CONFIG_HOME || join5(home, ".config"), "opencode");
|
|
9563
|
+
}
|
|
9528
9564
|
function getAftCacheRoot(env = process.env) {
|
|
9529
9565
|
if (env.AFT_CACHE_DIR)
|
|
9530
9566
|
return env.AFT_CACHE_DIR;
|
|
@@ -14284,6 +14320,7 @@ async function ensureStorageMigrated(opts) {
|
|
|
14284
14320
|
throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
|
|
14285
14321
|
}
|
|
14286
14322
|
// ../aft-bridge/dist/npm-resolver.js
|
|
14323
|
+
import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
|
|
14287
14324
|
import { readdirSync as readdirSync2, statSync as statSync5 } from "node:fs";
|
|
14288
14325
|
import { homedir as homedir8 } from "node:os";
|
|
14289
14326
|
import { delimiter as delimiter2, dirname as dirname4, isAbsolute as isAbsolute3, join as join10 } from "node:path";
|
|
@@ -14390,6 +14427,189 @@ function resolveNpm(deps = defaultDeps()) {
|
|
|
14390
14427
|
}
|
|
14391
14428
|
return null;
|
|
14392
14429
|
}
|
|
14430
|
+
function quoteCmdArgument(value) {
|
|
14431
|
+
if (/[\0\r\n"%]/.test(value)) {
|
|
14432
|
+
throw new Error(`npm argument cannot be represented safely for cmd.exe: ${JSON.stringify(value)}`);
|
|
14433
|
+
}
|
|
14434
|
+
return `"${value}"`;
|
|
14435
|
+
}
|
|
14436
|
+
function npmInvocation(resolved, npmArgs, platform = process.platform, env = process.env) {
|
|
14437
|
+
if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(resolved.command)) {
|
|
14438
|
+
return { command: resolved.command, args: [...npmArgs] };
|
|
14439
|
+
}
|
|
14440
|
+
if (/[\0\r\n"]/.test(resolved.command)) {
|
|
14441
|
+
throw new Error(`npm command cannot be represented safely for cmd.exe: ${JSON.stringify(resolved.command)}`);
|
|
14442
|
+
}
|
|
14443
|
+
const commandEnvName = "AFT_NPM_COMMAND";
|
|
14444
|
+
const quotedArgs = npmArgs.map(quoteCmdArgument);
|
|
14445
|
+
const commandLine = `""%${commandEnvName}%"${quotedArgs.length > 0 ? ` ${quotedArgs.join(" ")}` : ""}"`;
|
|
14446
|
+
return {
|
|
14447
|
+
command: env.ComSpec ?? env.COMSPEC ?? "cmd.exe",
|
|
14448
|
+
args: ["/d", "/s", "/v:off", "/c", commandLine],
|
|
14449
|
+
env: { [commandEnvName]: resolved.command },
|
|
14450
|
+
windowsVerbatimArguments: true,
|
|
14451
|
+
windowsCmdShim: true
|
|
14452
|
+
};
|
|
14453
|
+
}
|
|
14454
|
+
|
|
14455
|
+
class NpmTerminationUnknownError extends Error {
|
|
14456
|
+
code = "npm_termination_unknown";
|
|
14457
|
+
constructor(detail) {
|
|
14458
|
+
super(`npm process-tree termination could not be confirmed: ${detail}`);
|
|
14459
|
+
this.name = "NpmTerminationUnknownError";
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
function terminateDirectNpmChild(child, gracePeriodMs) {
|
|
14463
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
14464
|
+
return Promise.resolve();
|
|
14465
|
+
return new Promise((resolve7, reject) => {
|
|
14466
|
+
let settled = false;
|
|
14467
|
+
let forceTimer = null;
|
|
14468
|
+
let confirmationTimer = null;
|
|
14469
|
+
let signalFailure = null;
|
|
14470
|
+
const cleanup = () => {
|
|
14471
|
+
if (forceTimer)
|
|
14472
|
+
clearTimeout(forceTimer);
|
|
14473
|
+
if (confirmationTimer)
|
|
14474
|
+
clearTimeout(confirmationTimer);
|
|
14475
|
+
child.removeListener("exit", finish);
|
|
14476
|
+
child.removeListener("error", onChildError);
|
|
14477
|
+
};
|
|
14478
|
+
const finish = () => {
|
|
14479
|
+
if (settled)
|
|
14480
|
+
return;
|
|
14481
|
+
settled = true;
|
|
14482
|
+
cleanup();
|
|
14483
|
+
resolve7();
|
|
14484
|
+
};
|
|
14485
|
+
const fail = () => {
|
|
14486
|
+
if (settled)
|
|
14487
|
+
return;
|
|
14488
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
14489
|
+
finish();
|
|
14490
|
+
return;
|
|
14491
|
+
}
|
|
14492
|
+
settled = true;
|
|
14493
|
+
cleanup();
|
|
14494
|
+
reject(new NpmTerminationUnknownError(signalFailure ?? `direct npm child did not exit after SIGKILL within ${gracePeriodMs}ms`));
|
|
14495
|
+
};
|
|
14496
|
+
const onChildError = (error2) => {
|
|
14497
|
+
signalFailure = `direct npm child error during termination: ${String(error2)}`;
|
|
14498
|
+
};
|
|
14499
|
+
const signal = (value) => {
|
|
14500
|
+
try {
|
|
14501
|
+
if (!child.kill(value)) {
|
|
14502
|
+
signalFailure = `direct npm child rejected ${value ?? "SIGTERM"}`;
|
|
14503
|
+
}
|
|
14504
|
+
} catch (error2) {
|
|
14505
|
+
signalFailure = `direct npm child ${value ?? "SIGTERM"} failed: ${String(error2)}`;
|
|
14506
|
+
}
|
|
14507
|
+
};
|
|
14508
|
+
child.once("exit", finish);
|
|
14509
|
+
child.on("error", onChildError);
|
|
14510
|
+
forceTimer = setTimeout(() => {
|
|
14511
|
+
signal("SIGKILL");
|
|
14512
|
+
confirmationTimer = setTimeout(fail, gracePeriodMs);
|
|
14513
|
+
}, gracePeriodMs);
|
|
14514
|
+
signal();
|
|
14515
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
14516
|
+
finish();
|
|
14517
|
+
});
|
|
14518
|
+
}
|
|
14519
|
+
function terminateNpmProcessTree(child, invocation, env = process.env, gracePeriodMs = 5000) {
|
|
14520
|
+
if (!invocation.windowsCmdShim)
|
|
14521
|
+
return terminateDirectNpmChild(child, gracePeriodMs);
|
|
14522
|
+
const exitedSuccessfully = () => child.exitCode === 0 && child.signalCode === null;
|
|
14523
|
+
if (exitedSuccessfully())
|
|
14524
|
+
return Promise.resolve();
|
|
14525
|
+
if (child.pid === undefined) {
|
|
14526
|
+
return Promise.reject(new NpmTerminationUnknownError("cmd.exe child has no process ID"));
|
|
14527
|
+
}
|
|
14528
|
+
return new Promise((resolve7, reject) => {
|
|
14529
|
+
let settled = false;
|
|
14530
|
+
let childExited = child.exitCode !== null || child.signalCode !== null;
|
|
14531
|
+
let treeKillConfirmed = false;
|
|
14532
|
+
let treeKillFailure = null;
|
|
14533
|
+
let killer = null;
|
|
14534
|
+
const timeout = setTimeout(() => {
|
|
14535
|
+
try {
|
|
14536
|
+
killer?.kill();
|
|
14537
|
+
} catch {}
|
|
14538
|
+
if (treeKillConfirmed || exitedSuccessfully()) {
|
|
14539
|
+
settled = true;
|
|
14540
|
+
cleanup();
|
|
14541
|
+
resolve7();
|
|
14542
|
+
return;
|
|
14543
|
+
}
|
|
14544
|
+
fail(treeKillFailure ?? `taskkill.exe did not finish within ${gracePeriodMs}ms`);
|
|
14545
|
+
}, gracePeriodMs);
|
|
14546
|
+
const cleanup = () => {
|
|
14547
|
+
clearTimeout(timeout);
|
|
14548
|
+
child.removeListener("exit", onChildExit);
|
|
14549
|
+
};
|
|
14550
|
+
const succeedIfConfirmed = () => {
|
|
14551
|
+
if (settled || !childExited || !treeKillConfirmed)
|
|
14552
|
+
return;
|
|
14553
|
+
settled = true;
|
|
14554
|
+
cleanup();
|
|
14555
|
+
resolve7();
|
|
14556
|
+
};
|
|
14557
|
+
const fail = (detail) => {
|
|
14558
|
+
if (settled)
|
|
14559
|
+
return;
|
|
14560
|
+
settled = true;
|
|
14561
|
+
cleanup();
|
|
14562
|
+
reject(new NpmTerminationUnknownError(detail));
|
|
14563
|
+
};
|
|
14564
|
+
function onChildExit() {
|
|
14565
|
+
childExited = true;
|
|
14566
|
+
if (treeKillFailure !== null) {
|
|
14567
|
+
if (exitedSuccessfully()) {
|
|
14568
|
+
settled = true;
|
|
14569
|
+
cleanup();
|
|
14570
|
+
resolve7();
|
|
14571
|
+
} else {
|
|
14572
|
+
fail(treeKillFailure);
|
|
14573
|
+
}
|
|
14574
|
+
return;
|
|
14575
|
+
}
|
|
14576
|
+
succeedIfConfirmed();
|
|
14577
|
+
}
|
|
14578
|
+
const recordTreeKillFailure = (detail) => {
|
|
14579
|
+
if (settled)
|
|
14580
|
+
return;
|
|
14581
|
+
if (exitedSuccessfully()) {
|
|
14582
|
+
settled = true;
|
|
14583
|
+
cleanup();
|
|
14584
|
+
resolve7();
|
|
14585
|
+
return;
|
|
14586
|
+
}
|
|
14587
|
+
treeKillFailure = detail;
|
|
14588
|
+
if (childExited)
|
|
14589
|
+
fail(detail);
|
|
14590
|
+
};
|
|
14591
|
+
child.once("exit", onChildExit);
|
|
14592
|
+
const systemRoot = env.SystemRoot ?? env.SYSTEMROOT;
|
|
14593
|
+
const taskkill = systemRoot ? join10(systemRoot, "System32", "taskkill.exe") : "taskkill.exe";
|
|
14594
|
+
try {
|
|
14595
|
+
killer = spawn3(taskkill, ["/pid", String(child.pid), "/t", "/f"], {
|
|
14596
|
+
stdio: "ignore",
|
|
14597
|
+
windowsHide: true
|
|
14598
|
+
});
|
|
14599
|
+
killer.once("error", (error2) => recordTreeKillFailure(`taskkill.exe failed to start: ${String(error2)}`));
|
|
14600
|
+
killer.once("exit", (code) => {
|
|
14601
|
+
if (code !== 0) {
|
|
14602
|
+
recordTreeKillFailure(`taskkill.exe exited with code ${code ?? "unknown"}`);
|
|
14603
|
+
return;
|
|
14604
|
+
}
|
|
14605
|
+
treeKillConfirmed = true;
|
|
14606
|
+
succeedIfConfirmed();
|
|
14607
|
+
});
|
|
14608
|
+
} catch (error2) {
|
|
14609
|
+
recordTreeKillFailure(`taskkill.exe failed to start: ${String(error2)}`);
|
|
14610
|
+
}
|
|
14611
|
+
});
|
|
14612
|
+
}
|
|
14393
14613
|
function npmSpawnEnv(resolved, baseEnv = process.env) {
|
|
14394
14614
|
if (!resolved.binDir)
|
|
14395
14615
|
return { ...baseEnv };
|
|
@@ -15142,6 +15362,10 @@ function pathValue(record, property) {
|
|
|
15142
15362
|
return value;
|
|
15143
15363
|
}
|
|
15144
15364
|
function normalizeAliasPair(record, canonical, legacy, required) {
|
|
15365
|
+
if (isNullOrEmptyString(record[canonical]))
|
|
15366
|
+
delete record[canonical];
|
|
15367
|
+
if (isNullOrEmptyString(record[legacy]))
|
|
15368
|
+
delete record[legacy];
|
|
15145
15369
|
const hasCanonical = hasOwn(record, canonical);
|
|
15146
15370
|
const hasLegacy = hasOwn(record, legacy);
|
|
15147
15371
|
if (!hasCanonical && !hasLegacy) {
|
|
@@ -15173,8 +15397,13 @@ function normalizeAliasPair(record, canonical, legacy, required) {
|
|
|
15173
15397
|
delete record[legacy];
|
|
15174
15398
|
}
|
|
15175
15399
|
function validateOptionalCanonicalPath(record, property) {
|
|
15176
|
-
if (hasOwn(record, property))
|
|
15177
|
-
|
|
15400
|
+
if (!hasOwn(record, property))
|
|
15401
|
+
return;
|
|
15402
|
+
if (isNullOrEmptyString(record[property])) {
|
|
15403
|
+
delete record[property];
|
|
15404
|
+
return;
|
|
15405
|
+
}
|
|
15406
|
+
pathValue(record, property);
|
|
15178
15407
|
}
|
|
15179
15408
|
function normalizeZoomTargets(record) {
|
|
15180
15409
|
if (!hasOwn(record, "targets"))
|
|
@@ -15330,6 +15559,10 @@ function prepareCanonicalEditArguments(toolName, rawArguments) {
|
|
|
15330
15559
|
return record;
|
|
15331
15560
|
}
|
|
15332
15561
|
function normalizeEditPathAlias(record) {
|
|
15562
|
+
if (isNullOrEmptyString(record.path))
|
|
15563
|
+
delete record.path;
|
|
15564
|
+
if (isNullOrEmptyString(record.filePath))
|
|
15565
|
+
delete record.filePath;
|
|
15333
15566
|
const hasCanonical = hasOwn(record, "path");
|
|
15334
15567
|
const hasLegacy = hasOwn(record, "filePath");
|
|
15335
15568
|
if (!hasCanonical && !hasLegacy)
|
|
@@ -15355,7 +15588,7 @@ function normalizeEditPathAlias(record) {
|
|
|
15355
15588
|
}
|
|
15356
15589
|
}
|
|
15357
15590
|
function validateEditPath(record) {
|
|
15358
|
-
if (!hasOwn(record, "path")) {
|
|
15591
|
+
if (!hasOwn(record, "path") || isNullOrEmptyString(record.path)) {
|
|
15359
15592
|
throw new InvalidRequestError("'path' is required");
|
|
15360
15593
|
}
|
|
15361
15594
|
pathValue(record, "path");
|
|
@@ -15416,10 +15649,13 @@ function isEditSentinelItem(item) {
|
|
|
15416
15649
|
if (record.oldString === null && ["startLine", "endLine"].some((key) => hasOwn(record, key) && record[key] !== null)) {
|
|
15417
15650
|
return false;
|
|
15418
15651
|
}
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
return
|
|
15652
|
+
return (!hasOwn(record, "newString") || isNullOrEmptyString(record.newString)) && (!hasOwn(record, "content") || isNullOrEmptyString(record.content)) && (!hasOwn(record, "replaceAll") || record.replaceAll === null || record.replaceAll === false) && isDefaultOccurrence(record.occurrence);
|
|
15653
|
+
}
|
|
15654
|
+
function hasMeaningfulFindPayload(item) {
|
|
15655
|
+
return isNonEmptyString(item.oldString);
|
|
15656
|
+
}
|
|
15657
|
+
function isDefaultOccurrence(value) {
|
|
15658
|
+
return value === undefined || value === null || value === 1;
|
|
15423
15659
|
}
|
|
15424
15660
|
function normalizeEditArraySentinels(record) {
|
|
15425
15661
|
const value = record.edits;
|
|
@@ -15472,8 +15708,8 @@ function parseEditArray(value) {
|
|
|
15472
15708
|
}
|
|
15473
15709
|
return value;
|
|
15474
15710
|
}
|
|
15475
|
-
function
|
|
15476
|
-
const
|
|
15711
|
+
function normalizeEditItemSentinels(item) {
|
|
15712
|
+
const hadRangeFields = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
15477
15713
|
for (const key of [
|
|
15478
15714
|
"oldString",
|
|
15479
15715
|
"newString",
|
|
@@ -15486,7 +15722,19 @@ function stripLineRangeSentinels(item) {
|
|
|
15486
15722
|
if (item[key] === null)
|
|
15487
15723
|
delete item[key];
|
|
15488
15724
|
}
|
|
15489
|
-
|
|
15725
|
+
const contentIsEmpty = item.content === "";
|
|
15726
|
+
if (hasMeaningfulFindPayload(item) && (!hasOwn(item, "content") || contentIsEmpty)) {
|
|
15727
|
+
for (const key of ["startLine", "endLine", "content"])
|
|
15728
|
+
delete item[key];
|
|
15729
|
+
if (hadRangeFields) {
|
|
15730
|
+
if (item.replaceAll === false)
|
|
15731
|
+
delete item.replaceAll;
|
|
15732
|
+
if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
|
|
15733
|
+
delete item.occurrence;
|
|
15734
|
+
}
|
|
15735
|
+
return;
|
|
15736
|
+
}
|
|
15737
|
+
if (!isNonEmptyString(item.content))
|
|
15490
15738
|
return;
|
|
15491
15739
|
if (item.oldString === "")
|
|
15492
15740
|
delete item.oldString;
|
|
@@ -15494,7 +15742,7 @@ function stripLineRangeSentinels(item) {
|
|
|
15494
15742
|
delete item.newString;
|
|
15495
15743
|
if (item.replaceAll === false)
|
|
15496
15744
|
delete item.replaceAll;
|
|
15497
|
-
if (item
|
|
15745
|
+
if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
|
|
15498
15746
|
delete item.occurrence;
|
|
15499
15747
|
}
|
|
15500
15748
|
function normalizeEditItem(value, index) {
|
|
@@ -15505,7 +15753,7 @@ function normalizeEditItem(value, index) {
|
|
|
15505
15753
|
const item = copyOwnProperties(source);
|
|
15506
15754
|
normalizeItemAlias(item, "oldString", "oldText");
|
|
15507
15755
|
normalizeItemAlias(item, "newString", "newText");
|
|
15508
|
-
|
|
15756
|
+
normalizeEditItemSentinels(item);
|
|
15509
15757
|
const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
|
|
15510
15758
|
const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
15511
15759
|
if (hasFindField && hasRangeField) {
|
|
@@ -30707,6 +30955,9 @@ var GhShimConfigSchema = exports_external.object({
|
|
|
30707
30955
|
enabled: exports_external.boolean().optional(),
|
|
30708
30956
|
binary_path: exports_external.string().trim().refine(isAbsolute6, "gh_shim.binary_path must be absolute").optional()
|
|
30709
30957
|
});
|
|
30958
|
+
var GhReadConfigSchema = exports_external.object({
|
|
30959
|
+
enabled: exports_external.boolean().optional()
|
|
30960
|
+
});
|
|
30710
30961
|
var GitCoAuthorSchema = exports_external.string().trim().refine((value) => value === "off" || value === "auto" || /^[^<>\r\n]+\s+<[^<>\s@]+@[^<>\s@]+>$/.test(value), "git.co_author must be 'off', 'auto', or an explicit 'Name <email>' identity");
|
|
30711
30962
|
var GitConfigSchema = exports_external.object({
|
|
30712
30963
|
co_author: GitCoAuthorSchema.optional()
|
|
@@ -30768,6 +31019,7 @@ var AftConfigFieldsSchema = exports_external.object({
|
|
|
30768
31019
|
bridge: BridgeConfigSchema.optional(),
|
|
30769
31020
|
subc: SubcConfigSchema.optional(),
|
|
30770
31021
|
gh_shim: GhShimConfigSchema.optional(),
|
|
31022
|
+
gh_read: GhReadConfigSchema.optional(),
|
|
30771
31023
|
git: GitConfigSchema.optional()
|
|
30772
31024
|
});
|
|
30773
31025
|
var HarnessOverrideSchema = exports_external.preprocess((value) => {
|
|
@@ -31246,6 +31498,8 @@ function getStrippedTopLevelKeys(override) {
|
|
|
31246
31498
|
stripped.push("subc");
|
|
31247
31499
|
if (override.gh_shim !== undefined)
|
|
31248
31500
|
stripped.push("gh_shim");
|
|
31501
|
+
if (override.gh_read !== undefined)
|
|
31502
|
+
stripped.push("gh_read");
|
|
31249
31503
|
if (override.disabled_tools?.includes("aft_safety"))
|
|
31250
31504
|
stripped.push("disabled_tools.aft_safety");
|
|
31251
31505
|
return stripped;
|
|
@@ -31463,6 +31717,8 @@ var UNKNOWN_COMPLETION_CAP = 32;
|
|
|
31463
31717
|
var DEFAULT_SESSION_ID2 = "__default__";
|
|
31464
31718
|
var LOG_PREFIX = "[aft-plugin] bg-notifications:";
|
|
31465
31719
|
var SUBC_NUDGE_LOG_INTERVAL_MS = 60000;
|
|
31720
|
+
var DEFAULT_BG_HOP_TIMEOUT_MS = 15000;
|
|
31721
|
+
var bgHopTimeoutMs = DEFAULT_BG_HOP_TIMEOUT_MS;
|
|
31466
31722
|
var subcNudgesInFlight = new Map;
|
|
31467
31723
|
var subcNudgeLogState = new Map;
|
|
31468
31724
|
function consumeBgCompletion(sessionID, taskId) {
|
|
@@ -31690,11 +31946,20 @@ function handleSubcBgEventsNudge(drainContext) {
|
|
|
31690
31946
|
}
|
|
31691
31947
|
async function handleSubcBgEventsNudgeOnce(drainContext) {
|
|
31692
31948
|
bridgeForDrain(drainContext);
|
|
31693
|
-
stateFor(drainContext.sessionID)
|
|
31949
|
+
const state = stateFor(drainContext.sessionID);
|
|
31950
|
+
state.wakeDeferredTaskIds.clear();
|
|
31951
|
+
rearmHardStoppedWake(state, drainContext);
|
|
31694
31952
|
await triggerWakeIfPending(drainContext, false, true, true);
|
|
31695
31953
|
}
|
|
31696
31954
|
function logSubcNudgeLifecycle(drainContext, kind, message) {
|
|
31697
|
-
|
|
31955
|
+
logDeliveryHop(drainContext, kind, message, {
|
|
31956
|
+
event: "subc_bg_nudge_delivery",
|
|
31957
|
+
cause: kind
|
|
31958
|
+
});
|
|
31959
|
+
}
|
|
31960
|
+
function logDeliveryHop(drainContext, kind, message, data, level = "info") {
|
|
31961
|
+
const taskKey = typeof data.task_id === "string" ? data.task_id : Array.isArray(data.task_ids) ? data.task_ids.join(",") : "";
|
|
31962
|
+
const key = `${kind}\x00${taskKey}\x00${drainContext.directory}\x00${drainContext.sessionID}`;
|
|
31698
31963
|
const now = Date.now();
|
|
31699
31964
|
const state = subcNudgeLogState.get(key);
|
|
31700
31965
|
if (state && now - state.lastEmittedAt < SUBC_NUDGE_LOG_INTERVAL_MS) {
|
|
@@ -31703,13 +31968,59 @@ function logSubcNudgeLifecycle(drainContext, kind, message) {
|
|
|
31703
31968
|
}
|
|
31704
31969
|
const suppressed = state?.suppressed ?? 0;
|
|
31705
31970
|
subcNudgeLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
|
|
31706
|
-
|
|
31707
|
-
|
|
31708
|
-
cause: kind,
|
|
31971
|
+
const payload = {
|
|
31972
|
+
...data,
|
|
31709
31973
|
canonical_root: drainContext.directory,
|
|
31710
31974
|
suppressed
|
|
31975
|
+
};
|
|
31976
|
+
if (level === "warn") {
|
|
31977
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} ${message}`, payload);
|
|
31978
|
+
} else {
|
|
31979
|
+
sessionLog(drainContext.sessionID, `${LOG_PREFIX} ${message}`, payload);
|
|
31980
|
+
}
|
|
31981
|
+
}
|
|
31982
|
+
function logPerTaskDeliveryHop(drainContext, kind, message, taskIDs, data, level = "info") {
|
|
31983
|
+
for (const taskID of taskIDs) {
|
|
31984
|
+
logDeliveryHop(drainContext, kind, message, { ...data, task_id: taskID, task_ids: taskIDs }, level);
|
|
31985
|
+
}
|
|
31986
|
+
}
|
|
31987
|
+
function rearmHardStoppedWake(state, drainContext) {
|
|
31988
|
+
if (!state.wakeHardStopped)
|
|
31989
|
+
return;
|
|
31990
|
+
state.wakeHardStopped = false;
|
|
31991
|
+
state.wakeRetryAttempts = 0;
|
|
31992
|
+
state.retryDelayMs = null;
|
|
31993
|
+
logDeliveryHop(drainContext, "inject-rearmed", "delivery retry re-armed", {
|
|
31994
|
+
event: "bash_completion_inject_rearmed",
|
|
31995
|
+
task_ids: state.pendingCompletions.map((completion) => completion.task_id)
|
|
31711
31996
|
});
|
|
31712
31997
|
}
|
|
31998
|
+
async function withBgHopTimeout(operation, label) {
|
|
31999
|
+
let timer;
|
|
32000
|
+
try {
|
|
32001
|
+
return await Promise.race([
|
|
32002
|
+
operation,
|
|
32003
|
+
new Promise((_, reject) => {
|
|
32004
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${bgHopTimeoutMs}ms`)), bgHopTimeoutMs);
|
|
32005
|
+
timer.unref?.();
|
|
32006
|
+
})
|
|
32007
|
+
]);
|
|
32008
|
+
} finally {
|
|
32009
|
+
if (timer)
|
|
32010
|
+
clearTimeout(timer);
|
|
32011
|
+
}
|
|
32012
|
+
}
|
|
32013
|
+
function promptAsyncFailure(response) {
|
|
32014
|
+
if (!response || typeof response !== "object" || Array.isArray(response))
|
|
32015
|
+
return null;
|
|
32016
|
+
const record2 = response;
|
|
32017
|
+
if (record2.error != null) {
|
|
32018
|
+
return typeof record2.error === "string" ? record2.error : JSON.stringify(record2.error);
|
|
32019
|
+
}
|
|
32020
|
+
if (record2.success === false)
|
|
32021
|
+
return String(record2.message ?? "promptAsync returned success=false");
|
|
32022
|
+
return null;
|
|
32023
|
+
}
|
|
31713
32024
|
async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false) {
|
|
31714
32025
|
const state = stateFor(drainContext.sessionID);
|
|
31715
32026
|
if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
|
|
@@ -31732,11 +32043,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31732
32043
|
}
|
|
31733
32044
|
return drainContext.client;
|
|
31734
32045
|
};
|
|
31735
|
-
const sendPrompt = async (
|
|
31736
|
-
|
|
32046
|
+
const sendPrompt = async (client) => {
|
|
32047
|
+
const promptAsync = client.session?.promptAsync;
|
|
32048
|
+
if (typeof promptAsync !== "function") {
|
|
31737
32049
|
throw new Error("wake client.session.promptAsync is unavailable");
|
|
31738
32050
|
}
|
|
31739
|
-
const promptContext = await resolvePromptContext(
|
|
32051
|
+
const promptContext = await resolvePromptContext(client, drainContext.sessionID);
|
|
31740
32052
|
const body = {
|
|
31741
32053
|
noReply: false,
|
|
31742
32054
|
parts: [{ type: "text", text: reminder, synthetic: true }]
|
|
@@ -31751,52 +32063,73 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31751
32063
|
}
|
|
31752
32064
|
if (promptContext?.variant)
|
|
31753
32065
|
body.variant = promptContext.variant;
|
|
31754
|
-
const
|
|
31755
|
-
|
|
31756
|
-
|
|
31757
|
-
attempt: state.wakeRetryAttempts + 1,
|
|
31758
|
-
task_ids: taskIDs,
|
|
31759
|
-
directory: drainContext.directory,
|
|
31760
|
-
reminder_sha256: hashReminder(reminder),
|
|
31761
|
-
reminder_chars: reminder.length,
|
|
31762
|
-
prompt_context: promptContext ? {
|
|
31763
|
-
agent: promptContext.agent,
|
|
31764
|
-
model: promptContext.model ? {
|
|
31765
|
-
providerID: promptContext.model.providerID,
|
|
31766
|
-
modelID: promptContext.model.modelID
|
|
31767
|
-
} : null,
|
|
31768
|
-
variant: promptContext.variant ?? null
|
|
31769
|
-
} : null
|
|
31770
|
-
};
|
|
31771
|
-
sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync start`, {
|
|
31772
|
-
event: "bash_completion_wake_prompt_async_start",
|
|
31773
|
-
...wakeMeta
|
|
31774
|
-
});
|
|
31775
|
-
try {
|
|
31776
|
-
await client2.session.promptAsync({
|
|
31777
|
-
path: { id: drainContext.sessionID },
|
|
31778
|
-
body
|
|
31779
|
-
});
|
|
31780
|
-
} catch (err) {
|
|
31781
|
-
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
|
|
31782
|
-
event: "bash_completion_wake_prompt_async_error",
|
|
32066
|
+
const attemptPrompt = async (attemptBody, mode) => {
|
|
32067
|
+
const deliveryID2 = `aftdel_${randomUUID2()}`;
|
|
32068
|
+
const wakeMeta = {
|
|
31783
32069
|
delivery_id: deliveryID2,
|
|
31784
32070
|
attempt: state.wakeRetryAttempts + 1,
|
|
31785
32071
|
task_ids: taskIDs,
|
|
31786
|
-
|
|
32072
|
+
directory: drainContext.directory,
|
|
32073
|
+
reminder_sha256: hashReminder(reminder),
|
|
32074
|
+
reminder_chars: reminder.length,
|
|
32075
|
+
injection_mode: mode,
|
|
32076
|
+
prompt_context: promptContext ? {
|
|
32077
|
+
agent: promptContext.agent,
|
|
32078
|
+
model: promptContext.model ? {
|
|
32079
|
+
providerID: promptContext.model.providerID,
|
|
32080
|
+
modelID: promptContext.model.modelID
|
|
32081
|
+
} : null,
|
|
32082
|
+
variant: promptContext.variant ?? null
|
|
32083
|
+
} : null
|
|
32084
|
+
};
|
|
32085
|
+
logPerTaskDeliveryHop(drainContext, `inject-start-${mode}`, "session inject start", taskIDs, { event: "bash_completion_wake_prompt_async_start", ...wakeMeta });
|
|
32086
|
+
try {
|
|
32087
|
+
const response = await withBgHopTimeout(Promise.resolve(promptAsync({
|
|
32088
|
+
path: { id: drainContext.sessionID },
|
|
32089
|
+
body: attemptBody
|
|
32090
|
+
})), "session promptAsync injection");
|
|
32091
|
+
const failure = promptAsyncFailure(response);
|
|
32092
|
+
if (failure)
|
|
32093
|
+
throw new Error(failure);
|
|
32094
|
+
} catch (err) {
|
|
32095
|
+
logPerTaskDeliveryHop(drainContext, "inject-error", "session inject failed", taskIDs, {
|
|
32096
|
+
event: "bash_completion_wake_prompt_async_error",
|
|
32097
|
+
delivery_id: deliveryID2,
|
|
32098
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
32099
|
+
injection_mode: mode,
|
|
32100
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32101
|
+
}, "warn");
|
|
32102
|
+
throw err;
|
|
32103
|
+
}
|
|
32104
|
+
logPerTaskDeliveryHop(drainContext, `inject-ok-${mode}`, "session inject ok", taskIDs, {
|
|
32105
|
+
event: "bash_completion_wake_prompt_async_ok",
|
|
32106
|
+
delivery_id: deliveryID2,
|
|
32107
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
32108
|
+
injection_mode: mode
|
|
31787
32109
|
});
|
|
31788
|
-
|
|
32110
|
+
return deliveryID2;
|
|
32111
|
+
};
|
|
32112
|
+
try {
|
|
32113
|
+
return await attemptPrompt(body, "mirrored-context");
|
|
32114
|
+
} catch (err) {
|
|
32115
|
+
if (!promptContext?.model)
|
|
32116
|
+
throw err;
|
|
32117
|
+
const fallbackBody = { ...body };
|
|
32118
|
+
delete fallbackBody.model;
|
|
32119
|
+
delete fallbackBody.variant;
|
|
32120
|
+
return attemptPrompt(fallbackBody, "model-free-fallback");
|
|
31789
32121
|
}
|
|
31790
|
-
sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync ok`, {
|
|
31791
|
-
event: "bash_completion_wake_prompt_async_ok",
|
|
31792
|
-
delivery_id: deliveryID2,
|
|
31793
|
-
attempt: state.wakeRetryAttempts + 1,
|
|
31794
|
-
task_ids: taskIDs
|
|
31795
|
-
});
|
|
31796
|
-
return deliveryID2;
|
|
31797
32122
|
};
|
|
31798
|
-
|
|
31799
|
-
|
|
32123
|
+
let deliveryID;
|
|
32124
|
+
try {
|
|
32125
|
+
deliveryID = await sendPrompt(getInProcessClient());
|
|
32126
|
+
} catch (err) {
|
|
32127
|
+
logPerTaskDeliveryHop(drainContext, "inject-error", "session inject failed", taskIDs, {
|
|
32128
|
+
event: "bash_completion_wake_prompt_async_error",
|
|
32129
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32130
|
+
}, "warn");
|
|
32131
|
+
throw err;
|
|
32132
|
+
}
|
|
31800
32133
|
markDeliveredAwaitingAck(state, taskIDs);
|
|
31801
32134
|
const acked = await ackCompletions(drainContext, deliveredCompletions, deliveryID);
|
|
31802
32135
|
if (acked)
|
|
@@ -31812,7 +32145,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31812
32145
|
});
|
|
31813
32146
|
return;
|
|
31814
32147
|
}
|
|
31815
|
-
|
|
32148
|
+
if (hardStopped) {
|
|
32149
|
+
logDeliveryHop(drainContext, "inject-hard-stopped", "delivery retries paused", {
|
|
32150
|
+
event: "bash_completion_inject_hard_stopped",
|
|
32151
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32152
|
+
});
|
|
32153
|
+
}
|
|
31816
32154
|
}, drainContext.sessionID, includeDeferredCompletions);
|
|
31817
32155
|
}
|
|
31818
32156
|
function formatSystemReminder(completions) {
|
|
@@ -31899,14 +32237,21 @@ async function drainCompletions(drainContext) {
|
|
|
31899
32237
|
try {
|
|
31900
32238
|
const bridge = bridgeForDrain(drainContext);
|
|
31901
32239
|
const state = stateFor(drainContext.sessionID);
|
|
31902
|
-
const response = await bridge.send("bash_drain_completions", {
|
|
31903
|
-
session_id: drainContext.sessionID
|
|
31904
|
-
});
|
|
32240
|
+
const response = await withBgHopTimeout(bridge.send("bash_drain_completions", { session_id: drainContext.sessionID }, { timeoutMs: bgHopTimeoutMs }), "bash_drain_completions");
|
|
31905
32241
|
if (response.success === false) {
|
|
31906
|
-
|
|
32242
|
+
logDeliveryHop(drainContext, "drain-error", "drain failed", {
|
|
32243
|
+
event: "bash_completion_drain_error",
|
|
32244
|
+
cause: String(response.message ?? "unknown error")
|
|
32245
|
+
}, "warn");
|
|
31907
32246
|
return;
|
|
31908
32247
|
}
|
|
31909
32248
|
state.forcedDrainCompleted = true;
|
|
32249
|
+
const drainedTaskIds = Array.isArray(response.bg_completions) ? response.bg_completions.filter(isBgCompletion).map((completion) => completion.task_id) : [];
|
|
32250
|
+
logDeliveryHop(drainContext, "drain-ok", "drain ok", {
|
|
32251
|
+
event: "bash_completion_drain_ok",
|
|
32252
|
+
count: drainedTaskIds.length,
|
|
32253
|
+
task_ids: drainedTaskIds
|
|
32254
|
+
});
|
|
31910
32255
|
const reackTaskIds = reconcileAwaitingAck(state, response.bg_completions);
|
|
31911
32256
|
ingestDrainedBgCompletions(drainContext.sessionID, response.bg_completions);
|
|
31912
32257
|
if (reackTaskIds.length > 0) {
|
|
@@ -31922,7 +32267,10 @@ async function drainCompletions(drainContext) {
|
|
|
31922
32267
|
} catch (err) {
|
|
31923
32268
|
if (drainContext.nudgeRef && isTerminalNudgeError(err))
|
|
31924
32269
|
throw err;
|
|
31925
|
-
|
|
32270
|
+
logDeliveryHop(drainContext, "drain-error", "drain failed", {
|
|
32271
|
+
event: "bash_completion_drain_error",
|
|
32272
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32273
|
+
}, "warn");
|
|
31926
32274
|
}
|
|
31927
32275
|
}
|
|
31928
32276
|
async function ackCompletions(drainContext, completions, deliveryID) {
|
|
@@ -31931,24 +32279,26 @@ async function ackCompletions(drainContext, completions, deliveryID) {
|
|
|
31931
32279
|
return true;
|
|
31932
32280
|
try {
|
|
31933
32281
|
const bridge = bridgeForDrain(drainContext);
|
|
31934
|
-
const response = await bridge.send("bash_ack_completions", {
|
|
31935
|
-
session_id: drainContext.sessionID,
|
|
31936
|
-
task_ids: taskIds
|
|
31937
|
-
});
|
|
32282
|
+
const response = await withBgHopTimeout(bridge.send("bash_ack_completions", { session_id: drainContext.sessionID, task_ids: taskIds }, { timeoutMs: bgHopTimeoutMs }), "bash_ack_completions");
|
|
31938
32283
|
if (response.success === false) {
|
|
31939
|
-
|
|
32284
|
+
logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
|
|
32285
|
+
event: "bash_completion_ack_error",
|
|
32286
|
+
cause: String(response.message ?? "unknown error")
|
|
32287
|
+
}, "warn");
|
|
31940
32288
|
return false;
|
|
31941
32289
|
}
|
|
31942
|
-
|
|
32290
|
+
logPerTaskDeliveryHop(drainContext, "ack-ok", "ack ok", taskIds, {
|
|
31943
32291
|
event: "bash_completion_ack_ok",
|
|
31944
|
-
delivery_id: deliveryID ?? null
|
|
31945
|
-
task_ids: taskIds
|
|
32292
|
+
delivery_id: deliveryID ?? null
|
|
31946
32293
|
});
|
|
31947
32294
|
return true;
|
|
31948
32295
|
} catch (err) {
|
|
31949
32296
|
if (drainContext.nudgeRef && isTerminalNudgeError(err))
|
|
31950
32297
|
throw err;
|
|
31951
|
-
|
|
32298
|
+
logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
|
|
32299
|
+
event: "bash_completion_ack_error",
|
|
32300
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32301
|
+
}, "warn");
|
|
31952
32302
|
return false;
|
|
31953
32303
|
}
|
|
31954
32304
|
}
|
|
@@ -32540,6 +32890,15 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
|
|
|
32540
32890
|
// src/bash-wait-detach.ts
|
|
32541
32891
|
var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach";
|
|
32542
32892
|
var EMPTY_DETACH_MESSAGE = "(requested background detach)";
|
|
32893
|
+
var STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
|
|
32894
|
+
var STANDALONE_DETACH_KEYWORD_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "u");
|
|
32895
|
+
var STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "gu");
|
|
32896
|
+
function containsStandaloneDetachKeyword(messageText) {
|
|
32897
|
+
return STANDALONE_DETACH_KEYWORD_PATTERN.test(messageText);
|
|
32898
|
+
}
|
|
32899
|
+
function stripStandaloneDetachKeywords(messageText) {
|
|
32900
|
+
return messageText.replace(STANDALONE_DETACH_KEYWORDS_PATTERN, "$1");
|
|
32901
|
+
}
|
|
32543
32902
|
function userTextParts(output) {
|
|
32544
32903
|
if (!output || typeof output !== "object")
|
|
32545
32904
|
return [];
|
|
@@ -32561,9 +32920,9 @@ function stripUserMessageDetachKeyword(output) {
|
|
|
32561
32920
|
const parts = userTextParts(output);
|
|
32562
32921
|
const original = parts.map((part) => part.text).join(`
|
|
32563
32922
|
`);
|
|
32564
|
-
if (!original
|
|
32923
|
+
if (!containsStandaloneDetachKeyword(original))
|
|
32565
32924
|
return original;
|
|
32566
|
-
const stripped = parts.map((part) => part.text
|
|
32925
|
+
const stripped = parts.map((part) => stripStandaloneDetachKeywords(part.text)).map((text) => text.replace(/[ \t]{2,}/g, " "));
|
|
32567
32926
|
if (stripped.join(`
|
|
32568
32927
|
`).trim() === "") {
|
|
32569
32928
|
if (parts[0])
|
|
@@ -32579,7 +32938,7 @@ function stripUserMessageDetachKeyword(output) {
|
|
|
32579
32938
|
`);
|
|
32580
32939
|
}
|
|
32581
32940
|
function shouldDetachBashWaitOnUserMessage(config2, messageText) {
|
|
32582
|
-
return resolveBashConfig(config2).detach_on_user_message || messageText
|
|
32941
|
+
return resolveBashConfig(config2).detach_on_user_message || containsStandaloneDetachKeyword(messageText);
|
|
32583
32942
|
}
|
|
32584
32943
|
async function sendBashWaitDetach(bridge, sessionID) {
|
|
32585
32944
|
const response = await bridge.send("bash_wait_detach", { session_id: sessionID }, { keepBridgeOnTimeout: true, transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS });
|
|
@@ -33169,40 +33528,32 @@ import {
|
|
|
33169
33528
|
import { dirname as dirname8 } from "node:path";
|
|
33170
33529
|
|
|
33171
33530
|
// src/hooks/auto-update-checker/cache.ts
|
|
33172
|
-
import { spawn as
|
|
33531
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
33173
33532
|
import { cpSync, existsSync as existsSync14, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
33174
33533
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
33175
33534
|
import { basename as basename3, dirname as dirname7, join as join16 } from "node:path";
|
|
33176
33535
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
33177
33536
|
|
|
33178
33537
|
// src/hooks/auto-update-checker/checker.ts
|
|
33179
|
-
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
33180
33538
|
import { existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
33181
|
-
import { homedir as homedir14 } from "node:os";
|
|
33182
33539
|
import { dirname as dirname6, isAbsolute as isAbsolute8, join as join15, resolve as resolve9 } from "node:path";
|
|
33183
33540
|
import { fileURLToPath } from "node:url";
|
|
33541
|
+
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
33184
33542
|
|
|
33185
33543
|
// src/hooks/auto-update-checker/constants.ts
|
|
33186
|
-
import { homedir as homedir13, platform as platform2 } from "node:os";
|
|
33187
33544
|
import { join as join14 } from "node:path";
|
|
33188
33545
|
var PACKAGE_NAME = "@cortexkit/aft-opencode";
|
|
33189
33546
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
33190
33547
|
var NPM_FETCH_TIMEOUT = 1e4;
|
|
33191
|
-
function
|
|
33192
|
-
|
|
33193
|
-
return join14(process.env.LOCALAPPDATA ?? homedir13(), "opencode");
|
|
33194
|
-
}
|
|
33195
|
-
return join14(homedir13(), ".cache", "opencode");
|
|
33548
|
+
function cacheDir() {
|
|
33549
|
+
return join14(getOpenCodeCacheRoot(), "packages");
|
|
33196
33550
|
}
|
|
33197
|
-
function
|
|
33198
|
-
|
|
33199
|
-
|
|
33200
|
-
|
|
33201
|
-
return join14(
|
|
33551
|
+
function userOpenCodeConfig() {
|
|
33552
|
+
return join14(getOpenCodeConfigRoot(), "opencode.json");
|
|
33553
|
+
}
|
|
33554
|
+
function userOpenCodeConfigJsonc() {
|
|
33555
|
+
return join14(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
33202
33556
|
}
|
|
33203
|
-
var CACHE_DIR = join14(getOpenCodeCacheRoot(), "packages");
|
|
33204
|
-
var USER_OPENCODE_CONFIG = join14(getOpenCodeConfigRoot(), "opencode.json");
|
|
33205
|
-
var USER_OPENCODE_CONFIG_JSONC = join14(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
33206
33557
|
|
|
33207
33558
|
// src/hooks/auto-update-checker/types.ts
|
|
33208
33559
|
var NpmPackageEnvelopeSchema = exports_external.object({
|
|
@@ -33262,8 +33613,8 @@ function getConfigPaths(directory) {
|
|
|
33262
33613
|
return [
|
|
33263
33614
|
join15(directory, ".opencode", "opencode.json"),
|
|
33264
33615
|
join15(directory, ".opencode", "opencode.jsonc"),
|
|
33265
|
-
|
|
33266
|
-
|
|
33616
|
+
userOpenCodeConfig(),
|
|
33617
|
+
userOpenCodeConfigJsonc()
|
|
33267
33618
|
];
|
|
33268
33619
|
}
|
|
33269
33620
|
function resolvePathPluginSpec(spec, configPath) {
|
|
@@ -33368,7 +33719,7 @@ function findPluginEntry(directory) {
|
|
|
33368
33719
|
}
|
|
33369
33720
|
var cachedPackageVersion = null;
|
|
33370
33721
|
function getSpecCachePackageJsonPath(spec) {
|
|
33371
|
-
return join15(
|
|
33722
|
+
return join15(cacheDir(), spec, "node_modules", PACKAGE_NAME, "package.json");
|
|
33372
33723
|
}
|
|
33373
33724
|
function getCachedVersion(spec) {
|
|
33374
33725
|
if (!spec && cachedPackageVersion)
|
|
@@ -33377,7 +33728,7 @@ function getCachedVersion(spec) {
|
|
|
33377
33728
|
getCurrentRuntimePackageJsonPath(),
|
|
33378
33729
|
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
33379
33730
|
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
33380
|
-
join15(
|
|
33731
|
+
join15(getOpenCodeCacheRoot(), "node_modules", PACKAGE_NAME, "package.json")
|
|
33381
33732
|
].filter(isString);
|
|
33382
33733
|
for (const packageJsonPath of candidates) {
|
|
33383
33734
|
try {
|
|
@@ -33424,6 +33775,7 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
33424
33775
|
|
|
33425
33776
|
// src/hooks/auto-update-checker/cache.ts
|
|
33426
33777
|
var pendingSnapshots = new Map;
|
|
33778
|
+
var quarantinedInstallDirs = new Set;
|
|
33427
33779
|
function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
33428
33780
|
const packageDir = join16(installDir, "node_modules", packageName);
|
|
33429
33781
|
const lockfilePath = join16(installDir, "package-lock.json");
|
|
@@ -33517,6 +33869,13 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
|
|
|
33517
33869
|
return false;
|
|
33518
33870
|
}
|
|
33519
33871
|
}
|
|
33872
|
+
function restorePendingSnapshot(installDir) {
|
|
33873
|
+
const snapshot = pendingSnapshots.get(installDir);
|
|
33874
|
+
if (!snapshot)
|
|
33875
|
+
return;
|
|
33876
|
+
pendingSnapshots.delete(installDir);
|
|
33877
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
33878
|
+
}
|
|
33520
33879
|
function removeInstalledPackage(installDir, packageName) {
|
|
33521
33880
|
const packageDir = join16(installDir, "node_modules", packageName);
|
|
33522
33881
|
if (!existsSync14(packageDir))
|
|
@@ -33537,9 +33896,10 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
|
|
|
33537
33896
|
}
|
|
33538
33897
|
return null;
|
|
33539
33898
|
}
|
|
33540
|
-
const
|
|
33899
|
+
const cacheRoot = dirname7(cacheDir());
|
|
33900
|
+
const legacyPackageJsonPath = join16(cacheRoot, "package.json");
|
|
33541
33901
|
if (existsSync14(legacyPackageJsonPath)) {
|
|
33542
|
-
return { installDir:
|
|
33902
|
+
return { installDir: cacheRoot, packageJsonPath: legacyPackageJsonPath };
|
|
33543
33903
|
}
|
|
33544
33904
|
return null;
|
|
33545
33905
|
}
|
|
@@ -33550,6 +33910,12 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
|
|
|
33550
33910
|
warn2("[auto-update-checker] No install context found for auto-update");
|
|
33551
33911
|
return null;
|
|
33552
33912
|
}
|
|
33913
|
+
if (quarantinedInstallDirs.has(installContext.installDir)) {
|
|
33914
|
+
const recoverySnapshot = pendingSnapshots.get(installContext.installDir);
|
|
33915
|
+
const recoveryDetail = recoverySnapshot ? ` Recovery snapshot: ${recoverySnapshot.tempDir}` : "";
|
|
33916
|
+
warn2(`[auto-update-checker] Auto-update blocked after unconfirmed npm termination; ` + `restart OpenCode to retry.${recoveryDetail}`);
|
|
33917
|
+
return null;
|
|
33918
|
+
}
|
|
33553
33919
|
const snapshot = createAutoUpdateSnapshot(installContext.installDir, installContext.packageJsonPath, packageName);
|
|
33554
33920
|
pendingSnapshots.set(installContext.installDir, snapshot);
|
|
33555
33921
|
if (!ensureDependencyVersion(installContext.packageJsonPath, packageName, version2)) {
|
|
@@ -33573,18 +33939,29 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33573
33939
|
let timeout = null;
|
|
33574
33940
|
let stderrTail = "";
|
|
33575
33941
|
try {
|
|
33576
|
-
if (options.signal?.aborted)
|
|
33942
|
+
if (options.signal?.aborted) {
|
|
33943
|
+
restorePendingSnapshot(installDir);
|
|
33577
33944
|
return { ok: false, reason: "aborted" };
|
|
33945
|
+
}
|
|
33578
33946
|
const npm = resolveNpm();
|
|
33579
33947
|
if (!npm) {
|
|
33948
|
+
restorePendingSnapshot(installDir);
|
|
33580
33949
|
const reason = "npm not found on PATH or in known version-manager locations";
|
|
33581
33950
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33582
33951
|
return { ok: false, reason };
|
|
33583
33952
|
}
|
|
33584
|
-
const
|
|
33953
|
+
const invocation = npmInvocation(npm, [
|
|
33954
|
+
"install",
|
|
33955
|
+
"--no-audit",
|
|
33956
|
+
"--no-fund",
|
|
33957
|
+
"--no-progress",
|
|
33958
|
+
"--ignore-scripts"
|
|
33959
|
+
]);
|
|
33960
|
+
const proc = spawn4(invocation.command, invocation.args, {
|
|
33585
33961
|
cwd: installDir,
|
|
33586
33962
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33587
|
-
env: npmSpawnEnv(npm)
|
|
33963
|
+
env: { ...npmSpawnEnv(npm), ...invocation.env },
|
|
33964
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
33588
33965
|
});
|
|
33589
33966
|
proc.stderr?.on("data", (chunk) => {
|
|
33590
33967
|
stderrTail += chunk.toString("utf8");
|
|
@@ -33593,12 +33970,11 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33593
33970
|
}
|
|
33594
33971
|
});
|
|
33595
33972
|
proc.stdout?.on("data", () => {});
|
|
33973
|
+
let terminationPromise = null;
|
|
33596
33974
|
const abortProcess = () => {
|
|
33597
|
-
|
|
33598
|
-
|
|
33599
|
-
} catch {}
|
|
33975
|
+
terminationPromise ??= terminateNpmProcessTree(proc, invocation);
|
|
33976
|
+
return terminationPromise;
|
|
33600
33977
|
};
|
|
33601
|
-
options.signal?.addEventListener("abort", abortProcess, { once: true });
|
|
33602
33978
|
const exitPromise = new Promise((resolveExit) => {
|
|
33603
33979
|
proc.on("error", (err) => resolveExit({ ok: false, reason: `spawn error: ${String(err)}` }));
|
|
33604
33980
|
proc.on("exit", (code) => resolveExit(code === 0 ? { ok: true } : { ok: false, reason: `npm install exited with code ${code ?? "signal/unknown"}` }));
|
|
@@ -33606,16 +33982,29 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33606
33982
|
const timeoutPromise = new Promise((resolveTimeout) => {
|
|
33607
33983
|
timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
|
|
33608
33984
|
});
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
|
|
33618
|
-
|
|
33985
|
+
let resolveAbort;
|
|
33986
|
+
const abortPromise = new Promise((resolve10) => {
|
|
33987
|
+
resolveAbort = resolve10;
|
|
33988
|
+
});
|
|
33989
|
+
const onAbort = () => resolveAbort("abort");
|
|
33990
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
33991
|
+
if (options.signal?.aborted)
|
|
33992
|
+
onAbort();
|
|
33993
|
+
const result = await Promise.race([exitPromise, timeoutPromise, abortPromise]);
|
|
33994
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
33995
|
+
if (result === "timeout" || result === "abort") {
|
|
33996
|
+
try {
|
|
33997
|
+
await abortProcess();
|
|
33998
|
+
} catch (error53) {
|
|
33999
|
+
quarantinedInstallDirs.add(installDir);
|
|
34000
|
+
const recoverySnapshot = pendingSnapshots.get(installDir);
|
|
34001
|
+
const recoveryDetail = recoverySnapshot ? `; auto-update quarantined for this session; recovery snapshot: ${recoverySnapshot.tempDir}` : "; auto-update quarantined for this session";
|
|
34002
|
+
const reason2 = `termination outcome unknown: ${String(error53)}${recoveryDetail}`;
|
|
34003
|
+
warnNpmInstallFailure(reason2, stderrTail);
|
|
34004
|
+
return { ok: false, reason: reason2, stderrTail: stderrTail || undefined };
|
|
34005
|
+
}
|
|
34006
|
+
restorePendingSnapshot(installDir);
|
|
34007
|
+
const reason = result === "abort" ? "aborted" : "timeout";
|
|
33619
34008
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33620
34009
|
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
33621
34010
|
}
|
|
@@ -33631,11 +34020,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33631
34020
|
}
|
|
33632
34021
|
return { ...result, stderrTail: stderrTail || undefined };
|
|
33633
34022
|
} catch (err) {
|
|
33634
|
-
|
|
33635
|
-
if (snapshot) {
|
|
33636
|
-
pendingSnapshots.delete(installDir);
|
|
33637
|
-
restoreAutoUpdateSnapshot(snapshot);
|
|
33638
|
-
}
|
|
34023
|
+
restorePendingSnapshot(installDir);
|
|
33639
34024
|
const reason = `exception: ${String(err)}`;
|
|
33640
34025
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33641
34026
|
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
@@ -33910,7 +34295,7 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
33910
34295
|
}
|
|
33911
34296
|
|
|
33912
34297
|
// src/lsp-auto-install.ts
|
|
33913
|
-
import { spawn as
|
|
34298
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
33914
34299
|
import { createHash as createHash6 } from "node:crypto";
|
|
33915
34300
|
import {
|
|
33916
34301
|
createReadStream,
|
|
@@ -33925,6 +34310,7 @@ import {
|
|
|
33925
34310
|
import { join as join19 } from "node:path";
|
|
33926
34311
|
|
|
33927
34312
|
// src/lsp-cache.ts
|
|
34313
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33928
34314
|
import {
|
|
33929
34315
|
closeSync as closeSync6,
|
|
33930
34316
|
mkdirSync as mkdirSync7,
|
|
@@ -34006,27 +34392,46 @@ var STALE_LOCK_MS2 = 30 * 60 * 1000;
|
|
|
34006
34392
|
function acquireInstallLock(lockKey) {
|
|
34007
34393
|
mkdirSync7(lspPackageDir(lockKey), { recursive: true });
|
|
34008
34394
|
const lock = lockPath(lockKey);
|
|
34395
|
+
const token = randomUUID3();
|
|
34009
34396
|
const tryClaim = () => {
|
|
34010
34397
|
try {
|
|
34011
34398
|
const fd = openSync6(lock, "wx");
|
|
34012
34399
|
try {
|
|
34013
34400
|
writeFileSync8(fd, `${process.pid}
|
|
34014
34401
|
${new Date().toISOString()}
|
|
34402
|
+
${token}
|
|
34015
34403
|
`);
|
|
34016
34404
|
} finally {
|
|
34017
34405
|
closeSync6(fd);
|
|
34018
34406
|
}
|
|
34019
|
-
|
|
34407
|
+
let retained = false;
|
|
34408
|
+
let released = false;
|
|
34409
|
+
return {
|
|
34410
|
+
retain() {
|
|
34411
|
+
retained = true;
|
|
34412
|
+
},
|
|
34413
|
+
release() {
|
|
34414
|
+
if (released)
|
|
34415
|
+
return;
|
|
34416
|
+
released = true;
|
|
34417
|
+
if (retained) {
|
|
34418
|
+
warn2(`[lsp] retaining install lock for ${lockKey} after unconfirmed npm termination; ` + `cross-process retries remain blocked until stale-lock recovery`);
|
|
34419
|
+
return;
|
|
34420
|
+
}
|
|
34421
|
+
releaseInstallLock(lockKey, token);
|
|
34422
|
+
}
|
|
34423
|
+
};
|
|
34020
34424
|
} catch (err) {
|
|
34021
34425
|
const code = err.code;
|
|
34022
34426
|
if (code === "EEXIST")
|
|
34023
|
-
return
|
|
34427
|
+
return null;
|
|
34024
34428
|
warn2(`[lsp] unexpected error acquiring install lock for ${lockKey}: ${err}`);
|
|
34025
|
-
return
|
|
34429
|
+
return null;
|
|
34026
34430
|
}
|
|
34027
34431
|
};
|
|
34028
|
-
|
|
34029
|
-
|
|
34432
|
+
const claimed = tryClaim();
|
|
34433
|
+
if (claimed)
|
|
34434
|
+
return claimed;
|
|
34030
34435
|
let owningPid = null;
|
|
34031
34436
|
let lockMtimeMs = 0;
|
|
34032
34437
|
try {
|
|
@@ -34044,7 +34449,7 @@ ${new Date().toISOString()}
|
|
|
34044
34449
|
const skipLiveness = process.platform === "win32";
|
|
34045
34450
|
const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive3(owningPid);
|
|
34046
34451
|
if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
|
|
34047
|
-
return
|
|
34452
|
+
return null;
|
|
34048
34453
|
}
|
|
34049
34454
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
34050
34455
|
try {
|
|
@@ -34063,16 +34468,18 @@ function isProcessAlive3(pid) {
|
|
|
34063
34468
|
return true;
|
|
34064
34469
|
}
|
|
34065
34470
|
}
|
|
34066
|
-
function releaseInstallLock(lockKey) {
|
|
34471
|
+
function releaseInstallLock(lockKey, acquisitionToken) {
|
|
34067
34472
|
const lock = lockPath(lockKey);
|
|
34068
34473
|
try {
|
|
34069
34474
|
let owningPid = null;
|
|
34475
|
+
let owningToken = "";
|
|
34070
34476
|
try {
|
|
34071
34477
|
const raw = readFileSync12(lock, "utf8");
|
|
34072
|
-
const
|
|
34073
|
-
const parsed = Number.parseInt(
|
|
34478
|
+
const lines = raw.split(/\r?\n/);
|
|
34479
|
+
const parsed = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
|
34074
34480
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
34075
34481
|
owningPid = parsed;
|
|
34482
|
+
owningToken = lines[2]?.trim() ?? "";
|
|
34076
34483
|
} catch (readErr) {
|
|
34077
34484
|
const code = readErr.code;
|
|
34078
34485
|
if (code === "ENOENT")
|
|
@@ -34080,8 +34487,8 @@ function releaseInstallLock(lockKey) {
|
|
|
34080
34487
|
warn2(`[lsp] could not read install lock for ${lockKey} during release: ${readErr}`);
|
|
34081
34488
|
return;
|
|
34082
34489
|
}
|
|
34083
|
-
if (owningPid !== process.pid) {
|
|
34084
|
-
log2(`[lsp] not releasing install lock for ${lockKey}:
|
|
34490
|
+
if (owningPid !== process.pid || owningToken !== acquisitionToken) {
|
|
34491
|
+
log2(`[lsp] not releasing install lock for ${lockKey}: acquisition ownership changed`);
|
|
34085
34492
|
return;
|
|
34086
34493
|
}
|
|
34087
34494
|
try {
|
|
@@ -34097,12 +34504,13 @@ function releaseInstallLock(lockKey) {
|
|
|
34097
34504
|
}
|
|
34098
34505
|
}
|
|
34099
34506
|
async function withInstallLock(lockKey, task) {
|
|
34100
|
-
|
|
34507
|
+
const lease = acquireInstallLock(lockKey);
|
|
34508
|
+
if (!lease)
|
|
34101
34509
|
return null;
|
|
34102
34510
|
try {
|
|
34103
|
-
return await task();
|
|
34511
|
+
return await task(lease);
|
|
34104
34512
|
} finally {
|
|
34105
|
-
|
|
34513
|
+
lease.release();
|
|
34106
34514
|
}
|
|
34107
34515
|
}
|
|
34108
34516
|
var VERSION_CHECK_FILE = ".aft-version-check";
|
|
@@ -34447,6 +34855,7 @@ function buildExtensionMap(specs) {
|
|
|
34447
34855
|
return byExt;
|
|
34448
34856
|
}
|
|
34449
34857
|
var inFlightAutoInstalls = new Set;
|
|
34858
|
+
var quarantinedNpmInstalls = new Set;
|
|
34450
34859
|
function trackInFlightAutoInstall(controller, promise2) {
|
|
34451
34860
|
const entry = { controller, promise: promise2 };
|
|
34452
34861
|
inFlightAutoInstalls.add(entry);
|
|
@@ -34494,7 +34903,7 @@ function ensureInstallAnchor(cwd) {
|
|
|
34494
34903
|
warn2(`[lsp] could not write package.json stub in ${cwd}: ${err}`);
|
|
34495
34904
|
}
|
|
34496
34905
|
}
|
|
34497
|
-
function runInstall(spec, version2, cwd, signal) {
|
|
34906
|
+
function runInstall(spec, version2, cwd, installLock, signal) {
|
|
34498
34907
|
return new Promise((resolve10) => {
|
|
34499
34908
|
const target = `${spec.npm}@${version2}`;
|
|
34500
34909
|
log2(`[lsp] installing ${target} to ${cwd}`);
|
|
@@ -34510,19 +34919,25 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34510
34919
|
return;
|
|
34511
34920
|
}
|
|
34512
34921
|
ensureInstallAnchor(cwd);
|
|
34513
|
-
const
|
|
34922
|
+
const invocation = npmInvocation(npm, [
|
|
34923
|
+
"install",
|
|
34924
|
+
"--no-save",
|
|
34925
|
+
"--ignore-scripts",
|
|
34926
|
+
"--silent",
|
|
34927
|
+
target
|
|
34928
|
+
]);
|
|
34929
|
+
const child = spawn5(invocation.command, invocation.args, {
|
|
34514
34930
|
stdio: ["ignore", "pipe", "pipe"],
|
|
34515
34931
|
cwd,
|
|
34516
|
-
env: npmSpawnEnv(npm)
|
|
34932
|
+
env: { ...npmSpawnEnv(npm), ...invocation.env },
|
|
34933
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
34517
34934
|
});
|
|
34518
34935
|
child.unref();
|
|
34519
34936
|
let stderrBuf = "";
|
|
34520
34937
|
let settled = false;
|
|
34521
|
-
let
|
|
34938
|
+
let terminationPromise = null;
|
|
34522
34939
|
const cleanup = () => {
|
|
34523
34940
|
signal?.removeEventListener("abort", onAbort);
|
|
34524
|
-
if (killTimer)
|
|
34525
|
-
clearTimeout(killTimer);
|
|
34526
34941
|
};
|
|
34527
34942
|
const finish = (ok) => {
|
|
34528
34943
|
if (settled)
|
|
@@ -34531,14 +34946,31 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34531
34946
|
cleanup();
|
|
34532
34947
|
resolve10(ok);
|
|
34533
34948
|
};
|
|
34949
|
+
const childCompletedSuccessfully = () => child.exitCode === 0 && child.signalCode === null;
|
|
34950
|
+
const finishAfterConfirmedTermination = () => {
|
|
34951
|
+
const completedSuccessfully = childCompletedSuccessfully();
|
|
34952
|
+
if (completedSuccessfully)
|
|
34953
|
+
log2(`[lsp] installed ${target}`);
|
|
34954
|
+
finish(completedSuccessfully);
|
|
34955
|
+
};
|
|
34534
34956
|
const onAbort = () => {
|
|
34957
|
+
if (childCompletedSuccessfully()) {
|
|
34958
|
+
finishAfterConfirmedTermination();
|
|
34959
|
+
return;
|
|
34960
|
+
}
|
|
34535
34961
|
warn2(`[lsp] install ${target} aborted during shutdown`);
|
|
34536
|
-
child
|
|
34537
|
-
|
|
34538
|
-
if (
|
|
34539
|
-
|
|
34540
|
-
|
|
34541
|
-
|
|
34962
|
+
terminationPromise ??= terminateNpmProcessTree(child, invocation);
|
|
34963
|
+
terminationPromise.then(finishAfterConfirmedTermination, (terminationError) => {
|
|
34964
|
+
if (child.pid === undefined) {
|
|
34965
|
+
error52(`[lsp] install ${target} failed to spawn during shutdown: ${terminationError}`);
|
|
34966
|
+
finish(false);
|
|
34967
|
+
return;
|
|
34968
|
+
}
|
|
34969
|
+
quarantinedNpmInstalls.add(spec.npm);
|
|
34970
|
+
installLock.retain();
|
|
34971
|
+
error52(`[lsp] install ${target} termination outcome unknown; quarantining retries for this session: ${String(terminationError)}`);
|
|
34972
|
+
finish(false);
|
|
34973
|
+
});
|
|
34542
34974
|
};
|
|
34543
34975
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
34544
34976
|
if (signal?.aborted)
|
|
@@ -34552,10 +34984,16 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34552
34984
|
}
|
|
34553
34985
|
});
|
|
34554
34986
|
child.on("error", (err) => {
|
|
34555
|
-
|
|
34987
|
+
if (settled)
|
|
34988
|
+
return;
|
|
34989
|
+
error52(`[lsp] install ${target} child-process error: ${err}`);
|
|
34990
|
+
if (terminationPromise && child.pid !== undefined)
|
|
34991
|
+
return;
|
|
34556
34992
|
finish(false);
|
|
34557
34993
|
});
|
|
34558
34994
|
child.on("exit", (code) => {
|
|
34995
|
+
if (settled || terminationPromise)
|
|
34996
|
+
return;
|
|
34559
34997
|
if (code === 0) {
|
|
34560
34998
|
log2(`[lsp] installed ${target}`);
|
|
34561
34999
|
finish(true);
|
|
@@ -34568,7 +35006,13 @@ ${stderrBuf.trim()}`);
|
|
|
34568
35006
|
});
|
|
34569
35007
|
}
|
|
34570
35008
|
async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
34571
|
-
|
|
35009
|
+
if (quarantinedNpmInstalls.has(spec.npm)) {
|
|
35010
|
+
return {
|
|
35011
|
+
started: false,
|
|
35012
|
+
reason: "install blocked after unconfirmed npm termination; restart the host to retry"
|
|
35013
|
+
};
|
|
35014
|
+
}
|
|
35015
|
+
const outcome = await withInstallLock(spec.npm, async (installLock) => {
|
|
34572
35016
|
const { version: version2, probe } = await resolveTargetVersion(spec, config2, fetchImpl);
|
|
34573
35017
|
if (!version2) {
|
|
34574
35018
|
const installed = isInstalled(spec.npm, spec.binary);
|
|
@@ -34604,7 +35048,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
|
34604
35048
|
log2(`[lsp] reinstalling ${spec.npm}@${version2}: no installed-version metadata recorded`);
|
|
34605
35049
|
}
|
|
34606
35050
|
}
|
|
34607
|
-
const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
|
|
35051
|
+
const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), installLock, signal).catch((err) => {
|
|
34608
35052
|
error52(`[lsp] background install ${spec.npm} crashed: ${err}`);
|
|
34609
35053
|
return false;
|
|
34610
35054
|
});
|
|
@@ -34787,69 +35231,69 @@ import { Readable as Readable3 } from "node:stream";
|
|
|
34787
35231
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
34788
35232
|
|
|
34789
35233
|
// src/lsp-github-table.ts
|
|
34790
|
-
function exe(
|
|
34791
|
-
return
|
|
35234
|
+
function exe(platform2, name) {
|
|
35235
|
+
return platform2 === "win32" ? `${name}.exe` : name;
|
|
34792
35236
|
}
|
|
34793
35237
|
var CLANGD = {
|
|
34794
35238
|
id: "clangd",
|
|
34795
35239
|
githubRepo: "clangd/clangd",
|
|
34796
35240
|
binary: "clangd",
|
|
34797
|
-
resolveAsset: (
|
|
34798
|
-
const platformName =
|
|
35241
|
+
resolveAsset: (platform2, _arch, version2) => {
|
|
35242
|
+
const platformName = platform2 === "darwin" ? "mac" : platform2 === "linux" ? "linux" : "windows";
|
|
34799
35243
|
return { name: `clangd-${platformName}-${version2}.zip`, archive: "zip" };
|
|
34800
35244
|
},
|
|
34801
|
-
binaryPathInArchive: (
|
|
35245
|
+
binaryPathInArchive: (platform2, _arch, version2) => `clangd_${version2}/bin/${exe(platform2, "clangd")}`
|
|
34802
35246
|
};
|
|
34803
35247
|
var LUA_LS = {
|
|
34804
35248
|
id: "lua-ls",
|
|
34805
35249
|
githubRepo: "LuaLS/lua-language-server",
|
|
34806
35250
|
binary: "lua-language-server",
|
|
34807
|
-
resolveAsset: (
|
|
34808
|
-
const ext =
|
|
34809
|
-
const platformName =
|
|
35251
|
+
resolveAsset: (platform2, arch, version2) => {
|
|
35252
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
35253
|
+
const platformName = platform2 === "darwin" ? "darwin" : platform2 === "linux" ? "linux" : "win32";
|
|
34810
35254
|
const archName = arch === "arm64" ? "arm64" : "x64";
|
|
34811
35255
|
return {
|
|
34812
35256
|
name: `lua-language-server-${version2}-${platformName}-${archName}.${ext}`,
|
|
34813
35257
|
archive: ext
|
|
34814
35258
|
};
|
|
34815
35259
|
},
|
|
34816
|
-
binaryPathInArchive: (
|
|
35260
|
+
binaryPathInArchive: (platform2, _arch, _version) => `bin/${exe(platform2, "lua-language-server")}`
|
|
34817
35261
|
};
|
|
34818
35262
|
var ZLS = {
|
|
34819
35263
|
id: "zls",
|
|
34820
35264
|
githubRepo: "zigtools/zls",
|
|
34821
35265
|
binary: "zls",
|
|
34822
|
-
resolveAsset: (
|
|
34823
|
-
const ext =
|
|
35266
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
35267
|
+
const ext = platform2 === "win32" ? "zip" : "tar.xz";
|
|
34824
35268
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34825
|
-
const platformName =
|
|
35269
|
+
const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
|
|
34826
35270
|
return { name: `zls-${archName}-${platformName}.${ext}`, archive: ext };
|
|
34827
35271
|
},
|
|
34828
|
-
binaryPathInArchive: (
|
|
35272
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "zls")
|
|
34829
35273
|
};
|
|
34830
35274
|
var TINYMIST = {
|
|
34831
35275
|
id: "tinymist",
|
|
34832
35276
|
githubRepo: "Myriad-Dreamin/tinymist",
|
|
34833
35277
|
binary: "tinymist",
|
|
34834
|
-
resolveAsset: (
|
|
35278
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
34835
35279
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34836
|
-
const triple =
|
|
34837
|
-
const ext =
|
|
35280
|
+
const triple = platform2 === "darwin" ? "apple-darwin" : platform2 === "linux" ? "unknown-linux-gnu" : "pc-windows-msvc";
|
|
35281
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
34838
35282
|
return { name: `tinymist-${archName}-${triple}.${ext}`, archive: ext };
|
|
34839
35283
|
},
|
|
34840
|
-
binaryPathInArchive: (
|
|
35284
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "tinymist")
|
|
34841
35285
|
};
|
|
34842
35286
|
var TEXLAB = {
|
|
34843
35287
|
id: "texlab",
|
|
34844
35288
|
githubRepo: "latex-lsp/texlab",
|
|
34845
35289
|
binary: "texlab",
|
|
34846
|
-
resolveAsset: (
|
|
35290
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
34847
35291
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34848
|
-
const platformName =
|
|
34849
|
-
const ext =
|
|
35292
|
+
const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
|
|
35293
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
34850
35294
|
return { name: `texlab-${archName}-${platformName}.${ext}`, archive: ext };
|
|
34851
35295
|
},
|
|
34852
|
-
binaryPathInArchive: (
|
|
35296
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "texlab")
|
|
34853
35297
|
};
|
|
34854
35298
|
var GITHUB_LSP_TABLE = [
|
|
34855
35299
|
CLANGD,
|
|
@@ -34859,14 +35303,14 @@ var GITHUB_LSP_TABLE = [
|
|
|
34859
35303
|
TEXLAB
|
|
34860
35304
|
];
|
|
34861
35305
|
function detectHostPlatform() {
|
|
34862
|
-
const
|
|
34863
|
-
if (
|
|
35306
|
+
const platform2 = process.platform;
|
|
35307
|
+
if (platform2 !== "darwin" && platform2 !== "linux" && platform2 !== "win32")
|
|
34864
35308
|
return null;
|
|
34865
35309
|
const arch = process.arch;
|
|
34866
35310
|
if (arch === "x64")
|
|
34867
|
-
return { platform:
|
|
35311
|
+
return { platform: platform2, arch: "x64" };
|
|
34868
35312
|
if (arch === "arm64")
|
|
34869
|
-
return { platform:
|
|
35313
|
+
return { platform: platform2, arch: "arm64" };
|
|
34870
35314
|
return null;
|
|
34871
35315
|
}
|
|
34872
35316
|
|
|
@@ -34884,12 +35328,12 @@ function ghExtractDir(spec) {
|
|
|
34884
35328
|
return join20(ghPackageDir(spec), "extracted");
|
|
34885
35329
|
}
|
|
34886
35330
|
var INSTALLED_META_FILE2 = ".aft-installed";
|
|
34887
|
-
function ghBinaryPath(spec,
|
|
34888
|
-
const ext =
|
|
35331
|
+
function ghBinaryPath(spec, platform2) {
|
|
35332
|
+
const ext = platform2 === "win32" ? ".exe" : "";
|
|
34889
35333
|
return join20(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
34890
35334
|
}
|
|
34891
|
-
function isGithubInstalled(spec,
|
|
34892
|
-
for (const candidate of ghBinaryCandidates(spec,
|
|
35335
|
+
function isGithubInstalled(spec, platform2) {
|
|
35336
|
+
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
34893
35337
|
try {
|
|
34894
35338
|
if (statSync11(join20(ghBinDir(spec), candidate)).isFile())
|
|
34895
35339
|
return true;
|
|
@@ -34897,8 +35341,8 @@ function isGithubInstalled(spec, platform3) {
|
|
|
34897
35341
|
}
|
|
34898
35342
|
return false;
|
|
34899
35343
|
}
|
|
34900
|
-
function ghBinaryCandidates(spec,
|
|
34901
|
-
if (
|
|
35344
|
+
function ghBinaryCandidates(spec, platform2) {
|
|
35345
|
+
if (platform2 !== "win32")
|
|
34902
35346
|
return [spec.binary];
|
|
34903
35347
|
return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
|
|
34904
35348
|
}
|
|
@@ -35278,10 +35722,10 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
35278
35722
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
35279
35723
|
}
|
|
35280
35724
|
}
|
|
35281
|
-
function validateCachedGithubInstall(spec,
|
|
35725
|
+
function validateCachedGithubInstall(spec, platform2) {
|
|
35282
35726
|
const packageDir = ghPackageDir(spec);
|
|
35283
35727
|
const meta3 = readGithubInstalledMetaIn(packageDir);
|
|
35284
|
-
const binaryPath = ghBinaryCandidates(spec,
|
|
35728
|
+
const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join20(ghBinDir(spec), candidate)).find((candidate) => {
|
|
35285
35729
|
try {
|
|
35286
35730
|
return statSync11(candidate).isFile();
|
|
35287
35731
|
} catch {
|
|
@@ -35345,11 +35789,11 @@ function runPlatformExtractor(archivePath, destDir, archiveType) {
|
|
|
35345
35789
|
}
|
|
35346
35790
|
throw new Error(`unsupported archive type: ${archiveType}`);
|
|
35347
35791
|
}
|
|
35348
|
-
async function downloadAndInstall(spec, tag, assets,
|
|
35792
|
+
async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal) {
|
|
35349
35793
|
const version2 = stripTagV(tag);
|
|
35350
|
-
const expected = spec.resolveAsset(
|
|
35794
|
+
const expected = spec.resolveAsset(platform2, arch, version2);
|
|
35351
35795
|
if (!expected) {
|
|
35352
|
-
warn2(`[lsp] ${spec.id}: unsupported platform/arch combo ${
|
|
35796
|
+
warn2(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform2}/${arch}`);
|
|
35353
35797
|
return null;
|
|
35354
35798
|
}
|
|
35355
35799
|
const matchingAsset = assets.find((a) => a.name === expected.name);
|
|
@@ -35399,16 +35843,16 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
35399
35843
|
unlinkSync7(archivePath);
|
|
35400
35844
|
} catch {}
|
|
35401
35845
|
}
|
|
35402
|
-
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(
|
|
35846
|
+
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
35403
35847
|
if (!existsSync18(innerBinaryPath)) {
|
|
35404
35848
|
error52(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
35405
35849
|
return null;
|
|
35406
35850
|
}
|
|
35407
|
-
const targetBinary = ghBinaryPath(spec,
|
|
35851
|
+
const targetBinary = ghBinaryPath(spec, platform2);
|
|
35408
35852
|
mkdirSync9(dirname9(targetBinary), { recursive: true });
|
|
35409
35853
|
try {
|
|
35410
35854
|
copyFileSync4(innerBinaryPath, targetBinary);
|
|
35411
|
-
if (
|
|
35855
|
+
if (platform2 !== "win32") {
|
|
35412
35856
|
const { chmodSync: chmodSync4 } = await import("node:fs");
|
|
35413
35857
|
chmodSync4(targetBinary, 493);
|
|
35414
35858
|
}
|
|
@@ -35421,11 +35865,11 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
35421
35865
|
log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
|
|
35422
35866
|
return { archiveSha256, binarySha256 };
|
|
35423
35867
|
}
|
|
35424
|
-
async function ensureGithubInstalled(spec, config2, fetchImpl,
|
|
35868
|
+
async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
|
|
35425
35869
|
const outcome = await withInstallLock(spec.githubRepo, async () => {
|
|
35426
35870
|
const { tag, assets, blockedByGrace, reason } = await resolveTargetTag(spec, config2, fetchImpl, signal);
|
|
35427
35871
|
if (!tag) {
|
|
35428
|
-
const installed = isGithubInstalled(spec,
|
|
35872
|
+
const installed = isGithubInstalled(spec, platform2);
|
|
35429
35873
|
if (installed) {
|
|
35430
35874
|
warn2(`[lsp] no eligible release of ${spec.githubRepo} (grace=${config2.graceDays}d); keeping existing install`);
|
|
35431
35875
|
return { started: false, reason: "kept existing install" };
|
|
@@ -35434,7 +35878,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform3, arch,
|
|
|
35434
35878
|
warn2(`[lsp] skipping ${spec.id}: ${fallbackReason}`);
|
|
35435
35879
|
return { started: false, reason: fallbackReason };
|
|
35436
35880
|
}
|
|
35437
|
-
if (isGithubInstalled(spec,
|
|
35881
|
+
if (isGithubInstalled(spec, platform2)) {
|
|
35438
35882
|
const installedMeta = readInstalledMetaIn(ghPackageDir(spec));
|
|
35439
35883
|
if (installedMeta && installedMeta.version === tag) {
|
|
35440
35884
|
return { started: false, reason: "already installed" };
|
|
@@ -35445,7 +35889,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform3, arch,
|
|
|
35445
35889
|
log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
|
|
35446
35890
|
}
|
|
35447
35891
|
}
|
|
35448
|
-
const hashes = await downloadAndInstall(spec, tag, assets,
|
|
35892
|
+
const hashes = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
|
|
35449
35893
|
error52(`[lsp] github install ${spec.id} crashed: ${err}`);
|
|
35450
35894
|
return null;
|
|
35451
35895
|
});
|
|
@@ -37825,6 +38269,48 @@ function relativeToWorktree(fp, worktree) {
|
|
|
37825
38269
|
function readAttachments(data) {
|
|
37826
38270
|
return Array.isArray(data.attachments) ? data.attachments : [];
|
|
37827
38271
|
}
|
|
38272
|
+
var ISSUE_AND_PR_READ_DESCRIPTION = "GitHub issues and pull requests can be read with `issue://NUMBER` and `pr://NUMBER` (or `issue://OWNER/REPO/NUMBER` and `pr://OWNER/REPO/NUMBER`).";
|
|
38273
|
+
function asRecord2(value) {
|
|
38274
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
38275
|
+
}
|
|
38276
|
+
function visionCapabilityForOpenCodeModel(model) {
|
|
38277
|
+
const entry = model;
|
|
38278
|
+
if (Array.isArray(entry?.modalities?.input)) {
|
|
38279
|
+
return entry.modalities.input.includes("image");
|
|
38280
|
+
}
|
|
38281
|
+
return typeof entry?.attachment === "boolean" ? entry.attachment : undefined;
|
|
38282
|
+
}
|
|
38283
|
+
function modelFromOpenCodeProvider(provider, modelID) {
|
|
38284
|
+
const models = provider.models;
|
|
38285
|
+
if (Array.isArray(models)) {
|
|
38286
|
+
return models.find((model) => asRecord2(model)?.id === modelID);
|
|
38287
|
+
}
|
|
38288
|
+
return asRecord2(models)?.[modelID];
|
|
38289
|
+
}
|
|
38290
|
+
async function currentSessionVisionCapability(client, sessionID) {
|
|
38291
|
+
if (!sessionID)
|
|
38292
|
+
return;
|
|
38293
|
+
const promptContext = await resolvePromptContext(client, sessionID);
|
|
38294
|
+
const currentModel = promptContext?.model;
|
|
38295
|
+
if (!currentModel)
|
|
38296
|
+
return;
|
|
38297
|
+
const providerApi = client.provider;
|
|
38298
|
+
if (typeof providerApi?.list !== "function")
|
|
38299
|
+
return;
|
|
38300
|
+
let listed;
|
|
38301
|
+
try {
|
|
38302
|
+
listed = await providerApi.list();
|
|
38303
|
+
} catch {
|
|
38304
|
+
return;
|
|
38305
|
+
}
|
|
38306
|
+
const result = asRecord2(listed);
|
|
38307
|
+
const catalog = asRecord2(result?.data) ?? result;
|
|
38308
|
+
const providers = Array.isArray(catalog?.all) ? catalog.all : Array.isArray(catalog?.providers) ? catalog.providers : [];
|
|
38309
|
+
const provider = providers.map(asRecord2).find((entry) => entry?.id === currentModel.providerID);
|
|
38310
|
+
if (!provider)
|
|
38311
|
+
return;
|
|
38312
|
+
return visionCapabilityForOpenCodeModel(modelFromOpenCodeProvider(provider, currentModel.modelID));
|
|
38313
|
+
}
|
|
37828
38314
|
function persistFilePathAlias(args, context) {
|
|
37829
38315
|
if (typeof args.path === "string" && !Object.hasOwn(args, "filePath")) {
|
|
37830
38316
|
args.filePath = args.path;
|
|
@@ -38012,10 +38498,19 @@ Examples:
|
|
|
38012
38498
|
Read 30 lines from line 200: { "path": "src/app.ts", "offset": 200, "limit": 30 }
|
|
38013
38499
|
List directory: { "path": "src/" }
|
|
38014
38500
|
`;
|
|
38501
|
+
function readDescription(ghReadEnabled) {
|
|
38502
|
+
if (!ghReadEnabled)
|
|
38503
|
+
return READ_DESCRIPTION;
|
|
38504
|
+
return READ_DESCRIPTION.replace(`
|
|
38505
|
+
Examples:`, `
|
|
38506
|
+
${ISSUE_AND_PR_READ_DESCRIPTION}
|
|
38507
|
+
|
|
38508
|
+
Examples:`);
|
|
38509
|
+
}
|
|
38015
38510
|
function createReadTool(ctx) {
|
|
38016
38511
|
return prepareToolMap({
|
|
38017
38512
|
read: {
|
|
38018
|
-
description:
|
|
38513
|
+
description: readDescription(ctx.config.gh_read?.enabled === true),
|
|
38019
38514
|
args: {
|
|
38020
38515
|
filePath: z8.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
38021
38516
|
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
@@ -38066,6 +38561,9 @@ function createReadTool(ctx) {
|
|
|
38066
38561
|
rawArgs.endLine = endLine;
|
|
38067
38562
|
if (rawLimit !== undefined && rawOffset === undefined)
|
|
38068
38563
|
rawArgs.limit = rawLimit;
|
|
38564
|
+
const visionCapability = await currentSessionVisionCapability(ctx.client, context.sessionID);
|
|
38565
|
+
if (visionCapability !== undefined)
|
|
38566
|
+
rawArgs.vision_capability = visionCapability;
|
|
38069
38567
|
const response = await callToolCall(ctx, context, "read", rawArgs);
|
|
38070
38568
|
if (response.success === false) {
|
|
38071
38569
|
throw new Error(response.message || "read failed");
|
|
@@ -38700,7 +39198,7 @@ async function resolveAndGateScope(ctx, context, scope) {
|
|
|
38700
39198
|
}
|
|
38701
39199
|
return { scope: Array.isArray(scope) ? resolved : resolved[0] };
|
|
38702
39200
|
}
|
|
38703
|
-
function
|
|
39201
|
+
function asRecord3(value) {
|
|
38704
39202
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
38705
39203
|
}
|
|
38706
39204
|
function asString(value) {
|
|
@@ -38708,7 +39206,7 @@ function asString(value) {
|
|
|
38708
39206
|
}
|
|
38709
39207
|
function firstRecord(...values) {
|
|
38710
39208
|
for (const value of values) {
|
|
38711
|
-
const record2 =
|
|
39209
|
+
const record2 = asRecord3(value);
|
|
38712
39210
|
if (record2)
|
|
38713
39211
|
return record2;
|
|
38714
39212
|
}
|
|
@@ -38743,7 +39241,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
38743
39241
|
if (!Array.isArray(value))
|
|
38744
39242
|
return [];
|
|
38745
39243
|
return value.flatMap((candidate) => {
|
|
38746
|
-
const entry =
|
|
39244
|
+
const entry = asRecord3(candidate);
|
|
38747
39245
|
const id = asString(entry?.id);
|
|
38748
39246
|
if (!id)
|
|
38749
39247
|
return [];
|
|
@@ -38759,7 +39257,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
38759
39257
|
});
|
|
38760
39258
|
}
|
|
38761
39259
|
function parseInspectTerminal(payload) {
|
|
38762
|
-
const response =
|
|
39260
|
+
const response = asRecord3(payload);
|
|
38763
39261
|
if (!response)
|
|
38764
39262
|
return;
|
|
38765
39263
|
const kind = terminalKind(response);
|
|
@@ -38774,7 +39272,7 @@ function parseInspectTerminal(payload) {
|
|
|
38774
39272
|
waitStampText: kind === "FRESH" ? firstString(waitStamp?.text, waitStamp?.human_text) : undefined
|
|
38775
39273
|
};
|
|
38776
39274
|
}
|
|
38777
|
-
const failedPhaseRecord =
|
|
39275
|
+
const failedPhaseRecord = asRecord3(response.failed_phase);
|
|
38778
39276
|
const failedPhaseId = firstString(failedPhaseRecord?.id, response.failed_phase);
|
|
38779
39277
|
const failedPhase = failedPhaseId ? parseInspectPhaseEntries([
|
|
38780
39278
|
{
|
|
@@ -38870,12 +39368,12 @@ function createInspectTier2IdleScheduler(options) {
|
|
|
38870
39368
|
}
|
|
38871
39369
|
function inspectTools(ctx) {
|
|
38872
39370
|
const inspectTool = {
|
|
38873
|
-
description: "Blocking-fresh codebase health inspection. Each call completes current analysis and produces exactly one terminal result: FRESH includes a wait-stamp and completed phases; INTERRUPTED and PHASE-FAILED retain completed phases, with PHASE-FAILED also reporting its phase attribution and failure reason. `sections` selects drill-down detail, not the categories verified.\n\n" + "Use `scope=` to narrow returned results. Scope filters rendered diagnostics; it does not
|
|
39371
|
+
description: "Blocking-fresh codebase health inspection. Each call completes current analysis and produces exactly one terminal result: FRESH includes a wait-stamp and completed phases; INTERRUPTED and PHASE-FAILED retain completed phases, with PHASE-FAILED also reporting its phase attribution and failure reason. `sections` selects drill-down detail, not the categories verified.\n\n" + "Use `scope=` to narrow returned results. Scope filters rendered diagnostics and limits Rust LSP startup to Cargo workspaces owning the scoped paths; it does not trigger per-file collection work. Scoped files no producer has authoritatively analyzed are reported as named gaps (complete: false). Passive health changes use the alert channel; do not infer inspect completion from that channel.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
|
|
38874
39372
|
|
|
38875
39373
|
` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
|
|
38876
39374
|
args: {
|
|
38877
39375
|
sections: arg(z10.union([z10.string(), z10.array(z10.string())]).optional().describe("Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode. `sections` changes detail, not the categories verified.")),
|
|
38878
|
-
scope: arg(z10.union([z10.string(), z10.array(z10.string())]).optional().describe("Restrict returned results to paths under this scope (file or directory, absolute or relative to project root). `scope=` narrows results; it does not
|
|
39376
|
+
scope: arg(z10.union([z10.string(), z10.array(z10.string())]).optional().describe("Restrict returned results to paths under this scope (file or directory, absolute or relative to project root). `scope=` narrows results and limits Rust LSP startup to owning Cargo workspaces; it does not trigger per-file diagnostic collection. Scoped files no producer has authoritatively analyzed are reported as named gaps (complete: false), never as a clean empty result.")),
|
|
38879
39377
|
topK: arg(z10.number().int().positive().max(100).optional().describe("Max drill-down items per category. Default 20, max 100."))
|
|
38880
39378
|
},
|
|
38881
39379
|
execute: async (args, context) => {
|
|
@@ -39724,7 +40222,9 @@ function semanticTools(ctx) {
|
|
|
39724
40222
|
rawArgs.includeTests = args.includeTests;
|
|
39725
40223
|
if (pathArg)
|
|
39726
40224
|
rawArgs.path = pathArg;
|
|
39727
|
-
const response = await callToolCall(ctx, context, "search", rawArgs
|
|
40225
|
+
const response = await callToolCall(ctx, context, "search", rawArgs, {
|
|
40226
|
+
abortSignal: context.abort
|
|
40227
|
+
});
|
|
39728
40228
|
if (response.success === false) {
|
|
39729
40229
|
const message = typeof response.text === "string" && response.text.length > 0 ? response.text : typeof response.message === "string" && response.message.length > 0 ? response.message : "semantic_search failed";
|
|
39730
40230
|
throw new Error(message);
|
|
@@ -39903,7 +40403,7 @@ var PLUGIN_VERSION = (() => {
|
|
|
39903
40403
|
return "0.0.0";
|
|
39904
40404
|
}
|
|
39905
40405
|
})();
|
|
39906
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
40406
|
+
var ANNOUNCEMENT_VERSION = "0.55.1";
|
|
39907
40407
|
var ANNOUNCEMENT_FEATURES = [
|
|
39908
40408
|
"Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
|
|
39909
40409
|
"aft_zoom handles real-world .jsonc: files with comment banners above the opening brace now resolve, and not-found errors point at the segment that actually failed (thanks @iceteaSA).",
|