@cortexkit/aft-opencode 0.25.2 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +715 -334
- package/dist/lsp-github-install.d.ts +2 -2
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/shared/session-directory.d.ts +4 -3
- package/dist/shared/session-directory.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tui.js +7 -7
- package/package.json +7 -7
- package/src/shared/session-directory.ts +12 -6
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";
|
|
@@ -7931,6 +7942,15 @@ function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
|
|
|
7931
7942
|
};
|
|
7932
7943
|
}
|
|
7933
7944
|
|
|
7945
|
+
class BridgeReplacedDuringVersionCheck extends Error {
|
|
7946
|
+
newBinaryPath;
|
|
7947
|
+
constructor(newBinaryPath) {
|
|
7948
|
+
super(`Bridge binary replaced during version check: ${newBinaryPath}`);
|
|
7949
|
+
this.newBinaryPath = newBinaryPath;
|
|
7950
|
+
this.name = "BridgeReplacedDuringVersionCheck";
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
|
|
7934
7954
|
class BinaryBridge {
|
|
7935
7955
|
static RESTART_RESET_MS = 5 * 60 * 1000;
|
|
7936
7956
|
static STDERR_TAIL_MAX = 20;
|
|
@@ -7940,6 +7960,7 @@ class BinaryBridge {
|
|
|
7940
7960
|
pending = new Map;
|
|
7941
7961
|
nextId = 1;
|
|
7942
7962
|
stdoutBuffer = "";
|
|
7963
|
+
stderrBuffer = "";
|
|
7943
7964
|
stderrTail = [];
|
|
7944
7965
|
_restartCount = 0;
|
|
7945
7966
|
_shuttingDown = false;
|
|
@@ -7975,24 +7996,62 @@ class BinaryBridge {
|
|
|
7975
7996
|
}
|
|
7976
7997
|
logVia(message, meta) {
|
|
7977
7998
|
const logger = this.logger ?? getActiveLogger();
|
|
7978
|
-
if (logger)
|
|
7979
|
-
|
|
7980
|
-
|
|
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 {
|
|
7981
8007
|
log(message, meta);
|
|
8008
|
+
}
|
|
7982
8009
|
}
|
|
7983
8010
|
warnVia(message, meta) {
|
|
7984
8011
|
const logger = this.logger ?? getActiveLogger();
|
|
7985
|
-
if (logger)
|
|
7986
|
-
|
|
7987
|
-
|
|
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 {
|
|
7988
8020
|
warn(message, meta);
|
|
8021
|
+
}
|
|
7989
8022
|
}
|
|
7990
8023
|
errorVia(message, meta) {
|
|
7991
8024
|
const logger = this.logger ?? getActiveLogger();
|
|
7992
|
-
if (logger)
|
|
7993
|
-
|
|
7994
|
-
|
|
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 {
|
|
7995
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);
|
|
7996
8055
|
}
|
|
7997
8056
|
get restartCount() {
|
|
7998
8057
|
return this._restartCount;
|
|
@@ -8019,97 +8078,108 @@ class BinaryBridge {
|
|
|
8019
8078
|
this.cachedStatus = snapshot;
|
|
8020
8079
|
}
|
|
8021
8080
|
async send(command, params = {}, options) {
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
if (
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8081
|
+
return this.sendWithVersionMismatchRetry(command, params, options, true);
|
|
8082
|
+
}
|
|
8083
|
+
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
8084
|
+
try {
|
|
8085
|
+
if (this._shuttingDown) {
|
|
8086
|
+
throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
|
|
8087
|
+
}
|
|
8088
|
+
if (Object.hasOwn(params, "id")) {
|
|
8089
|
+
throw new Error("params cannot contain reserved key 'id'");
|
|
8090
|
+
}
|
|
8091
|
+
const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
|
|
8092
|
+
this.ensureSpawned(requestSessionId);
|
|
8093
|
+
if (requestSessionId && options?.configureWarningClient !== undefined) {
|
|
8094
|
+
this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
|
|
8095
|
+
}
|
|
8096
|
+
if (!this.configured) {
|
|
8097
|
+
if (command !== "configure" && command !== "version") {
|
|
8098
|
+
if (!this._configurePromise) {
|
|
8099
|
+
const sessionIdForConfigure = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
8100
|
+
this._configurePromise = (async () => {
|
|
8101
|
+
try {
|
|
8102
|
+
const configResult = await this.send("configure", {
|
|
8103
|
+
project_root: this.cwd,
|
|
8104
|
+
...this.configOverrides,
|
|
8105
|
+
...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
|
|
8106
|
+
});
|
|
8107
|
+
if (configResult.success === false) {
|
|
8108
|
+
throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
|
|
8109
|
+
}
|
|
8110
|
+
await this.deliverConfigureWarnings(configResult, params, options);
|
|
8111
|
+
await this.checkVersion();
|
|
8112
|
+
if (!this.isAlive()) {
|
|
8113
|
+
throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
|
|
8114
|
+
}
|
|
8115
|
+
this.configured = true;
|
|
8116
|
+
} finally {
|
|
8117
|
+
this._configurePromise = null;
|
|
8051
8118
|
}
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
if (Object.hasOwn(nested, key)) {
|
|
8068
|
-
reserved[key] = nested[key];
|
|
8069
|
-
delete nested[key];
|
|
8119
|
+
})();
|
|
8120
|
+
}
|
|
8121
|
+
await this._configurePromise;
|
|
8122
|
+
}
|
|
8123
|
+
}
|
|
8124
|
+
const id = String(this.nextId++);
|
|
8125
|
+
let request;
|
|
8126
|
+
if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
8127
|
+
const nested = { ...params };
|
|
8128
|
+
const reserved = {};
|
|
8129
|
+
for (const key of ["session_id", "lsp_hints"]) {
|
|
8130
|
+
if (Object.hasOwn(nested, key)) {
|
|
8131
|
+
reserved[key] = nested[key];
|
|
8132
|
+
delete nested[key];
|
|
8133
|
+
}
|
|
8070
8134
|
}
|
|
8135
|
+
request = { id, command, ...reserved, params: nested };
|
|
8136
|
+
} else {
|
|
8137
|
+
request = { id, command, ...params };
|
|
8071
8138
|
}
|
|
8072
|
-
|
|
8073
|
-
} else {
|
|
8074
|
-
request = { id, command, ...params };
|
|
8075
|
-
}
|
|
8076
|
-
const line = `${JSON.stringify(request)}
|
|
8139
|
+
const line = `${JSON.stringify(request)}
|
|
8077
8140
|
`;
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
}
|
|
8090
|
-
reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
8091
|
-
if (!keepBridgeOnTimeout) {
|
|
8092
|
-
this.handleTimeout(requestSessionId);
|
|
8093
|
-
}
|
|
8094
|
-
}, effectiveTimeoutMs);
|
|
8095
|
-
this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
|
|
8096
|
-
if (!this.process?.stdin?.writable) {
|
|
8097
|
-
this.pending.delete(id);
|
|
8098
|
-
clearTimeout(timer);
|
|
8099
|
-
reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
|
|
8100
|
-
return;
|
|
8101
|
-
}
|
|
8102
|
-
this.process.stdin.write(line, (err) => {
|
|
8103
|
-
if (err) {
|
|
8104
|
-
const entry = this.pending.get(id);
|
|
8105
|
-
if (entry) {
|
|
8106
|
-
this.pending.delete(id);
|
|
8107
|
-
clearTimeout(entry.timer);
|
|
8108
|
-
entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
|
|
8141
|
+
const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
|
|
8142
|
+
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
8143
|
+
return new Promise((resolve, reject) => {
|
|
8144
|
+
const timer = setTimeout(() => {
|
|
8145
|
+
this.pending.delete(id);
|
|
8146
|
+
const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
|
|
8147
|
+
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
8148
|
+
if (requestSessionId) {
|
|
8149
|
+
this.sessionWarnVia(requestSessionId, timeoutMsg);
|
|
8150
|
+
} else {
|
|
8151
|
+
this.warnVia(timeoutMsg);
|
|
8109
8152
|
}
|
|
8153
|
+
reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
8154
|
+
if (!keepBridgeOnTimeout) {
|
|
8155
|
+
this.handleTimeout(requestSessionId);
|
|
8156
|
+
}
|
|
8157
|
+
}, effectiveTimeoutMs);
|
|
8158
|
+
this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
|
|
8159
|
+
if (!this.process?.stdin?.writable) {
|
|
8160
|
+
this.pending.delete(id);
|
|
8161
|
+
clearTimeout(timer);
|
|
8162
|
+
reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
|
|
8163
|
+
return;
|
|
8110
8164
|
}
|
|
8165
|
+
this.process.stdin.write(line, (err) => {
|
|
8166
|
+
if (err) {
|
|
8167
|
+
const entry = this.pending.get(id);
|
|
8168
|
+
if (entry) {
|
|
8169
|
+
this.pending.delete(id);
|
|
8170
|
+
clearTimeout(entry.timer);
|
|
8171
|
+
entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
|
|
8172
|
+
}
|
|
8173
|
+
}
|
|
8174
|
+
});
|
|
8111
8175
|
});
|
|
8112
|
-
})
|
|
8176
|
+
} catch (err) {
|
|
8177
|
+
if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
|
|
8178
|
+
this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
|
|
8179
|
+
return this.sendWithVersionMismatchRetry(command, params, options, false);
|
|
8180
|
+
}
|
|
8181
|
+
throw err;
|
|
8182
|
+
}
|
|
8113
8183
|
}
|
|
8114
8184
|
async deliverConfigureWarnings(configResult, params, options) {
|
|
8115
8185
|
if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
|
|
@@ -8125,7 +8195,7 @@ class BinaryBridge {
|
|
|
8125
8195
|
warnings: configResult.warnings
|
|
8126
8196
|
});
|
|
8127
8197
|
} catch (err) {
|
|
8128
|
-
|
|
8198
|
+
this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8129
8199
|
} finally {
|
|
8130
8200
|
if (sessionId) {
|
|
8131
8201
|
this.configureWarningClients.delete(sessionId);
|
|
@@ -8159,7 +8229,7 @@ class BinaryBridge {
|
|
|
8159
8229
|
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
8160
8230
|
return;
|
|
8161
8231
|
this.cachedStatus = snapshot;
|
|
8162
|
-
|
|
8232
|
+
this.logVia("Received status_changed push frame; cached AFT status snapshot");
|
|
8163
8233
|
for (const listener of this.statusListeners) {
|
|
8164
8234
|
this.deliverStatusSnapshot(listener, this.cachedStatus);
|
|
8165
8235
|
}
|
|
@@ -8168,7 +8238,7 @@ class BinaryBridge {
|
|
|
8168
8238
|
try {
|
|
8169
8239
|
listener(snapshot);
|
|
8170
8240
|
} catch (err) {
|
|
8171
|
-
|
|
8241
|
+
this.warnVia(`status listener threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
8172
8242
|
}
|
|
8173
8243
|
}
|
|
8174
8244
|
async shutdown() {
|
|
@@ -8186,7 +8256,7 @@ class BinaryBridge {
|
|
|
8186
8256
|
}, 5000);
|
|
8187
8257
|
proc.once("exit", () => {
|
|
8188
8258
|
clearTimeout(forceKillTimer);
|
|
8189
|
-
|
|
8259
|
+
this.logVia("Process exited during shutdown");
|
|
8190
8260
|
resolve();
|
|
8191
8261
|
});
|
|
8192
8262
|
proc.kill("SIGTERM");
|
|
@@ -8205,16 +8275,46 @@ class BinaryBridge {
|
|
|
8205
8275
|
if (typeof binaryVersion !== "string") {
|
|
8206
8276
|
throw new Error(`Binary did not report a version \u2014 likely too old (minVersion: ${this.minVersion})`);
|
|
8207
8277
|
}
|
|
8208
|
-
|
|
8278
|
+
this.logVia(`Binary version: ${binaryVersion}`);
|
|
8209
8279
|
if (compareSemver(binaryVersion, this.minVersion) < 0) {
|
|
8210
|
-
|
|
8211
|
-
this.onVersionMismatch?.(binaryVersion, this.minVersion);
|
|
8280
|
+
this.warnVia(`Binary version ${binaryVersion} is older than required ${this.minVersion}`);
|
|
8281
|
+
const replacementPath = await this.onVersionMismatch?.(binaryVersion, this.minVersion);
|
|
8282
|
+
if (replacementPath === undefined) {
|
|
8283
|
+
return;
|
|
8284
|
+
}
|
|
8285
|
+
if (replacementPath === null || replacementPath.length === 0) {
|
|
8286
|
+
throw new Error(`Binary version ${binaryVersion} is older than required ${this.minVersion}; no compatible replacement binary was provided`);
|
|
8287
|
+
}
|
|
8288
|
+
await this.replaceCurrentBinary(replacementPath);
|
|
8289
|
+
throw new BridgeReplacedDuringVersionCheck(replacementPath);
|
|
8212
8290
|
}
|
|
8213
8291
|
} catch (err) {
|
|
8214
|
-
|
|
8292
|
+
this.warnVia(`Version check failed: ${err.message}`);
|
|
8215
8293
|
throw err;
|
|
8216
8294
|
}
|
|
8217
8295
|
}
|
|
8296
|
+
async replaceCurrentBinary(newBinaryPath) {
|
|
8297
|
+
this.binaryPath = newBinaryPath;
|
|
8298
|
+
this.configured = false;
|
|
8299
|
+
this.clearRestartResetTimer();
|
|
8300
|
+
this.rejectAllPending(new Error(`${this.errorPrefix} Bridge restarting with updated binary: ${newBinaryPath}`));
|
|
8301
|
+
if (!this.process)
|
|
8302
|
+
return;
|
|
8303
|
+
const proc = this.process;
|
|
8304
|
+
this.process = null;
|
|
8305
|
+
await new Promise((resolve) => {
|
|
8306
|
+
const forceKillTimer = setTimeout(() => {
|
|
8307
|
+
proc.kill("SIGKILL");
|
|
8308
|
+
resolve();
|
|
8309
|
+
}, 5000);
|
|
8310
|
+
proc.once("exit", () => {
|
|
8311
|
+
clearTimeout(forceKillTimer);
|
|
8312
|
+
this.logVia("Process exited during coordinated binary replacement");
|
|
8313
|
+
resolve();
|
|
8314
|
+
});
|
|
8315
|
+
proc.kill("SIGTERM");
|
|
8316
|
+
});
|
|
8317
|
+
}
|
|
8218
8318
|
ensureSpawned(triggeringSessionId) {
|
|
8219
8319
|
if (this.isAlive())
|
|
8220
8320
|
return;
|
|
@@ -8222,9 +8322,9 @@ class BinaryBridge {
|
|
|
8222
8322
|
}
|
|
8223
8323
|
spawnProcess(triggeringSessionId) {
|
|
8224
8324
|
if (triggeringSessionId) {
|
|
8225
|
-
|
|
8325
|
+
this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
8226
8326
|
} else {
|
|
8227
|
-
|
|
8327
|
+
this.logVia(`Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
8228
8328
|
}
|
|
8229
8329
|
const semantic = this.configOverrides.semantic;
|
|
8230
8330
|
const semanticBackend = (() => {
|
|
@@ -8271,11 +8371,12 @@ class BinaryBridge {
|
|
|
8271
8371
|
const remaining = stderrDecoder.end();
|
|
8272
8372
|
if (remaining)
|
|
8273
8373
|
this.onStderrData(remaining);
|
|
8374
|
+
this.flushStderrBuffer();
|
|
8274
8375
|
});
|
|
8275
8376
|
child.on("error", (err) => {
|
|
8276
8377
|
if (this.process !== currentChild)
|
|
8277
8378
|
return;
|
|
8278
|
-
|
|
8379
|
+
this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
|
|
8279
8380
|
this.handleCrash();
|
|
8280
8381
|
});
|
|
8281
8382
|
child.on("exit", (code, signal) => {
|
|
@@ -8283,7 +8384,7 @@ class BinaryBridge {
|
|
|
8283
8384
|
return;
|
|
8284
8385
|
if (this._shuttingDown)
|
|
8285
8386
|
return;
|
|
8286
|
-
|
|
8387
|
+
this.logVia(`Process exited: code=${code}, signal=${signal}`);
|
|
8287
8388
|
if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
|
|
8288
8389
|
this.process = null;
|
|
8289
8390
|
this.configured = false;
|
|
@@ -8295,6 +8396,7 @@ class BinaryBridge {
|
|
|
8295
8396
|
});
|
|
8296
8397
|
this.process = child;
|
|
8297
8398
|
this.stdoutBuffer = "";
|
|
8399
|
+
this.stderrBuffer = "";
|
|
8298
8400
|
this.stderrTail = [];
|
|
8299
8401
|
}
|
|
8300
8402
|
pushStderrLine(line) {
|
|
@@ -8304,16 +8406,28 @@ class BinaryBridge {
|
|
|
8304
8406
|
}
|
|
8305
8407
|
}
|
|
8306
8408
|
onStderrData(data) {
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
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);
|
|
8310
8415
|
if (!line)
|
|
8311
8416
|
continue;
|
|
8312
8417
|
const tagged = tagStderrLine(line);
|
|
8313
|
-
|
|
8418
|
+
this.logVia(tagged);
|
|
8314
8419
|
this.pushStderrLine(tagged);
|
|
8315
8420
|
}
|
|
8316
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
|
+
}
|
|
8317
8431
|
formatStderrTail() {
|
|
8318
8432
|
if (this.stderrTail.length === 0)
|
|
8319
8433
|
return "";
|
|
@@ -8371,7 +8485,7 @@ class BinaryBridge {
|
|
|
8371
8485
|
}
|
|
8372
8486
|
if (response.type === "configure_warnings") {
|
|
8373
8487
|
this.handleConfigureWarningsFrame(response).catch((err) => {
|
|
8374
|
-
|
|
8488
|
+
this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8375
8489
|
});
|
|
8376
8490
|
continue;
|
|
8377
8491
|
}
|
|
@@ -8389,10 +8503,10 @@ class BinaryBridge {
|
|
|
8389
8503
|
this.scheduleRestartCountReset();
|
|
8390
8504
|
entry.resolve(response);
|
|
8391
8505
|
} else if (typeof response.type === "string") {
|
|
8392
|
-
|
|
8506
|
+
this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
|
|
8393
8507
|
}
|
|
8394
8508
|
} catch (_err) {
|
|
8395
|
-
|
|
8509
|
+
this.warnVia(`Failed to parse stdout line: ${line}`);
|
|
8396
8510
|
}
|
|
8397
8511
|
}
|
|
8398
8512
|
}
|
|
@@ -8406,17 +8520,17 @@ class BinaryBridge {
|
|
|
8406
8520
|
this.configured = false;
|
|
8407
8521
|
const tail = this.formatStderrTail();
|
|
8408
8522
|
this.stderrTail = [];
|
|
8409
|
-
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()})`;
|
|
8410
8524
|
if (tail) {
|
|
8411
8525
|
if (triggeringSessionId) {
|
|
8412
|
-
|
|
8526
|
+
this.sessionErrorVia(triggeringSessionId, killedMsg);
|
|
8413
8527
|
} else {
|
|
8414
|
-
|
|
8528
|
+
this.errorVia(killedMsg);
|
|
8415
8529
|
}
|
|
8416
8530
|
} else if (triggeringSessionId) {
|
|
8417
|
-
|
|
8531
|
+
this.sessionWarnVia(triggeringSessionId, killedMsg);
|
|
8418
8532
|
} else {
|
|
8419
|
-
|
|
8533
|
+
this.warnVia(killedMsg);
|
|
8420
8534
|
}
|
|
8421
8535
|
}
|
|
8422
8536
|
handleCrash(cause) {
|
|
@@ -8429,25 +8543,25 @@ class BinaryBridge {
|
|
|
8429
8543
|
this.configured = false;
|
|
8430
8544
|
const tail = this.formatStderrTail();
|
|
8431
8545
|
if (tail) {
|
|
8432
|
-
|
|
8546
|
+
this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
|
|
8433
8547
|
}
|
|
8434
|
-
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()})`));
|
|
8435
8549
|
if (this._restartCount < this.maxRestarts) {
|
|
8436
8550
|
const delay = 100 * 2 ** this._restartCount;
|
|
8437
8551
|
this._restartCount++;
|
|
8438
|
-
|
|
8552
|
+
this.logVia(`Auto-restart #${this._restartCount} in ${delay}ms`);
|
|
8439
8553
|
setTimeout(() => {
|
|
8440
8554
|
if (!this._shuttingDown && !this.isAlive()) {
|
|
8441
8555
|
try {
|
|
8442
8556
|
this.spawnProcess();
|
|
8443
8557
|
} catch (err) {
|
|
8444
|
-
|
|
8558
|
+
this.errorVia(`Failed to restart: ${err.message}`);
|
|
8445
8559
|
}
|
|
8446
8560
|
}
|
|
8447
8561
|
}, delay);
|
|
8448
8562
|
this.scheduleRestartCountReset();
|
|
8449
8563
|
} else {
|
|
8450
|
-
|
|
8564
|
+
this.errorVia(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${this.getLogFilePathVia()}${tail}`);
|
|
8451
8565
|
this.scheduleRestartCountReset();
|
|
8452
8566
|
}
|
|
8453
8567
|
}
|
|
@@ -8473,9 +8587,12 @@ class BinaryBridge {
|
|
|
8473
8587
|
}
|
|
8474
8588
|
}
|
|
8475
8589
|
// ../aft-bridge/dist/downloader.js
|
|
8476
|
-
import {
|
|
8590
|
+
import { createHash } from "crypto";
|
|
8591
|
+
import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, unlinkSync } from "fs";
|
|
8477
8592
|
import { homedir as homedir2 } from "os";
|
|
8478
8593
|
import { join as join2 } from "path";
|
|
8594
|
+
import { Readable } from "stream";
|
|
8595
|
+
import { pipeline } from "stream/promises";
|
|
8479
8596
|
|
|
8480
8597
|
// ../aft-bridge/dist/platform.js
|
|
8481
8598
|
var PLATFORM_ARCH_MAP = {
|
|
@@ -8493,6 +8610,11 @@ var PLATFORM_ASSET_MAP = {
|
|
|
8493
8610
|
|
|
8494
8611
|
// ../aft-bridge/dist/downloader.js
|
|
8495
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;
|
|
8496
8618
|
function getCacheDir() {
|
|
8497
8619
|
if (process.platform === "win32") {
|
|
8498
8620
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
@@ -8534,54 +8656,101 @@ async function downloadBinary(version) {
|
|
|
8534
8656
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
|
|
8535
8657
|
const checksumUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.sha256`;
|
|
8536
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`;
|
|
8537
8666
|
try {
|
|
8538
8667
|
if (!existsSync(versionedCacheDir)) {
|
|
8539
8668
|
mkdirSync(versionedCacheDir, { recursive: true });
|
|
8540
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);
|
|
8541
8680
|
const [binaryResponse, checksumResponse] = await Promise.all([
|
|
8542
|
-
fetch(downloadUrl, { redirect: "follow" }),
|
|
8543
|
-
fetch(checksumUrl, { redirect: "follow" })
|
|
8681
|
+
fetch(downloadUrl, { redirect: "follow", signal: activeBinaryController.signal }),
|
|
8682
|
+
fetch(checksumUrl, { redirect: "follow", signal: activeChecksumController.signal })
|
|
8544
8683
|
]);
|
|
8545
8684
|
if (!binaryResponse.ok) {
|
|
8546
8685
|
throw new Error(`HTTP ${binaryResponse.status}: ${binaryResponse.statusText} (${downloadUrl})`);
|
|
8547
8686
|
}
|
|
8548
|
-
|
|
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
|
+
}
|
|
8549
8694
|
if (!checksumResponse.ok) {
|
|
8550
8695
|
warn(`Checksum verification failed: no checksums.sha256 found for ${tag}. ` + "Binary download aborted for security reasons.");
|
|
8551
8696
|
return null;
|
|
8552
8697
|
}
|
|
8553
8698
|
const checksumText = await checksumResponse.text();
|
|
8699
|
+
clearTimeout(checksumTimeout);
|
|
8700
|
+
checksumTimeout = null;
|
|
8554
8701
|
const expectedHash = parseChecksumForAsset(checksumText, assetName);
|
|
8555
8702
|
if (!expectedHash) {
|
|
8556
8703
|
warn(`Checksum verification failed: checksums.sha256 found but no entry for ${assetName}. ` + "Binary download aborted for security reasons.");
|
|
8557
8704
|
return null;
|
|
8558
8705
|
}
|
|
8559
|
-
const
|
|
8560
|
-
|
|
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");
|
|
8561
8725
|
if (actualHash !== expectedHash) {
|
|
8562
|
-
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.");
|
|
8563
8727
|
}
|
|
8564
8728
|
log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
|
|
8565
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
8566
|
-
const { writeFileSync } = await import("fs");
|
|
8567
|
-
writeFileSync(tmpPath, Buffer.from(arrayBuffer));
|
|
8568
8729
|
if (process.platform !== "win32") {
|
|
8569
8730
|
chmodSync(tmpPath, 493);
|
|
8570
8731
|
}
|
|
8571
|
-
const { renameSync } = await import("fs");
|
|
8572
8732
|
renameSync(tmpPath, binaryPath);
|
|
8573
8733
|
log(`AFT binary ready at ${binaryPath}`);
|
|
8574
8734
|
return binaryPath;
|
|
8575
8735
|
} catch (err) {
|
|
8576
8736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8577
8737
|
error(`Failed to download AFT binary: ${msg}`);
|
|
8578
|
-
const tmpPath = `${binaryPath}.tmp`;
|
|
8579
8738
|
if (existsSync(tmpPath)) {
|
|
8580
8739
|
try {
|
|
8581
8740
|
unlinkSync(tmpPath);
|
|
8582
8741
|
} catch {}
|
|
8583
8742
|
}
|
|
8584
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?.();
|
|
8585
8754
|
}
|
|
8586
8755
|
}
|
|
8587
8756
|
async function ensureBinary(version) {
|
|
@@ -8598,6 +8767,39 @@ async function ensureBinary(version) {
|
|
|
8598
8767
|
log("No cached binary found, downloading latest...");
|
|
8599
8768
|
return downloadBinary();
|
|
8600
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
|
+
}
|
|
8601
8803
|
function parseChecksumForAsset(checksumText, assetName) {
|
|
8602
8804
|
for (const line of checksumText.split(`
|
|
8603
8805
|
`)) {
|
|
@@ -8612,9 +8814,12 @@ function parseChecksumForAsset(checksumText, assetName) {
|
|
|
8612
8814
|
return null;
|
|
8613
8815
|
}
|
|
8614
8816
|
async function fetchLatestTag() {
|
|
8817
|
+
const controller = new AbortController;
|
|
8818
|
+
const timeout = setTimeout(() => controller.abort(), LATEST_TAG_TIMEOUT_MS);
|
|
8615
8819
|
try {
|
|
8616
8820
|
const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
|
8617
|
-
headers: { Accept: "application/vnd.github.v3+json" }
|
|
8821
|
+
headers: { Accept: "application/vnd.github.v3+json" },
|
|
8822
|
+
signal: controller.signal
|
|
8618
8823
|
});
|
|
8619
8824
|
if (!response.ok)
|
|
8620
8825
|
return null;
|
|
@@ -8622,18 +8827,20 @@ async function fetchLatestTag() {
|
|
|
8622
8827
|
return data.tag_name ?? null;
|
|
8623
8828
|
} catch {
|
|
8624
8829
|
return null;
|
|
8830
|
+
} finally {
|
|
8831
|
+
clearTimeout(timeout);
|
|
8625
8832
|
}
|
|
8626
8833
|
}
|
|
8627
8834
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
8628
8835
|
import { execFileSync } from "child_process";
|
|
8629
|
-
import { createHash } from "crypto";
|
|
8630
|
-
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";
|
|
8631
8838
|
import { dirname, join as join3, relative, resolve } from "path";
|
|
8632
|
-
import { Readable } from "stream";
|
|
8633
|
-
import { pipeline } from "stream/promises";
|
|
8839
|
+
import { Readable as Readable2 } from "stream";
|
|
8840
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
8634
8841
|
var ORT_VERSION = "1.24.4";
|
|
8635
8842
|
var ORT_REPO = "microsoft/onnxruntime";
|
|
8636
|
-
var
|
|
8843
|
+
var MAX_DOWNLOAD_BYTES2 = 256 * 1024 * 1024;
|
|
8637
8844
|
var MAX_EXTRACT_BYTES = 1 * 1024 * 1024 * 1024;
|
|
8638
8845
|
var ONNX_LOCK_FILE = ".aft-onnx-installing";
|
|
8639
8846
|
var ONNX_INSTALLED_META_FILE = ".aft-onnx-installed";
|
|
@@ -8754,7 +8961,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
8754
8961
|
if (Number.isFinite(pid) && pid > 0) {
|
|
8755
8962
|
if (process.platform === "win32") {
|
|
8756
8963
|
try {
|
|
8757
|
-
const ageMs = Date.now() -
|
|
8964
|
+
const ageMs = Date.now() - statSync2(stagingDir).mtimeMs;
|
|
8758
8965
|
abandoned = ageMs > STALE_LOCK_MS;
|
|
8759
8966
|
} catch {
|
|
8760
8967
|
abandoned = true;
|
|
@@ -8768,7 +8975,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
8768
8975
|
if (abandoned) {
|
|
8769
8976
|
log(`[onnx] removing abandoned staging dir ${stagingDir}`);
|
|
8770
8977
|
try {
|
|
8771
|
-
|
|
8978
|
+
rmSync2(stagingDir, { recursive: true, force: true });
|
|
8772
8979
|
} catch (err) {
|
|
8773
8980
|
warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
|
|
8774
8981
|
}
|
|
@@ -8780,7 +8987,7 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
|
8780
8987
|
try {
|
|
8781
8988
|
if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
|
|
8782
8989
|
log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
|
|
8783
|
-
|
|
8990
|
+
rmSync2(ortDir, { recursive: true, force: true });
|
|
8784
8991
|
}
|
|
8785
8992
|
} catch (err) {
|
|
8786
8993
|
warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
|
|
@@ -8860,24 +9067,24 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
8860
9067
|
throw new Error(`download failed (HTTP ${res.status})`);
|
|
8861
9068
|
}
|
|
8862
9069
|
const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
8863
|
-
if (Number.isFinite(advertised) && advertised >
|
|
8864
|
-
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}`);
|
|
8865
9072
|
}
|
|
8866
9073
|
mkdirSync2(dirname(destPath), { recursive: true });
|
|
8867
9074
|
let bytesWritten = 0;
|
|
8868
9075
|
const guard = new TransformStream({
|
|
8869
9076
|
transform(chunk, transformController) {
|
|
8870
9077
|
bytesWritten += chunk.byteLength;
|
|
8871
|
-
if (bytesWritten >
|
|
8872
|
-
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)`));
|
|
8873
9080
|
return;
|
|
8874
9081
|
}
|
|
8875
9082
|
transformController.enqueue(chunk);
|
|
8876
9083
|
}
|
|
8877
9084
|
});
|
|
8878
9085
|
const guarded = res.body.pipeThrough(guard);
|
|
8879
|
-
const nodeStream =
|
|
8880
|
-
await
|
|
9086
|
+
const nodeStream = Readable2.fromWeb(guarded);
|
|
9087
|
+
await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
|
|
8881
9088
|
} catch (err) {
|
|
8882
9089
|
try {
|
|
8883
9090
|
unlinkSync2(destPath);
|
|
@@ -8976,16 +9183,16 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
8976
9183
|
warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
|
|
8977
9184
|
}
|
|
8978
9185
|
writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
|
|
8979
|
-
|
|
9186
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
8980
9187
|
log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
|
|
8981
9188
|
return targetDir;
|
|
8982
9189
|
} catch (err) {
|
|
8983
9190
|
error(`Failed to download ONNX Runtime: ${err}`);
|
|
8984
9191
|
try {
|
|
8985
|
-
|
|
9192
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
8986
9193
|
} catch {}
|
|
8987
9194
|
try {
|
|
8988
|
-
|
|
9195
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
8989
9196
|
} catch {}
|
|
8990
9197
|
return null;
|
|
8991
9198
|
}
|
|
@@ -9002,7 +9209,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9002
9209
|
}
|
|
9003
9210
|
} catch (copyErr) {
|
|
9004
9211
|
if (requiredLibs.has(libFile)) {
|
|
9005
|
-
|
|
9212
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9006
9213
|
throw copyErr;
|
|
9007
9214
|
}
|
|
9008
9215
|
log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
|
|
@@ -9017,7 +9224,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9017
9224
|
symlinkSync(link.target, dst);
|
|
9018
9225
|
} catch (symlinkErr) {
|
|
9019
9226
|
if (requiredLibs.has(link.name)) {
|
|
9020
|
-
|
|
9227
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9021
9228
|
throw symlinkErr;
|
|
9022
9229
|
}
|
|
9023
9230
|
log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
|
|
@@ -9025,7 +9232,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
9025
9232
|
}
|
|
9026
9233
|
const requiredPath = join3(targetDir, info.libName);
|
|
9027
9234
|
if (!existsSync2(requiredPath)) {
|
|
9028
|
-
|
|
9235
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
9029
9236
|
throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
|
|
9030
9237
|
}
|
|
9031
9238
|
}
|
|
@@ -9058,7 +9265,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
9058
9265
|
function readOnnxInstalledMeta(installDir) {
|
|
9059
9266
|
const path = join3(installDir, ONNX_INSTALLED_META_FILE);
|
|
9060
9267
|
try {
|
|
9061
|
-
if (!
|
|
9268
|
+
if (!statSync2(path).isFile())
|
|
9062
9269
|
return null;
|
|
9063
9270
|
const raw = readFileSync(path, "utf8");
|
|
9064
9271
|
const parsed = JSON.parse(raw);
|
|
@@ -9075,20 +9282,20 @@ function readOnnxInstalledMeta(installDir) {
|
|
|
9075
9282
|
}
|
|
9076
9283
|
}
|
|
9077
9284
|
function sha256File(path) {
|
|
9078
|
-
const hash =
|
|
9285
|
+
const hash = createHash2("sha256");
|
|
9079
9286
|
hash.update(readFileSync(path));
|
|
9080
9287
|
return hash.digest("hex");
|
|
9081
9288
|
}
|
|
9082
9289
|
function acquireLock(lockPath) {
|
|
9083
9290
|
const tryClaim = () => {
|
|
9084
9291
|
try {
|
|
9085
|
-
const fd =
|
|
9292
|
+
const fd = openSync2(lockPath, "wx");
|
|
9086
9293
|
try {
|
|
9087
9294
|
writeFileSync(fd, `${process.pid}
|
|
9088
9295
|
${new Date().toISOString()}
|
|
9089
9296
|
`);
|
|
9090
9297
|
} finally {
|
|
9091
|
-
|
|
9298
|
+
closeSync2(fd);
|
|
9092
9299
|
}
|
|
9093
9300
|
return true;
|
|
9094
9301
|
} catch (err) {
|
|
@@ -9109,7 +9316,7 @@ ${new Date().toISOString()}
|
|
|
9109
9316
|
const parsed = Number.parseInt(firstLine, 10);
|
|
9110
9317
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
9111
9318
|
owningPid = parsed;
|
|
9112
|
-
lockMtimeMs =
|
|
9319
|
+
lockMtimeMs = statSync2(lockPath).mtimeMs;
|
|
9113
9320
|
} catch {
|
|
9114
9321
|
return tryClaim();
|
|
9115
9322
|
}
|
|
@@ -9226,7 +9433,9 @@ class BridgePool {
|
|
|
9226
9433
|
onVersionMismatch: options.onVersionMismatch,
|
|
9227
9434
|
onConfigureWarnings: options.onConfigureWarnings,
|
|
9228
9435
|
onBashCompletion: options.onBashCompletion,
|
|
9229
|
-
onBashLongRunning: options.onBashLongRunning
|
|
9436
|
+
onBashLongRunning: options.onBashLongRunning,
|
|
9437
|
+
errorPrefix: options.errorPrefix,
|
|
9438
|
+
logger: options.logger
|
|
9230
9439
|
};
|
|
9231
9440
|
this.configOverrides = configOverrides;
|
|
9232
9441
|
if (Number.isFinite(this.idleTimeoutMs)) {
|
|
@@ -9298,23 +9507,32 @@ class BridgePool {
|
|
|
9298
9507
|
}
|
|
9299
9508
|
async replaceBinary(newPath) {
|
|
9300
9509
|
this.binaryPath = newPath;
|
|
9301
|
-
const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
|
|
9302
9510
|
this.bridges.clear();
|
|
9303
|
-
await Promise.allSettled(shutdowns);
|
|
9304
9511
|
this.log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
|
|
9512
|
+
return newPath;
|
|
9305
9513
|
}
|
|
9306
9514
|
log(message, meta) {
|
|
9307
9515
|
const logger = this.logger ?? getActiveLogger();
|
|
9308
|
-
if (logger)
|
|
9309
|
-
|
|
9310
|
-
|
|
9516
|
+
if (logger) {
|
|
9517
|
+
try {
|
|
9518
|
+
logger.log(message, meta);
|
|
9519
|
+
} catch (err) {
|
|
9520
|
+
console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
9521
|
+
console.error(`[aft-bridge] ${message}`);
|
|
9522
|
+
}
|
|
9523
|
+
} else
|
|
9311
9524
|
log(message, meta);
|
|
9312
9525
|
}
|
|
9313
9526
|
error(message, meta) {
|
|
9314
9527
|
const logger = this.logger ?? getActiveLogger();
|
|
9315
|
-
if (logger)
|
|
9316
|
-
|
|
9317
|
-
|
|
9528
|
+
if (logger) {
|
|
9529
|
+
try {
|
|
9530
|
+
logger.error(message, meta);
|
|
9531
|
+
} catch (err) {
|
|
9532
|
+
console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
9533
|
+
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
9534
|
+
}
|
|
9535
|
+
} else
|
|
9318
9536
|
error(message, meta);
|
|
9319
9537
|
}
|
|
9320
9538
|
setConfigureOverride(key, value) {
|
|
@@ -9341,7 +9559,7 @@ function normalizeKey(projectRoot) {
|
|
|
9341
9559
|
}
|
|
9342
9560
|
// ../aft-bridge/dist/resolver.js
|
|
9343
9561
|
import { execSync, spawnSync } from "child_process";
|
|
9344
|
-
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync } from "fs";
|
|
9562
|
+
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "fs";
|
|
9345
9563
|
import { createRequire } from "module";
|
|
9346
9564
|
import { homedir as homedir4 } from "os";
|
|
9347
9565
|
import { join as join4 } from "path";
|
|
@@ -9352,7 +9570,9 @@ function readBinaryVersion(binaryPath) {
|
|
|
9352
9570
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9353
9571
|
timeout: 5000
|
|
9354
9572
|
});
|
|
9355
|
-
const
|
|
9573
|
+
const stdoutVersion = result.stdout?.trim();
|
|
9574
|
+
const stderrVersion = result.stderr?.trim();
|
|
9575
|
+
const rawVersion = stdoutVersion || stderrVersion;
|
|
9356
9576
|
if (!rawVersion)
|
|
9357
9577
|
return null;
|
|
9358
9578
|
return rawVersion.replace(/^aft\s+/, "");
|
|
@@ -9370,15 +9590,19 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
9370
9590
|
const versionedDir = join4(cacheDir, tag);
|
|
9371
9591
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
9372
9592
|
const cachedPath = join4(versionedDir, `aft${ext}`);
|
|
9373
|
-
if (existsSync3(cachedPath))
|
|
9374
|
-
|
|
9593
|
+
if (existsSync3(cachedPath)) {
|
|
9594
|
+
const cachedVersion = readBinaryVersion(cachedPath);
|
|
9595
|
+
if (cachedVersion === version)
|
|
9596
|
+
return cachedPath;
|
|
9597
|
+
warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
|
|
9598
|
+
}
|
|
9375
9599
|
mkdirSync3(versionedDir, { recursive: true });
|
|
9376
|
-
const tmpPath = `${cachedPath}.tmp`;
|
|
9600
|
+
const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
|
|
9377
9601
|
copyFileSync2(npmBinaryPath, tmpPath);
|
|
9378
9602
|
if (process.platform !== "win32") {
|
|
9379
9603
|
chmodSync3(tmpPath, 493);
|
|
9380
9604
|
}
|
|
9381
|
-
|
|
9605
|
+
renameSync2(tmpPath, cachedPath);
|
|
9382
9606
|
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
9383
9607
|
return cachedPath;
|
|
9384
9608
|
} catch (err) {
|
|
@@ -9386,6 +9610,17 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
9386
9610
|
return null;
|
|
9387
9611
|
}
|
|
9388
9612
|
}
|
|
9613
|
+
function normalizeBareVersion(version) {
|
|
9614
|
+
return version.startsWith("v") ? version.slice(1) : version;
|
|
9615
|
+
}
|
|
9616
|
+
function isExpectedCachedBinary(binaryPath, expectedVersion) {
|
|
9617
|
+
const expected = normalizeBareVersion(expectedVersion);
|
|
9618
|
+
const actual = readBinaryVersion(binaryPath);
|
|
9619
|
+
if (actual === expected)
|
|
9620
|
+
return true;
|
|
9621
|
+
warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
|
|
9622
|
+
return false;
|
|
9623
|
+
}
|
|
9389
9624
|
function platformKey(platform = process.platform, arch = process.arch) {
|
|
9390
9625
|
const archMap = PLATFORM_ARCH_MAP[platform];
|
|
9391
9626
|
if (!archMap) {
|
|
@@ -9410,7 +9645,7 @@ function findBinarySync(expectedVersion) {
|
|
|
9410
9645
|
if (pluginVersion) {
|
|
9411
9646
|
const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
|
|
9412
9647
|
const versionCached = getCachedBinaryPath(tag);
|
|
9413
|
-
if (versionCached)
|
|
9648
|
+
if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
|
|
9414
9649
|
return versionCached;
|
|
9415
9650
|
}
|
|
9416
9651
|
try {
|
|
@@ -9420,10 +9655,12 @@ function findBinarySync(expectedVersion) {
|
|
|
9420
9655
|
const resolved = req.resolve(packageBin);
|
|
9421
9656
|
if (existsSync3(resolved)) {
|
|
9422
9657
|
const npmVersion = readBinaryVersion(resolved);
|
|
9423
|
-
if (
|
|
9658
|
+
if (npmVersion === null) {
|
|
9659
|
+
warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
|
|
9660
|
+
} else if (pluginVersion && npmVersion !== normalizeBareVersion(pluginVersion)) {
|
|
9424
9661
|
warn(`npm platform package binary v${npmVersion} does not match plugin v${pluginVersion}; skipping (continuing to PATH lookup)`);
|
|
9425
9662
|
} else {
|
|
9426
|
-
const copied = copyToVersionedCache(resolved, npmVersion
|
|
9663
|
+
const copied = copyToVersionedCache(resolved, npmVersion);
|
|
9427
9664
|
return copied ?? resolved;
|
|
9428
9665
|
}
|
|
9429
9666
|
}
|
|
@@ -9472,7 +9709,7 @@ async function findBinary(expectedVersion) {
|
|
|
9472
9709
|
`));
|
|
9473
9710
|
}
|
|
9474
9711
|
// ../aft-bridge/dist/url-fetch.js
|
|
9475
|
-
import { createHash as
|
|
9712
|
+
import { createHash as createHash3 } from "crypto";
|
|
9476
9713
|
import { lookup } from "dns/promises";
|
|
9477
9714
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
9478
9715
|
import { isIP } from "net";
|
|
@@ -9486,7 +9723,7 @@ function cacheDir(storageDir) {
|
|
|
9486
9723
|
return join5(storageDir, "url_cache");
|
|
9487
9724
|
}
|
|
9488
9725
|
function hashUrl(url) {
|
|
9489
|
-
return
|
|
9726
|
+
return createHash3("sha256").update(url).digest("hex").slice(0, 16);
|
|
9490
9727
|
}
|
|
9491
9728
|
function metaPath(storageDir, hash) {
|
|
9492
9729
|
return join5(cacheDir(storageDir), `${hash}.meta.json`);
|
|
@@ -9688,6 +9925,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
9688
9925
|
throw new Error(`Only http:// and https:// URLs are supported, got: ${parsed.protocol}`);
|
|
9689
9926
|
}
|
|
9690
9927
|
const allowPrivate = options.allowPrivate === true;
|
|
9928
|
+
await assertPublicUrl(parsed, allowPrivate, options.lookup);
|
|
9691
9929
|
const dir = cacheDir(storageDir);
|
|
9692
9930
|
mkdirSync4(dir, { recursive: true });
|
|
9693
9931
|
const hash = hashUrl(url);
|
|
@@ -9743,8 +9981,8 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
9743
9981
|
const contentFile = contentPath(storageDir, hash, extension);
|
|
9744
9982
|
const tmpContent = `${contentFile}.tmp-${process.pid}`;
|
|
9745
9983
|
writeFileSync2(tmpContent, body);
|
|
9746
|
-
const { renameSync:
|
|
9747
|
-
|
|
9984
|
+
const { renameSync: renameSync3 } = await import("fs");
|
|
9985
|
+
renameSync3(tmpContent, contentFile);
|
|
9748
9986
|
const meta = {
|
|
9749
9987
|
url,
|
|
9750
9988
|
contentType,
|
|
@@ -9753,7 +9991,7 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
9753
9991
|
};
|
|
9754
9992
|
const tmpMeta = `${metaFile}.tmp-${process.pid}`;
|
|
9755
9993
|
writeFileSync2(tmpMeta, JSON.stringify(meta));
|
|
9756
|
-
|
|
9994
|
+
renameSync3(tmpMeta, metaFile);
|
|
9757
9995
|
log(`URL cached (${total} bytes): ${url}`);
|
|
9758
9996
|
return contentFile;
|
|
9759
9997
|
}
|
|
@@ -9907,24 +10145,24 @@ function warn2(message, data) {
|
|
|
9907
10145
|
function error2(message, data) {
|
|
9908
10146
|
write("ERROR", message, data);
|
|
9909
10147
|
}
|
|
9910
|
-
function
|
|
10148
|
+
function sessionLog(sessionId, message, data) {
|
|
9911
10149
|
write("INFO", message, data, sessionId);
|
|
9912
10150
|
}
|
|
9913
|
-
function
|
|
10151
|
+
function sessionWarn(sessionId, message, data) {
|
|
9914
10152
|
write("WARN", message, data, sessionId);
|
|
9915
10153
|
}
|
|
9916
|
-
function
|
|
10154
|
+
function sessionError(sessionId, message, data) {
|
|
9917
10155
|
write("ERROR", message, data, sessionId);
|
|
9918
10156
|
}
|
|
9919
10157
|
var bridgeLogger = {
|
|
9920
10158
|
log(message, meta) {
|
|
9921
|
-
|
|
10159
|
+
sessionLog(meta?.sessionId, message);
|
|
9922
10160
|
},
|
|
9923
10161
|
warn(message, meta) {
|
|
9924
|
-
|
|
10162
|
+
sessionWarn(meta?.sessionId, message);
|
|
9925
10163
|
},
|
|
9926
10164
|
error(message, meta) {
|
|
9927
|
-
|
|
10165
|
+
sessionError(meta?.sessionId, message);
|
|
9928
10166
|
},
|
|
9929
10167
|
getLogFilePath: () => logFile
|
|
9930
10168
|
};
|
|
@@ -10019,6 +10257,7 @@ var sessionBgStates = new Map;
|
|
|
10019
10257
|
var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
|
|
10020
10258
|
var DEBOUNCE_STEP_MS = 200;
|
|
10021
10259
|
var DEBOUNCE_CAP_MS = 1000;
|
|
10260
|
+
var MAX_WAKE_SEND_ATTEMPTS = 5;
|
|
10022
10261
|
var UNKNOWN_COMPLETION_TTL_MS = 5000;
|
|
10023
10262
|
var UNKNOWN_COMPLETION_CAP = 32;
|
|
10024
10263
|
var DEFAULT_SESSION_ID = "__default__";
|
|
@@ -10071,17 +10310,24 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
10071
10310
|
return;
|
|
10072
10311
|
const state = stateFor(drainContext.sessionID);
|
|
10073
10312
|
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
|
|
10074
|
-
|
|
10313
|
+
await drainCompletions(drainContext);
|
|
10314
|
+
if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
|
|
10315
|
+
return;
|
|
10316
|
+
}
|
|
10075
10317
|
}
|
|
10076
|
-
if (state.outstandingTaskIds.size > 0) {
|
|
10318
|
+
if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
|
|
10077
10319
|
await drainCompletions(drainContext);
|
|
10078
10320
|
}
|
|
10079
10321
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
|
|
10080
10322
|
return;
|
|
10323
|
+
const deliveredCompletions = [...state.pendingCompletions];
|
|
10081
10324
|
const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
|
|
10082
10325
|
output.output = appendReminder(output.output ?? "", reminder);
|
|
10083
10326
|
state.pendingCompletions = [];
|
|
10084
10327
|
state.pendingLongRunning = [];
|
|
10328
|
+
state.wakeRetryAttempts = 0;
|
|
10329
|
+
state.wakeHardStopped = false;
|
|
10330
|
+
await ackCompletions(drainContext, deliveredCompletions);
|
|
10085
10331
|
if (state.debounceTimer) {
|
|
10086
10332
|
clearTimeout(state.debounceTimer);
|
|
10087
10333
|
state.debounceTimer = null;
|
|
@@ -10095,12 +10341,12 @@ async function handleIdleBgCompletions(drainContext) {
|
|
|
10095
10341
|
}
|
|
10096
10342
|
async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
10097
10343
|
const state = stateFor(drainContext.sessionID);
|
|
10098
|
-
if (!skipDrain && state.outstandingTaskIds.size > 0) {
|
|
10344
|
+
if (!skipDrain && (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted)) {
|
|
10099
10345
|
await drainCompletions(drainContext);
|
|
10100
10346
|
}
|
|
10101
10347
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
|
|
10102
10348
|
return;
|
|
10103
|
-
scheduleWake(state, async (reminder) => {
|
|
10349
|
+
scheduleWake(state, async (reminder, deliveredCompletions) => {
|
|
10104
10350
|
const client = drainContext.client;
|
|
10105
10351
|
if (typeof client.session?.promptAsync !== "function") {
|
|
10106
10352
|
throw new Error("client.session.promptAsync is unavailable");
|
|
@@ -10124,8 +10370,9 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
10124
10370
|
path: { id: drainContext.sessionID },
|
|
10125
10371
|
body
|
|
10126
10372
|
});
|
|
10127
|
-
|
|
10128
|
-
|
|
10373
|
+
await ackCompletions(drainContext, deliveredCompletions);
|
|
10374
|
+
}, (err, hardStopped) => {
|
|
10375
|
+
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)}`);
|
|
10129
10376
|
});
|
|
10130
10377
|
}
|
|
10131
10378
|
function formatSystemReminder(completions) {
|
|
@@ -10176,19 +10423,40 @@ function extractSessionID(value) {
|
|
|
10176
10423
|
return;
|
|
10177
10424
|
}
|
|
10178
10425
|
async function drainCompletions({ ctx, directory, sessionID }) {
|
|
10426
|
+
const state = stateFor(sessionID);
|
|
10179
10427
|
try {
|
|
10180
10428
|
const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
|
|
10181
10429
|
const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
|
|
10182
10430
|
if (response.success === false) {
|
|
10183
|
-
|
|
10431
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
|
|
10184
10432
|
return;
|
|
10185
10433
|
}
|
|
10186
|
-
|
|
10434
|
+
state.forcedDrainCompleted = true;
|
|
10435
|
+
ingestDrainedBgCompletions(sessionID, response.bg_completions);
|
|
10436
|
+
} catch (err) {
|
|
10437
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
async function ackCompletions({ ctx, directory, sessionID }, completions) {
|
|
10441
|
+
const taskIds = [...new Set(completions.map((completion) => completion.task_id))];
|
|
10442
|
+
if (taskIds.length === 0)
|
|
10443
|
+
return;
|
|
10444
|
+
try {
|
|
10445
|
+
const bridge = ctx.pool.getActiveBridgeForRoot(directory) ?? ctx.pool.getBridge(directory);
|
|
10446
|
+
const response = await bridge.send("bash_ack_completions", {
|
|
10447
|
+
session_id: sessionID,
|
|
10448
|
+
task_ids: taskIds
|
|
10449
|
+
});
|
|
10450
|
+
if (response.success === false) {
|
|
10451
|
+
sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${String(response.message ?? "unknown error")}`);
|
|
10452
|
+
}
|
|
10187
10453
|
} catch (err) {
|
|
10188
|
-
|
|
10454
|
+
sessionWarn(sessionID, `${LOG_PREFIX} ack failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10189
10455
|
}
|
|
10190
10456
|
}
|
|
10191
10457
|
function scheduleWake(state, sendWake, onSendFailure) {
|
|
10458
|
+
if (state.wakeHardStopped)
|
|
10459
|
+
return;
|
|
10192
10460
|
const now = Date.now();
|
|
10193
10461
|
const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
|
|
10194
10462
|
if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
|
|
@@ -10217,13 +10485,22 @@ function scheduleWake(state, sendWake, onSendFailure) {
|
|
|
10217
10485
|
const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
|
|
10218
10486
|
state.pendingCompletions = [];
|
|
10219
10487
|
state.pendingLongRunning = [];
|
|
10220
|
-
sendWake(reminder).then(() => {
|
|
10488
|
+
sendWake(reminder, pending).then(() => {
|
|
10221
10489
|
state.retryDelayMs = null;
|
|
10490
|
+
state.wakeRetryAttempts = 0;
|
|
10491
|
+
state.wakeHardStopped = false;
|
|
10222
10492
|
}).catch((err) => {
|
|
10223
10493
|
state.pendingCompletions = [...pending, ...state.pendingCompletions];
|
|
10224
10494
|
state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
|
|
10495
|
+
state.wakeRetryAttempts += 1;
|
|
10496
|
+
if (state.wakeRetryAttempts >= MAX_WAKE_SEND_ATTEMPTS) {
|
|
10497
|
+
state.retryDelayMs = null;
|
|
10498
|
+
state.wakeHardStopped = true;
|
|
10499
|
+
onSendFailure(err, true);
|
|
10500
|
+
return;
|
|
10501
|
+
}
|
|
10225
10502
|
state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
|
|
10226
|
-
onSendFailure(err);
|
|
10503
|
+
onSendFailure(err, false);
|
|
10227
10504
|
scheduleWake(state, sendWake, onSendFailure);
|
|
10228
10505
|
});
|
|
10229
10506
|
}, delay);
|
|
@@ -10244,6 +10521,9 @@ function stateFor(sessionID) {
|
|
|
10244
10521
|
scheduledFireAt: null,
|
|
10245
10522
|
scheduledCompletionCount: 0,
|
|
10246
10523
|
retryDelayMs: null,
|
|
10524
|
+
wakeRetryAttempts: 0,
|
|
10525
|
+
wakeHardStopped: false,
|
|
10526
|
+
forcedDrainCompleted: false,
|
|
10247
10527
|
unknownCompletions: [],
|
|
10248
10528
|
lastSeenAt: now
|
|
10249
10529
|
};
|
|
@@ -10253,6 +10533,22 @@ function stateFor(sessionID) {
|
|
|
10253
10533
|
}
|
|
10254
10534
|
return state;
|
|
10255
10535
|
}
|
|
10536
|
+
function ingestDrainedBgCompletions(sessionID, completions) {
|
|
10537
|
+
if (!Array.isArray(completions) || completions.length === 0)
|
|
10538
|
+
return [];
|
|
10539
|
+
const state = stateFor(sessionID);
|
|
10540
|
+
const accepted = [];
|
|
10541
|
+
for (const completion of completions) {
|
|
10542
|
+
if (!isBgCompletion(completion))
|
|
10543
|
+
continue;
|
|
10544
|
+
state.outstandingTaskIds.delete(completion.task_id);
|
|
10545
|
+
if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
|
|
10546
|
+
accepted.push(completion);
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
state.pendingCompletions.push(...accepted);
|
|
10550
|
+
return accepted;
|
|
10551
|
+
}
|
|
10256
10552
|
function cleanupIdleSessionStates(now) {
|
|
10257
10553
|
const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
|
|
10258
10554
|
for (const [sessionID, state] of sessionBgStates) {
|
|
@@ -10343,7 +10639,7 @@ function formatDuration(completion) {
|
|
|
10343
10639
|
|
|
10344
10640
|
// src/config.ts
|
|
10345
10641
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
10346
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as
|
|
10642
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
10347
10643
|
import { homedir as homedir5 } from "os";
|
|
10348
10644
|
import { join as join7 } from "path";
|
|
10349
10645
|
|
|
@@ -24122,7 +24418,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
|
|
|
24122
24418
|
${serialized}` : serialized;
|
|
24123
24419
|
tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
24124
24420
|
writeFileSync3(tmpPath, nextContent, "utf-8");
|
|
24125
|
-
|
|
24421
|
+
renameSync3(tmpPath, configPath);
|
|
24126
24422
|
logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
|
|
24127
24423
|
return { migrated: true, oldKeys };
|
|
24128
24424
|
} catch (err) {
|
|
@@ -24357,19 +24653,19 @@ function loadAftConfig(projectDirectory) {
|
|
|
24357
24653
|
}
|
|
24358
24654
|
|
|
24359
24655
|
// src/hooks/auto-update-checker/index.ts
|
|
24360
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as
|
|
24656
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
24361
24657
|
import { dirname as dirname4, join as join11 } from "path";
|
|
24362
24658
|
|
|
24363
24659
|
// src/hooks/auto-update-checker/cache.ts
|
|
24364
24660
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
24365
24661
|
import { spawn as spawn2 } from "child_process";
|
|
24366
|
-
import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as
|
|
24662
|
+
import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
24367
24663
|
import { tmpdir as tmpdir2 } from "os";
|
|
24368
24664
|
import { basename, dirname as dirname3, join as join10 } from "path";
|
|
24369
24665
|
|
|
24370
24666
|
// src/hooks/auto-update-checker/checker.ts
|
|
24371
24667
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
24372
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as
|
|
24668
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
24373
24669
|
import { homedir as homedir7 } from "os";
|
|
24374
24670
|
import { dirname as dirname2, isAbsolute, join as join9, resolve as resolve2 } from "path";
|
|
24375
24671
|
import { fileURLToPath } from "url";
|
|
@@ -24496,7 +24792,7 @@ function getLocalDevPath(directory) {
|
|
|
24496
24792
|
}
|
|
24497
24793
|
function findPackageJsonUp(startPath) {
|
|
24498
24794
|
try {
|
|
24499
|
-
const stat =
|
|
24795
|
+
const stat = statSync3(startPath);
|
|
24500
24796
|
let dir = stat.isDirectory() ? startPath : dirname2(startPath);
|
|
24501
24797
|
for (let i = 0;i < 10; i++) {
|
|
24502
24798
|
const pkgPath = join9(dir, "package.json");
|
|
@@ -24636,19 +24932,19 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
|
24636
24932
|
function restoreAutoUpdateSnapshot(snapshot) {
|
|
24637
24933
|
try {
|
|
24638
24934
|
if (snapshot.packageJson === null)
|
|
24639
|
-
|
|
24935
|
+
rmSync3(snapshot.packageJsonPath, { force: true });
|
|
24640
24936
|
else
|
|
24641
24937
|
writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
|
|
24642
24938
|
if (snapshot.lockfile === null)
|
|
24643
|
-
|
|
24939
|
+
rmSync3(snapshot.lockfilePath, { force: true });
|
|
24644
24940
|
else
|
|
24645
24941
|
writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
|
|
24646
|
-
|
|
24942
|
+
rmSync3(snapshot.packageDir, { recursive: true, force: true });
|
|
24647
24943
|
if (snapshot.stagedPackageDir) {
|
|
24648
24944
|
cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
|
|
24649
24945
|
}
|
|
24650
24946
|
} finally {
|
|
24651
|
-
|
|
24947
|
+
rmSync3(snapshot.tempDir, { recursive: true, force: true });
|
|
24652
24948
|
}
|
|
24653
24949
|
}
|
|
24654
24950
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
@@ -24713,7 +25009,7 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
24713
25009
|
const packageDir = join10(installDir, "node_modules", packageName);
|
|
24714
25010
|
if (!existsSync7(packageDir))
|
|
24715
25011
|
return false;
|
|
24716
|
-
|
|
25012
|
+
rmSync3(packageDir, { recursive: true, force: true });
|
|
24717
25013
|
log2(`[auto-update-checker] Package removed: ${packageDir}`);
|
|
24718
25014
|
return true;
|
|
24719
25015
|
}
|
|
@@ -24798,7 +25094,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24798
25094
|
if (!result && snapshot) {
|
|
24799
25095
|
restoreAutoUpdateSnapshot(snapshot);
|
|
24800
25096
|
} else if (snapshot) {
|
|
24801
|
-
|
|
25097
|
+
rmSync3(snapshot.tempDir, { recursive: true, force: true });
|
|
24802
25098
|
}
|
|
24803
25099
|
return result;
|
|
24804
25100
|
} catch (err) {
|
|
@@ -24882,7 +25178,7 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
24882
25178
|
mkdirSync5(dirname4(file2), { recursive: true });
|
|
24883
25179
|
const tmp = `${file2}.tmp.${process.pid}`;
|
|
24884
25180
|
writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
24885
|
-
|
|
25181
|
+
renameSync4(tmp, file2);
|
|
24886
25182
|
return true;
|
|
24887
25183
|
} catch (err) {
|
|
24888
25184
|
warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
|
|
@@ -24969,17 +25265,17 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
24969
25265
|
|
|
24970
25266
|
// src/lsp-auto-install.ts
|
|
24971
25267
|
import { spawn as spawn3 } from "child_process";
|
|
24972
|
-
import { createHash as
|
|
24973
|
-
import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as
|
|
25268
|
+
import { createHash as createHash4 } from "crypto";
|
|
25269
|
+
import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
|
|
24974
25270
|
import { join as join14 } from "path";
|
|
24975
25271
|
|
|
24976
25272
|
// src/lsp-cache.ts
|
|
24977
25273
|
import {
|
|
24978
|
-
closeSync as
|
|
25274
|
+
closeSync as closeSync3,
|
|
24979
25275
|
mkdirSync as mkdirSync6,
|
|
24980
|
-
openSync as
|
|
25276
|
+
openSync as openSync3,
|
|
24981
25277
|
readFileSync as readFileSync7,
|
|
24982
|
-
statSync as
|
|
25278
|
+
statSync as statSync4,
|
|
24983
25279
|
unlinkSync as unlinkSync5,
|
|
24984
25280
|
writeFileSync as writeFileSync7
|
|
24985
25281
|
} from "fs";
|
|
@@ -25012,7 +25308,7 @@ function lspBinDir(npmPackage) {
|
|
|
25012
25308
|
function isInstalled(npmPackage, binary) {
|
|
25013
25309
|
for (const candidate of lspBinaryCandidates(binary)) {
|
|
25014
25310
|
try {
|
|
25015
|
-
if (
|
|
25311
|
+
if (statSync4(join12(lspBinDir(npmPackage), candidate)).isFile())
|
|
25016
25312
|
return true;
|
|
25017
25313
|
} catch {}
|
|
25018
25314
|
}
|
|
@@ -25040,7 +25336,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
|
25040
25336
|
function readInstalledMetaIn(installDir) {
|
|
25041
25337
|
const path2 = join12(installDir, INSTALLED_META_FILE);
|
|
25042
25338
|
try {
|
|
25043
|
-
if (!
|
|
25339
|
+
if (!statSync4(path2).isFile())
|
|
25044
25340
|
return null;
|
|
25045
25341
|
const raw = readFileSync7(path2, "utf8");
|
|
25046
25342
|
const parsed = JSON.parse(raw);
|
|
@@ -25070,13 +25366,13 @@ function acquireInstallLock(lockKey) {
|
|
|
25070
25366
|
const lock = lockPath(lockKey);
|
|
25071
25367
|
const tryClaim = () => {
|
|
25072
25368
|
try {
|
|
25073
|
-
const fd =
|
|
25369
|
+
const fd = openSync3(lock, "wx");
|
|
25074
25370
|
try {
|
|
25075
25371
|
writeFileSync7(fd, `${process.pid}
|
|
25076
25372
|
${new Date().toISOString()}
|
|
25077
25373
|
`);
|
|
25078
25374
|
} finally {
|
|
25079
|
-
|
|
25375
|
+
closeSync3(fd);
|
|
25080
25376
|
}
|
|
25081
25377
|
return true;
|
|
25082
25378
|
} catch (err) {
|
|
@@ -25097,7 +25393,7 @@ ${new Date().toISOString()}
|
|
|
25097
25393
|
const parsed = Number.parseInt(firstLine, 10);
|
|
25098
25394
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
25099
25395
|
owningPid = parsed;
|
|
25100
|
-
lockMtimeMs =
|
|
25396
|
+
lockMtimeMs = statSync4(lock).mtimeMs;
|
|
25101
25397
|
} catch {
|
|
25102
25398
|
return tryClaim();
|
|
25103
25399
|
}
|
|
@@ -25638,7 +25934,7 @@ function hashInstalledBinary(spec) {
|
|
|
25638
25934
|
let pathToHash = null;
|
|
25639
25935
|
for (const p of candidates) {
|
|
25640
25936
|
try {
|
|
25641
|
-
if (
|
|
25937
|
+
if (statSync5(p).isFile()) {
|
|
25642
25938
|
pathToHash = p;
|
|
25643
25939
|
break;
|
|
25644
25940
|
}
|
|
@@ -25648,7 +25944,7 @@ function hashInstalledBinary(spec) {
|
|
|
25648
25944
|
reject(new Error(`installed binary not found at any of: ${candidates.join(", ")}`));
|
|
25649
25945
|
return;
|
|
25650
25946
|
}
|
|
25651
|
-
const hash2 =
|
|
25947
|
+
const hash2 = createHash4("sha256");
|
|
25652
25948
|
const stream = createReadStream(pathToHash);
|
|
25653
25949
|
stream.on("error", reject);
|
|
25654
25950
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
@@ -25664,14 +25960,14 @@ function installedBinaryPath(spec) {
|
|
|
25664
25960
|
] : [lspBinaryPath(spec.npm, spec.binary)];
|
|
25665
25961
|
for (const candidate of candidates) {
|
|
25666
25962
|
try {
|
|
25667
|
-
if (
|
|
25963
|
+
if (statSync5(candidate).isFile())
|
|
25668
25964
|
return candidate;
|
|
25669
25965
|
} catch {}
|
|
25670
25966
|
}
|
|
25671
25967
|
return null;
|
|
25672
25968
|
}
|
|
25673
25969
|
function sha256OfFileSync(path2) {
|
|
25674
|
-
return
|
|
25970
|
+
return createHash4("sha256").update(readFileSync8(path2)).digest("hex");
|
|
25675
25971
|
}
|
|
25676
25972
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
25677
25973
|
const packageDir = lspPackageDir(spec.npm);
|
|
@@ -25679,8 +25975,8 @@ function quarantineCachedNpmInstall(spec, reason) {
|
|
|
25679
25975
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
25680
25976
|
try {
|
|
25681
25977
|
mkdirSync7(join14(dest, ".."), { recursive: true });
|
|
25682
|
-
|
|
25683
|
-
|
|
25978
|
+
rmSync4(dest, { recursive: true, force: true });
|
|
25979
|
+
renameSync5(packageDir, dest);
|
|
25684
25980
|
} catch (err) {
|
|
25685
25981
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
25686
25982
|
}
|
|
@@ -25756,11 +26052,11 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
25756
26052
|
|
|
25757
26053
|
// src/lsp-github-install.ts
|
|
25758
26054
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
25759
|
-
import { createHash as
|
|
26055
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
25760
26056
|
import {
|
|
25761
26057
|
copyFileSync as copyFileSync3,
|
|
25762
26058
|
createReadStream as createReadStream2,
|
|
25763
|
-
createWriteStream as
|
|
26059
|
+
createWriteStream as createWriteStream3,
|
|
25764
26060
|
existsSync as existsSync10,
|
|
25765
26061
|
lstatSync as lstatSync2,
|
|
25766
26062
|
mkdirSync as mkdirSync8,
|
|
@@ -25768,14 +26064,15 @@ import {
|
|
|
25768
26064
|
readFileSync as readFileSync9,
|
|
25769
26065
|
readlinkSync as readlinkSync2,
|
|
25770
26066
|
realpathSync as realpathSync3,
|
|
25771
|
-
renameSync as
|
|
25772
|
-
rmSync as
|
|
25773
|
-
statSync as
|
|
25774
|
-
unlinkSync as unlinkSync6
|
|
26067
|
+
renameSync as renameSync6,
|
|
26068
|
+
rmSync as rmSync5,
|
|
26069
|
+
statSync as statSync6,
|
|
26070
|
+
unlinkSync as unlinkSync6,
|
|
26071
|
+
writeFileSync as writeFileSync8
|
|
25775
26072
|
} from "fs";
|
|
25776
26073
|
import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
|
|
25777
|
-
import { Readable as
|
|
25778
|
-
import { pipeline as
|
|
26074
|
+
import { Readable as Readable3 } from "stream";
|
|
26075
|
+
import { pipeline as pipeline3 } from "stream/promises";
|
|
25779
26076
|
|
|
25780
26077
|
// src/lsp-github-table.ts
|
|
25781
26078
|
function exe(platform2, name) {
|
|
@@ -25874,6 +26171,7 @@ function ghBinDir(spec) {
|
|
|
25874
26171
|
function ghExtractDir(spec) {
|
|
25875
26172
|
return join15(ghPackageDir(spec), "extracted");
|
|
25876
26173
|
}
|
|
26174
|
+
var INSTALLED_META_FILE2 = ".aft-installed";
|
|
25877
26175
|
function ghBinaryPath(spec, platform2) {
|
|
25878
26176
|
const ext = platform2 === "win32" ? ".exe" : "";
|
|
25879
26177
|
return join15(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
@@ -25881,7 +26179,7 @@ function ghBinaryPath(spec, platform2) {
|
|
|
25881
26179
|
function isGithubInstalled(spec, platform2) {
|
|
25882
26180
|
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
25883
26181
|
try {
|
|
25884
|
-
if (
|
|
26182
|
+
if (statSync6(join15(ghBinDir(spec), candidate)).isFile())
|
|
25885
26183
|
return true;
|
|
25886
26184
|
} catch {}
|
|
25887
26185
|
}
|
|
@@ -25892,11 +26190,45 @@ function ghBinaryCandidates(spec, platform2) {
|
|
|
25892
26190
|
return [spec.binary];
|
|
25893
26191
|
return [spec.binary, `${spec.binary}.cmd`, `${spec.binary}.exe`, `${spec.binary}.bat`];
|
|
25894
26192
|
}
|
|
25895
|
-
|
|
26193
|
+
function readGithubInstalledMetaIn(installDir) {
|
|
26194
|
+
try {
|
|
26195
|
+
const path2 = join15(installDir, INSTALLED_META_FILE2);
|
|
26196
|
+
if (!statSync6(path2).isFile())
|
|
26197
|
+
return null;
|
|
26198
|
+
const parsed = JSON.parse(readFileSync9(path2, "utf8"));
|
|
26199
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
26200
|
+
return null;
|
|
26201
|
+
return {
|
|
26202
|
+
version: parsed.version,
|
|
26203
|
+
installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : "",
|
|
26204
|
+
...typeof parsed.sha256 === "string" && parsed.sha256.length > 0 ? { sha256: parsed.sha256 } : {},
|
|
26205
|
+
...typeof parsed.binarySha256 === "string" && parsed.binarySha256.length > 0 ? { binarySha256: parsed.binarySha256 } : {},
|
|
26206
|
+
...typeof parsed.archiveSha256 === "string" && parsed.archiveSha256.length > 0 ? { archiveSha256: parsed.archiveSha256 } : {}
|
|
26207
|
+
};
|
|
26208
|
+
} catch {
|
|
26209
|
+
return null;
|
|
26210
|
+
}
|
|
26211
|
+
}
|
|
26212
|
+
function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
|
|
26213
|
+
try {
|
|
26214
|
+
mkdirSync8(installDir, { recursive: true });
|
|
26215
|
+
const meta3 = {
|
|
26216
|
+
version: version2,
|
|
26217
|
+
installedAt: new Date().toISOString(),
|
|
26218
|
+
sha256: binarySha256,
|
|
26219
|
+
binarySha256,
|
|
26220
|
+
...archiveSha256 ? { archiveSha256 } : {}
|
|
26221
|
+
};
|
|
26222
|
+
writeFileSync8(join15(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
|
|
26223
|
+
} catch (err) {
|
|
26224
|
+
warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
|
|
26225
|
+
}
|
|
26226
|
+
}
|
|
26227
|
+
var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
|
|
25896
26228
|
var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
|
|
25897
26229
|
function sha256OfFile(path2) {
|
|
25898
26230
|
return new Promise((resolve4, reject) => {
|
|
25899
|
-
const hash2 =
|
|
26231
|
+
const hash2 = createHash5("sha256");
|
|
25900
26232
|
const stream = createReadStream2(path2);
|
|
25901
26233
|
stream.on("error", reject);
|
|
25902
26234
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
@@ -25904,7 +26236,7 @@ function sha256OfFile(path2) {
|
|
|
25904
26236
|
});
|
|
25905
26237
|
}
|
|
25906
26238
|
function sha256OfFileSync2(path2) {
|
|
25907
|
-
return
|
|
26239
|
+
return createHash5("sha256").update(readFileSync9(path2)).digest("hex");
|
|
25908
26240
|
}
|
|
25909
26241
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
25910
26242
|
const candidates = [];
|
|
@@ -26067,8 +26399,8 @@ function assertAllowedDownloadUrl(rawUrl) {
|
|
|
26067
26399
|
return parsed;
|
|
26068
26400
|
}
|
|
26069
26401
|
async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
26070
|
-
if (assetSize !== undefined && assetSize >
|
|
26071
|
-
throw new Error(`asset size ${assetSize} exceeds max ${
|
|
26402
|
+
if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES3) {
|
|
26403
|
+
throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES3} (set lsp.versions to pin a smaller release if this is wrong)`);
|
|
26072
26404
|
}
|
|
26073
26405
|
const timeout = controlledTimeoutSignal(120000, signal);
|
|
26074
26406
|
try {
|
|
@@ -26077,24 +26409,24 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
26077
26409
|
throw new Error(`download failed (${res.status})`);
|
|
26078
26410
|
}
|
|
26079
26411
|
const advertised = Number.parseInt(res.headers.get("content-length") ?? "", 10);
|
|
26080
|
-
if (Number.isFinite(advertised) && advertised >
|
|
26081
|
-
throw new Error(`Content-Length ${advertised} exceeds max ${
|
|
26412
|
+
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
26413
|
+
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
26082
26414
|
}
|
|
26083
26415
|
mkdirSync8(dirname5(destPath), { recursive: true });
|
|
26084
26416
|
let bytesWritten = 0;
|
|
26085
26417
|
const guard = new TransformStream({
|
|
26086
26418
|
transform(chunk, controller) {
|
|
26087
26419
|
bytesWritten += chunk.byteLength;
|
|
26088
|
-
if (bytesWritten >
|
|
26089
|
-
controller.error(new Error(`download exceeded ${
|
|
26420
|
+
if (bytesWritten > MAX_DOWNLOAD_BYTES3) {
|
|
26421
|
+
controller.error(new Error(`download exceeded ${MAX_DOWNLOAD_BYTES3} bytes after streaming (server lied about size or sent unbounded body)`));
|
|
26090
26422
|
return;
|
|
26091
26423
|
}
|
|
26092
26424
|
controller.enqueue(chunk);
|
|
26093
26425
|
}
|
|
26094
26426
|
});
|
|
26095
26427
|
const guarded = res.body.pipeThrough(guard);
|
|
26096
|
-
const nodeStream =
|
|
26097
|
-
await
|
|
26428
|
+
const nodeStream = Readable3.fromWeb(guarded);
|
|
26429
|
+
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
26098
26430
|
} catch (err) {
|
|
26099
26431
|
try {
|
|
26100
26432
|
unlinkSync6(destPath);
|
|
@@ -26174,7 +26506,7 @@ function validateExtraction(stagingRoot) {
|
|
|
26174
26506
|
};
|
|
26175
26507
|
walk(realStagingRoot);
|
|
26176
26508
|
}
|
|
26177
|
-
function
|
|
26509
|
+
function precheckArchiveContents(archivePath, archiveType) {
|
|
26178
26510
|
let totalBytes = 0;
|
|
26179
26511
|
if (archiveType === "zip") {
|
|
26180
26512
|
const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
|
|
@@ -26185,6 +26517,9 @@ function precheckArchiveSize(archivePath, archiveType) {
|
|
|
26185
26517
|
const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
|
|
26186
26518
|
for (const line of out.split(`
|
|
26187
26519
|
`)) {
|
|
26520
|
+
if (line.startsWith("h")) {
|
|
26521
|
+
throw new Error(`archive contains hardlink entry: ${line.trim()}`);
|
|
26522
|
+
}
|
|
26188
26523
|
const parts = line.trim().split(/\s+/);
|
|
26189
26524
|
if (parts.length >= 6) {
|
|
26190
26525
|
const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
|
|
@@ -26201,20 +26536,20 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
|
26201
26536
|
const suffix = randomBytes(8).toString("hex");
|
|
26202
26537
|
const stagingDir = `${destDir}.staging-${suffix}`;
|
|
26203
26538
|
try {
|
|
26204
|
-
|
|
26539
|
+
rmSync5(stagingDir, { recursive: true, force: true });
|
|
26205
26540
|
} catch {}
|
|
26206
26541
|
mkdirSync8(stagingDir, { recursive: true });
|
|
26207
26542
|
try {
|
|
26208
|
-
|
|
26543
|
+
precheckArchiveContents(archivePath, archiveType);
|
|
26209
26544
|
runPlatformExtractor(archivePath, stagingDir, archiveType);
|
|
26210
26545
|
validateExtraction(stagingDir);
|
|
26211
26546
|
try {
|
|
26212
|
-
|
|
26547
|
+
rmSync5(destDir, { recursive: true, force: true });
|
|
26213
26548
|
} catch {}
|
|
26214
|
-
|
|
26549
|
+
renameSync6(stagingDir, destDir);
|
|
26215
26550
|
} catch (err) {
|
|
26216
26551
|
try {
|
|
26217
|
-
|
|
26552
|
+
rmSync5(stagingDir, { recursive: true, force: true });
|
|
26218
26553
|
} catch {}
|
|
26219
26554
|
throw err;
|
|
26220
26555
|
}
|
|
@@ -26225,28 +26560,44 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
26225
26560
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
26226
26561
|
try {
|
|
26227
26562
|
mkdirSync8(dirname5(dest), { recursive: true });
|
|
26228
|
-
|
|
26229
|
-
|
|
26563
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
26564
|
+
renameSync6(packageDir, dest);
|
|
26230
26565
|
} catch (err) {
|
|
26231
26566
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
26232
26567
|
}
|
|
26233
26568
|
}
|
|
26234
26569
|
function validateCachedGithubInstall(spec, platform2) {
|
|
26235
|
-
const
|
|
26570
|
+
const packageDir = ghPackageDir(spec);
|
|
26571
|
+
const meta3 = readGithubInstalledMetaIn(packageDir);
|
|
26236
26572
|
const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
|
|
26237
26573
|
try {
|
|
26238
|
-
return
|
|
26574
|
+
return statSync6(candidate).isFile();
|
|
26239
26575
|
} catch {
|
|
26240
26576
|
return false;
|
|
26241
26577
|
}
|
|
26242
26578
|
});
|
|
26243
|
-
if (!meta3?.
|
|
26579
|
+
if (!meta3?.version || !isSafeVersion(meta3.version) || !binaryPath) {
|
|
26244
26580
|
quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
|
|
26245
26581
|
return false;
|
|
26246
26582
|
}
|
|
26247
26583
|
const currentHash = sha256OfFileSync2(binaryPath);
|
|
26248
|
-
|
|
26249
|
-
|
|
26584
|
+
const recordedBinaryHash = meta3.binarySha256 ?? meta3.sha256;
|
|
26585
|
+
if (recordedBinaryHash && currentHash === recordedBinaryHash) {
|
|
26586
|
+
if (meta3.sha256 !== currentHash || meta3.binarySha256 !== currentHash) {
|
|
26587
|
+
writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.archiveSha256);
|
|
26588
|
+
}
|
|
26589
|
+
return true;
|
|
26590
|
+
}
|
|
26591
|
+
if (meta3.sha256 && !meta3.binarySha256 && !meta3.archiveSha256) {
|
|
26592
|
+
writeGithubInstalledMetaIn(packageDir, meta3.version, currentHash, meta3.sha256);
|
|
26593
|
+
return true;
|
|
26594
|
+
}
|
|
26595
|
+
if (!recordedBinaryHash) {
|
|
26596
|
+
quarantineCachedGithubInstall(spec, "missing binary sha256 metadata");
|
|
26597
|
+
return false;
|
|
26598
|
+
}
|
|
26599
|
+
if (currentHash !== recordedBinaryHash) {
|
|
26600
|
+
quarantineCachedGithubInstall(spec, `recorded ${recordedBinaryHash}, current ${currentHash}`);
|
|
26250
26601
|
return false;
|
|
26251
26602
|
}
|
|
26252
26603
|
return true;
|
|
@@ -26315,10 +26666,11 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
26315
26666
|
return null;
|
|
26316
26667
|
}
|
|
26317
26668
|
log2(`[lsp] ${spec.id} ${tag} sha256=${archiveSha256}`);
|
|
26318
|
-
const previousMeta =
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26669
|
+
const previousMeta = readGithubInstalledMetaIn(ghPackageDir(spec));
|
|
26670
|
+
const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
|
|
26671
|
+
if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
|
|
26672
|
+
if (previousArchiveSha256 !== archiveSha256) {
|
|
26673
|
+
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.`);
|
|
26322
26674
|
try {
|
|
26323
26675
|
unlinkSync6(archivePath);
|
|
26324
26676
|
} catch {}
|
|
@@ -26353,7 +26705,9 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
26353
26705
|
return null;
|
|
26354
26706
|
}
|
|
26355
26707
|
log2(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
|
|
26356
|
-
|
|
26708
|
+
const binarySha256 = await sha256OfFile(targetBinary);
|
|
26709
|
+
log2(`[lsp] ${spec.id} ${tag} binary_sha256=${binarySha256}`);
|
|
26710
|
+
return { archiveSha256, binarySha256 };
|
|
26357
26711
|
}
|
|
26358
26712
|
async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch, signal) {
|
|
26359
26713
|
const outcome = await withInstallLock(spec.githubRepo, async () => {
|
|
@@ -26379,14 +26733,14 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch,
|
|
|
26379
26733
|
log2(`[lsp] reinstalling ${spec.id}@${tag}: no installed-version metadata recorded`);
|
|
26380
26734
|
}
|
|
26381
26735
|
}
|
|
26382
|
-
const
|
|
26736
|
+
const hashes = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
|
|
26383
26737
|
error2(`[lsp] github install ${spec.id} crashed: ${err}`);
|
|
26384
26738
|
return null;
|
|
26385
26739
|
});
|
|
26386
|
-
if (!
|
|
26740
|
+
if (!hashes) {
|
|
26387
26741
|
return { started: true, reason: "install failed (see plugin log)" };
|
|
26388
26742
|
}
|
|
26389
|
-
|
|
26743
|
+
writeGithubInstalledMetaIn(ghPackageDir(spec), tag, hashes.binarySha256, hashes.archiveSha256);
|
|
26390
26744
|
return { started: true };
|
|
26391
26745
|
});
|
|
26392
26746
|
if (outcome === null) {
|
|
@@ -26581,7 +26935,7 @@ function normalizeToolMap(tools) {
|
|
|
26581
26935
|
}
|
|
26582
26936
|
|
|
26583
26937
|
// src/notifications.ts
|
|
26584
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as
|
|
26938
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync7, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
26585
26939
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
26586
26940
|
import { join as join16 } from "path";
|
|
26587
26941
|
function isTuiMode() {
|
|
@@ -26688,7 +27042,7 @@ async function sendIgnoredMessage(client, sessionId, text) {
|
|
|
26688
27042
|
return true;
|
|
26689
27043
|
}
|
|
26690
27044
|
} catch (err) {
|
|
26691
|
-
|
|
27045
|
+
sessionLog(sessionId, `[aft-plugin] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26692
27046
|
}
|
|
26693
27047
|
return false;
|
|
26694
27048
|
}
|
|
@@ -26714,7 +27068,7 @@ async function sendWarning(opts, message) {
|
|
|
26714
27068
|
if (!sessionId)
|
|
26715
27069
|
return;
|
|
26716
27070
|
const text = `${WARNING_MARKER} ${message}`;
|
|
26717
|
-
|
|
27071
|
+
sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
|
|
26718
27072
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
26719
27073
|
}
|
|
26720
27074
|
async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
@@ -26737,13 +27091,13 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
|
26737
27091
|
return;
|
|
26738
27092
|
const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
|
|
26739
27093
|
`);
|
|
26740
|
-
|
|
27094
|
+
sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
|
|
26741
27095
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
26742
27096
|
}
|
|
26743
27097
|
if (storageDir) {
|
|
26744
27098
|
try {
|
|
26745
27099
|
mkdirSync9(storageDir, { recursive: true });
|
|
26746
|
-
|
|
27100
|
+
writeFileSync9(join16(storageDir, "last_announced_version"), version2);
|
|
26747
27101
|
} catch {}
|
|
26748
27102
|
}
|
|
26749
27103
|
}
|
|
@@ -26771,9 +27125,9 @@ function writeWarnedTools(storageDir, warned) {
|
|
|
26771
27125
|
mkdirSync9(storageDir, { recursive: true });
|
|
26772
27126
|
const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
|
|
26773
27127
|
const tmpPath = join16(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
26774
|
-
|
|
27128
|
+
writeFileSync9(tmpPath, `${JSON.stringify(warned, null, 2)}
|
|
26775
27129
|
`);
|
|
26776
|
-
|
|
27130
|
+
renameSync7(tmpPath, warnedToolsPath);
|
|
26777
27131
|
} catch {}
|
|
26778
27132
|
}
|
|
26779
27133
|
async function withWarnedToolsLock(storageDir, fn) {
|
|
@@ -26785,7 +27139,7 @@ async function withWarnedToolsLock(storageDir, fn) {
|
|
|
26785
27139
|
try {
|
|
26786
27140
|
return await fn();
|
|
26787
27141
|
} finally {
|
|
26788
|
-
|
|
27142
|
+
rmSync6(lockDir, { recursive: true, force: true });
|
|
26789
27143
|
}
|
|
26790
27144
|
} catch (err) {
|
|
26791
27145
|
const code = err.code;
|
|
@@ -26886,7 +27240,7 @@ async function cleanupWarnings(opts) {
|
|
|
26886
27240
|
}
|
|
26887
27241
|
if (warningIds.length === 0)
|
|
26888
27242
|
return;
|
|
26889
|
-
|
|
27243
|
+
sessionLog(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
|
|
26890
27244
|
for (const id of warningIds) {
|
|
26891
27245
|
await deleteMessage(effectiveServerUrl, sessionId, id);
|
|
26892
27246
|
}
|
|
@@ -26894,16 +27248,16 @@ async function cleanupWarnings(opts) {
|
|
|
26894
27248
|
|
|
26895
27249
|
// src/shared/rpc-server.ts
|
|
26896
27250
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
26897
|
-
import { mkdirSync as mkdirSync10, renameSync as
|
|
27251
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
26898
27252
|
import { createServer } from "http";
|
|
26899
27253
|
import { dirname as dirname6 } from "path";
|
|
26900
27254
|
|
|
26901
27255
|
// src/shared/rpc-utils.ts
|
|
26902
|
-
import { createHash as
|
|
27256
|
+
import { createHash as createHash6 } from "crypto";
|
|
26903
27257
|
import { join as join17 } from "path";
|
|
26904
27258
|
function projectHash(directory) {
|
|
26905
27259
|
const normalized = directory.replace(/\/+$/, "");
|
|
26906
|
-
return
|
|
27260
|
+
return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
26907
27261
|
}
|
|
26908
27262
|
function rpcPortFilePath(storageDir, directory) {
|
|
26909
27263
|
const hash2 = projectHash(directory);
|
|
@@ -26943,11 +27297,11 @@ class AftRpcServer {
|
|
|
26943
27297
|
const dir = dirname6(this.portFilePath);
|
|
26944
27298
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
26945
27299
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
26946
|
-
|
|
27300
|
+
writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
26947
27301
|
encoding: "utf-8",
|
|
26948
27302
|
mode: 384
|
|
26949
27303
|
});
|
|
26950
|
-
|
|
27304
|
+
renameSync8(tmpPath, this.portFilePath);
|
|
26951
27305
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
26952
27306
|
} catch (err) {
|
|
26953
27307
|
warn2(`Failed to write RPC port file: ${err}`);
|
|
@@ -27044,13 +27398,13 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
27044
27398
|
}
|
|
27045
27399
|
let dir = null;
|
|
27046
27400
|
try {
|
|
27047
|
-
const result = await sessionApi.get({
|
|
27401
|
+
const result = await sessionApi.get({ path: { id: sessionId } });
|
|
27048
27402
|
const session = result?.data ?? result;
|
|
27049
27403
|
if (session && typeof session.directory === "string" && session.directory.length > 0) {
|
|
27050
27404
|
dir = session.directory;
|
|
27051
27405
|
}
|
|
27052
27406
|
} catch (err) {
|
|
27053
|
-
|
|
27407
|
+
sessionWarn(sessionId, `[aft-plugin] session.get lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
27054
27408
|
return null;
|
|
27055
27409
|
}
|
|
27056
27410
|
setCache(sessionId, dir);
|
|
@@ -27234,7 +27588,7 @@ function formatStatusMarkdown(status) {
|
|
|
27234
27588
|
|
|
27235
27589
|
// src/shared/tui-config.ts
|
|
27236
27590
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
27237
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as
|
|
27591
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
27238
27592
|
import { dirname as dirname7, join as join19 } from "path";
|
|
27239
27593
|
|
|
27240
27594
|
// src/shared/opencode-config-dir.ts
|
|
@@ -27291,7 +27645,7 @@ function ensureTuiPluginEntry() {
|
|
|
27291
27645
|
plugins.push(PLUGIN_ENTRY);
|
|
27292
27646
|
config2.plugin = plugins;
|
|
27293
27647
|
mkdirSync11(dirname7(configPath), { recursive: true });
|
|
27294
|
-
|
|
27648
|
+
writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
27295
27649
|
`);
|
|
27296
27650
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
27297
27651
|
return true;
|
|
@@ -28170,7 +28524,7 @@ var CACHE_MAX_ENTRIES2 = 200;
|
|
|
28170
28524
|
var cache2 = new Map;
|
|
28171
28525
|
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
28172
28526
|
if (!sessionId) {
|
|
28173
|
-
|
|
28527
|
+
sessionLog(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
|
|
28174
28528
|
return false;
|
|
28175
28529
|
}
|
|
28176
28530
|
const cached2 = cache2.get(sessionId);
|
|
@@ -28182,11 +28536,11 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
|
28182
28536
|
const c = client;
|
|
28183
28537
|
const sessionApi = c?.session;
|
|
28184
28538
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
28185
|
-
|
|
28539
|
+
sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
|
|
28186
28540
|
setCache2(sessionId, false);
|
|
28187
28541
|
return false;
|
|
28188
28542
|
}
|
|
28189
|
-
|
|
28543
|
+
sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
28190
28544
|
let isSubagent = false;
|
|
28191
28545
|
let parentIdRaw;
|
|
28192
28546
|
try {
|
|
@@ -28196,9 +28550,9 @@ async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
|
28196
28550
|
const session = result?.data ?? result;
|
|
28197
28551
|
parentIdRaw = session?.parentID;
|
|
28198
28552
|
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
28199
|
-
|
|
28553
|
+
sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
|
|
28200
28554
|
} catch (err) {
|
|
28201
|
-
|
|
28555
|
+
sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28202
28556
|
return false;
|
|
28203
28557
|
}
|
|
28204
28558
|
setCache2(sessionId, isSubagent);
|
|
@@ -28266,7 +28620,7 @@ function createBashTool(ctx) {
|
|
|
28266
28620
|
const requestedBackground = args.background === true;
|
|
28267
28621
|
const effectiveBackground = isSubagent ? false : requestedBackground;
|
|
28268
28622
|
if (isSubagent && requestedBackground) {
|
|
28269
|
-
|
|
28623
|
+
sessionLog(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
|
|
28270
28624
|
}
|
|
28271
28625
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
28272
28626
|
const data = await withPermissionLoop(ctx, context, {
|
|
@@ -29040,8 +29394,11 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
29040
29394
|
...op,
|
|
29041
29395
|
file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
|
|
29042
29396
|
}));
|
|
29043
|
-
const
|
|
29044
|
-
|
|
29397
|
+
const response = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
29398
|
+
if (response.success === false) {
|
|
29399
|
+
throw new Error(response.message ?? "transaction failed");
|
|
29400
|
+
}
|
|
29401
|
+
return JSON.stringify(response);
|
|
29045
29402
|
}
|
|
29046
29403
|
const file2 = args.filePath;
|
|
29047
29404
|
if (!file2)
|
|
@@ -29520,6 +29877,9 @@ function createDeleteTool(ctx) {
|
|
|
29520
29877
|
files: absolutePaths,
|
|
29521
29878
|
recursive
|
|
29522
29879
|
});
|
|
29880
|
+
if (response.success === false) {
|
|
29881
|
+
throw new Error(response.message ?? "delete failed");
|
|
29882
|
+
}
|
|
29523
29883
|
const deletedEntries = response.deleted ?? [];
|
|
29524
29884
|
const skipped = response.skipped_files ?? [];
|
|
29525
29885
|
const deleted = deletedEntries.map((entry) => entry.file);
|
|
@@ -29603,7 +29963,7 @@ function formatError2(error50) {
|
|
|
29603
29963
|
}
|
|
29604
29964
|
function aftPrefixedTools(ctx) {
|
|
29605
29965
|
const aftEditTool = createEditTool(ctx, "aft_write");
|
|
29606
|
-
|
|
29966
|
+
const tools = {
|
|
29607
29967
|
aft_read: createReadTool(ctx),
|
|
29608
29968
|
aft_write: createWriteTool(ctx, "aft_edit"),
|
|
29609
29969
|
aft_edit: {
|
|
@@ -29632,8 +29992,11 @@ function aftPrefixedTools(ctx) {
|
|
|
29632
29992
|
create_dirs: normalizedArgs.create_dirs !== false,
|
|
29633
29993
|
diagnostics: true
|
|
29634
29994
|
};
|
|
29635
|
-
const
|
|
29636
|
-
|
|
29995
|
+
const response = await callBridge(ctx, context, "write", writeParams);
|
|
29996
|
+
if (response.success === false) {
|
|
29997
|
+
throw new Error(response.message ?? "write failed");
|
|
29998
|
+
}
|
|
29999
|
+
return JSON.stringify(response);
|
|
29637
30000
|
}
|
|
29638
30001
|
return aftEditTool.execute(normalizedArgs, context);
|
|
29639
30002
|
}
|
|
@@ -29642,6 +30005,16 @@ function aftPrefixedTools(ctx) {
|
|
|
29642
30005
|
aft_delete: createDeleteTool(ctx),
|
|
29643
30006
|
aft_move: createMoveTool(ctx)
|
|
29644
30007
|
};
|
|
30008
|
+
const bashRewrite = ctx.config.experimental?.bash?.rewrite === true;
|
|
30009
|
+
const bashCompress = ctx.config.experimental?.bash?.compress === true;
|
|
30010
|
+
const bashBackground = ctx.config.experimental?.bash?.background === true;
|
|
30011
|
+
const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
|
|
30012
|
+
if (anyBashExperimental) {
|
|
30013
|
+
tools.aft_bash = createBashTool(ctx);
|
|
30014
|
+
tools.bash_status = createBashStatusTool(ctx);
|
|
30015
|
+
tools.bash_kill = createBashKillTool(ctx);
|
|
30016
|
+
}
|
|
30017
|
+
return tools;
|
|
29645
30018
|
}
|
|
29646
30019
|
|
|
29647
30020
|
// src/tools/imports.ts
|
|
@@ -30025,7 +30398,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
|
30025
30398
|
}
|
|
30026
30399
|
return { symbols: hints };
|
|
30027
30400
|
} catch (err) {
|
|
30028
|
-
|
|
30401
|
+
sessionWarn(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
|
|
30029
30402
|
return;
|
|
30030
30403
|
}
|
|
30031
30404
|
}
|
|
@@ -30814,29 +31187,37 @@ ${lines}
|
|
|
30814
31187
|
} catch (err) {
|
|
30815
31188
|
warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
30816
31189
|
}
|
|
30817
|
-
|
|
31190
|
+
const versionUpgradePromises = new Map;
|
|
30818
31191
|
const poolOptions = {
|
|
30819
31192
|
errorPrefix: "[aft-plugin]",
|
|
30820
31193
|
minVersion: PLUGIN_VERSION,
|
|
30821
|
-
onVersionMismatch: (binaryVersion, minVersion) => {
|
|
30822
|
-
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
|
|
30826
|
-
|
|
30827
|
-
|
|
30828
|
-
|
|
30829
|
-
|
|
31194
|
+
onVersionMismatch: async (binaryVersion, minVersion) => {
|
|
31195
|
+
const existing = versionUpgradePromises.get(minVersion);
|
|
31196
|
+
if (existing) {
|
|
31197
|
+
log2(`Version ${binaryVersion} < ${minVersion}; awaiting in-flight compatible binary upgrade`);
|
|
31198
|
+
return existing;
|
|
31199
|
+
}
|
|
31200
|
+
const upgradePromise = (async () => {
|
|
31201
|
+
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
31202
|
+
try {
|
|
31203
|
+
const path7 = await ensureBinary(`v${minVersion}`);
|
|
31204
|
+
if (!path7) {
|
|
31205
|
+
warn2(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
|
|
31206
|
+
return null;
|
|
31207
|
+
}
|
|
30830
31208
|
log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
|
|
30831
|
-
pool.replaceBinary(path7)
|
|
30832
|
-
|
|
30833
|
-
|
|
30834
|
-
}
|
|
30835
|
-
|
|
30836
|
-
|
|
30837
|
-
|
|
30838
|
-
|
|
30839
|
-
|
|
31209
|
+
const replaced = await pool.replaceBinary(path7);
|
|
31210
|
+
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
31211
|
+
return replaced;
|
|
31212
|
+
} catch (err) {
|
|
31213
|
+
error2(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
|
|
31214
|
+
return null;
|
|
31215
|
+
} finally {
|
|
31216
|
+
versionUpgradePromises.delete(minVersion);
|
|
31217
|
+
}
|
|
31218
|
+
})();
|
|
31219
|
+
versionUpgradePromises.set(minVersion, upgradePromise);
|
|
31220
|
+
return upgradePromise;
|
|
30840
31221
|
},
|
|
30841
31222
|
onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
|
|
30842
31223
|
const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
|
|
@@ -30970,7 +31351,7 @@ ${lines}
|
|
|
30970
31351
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
30971
31352
|
try {
|
|
30972
31353
|
mkdirSync12(storageDir, { recursive: true });
|
|
30973
|
-
|
|
31354
|
+
writeFileSync12(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
30974
31355
|
} catch {}
|
|
30975
31356
|
}
|
|
30976
31357
|
return { success: true };
|