@cortexkit/aft-opencode 0.54.0 → 0.55.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/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 +701 -214
- 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/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 };
|
|
@@ -15416,10 +15636,13 @@ function isEditSentinelItem(item) {
|
|
|
15416
15636
|
if (record.oldString === null && ["startLine", "endLine"].some((key) => hasOwn(record, key) && record[key] !== null)) {
|
|
15417
15637
|
return false;
|
|
15418
15638
|
}
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
return
|
|
15639
|
+
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);
|
|
15640
|
+
}
|
|
15641
|
+
function hasMeaningfulFindPayload(item) {
|
|
15642
|
+
return isNonEmptyString(item.oldString);
|
|
15643
|
+
}
|
|
15644
|
+
function isDefaultOccurrence(value) {
|
|
15645
|
+
return value === undefined || value === null || value === 1;
|
|
15423
15646
|
}
|
|
15424
15647
|
function normalizeEditArraySentinels(record) {
|
|
15425
15648
|
const value = record.edits;
|
|
@@ -15472,8 +15695,8 @@ function parseEditArray(value) {
|
|
|
15472
15695
|
}
|
|
15473
15696
|
return value;
|
|
15474
15697
|
}
|
|
15475
|
-
function
|
|
15476
|
-
const
|
|
15698
|
+
function normalizeEditItemSentinels(item) {
|
|
15699
|
+
const hadRangeFields = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
15477
15700
|
for (const key of [
|
|
15478
15701
|
"oldString",
|
|
15479
15702
|
"newString",
|
|
@@ -15486,7 +15709,19 @@ function stripLineRangeSentinels(item) {
|
|
|
15486
15709
|
if (item[key] === null)
|
|
15487
15710
|
delete item[key];
|
|
15488
15711
|
}
|
|
15489
|
-
|
|
15712
|
+
const contentIsEmpty = item.content === "";
|
|
15713
|
+
if (hasMeaningfulFindPayload(item) && (!hasOwn(item, "content") || contentIsEmpty)) {
|
|
15714
|
+
for (const key of ["startLine", "endLine", "content"])
|
|
15715
|
+
delete item[key];
|
|
15716
|
+
if (hadRangeFields) {
|
|
15717
|
+
if (item.replaceAll === false)
|
|
15718
|
+
delete item.replaceAll;
|
|
15719
|
+
if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
|
|
15720
|
+
delete item.occurrence;
|
|
15721
|
+
}
|
|
15722
|
+
return;
|
|
15723
|
+
}
|
|
15724
|
+
if (!isNonEmptyString(item.content))
|
|
15490
15725
|
return;
|
|
15491
15726
|
if (item.oldString === "")
|
|
15492
15727
|
delete item.oldString;
|
|
@@ -15494,7 +15729,7 @@ function stripLineRangeSentinels(item) {
|
|
|
15494
15729
|
delete item.newString;
|
|
15495
15730
|
if (item.replaceAll === false)
|
|
15496
15731
|
delete item.replaceAll;
|
|
15497
|
-
if (item
|
|
15732
|
+
if (hasOwn(item, "occurrence") && isDefaultOccurrence(item.occurrence))
|
|
15498
15733
|
delete item.occurrence;
|
|
15499
15734
|
}
|
|
15500
15735
|
function normalizeEditItem(value, index) {
|
|
@@ -15505,7 +15740,7 @@ function normalizeEditItem(value, index) {
|
|
|
15505
15740
|
const item = copyOwnProperties(source);
|
|
15506
15741
|
normalizeItemAlias(item, "oldString", "oldText");
|
|
15507
15742
|
normalizeItemAlias(item, "newString", "newText");
|
|
15508
|
-
|
|
15743
|
+
normalizeEditItemSentinels(item);
|
|
15509
15744
|
const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
|
|
15510
15745
|
const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
15511
15746
|
if (hasFindField && hasRangeField) {
|
|
@@ -30707,6 +30942,9 @@ var GhShimConfigSchema = exports_external.object({
|
|
|
30707
30942
|
enabled: exports_external.boolean().optional(),
|
|
30708
30943
|
binary_path: exports_external.string().trim().refine(isAbsolute6, "gh_shim.binary_path must be absolute").optional()
|
|
30709
30944
|
});
|
|
30945
|
+
var GhReadConfigSchema = exports_external.object({
|
|
30946
|
+
enabled: exports_external.boolean().optional()
|
|
30947
|
+
});
|
|
30710
30948
|
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
30949
|
var GitConfigSchema = exports_external.object({
|
|
30712
30950
|
co_author: GitCoAuthorSchema.optional()
|
|
@@ -30768,6 +31006,7 @@ var AftConfigFieldsSchema = exports_external.object({
|
|
|
30768
31006
|
bridge: BridgeConfigSchema.optional(),
|
|
30769
31007
|
subc: SubcConfigSchema.optional(),
|
|
30770
31008
|
gh_shim: GhShimConfigSchema.optional(),
|
|
31009
|
+
gh_read: GhReadConfigSchema.optional(),
|
|
30771
31010
|
git: GitConfigSchema.optional()
|
|
30772
31011
|
});
|
|
30773
31012
|
var HarnessOverrideSchema = exports_external.preprocess((value) => {
|
|
@@ -31246,6 +31485,8 @@ function getStrippedTopLevelKeys(override) {
|
|
|
31246
31485
|
stripped.push("subc");
|
|
31247
31486
|
if (override.gh_shim !== undefined)
|
|
31248
31487
|
stripped.push("gh_shim");
|
|
31488
|
+
if (override.gh_read !== undefined)
|
|
31489
|
+
stripped.push("gh_read");
|
|
31249
31490
|
if (override.disabled_tools?.includes("aft_safety"))
|
|
31250
31491
|
stripped.push("disabled_tools.aft_safety");
|
|
31251
31492
|
return stripped;
|
|
@@ -31463,6 +31704,8 @@ var UNKNOWN_COMPLETION_CAP = 32;
|
|
|
31463
31704
|
var DEFAULT_SESSION_ID2 = "__default__";
|
|
31464
31705
|
var LOG_PREFIX = "[aft-plugin] bg-notifications:";
|
|
31465
31706
|
var SUBC_NUDGE_LOG_INTERVAL_MS = 60000;
|
|
31707
|
+
var DEFAULT_BG_HOP_TIMEOUT_MS = 15000;
|
|
31708
|
+
var bgHopTimeoutMs = DEFAULT_BG_HOP_TIMEOUT_MS;
|
|
31466
31709
|
var subcNudgesInFlight = new Map;
|
|
31467
31710
|
var subcNudgeLogState = new Map;
|
|
31468
31711
|
function consumeBgCompletion(sessionID, taskId) {
|
|
@@ -31690,11 +31933,20 @@ function handleSubcBgEventsNudge(drainContext) {
|
|
|
31690
31933
|
}
|
|
31691
31934
|
async function handleSubcBgEventsNudgeOnce(drainContext) {
|
|
31692
31935
|
bridgeForDrain(drainContext);
|
|
31693
|
-
stateFor(drainContext.sessionID)
|
|
31936
|
+
const state = stateFor(drainContext.sessionID);
|
|
31937
|
+
state.wakeDeferredTaskIds.clear();
|
|
31938
|
+
rearmHardStoppedWake(state, drainContext);
|
|
31694
31939
|
await triggerWakeIfPending(drainContext, false, true, true);
|
|
31695
31940
|
}
|
|
31696
31941
|
function logSubcNudgeLifecycle(drainContext, kind, message) {
|
|
31697
|
-
|
|
31942
|
+
logDeliveryHop(drainContext, kind, message, {
|
|
31943
|
+
event: "subc_bg_nudge_delivery",
|
|
31944
|
+
cause: kind
|
|
31945
|
+
});
|
|
31946
|
+
}
|
|
31947
|
+
function logDeliveryHop(drainContext, kind, message, data, level = "info") {
|
|
31948
|
+
const taskKey = typeof data.task_id === "string" ? data.task_id : Array.isArray(data.task_ids) ? data.task_ids.join(",") : "";
|
|
31949
|
+
const key = `${kind}\x00${taskKey}\x00${drainContext.directory}\x00${drainContext.sessionID}`;
|
|
31698
31950
|
const now = Date.now();
|
|
31699
31951
|
const state = subcNudgeLogState.get(key);
|
|
31700
31952
|
if (state && now - state.lastEmittedAt < SUBC_NUDGE_LOG_INTERVAL_MS) {
|
|
@@ -31703,13 +31955,59 @@ function logSubcNudgeLifecycle(drainContext, kind, message) {
|
|
|
31703
31955
|
}
|
|
31704
31956
|
const suppressed = state?.suppressed ?? 0;
|
|
31705
31957
|
subcNudgeLogState.set(key, { lastEmittedAt: now, suppressed: 0 });
|
|
31706
|
-
|
|
31707
|
-
|
|
31708
|
-
cause: kind,
|
|
31958
|
+
const payload = {
|
|
31959
|
+
...data,
|
|
31709
31960
|
canonical_root: drainContext.directory,
|
|
31710
31961
|
suppressed
|
|
31962
|
+
};
|
|
31963
|
+
if (level === "warn") {
|
|
31964
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} ${message}`, payload);
|
|
31965
|
+
} else {
|
|
31966
|
+
sessionLog(drainContext.sessionID, `${LOG_PREFIX} ${message}`, payload);
|
|
31967
|
+
}
|
|
31968
|
+
}
|
|
31969
|
+
function logPerTaskDeliveryHop(drainContext, kind, message, taskIDs, data, level = "info") {
|
|
31970
|
+
for (const taskID of taskIDs) {
|
|
31971
|
+
logDeliveryHop(drainContext, kind, message, { ...data, task_id: taskID, task_ids: taskIDs }, level);
|
|
31972
|
+
}
|
|
31973
|
+
}
|
|
31974
|
+
function rearmHardStoppedWake(state, drainContext) {
|
|
31975
|
+
if (!state.wakeHardStopped)
|
|
31976
|
+
return;
|
|
31977
|
+
state.wakeHardStopped = false;
|
|
31978
|
+
state.wakeRetryAttempts = 0;
|
|
31979
|
+
state.retryDelayMs = null;
|
|
31980
|
+
logDeliveryHop(drainContext, "inject-rearmed", "delivery retry re-armed", {
|
|
31981
|
+
event: "bash_completion_inject_rearmed",
|
|
31982
|
+
task_ids: state.pendingCompletions.map((completion) => completion.task_id)
|
|
31711
31983
|
});
|
|
31712
31984
|
}
|
|
31985
|
+
async function withBgHopTimeout(operation, label) {
|
|
31986
|
+
let timer;
|
|
31987
|
+
try {
|
|
31988
|
+
return await Promise.race([
|
|
31989
|
+
operation,
|
|
31990
|
+
new Promise((_, reject) => {
|
|
31991
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${bgHopTimeoutMs}ms`)), bgHopTimeoutMs);
|
|
31992
|
+
timer.unref?.();
|
|
31993
|
+
})
|
|
31994
|
+
]);
|
|
31995
|
+
} finally {
|
|
31996
|
+
if (timer)
|
|
31997
|
+
clearTimeout(timer);
|
|
31998
|
+
}
|
|
31999
|
+
}
|
|
32000
|
+
function promptAsyncFailure(response) {
|
|
32001
|
+
if (!response || typeof response !== "object" || Array.isArray(response))
|
|
32002
|
+
return null;
|
|
32003
|
+
const record2 = response;
|
|
32004
|
+
if (record2.error != null) {
|
|
32005
|
+
return typeof record2.error === "string" ? record2.error : JSON.stringify(record2.error);
|
|
32006
|
+
}
|
|
32007
|
+
if (record2.success === false)
|
|
32008
|
+
return String(record2.message ?? "promptAsync returned success=false");
|
|
32009
|
+
return null;
|
|
32010
|
+
}
|
|
31713
32011
|
async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredCompletions = true, forceDrain = false) {
|
|
31714
32012
|
const state = stateFor(drainContext.sessionID);
|
|
31715
32013
|
if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
|
|
@@ -31732,11 +32030,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31732
32030
|
}
|
|
31733
32031
|
return drainContext.client;
|
|
31734
32032
|
};
|
|
31735
|
-
const sendPrompt = async (
|
|
31736
|
-
|
|
32033
|
+
const sendPrompt = async (client) => {
|
|
32034
|
+
const promptAsync = client.session?.promptAsync;
|
|
32035
|
+
if (typeof promptAsync !== "function") {
|
|
31737
32036
|
throw new Error("wake client.session.promptAsync is unavailable");
|
|
31738
32037
|
}
|
|
31739
|
-
const promptContext = await resolvePromptContext(
|
|
32038
|
+
const promptContext = await resolvePromptContext(client, drainContext.sessionID);
|
|
31740
32039
|
const body = {
|
|
31741
32040
|
noReply: false,
|
|
31742
32041
|
parts: [{ type: "text", text: reminder, synthetic: true }]
|
|
@@ -31751,52 +32050,73 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31751
32050
|
}
|
|
31752
32051
|
if (promptContext?.variant)
|
|
31753
32052
|
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",
|
|
32053
|
+
const attemptPrompt = async (attemptBody, mode) => {
|
|
32054
|
+
const deliveryID2 = `aftdel_${randomUUID2()}`;
|
|
32055
|
+
const wakeMeta = {
|
|
31783
32056
|
delivery_id: deliveryID2,
|
|
31784
32057
|
attempt: state.wakeRetryAttempts + 1,
|
|
31785
32058
|
task_ids: taskIDs,
|
|
31786
|
-
|
|
32059
|
+
directory: drainContext.directory,
|
|
32060
|
+
reminder_sha256: hashReminder(reminder),
|
|
32061
|
+
reminder_chars: reminder.length,
|
|
32062
|
+
injection_mode: mode,
|
|
32063
|
+
prompt_context: promptContext ? {
|
|
32064
|
+
agent: promptContext.agent,
|
|
32065
|
+
model: promptContext.model ? {
|
|
32066
|
+
providerID: promptContext.model.providerID,
|
|
32067
|
+
modelID: promptContext.model.modelID
|
|
32068
|
+
} : null,
|
|
32069
|
+
variant: promptContext.variant ?? null
|
|
32070
|
+
} : null
|
|
32071
|
+
};
|
|
32072
|
+
logPerTaskDeliveryHop(drainContext, `inject-start-${mode}`, "session inject start", taskIDs, { event: "bash_completion_wake_prompt_async_start", ...wakeMeta });
|
|
32073
|
+
try {
|
|
32074
|
+
const response = await withBgHopTimeout(Promise.resolve(promptAsync({
|
|
32075
|
+
path: { id: drainContext.sessionID },
|
|
32076
|
+
body: attemptBody
|
|
32077
|
+
})), "session promptAsync injection");
|
|
32078
|
+
const failure = promptAsyncFailure(response);
|
|
32079
|
+
if (failure)
|
|
32080
|
+
throw new Error(failure);
|
|
32081
|
+
} catch (err) {
|
|
32082
|
+
logPerTaskDeliveryHop(drainContext, "inject-error", "session inject failed", taskIDs, {
|
|
32083
|
+
event: "bash_completion_wake_prompt_async_error",
|
|
32084
|
+
delivery_id: deliveryID2,
|
|
32085
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
32086
|
+
injection_mode: mode,
|
|
32087
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32088
|
+
}, "warn");
|
|
32089
|
+
throw err;
|
|
32090
|
+
}
|
|
32091
|
+
logPerTaskDeliveryHop(drainContext, `inject-ok-${mode}`, "session inject ok", taskIDs, {
|
|
32092
|
+
event: "bash_completion_wake_prompt_async_ok",
|
|
32093
|
+
delivery_id: deliveryID2,
|
|
32094
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
32095
|
+
injection_mode: mode
|
|
31787
32096
|
});
|
|
31788
|
-
|
|
32097
|
+
return deliveryID2;
|
|
32098
|
+
};
|
|
32099
|
+
try {
|
|
32100
|
+
return await attemptPrompt(body, "mirrored-context");
|
|
32101
|
+
} catch (err) {
|
|
32102
|
+
if (!promptContext?.model)
|
|
32103
|
+
throw err;
|
|
32104
|
+
const fallbackBody = { ...body };
|
|
32105
|
+
delete fallbackBody.model;
|
|
32106
|
+
delete fallbackBody.variant;
|
|
32107
|
+
return attemptPrompt(fallbackBody, "model-free-fallback");
|
|
31789
32108
|
}
|
|
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
32109
|
};
|
|
31798
|
-
|
|
31799
|
-
|
|
32110
|
+
let deliveryID;
|
|
32111
|
+
try {
|
|
32112
|
+
deliveryID = await sendPrompt(getInProcessClient());
|
|
32113
|
+
} catch (err) {
|
|
32114
|
+
logPerTaskDeliveryHop(drainContext, "inject-error", "session inject failed", taskIDs, {
|
|
32115
|
+
event: "bash_completion_wake_prompt_async_error",
|
|
32116
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32117
|
+
}, "warn");
|
|
32118
|
+
throw err;
|
|
32119
|
+
}
|
|
31800
32120
|
markDeliveredAwaitingAck(state, taskIDs);
|
|
31801
32121
|
const acked = await ackCompletions(drainContext, deliveredCompletions, deliveryID);
|
|
31802
32122
|
if (acked)
|
|
@@ -31812,7 +32132,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
31812
32132
|
});
|
|
31813
32133
|
return;
|
|
31814
32134
|
}
|
|
31815
|
-
|
|
32135
|
+
if (hardStopped) {
|
|
32136
|
+
logDeliveryHop(drainContext, "inject-hard-stopped", "delivery retries paused", {
|
|
32137
|
+
event: "bash_completion_inject_hard_stopped",
|
|
32138
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32139
|
+
});
|
|
32140
|
+
}
|
|
31816
32141
|
}, drainContext.sessionID, includeDeferredCompletions);
|
|
31817
32142
|
}
|
|
31818
32143
|
function formatSystemReminder(completions) {
|
|
@@ -31899,14 +32224,21 @@ async function drainCompletions(drainContext) {
|
|
|
31899
32224
|
try {
|
|
31900
32225
|
const bridge = bridgeForDrain(drainContext);
|
|
31901
32226
|
const state = stateFor(drainContext.sessionID);
|
|
31902
|
-
const response = await bridge.send("bash_drain_completions", {
|
|
31903
|
-
session_id: drainContext.sessionID
|
|
31904
|
-
});
|
|
32227
|
+
const response = await withBgHopTimeout(bridge.send("bash_drain_completions", { session_id: drainContext.sessionID }, { timeoutMs: bgHopTimeoutMs }), "bash_drain_completions");
|
|
31905
32228
|
if (response.success === false) {
|
|
31906
|
-
|
|
32229
|
+
logDeliveryHop(drainContext, "drain-error", "drain failed", {
|
|
32230
|
+
event: "bash_completion_drain_error",
|
|
32231
|
+
cause: String(response.message ?? "unknown error")
|
|
32232
|
+
}, "warn");
|
|
31907
32233
|
return;
|
|
31908
32234
|
}
|
|
31909
32235
|
state.forcedDrainCompleted = true;
|
|
32236
|
+
const drainedTaskIds = Array.isArray(response.bg_completions) ? response.bg_completions.filter(isBgCompletion).map((completion) => completion.task_id) : [];
|
|
32237
|
+
logDeliveryHop(drainContext, "drain-ok", "drain ok", {
|
|
32238
|
+
event: "bash_completion_drain_ok",
|
|
32239
|
+
count: drainedTaskIds.length,
|
|
32240
|
+
task_ids: drainedTaskIds
|
|
32241
|
+
});
|
|
31910
32242
|
const reackTaskIds = reconcileAwaitingAck(state, response.bg_completions);
|
|
31911
32243
|
ingestDrainedBgCompletions(drainContext.sessionID, response.bg_completions);
|
|
31912
32244
|
if (reackTaskIds.length > 0) {
|
|
@@ -31922,7 +32254,10 @@ async function drainCompletions(drainContext) {
|
|
|
31922
32254
|
} catch (err) {
|
|
31923
32255
|
if (drainContext.nudgeRef && isTerminalNudgeError(err))
|
|
31924
32256
|
throw err;
|
|
31925
|
-
|
|
32257
|
+
logDeliveryHop(drainContext, "drain-error", "drain failed", {
|
|
32258
|
+
event: "bash_completion_drain_error",
|
|
32259
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32260
|
+
}, "warn");
|
|
31926
32261
|
}
|
|
31927
32262
|
}
|
|
31928
32263
|
async function ackCompletions(drainContext, completions, deliveryID) {
|
|
@@ -31931,24 +32266,26 @@ async function ackCompletions(drainContext, completions, deliveryID) {
|
|
|
31931
32266
|
return true;
|
|
31932
32267
|
try {
|
|
31933
32268
|
const bridge = bridgeForDrain(drainContext);
|
|
31934
|
-
const response = await bridge.send("bash_ack_completions", {
|
|
31935
|
-
session_id: drainContext.sessionID,
|
|
31936
|
-
task_ids: taskIds
|
|
31937
|
-
});
|
|
32269
|
+
const response = await withBgHopTimeout(bridge.send("bash_ack_completions", { session_id: drainContext.sessionID, task_ids: taskIds }, { timeoutMs: bgHopTimeoutMs }), "bash_ack_completions");
|
|
31938
32270
|
if (response.success === false) {
|
|
31939
|
-
|
|
32271
|
+
logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
|
|
32272
|
+
event: "bash_completion_ack_error",
|
|
32273
|
+
cause: String(response.message ?? "unknown error")
|
|
32274
|
+
}, "warn");
|
|
31940
32275
|
return false;
|
|
31941
32276
|
}
|
|
31942
|
-
|
|
32277
|
+
logPerTaskDeliveryHop(drainContext, "ack-ok", "ack ok", taskIds, {
|
|
31943
32278
|
event: "bash_completion_ack_ok",
|
|
31944
|
-
delivery_id: deliveryID ?? null
|
|
31945
|
-
task_ids: taskIds
|
|
32279
|
+
delivery_id: deliveryID ?? null
|
|
31946
32280
|
});
|
|
31947
32281
|
return true;
|
|
31948
32282
|
} catch (err) {
|
|
31949
32283
|
if (drainContext.nudgeRef && isTerminalNudgeError(err))
|
|
31950
32284
|
throw err;
|
|
31951
|
-
|
|
32285
|
+
logPerTaskDeliveryHop(drainContext, "ack-error", "ack failed", taskIds, {
|
|
32286
|
+
event: "bash_completion_ack_error",
|
|
32287
|
+
cause: err instanceof Error ? err.message : String(err)
|
|
32288
|
+
}, "warn");
|
|
31952
32289
|
return false;
|
|
31953
32290
|
}
|
|
31954
32291
|
}
|
|
@@ -32540,6 +32877,15 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
|
|
|
32540
32877
|
// src/bash-wait-detach.ts
|
|
32541
32878
|
var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach";
|
|
32542
32879
|
var EMPTY_DETACH_MESSAGE = "(requested background detach)";
|
|
32880
|
+
var STANDALONE_DETACH_KEYWORD_SOURCE = `(^|[^\\p{L}\\p{N}_])${BASH_WAIT_DETACH_MAGIC_KEYWORD}(?![\\p{L}\\p{N}_])`;
|
|
32881
|
+
var STANDALONE_DETACH_KEYWORD_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "u");
|
|
32882
|
+
var STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SOURCE, "gu");
|
|
32883
|
+
function containsStandaloneDetachKeyword(messageText) {
|
|
32884
|
+
return STANDALONE_DETACH_KEYWORD_PATTERN.test(messageText);
|
|
32885
|
+
}
|
|
32886
|
+
function stripStandaloneDetachKeywords(messageText) {
|
|
32887
|
+
return messageText.replace(STANDALONE_DETACH_KEYWORDS_PATTERN, "$1");
|
|
32888
|
+
}
|
|
32543
32889
|
function userTextParts(output) {
|
|
32544
32890
|
if (!output || typeof output !== "object")
|
|
32545
32891
|
return [];
|
|
@@ -32561,9 +32907,9 @@ function stripUserMessageDetachKeyword(output) {
|
|
|
32561
32907
|
const parts = userTextParts(output);
|
|
32562
32908
|
const original = parts.map((part) => part.text).join(`
|
|
32563
32909
|
`);
|
|
32564
|
-
if (!original
|
|
32910
|
+
if (!containsStandaloneDetachKeyword(original))
|
|
32565
32911
|
return original;
|
|
32566
|
-
const stripped = parts.map((part) => part.text
|
|
32912
|
+
const stripped = parts.map((part) => stripStandaloneDetachKeywords(part.text)).map((text) => text.replace(/[ \t]{2,}/g, " "));
|
|
32567
32913
|
if (stripped.join(`
|
|
32568
32914
|
`).trim() === "") {
|
|
32569
32915
|
if (parts[0])
|
|
@@ -32579,7 +32925,7 @@ function stripUserMessageDetachKeyword(output) {
|
|
|
32579
32925
|
`);
|
|
32580
32926
|
}
|
|
32581
32927
|
function shouldDetachBashWaitOnUserMessage(config2, messageText) {
|
|
32582
|
-
return resolveBashConfig(config2).detach_on_user_message || messageText
|
|
32928
|
+
return resolveBashConfig(config2).detach_on_user_message || containsStandaloneDetachKeyword(messageText);
|
|
32583
32929
|
}
|
|
32584
32930
|
async function sendBashWaitDetach(bridge, sessionID) {
|
|
32585
32931
|
const response = await bridge.send("bash_wait_detach", { session_id: sessionID }, { keepBridgeOnTimeout: true, transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS });
|
|
@@ -33169,40 +33515,32 @@ import {
|
|
|
33169
33515
|
import { dirname as dirname8 } from "node:path";
|
|
33170
33516
|
|
|
33171
33517
|
// src/hooks/auto-update-checker/cache.ts
|
|
33172
|
-
import { spawn as
|
|
33518
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
33173
33519
|
import { cpSync, existsSync as existsSync14, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
33174
33520
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
33175
33521
|
import { basename as basename3, dirname as dirname7, join as join16 } from "node:path";
|
|
33176
33522
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
33177
33523
|
|
|
33178
33524
|
// src/hooks/auto-update-checker/checker.ts
|
|
33179
|
-
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
33180
33525
|
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
33526
|
import { dirname as dirname6, isAbsolute as isAbsolute8, join as join15, resolve as resolve9 } from "node:path";
|
|
33183
33527
|
import { fileURLToPath } from "node:url";
|
|
33528
|
+
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
33184
33529
|
|
|
33185
33530
|
// src/hooks/auto-update-checker/constants.ts
|
|
33186
|
-
import { homedir as homedir13, platform as platform2 } from "node:os";
|
|
33187
33531
|
import { join as join14 } from "node:path";
|
|
33188
33532
|
var PACKAGE_NAME = "@cortexkit/aft-opencode";
|
|
33189
33533
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
33190
33534
|
var NPM_FETCH_TIMEOUT = 1e4;
|
|
33191
|
-
function
|
|
33192
|
-
|
|
33193
|
-
return join14(process.env.LOCALAPPDATA ?? homedir13(), "opencode");
|
|
33194
|
-
}
|
|
33195
|
-
return join14(homedir13(), ".cache", "opencode");
|
|
33535
|
+
function cacheDir() {
|
|
33536
|
+
return join14(getOpenCodeCacheRoot(), "packages");
|
|
33196
33537
|
}
|
|
33197
|
-
function
|
|
33198
|
-
|
|
33199
|
-
|
|
33200
|
-
|
|
33201
|
-
return join14(
|
|
33538
|
+
function userOpenCodeConfig() {
|
|
33539
|
+
return join14(getOpenCodeConfigRoot(), "opencode.json");
|
|
33540
|
+
}
|
|
33541
|
+
function userOpenCodeConfigJsonc() {
|
|
33542
|
+
return join14(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
33202
33543
|
}
|
|
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
33544
|
|
|
33207
33545
|
// src/hooks/auto-update-checker/types.ts
|
|
33208
33546
|
var NpmPackageEnvelopeSchema = exports_external.object({
|
|
@@ -33262,8 +33600,8 @@ function getConfigPaths(directory) {
|
|
|
33262
33600
|
return [
|
|
33263
33601
|
join15(directory, ".opencode", "opencode.json"),
|
|
33264
33602
|
join15(directory, ".opencode", "opencode.jsonc"),
|
|
33265
|
-
|
|
33266
|
-
|
|
33603
|
+
userOpenCodeConfig(),
|
|
33604
|
+
userOpenCodeConfigJsonc()
|
|
33267
33605
|
];
|
|
33268
33606
|
}
|
|
33269
33607
|
function resolvePathPluginSpec(spec, configPath) {
|
|
@@ -33368,7 +33706,7 @@ function findPluginEntry(directory) {
|
|
|
33368
33706
|
}
|
|
33369
33707
|
var cachedPackageVersion = null;
|
|
33370
33708
|
function getSpecCachePackageJsonPath(spec) {
|
|
33371
|
-
return join15(
|
|
33709
|
+
return join15(cacheDir(), spec, "node_modules", PACKAGE_NAME, "package.json");
|
|
33372
33710
|
}
|
|
33373
33711
|
function getCachedVersion(spec) {
|
|
33374
33712
|
if (!spec && cachedPackageVersion)
|
|
@@ -33377,7 +33715,7 @@ function getCachedVersion(spec) {
|
|
|
33377
33715
|
getCurrentRuntimePackageJsonPath(),
|
|
33378
33716
|
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
33379
33717
|
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
33380
|
-
join15(
|
|
33718
|
+
join15(getOpenCodeCacheRoot(), "node_modules", PACKAGE_NAME, "package.json")
|
|
33381
33719
|
].filter(isString);
|
|
33382
33720
|
for (const packageJsonPath of candidates) {
|
|
33383
33721
|
try {
|
|
@@ -33424,6 +33762,7 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
33424
33762
|
|
|
33425
33763
|
// src/hooks/auto-update-checker/cache.ts
|
|
33426
33764
|
var pendingSnapshots = new Map;
|
|
33765
|
+
var quarantinedInstallDirs = new Set;
|
|
33427
33766
|
function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
33428
33767
|
const packageDir = join16(installDir, "node_modules", packageName);
|
|
33429
33768
|
const lockfilePath = join16(installDir, "package-lock.json");
|
|
@@ -33517,6 +33856,13 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
|
|
|
33517
33856
|
return false;
|
|
33518
33857
|
}
|
|
33519
33858
|
}
|
|
33859
|
+
function restorePendingSnapshot(installDir) {
|
|
33860
|
+
const snapshot = pendingSnapshots.get(installDir);
|
|
33861
|
+
if (!snapshot)
|
|
33862
|
+
return;
|
|
33863
|
+
pendingSnapshots.delete(installDir);
|
|
33864
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
33865
|
+
}
|
|
33520
33866
|
function removeInstalledPackage(installDir, packageName) {
|
|
33521
33867
|
const packageDir = join16(installDir, "node_modules", packageName);
|
|
33522
33868
|
if (!existsSync14(packageDir))
|
|
@@ -33537,9 +33883,10 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
|
|
|
33537
33883
|
}
|
|
33538
33884
|
return null;
|
|
33539
33885
|
}
|
|
33540
|
-
const
|
|
33886
|
+
const cacheRoot = dirname7(cacheDir());
|
|
33887
|
+
const legacyPackageJsonPath = join16(cacheRoot, "package.json");
|
|
33541
33888
|
if (existsSync14(legacyPackageJsonPath)) {
|
|
33542
|
-
return { installDir:
|
|
33889
|
+
return { installDir: cacheRoot, packageJsonPath: legacyPackageJsonPath };
|
|
33543
33890
|
}
|
|
33544
33891
|
return null;
|
|
33545
33892
|
}
|
|
@@ -33550,6 +33897,12 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
|
|
|
33550
33897
|
warn2("[auto-update-checker] No install context found for auto-update");
|
|
33551
33898
|
return null;
|
|
33552
33899
|
}
|
|
33900
|
+
if (quarantinedInstallDirs.has(installContext.installDir)) {
|
|
33901
|
+
const recoverySnapshot = pendingSnapshots.get(installContext.installDir);
|
|
33902
|
+
const recoveryDetail = recoverySnapshot ? ` Recovery snapshot: ${recoverySnapshot.tempDir}` : "";
|
|
33903
|
+
warn2(`[auto-update-checker] Auto-update blocked after unconfirmed npm termination; ` + `restart OpenCode to retry.${recoveryDetail}`);
|
|
33904
|
+
return null;
|
|
33905
|
+
}
|
|
33553
33906
|
const snapshot = createAutoUpdateSnapshot(installContext.installDir, installContext.packageJsonPath, packageName);
|
|
33554
33907
|
pendingSnapshots.set(installContext.installDir, snapshot);
|
|
33555
33908
|
if (!ensureDependencyVersion(installContext.packageJsonPath, packageName, version2)) {
|
|
@@ -33573,18 +33926,29 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33573
33926
|
let timeout = null;
|
|
33574
33927
|
let stderrTail = "";
|
|
33575
33928
|
try {
|
|
33576
|
-
if (options.signal?.aborted)
|
|
33929
|
+
if (options.signal?.aborted) {
|
|
33930
|
+
restorePendingSnapshot(installDir);
|
|
33577
33931
|
return { ok: false, reason: "aborted" };
|
|
33932
|
+
}
|
|
33578
33933
|
const npm = resolveNpm();
|
|
33579
33934
|
if (!npm) {
|
|
33935
|
+
restorePendingSnapshot(installDir);
|
|
33580
33936
|
const reason = "npm not found on PATH or in known version-manager locations";
|
|
33581
33937
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33582
33938
|
return { ok: false, reason };
|
|
33583
33939
|
}
|
|
33584
|
-
const
|
|
33940
|
+
const invocation = npmInvocation(npm, [
|
|
33941
|
+
"install",
|
|
33942
|
+
"--no-audit",
|
|
33943
|
+
"--no-fund",
|
|
33944
|
+
"--no-progress",
|
|
33945
|
+
"--ignore-scripts"
|
|
33946
|
+
]);
|
|
33947
|
+
const proc = spawn4(invocation.command, invocation.args, {
|
|
33585
33948
|
cwd: installDir,
|
|
33586
33949
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33587
|
-
env: npmSpawnEnv(npm)
|
|
33950
|
+
env: { ...npmSpawnEnv(npm), ...invocation.env },
|
|
33951
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
33588
33952
|
});
|
|
33589
33953
|
proc.stderr?.on("data", (chunk) => {
|
|
33590
33954
|
stderrTail += chunk.toString("utf8");
|
|
@@ -33593,12 +33957,11 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33593
33957
|
}
|
|
33594
33958
|
});
|
|
33595
33959
|
proc.stdout?.on("data", () => {});
|
|
33960
|
+
let terminationPromise = null;
|
|
33596
33961
|
const abortProcess = () => {
|
|
33597
|
-
|
|
33598
|
-
|
|
33599
|
-
} catch {}
|
|
33962
|
+
terminationPromise ??= terminateNpmProcessTree(proc, invocation);
|
|
33963
|
+
return terminationPromise;
|
|
33600
33964
|
};
|
|
33601
|
-
options.signal?.addEventListener("abort", abortProcess, { once: true });
|
|
33602
33965
|
const exitPromise = new Promise((resolveExit) => {
|
|
33603
33966
|
proc.on("error", (err) => resolveExit({ ok: false, reason: `spawn error: ${String(err)}` }));
|
|
33604
33967
|
proc.on("exit", (code) => resolveExit(code === 0 ? { ok: true } : { ok: false, reason: `npm install exited with code ${code ?? "signal/unknown"}` }));
|
|
@@ -33606,16 +33969,29 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33606
33969
|
const timeoutPromise = new Promise((resolveTimeout) => {
|
|
33607
33970
|
timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
|
|
33608
33971
|
});
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
|
|
33618
|
-
|
|
33972
|
+
let resolveAbort;
|
|
33973
|
+
const abortPromise = new Promise((resolve10) => {
|
|
33974
|
+
resolveAbort = resolve10;
|
|
33975
|
+
});
|
|
33976
|
+
const onAbort = () => resolveAbort("abort");
|
|
33977
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
33978
|
+
if (options.signal?.aborted)
|
|
33979
|
+
onAbort();
|
|
33980
|
+
const result = await Promise.race([exitPromise, timeoutPromise, abortPromise]);
|
|
33981
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
33982
|
+
if (result === "timeout" || result === "abort") {
|
|
33983
|
+
try {
|
|
33984
|
+
await abortProcess();
|
|
33985
|
+
} catch (error53) {
|
|
33986
|
+
quarantinedInstallDirs.add(installDir);
|
|
33987
|
+
const recoverySnapshot = pendingSnapshots.get(installDir);
|
|
33988
|
+
const recoveryDetail = recoverySnapshot ? `; auto-update quarantined for this session; recovery snapshot: ${recoverySnapshot.tempDir}` : "; auto-update quarantined for this session";
|
|
33989
|
+
const reason2 = `termination outcome unknown: ${String(error53)}${recoveryDetail}`;
|
|
33990
|
+
warnNpmInstallFailure(reason2, stderrTail);
|
|
33991
|
+
return { ok: false, reason: reason2, stderrTail: stderrTail || undefined };
|
|
33992
|
+
}
|
|
33993
|
+
restorePendingSnapshot(installDir);
|
|
33994
|
+
const reason = result === "abort" ? "aborted" : "timeout";
|
|
33619
33995
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33620
33996
|
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
33621
33997
|
}
|
|
@@ -33631,11 +34007,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
33631
34007
|
}
|
|
33632
34008
|
return { ...result, stderrTail: stderrTail || undefined };
|
|
33633
34009
|
} catch (err) {
|
|
33634
|
-
|
|
33635
|
-
if (snapshot) {
|
|
33636
|
-
pendingSnapshots.delete(installDir);
|
|
33637
|
-
restoreAutoUpdateSnapshot(snapshot);
|
|
33638
|
-
}
|
|
34010
|
+
restorePendingSnapshot(installDir);
|
|
33639
34011
|
const reason = `exception: ${String(err)}`;
|
|
33640
34012
|
warnNpmInstallFailure(reason, stderrTail);
|
|
33641
34013
|
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
@@ -33910,7 +34282,7 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
33910
34282
|
}
|
|
33911
34283
|
|
|
33912
34284
|
// src/lsp-auto-install.ts
|
|
33913
|
-
import { spawn as
|
|
34285
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
33914
34286
|
import { createHash as createHash6 } from "node:crypto";
|
|
33915
34287
|
import {
|
|
33916
34288
|
createReadStream,
|
|
@@ -33925,6 +34297,7 @@ import {
|
|
|
33925
34297
|
import { join as join19 } from "node:path";
|
|
33926
34298
|
|
|
33927
34299
|
// src/lsp-cache.ts
|
|
34300
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33928
34301
|
import {
|
|
33929
34302
|
closeSync as closeSync6,
|
|
33930
34303
|
mkdirSync as mkdirSync7,
|
|
@@ -34006,27 +34379,46 @@ var STALE_LOCK_MS2 = 30 * 60 * 1000;
|
|
|
34006
34379
|
function acquireInstallLock(lockKey) {
|
|
34007
34380
|
mkdirSync7(lspPackageDir(lockKey), { recursive: true });
|
|
34008
34381
|
const lock = lockPath(lockKey);
|
|
34382
|
+
const token = randomUUID3();
|
|
34009
34383
|
const tryClaim = () => {
|
|
34010
34384
|
try {
|
|
34011
34385
|
const fd = openSync6(lock, "wx");
|
|
34012
34386
|
try {
|
|
34013
34387
|
writeFileSync8(fd, `${process.pid}
|
|
34014
34388
|
${new Date().toISOString()}
|
|
34389
|
+
${token}
|
|
34015
34390
|
`);
|
|
34016
34391
|
} finally {
|
|
34017
34392
|
closeSync6(fd);
|
|
34018
34393
|
}
|
|
34019
|
-
|
|
34394
|
+
let retained = false;
|
|
34395
|
+
let released = false;
|
|
34396
|
+
return {
|
|
34397
|
+
retain() {
|
|
34398
|
+
retained = true;
|
|
34399
|
+
},
|
|
34400
|
+
release() {
|
|
34401
|
+
if (released)
|
|
34402
|
+
return;
|
|
34403
|
+
released = true;
|
|
34404
|
+
if (retained) {
|
|
34405
|
+
warn2(`[lsp] retaining install lock for ${lockKey} after unconfirmed npm termination; ` + `cross-process retries remain blocked until stale-lock recovery`);
|
|
34406
|
+
return;
|
|
34407
|
+
}
|
|
34408
|
+
releaseInstallLock(lockKey, token);
|
|
34409
|
+
}
|
|
34410
|
+
};
|
|
34020
34411
|
} catch (err) {
|
|
34021
34412
|
const code = err.code;
|
|
34022
34413
|
if (code === "EEXIST")
|
|
34023
|
-
return
|
|
34414
|
+
return null;
|
|
34024
34415
|
warn2(`[lsp] unexpected error acquiring install lock for ${lockKey}: ${err}`);
|
|
34025
|
-
return
|
|
34416
|
+
return null;
|
|
34026
34417
|
}
|
|
34027
34418
|
};
|
|
34028
|
-
|
|
34029
|
-
|
|
34419
|
+
const claimed = tryClaim();
|
|
34420
|
+
if (claimed)
|
|
34421
|
+
return claimed;
|
|
34030
34422
|
let owningPid = null;
|
|
34031
34423
|
let lockMtimeMs = 0;
|
|
34032
34424
|
try {
|
|
@@ -34044,7 +34436,7 @@ ${new Date().toISOString()}
|
|
|
34044
34436
|
const skipLiveness = process.platform === "win32";
|
|
34045
34437
|
const ownerAlive = !skipLiveness && owningPid !== null && isProcessAlive3(owningPid);
|
|
34046
34438
|
if (skipLiveness ? ageWithinFresh : ownerAlive && ageWithinFresh) {
|
|
34047
|
-
return
|
|
34439
|
+
return null;
|
|
34048
34440
|
}
|
|
34049
34441
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
34050
34442
|
try {
|
|
@@ -34063,16 +34455,18 @@ function isProcessAlive3(pid) {
|
|
|
34063
34455
|
return true;
|
|
34064
34456
|
}
|
|
34065
34457
|
}
|
|
34066
|
-
function releaseInstallLock(lockKey) {
|
|
34458
|
+
function releaseInstallLock(lockKey, acquisitionToken) {
|
|
34067
34459
|
const lock = lockPath(lockKey);
|
|
34068
34460
|
try {
|
|
34069
34461
|
let owningPid = null;
|
|
34462
|
+
let owningToken = "";
|
|
34070
34463
|
try {
|
|
34071
34464
|
const raw = readFileSync12(lock, "utf8");
|
|
34072
|
-
const
|
|
34073
|
-
const parsed = Number.parseInt(
|
|
34465
|
+
const lines = raw.split(/\r?\n/);
|
|
34466
|
+
const parsed = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
|
34074
34467
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
34075
34468
|
owningPid = parsed;
|
|
34469
|
+
owningToken = lines[2]?.trim() ?? "";
|
|
34076
34470
|
} catch (readErr) {
|
|
34077
34471
|
const code = readErr.code;
|
|
34078
34472
|
if (code === "ENOENT")
|
|
@@ -34080,8 +34474,8 @@ function releaseInstallLock(lockKey) {
|
|
|
34080
34474
|
warn2(`[lsp] could not read install lock for ${lockKey} during release: ${readErr}`);
|
|
34081
34475
|
return;
|
|
34082
34476
|
}
|
|
34083
|
-
if (owningPid !== process.pid) {
|
|
34084
|
-
log2(`[lsp] not releasing install lock for ${lockKey}:
|
|
34477
|
+
if (owningPid !== process.pid || owningToken !== acquisitionToken) {
|
|
34478
|
+
log2(`[lsp] not releasing install lock for ${lockKey}: acquisition ownership changed`);
|
|
34085
34479
|
return;
|
|
34086
34480
|
}
|
|
34087
34481
|
try {
|
|
@@ -34097,12 +34491,13 @@ function releaseInstallLock(lockKey) {
|
|
|
34097
34491
|
}
|
|
34098
34492
|
}
|
|
34099
34493
|
async function withInstallLock(lockKey, task) {
|
|
34100
|
-
|
|
34494
|
+
const lease = acquireInstallLock(lockKey);
|
|
34495
|
+
if (!lease)
|
|
34101
34496
|
return null;
|
|
34102
34497
|
try {
|
|
34103
|
-
return await task();
|
|
34498
|
+
return await task(lease);
|
|
34104
34499
|
} finally {
|
|
34105
|
-
|
|
34500
|
+
lease.release();
|
|
34106
34501
|
}
|
|
34107
34502
|
}
|
|
34108
34503
|
var VERSION_CHECK_FILE = ".aft-version-check";
|
|
@@ -34447,6 +34842,7 @@ function buildExtensionMap(specs) {
|
|
|
34447
34842
|
return byExt;
|
|
34448
34843
|
}
|
|
34449
34844
|
var inFlightAutoInstalls = new Set;
|
|
34845
|
+
var quarantinedNpmInstalls = new Set;
|
|
34450
34846
|
function trackInFlightAutoInstall(controller, promise2) {
|
|
34451
34847
|
const entry = { controller, promise: promise2 };
|
|
34452
34848
|
inFlightAutoInstalls.add(entry);
|
|
@@ -34494,7 +34890,7 @@ function ensureInstallAnchor(cwd) {
|
|
|
34494
34890
|
warn2(`[lsp] could not write package.json stub in ${cwd}: ${err}`);
|
|
34495
34891
|
}
|
|
34496
34892
|
}
|
|
34497
|
-
function runInstall(spec, version2, cwd, signal) {
|
|
34893
|
+
function runInstall(spec, version2, cwd, installLock, signal) {
|
|
34498
34894
|
return new Promise((resolve10) => {
|
|
34499
34895
|
const target = `${spec.npm}@${version2}`;
|
|
34500
34896
|
log2(`[lsp] installing ${target} to ${cwd}`);
|
|
@@ -34510,19 +34906,25 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34510
34906
|
return;
|
|
34511
34907
|
}
|
|
34512
34908
|
ensureInstallAnchor(cwd);
|
|
34513
|
-
const
|
|
34909
|
+
const invocation = npmInvocation(npm, [
|
|
34910
|
+
"install",
|
|
34911
|
+
"--no-save",
|
|
34912
|
+
"--ignore-scripts",
|
|
34913
|
+
"--silent",
|
|
34914
|
+
target
|
|
34915
|
+
]);
|
|
34916
|
+
const child = spawn5(invocation.command, invocation.args, {
|
|
34514
34917
|
stdio: ["ignore", "pipe", "pipe"],
|
|
34515
34918
|
cwd,
|
|
34516
|
-
env: npmSpawnEnv(npm)
|
|
34919
|
+
env: { ...npmSpawnEnv(npm), ...invocation.env },
|
|
34920
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
34517
34921
|
});
|
|
34518
34922
|
child.unref();
|
|
34519
34923
|
let stderrBuf = "";
|
|
34520
34924
|
let settled = false;
|
|
34521
|
-
let
|
|
34925
|
+
let terminationPromise = null;
|
|
34522
34926
|
const cleanup = () => {
|
|
34523
34927
|
signal?.removeEventListener("abort", onAbort);
|
|
34524
|
-
if (killTimer)
|
|
34525
|
-
clearTimeout(killTimer);
|
|
34526
34928
|
};
|
|
34527
34929
|
const finish = (ok) => {
|
|
34528
34930
|
if (settled)
|
|
@@ -34531,14 +34933,31 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34531
34933
|
cleanup();
|
|
34532
34934
|
resolve10(ok);
|
|
34533
34935
|
};
|
|
34936
|
+
const childCompletedSuccessfully = () => child.exitCode === 0 && child.signalCode === null;
|
|
34937
|
+
const finishAfterConfirmedTermination = () => {
|
|
34938
|
+
const completedSuccessfully = childCompletedSuccessfully();
|
|
34939
|
+
if (completedSuccessfully)
|
|
34940
|
+
log2(`[lsp] installed ${target}`);
|
|
34941
|
+
finish(completedSuccessfully);
|
|
34942
|
+
};
|
|
34534
34943
|
const onAbort = () => {
|
|
34944
|
+
if (childCompletedSuccessfully()) {
|
|
34945
|
+
finishAfterConfirmedTermination();
|
|
34946
|
+
return;
|
|
34947
|
+
}
|
|
34535
34948
|
warn2(`[lsp] install ${target} aborted during shutdown`);
|
|
34536
|
-
child
|
|
34537
|
-
|
|
34538
|
-
if (
|
|
34539
|
-
|
|
34540
|
-
|
|
34541
|
-
|
|
34949
|
+
terminationPromise ??= terminateNpmProcessTree(child, invocation);
|
|
34950
|
+
terminationPromise.then(finishAfterConfirmedTermination, (terminationError) => {
|
|
34951
|
+
if (child.pid === undefined) {
|
|
34952
|
+
error52(`[lsp] install ${target} failed to spawn during shutdown: ${terminationError}`);
|
|
34953
|
+
finish(false);
|
|
34954
|
+
return;
|
|
34955
|
+
}
|
|
34956
|
+
quarantinedNpmInstalls.add(spec.npm);
|
|
34957
|
+
installLock.retain();
|
|
34958
|
+
error52(`[lsp] install ${target} termination outcome unknown; quarantining retries for this session: ${String(terminationError)}`);
|
|
34959
|
+
finish(false);
|
|
34960
|
+
});
|
|
34542
34961
|
};
|
|
34543
34962
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
34544
34963
|
if (signal?.aborted)
|
|
@@ -34552,10 +34971,16 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
34552
34971
|
}
|
|
34553
34972
|
});
|
|
34554
34973
|
child.on("error", (err) => {
|
|
34555
|
-
|
|
34974
|
+
if (settled)
|
|
34975
|
+
return;
|
|
34976
|
+
error52(`[lsp] install ${target} child-process error: ${err}`);
|
|
34977
|
+
if (terminationPromise && child.pid !== undefined)
|
|
34978
|
+
return;
|
|
34556
34979
|
finish(false);
|
|
34557
34980
|
});
|
|
34558
34981
|
child.on("exit", (code) => {
|
|
34982
|
+
if (settled || terminationPromise)
|
|
34983
|
+
return;
|
|
34559
34984
|
if (code === 0) {
|
|
34560
34985
|
log2(`[lsp] installed ${target}`);
|
|
34561
34986
|
finish(true);
|
|
@@ -34568,7 +34993,13 @@ ${stderrBuf.trim()}`);
|
|
|
34568
34993
|
});
|
|
34569
34994
|
}
|
|
34570
34995
|
async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
34571
|
-
|
|
34996
|
+
if (quarantinedNpmInstalls.has(spec.npm)) {
|
|
34997
|
+
return {
|
|
34998
|
+
started: false,
|
|
34999
|
+
reason: "install blocked after unconfirmed npm termination; restart the host to retry"
|
|
35000
|
+
};
|
|
35001
|
+
}
|
|
35002
|
+
const outcome = await withInstallLock(spec.npm, async (installLock) => {
|
|
34572
35003
|
const { version: version2, probe } = await resolveTargetVersion(spec, config2, fetchImpl);
|
|
34573
35004
|
if (!version2) {
|
|
34574
35005
|
const installed = isInstalled(spec.npm, spec.binary);
|
|
@@ -34604,7 +35035,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
|
34604
35035
|
log2(`[lsp] reinstalling ${spec.npm}@${version2}: no installed-version metadata recorded`);
|
|
34605
35036
|
}
|
|
34606
35037
|
}
|
|
34607
|
-
const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
|
|
35038
|
+
const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), installLock, signal).catch((err) => {
|
|
34608
35039
|
error52(`[lsp] background install ${spec.npm} crashed: ${err}`);
|
|
34609
35040
|
return false;
|
|
34610
35041
|
});
|
|
@@ -34787,69 +35218,69 @@ import { Readable as Readable3 } from "node:stream";
|
|
|
34787
35218
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
34788
35219
|
|
|
34789
35220
|
// src/lsp-github-table.ts
|
|
34790
|
-
function exe(
|
|
34791
|
-
return
|
|
35221
|
+
function exe(platform2, name) {
|
|
35222
|
+
return platform2 === "win32" ? `${name}.exe` : name;
|
|
34792
35223
|
}
|
|
34793
35224
|
var CLANGD = {
|
|
34794
35225
|
id: "clangd",
|
|
34795
35226
|
githubRepo: "clangd/clangd",
|
|
34796
35227
|
binary: "clangd",
|
|
34797
|
-
resolveAsset: (
|
|
34798
|
-
const platformName =
|
|
35228
|
+
resolveAsset: (platform2, _arch, version2) => {
|
|
35229
|
+
const platformName = platform2 === "darwin" ? "mac" : platform2 === "linux" ? "linux" : "windows";
|
|
34799
35230
|
return { name: `clangd-${platformName}-${version2}.zip`, archive: "zip" };
|
|
34800
35231
|
},
|
|
34801
|
-
binaryPathInArchive: (
|
|
35232
|
+
binaryPathInArchive: (platform2, _arch, version2) => `clangd_${version2}/bin/${exe(platform2, "clangd")}`
|
|
34802
35233
|
};
|
|
34803
35234
|
var LUA_LS = {
|
|
34804
35235
|
id: "lua-ls",
|
|
34805
35236
|
githubRepo: "LuaLS/lua-language-server",
|
|
34806
35237
|
binary: "lua-language-server",
|
|
34807
|
-
resolveAsset: (
|
|
34808
|
-
const ext =
|
|
34809
|
-
const platformName =
|
|
35238
|
+
resolveAsset: (platform2, arch, version2) => {
|
|
35239
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
35240
|
+
const platformName = platform2 === "darwin" ? "darwin" : platform2 === "linux" ? "linux" : "win32";
|
|
34810
35241
|
const archName = arch === "arm64" ? "arm64" : "x64";
|
|
34811
35242
|
return {
|
|
34812
35243
|
name: `lua-language-server-${version2}-${platformName}-${archName}.${ext}`,
|
|
34813
35244
|
archive: ext
|
|
34814
35245
|
};
|
|
34815
35246
|
},
|
|
34816
|
-
binaryPathInArchive: (
|
|
35247
|
+
binaryPathInArchive: (platform2, _arch, _version) => `bin/${exe(platform2, "lua-language-server")}`
|
|
34817
35248
|
};
|
|
34818
35249
|
var ZLS = {
|
|
34819
35250
|
id: "zls",
|
|
34820
35251
|
githubRepo: "zigtools/zls",
|
|
34821
35252
|
binary: "zls",
|
|
34822
|
-
resolveAsset: (
|
|
34823
|
-
const ext =
|
|
35253
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
35254
|
+
const ext = platform2 === "win32" ? "zip" : "tar.xz";
|
|
34824
35255
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34825
|
-
const platformName =
|
|
35256
|
+
const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
|
|
34826
35257
|
return { name: `zls-${archName}-${platformName}.${ext}`, archive: ext };
|
|
34827
35258
|
},
|
|
34828
|
-
binaryPathInArchive: (
|
|
35259
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "zls")
|
|
34829
35260
|
};
|
|
34830
35261
|
var TINYMIST = {
|
|
34831
35262
|
id: "tinymist",
|
|
34832
35263
|
githubRepo: "Myriad-Dreamin/tinymist",
|
|
34833
35264
|
binary: "tinymist",
|
|
34834
|
-
resolveAsset: (
|
|
35265
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
34835
35266
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34836
|
-
const triple =
|
|
34837
|
-
const ext =
|
|
35267
|
+
const triple = platform2 === "darwin" ? "apple-darwin" : platform2 === "linux" ? "unknown-linux-gnu" : "pc-windows-msvc";
|
|
35268
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
34838
35269
|
return { name: `tinymist-${archName}-${triple}.${ext}`, archive: ext };
|
|
34839
35270
|
},
|
|
34840
|
-
binaryPathInArchive: (
|
|
35271
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "tinymist")
|
|
34841
35272
|
};
|
|
34842
35273
|
var TEXLAB = {
|
|
34843
35274
|
id: "texlab",
|
|
34844
35275
|
githubRepo: "latex-lsp/texlab",
|
|
34845
35276
|
binary: "texlab",
|
|
34846
|
-
resolveAsset: (
|
|
35277
|
+
resolveAsset: (platform2, arch, _version) => {
|
|
34847
35278
|
const archName = arch === "arm64" ? "aarch64" : "x86_64";
|
|
34848
|
-
const platformName =
|
|
34849
|
-
const ext =
|
|
35279
|
+
const platformName = platform2 === "darwin" ? "macos" : platform2 === "linux" ? "linux" : "windows";
|
|
35280
|
+
const ext = platform2 === "win32" ? "zip" : "tar.gz";
|
|
34850
35281
|
return { name: `texlab-${archName}-${platformName}.${ext}`, archive: ext };
|
|
34851
35282
|
},
|
|
34852
|
-
binaryPathInArchive: (
|
|
35283
|
+
binaryPathInArchive: (platform2, _arch, _version) => exe(platform2, "texlab")
|
|
34853
35284
|
};
|
|
34854
35285
|
var GITHUB_LSP_TABLE = [
|
|
34855
35286
|
CLANGD,
|
|
@@ -34859,14 +35290,14 @@ var GITHUB_LSP_TABLE = [
|
|
|
34859
35290
|
TEXLAB
|
|
34860
35291
|
];
|
|
34861
35292
|
function detectHostPlatform() {
|
|
34862
|
-
const
|
|
34863
|
-
if (
|
|
35293
|
+
const platform2 = process.platform;
|
|
35294
|
+
if (platform2 !== "darwin" && platform2 !== "linux" && platform2 !== "win32")
|
|
34864
35295
|
return null;
|
|
34865
35296
|
const arch = process.arch;
|
|
34866
35297
|
if (arch === "x64")
|
|
34867
|
-
return { platform:
|
|
35298
|
+
return { platform: platform2, arch: "x64" };
|
|
34868
35299
|
if (arch === "arm64")
|
|
34869
|
-
return { platform:
|
|
35300
|
+
return { platform: platform2, arch: "arm64" };
|
|
34870
35301
|
return null;
|
|
34871
35302
|
}
|
|
34872
35303
|
|
|
@@ -34884,12 +35315,12 @@ function ghExtractDir(spec) {
|
|
|
34884
35315
|
return join20(ghPackageDir(spec), "extracted");
|
|
34885
35316
|
}
|
|
34886
35317
|
var INSTALLED_META_FILE2 = ".aft-installed";
|
|
34887
|
-
function ghBinaryPath(spec,
|
|
34888
|
-
const ext =
|
|
35318
|
+
function ghBinaryPath(spec, platform2) {
|
|
35319
|
+
const ext = platform2 === "win32" ? ".exe" : "";
|
|
34889
35320
|
return join20(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
34890
35321
|
}
|
|
34891
|
-
function isGithubInstalled(spec,
|
|
34892
|
-
for (const candidate of ghBinaryCandidates(spec,
|
|
35322
|
+
function isGithubInstalled(spec, platform2) {
|
|
35323
|
+
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
34893
35324
|
try {
|
|
34894
35325
|
if (statSync11(join20(ghBinDir(spec), candidate)).isFile())
|
|
34895
35326
|
return true;
|
|
@@ -34897,8 +35328,8 @@ function isGithubInstalled(spec, platform3) {
|
|
|
34897
35328
|
}
|
|
34898
35329
|
return false;
|
|
34899
35330
|
}
|
|
34900
|
-
function ghBinaryCandidates(spec,
|
|
34901
|
-
if (
|
|
35331
|
+
function ghBinaryCandidates(spec, platform2) {
|
|
35332
|
+
if (platform2 !== "win32")
|
|
34902
35333
|
return [spec.binary];
|
|
34903
35334
|
return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
|
|
34904
35335
|
}
|
|
@@ -35278,10 +35709,10 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
35278
35709
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
35279
35710
|
}
|
|
35280
35711
|
}
|
|
35281
|
-
function validateCachedGithubInstall(spec,
|
|
35712
|
+
function validateCachedGithubInstall(spec, platform2) {
|
|
35282
35713
|
const packageDir = ghPackageDir(spec);
|
|
35283
35714
|
const meta3 = readGithubInstalledMetaIn(packageDir);
|
|
35284
|
-
const binaryPath = ghBinaryCandidates(spec,
|
|
35715
|
+
const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join20(ghBinDir(spec), candidate)).find((candidate) => {
|
|
35285
35716
|
try {
|
|
35286
35717
|
return statSync11(candidate).isFile();
|
|
35287
35718
|
} catch {
|
|
@@ -35345,11 +35776,11 @@ function runPlatformExtractor(archivePath, destDir, archiveType) {
|
|
|
35345
35776
|
}
|
|
35346
35777
|
throw new Error(`unsupported archive type: ${archiveType}`);
|
|
35347
35778
|
}
|
|
35348
|
-
async function downloadAndInstall(spec, tag, assets,
|
|
35779
|
+
async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal) {
|
|
35349
35780
|
const version2 = stripTagV(tag);
|
|
35350
|
-
const expected = spec.resolveAsset(
|
|
35781
|
+
const expected = spec.resolveAsset(platform2, arch, version2);
|
|
35351
35782
|
if (!expected) {
|
|
35352
|
-
warn2(`[lsp] ${spec.id}: unsupported platform/arch combo ${
|
|
35783
|
+
warn2(`[lsp] ${spec.id}: unsupported platform/arch combo ${platform2}/${arch}`);
|
|
35353
35784
|
return null;
|
|
35354
35785
|
}
|
|
35355
35786
|
const matchingAsset = assets.find((a) => a.name === expected.name);
|
|
@@ -35399,16 +35830,16 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
35399
35830
|
unlinkSync7(archivePath);
|
|
35400
35831
|
} catch {}
|
|
35401
35832
|
}
|
|
35402
|
-
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(
|
|
35833
|
+
const innerBinaryPath = join20(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
35403
35834
|
if (!existsSync18(innerBinaryPath)) {
|
|
35404
35835
|
error52(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
35405
35836
|
return null;
|
|
35406
35837
|
}
|
|
35407
|
-
const targetBinary = ghBinaryPath(spec,
|
|
35838
|
+
const targetBinary = ghBinaryPath(spec, platform2);
|
|
35408
35839
|
mkdirSync9(dirname9(targetBinary), { recursive: true });
|
|
35409
35840
|
try {
|
|
35410
35841
|
copyFileSync4(innerBinaryPath, targetBinary);
|
|
35411
|
-
if (
|
|
35842
|
+
if (platform2 !== "win32") {
|
|
35412
35843
|
const { chmodSync: chmodSync4 } = await import("node:fs");
|
|
35413
35844
|
chmodSync4(targetBinary, 493);
|
|
35414
35845
|
}
|
|
@@ -35421,11 +35852,11 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
35421
35852
|
log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
|
|
35422
35853
|
return { archiveSha256, binarySha256 };
|
|
35423
35854
|
}
|
|
35424
|
-
async function ensureGithubInstalled(spec, config2, fetchImpl,
|
|
35855
|
+
async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
|
|
35425
35856
|
const outcome = await withInstallLock(spec.githubRepo, async () => {
|
|
35426
35857
|
const { tag, assets, blockedByGrace, reason } = await resolveTargetTag(spec, config2, fetchImpl, signal);
|
|
35427
35858
|
if (!tag) {
|
|
35428
|
-
const installed = isGithubInstalled(spec,
|
|
35859
|
+
const installed = isGithubInstalled(spec, platform2);
|
|
35429
35860
|
if (installed) {
|
|
35430
35861
|
warn2(`[lsp] no eligible release of ${spec.githubRepo} (grace=${config2.graceDays}d); keeping existing install`);
|
|
35431
35862
|
return { started: false, reason: "kept existing install" };
|
|
@@ -35434,7 +35865,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform3, arch,
|
|
|
35434
35865
|
warn2(`[lsp] skipping ${spec.id}: ${fallbackReason}`);
|
|
35435
35866
|
return { started: false, reason: fallbackReason };
|
|
35436
35867
|
}
|
|
35437
|
-
if (isGithubInstalled(spec,
|
|
35868
|
+
if (isGithubInstalled(spec, platform2)) {
|
|
35438
35869
|
const installedMeta = readInstalledMetaIn(ghPackageDir(spec));
|
|
35439
35870
|
if (installedMeta && installedMeta.version === tag) {
|
|
35440
35871
|
return { started: false, reason: "already installed" };
|
|
@@ -35445,7 +35876,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform3, arch,
|
|
|
35445
35876
|
log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
|
|
35446
35877
|
}
|
|
35447
35878
|
}
|
|
35448
|
-
const hashes = await downloadAndInstall(spec, tag, assets,
|
|
35879
|
+
const hashes = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
|
|
35449
35880
|
error52(`[lsp] github install ${spec.id} crashed: ${err}`);
|
|
35450
35881
|
return null;
|
|
35451
35882
|
});
|
|
@@ -37825,6 +38256,48 @@ function relativeToWorktree(fp, worktree) {
|
|
|
37825
38256
|
function readAttachments(data) {
|
|
37826
38257
|
return Array.isArray(data.attachments) ? data.attachments : [];
|
|
37827
38258
|
}
|
|
38259
|
+
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`).";
|
|
38260
|
+
function asRecord2(value) {
|
|
38261
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
38262
|
+
}
|
|
38263
|
+
function visionCapabilityForOpenCodeModel(model) {
|
|
38264
|
+
const entry = model;
|
|
38265
|
+
if (Array.isArray(entry?.modalities?.input)) {
|
|
38266
|
+
return entry.modalities.input.includes("image");
|
|
38267
|
+
}
|
|
38268
|
+
return typeof entry?.attachment === "boolean" ? entry.attachment : undefined;
|
|
38269
|
+
}
|
|
38270
|
+
function modelFromOpenCodeProvider(provider, modelID) {
|
|
38271
|
+
const models = provider.models;
|
|
38272
|
+
if (Array.isArray(models)) {
|
|
38273
|
+
return models.find((model) => asRecord2(model)?.id === modelID);
|
|
38274
|
+
}
|
|
38275
|
+
return asRecord2(models)?.[modelID];
|
|
38276
|
+
}
|
|
38277
|
+
async function currentSessionVisionCapability(client, sessionID) {
|
|
38278
|
+
if (!sessionID)
|
|
38279
|
+
return;
|
|
38280
|
+
const promptContext = await resolvePromptContext(client, sessionID);
|
|
38281
|
+
const currentModel = promptContext?.model;
|
|
38282
|
+
if (!currentModel)
|
|
38283
|
+
return;
|
|
38284
|
+
const providerApi = client.provider;
|
|
38285
|
+
if (typeof providerApi?.list !== "function")
|
|
38286
|
+
return;
|
|
38287
|
+
let listed;
|
|
38288
|
+
try {
|
|
38289
|
+
listed = await providerApi.list();
|
|
38290
|
+
} catch {
|
|
38291
|
+
return;
|
|
38292
|
+
}
|
|
38293
|
+
const result = asRecord2(listed);
|
|
38294
|
+
const catalog = asRecord2(result?.data) ?? result;
|
|
38295
|
+
const providers = Array.isArray(catalog?.all) ? catalog.all : Array.isArray(catalog?.providers) ? catalog.providers : [];
|
|
38296
|
+
const provider = providers.map(asRecord2).find((entry) => entry?.id === currentModel.providerID);
|
|
38297
|
+
if (!provider)
|
|
38298
|
+
return;
|
|
38299
|
+
return visionCapabilityForOpenCodeModel(modelFromOpenCodeProvider(provider, currentModel.modelID));
|
|
38300
|
+
}
|
|
37828
38301
|
function persistFilePathAlias(args, context) {
|
|
37829
38302
|
if (typeof args.path === "string" && !Object.hasOwn(args, "filePath")) {
|
|
37830
38303
|
args.filePath = args.path;
|
|
@@ -38012,10 +38485,19 @@ Examples:
|
|
|
38012
38485
|
Read 30 lines from line 200: { "path": "src/app.ts", "offset": 200, "limit": 30 }
|
|
38013
38486
|
List directory: { "path": "src/" }
|
|
38014
38487
|
`;
|
|
38488
|
+
function readDescription(ghReadEnabled) {
|
|
38489
|
+
if (!ghReadEnabled)
|
|
38490
|
+
return READ_DESCRIPTION;
|
|
38491
|
+
return READ_DESCRIPTION.replace(`
|
|
38492
|
+
Examples:`, `
|
|
38493
|
+
${ISSUE_AND_PR_READ_DESCRIPTION}
|
|
38494
|
+
|
|
38495
|
+
Examples:`);
|
|
38496
|
+
}
|
|
38015
38497
|
function createReadTool(ctx) {
|
|
38016
38498
|
return prepareToolMap({
|
|
38017
38499
|
read: {
|
|
38018
|
-
description:
|
|
38500
|
+
description: readDescription(ctx.config.gh_read?.enabled === true),
|
|
38019
38501
|
args: {
|
|
38020
38502
|
filePath: z8.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
38021
38503
|
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
@@ -38066,6 +38548,9 @@ function createReadTool(ctx) {
|
|
|
38066
38548
|
rawArgs.endLine = endLine;
|
|
38067
38549
|
if (rawLimit !== undefined && rawOffset === undefined)
|
|
38068
38550
|
rawArgs.limit = rawLimit;
|
|
38551
|
+
const visionCapability = await currentSessionVisionCapability(ctx.client, context.sessionID);
|
|
38552
|
+
if (visionCapability !== undefined)
|
|
38553
|
+
rawArgs.vision_capability = visionCapability;
|
|
38069
38554
|
const response = await callToolCall(ctx, context, "read", rawArgs);
|
|
38070
38555
|
if (response.success === false) {
|
|
38071
38556
|
throw new Error(response.message || "read failed");
|
|
@@ -38700,7 +39185,7 @@ async function resolveAndGateScope(ctx, context, scope) {
|
|
|
38700
39185
|
}
|
|
38701
39186
|
return { scope: Array.isArray(scope) ? resolved : resolved[0] };
|
|
38702
39187
|
}
|
|
38703
|
-
function
|
|
39188
|
+
function asRecord3(value) {
|
|
38704
39189
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
38705
39190
|
}
|
|
38706
39191
|
function asString(value) {
|
|
@@ -38708,7 +39193,7 @@ function asString(value) {
|
|
|
38708
39193
|
}
|
|
38709
39194
|
function firstRecord(...values) {
|
|
38710
39195
|
for (const value of values) {
|
|
38711
|
-
const record2 =
|
|
39196
|
+
const record2 = asRecord3(value);
|
|
38712
39197
|
if (record2)
|
|
38713
39198
|
return record2;
|
|
38714
39199
|
}
|
|
@@ -38743,7 +39228,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
38743
39228
|
if (!Array.isArray(value))
|
|
38744
39229
|
return [];
|
|
38745
39230
|
return value.flatMap((candidate) => {
|
|
38746
|
-
const entry =
|
|
39231
|
+
const entry = asRecord3(candidate);
|
|
38747
39232
|
const id = asString(entry?.id);
|
|
38748
39233
|
if (!id)
|
|
38749
39234
|
return [];
|
|
@@ -38759,7 +39244,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
38759
39244
|
});
|
|
38760
39245
|
}
|
|
38761
39246
|
function parseInspectTerminal(payload) {
|
|
38762
|
-
const response =
|
|
39247
|
+
const response = asRecord3(payload);
|
|
38763
39248
|
if (!response)
|
|
38764
39249
|
return;
|
|
38765
39250
|
const kind = terminalKind(response);
|
|
@@ -38774,7 +39259,7 @@ function parseInspectTerminal(payload) {
|
|
|
38774
39259
|
waitStampText: kind === "FRESH" ? firstString(waitStamp?.text, waitStamp?.human_text) : undefined
|
|
38775
39260
|
};
|
|
38776
39261
|
}
|
|
38777
|
-
const failedPhaseRecord =
|
|
39262
|
+
const failedPhaseRecord = asRecord3(response.failed_phase);
|
|
38778
39263
|
const failedPhaseId = firstString(failedPhaseRecord?.id, response.failed_phase);
|
|
38779
39264
|
const failedPhase = failedPhaseId ? parseInspectPhaseEntries([
|
|
38780
39265
|
{
|
|
@@ -39724,7 +40209,9 @@ function semanticTools(ctx) {
|
|
|
39724
40209
|
rawArgs.includeTests = args.includeTests;
|
|
39725
40210
|
if (pathArg)
|
|
39726
40211
|
rawArgs.path = pathArg;
|
|
39727
|
-
const response = await callToolCall(ctx, context, "search", rawArgs
|
|
40212
|
+
const response = await callToolCall(ctx, context, "search", rawArgs, {
|
|
40213
|
+
abortSignal: context.abort
|
|
40214
|
+
});
|
|
39728
40215
|
if (response.success === false) {
|
|
39729
40216
|
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
40217
|
throw new Error(message);
|
|
@@ -39903,7 +40390,7 @@ var PLUGIN_VERSION = (() => {
|
|
|
39903
40390
|
return "0.0.0";
|
|
39904
40391
|
}
|
|
39905
40392
|
})();
|
|
39906
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
40393
|
+
var ANNOUNCEMENT_VERSION = "0.55.0";
|
|
39907
40394
|
var ANNOUNCEMENT_FEATURES = [
|
|
39908
40395
|
"Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
|
|
39909
40396
|
"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).",
|