@cortexkit/aft-opencode 0.26.0 → 0.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bg-notifications.d.ts +3 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +573 -260
- package/dist/lsp-github-install.d.ts +2 -2
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/shared/status.d.ts +11 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tui.js +66 -16
- package/package.json +7 -7
- package/src/shared/status.ts +15 -0
- package/src/tui/sidebar.tsx +50 -5
package/dist/index.js
CHANGED
|
@@ -7803,7 +7803,7 @@ var require_src2 = __commonJS((exports, module) => {
|
|
|
7803
7803
|
});
|
|
7804
7804
|
|
|
7805
7805
|
// src/index.ts
|
|
7806
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as
|
|
7806
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
7807
7807
|
import { createRequire as createRequire2 } from "module";
|
|
7808
7808
|
import { homedir as homedir11 } from "os";
|
|
7809
7809
|
import { join as join21 } from "path";
|
|
@@ -7820,12 +7820,22 @@ function getActiveLogger() {
|
|
|
7820
7820
|
return loggerGlobal()[ACTIVE_LOGGER_SYMBOL];
|
|
7821
7821
|
}
|
|
7822
7822
|
function getLogFilePath() {
|
|
7823
|
-
|
|
7823
|
+
try {
|
|
7824
|
+
return getActiveLogger()?.getLogFilePath?.();
|
|
7825
|
+
} catch (err) {
|
|
7826
|
+
console.error(`[aft-bridge] ERROR: active logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7827
|
+
return;
|
|
7828
|
+
}
|
|
7824
7829
|
}
|
|
7825
7830
|
function log(message, meta) {
|
|
7826
7831
|
const active = getActiveLogger();
|
|
7827
7832
|
if (active) {
|
|
7828
|
-
|
|
7833
|
+
try {
|
|
7834
|
+
active.log(message, meta);
|
|
7835
|
+
} catch (err) {
|
|
7836
|
+
console.error(`[aft-bridge] ERROR: active logger log threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7837
|
+
console.error(`[aft-bridge] ${message}`);
|
|
7838
|
+
}
|
|
7829
7839
|
} else {
|
|
7830
7840
|
console.error(`[aft-bridge] ${message}`);
|
|
7831
7841
|
}
|
|
@@ -7833,7 +7843,12 @@ function log(message, meta) {
|
|
|
7833
7843
|
function warn(message, meta) {
|
|
7834
7844
|
const active = getActiveLogger();
|
|
7835
7845
|
if (active) {
|
|
7836
|
-
|
|
7846
|
+
try {
|
|
7847
|
+
active.warn(message, meta);
|
|
7848
|
+
} catch (err) {
|
|
7849
|
+
console.error(`[aft-bridge] ERROR: active logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7850
|
+
console.error(`[aft-bridge] WARN: ${message}`);
|
|
7851
|
+
}
|
|
7837
7852
|
} else {
|
|
7838
7853
|
console.error(`[aft-bridge] WARN: ${message}`);
|
|
7839
7854
|
}
|
|
@@ -7841,20 +7856,16 @@ function warn(message, meta) {
|
|
|
7841
7856
|
function error(message, meta) {
|
|
7842
7857
|
const active = getActiveLogger();
|
|
7843
7858
|
if (active) {
|
|
7844
|
-
|
|
7859
|
+
try {
|
|
7860
|
+
active.error(message, meta);
|
|
7861
|
+
} catch (err) {
|
|
7862
|
+
console.error(`[aft-bridge] ERROR: active logger error threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
7863
|
+
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
7864
|
+
}
|
|
7845
7865
|
} else {
|
|
7846
7866
|
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
7847
7867
|
}
|
|
7848
7868
|
}
|
|
7849
|
-
function sessionLog(sessionId, message) {
|
|
7850
|
-
log(message, sessionId ? { sessionId } : undefined);
|
|
7851
|
-
}
|
|
7852
|
-
function sessionWarn(sessionId, message) {
|
|
7853
|
-
warn(message, sessionId ? { sessionId } : undefined);
|
|
7854
|
-
}
|
|
7855
|
-
function sessionError(sessionId, message) {
|
|
7856
|
-
error(message, sessionId ? { sessionId } : undefined);
|
|
7857
|
-
}
|
|
7858
7869
|
// ../aft-bridge/dist/bridge.js
|
|
7859
7870
|
import { spawn } from "child_process";
|
|
7860
7871
|
import { homedir } from "os";
|
|
@@ -7949,6 +7960,7 @@ class BinaryBridge {
|
|
|
7949
7960
|
pending = new Map;
|
|
7950
7961
|
nextId = 1;
|
|
7951
7962
|
stdoutBuffer = "";
|
|
7963
|
+
stderrBuffer = "";
|
|
7952
7964
|
stderrTail = [];
|
|
7953
7965
|
_restartCount = 0;
|
|
7954
7966
|
_shuttingDown = false;
|
|
@@ -7984,24 +7996,62 @@ class BinaryBridge {
|
|
|
7984
7996
|
}
|
|
7985
7997
|
logVia(message, meta) {
|
|
7986
7998
|
const logger = this.logger ?? getActiveLogger();
|
|
7987
|
-
if (logger)
|
|
7988
|
-
|
|
7989
|
-
|
|
7999
|
+
if (logger) {
|
|
8000
|
+
try {
|
|
8001
|
+
logger.log(message, meta);
|
|
8002
|
+
} catch (err) {
|
|
8003
|
+
console.error(`[aft-bridge] ERROR: logger log threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8004
|
+
console.error(`[aft-bridge] ${message}`);
|
|
8005
|
+
}
|
|
8006
|
+
} else {
|
|
7990
8007
|
log(message, meta);
|
|
8008
|
+
}
|
|
7991
8009
|
}
|
|
7992
8010
|
warnVia(message, meta) {
|
|
7993
8011
|
const logger = this.logger ?? getActiveLogger();
|
|
7994
|
-
if (logger)
|
|
7995
|
-
|
|
7996
|
-
|
|
8012
|
+
if (logger) {
|
|
8013
|
+
try {
|
|
8014
|
+
logger.warn(message, meta);
|
|
8015
|
+
} catch (err) {
|
|
8016
|
+
console.error(`[aft-bridge] ERROR: logger warn threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8017
|
+
console.error(`[aft-bridge] WARN: ${message}`);
|
|
8018
|
+
}
|
|
8019
|
+
} else {
|
|
7997
8020
|
warn(message, meta);
|
|
8021
|
+
}
|
|
7998
8022
|
}
|
|
7999
8023
|
errorVia(message, meta) {
|
|
8000
8024
|
const logger = this.logger ?? getActiveLogger();
|
|
8001
|
-
if (logger)
|
|
8002
|
-
|
|
8003
|
-
|
|
8025
|
+
if (logger) {
|
|
8026
|
+
try {
|
|
8027
|
+
logger.error(message, meta);
|
|
8028
|
+
} catch (err) {
|
|
8029
|
+
console.error(`[aft-bridge] ERROR: logger error threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8030
|
+
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
8031
|
+
}
|
|
8032
|
+
} else {
|
|
8004
8033
|
error(message, meta);
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
getLogFilePathVia() {
|
|
8037
|
+
if (this.logger?.getLogFilePath) {
|
|
8038
|
+
try {
|
|
8039
|
+
return this.logger.getLogFilePath();
|
|
8040
|
+
} catch (err) {
|
|
8041
|
+
console.error(`[aft-bridge] ERROR: logger getLogFilePath threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8042
|
+
return;
|
|
8043
|
+
}
|
|
8044
|
+
}
|
|
8045
|
+
return getLogFilePath();
|
|
8046
|
+
}
|
|
8047
|
+
sessionLogVia(sessionId, message) {
|
|
8048
|
+
this.logVia(message, sessionId ? { sessionId } : undefined);
|
|
8049
|
+
}
|
|
8050
|
+
sessionWarnVia(sessionId, message) {
|
|
8051
|
+
this.warnVia(message, sessionId ? { sessionId } : undefined);
|
|
8052
|
+
}
|
|
8053
|
+
sessionErrorVia(sessionId, message) {
|
|
8054
|
+
this.errorVia(message, sessionId ? { sessionId } : undefined);
|
|
8005
8055
|
}
|
|
8006
8056
|
get restartCount() {
|
|
8007
8057
|
return this._restartCount;
|
|
@@ -8060,7 +8110,7 @@ class BinaryBridge {
|
|
|
8060
8110
|
await this.deliverConfigureWarnings(configResult, params, options);
|
|
8061
8111
|
await this.checkVersion();
|
|
8062
8112
|
if (!this.isAlive()) {
|
|
8063
|
-
throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${
|
|
8113
|
+
throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
|
|
8064
8114
|
}
|
|
8065
8115
|
this.configured = true;
|
|
8066
8116
|
} finally {
|
|
@@ -8096,9 +8146,9 @@ class BinaryBridge {
|
|
|
8096
8146
|
const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
|
|
8097
8147
|
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
8098
8148
|
if (requestSessionId) {
|
|
8099
|
-
|
|
8149
|
+
this.sessionWarnVia(requestSessionId, timeoutMsg);
|
|
8100
8150
|
} else {
|
|
8101
|
-
|
|
8151
|
+
this.warnVia(timeoutMsg);
|
|
8102
8152
|
}
|
|
8103
8153
|
reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
8104
8154
|
if (!keepBridgeOnTimeout) {
|
|
@@ -8125,7 +8175,7 @@ class BinaryBridge {
|
|
|
8125
8175
|
});
|
|
8126
8176
|
} catch (err) {
|
|
8127
8177
|
if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
|
|
8128
|
-
|
|
8178
|
+
this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
|
|
8129
8179
|
return this.sendWithVersionMismatchRetry(command, params, options, false);
|
|
8130
8180
|
}
|
|
8131
8181
|
throw err;
|
|
@@ -8145,7 +8195,7 @@ class BinaryBridge {
|
|
|
8145
8195
|
warnings: configResult.warnings
|
|
8146
8196
|
});
|
|
8147
8197
|
} catch (err) {
|
|
8148
|
-
|
|
8198
|
+
this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8149
8199
|
} finally {
|
|
8150
8200
|
if (sessionId) {
|
|
8151
8201
|
this.configureWarningClients.delete(sessionId);
|
|
@@ -8179,7 +8229,7 @@ class BinaryBridge {
|
|
|
8179
8229
|
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
8180
8230
|
return;
|
|
8181
8231
|
this.cachedStatus = snapshot;
|
|
8182
|
-
|
|
8232
|
+
this.logVia("Received status_changed push frame; cached AFT status snapshot");
|
|
8183
8233
|
for (const listener of this.statusListeners) {
|
|
8184
8234
|
this.deliverStatusSnapshot(listener, this.cachedStatus);
|
|
8185
8235
|
}
|
|
@@ -8188,7 +8238,7 @@ class BinaryBridge {
|
|
|
8188
8238
|
try {
|
|
8189
8239
|
listener(snapshot);
|
|
8190
8240
|
} catch (err) {
|
|
8191
|
-
|
|
8241
|
+
this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8192
8242
|
}
|
|
8193
8243
|
}
|
|
8194
8244
|
async shutdown() {
|
|
@@ -8206,7 +8256,7 @@ class BinaryBridge {
|
|
|
8206
8256
|
}, 5000);
|
|
8207
8257
|
proc.once("exit", () => {
|
|
8208
8258
|
clearTimeout(forceKillTimer);
|
|
8209
|
-
|
|
8259
|
+
this.logVia("Process exited during shutdown");
|
|
8210
8260
|
resolve();
|
|
8211
8261
|
});
|
|
8212
8262
|
proc.kill("SIGTERM");
|
|
@@ -8225,9 +8275,9 @@ class BinaryBridge {
|
|
|
8225
8275
|
if (typeof binaryVersion !== "string") {
|
|
8226
8276
|
throw new Error(`Binary did not report a version \u2014 likely too old (minVersion: ${this.minVersion})`);
|
|
8227
8277
|
}
|
|
8228
|
-
|
|
8278
|
+
this.logVia(`Binary version: ${binaryVersion}`);
|
|
8229
8279
|
if (compareSemver(binaryVersion, this.minVersion) < 0) {
|
|
8230
|
-
|
|
8280
|
+
this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
|
|
8231
8281
|
const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
|
|
8232
8282
|
if (replacementPath === undefined) {
|
|
8233
8283
|
return;
|
|
@@ -8239,7 +8289,7 @@ class BinaryBridge {
|
|
|
8239
8289
|
throw new BridgeReplacedDuringVersionCheck(replacementPath);
|
|
8240
8290
|
}
|
|
8241
8291
|
} catch (err) {
|
|
8242
|
-
|
|
8292
|
+
this.warnVia(`Version check failed: ${err.message}`);
|
|
8243
8293
|
throw err;
|
|
8244
8294
|
}
|
|
8245
8295
|
}
|
|
@@ -8259,7 +8309,7 @@ class BinaryBridge {
|
|
|
8259
8309
|
}, 5000);
|
|
8260
8310
|
proc.once("exit", () => {
|
|
8261
8311
|
clearTimeout(forceKillTimer);
|
|
8262
|
-
|
|
8312
|
+
this.logVia("Process exited during coordinated binary replacement");
|
|
8263
8313
|
resolve();
|
|
8264
8314
|
});
|
|
8265
8315
|
proc.kill("SIGTERM");
|
|
@@ -8272,9 +8322,9 @@ class BinaryBridge {
|
|
|
8272
8322
|
}
|
|
8273
8323
|
spawnProcess(triggeringSessionId) {
|
|
8274
8324
|
if (triggeringSessionId) {
|
|
8275
|
-
|
|
8325
|
+
this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
8276
8326
|
} else {
|
|
8277
|
-
|
|
8327
|
+
this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
8278
8328
|
}
|
|
8279
8329
|
const semantic = this.configOverrides.semantic;
|
|
8280
8330
|
const semanticBackend = (() => {
|
|
@@ -8321,11 +8371,12 @@ class BinaryBridge {
|
|
|
8321
8371
|
const remaining = stderrDecoder.end();
|
|
8322
8372
|
if (remaining)
|
|
8323
8373
|
this.onStderrData(remaining);
|
|
8374
|
+
this.flushStderrBuffer();
|
|
8324
8375
|
});
|
|
8325
8376
|
child.on("error", (err) => {
|
|
8326
8377
|
if (this.process !== currentChild)
|
|
8327
8378
|
return;
|
|
8328
|
-
|
|
8379
|
+
this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
|
|
8329
8380
|
this.handleCrash();
|
|
8330
8381
|
});
|
|
8331
8382
|
child.on("exit", (code, signal) => {
|
|
@@ -8333,7 +8384,7 @@ class BinaryBridge {
|
|
|
8333
8384
|
return;
|
|
8334
8385
|
if (this._shuttingDown)
|
|
8335
8386
|
return;
|
|
8336
|
-
|
|
8387
|
+
this.logVia(`Process exited: code=${code}, signal=${signal}`);
|
|
8337
8388
|
if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
|
|
8338
8389
|
this.process = null;
|
|
8339
8390
|
this.configured = false;
|
|
@@ -8345,6 +8396,7 @@ class BinaryBridge {
|
|
|
8345
8396
|
});
|
|
8346
8397
|
this.process = child;
|
|
8347
8398
|
this.stdoutBuffer = "";
|
|
8399
|
+
this.stderrBuffer = "";
|
|
8348
8400
|
this.stderrTail = [];
|
|
8349
8401
|
}
|
|
8350
8402
|
pushStderrLine(line) {
|
|
@@ -8354,16 +8406,28 @@ class BinaryBridge {
|
|
|
8354
8406
|
}
|
|
8355
8407
|
}
|
|
8356
8408
|
onStderrData(data) {
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8409
|
+
this.stderrBuffer += data;
|
|
8410
|
+
let newlineIdx;
|
|
8411
|
+
while ((newlineIdx = this.stderrBuffer.indexOf(`
|
|
8412
|
+
`)) !== -1) {
|
|
8413
|
+
const line = this.stderrBuffer.slice(0, newlineIdx).replace(/\r$/, "");
|
|
8414
|
+
this.stderrBuffer = this.stderrBuffer.slice(newlineIdx + 1);
|
|
8360
8415
|
if (!line)
|
|
8361
8416
|
continue;
|
|
8362
8417
|
const tagged = tagStderrLine(line);
|
|
8363
|
-
|
|
8418
|
+
this.logVia(tagged);
|
|
8364
8419
|
this.pushStderrLine(tagged);
|
|
8365
8420
|
}
|
|
8366
8421
|
}
|
|
8422
|
+
flushStderrBuffer() {
|
|
8423
|
+
const line = this.stderrBuffer.replace(/\r$/, "");
|
|
8424
|
+
this.stderrBuffer = "";
|
|
8425
|
+
if (!line)
|
|
8426
|
+
return;
|
|
8427
|
+
const tagged = tagStderrLine(line);
|
|
8428
|
+
this.logVia(tagged);
|
|
8429
|
+
this.pushStderrLine(tagged);
|
|
8430
|
+
}
|
|
8367
8431
|
formatStderrTail() {
|
|
8368
8432
|
if (this.stderrTail.length === 0)
|
|
8369
8433
|
return "";
|
|
@@ -8421,7 +8485,7 @@ class BinaryBridge {
|
|
|
8421
8485
|
}
|
|
8422
8486
|
if (response.type === "configure_warnings") {
|
|
8423
8487
|
this.handleConfigureWarningsFrame(response).catch((err) => {
|
|
8424
|
-
|
|
8488
|
+
this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8425
8489
|
});
|
|
8426
8490
|
continue;
|
|
8427
8491
|
}
|
|
@@ -8439,10 +8503,10 @@ class BinaryBridge {
|
|
|
8439
8503
|
this.scheduleRestartCountReset();
|
|
8440
8504
|
entry.resolve(response);
|
|
8441
8505
|
} else if (typeof response.type === "string") {
|
|
8442
|
-
|
|
8506
|
+
this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
|
|
8443
8507
|
}
|
|
8444
8508
|
} catch (_err) {
|
|
8445
|
-
|
|
8509
|
+
this.warnVia(`Failed to parse stdout line: ${line}`);
|
|
8446
8510
|
}
|
|
8447
8511
|
}
|
|
8448
8512
|
}
|
|
@@ -8456,17 +8520,17 @@ class BinaryBridge {
|
|
|
8456
8520
|
this.configured = false;
|
|
8457
8521
|
const tail = this.formatStderrTail();
|
|
8458
8522
|
this.stderrTail = [];
|
|
8459
|
-
const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${
|
|
8523
|
+
const killedMsg = tail ? `Bridge killed after timeout.${tail}` : `Bridge killed after timeout (see ${this.getLogFilePathVia()})`;
|
|
8460
8524
|
if (tail) {
|
|
8461
8525
|
if (triggeringSessionId) {
|
|
8462
|
-
|
|
8526
|
+
this.sessionErrorVia(triggeringSessionId, killedMsg);
|
|
8463
8527
|
} else {
|
|
8464
|
-
|
|
8528
|
+
this.errorVia(killedMsg);
|
|
8465
8529
|
}
|
|
8466
8530
|
} else if (triggeringSessionId) {
|
|
8467
|
-
|
|
8531
|
+
this.sessionWarnVia(triggeringSessionId, killedMsg);
|
|
8468
8532
|
} else {
|
|
8469
|
-
|
|
8533
|
+
this.warnVia(killedMsg);
|
|
8470
8534
|
}
|
|
8471
8535
|
}
|
|
8472
8536
|
handleCrash(cause) {
|
|
@@ -8479,25 +8543,25 @@ class BinaryBridge {
|
|
|
8479
8543
|
this.configured = false;
|
|
8480
8544
|
const tail = this.formatStderrTail();
|
|
8481
8545
|
if (tail) {
|
|
8482
|
-
|
|
8546
|
+
this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
|
|
8483
8547
|
}
|
|
8484
|
-
this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${
|
|
8548
|
+
this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`));
|
|
8485
8549
|
if (this._restartCount < this.maxRestarts) {
|
|
8486
8550
|
const delay = 100 * 2 ** this._restartCount;
|
|
8487
8551
|
this._restartCount++;
|
|
8488
|
-
|
|
8552
|
+
this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
|
|
8489
8553
|
setTimeout(() => {
|
|
8490
8554
|
if (!this._shuttingDown && !this.isAlive()) {
|
|
8491
8555
|
try {
|
|
8492
8556
|
this.spawnProcess();
|
|
8493
8557
|
} catch (err) {
|
|
8494
|
-
|
|
8558
|
+
this.errorVia(`Failed to restart: ${err.message}`);
|
|
8495
8559
|
}
|
|
8496
8560
|
}
|
|
8497
8561
|
}, delay);
|
|
8498
8562
|
this.scheduleRestartCountReset();
|
|
8499
8563
|
} else {
|
|
8500
|
-
|
|
8564
|
+
this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
|
|
8501
8565
|
this.scheduleRestartCountReset();
|
|
8502
8566
|
}
|
|
8503
8567
|
}
|
|
@@ -8523,9 +8587,12 @@ class BinaryBridge {
|
|
|
8523
8587
|
}
|
|
8524
8588
|
}
|
|
8525
8589
|
// ../aft-bridge/dist/downloader.js
|
|
8526
|
-
import {
|
|
8590
|
+
import { createHash } from "crypto";
|
|
8591
|
+
import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, unlinkSync } from "fs";
|
|
8527
8592
|
import { homedir as homedir2 } from "os";
|
|
8528
8593
|
import { join as join2 } from "path";
|
|
8594
|
+
import { Readable } from "stream";
|
|
8595
|
+
import { pipeline } from "stream/promises";
|
|
8529
8596
|
|
|
8530
8597
|
// ../aft-bridge/dist/platform.js
|
|
8531
8598
|
var PLATFORM_ARCH_MAP = {
|
|
@@ -8543,6 +8610,11 @@ var PLATFORM_ASSET_MAP = {
|
|
|
8543
8610
|
|
|
8544
8611
|
// ../aft-bridge/dist/downloader.js
|
|
8545
8612
|
var REPO = "cortexkit/aft";
|
|
8613
|
+
var DOWNLOAD_TIMEOUT_MS = 300000;
|
|
8614
|
+
var LATEST_TAG_TIMEOUT_MS = 30000;
|
|
8615
|
+
var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
|
|
8616
|
+
var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
|
|
8617
|
+
var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
|
|
8546
8618
|
function getCacheDir() {
|
|
8547
8619
|
if (process.platform === "win32") {
|
|
8548
8620
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
@@ -8584,54 +8656,101 @@ async function downloadBinary(version) {
|
|
|
8584
8656
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
|
|
8585
8657
|
const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
|
|
8586
8658
|
log(`Downloading AFT binary (${tag}) for ${platformKey}...`);
|
|
8659
|
+
const lockPath = join2(versionedCacheDir, ".download.lock");
|
|
8660
|
+
let releaseLock = null;
|
|
8661
|
+
let binaryController = null;
|
|
8662
|
+
let checksumController = null;
|
|
8663
|
+
let binaryTimeout = null;
|
|
8664
|
+
let checksumTimeout = null;
|
|
8665
|
+
const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
8587
8666
|
try {
|
|
8588
8667
|
if (!existsSync(versionedCacheDir)) {
|
|
8589
8668
|
mkdirSync(versionedCacheDir, { recursive: true });
|
|
8590
8669
|
}
|
|
8670
|
+
releaseLock = await acquireDownloadLock(lockPath);
|
|
8671
|
+
if (existsSync(binaryPath)) {
|
|
8672
|
+
return binaryPath;
|
|
8673
|
+
}
|
|
8674
|
+
binaryController = new AbortController;
|
|
8675
|
+
checksumController = new AbortController;
|
|
8676
|
+
const activeBinaryController = binaryController;
|
|
8677
|
+
const activeChecksumController = checksumController;
|
|
8678
|
+
binaryTimeout = setTimeout(() => activeBinaryController.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
8679
|
+
checksumTimeout = setTimeout(() => activeChecksumController.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
8591
8680
|
const [binaryResponse, checksumResponse] = await Promise.all([
|
|
8592
|
-
fetch(downloadUrl, { redirect: "follow" }),
|
|
8593
|
-
fetch(checksumUrl, { redirect: "follow" })
|
|
8681
|
+
fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
|
|
8682
|
+
fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
|
|
8594
8683
|
]);
|
|
8595
8684
|
if (!binaryResponse.ok) {
|
|
8596
8685
|
throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
|
|
8597
8686
|
}
|
|
8598
|
-
|
|
8687
|
+
if (!binaryResponse.body) {
|
|
8688
|
+
throw new Error(`Download response for ${assetName} had no body`);
|
|
8689
|
+
}
|
|
8690
|
+
const advertised = Number.parseInt(binaryResponse.headers.get("content-length") ?? "", 10);
|
|
8691
|
+
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES) {
|
|
8692
|
+
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES}`);
|
|
8693
|
+
}
|
|
8599
8694
|
if (!checksumResponse.ok) {
|
|
8600
8695
|
warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
|
|
8601
8696
|
return null;
|
|
8602
8697
|
}
|
|
8603
8698
|
const checksumText = await checksumResponse.text();
|
|
8699
|
+
clearTimeout(checksumTimeout);
|
|
8700
|
+
checksumTimeout = null;
|
|
8604
8701
|
const expectedHash = parseChecksumForAsset(checksumText, assetName);
|
|
8605
8702
|
if (!expectedHash) {
|
|
8606
8703
|
warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
|
|
8607
8704
|
return null;
|
|
8608
8705
|
}
|
|
8609
|
-
const
|
|
8610
|
-
|
|
8706
|
+
const hash = createHash("sha256");
|
|
8707
|
+
let bytesWritten = 0;
|
|
8708
|
+
const guard = new TransformStream({
|
|
8709
|
+
transform(chunk, controller) {
|
|
8710
|
+
bytesWritten += chunk.byteLength;
|
|
8711
|
+
if (bytesWritten > MAX_DOWNLOAD_BYTES) {
|
|
8712
|
+
controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES} bytes after streaming (server lied about size or sent unbounded body)`));
|
|
8713
|
+
return;
|
|
8714
|
+
}
|
|
8715
|
+
hash.update(chunk);
|
|
8716
|
+
controller.enqueue(chunk);
|
|
8717
|
+
}
|
|
8718
|
+
});
|
|
8719
|
+
const guarded = binaryResponse.body.pipeThrough(guard);
|
|
8720
|
+
const nodeStream = Readable.fromWeb(guarded);
|
|
8721
|
+
await pipeline(nodeStream, createWriteStream(tmpPath), { signal: binaryController.signal });
|
|
8722
|
+
clearTimeout(binaryTimeout);
|
|
8723
|
+
binaryTimeout = null;
|
|
8724
|
+
const actualHash = hash.digest("hex");
|
|
8611
8725
|
if (actualHash !== expectedHash) {
|
|
8612
|
-
throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. The binary may have been tampered with
|
|
8726
|
+
throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. ` + "The binary may have been tampered with.");
|
|
8613
8727
|
}
|
|
8614
8728
|
log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
|
|
8615
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
8616
|
-
const { writeFileSync } = await import("fs");
|
|
8617
|
-
writeFileSync(tmpPath, Buffer.from(arrayBuffer));
|
|
8618
8729
|
if (process.platform !== "win32") {
|
|
8619
8730
|
chmodSync(tmpPath, 493);
|
|
8620
8731
|
}
|
|
8621
|
-
const { renameSync } = await import("fs");
|
|
8622
8732
|
renameSync(tmpPath, binaryPath);
|
|
8623
8733
|
log(`AFT binary ready at ${binaryPath}`);
|
|
8624
8734
|
return binaryPath;
|
|
8625
8735
|
} catch (err) {
|
|
8626
8736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8627
8737
|
error(`Failed to download AFT binary: ${msg}`);
|
|
8628
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
8629
8738
|
if (existsSync(tmpPath)) {
|
|
8630
8739
|
try {
|
|
8631
8740
|
unlinkSync(tmpPath);
|
|
8632
8741
|
} catch {}
|
|
8633
8742
|
}
|
|
8634
8743
|
return null;
|
|
8744
|
+
} finally {
|
|
8745
|
+
if (binaryTimeout) {
|
|
8746
|
+
binaryController?.abort();
|
|
8747
|
+
clearTimeout(binaryTimeout);
|
|
8748
|
+
}
|
|
8749
|
+
if (checksumTimeout) {
|
|
8750
|
+
checksumController?.abort();
|
|
8751
|
+
clearTimeout(checksumTimeout);
|
|
8752
|
+
}
|
|
8753
|
+
releaseLock?.();
|
|
8635
8754
|
}
|
|
8636
8755
|
}
|
|
8637
8756
|
async function ensureBinary(version) {
|
|
@@ -8648,6 +8767,39 @@ async function ensureBinary(version) {
|
|
|
8648
8767
|
log("No cached binary found, downloading latest...");
|
|
8649
8768
|
return downloadBinary();
|
|
8650
8769
|
}
|
|
8770
|
+
async function acquireDownloadLock(lockPath) {
|
|
8771
|
+
const startedAt = Date.now();
|
|
8772
|
+
while (true) {
|
|
8773
|
+
try {
|
|
8774
|
+
const fd = openSync(lockPath, "wx");
|
|
8775
|
+
return () => {
|
|
8776
|
+
try {
|
|
8777
|
+
closeSync(fd);
|
|
8778
|
+
} catch {}
|
|
8779
|
+
try {
|
|
8780
|
+
rmSync(lockPath, { force: true });
|
|
8781
|
+
} catch {}
|
|
8782
|
+
};
|
|
8783
|
+
} catch (err) {
|
|
8784
|
+
const code = err.code;
|
|
8785
|
+
if (code !== "EEXIST")
|
|
8786
|
+
throw err;
|
|
8787
|
+
try {
|
|
8788
|
+
const ageMs = Date.now() - statSync(lockPath).mtimeMs;
|
|
8789
|
+
if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
|
|
8790
|
+
rmSync(lockPath, { force: true });
|
|
8791
|
+
continue;
|
|
8792
|
+
}
|
|
8793
|
+
} catch {
|
|
8794
|
+
continue;
|
|
8795
|
+
}
|
|
8796
|
+
if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
|
|
8797
|
+
throw new Error(`Timed out waiting for download lock: ${lockPath}`);
|
|
8798
|
+
}
|
|
8799
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
8800
|
+
}
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8651
8803
|
function parseChecksumForAsset(checksumText, assetName) {
|
|
8652
8804
|
for (const line of checksumText.split(`
|
|
8653
8805
|
`)) {
|
|
@@ -8662,9 +8814,12 @@ function parseChecksumForAsset(checksumText, assetName) {
|
|
|
8662
8814
|
return null;
|
|
8663
8815
|
}
|
|
8664
8816
|
async function fetchLatestTag() {
|
|
8817
|
+
const controller = new AbortController;
|
|
8818
|
+
const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
|
|
8665
8819
|
try {
|
|
8666
8820
|
const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
|
8667
|
-
headers: { Accept: "application/vnd.github.v3+json" }
|
|
8821
|
+
headers: { Accept: "application/vnd.github.v3+json" },
|
|
8822
|
+
signal: controller.signal
|
|
8668
8823
|
});
|
|
8669
8824
|
if (!response.ok)
|
|
8670
8825
|
return null;
|
|
@@ -8672,18 +8827,20 @@ async function fetchLatestTag() {
|
|
|
8672
8827
|
return data.tag_name ?? null;
|
|
8673
8828
|
} catch {
|
|
8674
8829
|
return null;
|
|
8830
|
+
} finally {
|
|
8831
|
+
clearTimeout(timeout);
|
|
8675
8832
|
}
|
|
8676
8833
|
}
|
|
8677
8834
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
8678
8835
|
import { execFileSync } from "child_process";
|
|
8679
|
-
import { createHash } from "crypto";
|
|
8680
|
-
import { chmodSync as chmodSync2, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
8836
|
+
import { createHash as createHash2 } from "crypto";
|
|
8837
|
+
import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync, createWriteStream as createWriteStream2, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, openSync as openSync2, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
8681
8838
|
import { dirname, join as join3, relative, resolve } from "path";
|
|
8682
|
-
import { Readable } from "stream";
|
|
8683
|
-
import { pipeline } from "stream/promises";
|
|
8839
|
+
import { Readable as Readable2 } from "stream";
|
|
8840
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
8684
8841
|
var ORT_VERSION = "1.24.4";
|
|
8685
8842
|
var ORT_REPO = "microsoft/onnxruntime";
|
|
8686
|
-
var
|
|
8843
|
+
var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
|
|
8687
8844
|
var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
|
|
8688
8845
|
var ONNX_LOCK_FILE = ".aft-onnx-installing";
|
|
8689
8846
|
var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
|
|
@@ -8804,7 +8961,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
8804
8961
|
if (Number.isFinite(pid) && pid > 0) {
|
|
8805
8962
|
if (process.platform === "win32") {
|
|
8806
8963
|
try {
|
|
8807
|
-
const ageMs = Date.now() -
|
|
8964
|
+
const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
|
|
8808
8965
|
abandoned = ageMs > STALE_LOCK_MS;
|
|
8809
8966
|
} catch {
|
|
8810
8967
|
abandoned = true;
|
|
@@ -8818,7 +8975,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
8818
8975
|
if (abandoned) {
|
|
8819
8976
|
log(`[onnx] removing abandoned staging dir ${stagingDir}`);
|
|
8820
8977
|
try {
|
|
8821
|
-
|
|
8978
|
+
rmSync2(stagingDir, { recursive: true, force: true });
|
|
8822
8979
|
} catch (err) {
|
|
8823
8980
|
warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
|
|
8824
8981
|
}
|
|
@@ -8830,7 +8987,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
|
8830
8987
|
try {
|
|
8831
8988
|
if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
|
|
8832
8989
|
log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
|
|
8833
|
-
|
|
8990
|
+
rmSync2(ortDir, { recursive: true, force: true });
|
|
8834
8991
|
}
|
|
8835
8992
|
} catch (err) {
|
|
8836
8993
|
warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
|
|
@@ -8910,24 +9067,24 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
8910
9067
|
throw new Error(`download failed (HTTP ${res.status})`);
|
|
8911
9068
|
}
|
|
8912
9069
|
const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
8913
|
-
if (Number.isFinite(advertised) && advertised >
|
|
8914
|
-
throw new Error(`Content-Length ${advertised} exceeds max ${
|
|
9070
|
+
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
9071
|
+
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
8915
9072
|
}
|
|
8916
9073
|
mkdirSync2(dirname(destPath), { recursive: true });
|
|
8917
9074
|
let bytesWritten = 0;
|
|
8918
9075
|
const guard = new TransformStream({
|
|
8919
9076
|
transform(chunk, transformController) {
|
|
8920
9077
|
bytesWritten += chunk.byteLength;
|
|
8921
|
-
if (bytesWritten >
|
|
8922
|
-
transformController.error(new Error(`download exceeded ${
|
|
9078
|
+
if (bytesWritten > MAX_DOWNLOAD_BYTES2) {
|
|
9079
|
+
transformController.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES2} bytes after streaming (server lied about size or sent unbounded body)`));
|
|
8923
9080
|
return;
|
|
8924
9081
|
}
|
|
8925
9082
|
transformController.enqueue(chunk);
|
|
8926
9083
|
}
|
|
8927
9084
|
});
|
|
8928
9085
|
const guarded = res.body.pipeThrough(guard);
|
|
8929
|
-
const nodeStream =
|
|
8930
|
-
await
|
|
9086
|
+
const nodeStream = Readable2.fromWeb(guarded);
|
|
9087
|
+
await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
|
|
8931
9088
|
} catch (err) {
|
|
8932
9089
|
try {
|
|
8933
9090
|
unlinkSync2(destPath);
|
|
@@ -9026,16 +9183,16 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
9026
9183
|
warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
|
|
9027
9184
|
}
|
|
9028
9185
|
writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
|
|
9029
|
-
|
|
9186
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
9030
9187
|
log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
|
|
9031
9188
|
return targetDir;
|
|
9032
9189
|
} catch (err) {
|
|
9033
9190
|
error(`Failed to download ONNX Runtime: ${err}`);
|
|
9034
9191
|
try {
|
|
9035
|
-
|
|
9192
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
9036
9193
|
} catch {}
|
|
9037
9194
|
try {
|
|
9038
|
-
|
|
9195
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9039
9196
|
} catch {}
|
|
9040
9197
|
return null;
|
|
9041
9198
|
}
|
|
@@ -9052,7 +9209,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9052
9209
|
}
|
|
9053
9210
|
} catch (copyErr) {
|
|
9054
9211
|
if (requiredLibs.has(libFile)) {
|
|
9055
|
-
|
|
9212
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9056
9213
|
throw copyErr;
|
|
9057
9214
|
}
|
|
9058
9215
|
log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
|
|
@@ -9067,7 +9224,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9067
9224
|
symlinkSync(link.target, dst);
|
|
9068
9225
|
} catch (symlinkErr) {
|
|
9069
9226
|
if (requiredLibs.has(link.name)) {
|
|
9070
|
-
|
|
9227
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9071
9228
|
throw symlinkErr;
|
|
9072
9229
|
}
|
|
9073
9230
|
log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
|
|
@@ -9075,7 +9232,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9075
9232
|
}
|
|
9076
9233
|
const requiredPath = join3(targetDir, info.libName);
|
|
9077
9234
|
if (!existsSync2(requiredPath)) {
|
|
9078
|
-
|
|
9235
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9079
9236
|
throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
|
|
9080
9237
|
}
|
|
9081
9238
|
}
|
|
@@ -9108,7 +9265,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
9108
9265
|
function readOnnxInstalledMeta(installDir) {
|
|
9109
9266
|
const path = join3(installDir, ONNX_INSTALLED_META_FILE);
|
|
9110
9267
|
try {
|
|
9111
|
-
if (!
|
|
9268
|
+
if (!statSync2(path).isFile())
|
|
9112
9269
|
return null;
|
|
9113
9270
|
const raw = readFileSync(path, "utf8");
|
|
9114
9271
|
const parsed = JSON.parse(raw);
|
|
@@ -9125,20 +9282,20 @@ function readOnnxInstalledMeta(installDir) {
|
|
|
9125
9282
|
}
|
|
9126
9283
|
}
|
|
9127
9284
|
function sha256File(path) {
|
|
9128
|
-
const hash =
|
|
9285
|
+
const hash = createHash2("sha256");
|
|
9129
9286
|
hash.update(readFileSync(path));
|
|
9130
9287
|
return hash.digest("hex");
|
|
9131
9288
|
}
|
|
9132
9289
|
function acquireLock(lockPath) {
|
|
9133
9290
|
const tryClaim = () => {
|
|
9134
9291
|
try {
|
|
9135
|
-
const fd =
|
|
9292
|
+
const fd = openSync2(lockPath, "wx");
|
|
9136
9293
|
try {
|
|
9137
9294
|
writeFileSync(fd, `${process.pid}
|
|
9138
9295
|
${new Date().toISOString()}
|
|
9139
9296
|
`);
|
|
9140
9297
|
} finally {
|
|
9141
|
-
|
|
9298
|
+
closeSync2(fd);
|
|
9142
9299
|
}
|
|
9143
9300
|
return true;
|
|
9144
9301
|
} catch (err) {
|
|
@@ -9159,7 +9316,7 @@ ${new Date().toISOString()}
|
|
|
9159
9316
|
const parsed = Number.parseInt(firstLine, 10);
|
|
9160
9317
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
9161
9318
|
owningPid = parsed;
|
|
9162
|
-
lockMtimeMs =
|
|
9319
|
+
lockMtimeMs = statSync2(lockPath).mtimeMs;
|
|
9163
9320
|
} catch {
|
|
9164
9321
|
return tryClaim();
|
|
9165
9322
|
}
|
|
@@ -9225,15 +9382,6 @@ import { homedir as homedir3 } from "os";
|
|
|
9225
9382
|
var DEFAULT_IDLE_TIMEOUT_MS = Infinity;
|
|
9226
9383
|
var DEFAULT_MAX_POOL_SIZE = 8;
|
|
9227
9384
|
var CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
9228
|
-
|
|
9229
|
-
class HomeProjectRootError extends Error {
|
|
9230
|
-
projectRoot;
|
|
9231
|
-
constructor(projectRoot) {
|
|
9232
|
-
super(`aft refuses to spawn a bridge with project_root=${projectRoot} (user home directory). ` + `Open OpenCode/Pi from a project subdirectory instead, or set the session's ` + `directory to a real project root.`);
|
|
9233
|
-
this.projectRoot = projectRoot;
|
|
9234
|
-
this.name = "HomeProjectRootError";
|
|
9235
|
-
}
|
|
9236
|
-
}
|
|
9237
9385
|
function canonicalHomeDir() {
|
|
9238
9386
|
try {
|
|
9239
9387
|
const home = homedir3();
|
|
@@ -9276,7 +9424,9 @@ class BridgePool {
|
|
|
9276
9424
|
onVersionMismatch: options.onVersionMismatch,
|
|
9277
9425
|
onConfigureWarnings: options.onConfigureWarnings,
|
|
9278
9426
|
onBashCompletion: options.onBashCompletion,
|
|
9279
|
-
onBashLongRunning: options.onBashLongRunning
|
|
9427
|
+
onBashLongRunning: options.onBashLongRunning,
|
|
9428
|
+
errorPrefix: options.errorPrefix,
|
|
9429
|
+
logger: options.logger
|
|
9280
9430
|
};
|
|
9281
9431
|
this.configOverrides = configOverrides;
|
|
9282
9432
|
if (Number.isFinite(this.idleTimeoutMs)) {
|
|
@@ -9294,9 +9444,6 @@ class BridgePool {
|
|
|
9294
9444
|
}
|
|
9295
9445
|
getBridge(projectRoot) {
|
|
9296
9446
|
const key = normalizeKey(projectRoot);
|
|
9297
|
-
if (isHomeDirectoryRoot(key)) {
|
|
9298
|
-
throw new HomeProjectRootError(key);
|
|
9299
|
-
}
|
|
9300
9447
|
const existing = this.bridges.get(key);
|
|
9301
9448
|
if (existing) {
|
|
9302
9449
|
existing.lastUsed = Date.now();
|
|
@@ -9348,24 +9495,32 @@ class BridgePool {
|
|
|
9348
9495
|
}
|
|
9349
9496
|
async replaceBinary(newPath) {
|
|
9350
9497
|
this.binaryPath = newPath;
|
|
9351
|
-
const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
|
|
9352
9498
|
this.bridges.clear();
|
|
9353
|
-
await Promise.allSettled(shutdowns);
|
|
9354
9499
|
this.log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
|
|
9355
9500
|
return newPath;
|
|
9356
9501
|
}
|
|
9357
9502
|
log(message, meta) {
|
|
9358
9503
|
const logger = this.logger ?? getActiveLogger();
|
|
9359
|
-
if (logger)
|
|
9360
|
-
|
|
9361
|
-
|
|
9504
|
+
if (logger) {
|
|
9505
|
+
try {
|
|
9506
|
+
logger.log(message, meta);
|
|
9507
|
+
} catch (err) {
|
|
9508
|
+
console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
9509
|
+
console.error(`[aft-bridge] ${message}`);
|
|
9510
|
+
}
|
|
9511
|
+
} else
|
|
9362
9512
|
log(message, meta);
|
|
9363
9513
|
}
|
|
9364
9514
|
error(message, meta) {
|
|
9365
9515
|
const logger = this.logger ?? getActiveLogger();
|
|
9366
|
-
if (logger)
|
|
9367
|
-
|
|
9368
|
-
|
|
9516
|
+
if (logger) {
|
|
9517
|
+
try {
|
|
9518
|
+
logger.error(message, meta);
|
|
9519
|
+
} catch (err) {
|
|
9520
|
+
console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
9521
|
+
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
9522
|
+
}
|
|
9523
|
+
} else
|
|
9369
9524
|
error(message, meta);
|
|
9370
9525
|
}
|
|
9371
9526
|
setConfigureOverride(key, value) {
|
|
@@ -9392,7 +9547,7 @@ function normalizeKey(projectRoot) {
|
|
|
9392
9547
|
}
|
|
9393
9548
|
// ../aft-bridge/dist/resolver.js
|
|
9394
9549
|
import { execSync, spawnSync } from "child_process";
|
|
9395
|
-
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "fs";
|
|
9550
|
+
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "fs";
|
|
9396
9551
|
import { createRequire } from "module";
|
|
9397
9552
|
import { homedir as homedir4 } from "os";
|
|
9398
9553
|
import { join as join4 } from "path";
|
|
@@ -9423,15 +9578,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
9423
9578
|
const versionedDir = join4(cacheDir, tag);
|
|
9424
9579
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
9425
9580
|
const cachedPath = join4(versionedDir, `aft${ext}`);
|
|
9426
|
-
if (existsSync3(cachedPath))
|
|
9427
|
-
|
|
9581
|
+
if (existsSync3(cachedPath)) {
|
|
9582
|
+
const cachedVersion = readBinaryVersion(cachedPath);
|
|
9583
|
+
if (cachedVersion === version)
|
|
9584
|
+
return cachedPath;
|
|
9585
|
+
warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
|
|
9586
|
+
}
|
|
9428
9587
|
mkdirSync3(versionedDir, { recursive: true });
|
|
9429
|
-
const tmpPath = `${cachedPath}.tmp`;
|
|
9588
|
+
const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
|
|
9430
9589
|
copyFileSync2(npmBinaryPath, tmpPath);
|
|
9431
9590
|
if (process.platform !== "win32") {
|
|
9432
9591
|
chmodSync3(tmpPath, 493);
|
|
9433
9592
|
}
|
|
9434
|
-
|
|
9593
|
+
renameSync2(tmpPath, cachedPath);
|
|
9435
9594
|
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
9436
9595
|
return cachedPath;
|
|
9437
9596
|
} catch (err) {
|
|
@@ -9439,6 +9598,32 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
9439
9598
|
return null;
|
|
9440
9599
|
}
|
|
9441
9600
|
}
|
|
9601
|
+
function normalizeBareVersion(version) {
|
|
9602
|
+
return version.startsWith("v") ? version.slice(1) : version;
|
|
9603
|
+
}
|
|
9604
|
+
function homeDirFromEnv(env) {
|
|
9605
|
+
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
|
|
9606
|
+
}
|
|
9607
|
+
function cacheDirFromEnv(env) {
|
|
9608
|
+
if (process.platform === "win32") {
|
|
9609
|
+
const base2 = env.LOCALAPPDATA || env.APPDATA || join4(homeDirFromEnv(env), "AppData", "Local");
|
|
9610
|
+
return join4(base2, "aft", "bin");
|
|
9611
|
+
}
|
|
9612
|
+
const base = env.XDG_CACHE_HOME || join4(homeDirFromEnv(env), ".cache");
|
|
9613
|
+
return join4(base, "aft", "bin");
|
|
9614
|
+
}
|
|
9615
|
+
function cachedBinaryPathFromEnv(version, env, ext) {
|
|
9616
|
+
const binaryPath = join4(cacheDirFromEnv(env), version, `aft${ext}`);
|
|
9617
|
+
return existsSync3(binaryPath) ? binaryPath : null;
|
|
9618
|
+
}
|
|
9619
|
+
function isExpectedCachedBinary(binaryPath, expectedVersion) {
|
|
9620
|
+
const expected = normalizeBareVersion(expectedVersion);
|
|
9621
|
+
const actual = readBinaryVersion(binaryPath);
|
|
9622
|
+
if (actual === expected)
|
|
9623
|
+
return true;
|
|
9624
|
+
warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
|
|
9625
|
+
return false;
|
|
9626
|
+
}
|
|
9442
9627
|
function platformKey(platform = process.platform, arch = process.arch) {
|
|
9443
9628
|
const archMap = PLATFORM_ARCH_MAP[platform];
|
|
9444
9629
|
if (!archMap) {
|
|
@@ -9452,6 +9637,7 @@ function platformKey(platform = process.platform, arch = process.arch) {
|
|
|
9452
9637
|
}
|
|
9453
9638
|
function findBinarySync(expectedVersion) {
|
|
9454
9639
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
9640
|
+
const env = { ...process.env };
|
|
9455
9641
|
const pluginVersion = expectedVersion ?? (() => {
|
|
9456
9642
|
try {
|
|
9457
9643
|
const req = createRequire(import.meta.url);
|
|
@@ -9462,8 +9648,8 @@ function findBinarySync(expectedVersion) {
|
|
|
9462
9648
|
})();
|
|
9463
9649
|
if (pluginVersion) {
|
|
9464
9650
|
const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
|
|
9465
|
-
const versionCached =
|
|
9466
|
-
if (versionCached)
|
|
9651
|
+
const versionCached = cachedBinaryPathFromEnv(tag, env, ext);
|
|
9652
|
+
if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
|
|
9467
9653
|
return versionCached;
|
|
9468
9654
|
}
|
|
9469
9655
|
try {
|
|
@@ -9473,10 +9659,12 @@ function findBinarySync(expectedVersion) {
|
|
|
9473
9659
|
const resolved = req.resolve(packageBin);
|
|
9474
9660
|
if (existsSync3(resolved)) {
|
|
9475
9661
|
const npmVersion = readBinaryVersion(resolved);
|
|
9476
|
-
if (
|
|
9662
|
+
if (npmVersion === null) {
|
|
9663
|
+
warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
|
|
9664
|
+
} else if (pluginVersion && npmVersion !== normalizeBareVersion(pluginVersion)) {
|
|
9477
9665
|
warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
|
|
9478
9666
|
} else {
|
|
9479
|
-
const copied = copyToVersionedCache(resolved, npmVersion
|
|
9667
|
+
const copied = copyToVersionedCache(resolved, npmVersion);
|
|
9480
9668
|
return copied ?? resolved;
|
|
9481
9669
|
}
|
|
9482
9670
|
}
|
|
@@ -9485,12 +9673,13 @@ function findBinarySync(expectedVersion) {
|
|
|
9485
9673
|
const whichCmd = process.platform === "win32" ? "where aft" : "which aft";
|
|
9486
9674
|
const result = execSync(whichCmd, {
|
|
9487
9675
|
encoding: "utf-8",
|
|
9676
|
+
env,
|
|
9488
9677
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9489
9678
|
}).trim();
|
|
9490
9679
|
if (result)
|
|
9491
9680
|
return result;
|
|
9492
9681
|
} catch {}
|
|
9493
|
-
const cargoPath = join4(
|
|
9682
|
+
const cargoPath = join4(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
|
|
9494
9683
|
if (existsSync3(cargoPath))
|
|
9495
9684
|
return cargoPath;
|
|
9496
9685
|
return null;
|
|
@@ -9525,7 +9714,7 @@ async function findBinary(expectedVersion) {
|
|
|
9525
9714
|
`));
|
|
9526
9715
|
}
|
|
9527
9716
|
// ../aft-bridge/dist/url-fetch.js
|
|
9528
|
-
import { createHash as
|
|
9717
|
+
import { createHash as createHash3 } from "crypto";
|
|
9529
9718
|
import { lookup } from "dns/promises";
|
|
9530
9719
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
9531
9720
|
import { isIP } from "net";
|
|
@@ -9539,7 +9728,7 @@ function cacheDir(storageDir) {
|
|
|
9539
9728
|
return join5(storageDir, "url_cache");
|
|
9540
9729
|
}
|
|
9541
9730
|
function hashUrl(url) {
|
|
9542
|
-
return
|
|
9731
|
+
return createHash3("sha256").update(url).digest("hex").slice(0, 16);
|
|
9543
9732
|
}
|
|
9544
9733
|
function metaPath(storageDir, hash) {
|
|
9545
9734
|
return join5(cacheDir(storageDir), `${hash}.meta.json`);
|
|
@@ -9797,8 +9986,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
9797
9986
|
const contentFile = contentPath(storageDir, hash, extension);
|
|
9798
9987
|
const tmpContent = `${contentFile}.tmp-${process.pid}`;
|
|
9799
9988
|
writeFileSync2(tmpContent, body);
|
|
9800
|
-
const { renameSync:
|
|
9801
|
-
|
|
9989
|
+
const { renameSync: renameSync3 } = await import("fs");
|
|
9990
|
+
renameSync3(tmpContent, contentFile);
|
|
9802
9991
|
const meta = {
|
|
9803
9992
|
url,
|
|
9804
9993
|
contentType,
|
|
@@ -9807,7 +9996,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
9807
9996
|
};
|
|
9808
9997
|
const tmpMeta = `${metaFile}.tmp-${process.pid}`;
|
|
9809
9998
|
writeFileSync2(tmpMeta, JSON.stringify(meta));
|
|
9810
|
-
|
|
9999
|
+
renameSync3(tmpMeta, metaFile);
|
|
9811
10000
|
log(`URL cached (${total} bytes): ${url}`);
|
|
9812
10001
|
return contentFile;
|
|
9813
10002
|
}
|
|
@@ -9961,24 +10150,24 @@ function warn2(message, data) {
|
|
|
9961
10150
|
function error2(message, data) {
|
|
9962
10151
|
write("ERROR", message, data);
|
|
9963
10152
|
}
|
|
9964
|
-
function
|
|
10153
|
+
function sessionLog(sessionId, message, data) {
|
|
9965
10154
|
write("INFO", message, data, sessionId);
|
|
9966
10155
|
}
|
|
9967
|
-
function
|
|
10156
|
+
function sessionWarn(sessionId, message, data) {
|
|
9968
10157
|
write("WARN", message, data, sessionId);
|
|
9969
10158
|
}
|
|
9970
|
-
function
|
|
10159
|
+
function sessionError(sessionId, message, data) {
|
|
9971
10160
|
write("ERROR", message, data, sessionId);
|
|
9972
10161
|
}
|
|
9973
10162
|
var bridgeLogger = {
|
|
9974
10163
|
log(message, meta) {
|
|
9975
|
-
|
|
10164
|
+
sessionLog(meta?.sessionId, message);
|
|
9976
10165
|
},
|
|
9977
10166
|
warn(message, meta) {
|
|
9978
|
-
|
|
10167
|
+
sessionWarn(meta?.sessionId, message);
|
|
9979
10168
|
},
|
|
9980
10169
|
error(message, meta) {
|
|
9981
|
-
|
|
10170
|
+
sessionError(meta?.sessionId, message);
|
|
9982
10171
|
},
|
|
9983
10172
|
getLogFilePath: () => logFile
|
|
9984
10173
|
};
|
|
@@ -10073,6 +10262,7 @@ var sessionBgStates = new Map;
|
|
|
10073
10262
|
var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
|
|
10074
10263
|
var DEBOUNCE_STEP_MS = 200;
|
|
10075
10264
|
var DEBOUNCE_CAP_MS = 1000;
|
|
10265
|
+
var MAX_WAKE_SEND_ATTEMPTS = 5;
|
|
10076
10266
|
var UNKNOWN_COMPLETION_TTL_MS = 5000;
|
|
10077
10267
|
var UNKNOWN_COMPLETION_CAP = 32;
|
|
10078
10268
|
var DEFAULT_SESSION_ID = "__default__";
|
|
@@ -10125,17 +10315,24 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
10125
10315
|
return;
|
|
10126
10316
|
const state = stateFor(drainContext.sessionID);
|
|
10127
10317
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
|
|
10128
|
-
|
|
10318
|
+
await drainCompletions(drainContext);
|
|
10319
|
+
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
|
|
10320
|
+
return;
|
|
10321
|
+
}
|
|
10129
10322
|
}
|
|
10130
|
-
if (state.outstandingTaskIds.size > 0) {
|
|
10323
|
+
if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
|
|
10131
10324
|
await drainCompletions(drainContext);
|
|
10132
10325
|
}
|
|
10133
10326
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
|
|
10134
10327
|
return;
|
|
10328
|
+
const deliveredCompletions = [...state.pendingCompletions];
|
|
10135
10329
|
const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
|
|
10136
10330
|
output.output = appendReminder(output.output ?? "", reminder);
|
|
10137
10331
|
state.pendingCompletions = [];
|
|
10138
10332
|
state.pendingLongRunning = [];
|
|
10333
|
+
state.wakeRetryAttempts = 0;
|
|
10334
|
+
state.wakeHardStopped = false;
|
|
10335
|
+
await ackCompletions(drainContext, deliveredCompletions);
|
|
10139
10336
|
if (state.debounceTimer) {
|
|
10140
10337
|
clearTimeout(state.debounceTimer);
|
|
10141
10338
|
state.debounceTimer = null;
|
|
@@ -10149,12 +10346,12 @@ async function handleIdleBgCompletions(drainContext) {
|
|
|
10149
10346
|
}
|
|
10150
10347
|
async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
10151
10348
|
const state = stateFor(drainContext.sessionID);
|
|
10152
|
-
if (!skipDrain && state.outstandingTaskIds.size > 0) {
|
|
10349
|
+
if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
|
|
10153
10350
|
await drainCompletions(drainContext);
|
|
10154
10351
|
}
|
|
10155
10352
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
|
|
10156
10353
|
return;
|
|
10157
|
-
scheduleWake(state, async (reminder) => {
|
|
10354
|
+
scheduleWake(state, async (reminder, deliveredCompletions) => {
|
|
10158
10355
|
const client = drainContext.client;
|
|
10159
10356
|
if (typeof client.session?.promptAsync !== "function") {
|
|
10160
10357
|
throw new Error("client.session.promptAsync is unavailable");
|
|
@@ -10178,8 +10375,9 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
10178
10375
|
path: { id: drainContext.sessionID },
|
|
10179
10376
|
body
|
|
10180
10377
|
});
|
|
10181
|
-
|
|
10182
|
-
|
|
10378
|
+
await ackCompletions(drainContext, deliveredCompletions);
|
|
10379
|
+
}, (err, hardStopped) => {
|
|
10380
|
+
sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10183
10381
|
});
|
|
10184
10382
|
}
|
|
10185
10383
|
function formatSystemReminder(completions) {
|
|
@@ -10230,19 +10428,40 @@ function extractSessionID(value) {
|
|
|
10230
10428
|
return;
|
|
10231
10429
|
}
|
|
10232
10430
|
async function drainCompletions({ ctx, directory, sessionID }) {
|
|
10431
|
+
const state = stateFor(sessionID);
|
|
10233
10432
|
try {
|
|
10234
10433
|
const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
|
|
10235
10434
|
const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
|
|
10236
10435
|
if (response.success === false) {
|
|
10237
|
-
|
|
10436
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
|
|
10238
10437
|
return;
|
|
10239
10438
|
}
|
|
10240
|
-
|
|
10439
|
+
state.forcedDrainCompleted = true;
|
|
10440
|
+
ingestDrainedBgCompletions(sessionID, response.bg_completions);
|
|
10241
10441
|
} catch (err) {
|
|
10242
|
-
|
|
10442
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10443
|
+
}
|
|
10444
|
+
}
|
|
10445
|
+
async function ackCompletions({ ctx, directory, sessionID }, completions) {
|
|
10446
|
+
const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
|
|
10447
|
+
if (taskIds.length === 0)
|
|
10448
|
+
return;
|
|
10449
|
+
try {
|
|
10450
|
+
const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
|
|
10451
|
+
const response = await bridge.send("bash_ack_completions", {
|
|
10452
|
+
session_id: sessionID,
|
|
10453
|
+
task_ids: taskIds
|
|
10454
|
+
});
|
|
10455
|
+
if (response.success === false) {
|
|
10456
|
+
sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
|
|
10457
|
+
}
|
|
10458
|
+
} catch (err) {
|
|
10459
|
+
sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10243
10460
|
}
|
|
10244
10461
|
}
|
|
10245
10462
|
function scheduleWake(state, sendWake, onSendFailure) {
|
|
10463
|
+
if (state.wakeHardStopped)
|
|
10464
|
+
return;
|
|
10246
10465
|
const now = Date.now();
|
|
10247
10466
|
const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
|
|
10248
10467
|
if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
|
|
@@ -10271,13 +10490,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
|
|
|
10271
10490
|
const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
|
|
10272
10491
|
state.pendingCompletions = [];
|
|
10273
10492
|
state.pendingLongRunning = [];
|
|
10274
|
-
sendWake(reminder).then(() => {
|
|
10493
|
+
sendWake(reminder, pending).then(() => {
|
|
10275
10494
|
state.retryDelayMs = null;
|
|
10495
|
+
state.wakeRetryAttempts = 0;
|
|
10496
|
+
state.wakeHardStopped = false;
|
|
10276
10497
|
}).catch((err) => {
|
|
10277
10498
|
state.pendingCompletions = [...pending, ...state.pendingCompletions];
|
|
10278
10499
|
state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
|
|
10500
|
+
state.wakeRetryAttempts += 1;
|
|
10501
|
+
if (state.wakeRetryAttempts >= MAX_WAKE_SEND_ATTEMPTS) {
|
|
10502
|
+
state.retryDelayMs = null;
|
|
10503
|
+
state.wakeHardStopped = true;
|
|
10504
|
+
onSendFailure(err, true);
|
|
10505
|
+
return;
|
|
10506
|
+
}
|
|
10279
10507
|
state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
|
|
10280
|
-
onSendFailure(err);
|
|
10508
|
+
onSendFailure(err, false);
|
|
10281
10509
|
scheduleWake(state, sendWake, onSendFailure);
|
|
10282
10510
|
});
|
|
10283
10511
|
}, delay);
|
|
@@ -10298,6 +10526,9 @@ function stateFor(sessionID) {
|
|
|
10298
10526
|
scheduledFireAt: null,
|
|
10299
10527
|
scheduledCompletionCount: 0,
|
|
10300
10528
|
retryDelayMs: null,
|
|
10529
|
+
wakeRetryAttempts: 0,
|
|
10530
|
+
wakeHardStopped: false,
|
|
10531
|
+
forcedDrainCompleted: false,
|
|
10301
10532
|
unknownCompletions: [],
|
|
10302
10533
|
lastSeenAt: now
|
|
10303
10534
|
};
|
|
@@ -10307,6 +10538,22 @@ function stateFor(sessionID) {
|
|
|
10307
10538
|
}
|
|
10308
10539
|
return state;
|
|
10309
10540
|
}
|
|
10541
|
+
function ingestDrainedBgCompletions(sessionID, completions) {
|
|
10542
|
+
if (!Array.isArray(completions) || completions.length === 0)
|
|
10543
|
+
return [];
|
|
10544
|
+
const state = stateFor(sessionID);
|
|
10545
|
+
const accepted = [];
|
|
10546
|
+
for (const completion of completions) {
|
|
10547
|
+
if (!isBgCompletion(completion))
|
|
10548
|
+
continue;
|
|
10549
|
+
state.outstandingTaskIds.delete(completion.task_id);
|
|
10550
|
+
if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
|
|
10551
|
+
accepted.push(completion);
|
|
10552
|
+
}
|
|
10553
|
+
}
|
|
10554
|
+
state.pendingCompletions.push(...accepted);
|
|
10555
|
+
return accepted;
|
|
10556
|
+
}
|
|
10310
10557
|
function cleanupIdleSessionStates(now) {
|
|
10311
10558
|
const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
|
|
10312
10559
|
for (const [sessionID, state] of sessionBgStates) {
|
|
@@ -10397,7 +10644,7 @@ function formatDuration(completion) {
|
|
|
10397
10644
|
|
|
10398
10645
|
// src/config.ts
|
|
10399
10646
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
10400
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as
|
|
10647
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
10401
10648
|
import { homedir as homedir5 } from "os";
|
|
10402
10649
|
import { join as join7 } from "path";
|
|
10403
10650
|
|
|
@@ -24176,7 +24423,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
|
|
|
24176
24423
|
${serialized}` : serialized;
|
|
24177
24424
|
tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
24178
24425
|
writeFileSync3(tmpPath, nextContent, "utf-8");
|
|
24179
|
-
|
|
24426
|
+
renameSync3(tmpPath, configPath);
|
|
24180
24427
|
logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
|
|
24181
24428
|
return { migrated: true, oldKeys };
|
|
24182
24429
|
} catch (err) {
|
|
@@ -24411,19 +24658,19 @@ function loadAftConfig(projectDirectory) {
|
|
|
24411
24658
|
}
|
|
24412
24659
|
|
|
24413
24660
|
// src/hooks/auto-update-checker/index.ts
|
|
24414
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as
|
|
24661
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
24415
24662
|
import { dirname as dirname4, join as join11 } from "path";
|
|
24416
24663
|
|
|
24417
24664
|
// src/hooks/auto-update-checker/cache.ts
|
|
24418
24665
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
24419
24666
|
import { spawn as spawn2 } from "child_process";
|
|
24420
|
-
import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as
|
|
24667
|
+
import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
24421
24668
|
import { tmpdir as tmpdir2 } from "os";
|
|
24422
24669
|
import { basename, dirname as dirname3, join as join10 } from "path";
|
|
24423
24670
|
|
|
24424
24671
|
// src/hooks/auto-update-checker/checker.ts
|
|
24425
24672
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
24426
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as
|
|
24673
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
24427
24674
|
import { homedir as homedir7 } from "os";
|
|
24428
24675
|
import { dirname as dirname2, isAbsolute, join as join9, resolve as resolve2 } from "path";
|
|
24429
24676
|
import { fileURLToPath } from "url";
|
|
@@ -24550,7 +24797,7 @@ function getLocalDevPath(directory) {
|
|
|
24550
24797
|
}
|
|
24551
24798
|
function findPackageJsonUp(startPath) {
|
|
24552
24799
|
try {
|
|
24553
|
-
const stat =
|
|
24800
|
+
const stat = statSync3(startPath);
|
|
24554
24801
|
let dir = stat.isDirectory() ? startPath : dirname2(startPath);
|
|
24555
24802
|
for (let i = 0;i < 10; i++) {
|
|
24556
24803
|
const pkgPath = join9(dir, "package.json");
|
|
@@ -24690,19 +24937,19 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
|
24690
24937
|
function restoreAutoUpdateSnapshot(snapshot) {
|
|
24691
24938
|
try {
|
|
24692
24939
|
if (snapshot.packageJson === null)
|
|
24693
|
-
|
|
24940
|
+
rmSync3(snapshot.packageJsonPath, { force: true });
|
|
24694
24941
|
else
|
|
24695
24942
|
writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
|
|
24696
24943
|
if (snapshot.lockfile === null)
|
|
24697
|
-
|
|
24944
|
+
rmSync3(snapshot.lockfilePath, { force: true });
|
|
24698
24945
|
else
|
|
24699
24946
|
writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
|
|
24700
|
-
|
|
24947
|
+
rmSync3(snapshot.packageDir, { recursive: true, force: true });
|
|
24701
24948
|
if (snapshot.stagedPackageDir) {
|
|
24702
24949
|
cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
|
|
24703
24950
|
}
|
|
24704
24951
|
} finally {
|
|
24705
|
-
|
|
24952
|
+
rmSync3(snapshot.tempDir, { recursive: true, force: true });
|
|
24706
24953
|
}
|
|
24707
24954
|
}
|
|
24708
24955
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
@@ -24767,7 +25014,7 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
24767
25014
|
const packageDir = join10(installDir, "node_modules", packageName);
|
|
24768
25015
|
if (!existsSync7(packageDir))
|
|
24769
25016
|
return false;
|
|
24770
|
-
|
|
25017
|
+
rmSync3(packageDir, { recursive: true, force: true });
|
|
24771
25018
|
log2(`[auto-update-checker] Package removed: ${packageDir}`);
|
|
24772
25019
|
return true;
|
|
24773
25020
|
}
|
|
@@ -24852,7 +25099,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24852
25099
|
if (!result && snapshot) {
|
|
24853
25100
|
restoreAutoUpdateSnapshot(snapshot);
|
|
24854
25101
|
} else if (snapshot) {
|
|
24855
|
-
|
|
25102
|
+
rmSync3(snapshot.tempDir, { recursive: true, force: true });
|
|
24856
25103
|
}
|
|
24857
25104
|
return result;
|
|
24858
25105
|
} catch (err) {
|
|
@@ -24936,7 +25183,7 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
24936
25183
|
mkdirSync5(dirname4(file2), { recursive: true });
|
|
24937
25184
|
const tmp = `${file2}.tmp.${process.pid}`;
|
|
24938
25185
|
writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
24939
|
-
|
|
25186
|
+
renameSync4(tmp, file2);
|
|
24940
25187
|
return true;
|
|
24941
25188
|
} catch (err) {
|
|
24942
25189
|
warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
|
|
@@ -25023,17 +25270,17 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
25023
25270
|
|
|
25024
25271
|
// src/lsp-auto-install.ts
|
|
25025
25272
|
import { spawn as spawn3 } from "child_process";
|
|
25026
|
-
import { createHash as
|
|
25027
|
-
import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as
|
|
25273
|
+
import { createHash as createHash4 } from "crypto";
|
|
25274
|
+
import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
|
|
25028
25275
|
import { join as join14 } from "path";
|
|
25029
25276
|
|
|
25030
25277
|
// src/lsp-cache.ts
|
|
25031
25278
|
import {
|
|
25032
|
-
closeSync as
|
|
25279
|
+
closeSync as closeSync3,
|
|
25033
25280
|
mkdirSync as mkdirSync6,
|
|
25034
|
-
openSync as
|
|
25281
|
+
openSync as openSync3,
|
|
25035
25282
|
readFileSync as readFileSync7,
|
|
25036
|
-
statSync as
|
|
25283
|
+
statSync as statSync4,
|
|
25037
25284
|
unlinkSync as unlinkSync5,
|
|
25038
25285
|
writeFileSync as writeFileSync7
|
|
25039
25286
|
} from "fs";
|
|
@@ -25066,7 +25313,7 @@ function lspBinDir(npmPackage) {
|
|
|
25066
25313
|
function isInstalled(npmPackage, binary) {
|
|
25067
25314
|
for (const candidate of lspBinaryCandidates(binary)) {
|
|
25068
25315
|
try {
|
|
25069
|
-
if (
|
|
25316
|
+
if (statSync4(join12(lspBinDir(npmPackage), candidate)).isFile())
|
|
25070
25317
|
return true;
|
|
25071
25318
|
} catch {}
|
|
25072
25319
|
}
|
|
@@ -25094,7 +25341,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
|
25094
25341
|
function readInstalledMetaIn(installDir) {
|
|
25095
25342
|
const path2 = join12(installDir, INSTALLED_META_FILE);
|
|
25096
25343
|
try {
|
|
25097
|
-
if (!
|
|
25344
|
+
if (!statSync4(path2).isFile())
|
|
25098
25345
|
return null;
|
|
25099
25346
|
const raw = readFileSync7(path2, "utf8");
|
|
25100
25347
|
const parsed = JSON.parse(raw);
|
|
@@ -25124,13 +25371,13 @@ function acquireInstallLock(lockKey) {
|
|
|
25124
25371
|
const lock = lockPath(lockKey);
|
|
25125
25372
|
const tryClaim = () => {
|
|
25126
25373
|
try {
|
|
25127
|
-
const fd =
|
|
25374
|
+
const fd = openSync3(lock, "wx");
|
|
25128
25375
|
try {
|
|
25129
25376
|
writeFileSync7(fd, `${process.pid}
|
|
25130
25377
|
${new Date().toISOString()}
|
|
25131
25378
|
`);
|
|
25132
25379
|
} finally {
|
|
25133
|
-
|
|
25380
|
+
closeSync3(fd);
|
|
25134
25381
|
}
|
|
25135
25382
|
return true;
|
|
25136
25383
|
} catch (err) {
|
|
@@ -25151,7 +25398,7 @@ ${new Date().toISOString()}
|
|
|
25151
25398
|
const parsed = Number.parseInt(firstLine, 10);
|
|
25152
25399
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
25153
25400
|
owningPid = parsed;
|
|
25154
|
-
lockMtimeMs =
|
|
25401
|
+
lockMtimeMs = statSync4(lock).mtimeMs;
|
|
25155
25402
|
} catch {
|
|
25156
25403
|
return tryClaim();
|
|
25157
25404
|
}
|
|
@@ -25692,7 +25939,7 @@ function hashInstalledBinary(spec) {
|
|
|
25692
25939
|
let pathToHash = null;
|
|
25693
25940
|
for (const p of candidates) {
|
|
25694
25941
|
try {
|
|
25695
|
-
if (
|
|
25942
|
+
if (statSync5(p).isFile()) {
|
|
25696
25943
|
pathToHash = p;
|
|
25697
25944
|
break;
|
|
25698
25945
|
}
|
|
@@ -25702,7 +25949,7 @@ function hashInstalledBinary(spec) {
|
|
|
25702
25949
|
reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
|
|
25703
25950
|
return;
|
|
25704
25951
|
}
|
|
25705
|
-
const hash2 =
|
|
25952
|
+
const hash2 = createHash4("sha256");
|
|
25706
25953
|
const stream = createReadStream(pathToHash);
|
|
25707
25954
|
stream.on("error", reject);
|
|
25708
25955
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
@@ -25718,14 +25965,14 @@ function installedBinaryPath(spec) {
|
|
|
25718
25965
|
] : [lspBinaryPath(spec.npm, spec.binary)];
|
|
25719
25966
|
for (const candidate of candidates) {
|
|
25720
25967
|
try {
|
|
25721
|
-
if (
|
|
25968
|
+
if (statSync5(candidate).isFile())
|
|
25722
25969
|
return candidate;
|
|
25723
25970
|
} catch {}
|
|
25724
25971
|
}
|
|
25725
25972
|
return null;
|
|
25726
25973
|
}
|
|
25727
25974
|
function sha256OfFileSync(path2) {
|
|
25728
|
-
return
|
|
25975
|
+
return createHash4("sha256").update(readFileSync8(path2)).digest("hex");
|
|
25729
25976
|
}
|
|
25730
25977
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
25731
25978
|
const packageDir = lspPackageDir(spec.npm);
|
|
@@ -25733,8 +25980,8 @@ function quarantineCachedNpmInstall(spec, reason) {
|
|
|
25733
25980
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
25734
25981
|
try {
|
|
25735
25982
|
mkdirSync7(join14(dest, ".."), { recursive: true });
|
|
25736
|
-
|
|
25737
|
-
|
|
25983
|
+
rmSync4(dest, { recursive: true, force: true });
|
|
25984
|
+
renameSync5(packageDir, dest);
|
|
25738
25985
|
} catch (err) {
|
|
25739
25986
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
25740
25987
|
}
|
|
@@ -25810,11 +26057,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
25810
26057
|
|
|
25811
26058
|
// src/lsp-github-install.ts
|
|
25812
26059
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
25813
|
-
import { createHash as
|
|
26060
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
25814
26061
|
import {
|
|
25815
26062
|
copyFileSync as copyFileSync3,
|
|
25816
26063
|
createReadStream as createReadStream2,
|
|
25817
|
-
createWriteStream as
|
|
26064
|
+
createWriteStream as createWriteStream3,
|
|
25818
26065
|
existsSync as existsSync10,
|
|
25819
26066
|
lstatSync as lstatSync2,
|
|
25820
26067
|
mkdirSync as mkdirSync8,
|
|
@@ -25822,14 +26069,15 @@ import {
|
|
|
25822
26069
|
readFileSync as readFileSync9,
|
|
25823
26070
|
readlinkSync as readlinkSync2,
|
|
25824
26071
|
realpathSync as realpathSync3,
|
|
25825
|
-
renameSync as
|
|
25826
|
-
rmSync as
|
|
25827
|
-
statSync as
|
|
25828
|
-
unlinkSync as unlinkSync6
|
|
26072
|
+
renameSync as renameSync6,
|
|
26073
|
+
rmSync as rmSync5,
|
|
26074
|
+
statSync as statSync6,
|
|
26075
|
+
unlinkSync as unlinkSync6,
|
|
26076
|
+
writeFileSync as writeFileSync8
|
|
25829
26077
|
} from "fs";
|
|
25830
26078
|
import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
|
|
25831
|
-
import { Readable as
|
|
25832
|
-
import { pipeline as
|
|
26079
|
+
import { Readable as Readable3 } from "stream";
|
|
26080
|
+
import { pipeline as pipeline3 } from "stream/promises";
|
|
25833
26081
|
|
|
25834
26082
|
// src/lsp-github-table.ts
|
|
25835
26083
|
function exe(platform2, name) {
|
|
@@ -25928,6 +26176,7 @@ function ghBinDir(spec) {
|
|
|
25928
26176
|
function ghExtractDir(spec) {
|
|
25929
26177
|
return join15(ghPackageDir(spec), "extracted");
|
|
25930
26178
|
}
|
|
26179
|
+
var INSTALLED_META_FILE2 = ".aft-installed";
|
|
25931
26180
|
function ghBinaryPath(spec, platform2) {
|
|
25932
26181
|
const ext = platform2 === "win32" ? ".exe" : "";
|
|
25933
26182
|
return join15(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
@@ -25935,7 +26184,7 @@ function ghBinaryPath(spec, platform2) {
|
|
|
25935
26184
|
function isGithubInstalled(spec, platform2) {
|
|
25936
26185
|
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
25937
26186
|
try {
|
|
25938
|
-
if (
|
|
26187
|
+
if (statSync6(join15(ghBinDir(spec), candidate)).isFile())
|
|
25939
26188
|
return true;
|
|
25940
26189
|
} catch {}
|
|
25941
26190
|
}
|
|
@@ -25946,11 +26195,45 @@ function ghBinaryCandidates(spec, platform2) {
|
|
|
25946
26195
|
return [spec.binary];
|
|
25947
26196
|
return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
|
|
25948
26197
|
}
|
|
25949
|
-
|
|
26198
|
+
function readGithubInstalledMetaIn(installDir) {
|
|
26199
|
+
try {
|
|
26200
|
+
const path2 = join15(installDir, INSTALLED_META_FILE2);
|
|
26201
|
+
if (!statSync6(path2).isFile())
|
|
26202
|
+
return null;
|
|
26203
|
+
const parsed = JSON.parse(readFileSync9(path2, "utf8"));
|
|
26204
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
26205
|
+
return null;
|
|
26206
|
+
return {
|
|
26207
|
+
version: parsed.version,
|
|
26208
|
+
installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
|
|
26209
|
+
...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
|
|
26210
|
+
...typeof parsed.binarySha256 === "string" && parsed.binarySha256.length > 0 ? { binarySha256: parsed.binarySha256 } : {},
|
|
26211
|
+
...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
|
|
26212
|
+
};
|
|
26213
|
+
} catch {
|
|
26214
|
+
return null;
|
|
26215
|
+
}
|
|
26216
|
+
}
|
|
26217
|
+
function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
|
|
26218
|
+
try {
|
|
26219
|
+
mkdirSync8(installDir, { recursive: true });
|
|
26220
|
+
const meta3 = {
|
|
26221
|
+
version: version2,
|
|
26222
|
+
installedAt: new Date().toISOString(),
|
|
26223
|
+
sha256: binarySha256,
|
|
26224
|
+
binarySha256,
|
|
26225
|
+
...archiveSha256 ? { archiveSha256 } : {}
|
|
26226
|
+
};
|
|
26227
|
+
writeFileSync8(join15(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
|
|
26228
|
+
} catch (err) {
|
|
26229
|
+
warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
|
|
26230
|
+
}
|
|
26231
|
+
}
|
|
26232
|
+
var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
|
|
25950
26233
|
var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
|
|
25951
26234
|
function sha256OfFile(path2) {
|
|
25952
26235
|
return new Promise((resolve4, reject) => {
|
|
25953
|
-
const hash2 =
|
|
26236
|
+
const hash2 = createHash5("sha256");
|
|
25954
26237
|
const stream = createReadStream2(path2);
|
|
25955
26238
|
stream.on("error", reject);
|
|
25956
26239
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
@@ -25958,7 +26241,7 @@ function sha256OfFile(path2) {
|
|
|
25958
26241
|
});
|
|
25959
26242
|
}
|
|
25960
26243
|
function sha256OfFileSync2(path2) {
|
|
25961
|
-
return
|
|
26244
|
+
return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
|
|
25962
26245
|
}
|
|
25963
26246
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
25964
26247
|
const candidates = [];
|
|
@@ -26121,8 +26404,8 @@ function assertAllowedDownloadUrl(rawUrl) {
|
|
|
26121
26404
|
return parsed;
|
|
26122
26405
|
}
|
|
26123
26406
|
async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
26124
|
-
if (assetSize !== undefined && assetSize >
|
|
26125
|
-
throw new Error(`asset size ${assetSize} exceeds max ${
|
|
26407
|
+
if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES3) {
|
|
26408
|
+
throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES3} (set lsp.versions to pin a smaller release if this is wrong)`);
|
|
26126
26409
|
}
|
|
26127
26410
|
const timeout = controlledTimeoutSignal(120000, signal);
|
|
26128
26411
|
try {
|
|
@@ -26131,24 +26414,24 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
26131
26414
|
throw new Error(`download failed (${res.status})`);
|
|
26132
26415
|
}
|
|
26133
26416
|
const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
26134
|
-
if (Number.isFinite(advertised) && advertised >
|
|
26135
|
-
throw new Error(`Content-Length ${advertised} exceeds max ${
|
|
26417
|
+
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
26418
|
+
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
26136
26419
|
}
|
|
26137
26420
|
mkdirSync8(dirname5(destPath), { recursive: true });
|
|
26138
26421
|
let bytesWritten = 0;
|
|
26139
26422
|
const guard = new TransformStream({
|
|
26140
26423
|
transform(chunk, controller) {
|
|
26141
26424
|
bytesWritten += chunk.byteLength;
|
|
26142
|
-
if (bytesWritten >
|
|
26143
|
-
controller.error(new Error(`download exceeded ${
|
|
26425
|
+
if (bytesWritten > MAX_DOWNLOAD_BYTES3) {
|
|
26426
|
+
controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES3} bytes after streaming (server lied about size or sent unbounded body)`));
|
|
26144
26427
|
return;
|
|
26145
26428
|
}
|
|
26146
26429
|
controller.enqueue(chunk);
|
|
26147
26430
|
}
|
|
26148
26431
|
});
|
|
26149
26432
|
const guarded = res.body.pipeThrough(guard);
|
|
26150
|
-
const nodeStream =
|
|
26151
|
-
await
|
|
26433
|
+
const nodeStream = Readable3.fromWeb(guarded);
|
|
26434
|
+
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
26152
26435
|
} catch (err) {
|
|
26153
26436
|
try {
|
|
26154
26437
|
unlinkSync6(destPath);
|
|
@@ -26228,7 +26511,7 @@ function validateExtraction(stagingRoot) {
|
|
|
26228
26511
|
};
|
|
26229
26512
|
walk(realStagingRoot);
|
|
26230
26513
|
}
|
|
26231
|
-
function
|
|
26514
|
+
function precheckArchiveContents(archivePath, archiveType) {
|
|
26232
26515
|
let totalBytes = 0;
|
|
26233
26516
|
if (archiveType === "zip") {
|
|
26234
26517
|
const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
|
|
@@ -26239,6 +26522,9 @@ function precheckArchiveSize(archivePath, archiveType) {
|
|
|
26239
26522
|
const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
|
|
26240
26523
|
for (const line of out.split(`
|
|
26241
26524
|
`)) {
|
|
26525
|
+
if (line.startsWith("h")) {
|
|
26526
|
+
throw new Error(`archive contains hardlink entry: ${line.trim()}`);
|
|
26527
|
+
}
|
|
26242
26528
|
const parts = line.trim().split(/\s+/);
|
|
26243
26529
|
if (parts.length >= 6) {
|
|
26244
26530
|
const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
|
|
@@ -26255,20 +26541,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
|
26255
26541
|
const suffix = randomBytes(8).toString("hex");
|
|
26256
26542
|
const stagingDir = `${destDir}.staging-${suffix}`;
|
|
26257
26543
|
try {
|
|
26258
|
-
|
|
26544
|
+
rmSync5(stagingDir, { recursive: true, force: true });
|
|
26259
26545
|
} catch {}
|
|
26260
26546
|
mkdirSync8(stagingDir, { recursive: true });
|
|
26261
26547
|
try {
|
|
26262
|
-
|
|
26548
|
+
precheckArchiveContents(archivePath, archiveType);
|
|
26263
26549
|
runPlatformExtractor(archivePath, stagingDir, archiveType);
|
|
26264
26550
|
validateExtraction(stagingDir);
|
|
26265
26551
|
try {
|
|
26266
|
-
|
|
26552
|
+
rmSync5(destDir, { recursive: true, force: true });
|
|
26267
26553
|
} catch {}
|
|
26268
|
-
|
|
26554
|
+
renameSync6(stagingDir, destDir);
|
|
26269
26555
|
} catch (err) {
|
|
26270
26556
|
try {
|
|
26271
|
-
|
|
26557
|
+
rmSync5(stagingDir, { recursive: true, force: true });
|
|
26272
26558
|
} catch {}
|
|
26273
26559
|
throw err;
|
|
26274
26560
|
}
|
|
@@ -26279,28 +26565,44 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
26279
26565
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
26280
26566
|
try {
|
|
26281
26567
|
mkdirSync8(dirname5(dest), { recursive: true });
|
|
26282
|
-
|
|
26283
|
-
|
|
26568
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
26569
|
+
renameSync6(packageDir, dest);
|
|
26284
26570
|
} catch (err) {
|
|
26285
26571
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
26286
26572
|
}
|
|
26287
26573
|
}
|
|
26288
26574
|
function validateCachedGithubInstall(spec, platform2) {
|
|
26289
|
-
const
|
|
26575
|
+
const packageDir = ghPackageDir(spec);
|
|
26576
|
+
const meta3 = readGithubInstalledMetaIn(packageDir);
|
|
26290
26577
|
const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
|
|
26291
26578
|
try {
|
|
26292
|
-
return
|
|
26579
|
+
return statSync6(candidate).isFile();
|
|
26293
26580
|
} catch {
|
|
26294
26581
|
return false;
|
|
26295
26582
|
}
|
|
26296
26583
|
});
|
|
26297
|
-
if (!meta3?.
|
|
26584
|
+
if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
|
|
26298
26585
|
quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
|
|
26299
26586
|
return false;
|
|
26300
26587
|
}
|
|
26301
26588
|
const currentHash = sha256OfFileSync2(binaryPath);
|
|
26302
|
-
|
|
26303
|
-
|
|
26589
|
+
const recordedBinaryHash = meta3.binarySha256 ?? meta3.sha256;
|
|
26590
|
+
if (recordedBinaryHash && currentHash === recordedBinaryHash) {
|
|
26591
|
+
if (meta3.sha256 !== currentHash || meta3.binarySha256 !== currentHash) {
|
|
26592
|
+
writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.archiveSha256);
|
|
26593
|
+
}
|
|
26594
|
+
return true;
|
|
26595
|
+
}
|
|
26596
|
+
if (meta3.sha256 && !meta3.binarySha256 && !meta3.archiveSha256) {
|
|
26597
|
+
writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.sha256);
|
|
26598
|
+
return true;
|
|
26599
|
+
}
|
|
26600
|
+
if (!recordedBinaryHash) {
|
|
26601
|
+
quarantineCachedGithubInstall(spec, "missing binary sha256 metadata");
|
|
26602
|
+
return false;
|
|
26603
|
+
}
|
|
26604
|
+
if (currentHash !== recordedBinaryHash) {
|
|
26605
|
+
quarantineCachedGithubInstall(spec, `recorded ${recordedBinaryHash}, current ${currentHash}`);
|
|
26304
26606
|
return false;
|
|
26305
26607
|
}
|
|
26306
26608
|
return true;
|
|
@@ -26369,10 +26671,11 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
26369
26671
|
return null;
|
|
26370
26672
|
}
|
|
26371
26673
|
log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
|
|
26372
|
-
const previousMeta =
|
|
26373
|
-
|
|
26374
|
-
|
|
26375
|
-
|
|
26674
|
+
const previousMeta = readGithubInstalledMetaIn(ghPackageDir(spec));
|
|
26675
|
+
const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
|
|
26676
|
+
if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
|
|
26677
|
+
if (previousArchiveSha256 !== archiveSha256) {
|
|
26678
|
+
error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
|
|
26376
26679
|
try {
|
|
26377
26680
|
unlinkSync6(archivePath);
|
|
26378
26681
|
} catch {}
|
|
@@ -26407,7 +26710,9 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
26407
26710
|
return null;
|
|
26408
26711
|
}
|
|
26409
26712
|
log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
|
|
26410
|
-
|
|
26713
|
+
const binarySha256 = await sha256OfFile(targetBinary);
|
|
26714
|
+
log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
|
|
26715
|
+
return { archiveSha256, binarySha256 };
|
|
26411
26716
|
}
|
|
26412
26717
|
async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
|
|
26413
26718
|
const outcome = await withInstallLock(spec.githubRepo, async () => {
|
|
@@ -26433,14 +26738,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch,
|
|
|
26433
26738
|
log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
|
|
26434
26739
|
}
|
|
26435
26740
|
}
|
|
26436
|
-
const
|
|
26741
|
+
const hashes = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
|
|
26437
26742
|
error2(`[lsp] github install ${spec.id} crashed: ${err}`);
|
|
26438
26743
|
return null;
|
|
26439
26744
|
});
|
|
26440
|
-
if (!
|
|
26745
|
+
if (!hashes) {
|
|
26441
26746
|
return { started: true, reason: "install failed (see plugin log)" };
|
|
26442
26747
|
}
|
|
26443
|
-
|
|
26748
|
+
writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
|
|
26444
26749
|
return { started: true };
|
|
26445
26750
|
});
|
|
26446
26751
|
if (outcome === null) {
|
|
@@ -26635,7 +26940,7 @@ function normalizeToolMap(tools) {
|
|
|
26635
26940
|
}
|
|
26636
26941
|
|
|
26637
26942
|
// src/notifications.ts
|
|
26638
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as
|
|
26943
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
26639
26944
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
26640
26945
|
import { join as join16 } from "path";
|
|
26641
26946
|
function isTuiMode() {
|
|
@@ -26742,7 +27047,7 @@ async function sendIgnoredMessage(client, sessionId, text) {
|
|
|
26742
27047
|
return true;
|
|
26743
27048
|
}
|
|
26744
27049
|
} catch (err) {
|
|
26745
|
-
|
|
27050
|
+
sessionLog(sessionId, `[aft-plugin] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26746
27051
|
}
|
|
26747
27052
|
return false;
|
|
26748
27053
|
}
|
|
@@ -26768,7 +27073,7 @@ async function sendWarning(opts, message) {
|
|
|
26768
27073
|
if (!sessionId)
|
|
26769
27074
|
return;
|
|
26770
27075
|
const text = `${WARNING_MARKER} ${message}`;
|
|
26771
|
-
|
|
27076
|
+
sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
|
|
26772
27077
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
26773
27078
|
}
|
|
26774
27079
|
async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
@@ -26791,13 +27096,13 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
|
26791
27096
|
return;
|
|
26792
27097
|
const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
|
|
26793
27098
|
`);
|
|
26794
|
-
|
|
27099
|
+
sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
|
|
26795
27100
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
26796
27101
|
}
|
|
26797
27102
|
if (storageDir) {
|
|
26798
27103
|
try {
|
|
26799
27104
|
mkdirSync9(storageDir, { recursive: true });
|
|
26800
|
-
|
|
27105
|
+
writeFileSync9(join16(storageDir, "last_announced_version"), version2);
|
|
26801
27106
|
} catch {}
|
|
26802
27107
|
}
|
|
26803
27108
|
}
|
|
@@ -26825,9 +27130,9 @@ function writeWarnedTools(storageDir, warned) {
|
|
|
26825
27130
|
mkdirSync9(storageDir, { recursive: true });
|
|
26826
27131
|
const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
|
|
26827
27132
|
const tmpPath = join16(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
26828
|
-
|
|
27133
|
+
writeFileSync9(tmpPath, `${JSON.stringify(warned, null, 2)}
|
|
26829
27134
|
`);
|
|
26830
|
-
|
|
27135
|
+
renameSync7(tmpPath, warnedToolsPath);
|
|
26831
27136
|
} catch {}
|
|
26832
27137
|
}
|
|
26833
27138
|
async function withWarnedToolsLock(storageDir, fn) {
|
|
@@ -26839,7 +27144,7 @@ async function withWarnedToolsLock(storageDir, fn) {
|
|
|
26839
27144
|
try {
|
|
26840
27145
|
return await fn();
|
|
26841
27146
|
} finally {
|
|
26842
|
-
|
|
27147
|
+
rmSync6(lockDir, { recursive: true, force: true });
|
|
26843
27148
|
}
|
|
26844
27149
|
} catch (err) {
|
|
26845
27150
|
const code = err.code;
|
|
@@ -26940,7 +27245,7 @@ async function cleanupWarnings(opts) {
|
|
|
26940
27245
|
}
|
|
26941
27246
|
if (warningIds.length === 0)
|
|
26942
27247
|
return;
|
|
26943
|
-
|
|
27248
|
+
sessionLog(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
|
|
26944
27249
|
for (const id of warningIds) {
|
|
26945
27250
|
await deleteMessage(effectiveServerUrl, sessionId, id);
|
|
26946
27251
|
}
|
|
@@ -26948,16 +27253,16 @@ async function cleanupWarnings(opts) {
|
|
|
26948
27253
|
|
|
26949
27254
|
// src/shared/rpc-server.ts
|
|
26950
27255
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
26951
|
-
import { mkdirSync as mkdirSync10, renameSync as
|
|
27256
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
26952
27257
|
import { createServer } from "http";
|
|
26953
27258
|
import { dirname as dirname6 } from "path";
|
|
26954
27259
|
|
|
26955
27260
|
// src/shared/rpc-utils.ts
|
|
26956
|
-
import { createHash as
|
|
27261
|
+
import { createHash as createHash6 } from "crypto";
|
|
26957
27262
|
import { join as join17 } from "path";
|
|
26958
27263
|
function projectHash(directory) {
|
|
26959
27264
|
const normalized = directory.replace(/\/+$/, "");
|
|
26960
|
-
return
|
|
27265
|
+
return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
26961
27266
|
}
|
|
26962
27267
|
function rpcPortFilePath(storageDir, directory) {
|
|
26963
27268
|
const hash2 = projectHash(directory);
|
|
@@ -26997,11 +27302,11 @@ class AftRpcServer {
|
|
|
26997
27302
|
const dir = dirname6(this.portFilePath);
|
|
26998
27303
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
26999
27304
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
27000
|
-
|
|
27305
|
+
writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
27001
27306
|
encoding: "utf-8",
|
|
27002
27307
|
mode: 384
|
|
27003
27308
|
});
|
|
27004
|
-
|
|
27309
|
+
renameSync8(tmpPath, this.portFilePath);
|
|
27005
27310
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
27006
27311
|
} catch (err) {
|
|
27007
27312
|
warn2(`Failed to write RPC port file: ${err}`);
|
|
@@ -27104,7 +27409,7 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
27104
27409
|
dir = session.directory;
|
|
27105
27410
|
}
|
|
27106
27411
|
} catch (err) {
|
|
27107
|
-
|
|
27412
|
+
sessionWarn(sessionId, `[aft-plugin] session.get lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
27108
27413
|
return null;
|
|
27109
27414
|
}
|
|
27110
27415
|
setCache(sessionId, dir);
|
|
@@ -27190,6 +27495,8 @@ function coerceAftStatus(response) {
|
|
|
27190
27495
|
project_root: readNullableString(response.project_root),
|
|
27191
27496
|
canonical_root: readNullableString(response.canonical_root),
|
|
27192
27497
|
cache_role: readString(response.cache_role, "not_initialized"),
|
|
27498
|
+
degraded: readBoolean(response.degraded),
|
|
27499
|
+
degraded_reasons: Array.isArray(response.degraded_reasons) ? response.degraded_reasons.filter((r) => typeof r === "string") : [],
|
|
27193
27500
|
features: {
|
|
27194
27501
|
format_on_edit: readBoolean(features.format_on_edit),
|
|
27195
27502
|
validate_on_edit: readString(features.validate_on_edit, "off"),
|
|
@@ -27288,7 +27595,7 @@ function formatStatusMarkdown(status) {
|
|
|
27288
27595
|
|
|
27289
27596
|
// src/shared/tui-config.ts
|
|
27290
27597
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
27291
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as
|
|
27598
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
27292
27599
|
import { dirname as dirname7, join as join19 } from "path";
|
|
27293
27600
|
|
|
27294
27601
|
// src/shared/opencode-config-dir.ts
|
|
@@ -27345,7 +27652,7 @@ function ensureTuiPluginEntry() {
|
|
|
27345
27652
|
plugins.push(PLUGIN_ENTRY);
|
|
27346
27653
|
config2.plugin = plugins;
|
|
27347
27654
|
mkdirSync11(dirname7(configPath), { recursive: true });
|
|
27348
|
-
|
|
27655
|
+
writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
27349
27656
|
`);
|
|
27350
27657
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
27351
27658
|
return true;
|
|
@@ -28224,7 +28531,7 @@ var CACHE_MAX_ENTRIES2 = 200;
|
|
|
28224
28531
|
var cache2 = new Map;
|
|
28225
28532
|
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
28226
28533
|
if (!sessionId) {
|
|
28227
|
-
|
|
28534
|
+
sessionLog(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
|
|
28228
28535
|
return false;
|
|
28229
28536
|
}
|
|
28230
28537
|
const cached2 = cache2.get(sessionId);
|
|
@@ -28236,11 +28543,11 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
|
28236
28543
|
const c = client;
|
|
28237
28544
|
const sessionApi = c?.session;
|
|
28238
28545
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
28239
|
-
|
|
28546
|
+
sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
|
|
28240
28547
|
setCache2(sessionId, false);
|
|
28241
28548
|
return false;
|
|
28242
28549
|
}
|
|
28243
|
-
|
|
28550
|
+
sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
28244
28551
|
let isSubagent = false;
|
|
28245
28552
|
let parentIdRaw;
|
|
28246
28553
|
try {
|
|
@@ -28250,9 +28557,9 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
|
28250
28557
|
const session = result?.data ?? result;
|
|
28251
28558
|
parentIdRaw = session?.parentID;
|
|
28252
28559
|
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
28253
|
-
|
|
28560
|
+
sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
|
|
28254
28561
|
} catch (err) {
|
|
28255
|
-
|
|
28562
|
+
sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28256
28563
|
return false;
|
|
28257
28564
|
}
|
|
28258
28565
|
setCache2(sessionId, isSubagent);
|
|
@@ -28320,7 +28627,7 @@ function createBashTool(ctx) {
|
|
|
28320
28627
|
const requestedBackground = args.background === true;
|
|
28321
28628
|
const effectiveBackground = isSubagent ? false : requestedBackground;
|
|
28322
28629
|
if (isSubagent && requestedBackground) {
|
|
28323
|
-
|
|
28630
|
+
sessionLog(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
|
|
28324
28631
|
}
|
|
28325
28632
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
28326
28633
|
const data = await withPermissionLoop(ctx, context, {
|
|
@@ -30098,7 +30405,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
|
30098
30405
|
}
|
|
30099
30406
|
return { symbols: hints };
|
|
30100
30407
|
} catch (err) {
|
|
30101
|
-
|
|
30408
|
+
sessionWarn(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
|
|
30102
30409
|
return;
|
|
30103
30410
|
}
|
|
30104
30411
|
}
|
|
@@ -30887,31 +31194,37 @@ ${lines}
|
|
|
30887
31194
|
} catch (err) {
|
|
30888
31195
|
warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
30889
31196
|
}
|
|
30890
|
-
|
|
31197
|
+
const versionUpgradePromises = new Map;
|
|
30891
31198
|
const poolOptions = {
|
|
30892
31199
|
errorPrefix: "[aft-plugin]",
|
|
30893
31200
|
minVersion: PLUGIN_VERSION,
|
|
30894
31201
|
onVersionMismatch: async (binaryVersion, minVersion) => {
|
|
30895
|
-
|
|
30896
|
-
|
|
30897
|
-
|
|
31202
|
+
const existing = versionUpgradePromises.get(minVersion);
|
|
31203
|
+
if (existing) {
|
|
31204
|
+
log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
|
|
31205
|
+
return existing;
|
|
30898
31206
|
}
|
|
30899
|
-
|
|
30900
|
-
|
|
30901
|
-
|
|
30902
|
-
|
|
30903
|
-
|
|
30904
|
-
|
|
31207
|
+
const upgradePromise = (async () => {
|
|
31208
|
+
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
31209
|
+
try {
|
|
31210
|
+
const path7 = await ensureBinary(`v${minVersion}`);
|
|
31211
|
+
if (!path7) {
|
|
31212
|
+
warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
|
|
31213
|
+
return null;
|
|
31214
|
+
}
|
|
31215
|
+
log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
|
|
31216
|
+
const replaced = await pool.replaceBinary(path7);
|
|
31217
|
+
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
31218
|
+
return replaced;
|
|
31219
|
+
} catch (err) {
|
|
31220
|
+
error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
|
|
30905
31221
|
return null;
|
|
31222
|
+
} finally {
|
|
31223
|
+
versionUpgradePromises.delete(minVersion);
|
|
30906
31224
|
}
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
return replaced;
|
|
30911
|
-
} catch (err) {
|
|
30912
|
-
error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
|
|
30913
|
-
return null;
|
|
30914
|
-
}
|
|
31225
|
+
})();
|
|
31226
|
+
versionUpgradePromises.set(minVersion, upgradePromise);
|
|
31227
|
+
return upgradePromise;
|
|
30915
31228
|
},
|
|
30916
31229
|
onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
|
|
30917
31230
|
const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
|
|
@@ -31045,7 +31358,7 @@ ${lines}
|
|
|
31045
31358
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
31046
31359
|
try {
|
|
31047
31360
|
mkdirSync12(storageDir, { recursive: true });
|
|
31048
|
-
|
|
31361
|
+
writeFileSync12(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
31049
31362
|
} catch {}
|
|
31050
31363
|
}
|
|
31051
31364
|
return { success: true };
|